MediaWiki:Gadget-page-contributors.js
Fuomáš: Maŋŋel go almmuhat, soaitá leat dárbbašlaš sihkkut neahttalohkkii gaskaráju vai oainnat rievdadusaid.
- Firefox / Safari: Doala Shift dan botta go deattát Reload, dahje deaddil Ctrl-F5 dahje Ctrl-R (⌘-R Mac'as)
- Google Chrome: Deaddil Ctrl-Shift-R (⌘-Shift-R Mac'as)
- Internet Explorer / Edge: Doala Ctrl dan botta go deattát Álggat ođđasit, dahje deaddil Ctrl-F5
- Opera: deaddil Ctrl-F5.
(function(mw, $) {
var i18nData = {
'en': {
'contributors': 'Contributors',
'description': 'Analyze how much each have contributed to the page',
'in-progress': 'Already in progress!',
'box-title': 'Contributors at this page',
'box-warning': 'Calculation of contributors is a heavy calculation which will download a lot of data. This can make your browser non-responsive,'
+ ' or your computer may stop completly. If you have a limited download capasity you may consider alternatives.',
'execute': 'Compute',
'cancel': 'Cancel',
'full-success': 'Success',
'partial-success': 'Success, reported: ',
'partial-error': 'Error, reported: ',
'loaded-revisions': ' revisions loaded…',
'all-loaded': 'All revisions loaded…',
'result-fnv': 'Calculate FNV-digests…',
'result-calculate-cosine': 'Calculate cosine measure…',
'result-adjust-cosine': 'Adjust cosine measure…',
'result-accumulate-cosine': 'Accumulate cosine measure…',
'result-contributors': 'Sort out main contributors…',
'result-presentation': 'Create presentation…',
'result-ingres': 'This is a simplified calculation of the individual contributors part of the overall text.'
+ ' It is used $num$ revisions in the analysis of total $tot$ loaded.',
'result-hidden': ' Among those there are $hidden$ hidden(e) revisions.',
'result-other': ' It is detected $shunted$ identical revisions, and it is searched $similar$ times after previous similar revisions due to large changes.',
'result-none': ' None revisions are left out.',
'entry-norm': '<a href="$uname$">$name$</a> (<a href="$tname$">talk</a> | <a href="$cname$">bidrag</a>) has contributed $amouth$% of the article',
'entry-norm': '<a href="$uname$">$name$</a> (<a href="$tname$">talk</a> | <a href="$cname$">bidrag</a>) has contributed $amouth$% of the article and is creator',
'entry-other': 'Other contributors have made $amouth$% of the article',
'additional-help': 'See <a href="/wiki/Hjelp:Forfattere_av_sider">help page</a> for additional explanation.'
},
'nb': {
'contributors': 'Bidragsytere',
'description': 'Analyserer hvor mye de enkelte har bidratt på siden',
'in-progress': 'Allerede under utføring!',
'box-title': 'Bidragsytere på siden',
'box-warning': 'Beregning av bidragsytere er en tung beregning som vil laste ned mye data. Dette kan gjøre at nettleseren slutter å reagere, eller'
+ ' at maskinen stopper helt opp. Hvis du har en begrenset mulighet til nedlasting så bør du vurdere andre alternativer.',
'execute': 'Beregn',
'cancel': 'Avbryt',
'full-success': 'Suksess',
'partial-success': 'Suksess, rapporterte: ',
'partial-error': 'Feil, rapporterte: ',
'loaded-revisions': ' revisjoner lastet…',
'all-loaded': 'Alle revisjoner lastet…',
'result-calculate-cosine': 'Beregn måltall for cosinus…',
'result-adjust-cosine': 'Juster måltall for cosinus…',
'result-accumulate-cosine': 'Summer måltall for cosinus…',
'result-contributors': 'Sorter ut største bidragsytere…',
'result-presentation': 'Lag presentasjon…',
'result-ingres': 'Dette er en forenklet beregning av de enkelte brukeres andel av den totale tekstmassen.'
+ ' Det er brukt $num$ revisjoner i analysen av de totalt $tot$ som ble lastet.',
'result-hidden': ' Blant disse er det $hidden$ skjult(e) revisjoner.',
'result-other': ' Det er påvist $shunted$ identiske revisjoner, og søkt $similar$ ganger etter forutgående lignende revisjoner på grunn av store endringer.',
'result-none': ' Ingen revisjoner er utelatt.',
'entry-norm': '<a href="$uname$">$name$</a> (<a href="$tname$">diskusjon</a> | <a href="$cname$">bidrag</a>) har bearbeidet $amouth$% av artikkelen',
'entry-main': '<a href="$uname$">$name$</a> (<a href="$tname$">diskusjon</a> | <a href="$cname$">bidrag</a>) har bearbeidet $amouth$% av artikkelen og er artikkeloppretter',
'entry-other': 'Andre bidragsytere har bearbeidet $amouth$% av artikkelen',
'additional-help': 'Se <a href="/wiki/Hjelp:Forfattere_av_sider">hjelpesiden</a> for ytterligere forklaring.'
},
};
i18nData.no = i18nData.nb;
var parts = mw.user.options.get( 'language' ).split("-");
if( i18nData[parts[0]] ) {
var lang = parts[0];
}
else {
var lang = 'en';
}
function i18n( key ) {
if( i18nData[ lang ][ key ] ) {
return i18nData[ lang ][ key ];
}
else {
return i18nData[ 'en' ][ key ];
}
}
var query = {
action: 'query',
prop: 'revisions',
titles: mw.config.get('wgPageName'),
rvlimit: 10,
rvprop: 'ids|timestamp|user|size|content',
format: 'json'
};
var revisions = [];
var queryContinue = null;
var str = null;
var hidden = 0;
function toHex(n) {
if (n < 0) n = 0xFFFFFFFF + n + 1;
return n.toString(16);
};
function fnv(str) {
// this is an implementation of the Fowler-Noll-Vo hash algorithm
var hash = 2166136261; // the 32 bit offset
for (var i = 0, l = str.length; i < l; i++) {
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
hash = hash ^ str.charCodeAt(i);
}
return hash & 0x0ffffffff;
};
// permutations for the lsh algorithm
var perm = [
1, 14, 110, 25, 97, 174, 132, 119, 138, 170, 125, 118, 27, 233, 140, 51,
87, 197, 177, 107, 234, 169, 56, 68, 30, 7, 173, 73, 188, 40, 36, 65,
49, 213, 104, 190, 57, 211, 148, 223, 48, 115, 15, 2, 67, 186, 210, 28,
12, 181, 103, 70, 22, 58, 75, 78, 183, 167, 238, 157, 124, 147, 172, 144,
176, 161, 141, 86, 60, 66, 128, 83, 156, 241, 79, 46, 168, 198, 41, 254,
178, 85, 253, 237, 250, 154, 133, 88, 35, 206, 95, 116, 252, 192, 54, 221,
102, 218, 255, 240, 82, 106, 158, 201, 61, 3, 89, 9, 42, 155, 159, 93,
166, 80, 50, 34, 175, 195, 100, 99, 26, 150, 16, 145, 4, 33, 8, 189,
121, 64, 77, 72, 208, 245, 130, 122, 143, 55, 105, 134, 29, 164, 185, 194,
193, 239, 101, 242, 5, 171, 126, 11, 74, 59, 137, 228, 108, 191, 232, 139,
6, 24, 81, 20, 127, 17, 91, 92, 251, 151, 225, 207, 21, 98, 113, 112,
84, 226, 18, 214, 199, 187, 13, 32, 94, 220, 224, 212, 247, 204, 196, 43,
249, 236, 45, 244, 111, 182, 153, 136, 129, 90, 217, 202, 19, 165, 231, 71,
230, 142, 96, 227, 62, 179, 246, 114, 162, 53, 160, 215, 205, 180, 47, 109,
44, 38, 31, 149, 135, 0, 216, 52, 63, 23, 37, 69, 39, 117, 146, 184,
163, 200, 222, 235, 248, 243, 219, 10, 152, 131, 123, 229, 203, 76, 120, 209 ];
function lsh(str) {
// this is an implementation of a lsh algorithm, loosley similar to nilsimsa
var arr = Array();
for (var i = 0; i <= 0x0ff; i++) {
arr[i] = 0;
}
for (var i = 0, l = str.length-2; i < l; i++) {
// first we do a Pearson hash of trigram from the text
var hash = 0;
hash = perm[ (hash ^ str.charCodeAt(i)) & 0x0ff ];
hash = perm[ (hash ^ str.charCodeAt(i+1)) & 0x0ff ];
hash = perm[ (hash ^ str.charCodeAt(i+2)) & 0x0ff ];
// then we accumulate in the array at indices given by the hash
arr[hash]++;
}
// fold the array
for (var i = 0; i <= 0x01f; i++) {
arr[i] += arr[i+0x020] + arr[i+0x040] + arr[i+0x060] + arr[i+0x080] + arr[i+0x0a0] + arr[i+0x0c0] + arr[i+0x0e0];
}
var acc = 0;
var lim = arr.slice(0, 0x01f).sort(function(a,b){return a-b})[0x00f];
for (var i = 0; i <= 0x01f; i++) {
acc |= (((lim < arr[i]) ? 0 : 1) << i);
}
return acc & 0x0ffffffff;
};
function rvec(str) {
// this is an implementation of a subspace reduction algorithm based upon the Pearson hash
var arr = new Array();
for (var i = 0; i <= 0x0ff; i++) {
arr[i] = 0;
}
for (var i = 0, l = str.length-2; i < l; i++) {
// first we do a Pearson hash of trigram from the text
var hash = 0;
hash = perm[ (hash ^ str.charCodeAt(i)) & 0x0ff ];
hash = perm[ (hash ^ str.charCodeAt(i+1)) & 0x0ff ];
hash = perm[ (hash ^ str.charCodeAt(i+2)) & 0x0ff ];
// then we accumulate in the array at indices given by the hash
arr[hash]++;
}
return arr;
};
function beans(n) {
// implementation of a bean counter
// make an unsigned 32bits int
if (n < 0) n = 0xFFFFFFFF + n + 1;
// From Hacker's Delight, p. 66, Figure 5-2
n = n - ((n >> 1) & 0x55555555);
n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
n = (n + (n >> 4)) & 0x0F0F0F0F;
n = n + (n >> 8);
n = n + (n >> 16);
return n & 0x0000003F;
};
function cosine(vec1, vec2) {
// really just a difference measure for the trigram vectors
var combined = Object();
for (var x in vec1)
combined[x] = vec1[x];
for (var x in vec2) {
if (combined[x] == null) combined[x] = 0;
combined[x] -= vec2[x];
}
var acc = 0;
for (var x in combined)
acc += combined[x] * combined[x];
return Math.sqrt(acc);
};
function rcos(vec1, vec2) {
// really just a difference measure for the trigram vectors
var acc = 0;
for (var i = 0; i <= 0x0ff; i++)
acc += Math.pow((vec1 ? vec1[i] : 0) - (vec2 ? vec2[i] : 0), 2);
return Math.sqrt(acc);
};
function create() {
mw.loader.using( ['jquery.ui'], function() {
$dialog = $('#page-contributors-title');
if ($dialog.length > 0) {
$dialog.dialog('open');
}
else {
var formFind = '<div id="page-contributors-title" title="' + i18n( 'box-title' ) + '"><form>';
formFind += '<p>' + i18n( 'box-warning' ) + '</p>';
formFind += '</form></div>';
var messageFind = $( formFind ).appendTo( '#content' );
out = $( formFind ).find( 'p' );
messageFind.dialog( {
autoOpen: true,
modal: true,
width: 500,
buttons: [
{
id: 'contributors-button-cancel',
text: i18n( 'cancel' ),
click: abortDownload
},
{
id: 'contributors-button-ok',
text: i18n( 'execute' ),
click: startDownload
}
]
} );
}
} );
};
function abortDownload() {
$.removeSpinner( 'contributors' );
$('#page-contributors-title').dialog( "close" );
if ( queryContinue != null) {
$( '#contributors-button-ok' ).button( 'option', 'disabled', false );
}
}
function startDownload() {
$( '#contributors-button-ok' ).button( 'option', 'disabled', true );
mw.loader.using( ['jquery.spinner'], function() {
$.createSpinner( {
size: 'large',
type: 'block',
id: 'contributors'
} ).appendTo( '#page-contributors-title' );
download( queryContinue );
});
}
function download( rvcontinue ) {
// ajax object
if (rvcontinue != null) {
query.rvstartid = rvcontinue;
}
$.ajax({
url: mw.util.wikiScript( 'api' ),
dataType: 'json',
data: query,
context: document.body
})
.done(function(data, textStatus) {
if (textStatus != 'success') {
alert( i18n( 'partial-success' ) + textStatus );
$('#page-contributors-title').dialog( "close" );
}
var r = data['query'].pages[parseInt(mw.config.get('wgArticleId'))].revisions;
for (var x in r) {
if (typeof r[x]['*'] == 'undefined') {
hidden++;
continue;
}
r[x].lsh = lsh(r[x]['*']);
r[x].fnv = toHex(fnv(r[x]['*']));
if(r[x].size == 'undefined') r[x].size = r[x]['*'].length;
r[x].vec = rvec(r[x]['*']);
r[x]['*'] = null; // conserve space
revisions.push(r[x]);
}
if (data['query-continue']) {
$('#page-contributors-title p').html( revisions.length + i18n( 'loaded-revisions' ) );
queryContinue = query.rvstartid = data['query-continue'].revisions.rvcontinue;
if ( $('#page-contributors-title').dialog( "isOpen" ) ) {
download();
}
}
else {
$.removeSpinner( 'contributors' );
queryContinue = null;
setResult();
}
})
.fail(function(jqXHR, textStatus, errorThrown) {
alert(i18n( 'error-partial' ) + textStatus);
$('#page-contributors-title').dialog( "close" );
});
};
function setResult() {
// final handler after all ajax calls has completed
$('#page-contributors-title:parent p').html( i18n( 'all-loaded' ) );
// variables
var digests = Object();
var users = Object();
var revids = Object();
// short form to get the revisions
var revs = revisions;
// element to use for output
out = $('#page-contributors-title p');
// get the individual revisions through a revid-key
for (var i = 0; i < revs.length; i++) revids[revs[i].revid] = revs[i];
// get the individual revisions through the fnv-digest
out.html( i18n( 'result-fnv' ) );
var shunted = 0;
for (var i = revs.length-1; 0<=i; i--) {
if (digests[revs[i].fnv]) {
revs[i].previousid = digests[revs[i].fnv].revid;
shunted++;
}
digests[revs[i].fnv] = revs[i];
}
// cache the cosine measure and set previousid to reflect the span
out.html( i18n( 'result-calculate-cosine' ) );
var i = 0;
while (i<revs.length) {
var j = i;
if (revs[i].previousid) {
revs[i].cosine = rcos(revs[i].vec, revids[revs[i].previousid].vec);
var previousid = revs[i].previousid;
while (revs[i].revid > previousid && i<revs.length) i++;
}
else if (revs[i].parentid) {
revs[i].cosine = rcos(revs[i].vec, revids[revs[i].parentid].vec);
var parentid = revs[i].parentid;
while (revs[i].revid > parentid && i<revs.length) i++;
}
else if (i+1<revs.length) {
revs[i].cosine = rcos(revs[i].vec, revs[i+1].vec);
i++;
}
else if (i+1==revs.length) {
revs[i].cosine = rcos(revs[i].vec);
revs[j].previousid = 0;
break;
}
if (i != j) revs[j].previousid = revs[i].revid;
}
// adjust the cosine measure if lsh is to far off
out.html( i18n( 'result-adjust-cosine' ) );
var similar = 0;
var revision = revs[0];
while (revision) {
var previous = revids[revision.previousid];
if (previous /* && revision.size > 100 */ && 64<revision.cosine) {
var p=previous;
var lsh=2147483647;
var keep = null;
for (var j = 0; j < 16; j++) {
if (p) {
var tmp = rcos(revision.vec, p.vec);
if (lsh > tmp) {
lsh = tmp;
keep = p;
}
p = revids[p.previousid];
}
else {
break;
}
}
if (keep && keep != previous) {
previous = keep;
similar++;
}
}
if (previous) {
revision.previousid = previous.revid;
revision.cosine = rcos(revision.vec, previous.vec);
}
revision = previous;
}
// accumulate the cosine measure for each user
out.html( i18n( 'result-accumulate-cosine' ) );
var revision = revs[0];
var num = 0;
while (revision) {
if (!users[revision.user]) users[revision.user] = { cosine: 0 };
users[revision.user].cosine += revision.cosine;
revision = revids[revision.previousid];
num++;
}
// accumulate the total cosine measure
var acc = { cosine: 0 };
for (var x in users) acc.cosine += users[x].cosine;
// sort out the main contributors
out.html( i18n( 'result-contributors' ) );
var authors = Array();
var pseudonyms = Array();
var count = 0;
for (var x in users) pseudonyms.push(x);
for (var x in pseudonyms.sort(function(a, b){ return users[b].cosine - users[a].cosine })) {
var name = pseudonyms[x];
var cosine = Math.round(100*users[name].cosine/acc.cosine);
if (count++ < 5 && 0 < cosine) authors.push(name);
else if (5 <= cosine ) authors.push(name);
else if (revs[revs.length-1].user == name) authors.push(name);
}
// print the cosine measure for each user
out.html( i18n( 'result-presentation' ) );
str = i18n( 'result-ingres' )
.replace('$num$', num)
.replace('$tot$', revisions.length);
if (0<hidden)
str += i18n( 'result-hidden' )
.replace('$hidden$', hidden);
if (0<shunted || 0<similar)
str += i18n( 'result-other' )
.replace('$shunted$', hidden)
.replace('$similar$', similar);
else
str += i18n( 'result-none' );
str += '<ul>';
var part = 0;
for (var x in authors) {
var name = authors[x];
part += users[name].cosine;
var amouth = (0.1<users[name].cosine/acc.cosine ? Math.round(100*users[name].cosine/acc.cosine) : Math.round(1000*users[name].cosine/acc.cosine)/10);
var entry = (revs[revs.length-1].user == name ? i18n( 'entry-main' ) : i18n( 'entry-norm' ))
.replace('$name$', name)
.replace('$uname$', mw.util.getUrl('User:' + name))
.replace('$tname$', mw.util.getUrl('User_talk:' + name))
.replace('$cname$', mw.util.getUrl('Special:Contributions/' + name))
.replace('$amouth$', String(amouth));
str += '<li>' + entry + '</li>';
}
amouth = Math.round(100*(acc.cosine-part)/acc.cosine);
entry = i18n( 'entry-other' ).replace('$amouth$', String(amouth));
str += '<li>' + entry + '</li>';
str += '</ul>';
str += '<small>' + i18n( 'entry-other' ) + '</small>';
out.html(str);
};
var nsnum = parseInt(mw.config.get('wgNamespaceNumber'));
function init() {
// the strange test (2147483647 <= -1 >>> 1) is to verify that we have at least 32 bit ints
// note that 32 bit ints are according to standard, while it can be longer but not shorter to respect the standard
if (0 <= nsnum && 0 == nsnum%2 && mw.config.get('wgIsArticle') && (2147483647 <= -1 >>> 1)) {
$( function() {
mw.util.addPortletLink(
"p-cactions",
'#',
i18n( 'contributors' ),
"t-article-contributors",
i18n( 'description' ),
null,
null
);
$('#t-article-contributors').click(create);
});
}
};
$( document ).ready( init );
} (mediaWiki, jQuery) );