User:Dbostwick/editcount.js

/** * A javascript edit counter, using query.php as the backend * * Usage instructions for popups users: add *

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" * */

//

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=' Total: ' + ec.countFigure + ' First edit: ' + ec.firstEdit.replace(/[TZ]/g, ' ') + '(UTC)' + ec.statsTable(start, end); },

outputHeading: function { return ' Edit count for ' + ec.user + ' '; },

doStatus: function { var d=document.getElementById('editcount_stats'); d.innerHTML=ec.outputHeading + ' 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.keytimestamp && (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=' Statistics between '+formatTs(startEdit.key) + ' and '+formatTs(endEdit.key); r+=' '; 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 ' '; },

histogramCell: function(scale, values) { var r=' '; for (var i=0; i<values.length; i+=3) { r+=ec.histogramBar(values[i], scale, values[i+1], values[i+2]); } r+=' '; 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; }

// //ec.doEditCount('Amanda77') // ec.doEditCount('Llama man')