MediaWiki:Gadget-Merge.js: Difference between revisions
Jump to navigation
Jump to search
Content deleted Content added
No edit summary |
No edit summary |
||
(2 intermediate revisions by the same user not shown) | |||
Line 2: | Line 2: | ||
* merge.js - Script to merge Wikidata items |
* merge.js - Script to merge Wikidata items |
||
* |
* |
||
* Written by Ebraminio |
* Written by Ebraminio, and Ricordisamoa |
||
* modified by Ricordisamoa |
|||
* |
* |
||
* Released under CC-Zero |
* Released under CC-Zero |
||
Line 35: | Line 34: | ||
'html': 'Error: ' + error |
'html': 'Error: ' + error |
||
})); |
})); |
||
} |
|||
/** |
|||
* Check if user has right to delete an item |
|||
*/ |
|||
function canDelete() { |
|||
return wgUserGroups.indexOf("sysop") !== -1; |
|||
} |
} |
||
Line 109: | Line 115: | ||
token: mw.user.tokens.get('editToken'), |
token: mw.user.tokens.get('editToken'), |
||
summary: summary |
summary: summary |
||
}).always(log).done(callback); |
|||
} |
|||
/** |
|||
* Item deleter |
|||
*/ |
|||
function deleteItem(id, reason, callback) { |
|||
if (id === undefined) { |
|||
displayError("the item's id is not defined"); |
|||
return; |
|||
} |
|||
if (canDelete() === false) { |
|||
displayError("you're not an administrator"); |
|||
return; |
|||
} |
|||
displayProgress('Deleting the item'); |
|||
new mw.Api().post({ |
|||
action: "delete", |
|||
title: id, |
|||
reason: reason, |
|||
token: mw.user.tokens.get('editToken') |
|||
}).always(log).done(callback); |
}).always(log).done(callback); |
||
} |
} |
||
Line 143: | Line 170: | ||
*/ |
*/ |
||
function addClaim(to, claim, callback) { |
function addClaim(to, claim, callback) { |
||
console.log(to, claim, callback); |
|||
new mw.Api().post({ |
new mw.Api().post({ |
||
action: 'wbcreateclaim', |
action: 'wbcreateclaim', |
||
Line 150: | Line 178: | ||
value: JSON.stringify(claim.mainsnak.datavalue.value), |
value: JSON.stringify(claim.mainsnak.datavalue.value), |
||
token: mw.user.tokens.get('editToken') |
token: mw.user.tokens.get('editToken') |
||
}).always(log).done( |
}).always(log).done(function () { |
||
if (claim.references !== undefined) { |
|||
new mw.Api().post({ |
|||
statement: claim.id, |
|||
action: 'wbsetreference', |
|||
snaks: JSON.stringify(claim.references[0].snaks), |
|||
reference: claim.references[0].hash, |
|||
token: mw.user.tokens.get('editToken') |
|||
}).always(log).done(function () { |
|||
callback(); |
|||
⚫ | |||
⚫ | |||
callback(); |
|||
} |
|||
}); |
|||
} |
} |
||
Line 273: | Line 315: | ||
}); |
}); |
||
moveItemContent(x, items.to, function () { |
moveItemContent(x, items.to, function () { |
||
if ($('#merge- |
if (canDelete() && $('#merge-delete')[0].checked === true) { |
||
deleteItem(x.id.toUpperCase(), 'Merged with [[' + items.to.id.toUpperCase() + ']] ([[User:Ricordisamoa/merge.js|merge.js]])', function () { |
|||
counter.step(); |
|||
}); |
|||
⚫ | |||
requestDeletion(x.id.toUpperCase(), function () { |
requestDeletion(x.id.toUpperCase(), function () { |
||
counter.step(); |
counter.step(); |
||
Line 330: | Line 376: | ||
$(this).attr("data-selected-id", ui.item.id); |
$(this).attr("data-selected-id", ui.item.id); |
||
})*/ |
})*/ |
||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
).append( |
).append( |
||
$('< |
$('<div />').append( |
||
' |
$('<input />', { |
||
' |
'id': 'merge-send-to-rfd', |
||
⚫ | |||
⚫ | |||
⚫ | |||
}) |
|||
).append( |
|||
$('<label />', { |
|||
'id': 'merge-send-to-rfd-label', |
|||
'for': 'merge-send-to-rfd', |
|||
'text': 'Request deletion for this item on RfD' |
|||
}) |
|||
) |
|||
).append( |
|||
⚫ | |||
⚫ | |||
'id': 'merge-delete', |
|||
'name': 'merge-delete', |
|||
'type': 'checkbox' |
|||
}) |
|||
).append( |
|||
$('<label />', { |
|||
'id': 'merge-delete-label', |
|||
'for': 'merge-delete', |
|||
'text': 'Try to automatically delete this item after merge (only admins)' |
|||
}) |
|||
) |
|||
) |
) |
||
).dialog({ |
).dialog({ |
Revision as of 19:32, 22 May 2013
/*!
* merge.js - Script to merge Wikidata items
*
* Written by Ebraminio, and Ricordisamoa
*
* Released under CC-Zero
*
*/
//<nowiki>
(function ($, mw) {
'use strict';
var formName = 'merge-form';
/**
* Display progress on form dialog
*/
function displayProgress(message) {
$('#' + formName + ' div').hide();
$('#' + formName).append($('<div />', {
'style': 'text-align:center; margin:3em 0;',
'html': message + '<br/><img src="//upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" />'
}));
}
/**
* Display error on form dialog
*/
function displayError(error) {
$('#' + formName + ' div').hide();
$('#' + formName).append($('<div />', {
'style': 'color: #990000; margin-top: 0.4em;',
'html': 'Error: ' + error
}));
}
/**
* Check if user has right to delete an item
*/
function canDelete() {
return wgUserGroups.indexOf("sysop") !== -1;
}
/**
* Retrieve items by id
*/
function getItems(ids, callback) {
new mw.Api().get({
action: "wbgetentities",
ids: ids.join('|'),
format: "json"
}).done(function (data) {
callback($.map(data.entities, function (x) { return x; }));
});
}
/**
* Check if items can be merged
*/
function detectConflicts(items) {
var all = {},
conflicts = {};
$.each(items, function (i, item) {
if (item.sitelinks !== undefined) {
$.each(item.sitelinks, function (i, x) {
if (all[i] !== undefined) {
if (conflicts[i] === undefined) {
conflicts[i] = [all[i]];
}
conflicts[i].push(item);
}
all[i] = item;
});
}
});
return conflicts;
}
/**
* Sort items by their serialized size. Useful for detecting eligible item to merged into.
* NOT USED CURRENTLY
*/
function sortItemsDesc(items) {
return $.map($.map(items, function (item) {
return {
item: item,
size: JSON.stringify(item).length
};
}).sort(function (x, y) { return y.size - x.size; }), function (item) {
return item.item;
});
}
/**
* Logger function
*/
function log(m) {
console.log(m);
}
/**
* Item editor
*/
function editItem(id, data, summary, callback) {
if (id === undefined) {
displayError("the item's id is not defined");
return;
}
data['id'] = undefined;
new mw.Api().post({
action: "wbeditentity",
id: id,
data: JSON.stringify(data),
token: mw.user.tokens.get('editToken'),
summary: summary
}).always(log).done(callback);
}
/**
* Item deleter
*/
function deleteItem(id, reason, callback) {
if (id === undefined) {
displayError("the item's id is not defined");
return;
}
if (canDelete() === false) {
displayError("you're not an administrator");
return;
}
displayProgress('Deleting the item');
new mw.Api().post({
action: "delete",
title: id,
reason: reason,
token: mw.user.tokens.get('editToken')
}).always(log).done(callback);
}
/**
* Clone just needed things
*/
function cloneCleanItem(item) {
return {
id: item.id,
sitelinks: $.extend({}, item.sitelinks),
labels: $.extend({}, item.labels),
descriptions: $.extend({}, item.descriptions)
};
}
/**
* Check equality of claims
*/
function isEqualClaim(claimFrom, claimTo) {
if (claimFrom.mainsnak === undefined && claimTo.mainsnak === undefined) {
return true; // nothing is available to merge
}
if (claimFrom.mainsnak.datavalue === undefined || claimTo.mainsnak.datavalue === undefined) {
throw new Error("Oh, this is not cool");
}
// this must be refactored
return JSON.stringify(claimFrom.mainsnak.datavalue) === JSON.stringify(claimTo.mainsnak.datavalue);
}
/**
* Add a claim
*/
function addClaim(to, claim, callback) {
console.log(to, claim, callback);
new mw.Api().post({
action: 'wbcreateclaim',
entity: to.id,
property: claim.mainsnak.property,
snaktype: claim.mainsnak.snaktype,
value: JSON.stringify(claim.mainsnak.datavalue.value),
token: mw.user.tokens.get('editToken')
}).always(log).done(function () {
if (claim.references !== undefined) {
new mw.Api().post({
statement: claim.id,
action: 'wbsetreference',
snaks: JSON.stringify(claim.references[0].snaks),
reference: claim.references[0].hash,
token: mw.user.tokens.get('editToken')
}).always(log).done(function () {
callback();
});
} else {
callback();
}
});
}
/**
* Claim moving logic
*/
function moveClaims(from, to, callback) {
var claimsToBeAdd = [],
claims = from.claims;
if (claims !== undefined) {
$.each(claims, function (name) {
$.each(from.claims[name], function (i, claim) {
var available = false;
if (to.claims !== undefined && to.claims[name] !== undefined) {
$.each(to.claims[name], function (j) {
if (claim.mainsnak === undefined || isEqualClaim(claim, to.claims[name][j])) {
available = true;
}
});
}
if (available) {
return;
}
claimsToBeAdd.push(claim);
});
});
}
console.log(claimsToBeAdd);
var counter = new AsyncCountDown(claimsToBeAdd.length, callback); // Counter will fire our callback
$.each(claimsToBeAdd, function (i) {
addClaim(to, claimsToBeAdd[i], function () {
counter.step();
});
});
}
/**
* Abstraction to support multiple ajax calls
*/
function AsyncCountDown(count, callback) {
if (count === 0) {
callback();
return;
}
this.count = count;
this.step = function () {
this.count = this.count - 1;
if (this.count === 0) {
this.end();
}
};
this.end = callback;
}
/**
* Moving logic
*/
function moveItemContent(from, to, callback) {
var newFrom = cloneCleanItem(from), // clone
newTo = cloneCleanItem(to);
$.each(newFrom.sitelinks, function (x) {
newTo.sitelinks[x] = $.extend({}, newFrom.sitelinks[x]);
newFrom.sitelinks[x].title = '';
});
$.each(newFrom.labels, function(x) {
newTo.labels[x] = $.extend({}, newFrom.labels[x]);
newFrom.labels[x].value = '';
});
$.each(newFrom.descriptions, function(x) {
newTo.descriptions[x] = $.extend({}, newFrom.descriptions[x]);
newFrom.descriptions[x].value = '';
});
// It must be done consequently
editItem(newFrom.id, newFrom, 'Removed by merge.js, moving to [[' + newTo.id + ']]', function () {
editItem(newTo.id, newTo, 'Added by merge.js, moving from [[' + newFrom.id + ']]', function () {
moveClaims(from, to, callback); // Original from and to objects, not new cleaned ones
});
});
}
// Copy edited [[MediaWiki:Gadget-RequestDeletion.js]]
function requestDeletion(entity, success, reason) {
displayProgress('Requesting deletion for item');
new mw.Api().post({
'format': 'json',
'action': 'edit',
'title': 'Wikidata:Requests for deletions',
'summary': '/* ' + entity + ' */ requested deletion ([[User:Ebraminio/merge.js|merge.js]])',
'appendtext': '\n\n{{subst:Request for deletion|itemid=' + entity + '|reason=' + reason + '}}' + ' ~~' + '~~',
'token': mw.user.tokens.get('editToken')
}).done(function (data) {
if (data.error && data.error.info) {
displayError(data.error.info);
} else {
success();
}
}).fail(function (data) {
displayError(data);
}).always(log);
}
/**
* Move a batch of items
*/
function batchMover(items, callback) {
displayProgress('Please wait...');
$.each(items.from, function (i, x) {
var sitelinks = x.sitelinks,
count = 0;
if (sitelinks === undefined) {
sitelinks = [];
}
if (items.from !== undefined) {
count = items.from.length;
}
var counter = new AsyncCountDown(items.from.length, function () {
window.location = mw.util.wikiGetlink(items.to.id);
});
moveItemContent(x, items.to, function () {
if (canDelete() && $('#merge-delete')[0].checked === true) {
deleteItem(x.id.toUpperCase(), 'Merged with [[' + items.to.id.toUpperCase() + ']] ([[User:Ricordisamoa/merge.js|merge.js]])', function () {
counter.step();
});
} else if ($('#merge-send-to-rfd')[0].checked === true) {
requestDeletion(x.id.toUpperCase(), function () {
counter.step();
}, 'Merged with [[' + items.to.id.toUpperCase() + ']]');
} else {
counter.step();
}
});
});
}
/**
* Merge button
*/
function merge(itemsName) {
getItems(itemsName, function (items) {
var conflicts = detectConflicts(items),
message;
if ($.map(conflicts, function (x) { return x; }).length === 0) {
// var items = sortItemsDesc(items); not sorted, first item is destination
batchMover({
from: items.slice(1),
to: items[0]
});
} else {
message = $.map(conflicts, function (x, i) {
return '<br />A conflict detected on ' + i.replace(/wiki$/, '') + ':' + $.map(x, function (y, j) {
return ' [[' + x[j].id.toUpperCase() + ']] with [[' + i.replace(/wiki$/, '') + ':' + y.sitelinks[i].title +']]';
}).join(',');
}).join('').replace(/\[\[(.*?)\]\]/g, function (x, y) {
return '<a href="' + mw.util.wikiGetlink(y) + '">' + y + '</a>';
})
displayError(message);
}
});
}
/**
* Dialog creator and launcher
*/
function launchDialog() {
$('<div />', {
'id': formName,
'style': 'position: relative;'
}).append(
$('<div />', {
'style': 'margin-top: 0.4em;',
'html': 'Merge into: '
}).append(
$('<input />', {
'id': 'merge-items',
'style': 'padding: 1px; vertical-align: baseline;'
})/*.entityselector({
url: mw.util.wikiScript('api')
}).on('entityselectorselect', function(e, ui) {
$(this).attr("data-selected-id", ui.item.id);
})*/
).append(
$('<div />').append(
$('<input />', {
'id': 'merge-send-to-rfd',
'name': 'merge-send-to-rfd',
'type': 'checkbox'
})
).append(
$('<label />', {
'id': 'merge-send-to-rfd-label',
'for': 'merge-send-to-rfd',
'text': 'Request deletion for this item on RfD'
})
)
).append(
$('<div />').append(
$('<input />', {
'id': 'merge-delete',
'name': 'merge-delete',
'type': 'checkbox'
})
).append(
$('<label />', {
'id': 'merge-delete-label',
'for': 'merge-delete',
'text': 'Try to automatically delete this item after merge (only admins)'
})
)
)
).dialog({
width: 500,
autoOpen: false,
title: 'Merge Wizard',
modal: true,
buttons: {
"Merge": function () {
merge([$('#merge-items').val()/*.attr("data-selected-id")*/, mw.config.get('wgPageName')]);
}
},
"close": function () {
$(formName).remove();
}
}).dialog('open');
}
// Initialization
if (mw.config.get('wgNamespaceNumber') === 0 && mw.config.get('wgAction') === 'view') {
mw.loader.using('jquery.ui.dialog', function() {
$(function() {
$('#ca-merge').remove();
$(mw.util.addPortletLink('p-cactions', '#', 'Merge it with...', 'ca-merge', 'Merge this item into another and send this to RfD'))
.click(function (event) {
event.preventDefault();
launchDialog();
});
});
});
}
}(jQuery, mediaWiki));