User:Ebrahames/All-in-one.js

/* This is an experimental attempt to integrate various user scripts, which the user can switch on or off in the preferences page. Read more at http://en.wikipedia.org/wiki/User:Cameltrader/All-in-one.js/Description This page was designed with the hope to make scripts more readable. It is both valid javascript and valid wikimarkup. */

Main.js

$(function {    Ajax.Responders.register({ onException: function (exception) { alert("exception=" + exception + "; message=" + exception.message); }   });    new CameltraderPluginManager; });

/* // Probably a future user script function fixCyrillicAnchors { // Precompute utf-8-to-codepoint conversion table var cyr = {}; for (var ch = 0x0400; ch <= 0x052f; ch++) { var chUtf8 = new Number( ((ch << 2) & 0x1f00) + (ch & 0x3f) + 0xc080 ).toString(16); var chPercent = "%" + chUtf8.substring(0, 2) + "%" + chUtf8.substring(2); cyr[chPercent] = cyr[chPercent.toUpperCase] = String.fromCharCode(ch); }   for (var i = 0; i < document.links.length; i++) { var href = document.links[i].href; var re = /%(D[01])%([0-9a-fA-F]{2})/gi; while (true) { re.lastIndex = 0; var m = re.exec(href); if (m == null) { break; }           href = href.substring(0, m.index) + cyr[m[0]] + href.substring(m.index + m[0].length); }       document.links[i].href = href; } } //     prototype.js   /*  Prototype JavaScript framework, version 1.5.0 * (c) 2005-2007 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://prototype.conio.net/ * /*--*/

var Prototype = { Version: '1.5.0', BrowserFeatures: { XPath: !!document.evaluate },

ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', emptyFunction: function {}, K: function(x) { return x } }

var Class = { create: function { return function { this.initialize.apply(this, arguments); } } }

var Abstract = new Object;

Object.extend = function(destination, source) { for (var property in source) { destination[property] = source[property]; } return destination; }

Object.extend(Object, { inspect: function(object) {    try {      if (object === undefined) return 'undefined';      if (object === null) return 'null';      return object.inspect ? object.inspect : object.toString;    } catch (e) {      if (e instanceof RangeError) return '...';      throw e;    }  },

keys: function(object) { var keys = []; for (var property in object) keys.push(property); return keys; },

values: function(object) { var values = []; for (var property in object) values.push(object[property]); return values; },

clone: function(object) { return Object.extend({}, object); } });

Function.prototype.bind = function { var __method = this, args = $A(arguments), object = args.shift; return function { return __method.apply(object, args.concat($A(arguments))); } }

Function.prototype.bindAsEventListener = function(object) { var __method = this, args = $A(arguments), object = args.shift; return function(event) { return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); } }

Object.extend(Number.prototype, { toColorPart: function {    var digits = this.toString(16);    if (this < 16) return '0' + digits;    return digits;  },

succ: function { return this + 1; },

times: function(iterator) { $R(0, this, true).each(iterator); return this; } });

var Try = { these: function { var returnValue;

for (var i = 0, length = arguments.length; i < length; i++) { var lambda = arguments[i]; try { returnValue = lambda; break; } catch (e) {} }

return returnValue; } }

/*--*/

var PeriodicalExecuter = Class.create; PeriodicalExecuter.prototype = { initialize: function(callback, frequency) { this.callback = callback; this.frequency = frequency; this.currentlyExecuting = false;

this.registerCallback; },

registerCallback: function { this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); },

stop: function { if (!this.timer) return; clearInterval(this.timer); this.timer = null; },

onTimerEvent: function { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; this.callback(this); } finally { this.currentlyExecuting = false; }   }  } } String.interpret = function(value){ return value == null ? '' : String(value); }

Object.extend(String.prototype, { gsub: function(pattern, replacement) {    var result = '', source = this, match;    replacement = arguments.callee.prepareReplacement(replacement);

while (source.length > 0) { if (match = source.match(pattern)) { result += source.slice(0, match.index); result += String.interpret(replacement(match)); source = source.slice(match.index + match[0].length); } else { result += source, source = ''; }   }    return result; },

sub: function(pattern, replacement, count) { replacement = this.gsub.prepareReplacement(replacement); count = count === undefined ? 1 : count;

return this.gsub(pattern, function(match) {     if (--count < 0) return match[0];      return replacement(match);    }); },

scan: function(pattern, iterator) { this.gsub(pattern, iterator); return this; },

truncate: function(length, truncation) { length = length || 30; truncation = truncation === undefined ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : this; },

strip: function { return this.replace(/^\s+/, ).replace(/\s+$/, ); },

stripTags: function { return this.replace(/<\/?[^>]+>/gi, ''); },

stripScripts: function { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); },

extractScripts: function { var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) {     return (scriptTag.match(matchOne) || [, ])[1];    }); },

evalScripts: function { return this.extractScripts.map(function(script) { return eval(script) }); },

escapeHTML: function { var div = document.createElement('div'); var text = document.createTextNode(this); div.appendChild(text); return div.innerHTML; },

unescapeHTML: function { var div = document.createElement('div'); div.innerHTML = this.stripTags; return div.childNodes[0] ? (div.childNodes.length > 1 ?     $A(div.childNodes).inject(,function(memo,node){ return memo+node.nodeValue }) :      div.childNodes[0].nodeValue) : ; },

toQueryParams: function(separator) { var match = this.strip.match(/([^?#]*)(#.*)?$/); if (!match) return {};

return match[1].split(separator || '&').inject({}, function(hash, pair) {     if ((pair = pair.split('='))[0]) {        var name = decodeURIComponent(pair[0]);        var value = pair[1] ? decodeURIComponent(pair[1]) : undefined;

if (hash[name] !== undefined) { if (hash[name].constructor != Array) hash[name] = [hash[name]]; if (value) hash[name].push(value); }       else hash[name] = value; }     return hash; }); },

toArray: function { return this.split(''); },

succ: function { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); },

camelize: function { var parts = this.split('-'), len = parts.length; if (len == 1) return parts[0];

var camelized = this.charAt(0) == '-' ? parts[0].charAt(0).toUpperCase + parts[0].substring(1) : parts[0];

for (var i = 1; i < len; i++) camelized += parts[i].charAt(0).toUpperCase + parts[i].substring(1);

return camelized; },

capitalize: function{ return this.charAt(0).toUpperCase + this.substring(1).toLowerCase; },

underscore: function { return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase; },

dasherize: function { return this.gsub(/_/,'-'); },

inspect: function(useDoubleQuotes) { var escapedString = this.replace(/\\/g, '\\\\'); if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; else return "'" + escapedString.replace(/'/g, '\\\'') + "'"; } });

String.prototype.gsub.prepareReplacement = function(replacement) { if (typeof replacement == 'function') return replacement; var template = new Template(replacement); return function(match) { return template.evaluate(match) }; }

String.prototype.parseQuery = String.prototype.toQueryParams;

var Template = Class.create; Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; Template.prototype = { initialize: function(template, pattern) { this.template = template.toString; this.pattern = pattern || Template.Pattern; },

evaluate: function(object) { return this.template.gsub(this.pattern, function(match) {     var before = match[1];      if (before == '\\') return match[2];      return before + String.interpret(object[match[3]]);    }); } }

var $break   = new Object; var $continue = new Object;

var Enumerable = { each: function(iterator) { var index = 0; try { this._each(function(value) {       try {          iterator(value, index++);        } catch (e) {          if (e != $continue) throw e;        }      }); } catch (e) { if (e != $break) throw e;   } return this; },

eachSlice: function(number, iterator) { var index = -number, slices = [], array = this.toArray; while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.map(iterator); },

all: function(iterator) { var result = true; this.each(function(value, index) {     result = result && !!(iterator || Prototype.K)(value, index);      if (!result) throw $break;    }); return result; },

any: function(iterator) { var result = false; this.each(function(value, index) {     if (result = !!(iterator || Prototype.K)(value, index))        throw $break;    }); return result; },

collect: function(iterator) { var results = []; this.each(function(value, index) {     results.push((iterator || Prototype.K)(value, index));    }); return results; },

detect: function(iterator) { var result; this.each(function(value, index) {     if (iterator(value, index)) {        result = value;        throw $break;      }    }); return result; },

findAll: function(iterator) { var results = []; this.each(function(value, index) {     if (iterator(value, index))        results.push(value);    }); return results; },

grep: function(pattern, iterator) { var results = []; this.each(function(value, index) {     var stringValue = value.toString;      if (stringValue.match(pattern))        results.push((iterator || Prototype.K)(value, index));    }) return results; },

include: function(object) { var found = false; this.each(function(value) {     if (value == object) {        found = true;        throw $break;      }    }); return found; },

inGroupsOf: function(number, fillWith) { fillWith = fillWith === undefined ? null : fillWith; return this.eachSlice(number, function(slice) {     while(slice.length < number) slice.push(fillWith);      return slice;    }); },

inject: function(memo, iterator) { this.each(function(value, index) {     memo = iterator(memo, value, index);    }); return memo; },

invoke: function(method) { var args = $A(arguments).slice(1); return this.map(function(value) {     return value[method].apply(value, args);    }); },

max: function(iterator) { var result; this.each(function(value, index) {     value = (iterator || Prototype.K)(value, index);      if (result == undefined || value >= result)        result = value;    }); return result; },

min: function(iterator) { var result; this.each(function(value, index) {     value = (iterator || Prototype.K)(value, index);      if (result == undefined || value < result)        result = value;    }); return result; },

partition: function(iterator) { var trues = [], falses = []; this.each(function(value, index) {     ((iterator || Prototype.K)(value, index) ? trues : falses).push(value);   }); return [trues, falses]; },

pluck: function(property) { var results = []; this.each(function(value, index) {     results.push(value[property]);    }); return results; },

reject: function(iterator) { var results = []; this.each(function(value, index) {     if (!iterator(value, index))        results.push(value);    }); return results; },

sortBy: function(iterator) { return this.map(function(value, index) {     return {value: value, criteria: iterator(value, index)};    }).sort(function(left, right) {      var a = left.criteria, b = right.criteria;      return a < b ? -1 : a > b ? 1 : 0;    }).pluck('value'); },

toArray: function { return this.map; },

zip: function { var iterator = Prototype.K, args = $A(arguments); if (typeof args.last == 'function') iterator = args.pop;

var collections = [this].concat(args).map($A); return this.map(function(value, index) {     return iterator(collections.pluck(index));    }); },

size: function { return this.toArray.length; },

inspect: function { return '#'; } }

Object.extend(Enumerable, { map:     Enumerable.collect,  find:    Enumerable.detect,  select:  Enumerable.findAll,  member:  Enumerable.include,  entries: Enumerable.toArray }); var $A = Array.from = function(iterable) { if (!iterable) return []; if (iterable.toArray) { return iterable.toArray; } else { var results = []; for (var i = 0, length = iterable.length; i < length; i++) results.push(iterable[i]); return results; } }

Object.extend(Array.prototype, Enumerable);

if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, { _each: function(iterator) {    for (var i = 0, length = this.length; i < length; i++)      iterator(this[i]);  },

clear: function { this.length = 0; return this; },

first: function { return this[0]; },

last: function { return this[this.length - 1]; },

compact: function { return this.select(function(value) {     return value != null;    }); },

flatten: function { return this.inject([], function(array, value) {     return array.concat(value && value.constructor == Array ? value.flatten : [value]);   }); },

without: function { var values = $A(arguments); return this.select(function(value) {     return !values.include(value);    }); },

indexOf: function(object) { for (var i = 0, length = this.length; i < length; i++) if (this[i] == object) return i;   return -1; },

reverse: function(inline) { return (inline !== false ? this : this.toArray)._reverse; },

reduce: function { return this.length > 1 ? this : this[0]; },

uniq: function { return this.inject([], function(array, value) {     return array.include(value) ? array : array.concat([value]);    }); },

clone: function { return [].concat(this); },

size: function { return this.length; },

inspect: function { return '[' + this.map(Object.inspect).join(', ') + ']'; } });

Array.prototype.toArray = Array.prototype.clone;

function $w(string){ string = string.strip; return string ? string.split(/\s+/) : []; }

if(window.opera){ Array.prototype.concat = function{ var array = []; for(var i = 0, length = this.length; i < length; i++) array.push(this[i]); for(var i = 0, length = arguments.length; i < length; i++) { if(arguments[i].constructor == Array) { for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) array.push(arguments[i][j]); } else { array.push(arguments[i]); }   }    return array; } } var Hash = function(obj) { Object.extend(this, obj || {}); };

Object.extend(Hash, { toQueryString: function(obj) {    var parts = [];

this.prototype._each.call(obj, function(pair) {     if (!pair.key) return;

if (pair.value && pair.value.constructor == Array) { var values = pair.value.compact; if (values.length < 2) pair.value = values.reduce; else { key = encodeURIComponent(pair.key); values.each(function(value) {           value = value != undefined ? encodeURIComponent(value) : '';            parts.push(key + '=' + encodeURIComponent(value));          }); return; }     }      if (pair.value == undefined) pair[1] = ''; parts.push(pair.map(encodeURIComponent).join('=')); });

return parts.join('&'); } });

Object.extend(Hash.prototype, Enumerable); Object.extend(Hash.prototype, { _each: function(iterator) {    for (var key in this) {      var value = this[key];      if (value && value == Hash.prototype[key]) continue;

var pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } },

keys: function { return this.pluck('key'); },

values: function { return this.pluck('value'); },

merge: function(hash) { return $H(hash).inject(this, function(mergedHash, pair) {     mergedHash[pair.key] = pair.value;      return mergedHash;    }); },

remove: function { var result; for(var i = 0, length = arguments.length; i < length; i++) { var value = this[arguments[i]]; if (value !== undefined){ if (result === undefined) result = value; else { if (result.constructor != Array) result = [result]; result.push(value) }     }      delete this[arguments[i]]; }   return result; },

toQueryString: function { return Hash.toQueryString(this); },

inspect: function { return '#'; } });

function $H(object) { if (object && object.constructor == Hash) return object; return new Hash(object); }; ObjectRange = Class.create; Object.extend(ObjectRange.prototype, Enumerable); Object.extend(ObjectRange.prototype, { initialize: function(start, end, exclusive) {    this.start = start;    this.end = end;    this.exclusive = exclusive;  },

_each: function(iterator) { var value = this.start; while (this.include(value)) { iterator(value); value = value.succ; } },

include: function(value) { if (value < this.start) return false; if (this.exclusive) return value < this.end; return value <= this.end; } });

var $R = function(start, end, exclusive) { return new ObjectRange(start, end, exclusive); }

var Ajax = { getTransport: function { return Try.these(     function {return new XMLHttpRequest},      function {return new ActiveXObject('Msxml2.XMLHTTP')},      function {return new ActiveXObject('Microsoft.XMLHTTP')}    ) || false; },

activeRequestCount: 0 }

Ajax.Responders = { responders: [],

_each: function(iterator) { this.responders._each(iterator); },

register: function(responder) { if (!this.include(responder)) this.responders.push(responder); },

unregister: function(responder) { this.responders = this.responders.without(responder); },

dispatch: function(callback, request, transport, json) { this.each(function(responder) {     if (typeof responder[callback] == 'function') {        try {          responder[callback].apply(responder, [request, transport, json]);        } catch (e) {}      }    }); } };

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({ onCreate: function {    Ajax.activeRequestCount++;  },  onComplete: function {    Ajax.activeRequestCount--;  } });

Ajax.Base = function {}; Ajax.Base.prototype = { setOptions: function(options) { this.options = { method:      'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding:    'UTF-8', parameters:  '' }   Object.extend(this.options, options || {});

this.options.method = this.options.method.toLowerCase; if (typeof this.options.parameters == 'string') this.options.parameters = this.options.parameters.toQueryParams; } }

Ajax.Request = Class.create; Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Request.prototype = Object.extend(new Ajax.Base, { _complete: false,

initialize: function(url, options) { this.transport = Ajax.getTransport; this.setOptions(options); this.request(url); },

request: function(url) { this.url = url; this.method = this.options.method; var params = this.options.parameters;

if (!['get', 'post'].include(this.method)) { // simulate other verbs over post params['_method'] = this.method; this.method = 'post'; }

params = Hash.toQueryString(params); if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='

// when GET, append parameters to URL if (this.method == 'get' && params) this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params;

try { Ajax.Responders.dispatch('onCreate', this, this.transport);

this.transport.open(this.method.toUpperCase, this.url,       this.options.asynchronous);

if (this.options.asynchronous) setTimeout(function { this.respondToReadyState(1) }.bind(this), 10);

this.transport.onreadystatechange = this.onStateChange.bind(this); this.setRequestHeaders;

var body = this.method == 'post' ? (this.options.postBody || params) : null;

this.transport.send(body);

/* Force Firefox to handle ready state 4 for synchronous requests */ if (!this.options.asynchronous && this.transport.overrideMimeType) this.onStateChange;

}   catch (e) { this.dispatchException(e); } },

onStateChange: function { var readyState = this.transport.readyState; if (readyState > 1 && !((readyState == 4) && this._complete)) this.respondToReadyState(this.transport.readyState); },

setRequestHeaders: function { var headers = { 'X-Requested-With': 'XMLHttpRequest', 'X-Prototype-Version': Prototype.Version, 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' };

if (this.method == 'post') { headers['Content-type'] = this.options.contentType + (this.options.encoding ? '; charset=' + this.options.encoding : '');

/* Force "Connection: close" for older Mozilla browsers to work * around a bug where XMLHttpRequest sends an incorrect * Content-length header. See Mozilla Bugzilla #246651. */     if (this.transport.overrideMimeType &&          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) headers['Connection'] = 'close'; }

// user-defined headers if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders;

if (typeof extras.push == 'function') for (var i = 0, length = extras.length; i < length; i += 2) headers[extras[i]] = extras[i+1]; else $H(extras).each(function(pair) { headers[pair.key] = pair.value }); }

for (var name in headers) this.transport.setRequestHeader(name, headers[name]); },

success: function { return !this.transport.status || (this.transport.status >= 200 && this.transport.status < 300); },

respondToReadyState: function(readyState) { var state = Ajax.Request.Events[readyState]; var transport = this.transport, json = this.evalJSON;

if (state == 'Complete') { try { this._complete = true; (this.options['on' + this.transport.status]        || this.options['on' + (this.success ? 'Success' : 'Failure')]        || Prototype.emptyFunction)(transport, json); } catch (e) { this.dispatchException(e); }

if ((this.getHeader('Content-type') || 'text/javascript').strip.       match(/^(text|application)\/(x-)?(java|ecma)script(.*)?$/i)) this.evalResponse; }

try { (this.options['on' + state] || Prototype.emptyFunction)(transport, json); Ajax.Responders.dispatch('on' + state, this, transport, json); } catch (e) { this.dispatchException(e); }

if (state == 'Complete') { // avoid memory leak in MSIE: clean up     this.transport.onreadystatechange = Prototype.emptyFunction; } },

getHeader: function(name) { try { return this.transport.getResponseHeader(name); } catch (e) { return null } },

evalJSON: function { try { var json = this.getHeader('X-JSON'); return json ? eval('(' + json + ')') : null; } catch (e) { return null } },

evalResponse: function { try { return eval(this.transport.responseText); } catch (e) { this.dispatchException(e); } },

dispatchException: function(exception) { (this.options.onException || Prototype.emptyFunction)(this, exception); Ajax.Responders.dispatch('onException', this, exception); } });

Ajax.Updater = Class.create;

Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { initialize: function(container, url, options) {    this.container = {      success: (container.success || container),      failure: (container.failure || (container.success ? null : container))    }

this.transport = Ajax.getTransport; this.setOptions(options);

var onComplete = this.options.onComplete || Prototype.emptyFunction; this.options.onComplete = (function(transport, param) {     this.updateContent;      onComplete(transport, param);    }).bind(this);

this.request(url); },

updateContent: function { var receiver = this.container[this.success ? 'success' : 'failure']; var response = this.transport.responseText;

if (!this.options.evalScripts) response = response.stripScripts;

if (receiver = $(receiver)) { if (this.options.insertion) new this.options.insertion(receiver, response); else receiver.update(response); }

if (this.success) { if (this.onComplete) setTimeout(this.onComplete.bind(this), 10); } } });

Ajax.PeriodicalUpdater = Class.create; Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base, { initialize: function(container, url, options) {    this.setOptions(options);    this.onComplete = this.options.onComplete;

this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1);

this.updater = {}; this.container = container; this.url = url;

this.start; },

start: function { this.options.onComplete = this.updateComplete.bind(this); this.onTimerEvent; },

stop: function { this.updater.options.onComplete = undefined; clearTimeout(this.timer); (this.onComplete || Prototype.emptyFunction).apply(this, arguments); },

updateComplete: function(request) { if (this.options.decay) { this.decay = (request.responseText == this.lastText ?       this.decay * this.options.decay : 1);

this.lastText = request.responseText; }   this.timer = setTimeout(this.onTimerEvent.bind(this),      this.decay * this.frequency * 1000); },

onTimerEvent: function { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); function $(element) { if (arguments.length > 1) {    for (var i = 0, elements = [], length = arguments.length; i < length; i++)      elements.push($(arguments[i]));    return elements;  }  if (typeof element == 'string')    element = document.getElementById(element);  return Element.extend(element); }

if (Prototype.BrowserFeatures.XPath) { document._getElementsByXPath = function(expression, parentElement) { var results = []; var query = document.evaluate(expression, $(parentElement) || document,     null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0, length = query.snapshotLength; i < length; i++) results.push(query.snapshotItem(i)); return results; }; }

document.getElementsByClassName = function(className, parentElement) { if (Prototype.BrowserFeatures.XPath) { var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; return document._getElementsByXPath(q, parentElement); } else { var children = ($(parentElement) || document.body).getElementsByTagName('*'); var elements = [], child; for (var i = 0, length = children.length; i < length; i++) { child = children[i]; if (Element.hasClassName(child, className)) elements.push(Element.extend(child)); }   return elements; } };

/*--*/

if (!window.Element) var Element = new Object;

Element.extend = function(element) { if (!element || _nativeExtensions || element.nodeType == 3) return element;

if (!element._extended && element.tagName && element != window) { var methods = Object.clone(Element.Methods), cache = Element.extend.cache;

if (element.tagName == 'FORM') Object.extend(methods, Form.Methods); if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) Object.extend(methods, Form.Element.Methods);

Object.extend(methods, Element.Methods.Simulated);

for (var property in methods) { var value = methods[property]; if (typeof value == 'function' && !(property in element)) element[property] = cache.findOrStore(value); } }

element._extended = true; return element; };

Element.extend.cache = { findOrStore: function(value) { return this[value] = this[value] || function { return value.apply(null, [this].concat($A(arguments))); } } };

Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; },

toggle: function(element) { element = $(element); Element[Element.visible(element) ? 'hide' : 'show'](element); return element; },

hide: function(element) { $(element).style.display = 'none'; return element; },

show: function(element) { $(element).style.display = ''; return element; },

remove: function(element) { element = $(element); element.parentNode.removeChild(element); return element; },

update: function(element, html) { html = typeof html == 'undefined' ? '' : html.toString; $(element).innerHTML = html.stripScripts; setTimeout(function {html.evalScripts}, 10); return element; },

replace: function(element, html) { element = $(element); html = typeof html == 'undefined' ? '' : html.toString; if (element.outerHTML) { element.outerHTML = html.stripScripts; } else { var range = element.ownerDocument.createRange; range.selectNodeContents(element); element.parentNode.replaceChild(       range.createContextualFragment(html.stripScripts), element); }   setTimeout(function {html.evalScripts}, 10); return element; },

inspect: function(element) { element = $(element); var result = '<' + element.tagName.toLowerCase; $H({'id': 'id', 'className': 'class'}).each(function(pair) {     var property = pair.first, attribute = pair.last;      var value = (element[property] || '').toString;      if (value) result += ' ' + attribute + '=' + value.inspect(true);    }); return result + '>'; },

recursivelyCollect: function(element, property) { element = $(element); var elements = []; while (element = element[property]) if (element.nodeType == 1) elements.push(Element.extend(element)); return elements; },

ancestors: function(element) { return $(element).recursivelyCollect('parentNode'); },

descendants: function(element) { return $A($(element).getElementsByTagName('*')); },

immediateDescendants: function(element) { if (!(element = $(element).firstChild)) return []; while (element && element.nodeType != 1) element = element.nextSibling; if (element) return [element].concat($(element).nextSiblings); return []; },

previousSiblings: function(element) { return $(element).recursivelyCollect('previousSibling'); },

nextSiblings: function(element) { return $(element).recursivelyCollect('nextSibling'); },

siblings: function(element) { element = $(element); return element.previousSiblings.reverse.concat(element.nextSiblings); },

match: function(element, selector) { if (typeof selector == 'string') selector = new Selector(selector); return selector.match($(element)); },

up: function(element, expression, index) { return Selector.findElement($(element).ancestors, expression, index); },

down: function(element, expression, index) { return Selector.findElement($(element).descendants, expression, index); },

previous: function(element, expression, index) { return Selector.findElement($(element).previousSiblings, expression, index); },

next: function(element, expression, index) { return Selector.findElement($(element).nextSiblings, expression, index); },

getElementsBySelector: function { var args = $A(arguments), element = $(args.shift); return Selector.findChildElements(element, args); },

getElementsByClassName: function(element, className) { return document.getElementsByClassName(className, element); },

readAttribute: function(element, name) { element = $(element); if (document.all && !window.opera) { var t = Element._attributeTranslations; if (t.values[name]) return t.values[name](element, name); if (t.names[name]) name = t.names[name]; var attribute = element.attributes[name]; if(attribute) return attribute.nodeValue; }   return element.getAttribute(name); },

getHeight: function(element) { return $(element).getDimensions.height; },

getWidth: function(element) { return $(element).getDimensions.width; },

classNames: function(element) { return new Element.ClassNames(element); },

hasClassName: function(element, className) { if (!(element = $(element))) return; var elementClassName = element.className; if (elementClassName.length == 0) return false; if (elementClassName == className ||       elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) return true; return false; },

addClassName: function(element, className) { if (!(element = $(element))) return; Element.classNames(element).add(className); return element; },

removeClassName: function(element, className) { if (!(element = $(element))) return; Element.classNames(element).remove(className); return element; },

toggleClassName: function(element, className) { if (!(element = $(element))) return; Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); return element; },

observe: function { Event.observe.apply(Event, arguments); return $A(arguments).first; },

stopObserving: function { Event.stopObserving.apply(Event, arguments); return $A(arguments).first; },

// removes whitespace-only text node children cleanWhitespace: function(element) { element = $(element); var node = element.firstChild; while (node) { var nextNode = node.nextSibling; if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) element.removeChild(node); node = nextNode; }   return element; },

empty: function(element) { return $(element).innerHTML.match(/^\s*$/); },

descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); while (element = element.parentNode) if (element == ancestor) return true; return false; },

scrollTo: function(element) { element = $(element); var pos = Position.cumulativeOffset(element); window.scrollTo(pos[0], pos[1]); return element; },

getStyle: function(element, style) { element = $(element); if (['float','cssFloat'].include(style)) style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat'); style = style.camelize; var value = element.style[style]; if (!value) { if (document.defaultView && document.defaultView.getComputedStyle) { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } else if (element.currentStyle) { value = element.currentStyle[style]; }   }

if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none')) value = element['offset'+style.capitalize] + 'px';

if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) if (Element.getStyle(element, 'position') == 'static') value = 'auto'; if(style == 'opacity') { if(value) return parseFloat(value); if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) if(value[1]) return parseFloat(value[1]) / 100; return 1.0; }   return value == 'auto' ? null : value; },

setStyle: function(element, style) { element = $(element); for (var name in style) { var value = style[name]; if(name == 'opacity') { if (value == 1) { value = (/Gecko/.test(navigator.userAgent) &&           !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0;         if(/MSIE/.test(navigator.userAgent) && !window.opera) element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,);       } else if(value == ) {          if(/MSIE/.test(navigator.userAgent) && !window.opera)            element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); } else { if(value < 0.00001) value = 0; if(/MSIE/.test(navigator.userAgent) && !window.opera) element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') +             'alpha(opacity='+value*100+')';        }      } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat';      element.style[name.camelize] = value;    }    return element;  },

getDimensions: function(element) { element = $(element); var display = $(element).getStyle('display'); if (display != 'none' && display != null) // Safari bug return {width: element.offsetWidth, height: element.offsetHeight};

// All *Width and *Height properties give 0 on elements with display none, // so enable the element temporarily var els = element.style; var originalVisibility = els.visibility; var originalPosition = els.position; var originalDisplay = els.display; els.visibility = 'hidden'; els.position = 'absolute'; els.display = 'block'; var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; els.display = originalDisplay; els.position = originalPosition; els.visibility = originalVisibility; return {width: originalWidth, height: originalHeight}; },

makePositioned: function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); if (pos == 'static' || !pos) { element._madePositioned = true; element.style.position = 'relative'; // Opera returns the offset relative to the positioning context, when an     // element is position relative but top and left have not been defined if (window.opera) { element.style.top = 0; element.style.left = 0; }   }    return element; },

undoPositioned: function(element) { element = $(element); if (element._madePositioned) { element._madePositioned = undefined; element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = ''; }   return element; },

makeClipping: function(element) { element = $(element); if (element._overflow) return element; element._overflow = element.style.overflow || 'auto'; if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') element.style.overflow = 'hidden'; return element; },

undoClipping: function(element) { element = $(element); if (!element._overflow) return element; element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; element._overflow = null; return element; } };

Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf});

Element._attributeTranslations = {};

Element._attributeTranslations.names = { colspan:  "colSpan", rowspan:  "rowSpan", valign:   "vAlign", datetime: "dateTime", accesskey: "accessKey", tabindex: "tabIndex", enctype:  "encType", maxlength: "maxLength", readonly: "readOnly", longdesc: "longDesc" };

Element._attributeTranslations.values = { _getAttr: function(element, attribute) { return element.getAttribute(attribute, 2); },

_flag: function(element, attribute) { return $(element).hasAttribute(attribute) ? attribute : null; },

style: function(element) { return element.style.cssText.toLowerCase; },

title: function(element) { var node = element.getAttributeNode('title'); return node.specified ? node.nodeValue : null; } };

Object.extend(Element._attributeTranslations.values, { href: Element._attributeTranslations.values._getAttr,  src:  Element._attributeTranslations.values._getAttr,  disabled: Element._attributeTranslations.values._flag,  checked:  Element._attributeTranslations.values._flag,  readonly: Element._attributeTranslations.values._flag,  multiple: Element._attributeTranslations.values._flag });

Element.Methods.Simulated = { hasAttribute: function(element, attribute) { var t = Element._attributeTranslations; attribute = t.names[attribute] || attribute; return $(element).getAttributeNode(attribute).specified; } };

// IE is missing .innerHTML support for TABLE-related elements if (document.all && !window.opera){ Element.Methods.update = function(element, html) { element = $(element); html = typeof html == 'undefined' ? '' : html.toString; var tagName = element.tagName.toUpperCase; if (['THEAD','TBODY','TR','TD'].include(tagName)) { var div = document.createElement('div'); switch (tagName) { case 'THEAD': case 'TBODY': div.innerHTML = ' '; depth = 2; break; case 'TR': div.innerHTML = ' '; depth = 3; break; case 'TD': div.innerHTML = ' '; depth = 4; }     $A(element.childNodes).each(function(node){        element.removeChild(node)      }); depth.times(function{ div = div.firstChild });

$A(div.childNodes).each(       function(node){ element.appendChild(node) }); } else { element.innerHTML = html.stripScripts; }   setTimeout(function {html.evalScripts}, 10); return element; } };

Object.extend(Element, Element.Methods);

var _nativeExtensions = false;

if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) {   var className = 'HTML' + tag + 'Element';    if(window[className]) return;    var klass = window[className] = {};    klass.prototype = document.createElement(tag ? tag.toLowerCase : 'div').__proto__; });

Element.addMethods = function(methods) { Object.extend(Element.Methods, methods || {});

function copy(methods, destination, onlyIfAbsent) { onlyIfAbsent = onlyIfAbsent || false; var cache = Element.extend.cache; for (var property in methods) { var value = methods[property]; if (!onlyIfAbsent || !(property in destination)) destination[property] = cache.findOrStore(value); } }

if (typeof HTMLElement != 'undefined') { copy(Element.Methods, HTMLElement.prototype); copy(Element.Methods.Simulated, HTMLElement.prototype, true); copy(Form.Methods, HTMLFormElement.prototype); [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) {     copy(Form.Element.Methods, klass.prototype);    }); _nativeExtensions = true; } }

var Toggle = new Object; Toggle.display = Element.toggle;

/*--*/

Abstract.Insertion = function(adjacency) { this.adjacency = adjacency; }

Abstract.Insertion.prototype = { initialize: function(element, content) { this.element = $(element); this.content = content.stripScripts;

if (this.adjacency && this.element.insertAdjacentHTML) { try { this.element.insertAdjacentHTML(this.adjacency, this.content); } catch (e) { var tagName = this.element.tagName.toUpperCase; if (['TBODY', 'TR'].include(tagName)) { this.insertContent(this.contentFromAnonymousTable); } else { throw e;       } }   } else { this.range = this.element.ownerDocument.createRange; if (this.initializeRange) this.initializeRange; this.insertContent([this.range.createContextualFragment(this.content)]); }

setTimeout(function {content.evalScripts}, 10); },

contentFromAnonymousTable: function { var div = document.createElement('div'); div.innerHTML = ' '; return $A(div.childNodes[0].childNodes[0].childNodes); } }

var Insertion = new Object;

Insertion.Before = Class.create; Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { initializeRange: function {    this.range.setStartBefore(this.element);  },

insertContent: function(fragments) { fragments.each((function(fragment) { this.element.parentNode.insertBefore(fragment, this.element); }).bind(this)); } });

Insertion.Top = Class.create; Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { initializeRange: function {    this.range.selectNodeContents(this.element);    this.range.collapse(true);  },

insertContent: function(fragments) { fragments.reverse(false).each((function(fragment) { this.element.insertBefore(fragment, this.element.firstChild); }).bind(this)); } });

Insertion.Bottom = Class.create; Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { initializeRange: function {    this.range.selectNodeContents(this.element);    this.range.collapse(this.element);  },

insertContent: function(fragments) { fragments.each((function(fragment) { this.element.appendChild(fragment); }).bind(this)); } });

Insertion.After = Class.create; Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { initializeRange: function {    this.range.setStartAfter(this.element);  },

insertContent: function(fragments) { fragments.each((function(fragment) { this.element.parentNode.insertBefore(fragment,       this.element.nextSibling); }).bind(this)); } });

/*--*/

Element.ClassNames = Class.create; Element.ClassNames.prototype = { initialize: function(element) { this.element = $(element); },

_each: function(iterator) { this.element.className.split(/\s+/).select(function(name) {     return name.length > 0;    })._each(iterator); },

set: function(className) { this.element.className = className; },

add: function(classNameToAdd) { if (this.include(classNameToAdd)) return; this.set($A(this).concat(classNameToAdd).join(' ')); },

remove: function(classNameToRemove) { if (!this.include(classNameToRemove)) return; this.set($A(this).without(classNameToRemove).join(' ')); },

toString: function { return $A(this).join(' '); } };

Object.extend(Element.ClassNames.prototype, Enumerable); var Selector = Class.create; Selector.prototype = { initialize: function(expression) { this.params = {classNames: []}; this.expression = expression.toString.strip; this.parseExpression; this.compileMatcher; },

parseExpression: function { function abort(message) { throw 'Parse error in selector: ' + message; }

if (this.expression == '') abort('empty expression');

var params = this.params, expr = this.expression, match, modifier, clause, rest; while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) {     params.attributes = params.attributes || [];      params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''});      expr = match[1];    }

if (expr == '*') return this.params.wildcard = true;

while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { modifier = match[1], clause = match[2], rest = match[3]; switch (modifier) { case '#':      params.id = clause; break; case '.':      params.classNames.push(clause); break; case '': case undefined: params.tagName = clause.toUpperCase; break; default:       abort(expr.inspect); }     expr = rest; }

if (expr.length > 0) abort(expr.inspect); },

buildMatchExpression: function { var params = this.params, conditions = [], clause;

if (params.wildcard) conditions.push('true'); if (clause = params.id) conditions.push('element.readAttribute("id") == ' + clause.inspect); if (clause = params.tagName) conditions.push('element.tagName.toUpperCase == ' + clause.inspect); if ((clause = params.classNames).length > 0) for (var i = 0, length = clause.length; i < length; i++) conditions.push('element.hasClassName(' + clause[i].inspect + ')'); if (clause = params.attributes) { clause.each(function(attribute) {       var value = 'element.readAttribute(' + attribute.name.inspect + ')';        var splitValueBy = function(delimiter) {          return value + ' && ' + value + '.split(' + delimiter.inspect + ')';        }

switch (attribute.operator) { case '=':      conditions.push(value + ' == ' + attribute.value.inspect); break; case '~=':     conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect + ')'); break; case '|=':     conditions.push(                            splitValueBy('-') + '.first.toUpperCase == ' + attribute.value.toUpperCase.inspect                          ); break; case '!=':     conditions.push(value + ' != ' + attribute.value.inspect); break; case '': case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect + ')'); break; default:       throw 'Unknown operator ' + attribute.operator + ' in selector'; }     });    }

return conditions.join(' && '); },

compileMatcher: function { this.match = new Function('element', 'if (!element.tagName) return false; \     element = $(element); \      return ' + this.buildMatchExpression); },

findElements: function(scope) { var element;

if (element = $(this.params.id)) if (this.match(element)) if (!scope || Element.childOf(element, scope)) return [element];

scope = (scope || document).getElementsByTagName(this.params.tagName || '*');

var results = []; for (var i = 0, length = scope.length; i < length; i++) if (this.match(element = scope[i])) results.push(Element.extend(element));

return results; },

toString: function { return this.expression; } }

Object.extend(Selector, { matchElements: function(elements, expression) {    var selector = new Selector(expression);    return elements.select(selector.match.bind(selector)).map(Element.extend);  },

findElement: function(elements, expression, index) { if (typeof expression == 'number') index = expression, expression = false; return Selector.matchElements(elements, expression || '*')[index || 0]; },

findChildElements: function(element, expressions) { return expressions.map(function(expression) {     return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) {        var selector = new Selector(expr);        return results.inject([], function(elements, result) {          return elements.concat(selector.findElements(result || element));        });      });    }).flatten;  } });

function $$ { return Selector.findChildElements(document, $A(arguments)); } var Form = { reset: function(form) { $(form).reset; return form; },

serializeElements: function(elements, getHash) { var data = elements.inject({}, function(result, element) {     if (!element.disabled && element.name) {        var key = element.name, value = $(element).getValue;        if (value != undefined) {          if (result[key]) {            if (result[key].constructor != Array) result[key] = [result[key]];            result[key].push(value);          }          else result[key] = value;        }      }      return result;    });

return getHash ? data : Hash.toQueryString(data); } };

Form.Methods = { serialize: function(form, getHash) { return Form.serializeElements(Form.getElements(form), getHash); },

getElements: function(form) { return $A($(form).getElementsByTagName('*')).inject([],     function(elements, child) {        if (Form.Element.Serializers[child.tagName.toLowerCase])          elements.push(Element.extend(child));        return elements;      }    ); },

getInputs: function(form, typeName, name) { form = $(form); var inputs = form.getElementsByTagName('input');

if (!typeName && !name) return $A(inputs).map(Element.extend);

for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { var input = inputs[i]; if ((typeName && input.type != typeName) || (name && input.name != name)) continue; matchingInputs.push(Element.extend(input)); }

return matchingInputs; },

disable: function(form) { form = $(form); form.getElements.each(function(element) {     element.blur;      element.disabled = 'true';    }); return form; },

enable: function(form) { form = $(form); form.getElements.each(function(element) {     element.disabled = '';    }); return form; },

findFirstElement: function(form) { return $(form).getElements.find(function(element) {     return element.type != 'hidden' && !element.disabled &&        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase);    }); },

focusFirstElement: function(form) { form = $(form); form.findFirstElement.activate; return form; } }

Object.extend(Form, Form.Methods);

/*--*/

Form.Element = { focus: function(element) { $(element).focus; return element; },

select: function(element) { $(element).select; return element; } }

Form.Element.Methods = { serialize: function(element) { element = $(element); if (!element.disabled && element.name) { var value = element.getValue; if (value != undefined) { var pair = {}; pair[element.name] = value; return Hash.toQueryString(pair); }   }    return ''; },

getValue: function(element) { element = $(element); var method = element.tagName.toLowerCase; return Form.Element.Serializers[method](element); },

clear: function(element) { $(element).value = ''; return element; },

present: function(element) { return $(element).value != ''; },

activate: function(element) { element = $(element); element.focus; if (element.select && ( element.tagName.toLowerCase != 'input' || !['button', 'reset', 'submit'].include(element.type) ) ) element.select; return element; },

disable: function(element) { element = $(element); element.disabled = true; return element; },

enable: function(element) { element = $(element); element.blur; element.disabled = false; return element; } }

Object.extend(Form.Element, Form.Element.Methods); var Field = Form.Element; var $F = Form.Element.getValue;

/*--*/

Form.Element.Serializers = { input: function(element) { switch (element.type.toLowerCase) { case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element); default: return Form.Element.Serializers.textarea(element); } },

inputSelector: function(element) { return element.checked ? element.value : null; },

textarea: function(element) { return element.value; },

select: function(element) { return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); },

selectOne: function(element) { var index = element.selectedIndex; return index >= 0 ? this.optionValue(element.options[index]) : null; },

selectMany: function(element) { var values, length = element.length; if (!length) return null;

for (var i = 0, values = []; i < length; i++) { var opt = element.options[i]; if (opt.selected) values.push(this.optionValue(opt)); }   return values; },

optionValue: function(opt) { // extend element because hasAttribute may not be native return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; } }

/*--*/

Abstract.TimedObserver = function {} Abstract.TimedObserver.prototype = { initialize: function(element, frequency, callback) { this.frequency = frequency; this.element  = $(element); this.callback = callback;

this.lastValue = this.getValue; this.registerCallback; },

registerCallback: function { setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); },

onTimerEvent: function { var value = this.getValue; var changed = ('string' == typeof this.lastValue && 'string' == typeof value     ? this.lastValue != value : String(this.lastValue) != String(value)); if (changed) { this.callback(this.element, value); this.lastValue = value; } } }

Form.Element.Observer = Class.create; Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver, { getValue: function {    return Form.Element.getValue(this.element);  } });

Form.Observer = Class.create; Form.Observer.prototype = Object.extend(new Abstract.TimedObserver, { getValue: function {    return Form.serialize(this.element);  } });

/*--*/

Abstract.EventObserver = function {} Abstract.EventObserver.prototype = { initialize: function(element, callback) { this.element = $(element); this.callback = callback;

this.lastValue = this.getValue; if (this.element.tagName.toLowerCase == 'form') this.registerFormCallbacks; else this.registerCallback(this.element); },

onElementEvent: function { var value = this.getValue; if (this.lastValue != value) { this.callback(this.element, value); this.lastValue = value; } },

registerFormCallbacks: function { Form.getElements(this.element).each(this.registerCallback.bind(this)); },

registerCallback: function(element) { if (element.type) { switch (element.type.toLowerCase) { case 'checkbox': case 'radio': Event.observe(element, 'click', this.onElementEvent.bind(this)); break; default: Event.observe(element, 'change', this.onElementEvent.bind(this)); break; }   }  } }

Form.Element.EventObserver = Class.create; Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver, { getValue: function {    return Form.Element.getValue(this.element);  } });

Form.EventObserver = Class.create; Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver, { getValue: function {    return Form.serialize(this.element);  } }); if (!window.Event) { var Event = new Object; }

Object.extend(Event, { KEY_BACKSPACE: 8,  KEY_TAB:       9,  KEY_RETURN:   13,  KEY_ESC:      27,  KEY_LEFT:     37,  KEY_UP:       38,  KEY_RIGHT:    39,  KEY_DOWN:     40,  KEY_DELETE:   46,  KEY_HOME:     36,  KEY_END:      35,  KEY_PAGEUP:   33,  KEY_PAGEDOWN: 34,

element: function(event) { return event.target || event.srcElement; },

isLeftClick: function(event) { return (((event.which) && (event.which == 1)) ||           ((event.button) && (event.button == 1))); },

pointerX: function(event) { return event.pageX || (event.clientX +     (document.documentElement.scrollLeft || document.body.scrollLeft)); },

pointerY: function(event) { return event.pageY || (event.clientY +     (document.documentElement.scrollTop || document.body.scrollTop)); },

stop: function(event) { if (event.preventDefault) { event.preventDefault; event.stopPropagation; } else { event.returnValue = false; event.cancelBubble = true; } },

// find the first node with the given tagName, starting from the // node the event was triggered on; traverses the DOM upwards findElement: function(event, tagName) { var element = Event.element(event); while (element.parentNode && (!element.tagName || (element.tagName.toUpperCase != tagName.toUpperCase))) element = element.parentNode; return element; },

observers: false,

_observeAndCache: function(element, name, observer, useCapture) { if (!this.observers) this.observers = []; if (element.addEventListener) { this.observers.push([element, name, observer, useCapture]); element.addEventListener(name, observer, useCapture); } else if (element.attachEvent) { this.observers.push([element, name, observer, useCapture]); element.attachEvent('on' + name, observer); } },

unloadCache: function { if (!Event.observers) return; for (var i = 0, length = Event.observers.length; i < length; i++) { Event.stopObserving.apply(this, Event.observers[i]); Event.observers[i][0] = null; }   Event.observers = false; },

observe: function(element, name, observer, useCapture) { element = $(element); useCapture = useCapture || false;

if (name == 'keypress' &&       (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.attachEvent)) name = 'keydown';

Event._observeAndCache(element, name, observer, useCapture); },

stopObserving: function(element, name, observer, useCapture) { element = $(element); useCapture = useCapture || false;

if (name == 'keypress' &&       (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.detachEvent)) name = 'keydown';

if (element.removeEventListener) { element.removeEventListener(name, observer, useCapture); } else if (element.detachEvent) { try { element.detachEvent('on' + name, observer); } catch (e) {} } } });

/* prevent memory leaks in IE */ if (navigator.appVersion.match(/\bMSIE\b/)) Event.observe(window, 'unload', Event.unloadCache, false); var Position = { // set to true if needed, warning: firefox performance problems // NOT neeeded for page scrolling, only if draggable contained in // scrollable elements includeScrollOffsets: false,

// must be called before calling withinIncludingScrolloffset, every time the // page is scrolled prepare: function { this.deltaX = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0;   this.deltaY =  window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; },

realOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return [valueL, valueT]; },

cumulativeOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; } while (element); return [valueL, valueT]; },

positionedOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { if(element.tagName=='BODY') break; var p = Element.getStyle(element, 'position'); if (p == 'relative' || p == 'absolute') break; }   } while (element); return [valueL, valueT]; },

offsetParent: function(element) { if (element.offsetParent) return element.offsetParent; if (element == document.body) return element;

while ((element = element.parentNode) && element != document.body) if (Element.getStyle(element, 'position') != 'static') return element;

return document.body; },

// caches x/y coordinate pair to use with overlap within: function(element, x, y) { if (this.includeScrollOffsets) return this.withinIncludingScrolloffsets(element, x, y); this.xcomp = x;   this.ycomp = y;    this.offset = this.cumulativeOffset(element);

return (y >= this.offset[1] &&           y <  this.offset[1] + element.offsetHeight &&            x >= this.offset[0] &&            x <  this.offset[0] + element.offsetWidth); },

withinIncludingScrolloffsets: function(element, x, y) { var offsetcache = this.realOffset(element);

this.xcomp = x + offsetcache[0] - this.deltaX; this.ycomp = y + offsetcache[1] - this.deltaY; this.offset = this.cumulativeOffset(element);

return (this.ycomp >= this.offset[1] &&           this.ycomp <  this.offset[1] + element.offsetHeight &&            this.xcomp >= this.offset[0] &&            this.xcomp <  this.offset[0] + element.offsetWidth); },

// within must be called directly before overlap: function(mode, element) { if (!mode) return 0; if (mode == 'vertical') return ((this.offset[1] + element.offsetHeight) - this.ycomp) / element.offsetHeight; if (mode == 'horizontal') return ((this.offset[0] + element.offsetWidth) - this.xcomp) / element.offsetWidth; },

page: function(forElement) { var valueT = 0, valueL = 0;

var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0;

// Safari fix if (element.offsetParent==document.body) if (Element.getStyle(element,'position')=='absolute') break;

} while (element = element.offsetParent);

element = forElement; do { if (!window.opera || element.tagName=='BODY') { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; }   } while (element = element.parentNode);

return [valueL, valueT]; },

clone: function(source, target) { var options = Object.extend({     setLeft:    true,      setTop:     true,      setWidth:   true,      setHeight:  true,      offsetTop:  0,      offsetLeft: 0    }, arguments[2] || {})

// find page position of source source = $(source); var p = Position.page(source);

// find coordinate system to use target = $(target); var delta = [0, 0]; var parent = null; // delta [0,0] will do fine with position: fixed elements, // position:absolute needs offsetParent deltas if (Element.getStyle(target,'position') == 'absolute') { parent = Position.offsetParent(target); delta = Position.page(parent); }

// correct by body offsets (fixes Safari) if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; }

// set position if(options.setLeft)  target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px'; if(options.setTop)   target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px'; if(options.setWidth) target.style.width = source.offsetWidth + 'px'; if(options.setHeight) target.style.height = source.offsetHeight + 'px'; },

absolutize: function(element) { element = $(element); if (element.style.position == 'absolute') return; Position.prepare;

var offsets = Position.positionedOffset(element); var top    = offsets[1]; var left   = offsets[0]; var width  = element.clientWidth; var height = element.clientHeight;

element._originalLeft  = left - parseFloat(element.style.left  || 0); element._originalTop   = top  - parseFloat(element.style.top || 0); element._originalWidth = element.style.width; element._originalHeight = element.style.height;

element.style.position = 'absolute'; element.style.top   = top + 'px'; element.style.left  = left + 'px'; element.style.width = width + 'px'; element.style.height = height + 'px'; },

relativize: function(element) { element = $(element); if (element.style.position == 'relative') return; Position.prepare;

element.style.position = 'relative'; var top = parseFloat(element.style.top  || 0) - (element._originalTop || 0); var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

element.style.top   = top + 'px'; element.style.left  = left + 'px'; element.style.height = element._originalHeight; element.style.width = element._originalWidth; } }

// Safari returns margins on body which is incorrect if the child is absolutely // positioned. For performance reasons, redefine Position.cumulativeOffset for // KHTML/WebKit only. if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { Position.cumulativeOffset = function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body) if (Element.getStyle(element, 'position') == 'absolute') break;

element = element.offsetParent; } while (element);

return [valueL, valueT]; } }

Element.addMethods; //     I18nManager.js   /* This class handles internationalization.

I18nManager is intended to be used through: _.KEY where "KEY" is an internationalization key and "_" is the short for I18nManager.bundle.en (or whatever language the user has chosen). "_.KEY" will give the internationalized string.

The convention I use for parameterized strings is inspired by Java. For instance, the value: s = "User {0} overwrote {1} of your edits." whould be filled in by using: s.replace(/\{0\}/g, someUsername).replace(/\{1\}/g, nEdits)

var I18nManager = {

bundle: {

en: { // English language

BUTTON_OK: "OK", BUTTON_CANCEL: "Cancel", BUTTON_CLOSE: "Close", BUTTON_SHOWALL: "Show All", BUTTON_REFRESH: "Refresh", BUTTON_EDIT: "Edit", BUTTON_ADD: "Add", BUTTON_REMOVE: "Remove", BUTTON_RESCAN: "Re-scan", BUTTON_FIX: "Fix", BUTTON_MYUSERSCRIPTS: "My User Scripts", LABEL_CONTACTS: "Contacts", LABEL_EMPTY: "Empty.", LABEL_ONESUGGESTION: "1 suggestion: ", LABEL_SUGGESTIONS: " suggestions: ", LABEL_ADDTOSUMMARY: "Add to summary", LABEL_CLICKTOFILLSUMMARY: "Click to fill the proposed summary into the input field below", LABEL_TALK: "Talk", LABEL_CONTRIBS: "Contribs", INFO_REFRESHING: "Refreshing...", INFO_NOSUGGESTIONS: "No suggestions.", INFO_REPEATANALYSIS: "Repeat the analysis of the text", INFO_PREFSINCOOKIE: "Your selection of user scripts will be remembered in a cookie. \                     If you use a different computer or a different browser, you have to configure them again.", INFO_PLUGIN_ADVISOR: "Advisor (suggests replacements while you edit)", INFO_PLUGIN_CONTACTLIST: "Contact list (monitors the online presence of selected users)", INFO_PLUGIN_ZOCKYLINKCOMPLETE: "Zocky's \"Link Complete\" (suggests names of existing articles after \                               you type \"[[foo\" and press TAB)", INFO_PLUGIN_CYRILLIC: "Helps display Cyrillic URL-s in the address bar", INFO_PLUGIN_SUBSTITUTIONS: "Replaces sequences of keystrokes with certain strings, similar to VIM's \":map\" command.", ERROR_APPLICABLETEXTCHANGED: "Error - The applicable text has changed.  Fix failed.", ERROR_EDITSUMMARYLIMIT: "Error - If the proposed text is added to the summary, \    its length will exceed the {0}-character maximum by {1} characters.", DUMMY: ""

},

bg: { // Bulgarian language

BUTTON_OK: "ОК", BUTTON_CANCEL: "Отказ", BUTTON_CLOSE: "Затвори", BUTTON_SHOWALL: "Покажи Всички", BUTTON_REFRESH: "Обнови", BUTTON_EDIT: "Редактирай", BUTTON_ADD: "Добави", BUTTON_REMOVE: "Махни", BUTTON_RESCAN: "Провери Пак", BUTTON_FIX: "Поправи", BUTTON_MYUSERSCRIPTS: "Моите Скриптове", LABEL_CONTACTS: "Контакти", LABEL_EMPTY: "Няма.", LABEL_ONESUGGESTION: "1 предложение: ", LABEL_SUGGESTIONS: " предложения: ", LABEL_ADDTOSUMMARY: "Добави към описанието", LABEL_CLICKTOFILLSUMMARY: "Добави предложеното описание към долното поле", LABEL_TALK: "Беседка", LABEL_CONTRIBS: "Приноси", INFO_REFRESHING: "Обновяване...", INFO_NOSUGGESTIONS: "Няма предложения.", INFO_REPEATANALYSIS: "Повтори анализа на текста", INFO_PREFSINCOOKIE: "Вашият избор на скриптове ще бъде запомнен в cookie. \                     Ако ползвате друг компютър или друг браузър, ще трябва пак да ги конфигурирате.", INFO_PLUGIN_ADVISOR: "Advisor (предлага промени на части от текста докато редактирате)", INFO_PLUGIN_CONTACTLIST: "Contact list (наблюдава онлайн-присъствието на избрани потребители)", INFO_PLUGIN_ZOCKYLINKCOMPLETE: "Zocky's \"Link Complete\" (предлага имена съществуващи статии след \                               като напишете \"[[нещо\" и натиснете TAB)", INFO_PLUGIN_CYRILLIC: "Помага да се показват кирилски адреси в адресната лента на браузъра", INFO_PLUGIN_SUBSTITUTIONS: "Подменя някои поредици от клавиши с определени низове, подобно на командата \":map\" във VIM", ERROR_APPLICABLETEXTCHANGED: "Грешка - Съответният текст е променен.  Поправянето е неуспешно.", ERROR_EDITSUMMARYLIMIT: "Грешка - Ако предложеният текст се добави към описанието, \    дължината му ще надвиши {0}-символното ограничение с {1} символа.", DUMMY: ""

}

} };

var _ = I18nManager.bundle[window.wgUserLanguage || "en"] // in case wgUserLanguage is not set || I18nManager.bundle.en; // in case we don't have a bundle for the user's language

// todo: Add a sanity self-check that every internationalization key is translated into every language //     DOMExtensions.js   /* These are shortcuts for frequently-used operations on DOM nodes.

function $elem(tagName/*, Element... children*/) { var e = $(document.createElement(tagName)); if (arguments.length > 1) { e.appendChildren.apply(e, $A(arguments).splice(1)); }   return e; }

/** * The second parameter is optional and can be either a function (attached as an * "onclick" observer) or a string (used as a "href" attribute). * * "title" is optional, too. */ function $anchor(text, hrefOrOnclick, title) { var e = $elem("A", text); e.title = title || null; if ((typeof hrefOrOnclick) == "string") { e.href = hrefOrOnclick; } else if ((typeof hrefOrOnclick) == "function") { e.href = "javascript: void(0);"; Event.observe(e, "click", function (hrefOrOnclick) {           hrefOrOnclick;            return false;        }.bind(null, hrefOrOnclick)); } else { e.href = "#"; }   return e; }

Element.addMethods({

removeAllChildren: function (e) { while (e.firstChild) { e.removeChild(e.firstChild); }       return e;    },

appendText: function (e, text) { e.appendChild(document.createTextNode(text)); return e;   },

appendChildren: function (e) { for (var i = 1; i < arguments.length; i++) { var x = arguments[i]; e.appendChild(((typeof x) == "string") ? document.createTextNode(x) : x); }       return e;    },

scrollToSelection: function (e) { if (e.nodeName != "TEXTAREA") { throw new Exception("scrollToSelection is available only for TEXTAREA-s."); }       var value = e.value; var selectionStart = e.selectionStart; var selectionEnd = e.selectionEnd; var pad = ""; var h = "\n"; var x = e.rows; while (x != 0) { if (x & 1) { pad += h;           } x >>>= 1; h += h;       } e.value = pad + value.substring(0, selectionStart); var yOffset = e.scrollHeight; e.value = value; e.selectionStart = selectionStart; e.selectionEnd = selectionEnd; e.scrollTop = (yOffset > e.clientHeight) ? (yOffset - (3 * e.clientHeight / 2)) : 0; return e;   } }); //     RegExpExtensions.js   /*    Regular expressions can sometimes become inconveniently large.    In order to make complex ones easier to understand, we introduce    a set of macros. Names enclosed with '{' and '}' will be replaced    according to the hashtable below.

In order to do the replacements, one must call .fix on the regex object and use the result instead, like this:

var re = /It happened in {month}/.fix;

Also, for the sake of convenience, we add the "getAllMatches(s)" method, which is a quick means to find all occurrences of a   regex in some text. The result is an array containing the results of applying RegExp.exec(..).

RegExp.REPLACEMENTS = { letter: // all Unicode letters // http://www.codeproject.com/dotnet/UnicodeCharCatHelper.asp "\\u0041-\\u005a\\u0061-\\u007a\\u00aa" + "\\u00b5\\u00ba\\u00c0-\\u00d6" + "\\u00d8-\\u00f6\\u00f8-\\u01ba\\u01bc-\\u01bf" + "\\u01c4-\\u02ad\\u0386\\u0388-\\u0481\\u048c-\\u0556" + "\\u0561-\\u0587\\u10a0-\\u10c5\\u1e00-\\u1fbc\\u1fbe" + "\\u1fc2-\\u1fcc\\u1fd0-\\u1fdb\\u1fe0-\\u1fec" + "\\u1ff2-\\u1ffc\\u207f\\u2102\\u2107\\u210a-\\u2113" + "\\u2115\\u2119-\\u211d\\u2124\\u2126\\u2128" + "\\u212a-\\u212d\\u212f-\\u2131\\u2133\\u2134\\u2139" + "\\ufb00-\\ufb17\\uff21-\\uff3a\\uff41-\\uff5a", letter_bg: // note that this is not the full set of Cyrillic letters "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЬЮЯабвгдежзийклмнопрстуфхцчшщъьюя", month: // "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec|"           + "January|February|March|April|June|July|August|September|"            + "October|November|December)" };

RegExp.escape = function (s) { var r = ""; var hex = "0123456789abcdef"; for (var i = 0; i < s.length; i++) { var ch = s.charCodeAt(i); r += "\\u" + hex[(ch >>> 12) & 0x0f] + hex[(ch >>> 8) & 0x0f] + hex[(ch >>> 4) & 0x0f] + hex[ch & 0x0f]; }   return r; };

RegExp.prototype.getAllMatches = function (s) { // : Match[] var p = 0; var a = []; while (true) { this.lastIndex = 0; var m = this.exec(s.substring(p)); if (m == null) { return a;       } m.start = p + m.index; m.end = p + m.index + m[0].length; a.push(m); p = m.end; } };

RegExp.prototype.fix = function { // : RegExp if (this.fixedRE != null) { return this.fixedRE; }   var s = this.source; for (var alias in RegExp.REPLACEMENTS) { s = s.replace(               new RegExp("{" + alias + "}", "g"),                RegExp.REPLACEMENTS[alias]        ); }   var re = new RegExp(s); re.global = this.global; re.ignoreCase = this.ignoreCase; re.multiline = this.multiline; this.fixedRE = re; // the fixed copy is cached return re; }; //     CookieManager.js

var CookieManager = Class.create;

CookieManager.getCookie = function (name) { var a = document.cookie.split(/; +/); var prefix = escape(name) + "="; for (var i = 0; i < a.length; i++) { if (a[i].substring(0, prefix.length) == prefix) { return unescape(a[i].substring(prefix.length)); }   }    return null; };

CookieManager.setCookie = function (name, value) { if ((value == null) || ("" + value == "")) { CookieManager.deleteCookie(name); return; }   var expirationDate = new Date(new Date.getTime + (5 * 365 * 24 * 60 * 60 * 1000)).toGMTString; // in 5 years document.cookie = escape(name) + "=" + escape(value) + "; Expires=" + expirationDate + "; Path=/;"; };

CookieManager.deleteCookie = function (name) { document.cookie = escape(name) + "=; Expires=" + new Date.toGMTString + "; Path=/;"; }; //     ModalDialogManager.js   /* Displays a DOM node on top of the rest of the page. The rest of the page is visible (or at least semi-transparent), but not clickable.

var ModalDialogManager = Class.create;

ModalDialogManager.show = function (e) { if (ModalDialogManager.dimmer != null) { return false; }   var whitebox = $elem("DIV", e); Object.extend(whitebox.style, {       position: "fixed",        backgroundColor: "white",        border: "solid black 1px",        padding: "1em",        left: Math.floor(innerWidth / 6) + "px",        top: Math.floor(innerHeight / 6) + "px",        width: Math.floor(innerWidth * 2 / 3) + "px",        height: Math.floor(innerHeight * 2 / 3) + "px",        overflow: "scroll"    }); var dimmer = ModalDialogManager.dimmer = $elem("DIV", whitebox); Object.extend(dimmer.style, {       position: "absolute",        width: document.body.clientWidth + "px",        height: document.body.clientHeight + "px",        left: "0",        top: "0",        zIndex: 999,        textAlign: "center" /*       , // transparency seems to slow rendering down too much        filter: "alpha(opacity=60)",        "-moz-opacity": 0.6,        opacity: 0.6,        backgroundColor: "grey"    }); document.body.appendChild(dimmer); return true; };

ModalDialogManager.hide = function { if (ModalDialogManager.dimmer != null) { ModalDialogManager.dimmer.remove; ModalDialogManager.dimmer = null; } }; //     MediawikiManager.js   /* Hides mediawiki quirks behind a convenient interface and encapsulates interaction with the surrounding DOM.

Some of my classes live happily without using MediawikiManager. I thought it is a good idea to encapsulate at least some of the interaction, though.

var MediawikiManager = {};

MediawikiManager.isPreferencesPage = function { return ($("preferences") != null); };

MediawikiManager.addPreferenceTab = function (e) { // Assuming we are at the "preferences" page now, we must first add our stuff. // the "tabbedprefs" function has already messed things up. // We'll just undo some of its actions, and make it work again // and include our new tab var index = $("preftoc").childNodes.length; $("preferences").insertBefore($elem("FIELDSET", $elem("LEGEND", _.BUTTON_MYUSERSCRIPTS), e), $("prefcontrol")); $("preftoc").remove; // tabbedprefs will re-generate the toc $("prefcontrol").id = "prefsubmit"; // tabbedprefs will swap the id-s back tabbedprefs; };

MediawikiManager.observePreferencesSubmit = function (f) { Event.observe($("prefcontrol").descendants("INPUT")[0], "click", f); };

MediawikiManager.importScript = function (s) { importScript.apply(window, arguments); }; //     CameltraderPluginManager.js

var CameltraderPluginManager = Class.create;

// Static info about plugins CameltraderPluginManager.PLUGINS = $H({   advisor: {        description: _.INFO_PLUGIN_ADVISOR,        implementation: function  { /*new AdvisorHighlighterPlugin(*/new AdvisorPlugin/*)*/; }    },    contactList: {        description: _.INFO_PLUGIN_CONTACTLIST,        implementation: function  { new ContactListPlugin; }    },    zockyLinkComplete: {        description: _.INFO_PLUGIN_ZOCKYLINKCOMPLETE,        implementation: function  { new ZockyLinkCompletePlugin; }    }, /*    lupinPopups: {        description: "User:Lupin's popups.js",        implementation: function  { new LupinPopupsPlugin; }    },    cacycleDiff: {        description: "User:Cacycle's diff.js",        implementation: function  { new CacycleDiffPlugin; }    },    cyrillic: {        description: _.INFO_PLUGIN_CYRILLIC,        implementation: function  { new CyrillicPlugin; }    },    substitutions: { description: _.INFO_PLUGIN_SUBSTITUTIONS, implementation: function { new SubstitutionsPlugin; } } });

CameltraderPluginManager.prototype = {

initialize: function { CameltraderPluginManager.INSTANCE = this; this.aConfiguredPluginNames = (CookieManager.getCookie("plugins") || "").split(",").compact.uniq; this.hConfiguredPluginNames = $H; this.aConfiguredPluginNames._each(function (name) {           if (CameltraderPluginManager.PLUGINS[name] == null) {                return;            }            this.hConfiguredPluginNames[name] = true;            CameltraderPluginManager.PLUGINS[name].implementation;        }.bind(this)); if (!MediawikiManager.isPreferencesPage) { return; }       this.hSumbittablePluginNames = $H.merge(this.hConfiguredPluginNames); var eDiv = $elem("DIV", _.INFO_PREFSINCOOKIE); CameltraderPluginManager.PLUGINS.keys._each(function (name) {           var eCheckbox = $elem("INPUT");            eCheckbox.id = "checkbox-" + name;            eCheckbox.type = "checkbox";            if (this.hConfiguredPluginNames[name]) {                eCheckbox.checked = true;            }            var eLabel = $elem("LABEL", CameltraderPluginManager.PLUGINS[name].description);            eLabel.htmlFor = eCheckbox.id;            eLabel.id = "label-" + name;            Event.observe(eCheckbox, "click", function (name, eCheckbox, eLabel) { if (eCheckbox.checked) { this.hSumbittablePluginNames[name] = true; } else { this.hSumbittablePluginNames.remove(name); }               var isConfigured = !!this.hConfiguredPluginNames[name]; if (eCheckbox.checked && !isConfigured) { eLabel.style.backgroundColor = "#afa"; // green } else if (isConfigured && !eCheckbox.checked) { eLabel.style.backgroundColor = "#faa"; // red } else { eLabel.style.backgroundColor = ""; }           }.bind(this, name, eCheckbox, eLabel));            eDiv.appendChild($elem("DIV", eCheckbox, eLabel));        }.bind(this)); MediawikiManager.addPreferenceTab(eDiv); MediawikiManager.observePreferencesSubmit(function {            var value = this.hSumbittablePluginNames.keys.join(",");            CookieManager.setCookie("plugins", value);        }.bind(this)); } }; //     AdvisorPlugin.js

var AdvisorPlugin = Class.create; AdvisorPlugin.DEFAULT_MAX_SUGGESTIONS = 8;

AdvisorPlugin.prototype = {

initialize: function { this.wpTextbox1 = $("wpTextbox1"); this.wpSummary = $("wpSummary"); var editform = $("editform"); var wpSummaryLabel = $("wpSummaryLabel"); //       this.maxSuggestions = AdvisorPlugin.DEFAULT_MAX_SUGGESTIONS; this.suggestions = null; // : AdvisorSuggestion[] this.appliedSuggestions = {}; // : Map if (this.wpTextbox1 == null) { // This is not an "?action=edit" page return; }       this.root = $elem("DIV"); // : Element; that's where suggestions are rendered var e = editform; while ((e.previousSibling != null) && (e.previousSibling.id != "toolbar")) { e = e.previousSibling; }       e.parentNode.insertBefore(this.root, e); this.root2 = $elem("DIV"); // : Element; the proposed edit summary appears there wpSummaryLabel.parentNode.insertBefore(this.root2, wpSummaryLabel); this.root2.hide; this.scan; this.root.style.border = "dashed #ccc 1px"; this.root2.style.border = "dashed #ccc 1px"; this.root.style.color = "#888"; this.root2.style.color = "#888"; },

scan: function { var s = this.wpTextbox1.value; this.suggestions = []; for (var i = 0; i < AdvisorRules.RULES.length; i++) { var a = AdvisorRules.RULES[i](s); if (a.constructor == AdvisorSuggestion) { a = [ a ]; }           for (var j = 0; j < a.length; j++) { this.suggestions.push(a[j]); }       }        this.suggestions.sort(function (x, y) {            return (x.start < y.start) ? -1 :                   (x.start > y.start) ? 1 :                   (x.end < y.end) ? -1 :                   (x.end > y.end) ? 1 : 0;        }); this.root.removeAllChildren; if (this.suggestions.length == 0) { this.root.appendText(_.INFO_NOSUGGESTIONS); } else { var nSuggestions = Math.min(this.maxSuggestions, this.suggestions.length); this.root.appendText(                   (this.suggestions.length == 1)                            ? _.LABEL_ONESUGGESTION                            : (this.suggestions.length + _.LABEL_SUGGESTIONS)            ); for (var i = 0; i < nSuggestions; i++) { this.root.appendChildren(                       $anchor( this.suggestions[i].name, this.markSuggestion.bind(this, this.suggestions[i]), this.suggestions[i].description ),                       $elem("SUP", $anchor(_.BUTTON_FIX, this.fixSuggestion.bind(this, this.suggestions[i]))),                        " "                ); }           if (this.suggestions.length > this.maxSuggestions) { this.root.appendChildren(                       $anchor( "...",                               function  { this.maxSuggestions = 1000; this.scan; this.maxSuggestions = AdvisorPlugin.DEFAULT_MAX_SUGGESTIONS; return false; }.bind(this), _.BUTTON_SHOWALL ), " "               );            }        }        this.root.appendChildren("(", $anchor(_.BUTTON_RESCAN, this.scan.bind(this), _.INFO_REPEATANALYSIS), ")"); },

fixSuggestion: function (suggestion) { if (this.wpTextbox1.value.substring(suggestion.start, suggestion.end)                       != suggestion.originalText) { alert(_.ERROR_APPLICABLETEXTCHANGED); this.scan; return; }       this.wpTextbox1.value = this.wpTextbox1.value.substring(0, suggestion.start) + suggestion.replacementText + this.wpTextbox1.value.substring(suggestion.end); this.wpTextbox1.selectionStart = suggestion.start; this.wpTextbox1.selectionEnd = suggestion.start + suggestion.replacementText.length; this.wpTextbox1.scrollToSelection; // Propose an edit summary if (this.appliedSuggestions[suggestion.name] == null) { this.appliedSuggestions[suggestion.name] = 1; } else { this.appliedSuggestions[suggestion.name]++; }       var a = []; for (var i in this.appliedSuggestions) { a.push(i); }       a.sort(function (x, y) {            var u = this.appliedSuggestions;            return (u[x] > u[y]) ? -1 : (u[x] < u[y]) ? 1 : (x < y) ? -1 : (x > y) ? 1 : 0;        }.bind(this)); var s = ""; for (var i = 0; i < a.length; i++) { var count = this.appliedSuggestions[a[i]]; s += ", " + ((count == 1) ? a[i] : (count + "x " + a[i])); }       // Cut off the leading ", " and add "formatting: " and "using Advisor.js" s = "formatting: " + s.substring(2) + " (using Advisor.js)"; // Render in DOM this.root2.removeAllChildren.appendChildren(               $anchor(_.LABEL_ADDTOSUMMARY, this.addToSummary.bind(this, s), _.LABEL_CLICKTOFILLSUMMARY),                ": \"" + s + "\""        ); this.root2.show; // Re-scan this.scan; },

addToSummary: function (summary) { var wpSummary = this.wpSummary; if (wpSummary.value != "") { summary = wpSummary.value + "; " + summary; }       if ((wpSummary.maxLength > 0) && (summary.length > wpSummary.maxLength)) { alert(_.ERROR_EDITSUMMARYLIMIT                   .replace(/\{0\}/g, wpSummary.maxLength)                    .replace(/\{1\}/g, summary.length - wpSummary.maxLength)            ); return; }       wpSummary.value = summary; this.root2.hide; },

markSuggestion: function (suggestion) { if (this.wpTextbox1.value.substring(suggestion.start, suggestion.end)                   != suggestion.originalText) { // The text has changed - just do another scan and don't change selection this.scan; return; }       this.wpTextbox1.selectionStart = suggestion.start; this.wpTextbox1.selectionEnd = suggestion.end; this.wpTextbox1.scrollToSelection; } }; //     AdvisorRules.js

var AdvisorRules = {};

AdvisorRules.RULES = []; // : Function[]

AdvisorRules.LANGUAGE_MAP = { // : Hashtable // From http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes // Note, that not all of these have a lang-xx template, but finding a reference // to such a language is a good reason to create the template. aa: "Afar", ab: "Abkhazian", ae: "Avestan", af: "Afrikaans", ak: "Akan", am: "Amharic", an: "Aragonese", ar: "Arabic", as: "Assamese", av: "Avaric", ay: "Aymara", az: "Azerbaijani", ba: "Bashkir", be: "Belarusian", bg: "Bulgarian", bh: "Bihari", bi: "Bislama", bm: "Bambara", bn: "Bengali", bo: "Tibetan", br: "Breton", bs: "Bosnian", ca: "Catalan", ce: "Chechen", ch: "Chamorro", co: "Corsican", cr: "Cree", cs: "Czech", cu: "Church Slavic", cv: "Chuvash", cy: "Welsh", da: "Danish", de: "German", dv: "Divehi", dz: "Dzongkha", ee: "Ewe", el: "Greek", en: "English", eo: "Esperanto", es: "Spanish", et: "Estonian", eu: "Basque", fa: "Persian", ff: "Fulah", fi: "Finnish", fj: "Fijian", fo: "Faroese", fr: "French", fy: "Western Frisian", ga: "Irish", gd: "Gaelic", gl: "Galician", gn: "Guaraní", gu: "Gujarati", gv: "Manx", ha: "Hausa", he: "Hebrew", hi: "Hindi", ho: "Hiri Motu", hr: "Croatian", ht: "Haitian", hu: "Hungarian", hy: "Armenian", hz: "Herero", ia: "Interlingua (International Auxiliary Language Association)", id: "Indonesian", ie: "Interlingue", ig: "Igbo", ii: "Sichuan Yi", ik: "Inupiaq", io: "Ido", is: "Icelandic", it: "Italian", iu: "Inuktitut", ja: "Japanese", jv: "Javanese", ka: "Georgian", kg: "Kongo", ki: "Kikuyu", kj: "Kuanyama", kk: "Kazakh", kl: "Kalaallisut", km: "Khmer", kn: "Kannada", ko: "Korean", kr: "Kanuri", ks: "Kashmiri", ku: "Kurdish", kv: "Komi", kw: "Cornish", ky: "Kirghiz", la: "Latin", lb: "Luxembourgish", lg: "Ganda", li: "Limburgish", ln: "Lingala", lo: "Lao", lt: "Lithuanian", lu: "Luba-Katanga", lv: "Latvian", mg: "Malagasy", mh: "Marshallese", mi: "Māori", mk: "Macedonian", ml: "Malayalam", mn: "Mongolian", mo: "Moldavian", mr: "Marathi", ms: "Malay", mt: "Maltese", my: "Burmese", na: "Nauru", nb: "Norwegian Bokmål", nd: "North Ndebele", ne: "Nepali", ng: "Ndonga", nl: "Dutch", nn: "Norwegian Nynorsk", no: "Norwegian", nr: "South Ndebele", nv: "Navajo", ny: "Chichewa", oc: "Occitan", oj: "Ojibwa", om: "Oromo", or: "Oriya", os: "Ossetian", pa: "Panjabi", pi: "Pāli", pl: "Polish", ps: "Pashto", pt: "Portuguese", qu: "Quechua", rm: "Raeto-Romance", rn: "Kirundi", ro: "Romanian", ru: "Russian", rw: "Kinyarwanda", sa: "Sanskrit", sc: "Sardinian", sd: "Sindhi", se: "Northern Sami", sg: "Sango", sh: "Serbo-Croatian", si: "Sinhala", sk: "Slovak", sl: "Slovenian", sm: "Samoan", sn: "Shona", so: "Somali", sq: "Albanian", sr: "Serbian", ss: "Swati", st: "Southern Sotho", su: "Sundanese", sv: "Swedish", sw: "Swahili", ta: "Tamil", te: "Telugu", tg: "Tajik", th: "Thai", ti: "Tigrinya", tk: "Turkmen", tl: "Tagalog", tn: "Tswana", to: "Tonga", tr: "Turkish", ts: "Tsonga", tt: "Tatar", tw: "Twi", ty: "Tahitian", ug: "Uighur", uk: "Ukrainian", ur: "Urdu", uz: "Uzbek", ve: "Venda", vi: "Vietnamese", vo: "Volapük", wa: "Walloon", wo: "Wolof", xh: "Xhosa", yi: "Yiddish", yo: "Yoruba", za: "Zhuang", zh: "Chinese", zu: "Zulu" };

AdvisorRules.REVERSE_LANGUAGE_MAP = {}; // : Hashtable for (var i in AdvisorRules.LANGUAGE_MAP) { AdvisorRules.REVERSE_LANGUAGE_MAP[AdvisorRules.LANGUAGE_MAP[i]] = i; }

AdvisorRules.MONTH_MAP = { Jan: "January", Feb: "February", Mar: "March", Apr: "April", May: "May", Jun: "June", Jul: "July", Aug: "August", Sep: "September", Oct: "October", Nov: "November", Dec: "December", January: "January", February: "February", March: "March", April: "April", June: "June", July: "July", August: "August", September: "September", October: "October", November: "November", December: "December" };

/*   Rules

AdvisorRules.RULES.push(function (s) {   var re = /^[ ':]*(?:(?:Further|More) +info(?:rmation))[ ']*:[ ']*([^\]]+)[ ']*$/mig;    var a = re.getAllMatches(s);    var b = [];    for (var i = 0; i < a.length; i++) {        var m = a[i];        if ((m[1] != null) && (m[1] != "")) {            b.push(new AdvisorSuggestion(                    m.start, m.end, m[0],                    "",                    "template-further",                    "Better use the  template."            ));        }    }    return b; });

AdvisorRules.RULES.push(function (s) {   var re = /^[ ':]*(?:Main +article)[ ']*:[ ']*\[\[([^\]]+)\]\][ ']*$/mig;    var a = re.getAllMatches(s);    var b = [];    for (var i = 0; i < a.length; i++) {        var m = a[i];        if ((m[1] != null) && (m[1] != "")) {            b.push(new AdvisorSuggestion(                    m.start, m.end, m[0],                    "",                    "template-main",                    "Better use the  template."            ));        }    }    return b; });

AdvisorRules.RULES.push(function (s) {   // This will match either a date+year or just a year,    // and will not match solitary dates.    // The rule only controls the transition from linked to unlinked,    // as practice has shown that improper linking is significantly    // more common than leaving linkable dates as plain text.    var re = /(?:\[\[((?:(\d\d?) +({month}))|(?:({month}) +(\d\d?)))\]\],?? *)?\[\[([12]\d\d\d)\]\]/;   var a = re.fix.getAllMatches(s);    var b = [];    for (var i = 0; i < a.length; i++) {        var m = a[i];        var date  = ((m[1] == null) || (m[1] == "")) ? null : m[1];        var year  = ((m[7] == null) || (m[7] == "")) ? null : m[7];        if (date == null) {            b.push(new AdvisorSuggestion(                    m.start, m.end, m[0],                    year,                    "year link",                    "It is useless to link a year unless it is preceded by a day and month."            ));        } else {            var isAmerican = ((m[2] == null) || (m[2] == ""));            var day = (isAmerican) ? m[5] : m[2];            var month = AdvisorRules.MONTH_MAP[(isAmerican) ? m[4] : m[3]];            var ws = m[6]; // whitespace between date and year            var replacement = (isAmerican)                    ? ("" + month + " " + day + "," + ws + "" + year + "") : ("" + day + " " + month + "" + ws + "" + year + ""); if (replacement != m[0]) { b.push(new AdvisorSuggestion( m.start, m.end, m[0], replacement, "date", "Date conventions." ));           }        }    }    return b; });

AdvisorRules.RULES.push(function (s) {   var re = /\[\[(\w+) language\|\1\]\] *: (\'+)*([{letter} \"\'\„\“\/—–\-]+)(?:\2)/g;    var a = re.fix.getAllMatches(s);    var b = [];    for (var i = 0; i < a.length; i++) {        var m = a[i];        var code = AdvisorRules.REVERSE_LANGUAGE_MAP[m[1]] || null;        if (code == null) {            continue;        }        // Markers for italics and bold are stripped off        b.push(new AdvisorSuggestion(                m.start, m.end, m[0],                "",                "lang-" + code,                "The  template can be applied for this text."        ));    }    return b; });

AdvisorRules.RULES.push(function (s) {   // Only accept years 1000...2999    var re = /[^0-9]([1-2][0-9]{3}) *(?:-|—|&mdash;|--) *([1-2][0-9]{3})[^0-9]/g;    var a = re.getAllMatches(s);    for (var i = 0; i < a.length; i++) {        var m = a[i];        a[i] = new AdvisorSuggestion( m.start + 1, m.end - 1, m[0].substring(1, m[0].length - 1), m[1] + "–" + m[2], // the char in the middle is an ndash "ndash", "Year ranges look better with an n-dash." );   }    return a; });

AdvisorRules.RULES.push(function (s) {   var re = /[{letter}]( +- +)[{letter}]/g;    var a = re.fix.getAllMatches(s);    for (var i = 0; i < a.length; i++) {        var m = a[i];        a[i] = new AdvisorSuggestion( m.start + 1, m.end - 1, m[1], " — ", // mdash "mdash", "In a sentence, a hyphen surrounded by spaces means almost certainly an mdash." );   }    return a; });

AdvisorRules.RULES.push(function (s) {   var re = /^(?: *)(==+)( *)([^=]*[^= ])(?: *)\1/gm;    var a = re.getAllMatches(s);    var b = [];    var level = 0; // == Level 1 ==, === Level 2 ===, ==== Level 3 ====, etc.    /* If we are editing a section, we have to be tolerant to the first heading's level */    var isSection = $("editform")                && ($("editform")["wpSection"] != null)                && ($("editform")["wpSection"].value != "");    for (var i = 0; i < a.length; i++) {        var m = a[i];        var suggestion = new AdvisorSuggestion( m.start, m.end, m[0], m[1] + m[2] + m[3] + m[2] + m[1], "heading", "Heading style should be \"== Heading ==\" or \"==Heading==\"." );       var oldLevel = level;        level = m[1].length - 1;        if ( (level - oldLevel > 1) && (!isSection || (oldLevel > 0)) ) {            // Adjust suggestion for the rare case of improper heading hesting            suggestion.name = "heading-nesting";            suggestion.description = "Improper heading nesting";            var h = "=======".substring(0, oldLevel + 2);            suggestion.replacementText = h + m[2] + m[3] + m[2] + h;            b.push(suggestion);            continue;        }        var frequentMistakes = [            { code: "see-also",  wrong: /^see *al+so$/i,          correct: "See also" },            { code: "ext-links", wrong: /^external links?$/i,     correct: "External links" },            { code: "refs",      wrong: /^ref+e?r+en(c|s)es?$/i,  correct: "References" }        ];        for (var j = 0; j < frequentMistakes.length; j++) {            var fm = frequentMistakes[j]; if (fm.wrong.test(m[3]) && (m[3] != fm.correct)) { suggestion.replacementText = m[1] + m[2] + fm.correct + m[2] + m[1]; suggestion.name = fm.code; suggestion.description = "The correct spelling/capitalization is \""                       + fm.correct + "\"."; }       }        if (suggestion.originalText != suggestion.replacementText) { b.push(suggestion); }   }    return b; });

AdvisorRules.RULES.push(function (s) {   var re = /\[\[([{letter} ,\(\)\-]+)\|\1\]\]/g;    var a = re.fix.getAllMatches(s);    for (var i = 0; i < a.length; i++) {        var m = a[i];        a[i] = new AdvisorSuggestion( m.start, m.end, m[0], "" + m[1] + "", "A|A", "\"A|A\" can be simplified to A." );   }    return a; });

AdvisorRules.RULES.push(function (s) {   var re = /\[\[([{letter} ,\(\)\-]+)\|\1([{letter}]+)\]\]/g;    var a = re.fix.getAllMatches(s);    for (var i = 0; i < a.length; i++) {        var m = a[i];        a[i] = new AdvisorSuggestion( m.start, m.end, m[0], "" + m[1] + "" + m[2], "A|AB", "\"A|AB\" can be simplified to AB." );   }    return a; });

// This rule has to be removed. The text could be in another // language using the Cyrillic alphabet. /* AdvisorRules.RULES.push(function (s) {   // For this rule to apply, at least four of the characters within the    // brackets must be Bulgarian Cyrillic letters, and the rest are allowed    // to be some limited punctuation    var re = /\((([ ,.;:\"\'\„\“\-\—\–]|–|&mdash;|&amp;|&#768;)*({letter_bg}+([ ,.;:\"\'\„\“\-\—\–]|–|&mdash;|&amp;|&#768;)*){4,})\)/g;    var a = re.fix.getAllMatches(s); // the previous line must be monolithic because of MSIE    for (var i = 0; i < a.length; i++) {        var m = a[i];        a[i] = new AdvisorSuggestion( m.start, m.end, m[0], "(" + m[1] + ")", "lang|bg", "Bulgarian text should be marked with \"\", " + "even when there is no Bulgarian language link." );   }    return a; });

// With the introduction of the magic word "DEFAULTSORT" it became the // preferred means to do category sorting. /* AdvisorRules.RULES.push(function (s) {   var exceptions = {}; // sure, there are many    exceptions["Dolni Bogrov"] = true;    exceptions["Gorni Bogrov"] = true;    if (exceptions[window.wgTitle]) {        return;    }    var re0 = /^([{letter}\-]+(?: [{letter}\-]+\.?)?) ([{letter}\-]+(?:ov|ev|ski))$/;    var m0 = re0.exec(window.wgTitle);    if (m0 == null) {        return [];    }    var firstNames = m0[1];    var lastName = m0[2];    // Should be "lastName, firstName" in categories    var re1 = /\[\[(Category:[\w _\(\),\-]+)\]\]/gi;    var a = re1.getAllMatches(s);    for (var i = 0; i < a.length; i++) {        var m = a[i];        a[i] = new AdvisorSuggestion( m.start, m.end, m[0], "" + lastName + ", " + firstNames + "", "cat-sort", "The sort key for categories should be the family name." // Wikipedia:Categorization_of_people#Ordering_names_in_a_category );   }    return a; });

AdvisorRules.RULES.push(function (s) {   var exceptions = {};    exceptions["Dolni Bogrov"] = true;    exceptions["Gorni Bogrov"] = true;    if (exceptions[window.wgTitle]) {        return [];    }    var re0 = /^([{letter}\-]+(?: [{letter}\-]+\.?)?) ([{letter}\-]+(?:ov|ev|ski))$/;    var m0 = re0.fix.exec(window.wgTitle);    if (m0 == null) {        return [];    }    if (s.indexOf("DEFAULTSORT") != -1) {        return [];    }    var firstNames = m0[1];    var lastName = m0[2];    /*var re1 = /\[\[(Category:[\w _\(\),\-]+)\|([\w _\(\),\-]+)\]\]/gi;*/    var re1 = new RegExp("\\[\\[(Category:[\\w _\\(\\),\\-]+)\\| *" + RegExp.escape(lastName) + ", *" + RegExp.escape(firstNames) + " *\\]\\]", "gi");   var a = re1.getAllMatches(s);    if (a.length == 0) {        return [];    }    var aStart = a[0].start;    var aEnd = a[a.length - 1].end;    var original = s.substring(aStart, aEnd);    var replacement = "\n"                    + original.replace(re1, "$1");    return [        new AdvisorSuggestion( aStart, aEnd, original, replacement, "default-sort", "The magic word DEFAULTSORT can be used to specify sort keys for categories." )   ]; });

AdvisorRules.RULES.push(function (s) {   var re = /\( *(?:b\.? *)?((?:19|20)[0-9][0-9]) *(?:[\-\—\–]|–|&mdash;|--) *\)/g;    var a = re.getAllMatches(s);    for (var i = 0; i < a.length; i++) {        var m = a[i];        a[i] = new AdvisorSuggestion( m.start, m.end, m[0], "(born " + m[1] + ")", "born", "The word \"born\" should be fully written." // WP:DATE#Dates_of_birth_and_death );   }    return a; }); //     AdvisorSuggestion.js   /* It makes sense to have a designated class for suggestions, and not use bare { name: value } syntax. Thus we have a clear concept of a suggestion, and we can validate parameters in the constructor.

var AdvisorSuggestion = Class.create;

AdvisorSuggestion.prototype = { initialize: function (start, end, originalText, replacementText, name, description) { // todo: validation this.start = start; this.end = end; this.originalText = originalText; this.replacementText = replacementText; this.name = name; this.description = description; } }; //     AdvisorHighlighterPlugin.js

var AdvisorHighlighterPlugin = Class.create;

AdvisorHighlighterPlugin.prototype = {

initialize: function (advisorPlugin) { if (advisorPlugin == null) { throw new Exception("An instance of AdvisorPlugin must be passed to the constructor of AdvisorHighlighterPlugin."); }       this.advisorPlugin = advisorPlugin; if (advisorPlugin.wpTextbox1 == null) { // nothing to do, this is not an "?action=edit" page return; }       this.overlays = [ advisorPlugin.wpTextbox1 ]; this.addOverlay("yellow"); this.addOverlay("pink"); Event.observe(this.overlays[0], "keyup", this.processInputEvent.bind(this)); Event.observe(this.overlays[0], "mouseover", this.processInputEvent.bind(this)); },

addOverlay: function (colour) { var overlay = this.overlays[0].cloneNode(true); var previousOverlay = this.overlays.last; overlay.id += "overlay" + this.overlays.length; overlay.readOnly = true; overlay.style.color = colour; overlay.style.position = ""; previousOverlay.style.position = "absolute"; //       previousOverlay.style.width = overlay.clientWidth + "px"; previousOverlay.style.zIndex = 1000 - this.overlays.length; previousOverlay.parentNode.insertBefore(overlay, previousOverlay.nextSibling); previousOverlay.style.backgroundColor = "transparent"; this.overlays.push(overlay); },

processInputEvent: function (event) { var a = this.overlays[0]; if (a.value == a.oldValue) { var scrollTop = a.scrollTop; if (a.oldScrollTop != scrollTop) { a.oldScrollTop = scrollTop; for (var i = 0; i < this.overlays.length; i++) { this.overlays[i].scrollTop = scrollTop; }           }            var scrollLeft = a.scrollLeft; if (a.oldScrollLeft != scrollLeft) { a.oldScrollLeft = scrollLeft; for (var i = 0; i < this.overlays.length; i++) { this.overlays[i].scrollLeft = scrollLeft; }           }            return; }       a.oldValue = a.value; this.updateHighlighting.bind(this).delay(1000); },

updateHighlighting: function { this.advisorPlugin.scan; var suggestions = this.advisorPlugin.suggestions; var s = this.overlays[0].value.replace(/[^ \n\r\t]/g, "\u00a0"); // U+00a0 is a non-breaking space var values = [ "" ]; for (var i = 1; i < this.overlays.length; i++) { values[i] = s;       } for (var i = 0; i < suggestions.length; i++) { var x = suggestions[i]; var overlayIndex = 2; // todo var v = values[overlayIndex]; values[overlayIndex] = v.substring(0, x.start) + v.substring(x.start, x.end) .replace(/\u00a0/g, "\u2588") // U+2588 is a full solid block + v.substring(x.end); }       for (var i = 1; i < this.overlays.length; i++) { this.overlays[i].value = values[i]; this.overlays[i].scrollTop = this.overlays[0].scrollTop; this.overlays[i].scrollLeft = this.overlays[0].scrollLeft; }   } };

// A candidate for FunctionExtensions.js Object.extend(Function.prototype, {   delay: function (t) {        var a = $A(arguments);        a.splice(1);        clearTimeout(this.timeoutId);        this.timeoutId = setTimeout(this.bind.apply(this, a), t);    } }); //     ContactListPlugin.js

var ContactListPlugin = Class.create;

ContactListPlugin.prototype = {

initialize: function { var eH5 = $elem("H5").appendChildren(               _.LABEL_CONTACTS + " (", $anchor(_.BUTTON_REFRESH, this.refresh.bind(this)), ", ", $anchor(_.BUTTON_EDIT, this.edit.bind(this)), ")"       ); var ePBody = $elem("DIV").addClassName("pBody"); ePBody.id = "contact-list-body"; $("column-one").appendChild($elem("DIV", eH5, ePBody).addClassName("portlet")); this.refresh; },

refresh: function { $("contact-list-body").removeAllChildren; var contacts = CookieManager.getCookie("contacts"); if ((contacts == null) || (contacts == "")) { $("contact-list-body").appendText(_.LABEL_EMPTY); return; }       $("contact-list-body").appendText(_.INFO_REFRESHING); new Ajax.Request("/w/query.php", { // unfortunately, "/w/api.php" doesn't offer retrieval of user contributions yet           method: "get",            parameters: {                format: "json",                what: "usercontribs",                uclimit: 20,                uccomments: "",                titles: "User:" + contacts.split("|").join("|User:")            },            onSuccess: function (transport) {                var result = new Function("return " + transport.responseText);                var a = [];                for (var i in result.pages) {                    var p = result.pages[i];                    a[a.length] = {                            user: p.title,                            timestamp: this.parseWPDate(p.contributions[0].timestamp)                    };                }                // Order by last edit                a.sort(function (x, y) { return y.timestamp.getTime - x.timestamp.getTime; });               var eUL = $elem("UL");                for (var i = 0; i < a.length; i++) {                    var u = a[i].user;                    if (u.substring(0, 5) == "User:") {                        u = u.substring(5);                    }                    eUL.appendChild($elem("LI",                            $anchor(u, "/wiki/Special:Contributions/" + escape(u)),                            " " + this.getHumanReadableTimestamp(a[i].timestamp).replace(/ /g, "\u00a0") // nbsp                    ));                }                $("contact-list-body").removeAllChildren.appendChild(eUL);            }.bind(this)        }); },

parseWPDate: function (s) { // "s" is like "2007-04-15T11:06:53Z" return new Date(Date.UTC( parseInt(s.substring(0, 4)), parseInt(s.substring(5, 7) - 1), // months are zero based in js               parseInt(s.substring(8, 10)), parseInt(s.substring(11, 13)), parseInt(s.substring(14, 16)), parseInt(s.substring(17, 19)) ));   },

getHumanReadableTimestamp: function (dateThen) { // todo: i18n var dateNow = new Date; // Calculate difference between now and then var diff = dateNow.getTime - dateThen.getTime; if (diff < 2 * 60 * 1000) { // less than a two minutes return "just now"; } else if (diff < 59 * 60 * 1000) { // minutes ago return Math.round(diff / (60 * 1000)) + "m ago"; } else if (diff < 4 * 60 * 60 * 1000) { // hours and minutes ago var h = Math.floor(diff / (60 * 60 * 1000)); var m = Math.round((diff - (h * 60 * 60 * 1000)) / (5 * 60 * 1000)) * 5; // round at 5m return (m == 0) ? (h + "h ago") : (h + "h " + m + "m ago"); }       // Calculate difference between last midnight (browser's timezone) and then dateNow.setHours(0); dateNow.setMinutes(0); dateNow.setSeconds(0); dateNow.setMilliseconds(0); var diffDays = Math.floor((dateNow.getTime - dateThen.getTime) / (24 * 60 * 60 * 1000)); if (diffDays < 1) { return Math.floor(diff / (60 * 60 * 1000)) + "h ago"; } else if (diffDays == 1) { return "yesterday"; } else { return diffDays + "d ago"; }   },

// What follows are methods related to the "edit" dialog

edit: function { this.eSettingsInnerDiv = $elem("DIV"); this.eSettingsOuterDiv = $elem("DIV",               this.eSettingsInnerDiv,                $anchor(_.BUTTON_CLOSE, this.closeEditDialog.bind(this))        ); this.renderEditDialog; this.isModified = false; ModalDialogManager.show(this.eSettingsOuterDiv); },

closeEditDialog: function { ModalDialogManager.hide; if (this.isModified) { this.refresh; this.isModified = false; }   },

addContact: function (s) { if (s == "") { return; }       var contactsCookie = CookieManager.getCookie("contacts"); var contacts = ((contactsCookie == null) || (contactsCookie == "")) ? [] : contactsCookie.split("|"); contacts.sort; contacts.push(s); CookieManager.setCookie("contacts", contacts.join("|")); this.isModified = true; this.renderEditDialog; },

addContactFromInputField: function { this.addContact(this.eInput.value); },

removeContact: function (index) { var contactsCookie = CookieManager.getCookie("contacts"); var contacts = ((contactsCookie == null) || (contactsCookie == "")) ? [] : contactsCookie.split("|"); contacts.splice(index, 1); CookieManager.setCookie("contacts", contacts.join("|")); this.renderEditDialog; this.isModified = true; },

renderEditDialog: function { this.eSettingsInnerDiv.removeAllChildren; var contactsCookie = CookieManager.getCookie("contacts"); var contacts = ((contactsCookie == null) || (contactsCookie == "")) ? [] : contactsCookie.split("|"); contacts.sort; this.eInput = $elem("INPUT"); Event.observe(this.eInput, "keyup", function (event) {           switch (event.keyCode) {                case Event.KEY_ESC: {                    this.closeEditDialog;                    return false;                }                case Event.KEY_RETURN: {                    this.addContactFromInputField;                    return false;                }                default: {                    // let go                }            }        }.bindAsEventListener(this)); this.eSettingsInnerDiv.appendChildren(               this.eInput, $anchor(_.BUTTON_ADD, this.addContactFromInputField.bind(this))        ); if (contacts.length != 0) { var eUL = $elem("UL"); for (var i = 0; i < contacts.length; i++) { eUL.appendChild($elem("LI", $anchor(contacts[i], wgServer + "/wiki/User:" + contacts[i]), " (", $anchor(_.LABEL_TALK, wgServer + "/wiki/User talk:" + contacts[i]),                       "·", $anchor(_.LABEL_CONTRIBS, wgServer + "/wiki/Special:Contributions/" + contacts[i]),                        ") [", $anchor(_.BUTTON_REMOVE, this.removeContact.bind(this, i)), "]" ));           }            this.eSettingsInnerDiv.appendChild(eUL); }       setTimeout(this.eInput.focus.bind(this.eInput), 10); } }; //     ZockyLinkCompletePlugin.js   /* This is a modified version of User:Zocky's excellent "Link Complete" script. http://en.wikipedia.org/wiki/User:Zocky/Link_Complete I made it look a bit more object-oriented and prototype.js-based. Surprisingly, the thing is still working after my chaotic refactoring.

var ZockyLinkCompletePlugin = Class.create;

ZockyLinkCompletePlugin.prototype = {

initialize: function { // Fields this.status = "idle"; this.find = ""; this.matches = []; this.nextPage = null; this.nextMatch = 0; this.start = 0; this.target = null; this.regExp = []; //       var wpTextbox1 = $("wpTextbox1"); if (wpTextbox1) { Event.observe(wpTextbox1, "keydown", this.keyHandler.bind(this)); Event.observe(wpTextbox1, "keyup", this.keyIgnorer.bind(this)); Event.observe(wpTextbox1, "keypress", this.keyIgnorer.bind(this)); this.regExp = window.linkCompleteTriggers || [ /\[\[([^\[\]\|\n]*?)$/ ]; }   },

insert: function (s) { var top = this.target.scrollTop; this.target.value = this.target.value.substr(0, this.start) + s               + this.target.value.substr(this.selectionGetStart(this.target)); this.target.scrollTop = top; this.selectionSet(this.target, this.start + s.length, this.start + s.length); },

insertMatch: function { if (this.nextMatch >= this.matches.length) { return false; }       this.insert(this.matches[this.nextMatch]); this.nextMatch++; return true; },

getMatches: function (from) { this.status = "waiting"; new Ajax.Request(wgScriptPath + "/api.php", {           parameters: {                format: "json",                action: "query",                list: "allpages",                apfrom: from,                aplimit: 50,                apfilterredir: "nonredirects",                apnamespace: window.linkCompleteNamespace || 0            },            method: "get",            onSuccess: function (transport) {                this.ajaxHandler(transport.responseText.parseJSON);            }.bind(this)        }); this.insert(this.find + "..."); },

reset: function { this.status = "idle"; this.insert(this.find); this.matches = []; },

keyIgnorer: function (e) { var keynum = e.charCode || e.keyCode; if (keynum == Event.KEY_TAB) { Event.stop(e); }   },

keyHandler: function (e) { var keynum = e.charCode || e.keyCode; var target = Event.element(e); if ( ((keynum == Event.KEY_TAB) || (e.ctrlKey && (keynum == 32)))                   && (this.selectionGetStart(target) == this.selectionGetEnd(target)) ) { if (target != this.target) { this.target = target; this.status = "idle"; }

switch (this.status) { case "idle": { var find; for (var i in this.regExp) { find = (target.value.substr(0, this.selectionGetStart(target)).match(this.regExp[i]) || [])[1]; if (find) break; }                   if (find) { this.matches = []; this.nextMatch = 0; this.find = find; this.start = this.selectionGetStart(target) - find.length; this.getMatches(find.capitalize); }                   break; }               case "waiting": { break; }               case "loaded": { if (this.nextMatch < this.matches.length) { if (!this.insertMatch) { this.reset; }                   } else { if (this.nextPage) { this.getMatches(this.nextPage); } else { this.nextMatch = 0; if (!this.insertMatch) { this.reset; }                       }                    }                    break; }               default: { break; }           }            Event.stop(e); } else { if (this.status == "waiting") { this.reset; }           this.status = "idle"; }   },

// JSON callback ajaxHandler: function (obj) { if (obj) { if (this.status == "waiting") { for (var i in obj.query.allpages) { page = obj.query.allpages[i]; if (page.title                           && (page.ns && page.title.replace(/^.*?:/, "").substr(0, this.find.length) == this.find.capitalize)                            || (page.title.substr(0, this.find.length) == this.find.capitalize)) { this.matches[this.matches.length] = (page.ns) ? page.title : this.find + page.title.substr(this.find.length); }               }                this.nextPage = (obj["query-continue"]) ? (                                   (obj["query-continue"].allpages.apfrom.substr(0, this.find.length) == this.find.capitalize)                                            ? obj["query-continue"].allpages.apfrom                                            : false                        ) : false;

if (this.insertMatch) { this.status = "loaded"; } else { this.reset; }           }        } else { this.reset; }   },

selectionSet: function (input, start, end) { if (input.setSelectionRange) { input.setSelectionRange(start, end) input.selectionEnd = end; } else { var range = input.createTextRange; range.collapse(true); range.moveStart("character", start); range.moveEnd("character", end - start); range.select; }   },

selectionGetStart: function (input) { if (input.setSelectionRange) { return input.selectionStart; } else { var range = document.selection.createRange; var isCollapsed = range.compareEndPoints("StartToEnd", range) == 0; if (!isCollapsed) { range.collapse(true); }           var b = range.getBookmark; return b.charCodeAt(2) - 2; }   },

selectionGetEnd: function (input) { if (input.setSelectionRange) { return input.selectionEnd; } else { var range = document.selection.createRange; var isCollapsed = range.compareEndPoints("StartToEnd", range) == 0; if (!isCollapsed) { range.collapse(false); }           var b = range.getBookmark; return b.charCodeAt(2) - 2; }   } };

// todo: Cameltrader - decide what to do with these

//from http://www.json.org/json.js String.prototype.parseJSON = function { try { return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test( this.replace(/"(\\.|[^"\\])*"/g, ""))) &&              eval("(" + this + ")");    } catch (e) {        return false;    } };

//"foo".capitalize -> "Foo" String.prototype.capitalize = function { return this.substring(0, 1).toUpperCase + this.substring(1) }; //     CyrillicPlugin.js   /* Contains enhancements which are suitable for the Bulgarian-language wikipedia

var CyrillicPlugin = Class.create;

CyrillicPlugin.prototype = {

initialize: function { // Precompute utf-8-to-codepoint conversion table var cyr = {}; for (var ch = 0x0400; ch <= 0x052f; ch++) { var chUtf8 = new Number( ((ch << 2) & 0x1f00) + (ch & 0x3f) + 0xc080 ).toString(16); var chPercent = "%" + chUtf8.substring(0, 2) + "%" + chUtf8.substring(2); cyr[chPercent] = cyr[chPercent.toUpperCase] = String.fromCharCode(ch); }       // Fix anchors for (var i = 0; i < document.links.length; i++) { var href = document.links[i].href; var re = /%(D[01])%([0-9a-fA-F]{2})/gi; while (true) { re.lastIndex = 0; var m = re.exec(href); if (m == null) { break; }               href = href.substring(0, m.index) + cyr[m[0]] + href.substring(m.index + m[0].length); }           document.links[i].href = href; }   } }; //      SubstitutionsPlugin.js

var SubstitutionsPlugin = Class.create;

SubstitutionsPlugin.SUBSTITUTIONS = [ { charCodeSequence: "\u0448\u0448", // sh-sh oldText: /\u0448\u0448$/, newText: "" },       { charCodeSequence: "\u0449\u0449", // sht-sht            oldText: /\u0449\u0449$/, newText: "" }, { charCodeSequence: "\u0428\u0428", // Sh-Sh oldText: /\u0428\u0428$/, newText: "{{" }, { charCodeSequence: "\u0429\u0429", // Sht-Sht oldText: /\u0429\u0429$/, newText: "}}" }, { charCodeSequence: "\u044e\u044e", // yu-yu oldText: /\u044e\u044e$/, newText: "|" }, { charCodeSequence: "", oldText: /(^|[ \n-])"([{letter}])$/.fix, newText: "$1\u201e$2" },       { charCodeSequence: "\"", oldText: /([{letter}])"$/.fix, newText: "$1\u201c" } ];

// number of letters before cursor required for replacements // or the size of the longest charCode sequence SubstitutionsPlugin.CONTEXT_SIZE = 20;

SubstitutionsPlugin.prototype = {

initialize: function { if ($("wpTextbox1") == null) { return; }       this.recentCharCodes = ""; Event.observe("wpTextbox1", "keypress", this.processKeyStroke.bind(this)); /*       var f = function (event) { $("out").value = "{type=" + event.type + ",keyCode=0x" + event.keyCode.toString(16) + ",charCode=0x" + event.charCode.toString(16) + "}\n" + $("out").value;

}       Event.observe("wpTextbox1", "keyup", f); Event.observe("wpTextbox1", "keypress", f); Event.observe("wpTextbox1", "keydown", f); },

processKeyStroke: function (event) { if (event.charCode == 0) { this.recentCharCodes = ""; return true; }       this.recentCharCodes += String.fromCharCode(event.charCode); if (this.recentCharCodes.length > 2 * SubstitutionsPlugin.CONTEXT_SIZE) { this.recentCharCodes = this.recentCharCodes.substring(SubstitutionsPlugin.CONTEXT_SIZE); }       var ta = $("wpTextbox1"); var p = ta.selectionStart; var s0 = ta.value.substring(p - SubstitutionsPlugin.CONTEXT_SIZE, p); var t = ta.scrollTop; for (var i = 0; i < SubstitutionsPlugin.SUBSTITUTIONS.length; i++) { var x = SubstitutionsPlugin.SUBSTITUTIONS[i]; if ((x.charCodeSequence == "")                   || (this.recentCharCodes.substr(-x.charCodeSequence.length) == x.charCodeSequence)) { var s1 = (s0 + String.fromCharCode(event.charCode)).replace(x.oldText, x.newText); if (s0 + String.fromCharCode(event.charCode) != s1) { ta.value = ta.value.substring(0, p - s0.length) + s1 + ta.value.substring(p); ta.selectionStart = ta.selectionEnd = p - s0.length + s1.length; ta.scrollTop = t;                   Event.stop(event); this.recentCharCodes = ""; break; }           }        }    } }; //