User:Lupin/editcount.js
From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Mozilla/Safari: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Internet Explorer: press Ctrl-F5, Opera/Konqueror: press F5.
/** <nowiki> * A javascript edit counter, using query.php as the backend * * Usage instructions for popups users: add * {{subst:js|User:Lupin/editcount.js}} popupEditCounterTool='custom'; popupEditCounterUrl='http://en.wikipedia.org/wiki/User:$1?ectarget=$1'; * * to your user javascript file (usually monobook.js), hover over a * user name and select "edit counter" * */ //<pre> ec = { getParamValue: function(paramName) { var cmdRe=RegExp('[&?]'+paramName+'=([^&]*)'); var h=document.location; var m; if (m=cmdRe.exec(h)) { try { while(m[1].indexOf('+')!=-1) { m[1]=m[1].substr(0,m[1].indexOf('+'))+" "+m[1].substr(m[1].indexOf('+')+1); } return decodeURIComponent(m[1]); } catch (someError) {} } return null; }, doEditCount: function(user) { if (!user) { return; } ec.user=user; ec.makeEditCountDivs(); ec.getContribs(user); setTimeout(ec.checkContribs, 1000); }, makeEditCountDivs: function() { var d=document.createElement('div'); d.id='editcount_output'; ec.appendDivs(d, [ 'editcount_title', 'editcount_intervalselector', 'editcount_stats' ]); var h=document.getElementById('siteSub'); h.parentNode.insertBefore(d, h.nextSibling); }, appendDivs: function(parent, list) { for (var i=0; i<list.length; ++i) { var d=document.createElement('div'); d.id=list[i]; parent.appendChild(d); } }, checkContribs: function() { if (ec.complete) { ec.doOutput(); } else { ec.doStatus(); setTimeout(ec.checkContribs, 1000); } }, doOutput: function(start, end) { var d=document.getElementById('editcount_stats'); if (!ec.count) { d.innerHTML='No edits found for ' + ec.user; return; } if (!this.intsel) { this.intsel = new IntervalSelector({ min: ts2unix(this.editlist.first.next.key), max: ts2unix(this.editlist.last.prev.key)}); var this2=this; this.intsel.doneDrag=function() { //document.title=[this.lo, this.hi]; this2.doOutput.apply(this2, map(unix2ts, [this.lo, this.hi])); }; this.intsel.dragging=function() { var start=unix2ts(this2.intsel.lo); var end=unix2ts(this2.intsel.hi); document.getElementById('editcount_range').innerHTML= formatTs(start) + ' - ' + formatTs(end); }; //this.intsel.dragging=this.intsel.doneDrag; // too slow - pretty cool tho var intdiv=document.getElementById('editcount_intervalselector'); intdiv.appendChild(this.intsel.box); this.appendDivs(intdiv, ['editcount_range']); this.intsel.dragging(); this.intseldebug=document.createElement('div'); this.intsel.box.parentNode.insertBefore(this.intseldebug, this.intsel.box); } document.getElementById('editcount_title').innerHTML=ec.outputHeading(); document.getElementById('editcount_stats').innerHTML='<p>Total: ' + ec.countFigure() + '<br>First edit: ' + ec.firstEdit.replace(/[TZ]/g, ' ') + '(UTC)' + ec.statsTable(start, end); }, outputHeading: function() { return '<h2>Edit count for ' + ec.user + '</h2>'; }, doStatus: function() { var d=document.getElementById('editcount_stats'); d.innerHTML=ec.outputHeading() + '<p>Downloaded ' + ec.countFigure() + ' so far' + ec.statsTable(); }, countFigure: function() { return ec.count + ' edits over ' + objSum(ec.namespaces, 'articleCount') + ' pages'; }, findEdit: function(timestamp, up) { // this is very broken - FIXME! if (up) { var e=this.editlist.first; while(e.key<timestamp && (e=e.next)){}; //console.log('findEdit, up: got '+timestamp+', found '+(e.prev && e.prev.key || null) ); return e.prev; } else { var e=this.editlist.last; while(e.key>timestamp && (e=e.prev)){} //console.log('findEdit, down: got '+timestamp+', found '+(e.next && e.next.key || null) ); return e.next; } }, statsTable: function(start, end) { //console.log('start: '+start + ', end: '+end); var barTotal=400; var endEdit=this.findEdit(end) || this.editlist.last; var startEdit=this.findEdit(start,true); if (!startEdit || !startEdit.key) { startEdit=this.editlist.first.next; } //console.log('endEdit:' + endEdit.key); //console.log('startEdit:'+ startEdit.key); var sumValue=function(val) { return objSum(startEdit.stats, val) - objSum(endEdit.stats, val); } var total=sumValue('count'); if (!total) { return ''; } var statValue=function(k, val) { if (!startEdit.stats[k]) { return 0; } var r=startEdit.stats[k][val]; //console.log(k + ' ' + val + ': ' + r); if (!endEdit.stats[k] || !endEdit.stats[k][val]) { return r; } return r - endEdit.stats[k][val]; }; // FIXME: abstract this away so it's trivial to add new columns r='<p>Statistics between '+formatTs(startEdit.key) + ' and '+formatTs(endEdit.key); r+='<table><tr><th>' + ['Namespace', 'New', 'Minor', 'Top', 'Summaries', '(manual)', 'Pages', 'Count', '%'].join('</th><th>') + '</th></tr>'; for (var k in ec.namespace_names) { if (!ec.namespaces[k]) { continue; } r += '<tr><td>'+[ec.namespace_names[k], statValue(k, 'newCount'), statValue(k, 'minorCount'), statValue(k, 'topCount'), statValue(k, 'commentCount'), statValue(k, 'manualCommentCount'), statValue(k, 'articleCount'), statValue(k, 'count'), percent(statValue(k, 'count'), total)].join('</td><td>') + '</td>'; r+=ec.ecBar(barTotal, total, statValue(k, 'count'), statValue(k, 'minorCount') || 0); r+='</tr>'; } var totalMinor = sumValue('minorCount'); r+='<tr><td>'+['<b>Total</b>', sumValue('newCount'), totalMinor, sumValue('topCount'), sumValue('commentCount'), sumValue('manualCommentCount'), sumValue('articleCount'), sumValue('count'), '100'].join('</td><td>') + '</td>'; r+=ec.ecBar(barTotal, total, sumValue('count'), totalMinor); r+='</table>'; return r; }, histogramBar: function(value, scale, colour, hint) { var height='2ex'; var style='height: '+ height; style += '; background: ' + colour; style += '; width: ' + value * scale + 'px'; style += '; float: left;'; return '<span style="' + style + '" title="' + hint + '"></span>'; }, histogramCell: function(scale, values) { var r='<td><div style="width: ' + scale + 'px;">'; for (var i=0; i<values.length; i+=3) { r+=ec.histogramBar(values[i], scale, values[i+1], values[i+2]); } r+='</div></td>'; return r; }, ecBar: function(scale, total, count, minor) { var nonMinorColour='blue'; var minorColour='#0A3'; return ec.histogramCell( scale, [(count-minor)/total, nonMinorColour, "non-minor edits", minor/total, minorColour, "minor edits"]); }, ajax: { download:function(bundle) { // mandatory: bundle.url // optional: bundle.onSuccess (xmlhttprequest, bundle) // optional: bundle.onFailure (xmlhttprequest, bundle) // optional: bundle.otherStuff OK too, passed to onSuccess and onFailure var x = window.XMLHttpRequest ? new XMLHttpRequest() : window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : false; if (x) { x.onreadystatechange=function() { x.readyState==4 && ec.ajax.downloadComplete(x,bundle); }; x.open("GET",bundle.url,true); x.send(null); } return x; }, downloadComplete:function(x,bundle) { x.status==200 && ( bundle.onSuccess && bundle.onSuccess(x,bundle) || true ) || ( bundle.onFailure && bundle.onFailure(x,bundle) || alert(x.statusText)); } }, getContribs: function(user, startAt) { var limit=500; // currently maximum allowed per page by query.php var url='http://en.wikipedia.org/w/query.php?what=usercontribs' + '&uccomments' + // enable for edit comment analysis '&format=json&uclimit=500&titles=User:'+escape(user); if (startAt) { url += '&ucend=' + startAt.replace(/[^0-9]/g, ''); } ec.ajax.download({ url: url, user: user, startAt: startAt, onSuccess: ec.readContribs, limit: limit}); }, readContribs: function(dl, bundle) { window.dl=dl; window.bundle=bundle; try { eval('var jsobj=' + dl.responseText); var pages=jsobj.pages; var child=ec.anyChild(pages); var contribs=child.contributions; } catch (summat) { throw new Error('Badness happened in readContribs: ' + summat.message); return; } var i=0, j=0; var minrev=null; for (var c in contribs) { ++i; var cc=contribs[c]; if (!minrev || cc.revid < minrev) { minrev = cc.revid; } if (ec.edits[cc.revid]) { continue; } ++j; ec.doStats(cc); ec.edits[cc.revid] = cc; } ec.count += j; if (i == bundle.limit && ec.edits[minrev]) { ec.getContribs(bundle.user, ec.edits[minrev].timestamp); } else { ec.complete=true; minrev && (ec.firstEdit=ec.edits[minrev].timestamp); } }, doStats: function (c) { var k=c.ns || 0; //if (!ec.namespaces[k]) { console.log('New namespace: '+k + ', title=' +c['*'] + // ', alleged NS=' + ec.namespace_names[k]); } if (!ec.namespaces[k]) { ec.namespaces[k] = {articles: {}}; } var n = ec.namespaces[k]; incr(n, 'count'); if (!n.articles[c['*']]) { incr(n, 'articleCount'); } incr(n.articles, c['*']); if (typeof c.minor != 'undefined') { incr(n, 'minorCount'); } if (typeof c.top != 'undefined') { incr(n, 'topCount'); } if (typeof c['new'] != 'undefined') { incr(n, 'newCount'); } if (c.comment) { incr(n, 'commentCount'); if (!RegExp("^/[*].*?[*]/ *$").test(c.comment)) { incr(n, 'manualCommentCount'); } } this.editlist.add({key: parseInt(c.timestamp.replace(/[^0-9]/g, ''), 10), edit: c, stats: this.saveStats()}); // more stuff here, perhaps }, saveStats: function() { var r={}; var list=['count', 'articleCount', 'minorCount', 'topCount', 'newCount', 'commentCount', 'manualCommentCount']; for (var k in ec.namespaces) { r[k]=getStuff(ec.namespaces[k],list); } return r; }, anyChild: function(obj) { for (var p in obj) { return obj[p]; } return null; }, edits: {}, count: 0, complete: false, namespaces: {}, namespace_names: {0: 'Article', 1: 'Talk', 2: 'User', 3: 'User talk', 4: 'Wikipedia', 5: 'Wikipedia talk', 6: 'Image', 7: 'Image talk', 8: 'MediaWiki', 9:'MediaWiki talk', 10: 'Template', 11: 'Template talk', 12: 'Help', 13: 'Help talk', 14: 'Category', 15: 'Category talk', 100: 'Portal', 101: 'Portal talk' // no comma }, firstEdit: 0, editlist: new linkedList( {key: 99990101011200, stats: {}}, {key: 0, stats: {}}), dummy: null // no comma }; window.incr=function(obj, key) { if (!obj[key]) { obj[key]=1; } else { obj[key]++; } } window.objSum=function(obj, x, y) { var r=0; if (x && y) { for (var k in obj) { r+= (obj[k][x][y] ? obj[k][x][y] : 0); } } else if (x) { for (var k in obj) { r+= (obj[k][x] ? obj[k][x] : 0); } } else { for (var k in obj) { r+= (obj[k] ? obj[k] : 0); } } return r; } window.percent=function(n, N) { return Math.floor(n/N * 1000 + .5)/10; }; if((user=ec.getParamValue('ectarget'))!==null) { addOnloadHook(function(){ec.doEditCount(user);}); } function linkedList(x0,y0) { this.first=null; this.last=null; this.hash={}; this.add=function(x) { this.hash[x.key]=x; if (!this.first) { this.first=x; this.last=x; x.prev=x.next=null; return; } var k=x.key; if (true || k - this.first.key < this.last.key - k) { this.pushTop(x); } else { this.pushTail(x); } }; this.pushTop=function(x) { if (x.key < this.first.key) { this.first.prev=x; x.next=this.first; this.first=x; x.prev=null; return; } if (x.key > this.last.key) { this.last.next=x; x.prev=this.last; this.last=x; x.next=null; } for (var y=this.first; y.next; y=y.next) { if (y.key < x.key && x.key <= y.next.key) { this.insertAfter(y, x); return; } } }; this.pushTail=function(x) { for (var y=this.last; y.prev; y=y.prev) { if (y.prev.key < x.key && x.key <= y.key) { this.insertAfter(y.prev, x); return; } } this.first.prev=x; x.next=this.first; this.first=x; x.prev=null; }; this.insertAfter=function(y,x) { x.next=y.next; x.prev=y; y.next.prev=x; y.next=x; }; if (x0) { this.add(x0); } if (y0) { this.add(y0); } } window.getStuff=function(obj, list) { var r={}; for (var i=0; i<list.length; ++i) { if (typeof obj[list[i]] != 'undefined') { r[list[i]]=obj[list[i]]; } } return r; } window.IntervalSelector=function(data) { if (!data) { data={}; } this.min=data.min || 10; this.max=data.max || 100; this.span=this.max-this.min; this.lo=data.lo || this.min; this.hi=data.hi || this.max; this.width=data.width || 400; this.height=data.height || 20; this.scale=this.width/this.span; this.minBarWidth=data.minBarWidth || 10; this.oldmousemove = null; this.createDiv(); } IntervalSelector.prototype.createDiv=function() { var d=document.createElement('div'); d.className='intervalselectorbox'; //d.style.position='absolute'; d.style.border='1px solid black'; // FIXME var s=document.createElement('div'); s.className='intervalselector'; s.style.position='relative'; s.style.background='orange'; // FIXME //s.style.border='2px solid red'; // FIXME d.appendChild(s); this.box=d; this.bar=s; var this2=this; this.bar.onmousedown=function(e){ this2.mouseDown.apply(this2, [e]); } this.box.onmousedown=function(e){ this2.mouseDown.apply(this2, [e]); } this.updatePosition(); }; IntervalSelector.prototype.updatePosition=function() { var d=this.box; d.style.width=this.width+'px'; d.style.height=this.height+'px'; var s=this.bar; s.style.left=(this.lo-this.min)*this.scale+ 'px'; s.style.width=(this.hi-this.lo)*this.scale + 'px'; s.style.height=this.height + 'px'; }; IntervalSelector.prototype.mouseDown=function(e) { var endWidth=8; var pos=this.getMousePos(e); var this2=this; var dragFunction=null; var leftPos=findPosX(this.bar); if (pos.x - leftPos < endWidth) { dragFunction=this2.dragLo; } else if ( leftPos + parseInt(this.bar.style.width, 10) - pos.x < endWidth) { dragFunction=this2.dragHi; } else { dragFunction = this2.dragBar; } var x=pos.x, lo=this.lo; if (document.onmousemove && document.onmousemove.origin != 'IntervalSelector') { this.oldmousemove = document.onmousemove; } document.onmousemove=function(e) { dragFunction.apply(this2, [e, x, lo]); this2.dragging.apply(this2); }; document.onmousemove.origin='IntervalSelector'; document.onmouseup=function() { //console.log(this2.oldmousemove.toString()); document.onmousemove= this2.oldmousemove; this2.doneDrag.apply(this2); }; document.onmouseup.origin='IntervalSelector'; //document.title=pos.x; }; IntervalSelector.prototype.doneDrag=function(){}; IntervalSelector.prototype.dragging=function(){}; IntervalSelector.prototype.dragLo=function(e) { var pos=this.getMousePos(e); var newLo=this.min + (pos.x - findPosX(this.box))/this.scale; if (newLo < this.min) { newLo=this.min; } else if (newLo > this.hi - this.minBarWidth/this.scale) { newLo=this.hi - this.minBarWidth/this.scale; } this.lo=newLo; this.updatePosition(); }; IntervalSelector.prototype.dragHi=function(e) { var pos=this.getMousePos(e); var newHi=this.min + (pos.x - findPosX(this.box))/this.scale; if (newHi > this.max) { newHi=this.max; } else if (newHi < this.lo + this.minBarWidth/this.scale) { newHi=this.lo + this.minBarWidth/this.scale; } this.hi=newHi; this.updatePosition(); }; IntervalSelector.prototype.dragBar=function(e, x0, l0) { var pos=this.getMousePos(e); var delta=pos.x-x0; var newLo=l0 + delta/this.scale; var newHi=newLo + this.hi-this.lo; if (newLo < this.min) { newLo=this.min; newHi=newLo+this.hi-this.lo; } else if (newHi > this.max) { newHi=this.max; newLo=newHi-(this.hi-this.lo); } this.hi=newHi; this.lo=newLo; this.updatePosition(); }; IntervalSelector.prototype.getMousePos=function(e) { e = e || window.event; var x, y; if (e) { if (e.pageX) { x=e.pageX; y=e.pageY; } else if (typeof e.clientX!='undefined') { var left, top, docElt = window.document.documentElement; if (docElt) { left=docElt.scrollLeft; } left = left || window.document.body.scrollLeft || window.document.scrollLeft || 0; if (docElt) { top=docElt.scrollTop; } top = top || window.document.body.scrollTop || window.document.scrollTop || 0; x=e.clientX + left; y=e.clientY + top; } else { throw new Error ('bad mouse wiggle event in getMousePos'); return; } } return {x:x, y:y}; }; window.findPosX=function(obj) { var curleft = 0; if (obj.offsetParent) { while (obj.offsetParent) { curleft += obj.offsetLeft obj = obj.offsetParent; } } else if (obj.x) curleft += obj.x; return curleft; } window.ts2unix=function(ts) { var t=ts.toString(); return +(Date.UTC( t.substring(0,4), parseInt(t.substring(4,6),10)-1, t.substring(6,8), t.substring(8,10), t.substring(10,12), t.substring(12,14))); } window.unix2ts=function(u) { var d=new Date(u); return map(zeroFill, [d.getUTCFullYear(), d.getUTCMonth()+1, d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds()]).join(''); } window.zeroFill=function(s, min) { min = min || 2; var t=s.toString(); return repeatString('0', min - t.length) + t; } window.map=function(f, o) { if (isArray(o)) { return map_array(f,o); } return map_object(f,o); } window.isArray =function(x) { return x instanceof Array; } window.map_array=function(f,o) { var ret=[]; for (var i=0; i<o.length; ++i) { ret.push(f(o[i])); } return ret; } window.map_object=function(f,o) { var ret={}; for (var i in o) { ret[o]=f(o[i]); } return ret; } window.repeatString=function(s,mult) { var ret=''; for (var i=0; i<mult; ++i) { ret += s; } return ret; }; window.formatTs=function(ts) { ts=ts.toString(); if (ts.substring(0,4)=='9999') { return 'now'; } return [ts.substring(0,4), ts.substring(4,6), ts.substring(6,8)].join('-') + ' ' + [ts.substring(8,10),ts.substring(10,12),ts.substring(12,14)].join(':'); }; function isMethodOf(klass, fn) { for (var f in klass.prototype) { if (fn===klass.prototype[f]) { return true; } } return false; } //</nowiki></pre> //ec.doEditCount('Amanda77') // ec.doEditCount('Llama man')