Difference between revisions of "User:Elliottmobile/monobook.js"
From Hydrogenaudio Knowledgebase
Line 1: | Line 1: | ||
− | // Modded version of http://en.wikipedia.org/wiki/User: | + | // Modded version of http://en.wikipedia.org/wiki/User:Lupin/editcount.js by Elliott. |
+ | /** <nowiki> | ||
+ | * A javascript edit counter, using query.php as the backend | ||
+ | * | ||
+ | */ | ||
− | // | + | //<pre> |
− | + | ec = { | |
− | + | ||
− | + | getParamValue: function(paramName) { | |
− | + | var cmdRe=RegExp('[&?]'+paramName+'=([^&]*)'); | |
− | var | + | 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 { | |
− | function | + | 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>'; | ||
+ | }, | ||
− | var | + | 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'; | ||
+ | }, | ||
− | return | + | 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://wiki.hydrogenaudio.org/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); | ||
+ | } | ||
+ | }, | ||
− | function | + | 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: 'Hydrogenaudio Knowledgebase', 5: 'Hydrogenaudio Knowledgebase 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', | ||
+ | 16: 'Foobar2000', 17: 'Foobar2000 talk', | ||
+ | 18: 'Foobar2000redirect', 19: 'Foobar2000redirect talk', | ||
+ | }, | ||
+ | firstEdit: 0, | ||
+ | editlist: new linkedList( | ||
+ | {key: 99990101011200, stats: {}}, | ||
+ | {key: 0, stats: {}}), | ||
+ | dummy: null // no comma | ||
+ | }; | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | function | + | window.incr=function(obj, key) { |
− | + | if (!obj[key]) { obj[key]=1; } | |
− | + | else { obj[key]++; } | |
− | + | } | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | window.objSum=function(obj, x, y) { | |
− | var | + | 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; | |
− | if ( | + | 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]]; } | |
− | function | + | } |
− | + | return r; | |
− | + | ||
− | var | + | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
} | } | ||
− | + | window.IntervalSelector=function(data) { | |
− | function | + | 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(); | ||
+ | }; | ||
− | var | + | 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; | ||
} | } | ||
− | function | + | 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) { | |
− | function | + | var d=new Date(u); |
− | var | + | 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(); | |
− | function | + | return repeatString('0', min - t.length) + t; |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
} | } | ||
− | + | window.map=function(f, o) { | |
− | function | + | 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) { | |
− | function | + | 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={}; | |
− | function | + | for (var i in o) { ret[o]=f(o[i]); } |
− | + | return ret; | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
} | } | ||
− | + | window.repeatString=function(s,mult) { | |
− | function | + | var ret=''; |
− | var ret = | + | for (var i=0; i<mult; ++i) { ret += s; } |
− | for (var | + | |
− | + | ||
− | + | ||
return ret; | 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 | + | 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') |
Revision as of 21:11, 21 September 2006
// Modded version of http://en.wikipedia.org/wiki/User:Lupin/editcount.js by Elliott. /** <nowiki> * A javascript edit counter, using query.php as the backend * */ //<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://wiki.hydrogenaudio.org/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: 'Hydrogenaudio Knowledgebase', 5: 'Hydrogenaudio Knowledgebase 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', 16: 'Foobar2000', 17: 'Foobar2000 talk', 18: 'Foobar2000redirect', 19: 'Foobar2000redirect talk', }, 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')