Difference between revisions of "User:Elliottmobile/monobook.js"

From Hydrogenaudio Knowledgebase
Jump to: navigation, search
Line 1: Line 1:
// Modded version of http://en.wikipedia.org/wiki/User:Interiot/Tool2/code.js by Elliott.
+
// 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
 +
*
 +
*/
  
// see http://paperlined.org/apps/wikipedia/Tool2/ for instructions on adding this to your monobook.js
+
//<pre>
  
// To run this tool on other servers:
+
ec = {
// 1. copy this script to the target server (this is required because of javascript cross-site security restrictions)
+
  
// 2. update the following URL
+
getParamValue: function(paramName) {
// for example: "User:Interiot/Tool2/code.js"
+
var cmdRe=RegExp('[&?]'+paramName+'=([^&]*)');
var tool2_url = "User:Elliottmobile/monobook.js";
+
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;
 +
},
  
// 3. update this namespace list, extracted from something like http://en.wikiquote.org/wiki/Special:Export//
+
doEditCount: function(user) {
// These *should not* have colons after them.
+
if (!user) { return; }
var namespaces = [
+
ec.user=user;
"Talk",
+
ec.makeEditCountDivs();
"User",
+
ec.getContribs(user);
"User talk",
+
setTimeout(ec.checkContribs, 1000);
"Hydrogenaudio Knowledgebase",
+
},
"Hydrogenaudio Knowledgebase talk",
+
makeEditCountDivs: function() {
"Image",
+
var d=document.createElement('div');
"Image talk",
+
d.id='editcount_output';
"MediaWiki",
+
ec.appendDivs(d, [ 'editcount_title', 'editcount_intervalselector',  
"MediaWiki talk",
+
  'editcount_stats' ]);
"Template",
+
var h=document.getElementById('siteSub');
"Template talk",
+
h.parentNode.insertBefore(d, h.nextSibling);
"Help",
+
},
"Help talk",
+
appendDivs: function(parent, list) {
"Category",
+
for (var i=0; i<list.length; ++i) {
"Category talk",
+
var d=document.createElement('div');
"Foobar2000",
+
d.id=list[i];
"Foobar2000 Talk",
+
parent.appendChild(d);
"Foobar2000redirect"
+
}
"Foobar2000redirect Talk"
+
},
];
+
  
// 4. update this date-parser to match the format and language of your specific wiki.  Feel free to contact Interiot regarding this, if you can't find another
+
checkContribs: function() {
// copy of this script that uses the same language.
+
if (ec.complete) {
// input: a text string from Special:Contributions.    output: a javascript Date object
+
ec.doOutput();
// documentation:  http://www.quirksmode.org/js/introdate.html#parse, http://www.elated.com/tutorials/programming/javascript/dates/
+
} else {
function date_parse(text) {
+
ec.doStatus();
var matches = text.match(/^([0-9:]+), +([0-9]+) +([a-z]+) +([0-9]+)$/i);
+
setTimeout(ec.checkContribs, 1000);
if (!matches) {
+
}
//dump_text("XXX"); // for debugging
+
},
return matches;
+
}
+
  
parseme = matches[3] + ", " + matches[2] + " "  + matches[4] + " " + matches[1] + ":00";
+
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);
 +
},
  
//dump_text(parseme); // for debugging
+
outputHeading: function() {
 +
return '<h2>Edit count for ' + ec.user + '</h2>';
 +
},
  
var dt = new Date();
+
doStatus: function() {
dt.setTime( Date.parse(parseme));
+
var d=document.getElementById('editcount_stats');
 +
d.innerHTML=ec.outputHeading() + '<p>Downloaded ' + ec.countFigure() + ' so far' + ec.statsTable();
 +
},
  
//dump_text(dt.toLocaleString()); // for debugging
+
countFigure: function() {
 +
return ec.count + ' edits over ' + objSum(ec.namespaces, 'articleCount') +  ' pages';
 +
},
  
return dt;
+
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;
 +
}
 +
},
  
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ end of server-specific configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
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;
 +
},
  
// TODO:
+
ecBar: function(scale, total, count, minor) {
// - the current document.location method doesn't work when the page is accessed sans-mod_rewrite
+
var nonMinorColour='blue';
// - test with non-ASCII characters
+
var minorColour='#0A3';
// - non-ascii usernames
+
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;
  
var prefix = "";
+
if (x) {
var params = parse_params();
+
x.onreadystatechange=function() {
 +
x.readyState==4 && ec.ajax.downloadComplete(x,bundle);
 +
};
 +
x.open("GET",bundle.url,true);
 +
x.send(null);
 +
}
 +
return x;
 +
},
  
addOnloadFunction(function() {
+
downloadComplete:function(x,bundle) {
  var path_len = document.location.pathname.length;
+
x.status==200 && ( bundle.onSuccess && bundle.onSuccess(x,bundle) || true )
  // trigger once we view the right page
+
|| ( bundle.onFailure && bundle.onFailure(x,bundle) || alert(x.statusText));
  if (document.location.pathname.substring(path_len - tool2_url.length, path_len) == tool2_url) {
+
}
    // get the prefix (needs to be fixed to work sans-mod_rewrite
+
},
    prefix = document.location.protocol + "//" + document.location.host + "/"
+
            + document.location.pathname.substring(1, path_len - tool2_url.length);
+
  
    // blank the inner contents of the page
 
    var bodyContent = document.getElementById("bodyContent");
 
    while (bodyContent.childNodes.length > 0) bodyContent.removeChild(bodyContent.lastChild);
 
  
    if (document.location.search.length == 0) {
+
getContribs: function(user, startAt) {
      generate_input_form(bodyContent);
+
var limit=500; // currently maximum allowed per page by query.php
    } else {
+
var url='http://wiki.hydrogenaudio.org/query.php?what=usercontribs' +
      generate_main_report(bodyContent);
+
'&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 generate_input_form(bodyContent) {
+
doStats: function (c) {
  if (navigator.userAgent.toLowerCase().indexOf('msie')+1)
+
var k=c.ns || 0;
  {
+
//if (!ec.namespaces[k]) { console.log('New namespace: '+k + ', title=' +c['*'] +
  bodyContent.innerHTML = "This counter does not currently work in Internet Explorer.  Please <a href='http://www.getfirefox.com'>get Firefox</a>.";
+
// ', alleged NS=' + ec.namespace_names[k]); }
  }
+
if (!ec.namespaces[k]) { ec.namespaces[k] = {articles: {}}; }
  else
+
var n = ec.namespaces[k];
  {
+
incr(n, 'count');
  bodyContent.innerHTML =
+
if (!n.articles[c['*']]) { incr(n, 'articleCount'); }
            "<form><table><tr><td>Username <td><input maxlength=128 name=username value='' id=username title='username'>" +
+
incr(n.articles, c['*']);
            "            <tr><td>        <td><input type=submit value='Submit'>" +
+
if (typeof c.minor != 'undefined') { incr(n, 'minorCount'); }
            "</table></form>";
+
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
 +
},
  
  var form = bodyContent.getElementsByTagName("form")[0];
+
saveStats: function() {
  form.method = "get";
+
var r={};
  form.action = document.location;
+
var list=['count', 'articleCount', 'minorCount', 'topCount',
 +
  'newCount', 'commentCount', 'manualCommentCount'];
 +
for (var k in ec.namespaces) {
 +
r[k]=getStuff(ec.namespaces[k],list);
 +
}
 +
return r;
 +
},
  
  document.getElementById("username").focus();
+
anyChild: function(obj) {
  }
+
for (var p in obj) {
}
+
return obj[p];
 +
}
 +
return null;
 +
},
  
function generate_main_report() {
+
edits: {},
  fetch_data(params["username"].replace(/\+/g, " "),
+
count: 0,
"", output_main_report, 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 add_stats_row(left_col, right_col) {
 
var row = document.createElement("tr");
 
var left = document.createElement("td");
 
var right = document.createElement("td");
 
 
document.getElementById("basic_stats").appendChild(row);
 
row.appendChild(left);
 
row.appendChild(right);
 
//left.innerHTML = left_col;
 
left.appendChild( document.createTextNode(left_col) );
 
right.appendChild( document.createTextNode(right_col) );
 
return row;
 
}
 
  
function output_main_report(history) {
+
window.incr=function(obj, key) {
// -- generate summary statistics
+
if (!obj[key]) { obj[key]=1; }
var unique_articles = new Array();
+
else { obj[key]++; }
var namespace_numedits = new Array();
+
}
for (var i=0; i<namespaces.length; i++) {
+
namespace_numedits[ namespaces[i] ] = 0;
+
}
+
namespace_numedits[""] = 0;
+
for (var i=0; i<history.length; i++) {
+
var h = history[i];
+
unique_articles[  h["title"] ]++;
+
namespace_numedits[  h["namespace"] ]++;
+
}
+
var unique_articles_keys = keys(unique_articles);
+
  
// -- output report
+
window.objSum=function(obj, x, y) {
var table = document.createElement("table");
+
var r=0;
table.id = "basic_stats";
+
if (x && y) { for (var k in obj) { r+= (obj[k][x][y] ? obj[k][x][y] : 0); } }
document.getElementById("bodyContent").appendChild(table);
+
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;
 +
}
  
add_stats_row("Username", params["username"].replace(/\+/g, " "));
+
window.percent=function(n, N) {
add_stats_row("Total edits", history.length);
+
return Math.floor(n/N * 1000 + .5)/10;
add_stats_row("Distinct pages edited", unique_articles_keys.length);
+
};
add_stats_row("Average edits/page", new Number(history.length / unique_articles_keys.length).toFixed(3));
+
add_stats_row("First edit", history[ history.length-1 ]["date_text"] );
+
  
// add a blank row
+
if((user=ec.getParamValue('ectarget'))!==null) { addOnloadHook(function(){ec.doEditCount(user);}); }
add_stats_row("", "").childNodes[0].style.height = "1em";
+
  
add_stats_row("(main)", namespace_numedits[""]);
+
function linkedList(x0,y0) {
for (var i=0; i<namespaces.length; i++) {
+
this.first=null;
var nmspc = namespaces[i];
+
this.last=null;
if (namespace_numedits[nmspc]) {
+
this.hash={};
add_stats_row(nmspc, namespace_numedits[nmspc]);
+
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={};
// ===================================== HTML-scraping backend =========================================
+
for (var i=0; i<list.length; ++i) {
 
+
if (typeof obj[list[i]] != 'undefined') { r[list[i]]=obj[list[i]]; }
function add_loading_notice() {
+
}
if (document.getElementById("loading_notice"))
+
return r;
return;
+
var loading = document.createElement("div");
+
loading.id = "loading_notice";
+
loading.innerHTML = "<br><br>Retrieving data<blink>...</blink>";
+
document.getElementById("bodyContent").appendChild(loading);
+
}
+
function remove_loading_notice() {
+
var loading = document.getElementById("loading_notice");
+
if (!loading) return;
+
loading.parentNode.removeChild(loading);
+
 
}
 
}
  
var offset_regexp = /href="[^"]+:Contributions[^"]+offset=(\d+)/gi;
+
window.IntervalSelector=function(data) {
function fetch_data(username, end_date, handler, offset, page_list) {
+
if (!data) { data={}; }
add_loading_notice();
+
this.min=data.min || 10;
var url = prefix + "Special:Contributions/" + username + "?offset=" + offset + "&limit=5000";
+
this.max=data.max || 100;
loadXMLDoc(url,
+
this.span=this.max-this.min;
function (request) {
+
this.lo=data.lo || this.min;
var next_offset = 0;
+
this.hi=data.hi || this.max;
if (request.readyState != 4)  return;
+
this.width=data.width || 400;
if (request.status == 200) {
+
this.height=data.height || 20;
page_list.push(request.responseText);
+
this.scale=this.width/this.span;
//dump_text(request.responseText);
+
this.minBarWidth=data.minBarWidth || 10;
 
+
this.oldmousemove = null;
// see if there's another pageful to get
+
this.createDiv();
var matches = map( function(p){
+
return p.match( /(\d+)$/ )[0];
+
}, request.responseText.match( offset_regexp ) );
+
for (var i=0; i<matches.length; i++) {
+
var v = matches[i] * 1;
+
if (v != 0 && (offset == 0 || v < offset)) {
+
next_offset = v;
+
break;
+
}
+
}
+
}
+
 
+
//next_offset = 0; // for testing only, retrieve just the first page of results
+
 
+
if (next_offset == 0) {
+
parse_data(page_list, handler);
+
} else {
+
// tail recurse
+
fetch_data(username, end_date, handler, next_offset, page_list);
+
}
+
});
+
 
}
 
}
  
  
// input: a list of strings, each string containing the HTML from a single page
+
IntervalSelector.prototype.createDiv=function() {
// output: a list, where each individual entry is a specific edit from history
+
var d=document.createElement('div');
function parse_data(page_list, handler) {
+
d.className='intervalselectorbox';
//var total_len = 0;
+
//d.style.position='absolute';
//for (var i=0; i<page_list.length; i++) total_len += page_list[i].length;
+
d.style.border='1px solid black'; // FIXME
//alert("parsing " + page_list.length + " pages comprising " + total_len + " total bytes");
+
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 last_history_ent = [];
+
IntervalSelector.prototype.updatePosition=function() {
last_history_ent["title"] = "";
+
var d=this.box;
last_history_ent["oldid"] = "";
+
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';
 +
};
  
var edit_history = new Array();
+
IntervalSelector.prototype.mouseDown=function(e) {
for (var pagecnt=0; pagecnt<page_list.length; pagecnt++) {
+
var endWidth=8;
var matches = page_list[pagecnt].match( /^<li>[^(]+\(<a href="[^"]+action=history.*/gim );
+
var pos=this.getMousePos(e);
//dump_lines(matches);
+
var this2=this;
for (var matchcnt=0; matchcnt<matches.length; matchcnt++) {
+
var history_text = matches[matchcnt];
+
  
var history_entry = new Array();
+
var dragFunction=null;
history_entry["date_text"] = history_text.match( /^<li>([^(<]+)/i )[1]
+
var leftPos=findPosX(this.bar);
.replace( / +$/, "");
+
if (pos.x - leftPos < endWidth) { dragFunction=this2.dragLo; }
history_entry["date"] = date_parse( history_entry["date_text"] );
+
else if ( leftPos + parseInt(this.bar.style.width, 10) - pos.x < endWidth) { dragFunction=this2.dragHi; }
history_entry["title"] = history_text.match( /title="([^"]+)"/i )[1]
+
else { dragFunction = this2.dragBar; }
.replace( /&quot;/g, "\"")
+
var x=pos.x, lo=this.lo;
.replace( /&amp;/g, "&");
+
if (document.onmousemove && document.onmousemove.origin != 'IntervalSelector') {
var find_comment = history_text.replace(/<span class="autocomment">.*?<\/span> ?/, "");
+
this.oldmousemove = document.onmousemove;
history_entry["comment"] = ifmatch(find_comment.match( /<span class='comment'>(.*?)<\/span>/ ))
+
.replace(/^\((.*)\)$/, "$1");
+
history_entry["minor"] = /<span class="minor"/.test(history_text);
+
history_entry["oldid"] = ifmatch(history_text.match(/oldid=([0-9]+)/i));
+
 
+
history_entry["namespace"] = "";
+
for (var nmspc_ctr=0; nmspc_ctr<namespaces.length; nmspc_ctr++) {
+
var nmspc = namespaces[nmspc_ctr] + ":";
+
if (history_entry["title"].substring(0, nmspc.length) == nmspc) {
+
history_entry["namespace"] = namespaces[nmspc_ctr];
+
break;
+
}
+
}
+
 
+
//dump_object(history_entry);
+
 
+
if (history_entry["title"] != last_history_ent["title"] || history_entry["oldid"] != last_history_ent["oldid"])
+
edit_history.push(history_entry);
+
last_history_ent = history_entry;
+
}
+
 
}
 
}
 +
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;
 +
};
  
remove_loading_notice();
+
IntervalSelector.prototype.doneDrag=function(){};
 +
IntervalSelector.prototype.dragging=function(){};
  
handler(edit_history);
+
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;
  
// ===================================== test/debug functions =========================================
+
if (docElt) { left=docElt.scrollLeft; }
 +
left = left || window.document.body.scrollLeft || window.document.scrollLeft || 0;
  
function dump_text(text) {
+
if (docElt) { top=docElt.scrollTop; }
  //alert("dump_text, with text of size " + text.length);
+
top = top || window.document.body.scrollTop || window.document.scrollTop || 0;
  
  var pre = document.createElement("pre");
+
x=e.clientX + left;
 +
y=e.clientY + top;
 +
} else { throw new Error ('bad mouse wiggle event in getMousePos'); return; }
 +
}
 +
return {x:x, y:y};
 +
};
  
  var div = document.createElement("div");
+
window.findPosX=function(obj)
  div.style.width = "60em";
+
{
  div.style.maxHeight = "40em";
+
var curleft = 0;
  div.style.overflow = "auto";
+
if (obj.offsetParent)
 
+
{
  pre.appendChild(document.createTextNode(text));
+
while (obj.offsetParent)
  div.appendChild(pre);
+
{
  document.getElementById("bodyContent").appendChild(div);
+
curleft += obj.offsetLeft
 +
obj = obj.offsetParent;
 +
}
 +
}
 +
else if (obj.x)
 +
curleft += obj.x;
 +
return curleft;
 
}
 
}
  
function dump_lines(ary) {
+
window.ts2unix=function(ts) {
  dump_text("--> " + ary.join("\n--> "));
+
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 dump_object(obj) {
+
var d=new Date(u);
var toString = "";
+
return map(zeroFill, [d.getUTCFullYear(), d.getUTCMonth()+1,
for (var prop in obj) {
+
      d.getUTCDate(), d.getUTCHours(),
toString += prop + ": " + obj[prop] + "\n";
+
      d.getUTCMinutes(), d.getUTCSeconds()]).join('');
}
+
dump_text(toString);
+
 
}
 
}
  
 
+
window.zeroFill=function(s, min) {
// ===================================== utility functions =========================================
+
min = min || 2;
 
+
var t=s.toString();
function addOnloadFunction(f) {
+
return repeatString('0', min - t.length) + t;
  if (window.addEventListener) window.addEventListener("load",f,false);
+
  else if (window.attachEvent) window.attachEvent("onload",f);
+
  else {
+
    var oldOnload='_old_onload_'+addOnloadFunction.uid;
+
    addOnloadFunction[oldOnload] = window.onload ? window.onload : function () {};
+
    window.onload = function() { addOnloadFunction[oldOnload]();  f(); }
+
    ++addOnloadFunction.uid;
+
  }
+
 
}
 
}
  
 
+
window.map=function(f, o) {
function parse_params() {
+
if (isArray(o)) { return map_array(f,o); }
  var pairs = document.location.search.substring(1).split("&");
+
return map_object(f,o);
  var ret = [];
+
  for (var i=0; i < pairs.length; i++) {
+
    var values = pairs[i].split("=");
+
    ret[values[0]] = unescape(values[1]);
+
  }
+
  return ret;  
+
 
}
 
}
 +
window.isArray =function(x) { return x instanceof Array; }
  
 
+
window.map_array=function(f,o) {
function loadXMLDoc(url, handler)
+
var ret=[];
{
+
for (var i=0; i<o.length; ++i) {
    // branch for native XMLHttpRequest object
+
ret.push(f(o[i]));
    if (window.XMLHttpRequest) {
+
}
        req = new XMLHttpRequest();
+
return ret;
req.onreadystatechange = function () {handler(req)};
+
        req.open("GET", url, true);
+
        req.send(null);
+
    // branch for IE/Windows ActiveX version
+
    } else if (window.ActiveXObject) {
+
        req = new ActiveXObject("Microsoft.XMLHTTP");
+
        if (req) {
+
            req.onreadystatechange = function () {handler(req)};
+
            req.open("GET", url, true);
+
            req.send();
+
        }
+
    }
+
 
}
 
}
  
 
+
window.map_object=function(f,o) {
// see http://search.cpan.org/dist/perl/pod/perlfunc.pod#map
+
var ret={};
function map (handler, list) {
+
for (var i in o) { ret[o]=f(o[i]); }
  var ret = new Array();
+
return ret;
  for (var i=0; i<list.length; i++) {
+
    ret[i] = handler( list[i] );
+
    // ret.push( handler( list[i] ) );
+
  }
+
  return ret;
+
 
}
 
}
  
// see http://search.cpan.org/dist/perl/pod/perlfunc.pod#keys
+
window.repeatString=function(s,mult) {
function keys (obj) {
+
var ret='';
var ret = new Array();
+
for (var i=0; i<mult; ++i) { ret += s; }
for (var key in obj) {
+
ret.push(key);
+
}
+
 
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 ifmatch(ary) {
+
function isMethodOf(klass, fn) {
if (ary && ary.length >= 2) {
+
for (var f in klass.prototype) {
return ary[1];
+
if (fn===klass.prototype[f]) { return true; }
} else {
+
return "";
+
 
}
 
}
 +
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')