User:SunAfterRain/merge.js
(Redirected from User:Sunny00217/merge.js)
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/*global jQuery, mediaWiki, OO, wikibase*/
/*!
* https://www.wikidata.org/w/index.php?title=MediaWiki:Gadget-Merge.js&oldid=1042304347
* merge.js - Script to merge Wikidata items
* @authors User:Ebrahim, User:Ricordisamoa, User:Fomafix, User:Bene*, User:Petr Matas, User:Matěj Suchánek
* @license CC-Zero
*/
// See also: MediaWiki:Gadget-EmptyDetect.js and MediaWiki:Gadget-RfDHelper.js
//mw.loader.load('//zh.wikipedia.org/w/load.php?modules=ext.gadget.site-lib');
function wgUXS (wg, hans, hant, cn, tw, hk, sg, zh, mo, my) {
var ret = {
'zh': zh || hans || hant || cn || tw || hk || sg || mo || my,
'zh-hans': hans || cn || sg || my,
'zh-hant': hant || tw || hk || mo,
'zh-cn': cn || hans || sg || my,
'zh-sg': sg || hans || cn || my,
'zh-tw': tw || hant || hk || mo,
'zh-hk': hk || hant || mo || tw,
'zh-mo': mo || hant || hk || tw
};
return ret[wg] || zh || hans || hant || cn || tw || hk || sg || mo || my; //保證每一語言有值
}
function wgULS (hans, hant, cn, tw, hk, sg, zh, mo, my) {
return wgUXS(mw.config.get('wgUserLanguage'), hans, hant, cn, tw, hk, sg, zh, mo, my);
}
(function ($, mw, OO) {
'use strict';
var entityId = mw.config.get('wbEntityId'), api = new mw.Api(wgULS('',''));
var messages = {
conflictMessage : wgULS('存在跨语言冲突:','存在跨語言衝突:'),
conflictWithMessage : wgULS('和','和'),
createRedirect : wgULS('创建重定向','建立重定向'),
creatingRedirect : wgULS('创建重定向中......','建立重定向中......'),
errorWhile : wgULS('“$1”时出错:','「$1」時出錯:'),
invalidInput : wgULS('目前只有一个Qid是有效的','目前只有一個Qid是有效的'),
lowestEntityId : wgULS('始终合并到较旧的实体(取消选中以合并到“要和此项合并的”实体)','始終合併到較舊的實體(取消選中以合併到「要和此項合併的數據項」實體)'),
merge : wgULS('合并','合併'),
mergePendingNotification : wgULS('Merge.js已经运行,请前往其他需要合并的项。','Merge.js已經運行,請前往其他需要合併的項。'),
mergeProcess : wgULS('开始合并','開始合併'),
mergeSummary : wgULS('将以下文本添加到自动生成的编辑摘要中:','將以下文本添加到自動生成的編輯摘要中:'),
mergeThisEntity : wgULS('合并此项','合併此項'),
mergeWithInput : wgULS('要和此项合并的数据项:','要和此項合併的數據項:'),
mergeWithProgress : wgULS('合并','合併'),
mergeWizard : wgULS('合并数据项','合併數據項'),
pleaseWait : wgULS('请稍候......','請稍後......'),
postpone : wgULS('和其他项合并','和其他項合併'),
postponeTitle : wgULS('储存此项编号以和其他项合并','儲存此項編號以和其他項合併'),
reportError : wgULS('请将上述错误报告到[[这里]],包括合并的来源及目的地','請將上述錯誤報告到[[這裡]],包括合併的來源及目的地'),
selectForMerging : wgULS('选择合并项','選擇合併項'),
selectForMergingTitle : wgULS('记住这个项目是要合并的两个项目中的第二个项目。','記住這個項目是要合併的兩個計畫中的第二個項目。'),
unwatchOption : wgULS('从监视列表移除重复项(若可能)','從監視列表移除重複項(若可能)'),
unwatching : wgULS('正在从监视列表移除重复项......','正在從監視列表移除重複項......'),
loadMergeDestination: wgULS('合并成功时加载目标项','合併成功時載入目標項'),
loadingMergeDestination: wgULS('正在合并中...','正在合併中...'),
};
/**
* Retrieve items by id
*/
function getItems(ids) {
return api.get({
action: 'wbgetentities',
ids: ids.join('|')
}).then(function (data) {
return Object.keys(data.entities).map(function (x) { return data.entities[x]; });
});
}
/**
* Set a Storage to postpone merge and deletion
*/
function mergePending(id) {
mw.storage.set('merge-pending-id', id);
mw.notify($.parseHTML(messages.mergePendingNotification));
}
/**
* ...and reset this Storage
*/
function removePending() {
mw.storage.remove('merge-pending-id');
}
/**
* Check if items can be merged
*/
function detectConflicts(items) {
var all = {},
conflicts = {};
items.forEach(function (item) {
if (!item.sitelinks) { return; }
Object.keys(item.sitelinks).forEach(function (dbName) {
if (all[dbName] && all[dbName].sitelinks[dbName].title !== item.sitelinks[dbName].title) {
if (!conflicts[dbName]) {
conflicts[dbName] = [all[dbName]];
}
conflicts[dbName].push(item);
}
all[dbName] = item;
});
});
return conflicts;
}
/**
* Create a redirect
*/
function createRedirect(fromId, toId) {
// ugly hack, we have to clear the entity first before creating the redirect...
return api.postWithEditToken({
action: 'wbeditentity',
id: fromId,
clear: true,
summary: 'Clearing item to prepare for redirect',
data: '{}'
}).then(function () {
return api.postWithEditToken({
action: 'wbcreateredirect',
from: fromId,
to: toId
});
});
}
/**
* Moving logic
*/
function mergeApi(from, to, mergeSummary) {
var data;
if (from.indexOf('L') === 0) {
data = {
action: 'wblmergelexemes',
source: from,
target: to,
};
} else {
data = {
action: 'wbmergeitems',
fromid: from,
toid: to,
ignoreconflicts: 'description', // ignore descriptions conflicts as old version of merge did
};
}
data.summary = '[[User:Sunny00217/merge.js]] ' + mergeSummary;
return api.postWithEditToken(data);
}
/**
* @class Merger
* @mixins OO.EventEmitter
*
* @constructor
*/
function Merger(mergeItems, mergeSummary, alwaysLowestId, unwatch, mergeCreateRedirect, loadMergeDestination) {
OO.EventEmitter.call(this);
this.mergeItems = mergeItems;
this.mergeSummary = mergeSummary;
if (/^\w/.test(this.mergeSummary)) {
this.mergeSummary = ' ' + this.mergeSummary;
}
this.alwaysLowestId = alwaysLowestId;
this.unwatch = unwatch;
this.mergeCreateRedirect = mergeCreateRedirect;
this.loadMergeDestination = loadMergeDestination;
}
OO.mixinClass(Merger, OO.EventEmitter);
/**
* Merge process
*/
Merger.prototype.merger = function (from, to) {
var self = this;
self.emit('progress', messages.mergeWithInput + ' ' + to);
var redirected;
var deferred = mergeApi(from, to, self.mergeSummary)
.then(function (data) {
redirected = data.redirected;
return $.Deferred().resolve();
});
if (self.unwatch) {
deferred = deferred.then(function () {
self.emit('progress', messages.unwatching);
return api.unwatch(from);
});
}
if (self.mergeCreateRedirect) {
deferred = deferred.then(function () {
if (redirected) { // don't create redirect if already redirected
return $.Deferred().resolve();
}
self.emit('progress', messages.creatingRedirect);
return createRedirect(from, to);
});
}
deferred.then(function () {
if (self.loadMergeDestination) {
self.emit('progress', messages.loadingMergeDestination);
var target = mw.config.get('wgPageName').replace(from, to);
// Purge (via API), then reload.
// XXX: Do we even need to purge? Why?
api.post({
action: 'purge',
titles: target
}).then(function () {
window.location = mw.util.getUrl(target);
});
} else {
self.emit('success');
}
}, function (code, result) {
self.emit('error', result.error.extradata[0] || result.error.info);
});
};
/**
* Merge button action, pre-merge checks
*/
Merger.prototype.merge = function () {
var self = this,
itemsNames = [$.trim(self.mergeItems).toUpperCase(), entityId],
isAllQ = itemsNames.every(function (x) { return /^Q\d*$/i.test(x); }),
isAllL = itemsNames.every(function (x) { return /^L\d*$/i.test(x); });
if (!(isAllQ || isAllL)) {
$('#merge-input-validation-message').text(messages.invalidInput);
return;
}
self.emit('progress', messages.pleaseWait);
getItems(itemsNames).then(function (items) {
// duplicate item if just an item is returned
// if item was being merged to itself this could conflict error that also useful for debugging conflict detector
if (items.length === 1) {
items = items.concat(items);
}
var conflicts = detectConflicts(items),
message;
if (Object.keys(conflicts).length === 0) {
if (self.alwaysLowestId) {
items.sort(function (x, y) { return +x.id.replace(/^[QL]/i, '') - y.id.replace(/^[QL]/i, ''); }); // sort by Qid _only_if_specified_
}
self.merger(items[1].id, items[0].id);
} else {
message = Object.keys(conflicts).map(function (i) {
var x = conflicts[i];
return '<br>' + messages.conflictMessage + i + ':' + x.map(function (y, j) {
return ' [[' + x[j].id + ']] ' + messages.conflictWithMessage +
' [[' + i + ':' + y.sitelinks[i].title + ']]';
}).join(',');
}).join('').replace(/\[\[([^\]\:]*?)\:([^\]]*?)\]\]/g, function (x, y, z) {
return mw.html.element( 'a', { href: wikibase.sites.getSite(y).getUrlTo(z) }, y + ':' + z );
});
self.emit('error', message, true);
}
});
};
/**
* @class MergeDialog
* @extends OO.ui.ProcessDialog
*
* @constructor
* @param {Object} config Configuration options
* @cfg {string} entityId Entity ID
*/
function MergeDialog(config) {
MergeDialog.parent.call(this, config);
this.entityId = config.entityId;
}
OO.inheritClass(MergeDialog, OO.ui.ProcessDialog);
MergeDialog.static.name = 'mergeDialog';
MergeDialog.static.title = messages.mergeWizard;
MergeDialog.static.size = 'medium';
MergeDialog.static.actions = [
{
action: 'postpone',
label: messages.postpone,
title: messages.postponeTitle,
flags: 'progressive'
},
{
action: 'merge',
label: messages.merge,
title: messages.mergeProcess,
flags: ['primary', 'constructive']
},
{
action: 'cancel',
label: mw.msg('ooui-dialog-message-reject'),
flags: 'safe'
}
];
/**
* @inheritdoc
*/
MergeDialog.prototype.initialize = function () {
MergeDialog.parent.prototype.initialize.apply(this, arguments);
var fieldset = new OO.ui.FieldsetLayout({});
this.entitySelector = new OO.ui.TextInputWidget({
value: this.entityId
});
fieldset.addItems([
new OO.ui.FieldLayout(
this.entitySelector,
{
align: 'left',
label: messages.mergeWithInput
}
)
]);
fieldset.$element.append($('<span>', {
id: 'merge-input-validation-message',
style: 'color: red;'
}));
this.mergeSummary = new OO.ui.TextInputWidget({});
fieldset.addItems([
new OO.ui.FieldLayout(
this.mergeSummary,
{
align: 'left',
label: messages.mergeSummary
}
)
]);
this.mergeAlwaysLowestId = new OO.ui.CheckboxInputWidget({
selected: true
});
fieldset.addItems([
new OO.ui.FieldLayout(
this.mergeAlwaysLowestId,
{
align: 'inline',
label: messages.lowestEntityId
}
)
]);
this.mergeCreateRedirect = new OO.ui.CheckboxInputWidget({
selected: true,
disabled: true
});
fieldset.addItems([
new OO.ui.FieldLayout(
this.mergeCreateRedirect,
{
align: 'inline',
label: messages.createRedirect
}
)
]);
this.mergeUnwatch = new OO.ui.CheckboxInputWidget({
selected: mw.storage.get('merge-unwatch') === 'true'
});
fieldset.addItems([
new OO.ui.FieldLayout(
this.mergeUnwatch,
{
align: 'inline',
label: messages.unwatchOption
}
)
]);
this.loadMergeDestination = new OO.ui.CheckboxInputWidget({
selected: mw.storage.get('merge-load-destination') !== 'false'
});
fieldset.addItems([
new OO.ui.FieldLayout(
this.loadMergeDestination,
{
align: 'inline',
label: messages.loadMergeDestination
}
)
]);
var content = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
content.$element.append(fieldset.$element);
this.$body.append(content.$element);
var self = this;
this.actions.once('add', function () {
self.actions.setAbilities({ postpone: self.entityId === '' });
});
this.$element.prop('lang', $('html').prop('lang'));
};
/**
* @inheritdoc
*/
MergeDialog.prototype.getReadyProcess = function (data) {
return MergeDialog.parent.prototype.getReadyProcess.call(this, data)
.next(function () {
// focus "Merge with" field:
// https://www.wikidata.org/wiki/?oldid=333747825#Request:_Improvements_for_keyboard_navigation
this.entitySelector.focus();
}, this);
};
/**
* Save options in storage
*/
MergeDialog.prototype.saveOptions = function () {
mw.storage.set('merge-always-lowest-id', this.mergeAlwaysLowestId.isSelected().toString());
mw.storage.set('merge-unwatch', this.mergeUnwatch.isSelected().toString());
mw.storage.set('merge-create-redirect', this.mergeCreateRedirect.isSelected().toString());
mw.storage.set('merge-load-destination', this.loadMergeDestination.isSelected().toString());
};
MergeDialog.prototype.merge = function () {
var self = this;
this.saveOptions();
removePending();
var merger = new Merger(
this.entitySelector.getValue(),
this.mergeSummary.getValue(),
this.mergeAlwaysLowestId.isSelected(),
this.mergeUnwatch.isSelected(),
this.mergeCreateRedirect.isSelected(),
this.loadMergeDestination.isSelected()
);
merger.on('progress', function () {
self.displayProgress.apply(self, arguments);
});
merger.on('error', function () {
self.displayError.apply(self, arguments);
});
merger.on('success', function () {
self.close();
$('#ca-merge-queue-process, #ca-merge, #ca-merge-select').remove();
});
merger.merge();
};
MergeDialog.prototype.postpone = function () {
this.saveOptions();
mergePending(entityId);
this.close();
};
/**
* @inheritdoc
*/
MergeDialog.prototype.getActionProcess = function (action) {
if (action === 'merge') {
return new OO.ui.Process(this.merge, this);
}
if (action === 'postpone') {
return new OO.ui.Process(this.postpone, this);
}
if (action === 'cancel') {
return new OO.ui.Process(this.close, this);
}
return MergeDialog.parent.prototype.getActionProcess.call(this, action);
};
/**
* Display progress on form dialog
*/
MergeDialog.prototype.displayProgress = function (message) {
if (this.$progressMessage) {
this.$progressMessage.text(message);
this.updateSize();
return;
}
this.$body.children().hide();
this.actions.forEach(null, function (action) {
action.setDisabled(true); // disable buttons
});
this.$progressMessage = $('<span>').text(message);
this.pushPending();
$('<div>').css({
'text-align': 'center',
'margin': '3em 0',
'font-size': '120%'
}).append(
this.$progressMessage
).appendTo(this.$body);
this.updateSize();
};
/**
* Display error on form dialog
*/
MergeDialog.prototype.displayError = function (error, hideReportLink) {
var reportLink;
this.$body.children().hide();
while (this.isPending()) {
this.popPending();
}
this.actions.forEach(null, function (action) {
// reenable 'cancel' button, disable all other buttons
// https://www.wikidata.org/wiki/?oldid=323115101#Close.2FCancel
action.setDisabled(action.getAction() !== 'cancel');
});
if (hideReportLink === true) {
reportLink = '';
} else {
reportLink = '<p>' + messages.reportError.replace(/\[\[(.*)\]\]/, '<a href="//www.wikidata.org/w/index.php?title=MediaWiki_talk:Gadget-Merge.js&action=edit§ion=new" target="_blank">$1</a>') + '</p>';
}
this.$body.append($('<div>', {
style: 'color: #990000; margin-top: 0.4em;',
html: '<p>' + messages.errorWhile.replace(/\$1/, this.$progressMessage.text()) + ' ' + error + '</p>' + reportLink
}));
this.updateSize();
};
/**
* Dialog creator and launcher
*/
function launchDialog(id) {
if (typeof id !== 'string') {
id = '';
}
var dialog = new MergeDialog({
entityId: id
});
var windowManager = new OO.ui.WindowManager();
$('body').append(windowManager.$element);
windowManager.addWindows([dialog]);
windowManager.openWindow(dialog);
}
// Initialization
if (entityId !== null &&
[0, 146].indexOf(mw.config.get('wgNamespaceNumber')) !== -1 &&
mw.config.get('wgAction') === 'view') {
$(window).on('focus storage', function () {
$('#ca-merge-queue-process').remove();
if (mw.storage.get('merge-pending-id') !== null &&
mw.storage.get('merge-pending-id') !== '' &&
mw.storage.get('merge-pending-id') !== entityId) {
$('#p-views ul')[$(document).prop('dir') === 'rtl' ? 'append' : 'prepend']($('<li>', {
id: 'ca-merge-queue-process'
}).append($('<a>', {
href: '#',
title: 'process the postponed merge'
}).append($('<img>', {
src: '//upload.wikimedia.org/wikipedia/commons/thumb/1/10/Pictogram_voting_merge.svg/26px-Pictogram_voting_merge.svg.png',
alt: 'merge icon'
}))).click(function (event) {
event.preventDefault();
launchDialog(mw.storage.get('merge-pending-id'));
}));
}
});
$(function () {
$('#ca-merge-queue-process, #ca-merge, #ca-merge-select').remove();
$(mw.util.addPortletLink(
'p-cactions',
'#',
messages.mergeWithProgress,
'ca-merge',
messages.mergeThisEntity
)).click(function (event) {
event.preventDefault();
launchDialog();
});
$(mw.util.addPortletLink(
'p-cactions',
'#',
messages.selectForMerging,
'ca-merge-select',
messages.selectForMergingTitle
)).click(function (event) {
event.preventDefault();
mergePending(entityId);
});
});
}
// Export section
// currently just for [[MediaWiki:Gadget-EmptyDetect.js]], just launchDialog is exposed
window.mergeTool = {
launchDialog: launchDialog
};
}(jQuery, mediaWiki, OO));