User:EpochFail/wg system.js

// /*! * jQuery contextMenu - Plugin for simple contextMenu handling * * Version: 1.6.6 * * Authors: Rodney Rehm, Addy Osmani (patches for FF) * Web: http://medialize.github.com/jQuery-contextMenu/ * * Licensed under *  MIT License http://www.opensource.org/licenses/mit-license *  GPL v3 http://opensource.org/licenses/GPL-3.0 * */

(function($, undefined){   // TODO: -        // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio        // create structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative

// determine html5 compatibility $.support.htmlMenuitem = ('HTMLMenuItemElement' in window); $.support.htmlCommand = ('HTMLCommandElement' in window); $.support.eventSelectstart = ("onselectstart" in document.documentElement); /* // should the need arise, test for css user-select $.support.cssUserSelect = (function{   var t = false,        e = document.createElement('div');    $.each('Moz|Webkit|Khtml|O|ms|Icab|'.split('|'), function(i, prefix) { var propCC = prefix + (prefix ? 'U' : 'u') + 'serSelect', prop = (prefix ? ('-' + prefix.toLowerCase + '-') : '') + 'user-select'; e.style.cssText = prop + ': text;'; if (e.style[propCC] == 'text') { t = true; return false; }       return true; });   return t; });

if (!$.ui || !$.ui.widget) { // duck punch $.cleanData like jQueryUI does to get that remove event // https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js#L16-24 var _cleanData = $.cleanData; $.cleanData = function( elems ) { for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { try { $( elem ).triggerHandler( "remove" ); // http://bugs.jquery.com/ticket/8235 } catch( e ) {} }       _cleanData( elems ); }; }

var // currently active contextMenu trigger $currentTrigger = null, // is contextMenu initialized with at least one menu? initialized = false, // window handle $win = $(window), // number of registered menus counter = 0, // mapping selector to namespace namespaces = {}, // mapping namespace to options menus = {}, // custom command type handlers types = {}, // default values defaults = { // selector of contextMenu trigger selector: null, // where to append the menu to       appendTo: null, // method to trigger context menu ["right", "left", "hover"] trigger: "right", // hide menu when mouse leaves trigger / menu elements autoHide: false, // ms to wait before showing a hover-triggered context menu delay: 200, // flag denoting if a second trigger should simply move (true) or rebuild (false) an open menu // as long as the trigger happened on one of the trigger-element's child nodes reposition: true, // determine position to show menu at       determinePosition: function($menu) { // position to the lower middle of the trigger element if ($.ui && $.ui.position) { // .position is provided as a jQuery UI utility // (...and it won't work on hidden elements) $menu.css('display', 'block').position({                   my: "center top",                    at: "center bottom",                    of: this,                    offset: "0 5",                    collision: "fit"                }).css('display', 'none'); } else { // determine contextMenu position var offset = this.offset; offset.top += this.outerHeight; offset.left += this.outerWidth / 2 - $menu.outerWidth / 2; $menu.css(offset); }       },        // position menu position: function(opt, x, y) { var $this = this, offset; // determine contextMenu position if (!x && !y) { opt.determinePosition.call(this, opt.$menu); return; } else if (x === "maintain" && y === "maintain") { // x and y must not be changed (after re-show on command click) offset = opt.$menu.position; } else { // x and y are given (by mouse event) offset = {top: y, left: x}; }           // correct offset if viewport demands it            var bottom = $win.scrollTop + $win.height, right = $win.scrollLeft + $win.width, height = opt.$menu.height, width = opt.$menu.width; if (offset.top + height > bottom) { offset.top -= height; }           if (offset.left + width > right) { offset.left -= width; }           opt.$menu.css(offset); },       // position the sub-menu positionSubmenu: function($menu) { if ($.ui && $.ui.position) { // .position is provided as a jQuery UI utility // (...and it won't work on hidden elements) $menu.css('display', 'block').position({                   my: "left top",                    at: "right top",                    of: this,                    collision: "flipfit fit"                }).css('display', ''); } else { // determine contextMenu position var offset = { top: 0, left: this.outerWidth };               $menu.css(offset); }       },        // offset to add to zIndex zIndex: 1, // show hide animation settings animation: { duration: 50, show: 'slideDown', hide: 'slideUp' },       // events events: { show: $.noop, hide: $.noop },       // default callback callback: null, // list of contextMenu items items: {} },   // mouse position for hover activation hoveract = { timer: null, pageX: null, pageY: null },   // determine zIndex zindex = function($t) { var zin = 0, $tt = $t;

while (true) { zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0); $tt = $tt.parent; if (!$tt || !$tt.length || "html body".indexOf($tt.prop('nodeName').toLowerCase) > -1 ) { break; }       }        return zin; },   // event handlers handle = { // abort anything abortevent: function(e){ e.preventDefault; e.stopImmediatePropagation; },       // contextmenu show dispatcher contextmenu: function(e) { var $this = $(this); // disable actual context-menu e.preventDefault; e.stopImmediatePropagation; // abort native-triggered events unless we're triggering on right click if (e.data.trigger != 'right' && e.originalEvent) { return; }           // abort event if menu is visible for this trigger if ($this.hasClass('context-menu-active')) { return; }           if (!$this.hasClass('context-menu-disabled')) { // theoretically need to fire a show event at                // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this }); // e.data.$menu.trigger(evt); $currentTrigger = $this; if (e.data.build) { var built = e.data.build($currentTrigger, e); // abort if build returned false if (built === false) { return; }                   // dynamically build menu on invocation e.data = $.extend(true, {}, defaults, e.data, built || {});

// abort if there are no items to display if (!e.data.items || $.isEmptyObject(e.data.items)) { // Note: jQuery captures and ignores errors from event handlers if (window.console) { (console.error || console.log)("No items specified to show in contextMenu"); }                       throw new Error('No Items specified'); }                   // backreference for custom command type creation e.data.$trigger = $currentTrigger; op.create(e.data); }               // show menu op.show.call($this, e.data, e.pageX, e.pageY); }       },        // contextMenu left-click trigger click: function(e) { e.preventDefault; e.stopImmediatePropagation; $(this).trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY })); },       // contextMenu right-click trigger mousedown: function(e) { // register mouse down var $this = $(this); // hide any previous menus if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) { $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide'); }           // activate on right click if (e.button == 2) { $currentTrigger = $this.data('contextMenuActive', true); }       },        // contextMenu right-click trigger mouseup: function(e) { // show menu var $this = $(this); if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) { e.preventDefault; e.stopImmediatePropagation; $currentTrigger = $this; $this.trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY })); }           $this.removeData('contextMenuActive'); },       // contextMenu hover trigger mouseenter: function(e) { var $this = $(this), $related = $(e.relatedTarget), $document = $(document); // abort if we're coming from a menu if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { return; }           // abort if a menu is shown if ($currentTrigger && $currentTrigger.length) { return; }           hoveract.pageX = e.pageX; hoveract.pageY = e.pageY; hoveract.data = e.data; $document.on('mousemove.contextMenuShow', handle.mousemove); hoveract.timer = setTimeout(function {               hoveract.timer = null;                $document.off('mousemove.contextMenuShow');                $currentTrigger = $this;                $this.trigger($.Event("contextmenu", { data: hoveract.data, pageX: hoveract.pageX, pageY: hoveract.pageY }));            }, e.data.delay ); },       // contextMenu hover trigger mousemove: function(e) { hoveract.pageX = e.pageX; hoveract.pageY = e.pageY; },       // contextMenu hover trigger mouseleave: function(e) { // abort if we're leaving for a menu var $related = $(e.relatedTarget); if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { return; }           try { clearTimeout(hoveract.timer); } catch(e) {} hoveract.timer = null; },       // click on layer to hide contextMenu layerClick: function(e) { var $this = $(this), root = $this.data('contextMenuRoot'), mouseup = false, button = e.button, x = e.pageX, y = e.pageY, target, offset, selectors; e.preventDefault; e.stopImmediatePropagation; setTimeout(function {               var $window, hideshow, possibleTarget;                var triggerAction = ((root.trigger == 'left' && button === 0) || (root.trigger == 'right' && button === 2));                // find the element that would've been clicked, wasn't the layer in the way                if (document.elementFromPoint) {                    root.$layer.hide;                    target = document.elementFromPoint(x - $win.scrollLeft, y - $win.scrollTop);                    root.$layer.show;                }                if (root.reposition && triggerAction) {                    if (document.elementFromPoint) {                        if (root.$trigger.is(target) || root.$trigger.has(target).length) {                            root.position.call(root.$trigger, root, x, y);                            return;                        }                    } else { offset = root.$trigger.offset; $window = $(window); // while this looks kinda awful, it's the best way to avoid // unnecessarily calculating any positions offset.top += $window.scrollTop; if (offset.top <= e.pageY) { offset.left += $window.scrollLeft; if (offset.left <= e.pageX) { offset.bottom = offset.top + root.$trigger.outerHeight; if (offset.bottom >= e.pageY) { offset.right = offset.left + root.$trigger.outerWidth; if (offset.right >= e.pageX) { // reposition root.position.call(root.$trigger, root, x, y); return; }                               }                            }                        }                    }                }                if (target && triggerAction) { root.$trigger.one('contextmenu:hidden', function {                       $(target).contextMenu({x: x, y: y});                    }); }

root.$menu.trigger('contextmenu:hide'); }, 50);       },        // key handled :hover        keyStop: function(e, opt) {            if (!opt.isInput) {                e.preventDefault;            }            e.stopPropagation;        },        key: function(e) {            var opt = $currentTrigger.data('contextMenu') || {};

switch (e.keyCode) { case 9: case 38: // up                   handle.keyStop(e, opt); // if keyCode is [38 (up)] or [9 (tab) with shift] if (opt.isInput) { if (e.keyCode == 9 && e.shiftKey) { e.preventDefault; opt.$selected && opt.$selected.find('input, textarea, select').blur; opt.$menu.trigger('prevcommand'); return; } else if (e.keyCode == 38 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') { // checkboxes don't capture this key e.preventDefault; return; }                   } else if (e.keyCode != 9 || e.shiftKey) { opt.$menu.trigger('prevcommand'); return; }                   // omitting break; // case 9: // tab - reached through omitted break; case 40: // down handle.keyStop(e, opt); if (opt.isInput) { if (e.keyCode == 9) { e.preventDefault; opt.$selected && opt.$selected.find('input, textarea, select').blur; opt.$menu.trigger('nextcommand'); return; } else if (e.keyCode == 40 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') { // checkboxes don't capture this key e.preventDefault; return; }                   } else { opt.$menu.trigger('nextcommand'); return; }                   break; case 37: // left handle.keyStop(e, opt); if (opt.isInput || !opt.$selected || !opt.$selected.length) { break; }                   if (!opt.$selected.parent.hasClass('context-menu-root')) { var $parent = opt.$selected.parent.parent; opt.$selected.trigger('contextmenu:blur'); opt.$selected = $parent; return; }                   break; case 39: // right handle.keyStop(e, opt); if (opt.isInput || !opt.$selected || !opt.$selected.length) { break; }                   var itemdata = opt.$selected.data('contextMenu') || {}; if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) { opt.$selected = null; itemdata.$selected = null; itemdata.$menu.trigger('nextcommand'); return; }                   break; case 35: // end case 36: // home if (opt.$selected && opt.$selected.find('input, textarea, select').length) { return; } else { (opt.$selected && opt.$selected.parent || opt.$menu) .children(':not(.disabled, .not-selectable)')[e.keyCode == 36 ? 'first' : 'last'] .trigger('contextmenu:focus'); e.preventDefault; return; }                   break; case 13: // enter handle.keyStop(e, opt); if (opt.isInput) { if (opt.$selected && !opt.$selected.is('textarea, select')) { e.preventDefault; return; }                       break; }                   opt.$selected && opt.$selected.trigger('mouseup'); return; case 32: // space case 33: // page up               case 34: // page down // prevent browser from scrolling down while menu is visible handle.keyStop(e, opt); return; case 27: // esc handle.keyStop(e, opt); opt.$menu.trigger('contextmenu:hide'); return; default: // 0-9, a-z var k = (String.fromCharCode(e.keyCode)).toUpperCase; if (opt.accesskeys[k]) { // according to the specs accesskeys must be invoked immediately opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu                           ? 'contextmenu:focus'                            : 'mouseup'                        ); return; }                   break; }           // pass event to selected item, // stop propagation to avoid endless recursion e.stopPropagation; opt.$selected && opt.$selected.trigger(e); },

// select previous possible command in menu prevItem: function(e) { e.stopPropagation; var opt = $(this).data('contextMenu') || {};

// obtain currently selected menu if (opt.$selected) { var $s = opt.$selected; opt = opt.$selected.parent.data('contextMenu') || {}; opt.$selected = $s; }           var $children = opt.$menu.children, $prev = !opt.$selected || !opt.$selected.prev.length ? $children.last : opt.$selected.prev, $round = $prev; // skip disabled while ($prev.hasClass('disabled') || $prev.hasClass('not-selectable')) { if ($prev.prev.length) { $prev = $prev.prev; } else { $prev = $children.last; }               if ($prev.is($round)) { // break endless loop return; }           }            // leave current if (opt.$selected) { handle.itemMouseleave.call(opt.$selected.get(0), e); }           // activate next handle.itemMouseenter.call($prev.get(0), e); // focus input var $input = $prev.find('input, textarea, select'); if ($input.length) { $input.focus; }       },        // select next possible command in menu nextItem: function(e) { e.stopPropagation; var opt = $(this).data('contextMenu') || {};

// obtain currently selected menu if (opt.$selected) { var $s = opt.$selected; opt = opt.$selected.parent.data('contextMenu') || {}; opt.$selected = $s; }

var $children = opt.$menu.children, $next = !opt.$selected || !opt.$selected.next.length ? $children.first : opt.$selected.next, $round = $next;

// skip disabled while ($next.hasClass('disabled') || $next.hasClass('not-selectable')) { if ($next.next.length) { $next = $next.next; } else { $next = $children.first; }               if ($next.is($round)) { // break endless loop return; }           }            // leave current if (opt.$selected) { handle.itemMouseleave.call(opt.$selected.get(0), e); }           // activate next handle.itemMouseenter.call($next.get(0), e); // focus input var $input = $next.find('input, textarea, select'); if ($input.length) { $input.focus; }       },        // flag that we're inside an input so the key handler can act accordingly focusInput: function(e) { var $this = $(this).closest('.context-menu-item'), data = $this.data, opt = data.contextMenu, root = data.contextMenuRoot;

root.$selected = opt.$selected = $this; root.isInput = opt.isInput = true; },       // flag that we're inside an input so the key handler can act accordingly blurInput: function(e) { var $this = $(this).closest('.context-menu-item'), data = $this.data, opt = data.contextMenu, root = data.contextMenuRoot;

root.isInput = opt.isInput = false; },       // :hover on menu menuMouseenter: function(e) { var root = $(this).data.contextMenuRoot; root.hovering = true; },       // :hover on menu menuMouseleave: function(e) { var root = $(this).data.contextMenuRoot; if (root.$layer && root.$layer.is(e.relatedTarget)) { root.hovering = false; }       },        // :hover done manually so key handling is possible itemMouseenter: function(e) { var $this = $(this), data = $this.data, opt = data.contextMenu, root = data.contextMenuRoot; root.hovering = true;

// abort if we're re-entering if (e && root.$layer && root.$layer.is(e.relatedTarget)) { e.preventDefault; e.stopImmediatePropagation; }

// make sure only one item is selected (opt.$menu ? opt : root).$menu .children('.hover').trigger('contextmenu:blur');

if ($this.hasClass('disabled') || $this.hasClass('not-selectable')) { opt.$selected = null; return; }           $this.trigger('contextmenu:focus'); },       // :hover done manually so key handling is possible itemMouseleave: function(e) { var $this = $(this), data = $this.data, opt = data.contextMenu, root = data.contextMenuRoot;

if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) { root.$selected && root.$selected.trigger('contextmenu:blur'); e.preventDefault; e.stopImmediatePropagation; root.$selected = opt.$selected = opt.$node; return; }           $this.trigger('contextmenu:blur'); },       // contextMenu item click itemClick: function(e) { var $this = $(this), data = $this.data, opt = data.contextMenu, root = data.contextMenuRoot, key = data.contextMenuKey, callback;

// abort if the key is unknown or disabled or is a menu if (!opt.items[key] || $this.is('.disabled, .context-menu-submenu, .context-menu-separator, .not-selectable')) { return; }

e.preventDefault; e.stopImmediatePropagation;

if ($.isFunction(root.callbacks[key]) && Object.prototype.hasOwnProperty.call(root.callbacks, key)) { // item-specific callback callback = root.callbacks[key]; } else if ($.isFunction(root.callback)) { // default callback callback = root.callback; } else { // no callback, no action return; }

// hide menu if callback doesn't stop that if (callback.call(root.$trigger, key, root) !== false) { root.$menu.trigger('contextmenu:hide'); } else if (root.$menu.parent.length) { op.update.call(root.$trigger, root); }       },        // ignore click events on input elements inputClick: function(e) { e.stopImmediatePropagation; },       // hide hideMenu: function(e, data) { var root = $(this).data('contextMenuRoot'); op.hide.call(root.$trigger, root, data && data.force); },       // focus focusItem: function(e) { e.stopPropagation; var $this = $(this), data = $this.data, opt = data.contextMenu, root = data.contextMenuRoot;

$this.addClass('hover') .siblings('.hover').trigger('contextmenu:blur'); // remember selected opt.$selected = root.$selected = $this; // position sub-menu - do after show so dumb $.ui.position can keep up           if (opt.$node) { root.positionSubmenu.call(opt.$node, opt.$menu); }       },        // blur blurItem: function(e) { e.stopPropagation; var $this = $(this), data = $this.data, opt = data.contextMenu, root = data.contextMenuRoot; $this.removeClass('hover'); opt.$selected = null; }   },    // operations op = { show: function(opt, x, y) { var $trigger = $(this), offset, css = {};

// hide any open menus $('#context-menu-layer').trigger('mousedown');

// backreference for callbacks opt.$trigger = $trigger;

// show event if (opt.events.show.call($trigger, opt) === false) { $currentTrigger = null; return; }

// create or update context menu op.update.call($trigger, opt); // position menu opt.position.call($trigger, opt, x, y);

// make sure we're in front if (opt.zIndex) { css.zIndex = zindex($trigger) + opt.zIndex; }           // add layer op.layer.call(opt.$menu, opt, css.zIndex); // adjust sub-menu zIndexes opt.$menu.find('ul').css('zIndex', css.zIndex + 1); // position and show context menu opt.$menu.css( css )[opt.animation.show](opt.animation.duration, function {               $trigger.trigger('contextmenu:visible');            }); // make options available and set state $trigger .data('contextMenu', opt) .addClass("context-menu-active"); // register key handler $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key); // register autoHide handler if (opt.autoHide) { // mouse position handler $(document).on('mousemove.contextMenuAutoHide', function(e) {                   // need to capture the offset on mousemove,                    // since the page might've been scrolled since activation                    var pos = $trigger.offset;                    pos.right = pos.left + $trigger.outerWidth;                    pos.bottom = pos.top + $trigger.outerHeight;                    if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) {                        // if mouse in menu...                        opt.$menu.trigger('contextmenu:hide');                    }                }); }       },        hide: function(opt, force) { var $trigger = $(this); if (!opt) { opt = $trigger.data('contextMenu') || {}; }           // hide event if (!force && opt.events && opt.events.hide.call($trigger, opt) === false) { return; }           // remove options and revert state $trigger .removeData('contextMenu') .removeClass("context-menu-active"); if (opt.$layer) { // keep layer for a bit so the contextmenu event can be aborted properly by opera setTimeout((function($layer) { return function{ $layer.remove; };               })(opt.$layer), 10); try { delete opt.$layer; } catch(e) { opt.$layer = null; }           }            // remove handle $currentTrigger = null; // remove selected opt.$menu.find('.hover').trigger('contextmenu:blur'); opt.$selected = null; // unregister key and mouse handlers //$(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705 $(document).off('.contextMenuAutoHide').off('keydown.contextMenu'); // hide menu opt.$menu && opt.$menu[opt.animation.hide](opt.animation.duration, function {               // tear down dynamically built menu after animation is completed.                if (opt.build) {                    opt.$menu.remove;                    $.each(opt, function(key, value) { switch (key) { case 'ns': case 'selector': case 'build': case 'trigger': return true;

default: opt[key] = undefined; try { delete opt[key]; } catch (e) {} return true; }                   });                }                setTimeout(function { $trigger.trigger('contextmenu:hidden'); }, 10);           });        },        create: function(opt, root) { if (root === undefined) { root = opt; }           // create contextMenu opt.$menu = $('').addClass(opt.className || "").data({               'contextMenu': opt,                'contextMenuRoot': root            }); $.each(['callbacks', 'commands', 'inputs'], function(i,k){               opt[k] = {};                if (!root[k]) {                    root[k] = {};                }            }); root.accesskeys || (root.accesskeys = {}); // create contextMenu items $.each(opt.items, function(key, item){               var $t = $('').addClass(item.className || ""),                    $label = null,                    $input = null;                // iOS needs to see a click-event bound to an element to actually                // have the TouchEvents infrastructure trigger the click event                $t.on('click', $.noop);                item.$node = $t.data({ 'contextMenu': opt, 'contextMenuRoot': root, 'contextMenuKey': key });               // register accesskey                // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that                if (item.accesskey) {                    var aks = splitAccesskey(item.accesskey);                    for (var i=0, ak; ak = aks[i]; i++) {                        if (!root.accesskeys[ak]) {                            root.accesskeys[ak] = item;                            item._name = item.name.replace(new RegExp('(' + ak + ')', 'i'), ' $1 ');                            break;                        }                    }                }                if (typeof item == "string") {                    $t.addClass('context-menu-separator not-selectable');                } else if (item.type && types[item.type]) {                    // run custom type handler                    types[item.type].call($t, item, opt, root); // register commands $.each([opt, root], function(i,k){                       k.commands[key] = item;                        if ($.isFunction(item.callback)) {                            k.callbacks[key] = item.callback;                        }                    }); } else { // add label for input if (item.type == 'html') { $t.addClass('context-menu-html not-selectable'); } else if (item.type) { $label = $(' ').appendTo($t); $(' ').html(item._name || item.name).appendTo($label); $t.addClass('context-menu-input'); opt.hasTypes = true; $.each([opt, root], function(i,k){                           k.commands[key] = item;                            k.inputs[key] = item;                        }); } else if (item.items) { item.type = 'sub'; }                   switch (item.type) { case 'text': $input = $('') .attr('name', 'context-menu-input-' + key) .val(item.value || "") .appendTo($label); break; case 'textarea': $input = $(' ') .attr('name', 'context-menu-input-' + key) .val(item.value || "") .appendTo($label);

if (item.height) { $input.height(item.height); }                           break;

case 'checkbox': $input = $('') .attr('name', 'context-menu-input-' + key) .val(item.value || "") .prop("checked", !!item.selected) .prependTo($label); break;

case 'radio': $input = $('') .attr('name', 'context-menu-input-' + item.radio) .val(item.value || "") .prop("checked", !!item.selected) .prependTo($label); break; case 'select': $input = $(' ') .attr('name', 'context-menu-input-' + key) .appendTo($label); if (item.options) { $.each(item.options, function(value, text) {                                   $('  ').val(value).text(text).appendTo($input);                                }); $input.val(item.selected); }                           break; case 'sub': // FIXME: shouldn't this .html be a .text? $(' ').html(item._name || item.name).appendTo($t); item.appendTo = item.$node; op.create(item, root); $t.data('contextMenu', item).addClass('context-menu-submenu'); item.callback = null; break; case 'html': $(item.html).appendTo($t); break; default: $.each([opt, root], function(i,k){                               k.commands[key] = item;                                if ($.isFunction(item.callback)) {                                    k.callbacks[key] = item.callback;                                }                            }); // FIXME: shouldn't this .html be a .text? $(' ').html(item._name || item.name || "").appendTo($t); break; }                   // disable key listener in                     if (item.type && item.type != 'sub' && item.type != 'html') { $input .on('focus', handle.focusInput) .on('blur', handle.blurInput); if (item.events) { $input.on(item.events, opt); }                   }                    // add icons if (item.icon) { $t.addClass("icon icon-" + item.icon); }               }                // cache contained elements item.$input = $input; item.$label = $label;

// attach item to menu $t.appendTo(opt.$menu); // Disable text selection if (!opt.hasTypes && $.support.eventSelectstart) { // browsers support user-select: none, // IE has a special event for text-selection // browsers supporting neither will not be preventing text-selection $t.on('selectstart.disableTextSelect', handle.abortevent); }           });            // attach contextMenu to (to bypass any possible overflow:hidden issues on parents of the trigger element)            if (!opt.$node) {                opt.$menu.css('display', 'none').addClass('context-menu-root');            }            opt.$menu.appendTo(opt.appendTo || document.body);        },        resize: function($menu, nested) {            // determine widths of submenus, as CSS won't grow them automatically            // position:absolute within position:absolute; min-width:100; max-width:200; results in width: 100;            // kinda sucks hard...

// determine width of absolutely positioned element $menu.css({position: 'absolute', display: 'block'}); // don't apply yet, because that would break nested elements' widths // add a pixel to circumvent word-break issue in IE9 - #80 $menu.data('width', Math.ceil($menu.width) + 1); // reset styles so they allow nested elements to grow/shrink naturally $menu.css({               position: 'static',                minWidth: '0px',                maxWidth: '100000px'            }); // identify width of nested menus $menu.find('> li > ul').each(function {               op.resize($(this), true);            }); // reset and apply changes in the end because nested // elements' widths wouldn't be calculatable otherwise if (!nested) { $menu.find('ul').addBack.css({                   position: ,                    display: ,                    minWidth: ,                    maxWidth:                 }).width(function {                    return $(this).data('width');                }); }       },        update: function(opt, root) { var $trigger = this; if (root === undefined) { root = opt; op.resize(opt.$menu); }           // re-check disabled for each item opt.$menu.children.each(function{               var $item = $(this),                    key = $item.data('contextMenuKey'),                    item = opt.items[key],                    disabled = ($.isFunction(item.disabled) && item.disabled.call($trigger, key, root)) || item.disabled === true;

// dis- / enable item $item[disabled ? 'addClass' : 'removeClass']('disabled'); if (item.type) { // dis- / enable input elements $item.find('input, select, textarea').prop('disabled', disabled); // update input states switch (item.type) { case 'text': case 'textarea': item.$input.val(item.value || ""); break; case 'checkbox': case 'radio': item.$input.val(item.value || "").prop('checked', !!item.selected); break; case 'select': item.$input.val(item.selected || ""); break; }               }                if (item.$menu) { // update sub-menu op.update.call($trigger, item, root); }           });        },        layer: function(opt, zIndex) {            // add transparent layer for click area            // filter and background for Internet Explorer, Issue #23            var $layer = opt.$layer = $(' ')                .css({height: $win.height, width: $win.width, display: 'block'})                .data('contextMenuRoot', opt)                .insertBefore(this)                .on('contextmenu', handle.abortevent)                .on('mousedown', handle.layerClick);            // IE6 doesn't know position:fixed;            if (!$.support.fixedPosition) {                $layer.css({ 'position' : 'absolute', 'height' : $(document).height });           }            return $layer;        }    };

// split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key function splitAccesskey(val) { var t = val.split(/\s+/), keys = []; for (var i=0, k; k = t[i]; i++) { k = k[0].toUpperCase; // first character only // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it. // a map to look up already used access keys would be nice keys.push(k); }   return keys; }

// handle contextMenu triggers $.fn.contextMenu = function(operation) { if (operation === undefined) { this.first.trigger('contextmenu'); } else if (operation.x && operation.y) { this.first.trigger($.Event("contextmenu", {pageX: operation.x, pageY: operation.y})); } else if (operation === "hide") { var $menu = this.data('contextMenu').$menu; $menu && $menu.trigger('contextmenu:hide'); } else if (operation === "destroy") { $.contextMenu("destroy", {context: this}); } else if ($.isPlainObject(operation)) { operation.context = this; $.contextMenu("create", operation); } else if (operation) { this.removeClass('context-menu-disabled'); } else if (!operation) { this.addClass('context-menu-disabled'); }   return this; };

// manage contextMenu instances $.contextMenu = function(operation, options) { if (typeof operation != 'string') { options = operation; operation = 'create'; }   if (typeof options == 'string') { options = {selector: options}; } else if (options === undefined) { options = {}; }   // merge with default options var o = $.extend(true, {}, defaults, options || {}); var $document = $(document); var $context = $document; var _hasContext = false; if (!o.context || !o.context.length) { o.context = document; } else { // you never know what they throw at you... $context = $(o.context).first; o.context = $context.get(0); _hasContext = o.context !== document; }   switch (operation) { case 'create': // no selector no joy if (!o.selector) { throw new Error('No selector specified'); }           // make sure internal classes are not bound to            if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) { throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className'); }           if (!o.build && (!o.items || $.isEmptyObject(o.items))) { throw new Error('No Items specified'); }           counter ++; o.ns = '.contextMenu' + counter; if (!_hasContext) { namespaces[o.selector] = o.ns; }           menus[o.ns] = o;            // default to right click if (!o.trigger) { o.trigger = 'right'; }           if (!initialized) { // make sure item click is registered first $document .on({                       'contextmenu:hide.contextMenu': handle.hideMenu,                        'prevcommand.contextMenu': handle.prevItem,                        'nextcommand.contextMenu': handle.nextItem,                        'contextmenu.contextMenu': handle.abortevent,                        'mouseenter.contextMenu': handle.menuMouseenter,                        'mouseleave.contextMenu': handle.menuMouseleave                    }, '.context-menu-list') .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick) .on({                       'mouseup.contextMenu': handle.itemClick,                        'contextmenu:focus.contextMenu': handle.focusItem,                        'contextmenu:blur.contextMenu': handle.blurItem,                        'contextmenu.contextMenu': handle.abortevent,                        'mouseenter.contextMenu': handle.itemMouseenter,                        'mouseleave.contextMenu': handle.itemMouseleave                    }, '.context-menu-item');

initialized = true; }           // engage native contextmenu event $context .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu); if (_hasContext) { // add remove hook, just in case $context.on('remove' + o.ns, function {                   $(this).contextMenu("destroy");                }); }           switch (o.trigger) { case 'hover': $context .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter) .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave); break; case 'left': $context.on('click' + o.ns, o.selector, o, handle.click); break; /*               default: // http://www.quirksmode.org/dom/events/contextmenu.html $document .on('mousedown' + o.ns, o.selector, o, handle.mousedown) .on('mouseup' + o.ns, o.selector, o, handle.mouseup); break; */           }            // create menu if (!o.build) { op.create(o); }           break; case 'destroy': var $visibleMenu; if (_hasContext) { // get proper options var context = o.context; $.each(menus, function(ns, o) {                   if (o.context !== context) {                        return true;                    }                    $visibleMenu = $('.context-menu-list').filter(':visible');                    if ($visibleMenu.length && $visibleMenu.data.contextMenuRoot.$trigger.is($(o.context).find(o.selector))) {                        $visibleMenu.trigger('contextmenu:hide', {force: true});                    }

try { if (menus[o.ns].$menu) { menus[o.ns].$menu.remove; }

delete menus[o.ns]; } catch(e) { menus[o.ns] = null; }

$(o.context).off(o.ns); return true; });           } else if (!o.selector) {                $document.off('.contextMenu .contextMenuAutoHide');                $.each(menus, function(ns, o) { $(o.context).off(o.ns); });               namespaces = {};                menus = {};                counter = 0;                initialized = false;                $('#context-menu-layer, .context-menu-list').remove;            } else if (namespaces[o.selector]) {                $visibleMenu = $('.context-menu-list').filter(':visible');                if ($visibleMenu.length && $visibleMenu.data.contextMenuRoot.$trigger.is(o.selector)) {                    $visibleMenu.trigger('contextmenu:hide', {force: true});                }                try {                    if (menus[namespaces[o.selector]].$menu) {                        menus[namespaces[o.selector]].$menu.remove;                    }                    delete menus[namespaces[o.selector]];                } catch(e) {                    menus[namespaces[o.selector]] = null;                }                $document.off(namespaces[o.selector]); }           break; case 'html5': // if or are not handled by the browser, // or options was a bool true, // initialize $.contextMenu for them if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options == "boolean" && options)) { $('menu[type="context"]').each(function {                   if (this.id) {                        $.contextMenu({ selector: '[contextmenu=' + this.id +']', items: $.contextMenu.fromMenu(this) });                   }                }).css('display', 'none'); }           break; default: throw new Error('Unknown operation "' + operation + '"'); }   return this; };

// import values into commands $.contextMenu.setInputValues = function(opt, data) { if (data === undefined) { data = {}; }   $.each(opt.inputs, function(key, item) {        switch (item.type) {            case 'text':            case 'textarea':                item.value = data[key] || "";                break;

case 'checkbox': item.selected = data[key] ? true : false; break; case 'radio': item.selected = (data[item.radio] || "") == item.value ? true : false; break; case 'select': item.selected = data[key] || ""; break; }   }); };

// export values from commands $.contextMenu.getInputValues = function(opt, data) { if (data === undefined) { data = {}; }   $.each(opt.inputs, function(key, item) {        switch (item.type) {            case 'text':            case 'textarea':            case 'select':                data[key] = item.$input.val;                break;

case 'checkbox': data[key] = item.$input.prop('checked'); break; case 'radio': if (item.$input.prop('checked')) { data[item.radio] = item.value; }               break; }   });    return data; };

// find function inputLabel(node) { return (node.id && $('label[for="'+ node.id +'"]').val) || node.name; }

// convert to items object function menuChildren(items, $children, counter) { if (!counter) { counter = 0; }   $children.each(function {        var $node = $(this),            node = this,            nodeName = this.nodeName.toLowerCase,            label,            item;        // extract          if (nodeName == 'label' && $node.find('input, textarea, select').length) {            label = $node.text;            $node = $node.children.first;            node = $node.get(0);            nodeName = node.nodeName.toLowerCase;        }        /*         * accepts flow-content as children. that means, and such are valid menu items.         * Not being the sadistic kind, $.contextMenu only accepts:         * , , , , , , and of course .         * Everything else will be imported as an html node, which is not interfaced with contextMenu.         */        // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#concept-command        switch (nodeName) { // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element case 'menu': item = {name: $node.attr('label'), items: {}}; counter = menuChildren(item.items, $node.children, counter); break; // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-a-element-to-define-a-command case 'a': // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-button-element-to-define-a-command case 'button': item = { name: $node.text, disabled: !!$node.attr('disabled'), callback: (function{ return function{ $node.click; }; }) };               break; // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-command-element-to-define-a-command

case 'menuitem': case 'command': switch ($node.attr('type')) { case undefined: case 'command': case 'menuitem': item = { name: $node.attr('label'), disabled: !!$node.attr('disabled'), callback: (function{ return function{ $node.click; }; }) };                       break; case 'checkbox': item = { type: 'checkbox', disabled: !!$node.attr('disabled'), name: $node.attr('label'), selected: !!$node.attr('checked') };                       break; case 'radio': item = { type: 'radio', disabled: !!$node.attr('disabled'), name: $node.attr('label'), radio: $node.attr('radiogroup'), value: $node.attr('id'), selected: !!$node.attr('checked') };                       break; default: item = undefined; }               break; case 'hr': item = '---'; break; case 'input': switch ($node.attr('type')) { case 'text': item = { type: 'text', name: label || inputLabel(node), disabled: !!$node.attr('disabled'), value: $node.val };                       break; case 'checkbox': item = { type: 'checkbox', name: label || inputLabel(node), disabled: !!$node.attr('disabled'), selected: !!$node.attr('checked') };                       break; case 'radio': item = { type: 'radio', name: label || inputLabel(node), disabled: !!$node.attr('disabled'), radio: !!$node.attr('name'), value: $node.val, selected: !!$node.attr('checked') };                       break; default: item = undefined; break; }               break; case 'select': item = { type: 'select', name: label || inputLabel(node), disabled: !!$node.attr('disabled'), selected: $node.val, options: {} };               $node.children.each(function{                    item.options[this.value] = $(this).text;                }); break; case 'textarea': item = { type: 'textarea', name: label || inputLabel(node), disabled: !!$node.attr('disabled'), value: $node.val };               break; case 'label': break; default: item = {type: 'html', html: $node.clone(true)}; break; }       if (item) { counter++; items['key' + counter] = item; }   });    return counter; }

// convert html5 menu $.contextMenu.fromMenu = function(element) { var $this = $(element), items = {}; menuChildren(items, $this.children); return items; };

// make defaults accessible $.contextMenu.defaults = defaults; $.contextMenu.types = types; // export internal functions - undocumented, for hacking only! $.contextMenu.handle = handle; $.contextMenu.op = op; $.contextMenu.menus = menus;

})(jQuery); /* Simple JavaScript Inheritance * By John Resig http://ejohn.org/ * MIT Licensed. */ // Inspired by base2 and Prototype (function{ var initializing = false, fnTest = /xyz/.test(function{xyz;}) ? /\b_super\b/ : /.*/; // The base Class implementation (does nothing) this.Class = function{}; // Create a new Class that inherits from this class Class.extend = function(prop) { var _super = this.prototype; // Instantiate a base class (but only create the instance,   // don't run the init constructor) initializing = true; var prototype = new this; initializing = false; // Copy the properties over onto the new prototype for (var name in prop) { // Check if we're overwriting an existing function prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){         return function {            var tmp = this._super;            // Add a new ._super method that is the same method            // but on the super-class            this._super = _super[name];            // The method only need to be bound temporarily, so we            // remove it when we're done executing            var ret = fn.apply(this, arguments);                    this._super = tmp;            return ret;          };        })(name, prop[name]) : prop[name]; }   // The dummy class constructor function Class { // All construction is actually done in the init method if ( !initializing && this.init ) this.init.apply(this, arguments); }   // Populate our constructed prototype object Class.prototype = prototype; // Enforce the constructor to be what we expect Class.prototype.constructor = Class;

// And make this class extendable Class.extend = arguments.callee; return Class; }; }); if(!window.WG){WG = {}}

WG.ConsoleMessage = function(type, prefix, message){ this.type = type this.prefix = prefix this.message = message this.div = $(' ') .append(			$(' ')				.addClass(type)				.text(prefix + ": ")		) .append(message) }	WG.ConsoleMessage.prototype.toString = function{ return this.prefix + ": " + this.message }

WG.Console = function{ this.pane = $(' ') this.info("Console loaded...") $(document).ready(function(self){			return function{				self.pane.prependTo($('#content'))			}		}(this)	) } WG.Console.prototype.log = function(consoleMessage){ this.pane.prepend(consoleMessage.div) } WG.Console.prototype.info = function(message){ this.log(new WG.ConsoleMessage("info", "INFO", message)) } WG.Console.prototype.warning = function(message){ this.log(new WG.ConsoleMessage("warning", "WARNING", message)) } WG.Console.prototype.error = function(message){ this.log(new WG.ConsoleMessage("error", "ERROR", message)) }

WG.HiddenConsole = function{ this.messageLog = [] } WG.HiddenConsole.prototype.log = function(consoleMessage){ out = '...\n' if(consoleMessage.type == "error"){ for(var i=Math.max(0, this.messageLog.length-5); i < this.messageLog.length;i++){ message = this.messageLog[i] out += message.toString + "\n" }		out += consoleMessage.toString alert("An error occurred: \n\n" + out) }	this.messageLog.push(consoleMessage) if(window.console){ console.log(consoleMessage.toString) } } WG.HiddenConsole.prototype.info = function(message){ this.log(new WG.ConsoleMessage("info", "INFO", message)) } WG.HiddenConsole.prototype.warning = function(message){ this.log(new WG.ConsoleMessage("warning", "WARNING", message)) } WG.HiddenConsole.prototype.error = function(message){ this.log(new WG.ConsoleMessage("error", "ERROR", message)) }

if(!window.WG){WG = {}}

$.extend(WG, {	NOTE_TEMPLATE: 'User:EpochFail/Snote' })

$.extend(WG, {	NOTE_TEMPLATE_RE: RegExp( '\\{\\{[ \\n\\r\\t]*' + WG.NOTE_TEMPLATE.replace(/\:/g, "\\:").replace(/\//g, "\\/") + '[ \\n\\r\\t]*' + '\\|?([^\\|]*?)(\\|.*)*\\}\\}'	),	TOKEN_MAP: {		white_space:        ' +',		number:              "[0-9]+(\\.[0-9]+)?|(\\.[0-9]+)",		word:                '\\w+',		entity:              '&\\w+;',		list_item:           '\\n+(\\*|\\#|\\:)+',		def_item:            '\\n+\\;',		header:              '(\\n+|^)(=[^=].+?=|==[^=].+?==|===[^=].+?===|====[^=].+?====|=====[^=].+?=====)',		open_table:          '\\n*\\{\\|| ',		nowiki:              ']*>(.|\\n|\\r)*?',		gallery:             ,		close_table:         '\\|\\}| ',		open_div:            ']*>',		close_div:           ' ',		table_row:           '\\|-',		line_break:          '\\n',		open_markup:         '<[^\\/][^>]*>',		close_markup:        ']*>',		bold:                "'",		italics:             "''",		quote:               "'|\\\"",		ellipsis:            '\\.\\.\\.',		period:              '\\.',		comma:               ',', exlamation:         '!', question:           '\\?', colon:              '\\:', semicolon:          '\\;', bar:                '\\|', other:              '.' },	concatTokens: function(tokens, lower, upper){ lower = lower||0 upper = upper||tokens.length str = '' for(var i = lower; i < upper; i++){ str += tokens[i].c		} return str },	NOTE_TEMPLATE: 'User:EpochFail/Snote' })

/** Converts MediaWiki markup into tokens. For possible tokens and the regexp they match, see `WG.TOKEN_MAP`. An arbitrary map of tokens can be provided.


 * Parameters:

markup : String mediawiki markup to be tokenized tokens : Object a map from " " to " " (as a string) WG.Tokenizer = Class.extend({	init: function(markup, tokens){		this.tokens = tokens || WG.TOKEN_MAP		this.markup = markup		var expressionParts = []		for(type in this.tokens){			expressionParts.push(this.tokens[type])		}		this.tokenRE = RegExp(expressionParts.join("|"), "gi")		this.lookAhead = this.__nextToken	},

/**	Returns an array of all tokens found in `markup'. */	popAll: function{ var tokens = [] while(this.peek){ tokens.push(this.pop) }		return tokens },	/**	Returns the next token without removing it. */	peek: function{ if(this.lookAhead){ return this.lookAhead }else{ return null }	},	/**	Returns and removes the next token. */	pop: function{ if(this.lookAhead){ var temp = this.lookAhead var lastTime = new Date.getTime/1000 this.lookAhead = this.__nextToken WG.WAIT_TIME += (new Date.getTime/1000) - lastTime return temp }else{ return null }	},	__nextToken: function{ var timeBefore = new Date.getTime / 1000 var match = this.tokenRE.exec(this.markup) WG.WAIT_TIME += (new Date.getTime / 1000) - timeBefore if(!match){ return null }else{ var content = match[0] for(type in this.tokens){ var re = RegExp("^" + this.tokens[type] + "$", "gi") if(re.test(content)){ return {t:type, c:content} }			}			throw "Unexpected token content '" + content + "' matched no known token types." }	} })

/** Converts MediaWiki markup into chunks of content. Possible chunks to be returned include: - header - note - template - table - list item - div - paragraph break - line break - sentence

All chunks follow the scheme: {t:" ", id:, c:" "}

Joining the token content of chunks in order should reproduce the original MediaWiki markup.


 * Parameters:

markup : String mediawiki markup to be tokenized tokens : Object a map from " " to " " (as a string) */ WG.Chunker = function(markup, tokens){ this.tokenizer = new WG.Tokenizer(markup, tokens) this.lookAhead = this.__nextChunk }	/**	Returns an array of all chunks found in `markup'. */	WG.Chunker.prototype.popAll = function{ var chunks = [] while(this.peek){ chunks.push(this.pop) }		return chunks }	/**	Returns the next chunk without removing it. */	WG.Chunker.prototype.peek = function{ if(this.lookAhead){ return this.lookAhead }else{ return null }	}	/**	Returns and removes the next chunk */	WG.Chunker.prototype.pop = function{ if(this.lookAhead){ var temp = this.lookAhead this.lookAhead = this.__nextChunk return temp }else{ return null }	}	WG.Chunker.prototype.__nextChunk = function{ if(!this.chunkId){ this.chunkId  = 0 }		if(this.tokenizer.peek){ //We have tokens to process switch(this.tokenizer.peek.t){ case "open_template": return { t: "template", c: WG.concatTokens(this.__template), id: this.chunkId++ }				case "open_note": var tokens = this.__note return { t: "note", c: WG.concatTokens(tokens), id: this.chunkId++, val: WG.concatTokens(tokens, 1,tokens.length-1) }				case "open_table": return { t: "table", c: WG.concatTokens(this.__table), id: this.chunkId++ }				case "open_div": return { t: "div", c: WG.concatTokens(this.__div), id: this.chunkId++ }				case "open_image": return { t: "image", c: WG.concatTokens(this.__image), id: this.chunkId++ }				case "def_item": return { t: "definition", c: WG.concatTokens(this.__definition), id: this.chunkId++ }				case "list_item": return { t: "def_list_item", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "header": return { t: "header", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "paragraph_break": return { t: "paragraph_break", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "line_break": return { t: "break", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "ref": return { t: "ref", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				/*case "math": return { t: "math", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }*/				case "pre": return { t: "pre", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "gallery": return { t: "gallery", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "comment": return { t: "comment", c: this.tokenizer.pop.c,						id: this.chunkId++ }				case "white_space": return { t: "white_space", c: this.tokenizer.pop.c,						id: this.chunkId++ }				case "colon": return { t: "colon", c: this.tokenizer.pop.c,						id: this.chunkId++ }				case "source": return { t: "source", c: this.tokenizer.pop.c,						id: this.chunkId++ }				case "close_template": case "close_table": case "close_div": case "close_markup": case "exclamation": case "question": case "period": case "comma": case "table_row": case "close_internal_link": case "white_space": case "colon": case "semicolon": case "open_markup": case "italics": case "bold": case "quote": case "ellipsis": case "open_internal_link": case "external_link": case "entity": case "number": case "word": case "nowiki": case "math": case "other": tokens = this.__sentence return { t: 'sentence', c: WG.concatTokens(tokens), id: this.chunkId++, tokens: tokens }					break; default: throw "Unexpected token type '" + this.tokenizer.peek.t + "' found while generating chunks." }		}else{ //No more tokens. No more chunks. return null }	}	WG.Chunker.prototype.__template = function{ var tokens = [this.tokenizer.pop] var templates = 1 while(this.tokenizer.peek && templates > 0){ switch(this.tokenizer.peek.t){ case "open_template": //going one deeper! templates++ break; case "close_template": //coming out of the templates templates-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }	WG.Chunker.prototype.__div = function{ var tokens = [this.tokenizer.pop] var divs = 1 while(this.tokenizer.peek && divs > 0){ switch(this.tokenizer.peek.t){ case "open_div": //going one deeper! divs++ break; case "close_div": //coming out of the divs divs-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }	WG.Chunker.prototype.__table = function{ var tokens = [this.tokenizer.pop] var tables = 1 while(this.tokenizer.peek && tables > 0){ switch(this.tokenizer.peek.t){ case "open_table": //going one deeper! tables++ break; case "close_table": //coming out of the tables tables-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }	WG.Chunker.prototype.__definition = function{ var tokens = [] var done = false while(this.tokenizer.peek && !done){ switch(this.tokenizer.peek.t){ case "colon":          //--- tokens.push(this.tokenizer.pop) tokens.push.apply(tokens, this.__extraDefinitionMatter) done = true; break; case "open_template":  //Suddently, a template. Eat it and its children. tokens.push.apply(tokens, this.__template) break; case "open_internal_link": //Suddenly, a link. Eat it and its children. tokens.push.apply(tokens, this.__internalLink) break; case "open_image": //Suddenly, an image. Eat it and its children. tokens.push.apply(tokens, this.__image) break; case "line_break": case "definition_item": case "header": case "pre": case "gallery": case "source": case "open_note":      // case "open_table":     // case "open_div":       //End of paragraph case "paragraph_break": // done = true break; default:               //Everything else tokens.push(this.tokenizer.pop) }		}		return tokens }	WG.Chunker.prototype.__extraDefinitionMatter = function{ var tokens = [] var done = false while(this.tokenizer.peek && !done){ switch(this.tokenizer.peek.t){ case "open_template":  //Suddently, a template. Eat it and its children. tokens.push.apply(tokens, this.__template) break; case "open_internal_link": //Suddenly, a link. Eat it and its children. tokens.push.apply(tokens, this.__internalLink) break; case "open_image": //Suddenly, an image. Eat it and its children. tokens.push.apply(tokens, this.__image) break; case "line_break": case "def_item": case "header": case "pre": case "gallery": case "source": case "open_note":      // case "open_table":     // case "open_div":       //End of paragraph case "paragraph_break": // done = true break; default:               //Everything else tokens.push(this.tokenizer.pop) }		}		return tokens }	WG.Chunker.prototype.__sentence = function{ var tokens = [] var done = false while(this.tokenizer.peek && !done){ switch(this.tokenizer.peek.t){ case "exclamation":    //--- case "question":       //End of sentence case "period":         // case "colon":          // case "ellipsis":       //--- tokens.push(this.tokenizer.pop) tokens.push.apply(tokens, this.__extraSentenceMatter) done = true; break; case "open_template":  //Suddently, a template. Eat it and its children. tokens.push.apply(tokens, this.__template) break; case "open_internal_link": //Suddenly, a link. Eat it and its children. tokens.push.apply(tokens, this.__internalLink) break; case "open_image": //Suddenly, an image. Eat it and its children. tokens.push.apply(tokens, this.__image) break; case "def_item": case "header": case "pre": case "gallery": case "source": case "open_note":      // case "open_table":     // case "open_div":       //End of paragraph case "paragraph_break": // done = true break; default:               //Everything else tokens.push(this.tokenizer.pop) }		}		return tokens }	WG.Chunker.prototype.__extraSentenceMatter = function{ var tokens = [] var done = false while(this.tokenizer.peek && !done){ switch(this.tokenizer.peek.t){ case "open_template":  //Suddently, a template. Eat it and its children. tokens.push.apply(tokens, this.__template) break; case "open_internal_link": // case "open_note":         // case "open_image":        // New element case "def_item":     // case "list_item":     // case "header":            // case "source":            // case "gallery":           // case "pre":               // case "open_table":     // case "open_div":       //New paragraph case "paragraph_break": // case "open_markup":        // case "italics":            // case "bold":               // case "open_internal_link": // New sentence case "external_link":      // case "entity":             // case "word":               // case "other":              // done = true break; default:               //Everything else tokens.push(this.tokenizer.pop) }		}		return tokens }	WG.Chunker.prototype.__internalLink = function{ var tokens = [this.tokenizer.pop] var links = 1 while(this.tokenizer.peek && links > 0){ switch(this.tokenizer.peek.t){ case "open_internal_link": //going one deeper! links++ break; case "close_internal_link": //coming out of the links links-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }	WG.Chunker.prototype.__image = function{ var tokens = [this.tokenizer.pop] var links = 1 while(this.tokenizer.peek && links > 0){ switch(this.tokenizer.peek.t){ case "open_internal_link": //going one deeper! links++ break; case "close_internal_link": //coming out of the links links-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }	WG.Chunker.prototype.__note = function{ var tokens = [this.tokenizer.pop] var templates = 1 while(this.tokenizer.peek && templates > 0){ switch(this.tokenizer.peek.t){ case "open_template": //going one deeper! templates++ break; case "close_template": //coming out of the links templates-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }

if(!window.WG){WG = {}}

/** A simple interface for interacting with a sentence in an article.


 * Parameters:

span : jQuery | DOM element the span containing the sentence to be edited

*/ WG.SentenceInteractor = Class.extend({	init: function(span){		this.span = $(span)		if(this.span.hasClass("editing")){			return		}		this.span.addClass("editing")		var id = parseInt(this.span.attr("id").split("_")[1])		this.chunk = WG.chunks.get(id)		this.currentHTML = this.span.html		this.markup = {			ltrim:  this.chunk.c.match(/^\s*/)[0],			rtrim:   this.chunk.c.match(/\s*$/)[0],			trimmed: $.trim(this.chunk.c)		}		this.div = $(' ')			.addClass("sentence_interactor")			.insertAfter(this.span)		this.pane = $(' ')			.addClass("pane")			.css("position", "absolute")			.appendTo(this.div)			.hide		this.menu   = new WG.SentenceMenu(this)		this.editor = new WG.SentenceEditor(this)		this.editor.text(this.markup.trimmed)		this.editor.summary( "Updating sentence starting with \"" + 			this.markup.trimmed.substring(0, Math.min(50, this.markup.trimmed.length)) + 			"...\"" )		this.show		this.resizer = function(interactor){return function(e){			interactor.resize		}}(this)		$(window).resize(this.resizer)		//this.captureClickEvent = function(interactor){return function(e){		//	if(e.button == 2 && e.ctrlKey){		//		e.stopPropagation		//		return false;		//	}		//}}(this)		//this.span.mousedown(this.captureClickEvent)	},	resize: function{		this.pane.css('width', $('#bodyContent .mw-content-ltr').innerWidth - 15)		this.div.css("height", this.pane.outerHeight)	},	preview: function(callback){		WG.api.pages.preview( WG.PAGE_TITLE, this.markup.ltrim + $.trim(this.editor.text) + this.markup.rtrim, function(interactor, callback){return function(html){ html = html .replace(/<\/?p>/gi, '') .replace(/\n .*?<\/strong>/g, '') interactor.span.html(html) if(callback){callback(html)} }}(this, callback), function(interactor, callback){return function(error){ WG.error(error) }}(this, callback) )	},	save: function(callback){		this.editor.disable		//Update chunk		this.chunk.c = this.markup.ltrim + 			this.editor.text + 			this.markup.rtrim		WG.chunks.save( this.menu.minor, this.editor.summary + WG.SUMMARY_SUFFIX, function(interactor, callback){return function(html){ interactor.editor.enable interactor.span.html(html) interactor.preview(					function(interactor, callback){return function(html){						interactor.currentHTML = html						interactor.span.html(html)						interactor.exit						if(callback){callback}					}}(interactor, callback)				) }}(this, callback), function(interactor, callback){return function(error){ WG.error(error) interactor.editor.enable }}(this, callback) )	},	show: function{		this.pane.css('left', $('#bodyContent .mw-content-ltr').position.left - 15)		this.pane.css('width', $('#bodyContent .mw-content-ltr').innerWidth - 15)		this.div.animate( {				height: this.pane.outerHeight },			{				duration: 200 }		)		this.pane.slideDown(200)	},	hide: function(callback){		this.pane.slideUp(200)		this.div.animate( {height: 0}, {				duration: 200, complete: function(interactor, callback){return function{ interactor.div.hide if(callback){callback} }}(this, callback) }		)	},	cancel: function{		this.span.html(this.currentHTML)		this.exit	},	exit: function{		this.hide( function(interactor){return function{ interactor.div.remove }}(this) )		this.span.removeClass("editing")		$(window).unbind(this.resizer)		//this.unbind('mousedown', this.captureClickEvent)	} })

WG.SentenceMenu = Class.extend({	init: function(interactor){		this.div = $(' ')			.addClass("menu")			.prependTo(interactor.pane)		this.cancel = $(' ')			.addClass("button")			.addClass("cancel")			.text("cancel")			.attr("title", "cancel editing")			.click( function(interactor){return function{ interactor.cancel }}(interactor) )			.appendTo(this.div)		this.minorCheck = {			div: $(' ')				.addClass("minor")				.appendTo(this.div),			label: $(' ')				.text("minor")				.attr('for', "minor_sentence_edit"),			checkbox: $(' ')				.attr('type', "checkbox")				.attr('id', "minor_sentence_edit")				.prop('checked', true)		}		this.minorCheck.div.append(this.minorCheck.label)		this.minorCheck.div.append(this.minorCheck.checkbox)		this.save = $(' ')			.addClass("button")			.addClass("save")			.addClass("primary")			.text("save")			.attr("title", "save your changes to the sentence")			.click( function(interactor){return function{ interactor.save }}(interactor) )			.appendTo(this.div)		this.preview = $(' ')			.addClass("button")			.addClass("preview")			.text("preview")			.attr("title", "preview your change to the sentence")			.click( function(interactor){return function{ interactor.preview }}(interactor) )			.appendTo(this.div)	},	minor: function{		return this.minorCheck.checkbox.is(":checked")	},	hide: function{		this.div.hide	},	show: function{		this.div.show	} })

WG.SentenceEditor = Class.extend({	init: function(interactor){		this.div = $(' ')			.addClass('editor')			.appendTo(interactor.pane)		this.textPane = {			textarea: $(" ")				.addClass("text")				.appendTo(this.div)		}		this.summaryPane = {			label: $(' ')				.text('Summary: ')				.attr('for', interactor.chunk.id + "_summary")				.appendTo(this.div),			textarea: $(" ")				.addClass("summary")				.appendTo(this.div)				.attr('id', interactor.chunk.id + "_summary")				.attr('rows', 1)		}	},	text: function(val){		if(val){			this.textPane.textarea				.attr("rows", Math.max(2, Math.ceil(val.length/80)))				.val(val)			return this		}else{			return this.textPane.textarea.val		}	},	summary: function(val){		if(val){			this.summaryPane.textarea.val(val)			return this		}else{			return this.summaryPane.textarea.val		}	},	disable: function{		this.textPane.textarea.prop('disabled', true)		this.summaryPane.textarea.prop('disabled', true) },	enable: function{ this.textPane.textarea.prop('disabled', false) this.summaryPane.textarea.prop('disabled', false) } }) if(!window.WG){WG = {}}

/** Centers a jQuery element(s) horizontally in relation to another element (usually a containing element).


 * Parameters:

of : DOM element | jQuery the element to center around


 * Returns:

this jQuery element jQuery.fn.center = function(of){ of = $(of || window) this.css("position", "absolute") this.css(		"left",		(of.position.left + of.outerWidth/2) - 		(this.outerWidth/2)	) return this }

/** Gets the absolute bottom of an element including padding, borders and margin.


 * Returns:

int pixels of position jQuery.fn.outerBottom = function{ return this.position.top + this.outerHeight(true) }

/** Positions an element beneath another with a specified offset.


 * Parameters:

of : DOM element | jQuery the element to place beneath offset : int the number of pixels to offset the placement (defualts to zero)


 * Returns:

this jQuery element jQuery.fn.beneath = function(of, offset){ offset = parseInt(offset || 0) of = $(of || $('body')) this.css("position", "absolute") this.css(		"top",		of.position.top + of.outerHeight(true) + offset	) return this }

WG.Gbutton = function(displayName){ var innerSpan = $(' ') .append($('')) .append(			$('<u />')				.text(displayName)		) return $(' ') .attr("type", "button") .addClass("btn") .append($(' ').append(innerSpan))

}

WG.lpad = function(number, width, padding){ padding = padding || 0 width -= number.toString.length; if ( width > 0 ){ return new Array( width + (/\./.test( number ) ? 2 : 1) ).join( '0' ) + number; }	return number; }

WG.dumpObj = function(obj){ str = 'Object: ' for(thing in obj){ str += "\n\t" + String(thing) + ": " + String(obj[thing]) }	WG.lastDumpedObj = obj return str }

if(!window.WG){WG = {}}

$.extend(WG, {	NOTE_HANDLE_HEIGHT: 25,	NOTE_LINK_INIT:         "User:EpochFail/Note_link_init",	NOTE_REFERENCE_INIT:     "User:EpochFail/Note_reference_init",	NOTE_REFERENCE_TEMPLATE: "User:EpochFail/Note_reference" })

/** Represents a group of notes in a drawer. WG.NoteDrawerGroup = Class.extend({	/**	Constructs a new NoteDrawer note Group.	*/	init: function(offset, notes){		this.div = $(' ')			.css("position", "absolute")		this.notes = []		notes = notes || []		for(i in notes){note = notes[i]			this.add(note)		}	},	/**	Adds a note to the group.	*/	add: function(note){		//Add node in appropriate location in group		for(var i in this.notes){var n = this.notes[i]			if(n.offset > note.offset){				this.notes.splice(i, 0, note)				note.viewer.div.insertBefore(n.viewer.div)				this.reposition				return			}		}		//Otherwise, add to the end.		this.notes.push(note)		this.div.append(note.viewer.div)		this.reposition	},	/**	Removes a note from the group if it exists in the group. Otherwise does	nothing.	*/	remove: function(note){		for(i in this.notes){var n = this.notes[i]			if(n == note){				note.viewer.div.detach				this.notes.splice(i, 1)				this.reposition }		}	},	/**	The absolute position top of the drawer including margin, padding and border. */	top: function{ return this.div.position.top },	/**	The absolute position bottom of the drawer including margin, padding and border. */	bottom: function{ //return this.div.outerBottom //instead, return what the bottom *should* be		return this.top + (WG.NOTE_HANDLE_HEIGHT * this.notes.length) },	/**	Detaches notes and removed self from DOM. */	del: function{ this.div.children.detach this.div.remove },	/**	*/	reposition: function{ if(this.notes.length > 0){ this.div.css("top", this.notes[0].offset) }	} })

/** Positions and aligns notes on the right side of the screen in an intelligent way. WG.NoteDrawer = Class.extend({	/**	Constructs a new NoteDrawer	*/	init: function{		this.div = $('<div class="note_drawer" />')			.appendTo('#bodyContent')			.css('position', 'absolute')			.css('top', 0)			.css( 'right', -15			)			.css('height', $('#bodyContent').height)		this.groups = []		$(window).resize( function(drawer){return function(e){ if(drawer.reloadTimer){ clearTimeout(						drawer.resizeTimer					) }				drawer.reloadTimer = setTimeout(					function(drawer){return function(e){						drawer.reload					}}(drawer)				) }}(this) )	},	/**	Redraws the note drawer and re-arranges the notes when necessary. For 	example, when the window is resized.	*/	reload: function{		this.clear		var oldGroups = this.groups		this.groups = []		for(var i in oldGroups){var group = oldGroups[i]			for(i in group.notes){var note = group.notes[i]				this.add(note)			}		}	},	/**	Adds a set of notes to the drawer.	*/	load: function(notes){		this.clear		this.groups = []		for(var i in notes){note = notes[i]			//make sure the note is hidden so we can align it correctly.			note.viewer.hide			//get the location that this note would want its handle positioned.			//If this is the first note or there is no overlap			if( this.groups.length == 0 || this.groups[this.groups.length-1].bottom < note.offset ){				//Easy case. We just create a new group at our desired offset				var group = new WG.NoteDrawerGroup(note.offset)				group.add(note)				//Add it to the drawer				this.div.append(group.div)				//Add it to our list of groups				this.groups.push(group)			}else{				//There is currently a group in the way of where				//we want to put this note's handle				//Let's just add it to the previous group.				this.groups[this.groups.length-1].add(note)			}		}		$('div.note_viewer').css('overflow', 'visible')	},	/**	Adds a note to the drawer in the most appropriate group	*/	add: function(note){		for(var i in this.groups){var group = this.groups[i]			if(group.bottom < note.offset){				//Not to the offset yet.			}else if(group.top <= note.offset + WG.NOTE_HANDLE_HEIGHT){				//We want to be inside of a group that already exists				group.add(note)				return			}else{				//We passed up the spot we want to put this note. //Drop it in it's desired spot. var group = new WG.NoteDrawerGroup(note.offset) group.add(note) this.div.append(group.div) //Insert in position. Don't put it *before* the //beginning. this.groups.splice(Math.max(0, i-1), 0, group) return }		}		//If you get here, that means we are adding a note that is below //all previous notes. We can just add it where we want it. var group = new WG.NoteDrawerGroup(note.offset) group.add(note) this.div.append(group.div) this.groups.push(group) },	/**	Removes a note from the drawer. */	remove: function(note){ note.viewer.div.detach for(var i in this.groups){var group = this.groups[i] //We don't have to check if the note is in the group //since this function does nothing if it isn't.			group.remove(note) }	},	/**	Clears all groups from the drawer. */	clear: function{ if(this.groups){ for(var i in this.groups){var group = this.groups[i] group.del }		}	} })

/** Represents a note in an article. WG.Note = Class.extend({	init: function(span){		this.span = $(span)		if(!this.span.attr('id')){			throw "Span missing id."		}		this.id = this.span.attr('id')		this.viewer = new WG.NoteViewer(this)		this.editor = new WG.NoteEditor(this)		this.viewer.div.append(this.editor.div)		this.hide		this.span			.click( function(note){return function(e){ note.toggle }}(this) )			.hover( function(note){return function(e){ note.viewer.handle.div.addClass("hover") }}(this), function(note){return function(e){ note.viewer.handle.div.removeClass("hover") }}(this) )	},	chunkId: function{		return this.chunk.id	},	pageTitle: function{		return WG.TALK_PAGE_TITLE + "/" + this.id	},	offset: function{		return this.span.position.top + this.span.outerHeight(true)+3//fudge	},	cancel: function{		this.editor.compress	},	/**	Toggles between showing and hiding the note viewer/editor	*/	toggle: function{		if(this.hidden){			this.show		}else{			this.hide		}	},	/**	Animated hide operation	*/	hide: function(callback){		this.hidden = true		this.editor.hide		//this.viewer.div.css("overflow", "hidden")		this.viewer.hide(callback)	},	/**	Animated show operation.	*/	show: function(callback){		this.hidden = false		this.viewer.show( function(note, callback){return function{ note.editor.show if(callback){callback} }}(this, callback) )	},	/**	Loads a preview of whatever if in the editor into the viewer using the API	*/	preview: function(callback){		WG.api.pages.preview( WG.TALK_PAGE_TITLE, this.editor.val, function(note, callback){return function(html){ note.viewer.view(html) if(callback){callback(html)} }}(this, callback), function(error){ WG.console.error(error) }		)	},	/**	Saves a new version of the note based on what is currently in the editor	*/	save: function(callback){		//First disable the editor. No clicking or typing while I'm saving!		this.editor.disable		//Start the call to save the current note.		WG.api.pages.save( this.pageTitle, this.token, this.preamble + this.editor.val, "Updating note", false, function(note, callback){return function{ note.preview(					function(note, callback){return function(html){						note.savedHTML = html						if(callback){callback}					}}(this, callback)				) //Now you can use the editor again note.editor.enable note.hide }}(this, callback), function(note, callback){return function(message){ WG.console.error(message) //Something bad happened, but you're welcome to try again. note.editor.enable }}(this, callback) )	},	/**	Removes a note placeholder from the chunks.	*/	remove: function(callback){		if(confirm("Are you sure you'd like to remove this note?")){			this.editor.disable			var summary = 'Removing note with "'			summary += this.editor.val.substring(0, 250-(summary.length+4)) + '..."'			WG.chunks.remove(this.chunk)			WG.chunks.save( false, summary, function(note, callback){return function{ note.span.remove //Remove self from drawer note.hide(						function(note){return function{							WG.noteDrawer.remove(note)						}}(note)					) }}(this, callback), function(note, callback){return function(error){ WG.console.error(error) this.editor.enable }}(this, callback) )		}	},	/**	Refresh with subpage content	**/	load: function{		WG.api.pages.get( this.pageTitle, function(note){return function(markup, page){ note.__loadMarkup(markup) note.preview note.editor.enable note.token = page.edittoken }}(this), function(note){return function(message){ WG.console.error("Could not load note markup " + note.id + ": " + message) }}(this) )	},	__loadMarkup: function(markup){		var noteHeaderRE = RegExp( "\n*" )		var match = markup.match(noteHeaderRE)		if(match){			//Found a note header template. Yay!			var parts = markup.split(match[0])			this.preamble = parts[0] + match[0],			this.editor.val(parts.slice(1).join(match[0]))		}else{			//No template :(. Try to process the first level two header. var level2RE = /(^|\n)+==[^\n]+?==/ match = markup.match(level2RE) if(match){ var parts = markup.split(match[0]) this.preamble = parts[0] + match[0], this.editor.val(parts.slice(1).join(match[0])) }else{ this.preamble = '",				this.editor.val(markup)			}		}	} })

WG.OldNote = WG.Note.extend({	init: function(chunk, span){		this._super(span)		//var id = parseInt(chunkSpan.attr('id').split("_")[1])		if(chunk.t != "note"){			throw "Non-note chunk(" + chunk.id + ") type '" + chunk.t + "' to be edited. No can do duder."		}		this.chunk = chunk		this.load	} })

WG.NewNote = WG.Note.extend({	init: function(previousId, noteClass, id){		//Creates a human-readable, searchable timestamp		var id = [				[					WG.lpad(d.getUTCFullYear, 4), 					WG.lpad(d.getUTCMonth+1), 					WG.lpad(d.getUTCDate, 2)				].join('-'),				[					WG.lpad(d.getUTCHours, 2), 					WG.lpad(d.getUTCMinutes, 2), 					WG.lpad(d.getUTCSeconds, 2)				].join(":")			].join('_')		var span = $(' ')			.addClass(noteClass)			.css("display", "inline-block;")			.attr("id", id)		this._super(span)		this.previousChunk = WG.chunks.get(previousId)		this.show(function(note){return function{note.editor.expand}}(this))	},	saved: function{		return this.chunk != undefined	},	chunkId: function{		if(this.saved) return this._super		return undefined	},	save: function(callback){		if(this.saved) return this._super(callback)		else{this.__createPage(callback)}	},	__createPage: function(callback){ //First disable the editor. No clicking or typing while I'm saving! this.editor.disable //Create the note page with new note content WG.api.pages.create(			this.pageTitle,			"== Re. Inline |note==\n" +			"\n" + 			this.editor.val,			"Creating note page for " + WG.PAGE_TITLE + "",			function(note, callback){return function(save){				//Created the note page. Now update the article with the placeholder				note.__insertIntoArticle(callback)				note.__appendToTalkPage			}}(this, callback),			function(note){return function(message){				WG.console.error("Could not create note subpage: " + message)				note.editor.enable			}}(this)		) },	__insertIntoArticle: function(callback){ //Update chunks this.chunk = { id: this.previousChunk.id+1, t: "note", c: "", val: this.id		} WG.chunks.insert(this.chunk) //Save a new version WG.chunks.save(			false,			"Inserting note placeholder for " + this.pageTitle + "",			function(note, callback){return function(save){				var summary = 'Inserting note with "'				summary += note.editor.val.substring(0, 250-(summary.length+4)) + '..."'				//Preview the new content				note.preview( function(note, callback){return function(html){ note.savedHTML = html if(callback){callback} }}(this, callback) )				//Re-enable the editor				note.editor.enable				note.hide			}}(this, callback),			function(note, callback){return function(message){				WG.console.error("Could not update article with note placeholder: " + message)				note.editor.enable			}}(this, callback)		) },	__appendToTalkPage: function{ WG.api.pages.append(			WG.TALK_PAGE_TITLE,			"\n\n",			"Adding section for new note",			function(save){},			function(message){				WG.console.error("Could not append note to talk page: " + message)			}		) },	cancel: function{ if(this.saved) return this._super //DESTROY EVERYTHING and forget we even started this.span.remove //Remove self from drawer this.hide(			function(note){return function{				WG.noteDrawer.remove(note)			}}(this)		) } })

/** An animated interface for viewing a note embedded in wiki markup. WG.NoteViewer = Class.extend({	init: function(note){		this.div = $('<div class="note_viewer" />')		this.handle = {			div: $(' ')				.appendTo(this.div)				.click( function(note){return function(e){ note.toggle }}(note) )				.hover( function(note){return function(e){ note.span.addClass("hover") }}(note), function(note){return function(e){ note.span.removeClass("hover") }}(note) )		}		var paneDiv = $(' ')		this.pane = {			div: paneDiv				.hide				.appendTo(this.div),			view: $(' ')				.appendTo(paneDiv)		}		this.hidden = true	},	/**	Loads new HTML into the note viewer.	*/	view: function(html){		this.pane.view.html(html)	},	/**	Animated hide operation	*/	hide: function(callback){		//this.viewer.div.css("overflow", "hidden")		this.pane.div.slideUp( 200,			function(viewer, callback){return function{ viewer.div.animate(					{						width: 0, 						right: 0					},					{						complete: function(callback){return function{							if(callback){callback}						}}(callback)					}				) }}(this, callback) )	},	/**	Animated show operation.	*/	show: function(callback){		this.div.animate( {width: 500, right: 500}, {				complete: function(viewer){return function{ viewer.pane.div.slideDown(200) if(callback){callback} }}(this) }		)	} })

/** An editor for creating and updating notes. WG.NoteEditor = Class.extend({	init: function(note){		this.note = note		this.div = $(' ')		this.remover = {			div: $(' ')				.text("remove")				.appendTo(this.div)				.click( function(editor){return function(e){ editor.note.remove }}(this) )		}		this.opener = {			div: $(' ')				.text("edit")				.addClass("primary")				.appendTo(this.div)				.click( function(editor){return function(e){ editor.expand }}(this) )		}		this.textarea = $(' ')			.hide			.appendTo(this.div)		this.canceller = {			div: $(' ')				.text("cancel")				.hide				.appendTo(this.div)				.click( function(editor){return function(e){ if(editor.div.hasClass("disabled")){return} editor.note.cancel }}(this) )		}		this.saver = {			div: $(' ')				.text("save")				.addClass("primary")				.hide				.appendTo(this.div)				.click( function(editor){return function(e){ if(editor.div.hasClass("disabled")){return} editor.note.save }}(this) )		}		this.previewer = {			div: $(' ')				.text("preview")				.hide				.appendTo(this.div)				.click( function(editor){return function(e){ if(editor.div.hasClass("disabled")){return} editor.note.preview }}(this) )		}		this.div.append($(' ').css('height', 0))	},	/**	Get and sets the value of the editor (what's in the text area)	*/	val: function(val){		if(val){			//Setting the value			this.textarea				.val(val)				.attr( "rows", Math.max(6, Math.floor(val.length/30)) )		}else{			//Asking for the value			return this.textarea.val		}	},	hide: function{		this.div.hide	},	show: function{		this.div.show	},	/**	Hides the edit pane with a nice little animation	*/	compress: function{		this.textarea.slideUp( 200,			function(editor){return function(e){ //Show the other buttons editor.saver.div.hide editor.previewer.div.hide editor.canceller.div.hide //Hide the edit div editor.opener.div.show editor.remover.div.show }}(this) )	},	/**	Shows the edit pane with a nice little animation	*/	expand: function{		this.div.show		//Hide the edit div		this.opener.div.hide		this.remover.div.hide		//Show the other buttons		this.saver.div.show		this.previewer.div.show		this.canceller.div.show		//Expand the text area		this.textarea.slideDown(200)		this.textarea.focus	},	/**	Cancels the editing operation. Reverts the markup in tghe textarea back	to the original and closes the editor.	*/	cancel: function{		//hide		this.compress		//restore old markup into textarea		this.textarea.val(this.note.chunk.val)		//Revert the viewer		this.note.viewer.revert	},	/**	Disables the buttons and text area in the editor.  This is useful when	new input should be restricted while an operation is being performed.	*/	disable: function{		//Disable text area		this.textarea.prop('disabled', true)		//add disabled class		this.div.addClass("disabled") },	/**	Enables (or re-enables) the buttons and text editor. */	enable: function{ //Re-enable textarea this.textarea.prop('disabled', false) //Remove the disabled class this.div.removeClass("disabled") },	/**	Focuses the cursor in the textarea */	focus: function{ this.textarea.focus } }) if(!window.WG){WG = {}}

WG.API = Class.extend({	init: function{		this.url = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + "/api.php"		this.pages = new WG.Pages(this)	} })

WG.Pages = Class.extend({	init: function(api){		this.api = api	},	get: function(title, success, error){		success = success || function{}		error  = error   || function{}		WG.console.info("API: Requesting the current version of " + title + "...")		$.ajax({ url: this.api.url, dataType: "json", data: { action: 'query', prop:  'revisions|info', titles: title, rvprop: 'content|timestamp', intoken:'edit', format: 'json' },			type: "POST", context: this, success: function(success, error){return function(data, status){ //alert(WG.dumpObj(this)) if(status != "success"){ error("The API is unavilable: " + status) }else if(data.error){ error("Received an error from the API: " + data.error.code + " - " + data.error.info) }else if(!data.query || !data.query.pages){ error("Received an unexpected response from the API: " + WG.dumpObj(data)) }else { for(key in data.query.pages){ var page = data.query.pages[key] }					if(page.revisions){ var markup = page.revisions[0]['*'] WG.console.info("API: Received revision " + page.lastrevid + " of " + page.title + " with markup of length " + markup.length) }else{ var markup = undefined WG.console.info("API: Received info for missing page " + page.title) }					success(markup, page) }			}}(success, error), error: function(error){return function(jqXHR, status, message){ //Sometimes an error happens when the request is //interrupted by the user changing pages. if(status != 'error' || message != ''){ error("An error occurred while contacting Wikipedia's API: " + status + ": " + message) }			}}(error) })	},	append: function(title, markup, summary, success, error){		success = success || function{}		error  = error   || function{}		WG.console.info("API: Trying to append to " + title + "...")		this.get( title, function(api, title, markup, summary, success, error){return function(___, page){ WG.console.info("API: Appending markup of length " + markup.length + " to " + title + "...") $.ajax({					url: api.url,					dataType: "json",					data: {						action:    'edit',						title:      title,						appendtext: markup,						token:      page.edittoken,						summary:    summary,						format:     'json'					},					type: "POST",					success: function(summary, success, error){return function(data, status){						if(status != "success"){							error("The API is unavilable: " + status)						}else if(data.error){							error("Received an error from the API: " + data.error.code + " - " + data.error.info)						}else if(!data.edit || !data.edit.result){							error("Received an unexpected response from the API: " + WG.dumpObj(data))						}else if(data.edit.result != "Success"){							error("Saving the edit failed: " + WG.dumpObj(data.edit))						}else{							WG.console.info("API: Successfully appended text in revision " + data.edit.newrevid + " of " + data.edit.title + ": " + summary) success(data.edit) }					}}(summary, success, error), error: function(error){return function(jqXHR, status, message){ //Sometimes an error happens when the request is //interrupted by the user changing pages. if(status != 'error' || message != ''){ error("An error occurred while contacting Wikipedia's API: " + status + ": " + message) }					}}(error) })			}}(this.api, title, markup, summary, success, error),			function(error){return function(message){				error(message)			}}(error)		) },	save: function(title, token, touched, markup, summary, minor, success, error){ success = success || function{} error  = error   || function{} WG.console.info("API: Saving a new revision of " + title + " with mark of length " + markup.length + "...") $.ajax({			url: this.api.url,			dataType: "json",			data: {				action:         'edit',				title:           title,				text:            markup,				token:           token,				basetimestamp:   touched,				summary:         summary,				minor:           minor,				format:          'json'			},			type: "POST",			success: function(summary, success, error){return function(data, status){				if(status != "success"){					error("The API is unavilable: " + status)				}else if(data.error){					error("Received an error from the API: " + data.error.code + " - " + data.error.info)				}else if(!data.edit || !data.edit.result){					error("Received an unexpected response from the API: " + WG.dumpObj(data))				}else if(data.edit.result != "Success"){					error("Saving the edit failed: " + WG.dumpObj(data.edit))				}else{					WG.console.info("API: Successfully saved revision " + data.edit.newrevid + " of " + data.edit.title + ": " + summary) success(data.edit) }			}}(summary, success, error), error: function(error){return function(jqXHR, status, message){ //Sometimes an error happens when the request is //interrupted by the user changing pages. if(status != 'error' || message != ''){ error("An error occurred while contacting Wikipedia's API: " + status + ": " + message) }			}}(error) })	},	create: function(title, markup, summary, success, error){		success = success || function{}		error  = error   || function{}		WG.console.info("API: Trying to create " + title + "...")		this.get( title, function(api, title, markup, summary, success, error){return function(___, page){ if(page.missing == undefined){ throw "Failed to create page " + title + ". Already exists." }				api.save(title, page.edittoken, markup, summary, false, success, error) }}(this, title, markup, summary, success, error), function(error){return function(message){ error(message) }}(error) )	},	preview: function(title, markup, success, error){		success = success || function{}		error  = error   || function{}		WG.console.info("API: Sending markup of length " + markup.length + " for " + title + " to be parsed...")		$.ajax({ url: this.api.url, dataType: "json", data: { action: 'parse', title: title, text:  markup, prop:  'text', pst:   true, format: 'json' },			type: "POST", context: this, success: function(success, failure){return function(data, status){ if(status != "success"){ error("The API is unavilable: " + status) }else if(data.error){ error("Received an error from the API: " + data.error.code + " - " + data.error.info) }else if(!data.parse || !data.parse.text || !data.parse.text['*']){ error("Received an unexpected response from the API: " + WG.dumpObj(data)) }else{ var html = data.parse.text['*'] .replace(//gi, '') success(html) }			}}(success, error), error: function(error){return function(jqXHR, status, message){ //Sometimes an error happens when the request is //interrupted by the user changing pages. if(status != 'error' || message != ''){ error("An error occurred while contacting Wikipedia's API: " + status + ": " + message) }			}}(error) })	} }) if(!window.WG){WG = {}}

WG.Chunks = Class.extend({	init: function(chunks){		this.chunks = chunks	},	get: function(id, type){		id = parseInt(id)		if(!this.chunks[id]){			throw "Chunk id " + id + " was not found."		}else if(type && this.chunks[id].t != type){			throw "Chunk id " + id + " is of type '" + this.chunks[id].t + "', not '" + type + "'"		}else{			return this.chunks[id]		}	},	insert: function(chunk){		this.chunks.splice(chunk.id, 0, chunk)		//Update future chunks and representation		for(var i=this.chunks.length-1;i>=chunk.id+1;i--){			var upChunk = this.chunks[i]			var span = $('#chunk_' + upChunk.id)			span.attr('id', 'chunk_' + i)			upChunk.id = i		}	},	remove: function(chunk){		//Remove chunk from chunk list		this.chunks.splice(chunk.id, 1)		//Remove id from span		$('#chunk_' + chunk.id).removeAttr('id')		//Update the affected spans and chunks		for(var i=chunk.id;i<this.chunks.length;i++){			var upChunk = this.chunks[i] var span = $('#chunk_' + upChunk.id) span.attr('id', 'chunk_' + i)			upChunk.id = i		} },	toString: function{ var newMarkup = '' for(i in this.chunks){ newMarkup += this.chunks[i].c		} return newMarkup },	remarkup: function(sentenceClass, noteClass, headerClass){ markup = '' for(var i in this.chunks){ var chunk = this.chunks[i] if(chunk.t == "sentence" || chunk.t == "definition"){ markup += (					'<span class="' + sentenceClass + 					'" id="chunk_' + i + '">' + 					chunk.c + ' '				) }else if(chunk.t == "note"){ markup += (					'<span class="' + noteClass + 					'" id="chunk_' + i + '">' + 					chunk.c + ' '				) }/*else if(chunk.t == "header"){ markup += (					'<div class="' + headerClass + 					'" id="chunk_' + i + '">' + 					chunk.c + '\n '				) }*/else{ markup += chunk.c			} }		return markup },	save: function(minor, summary, success, error){ WG.api.pages.save(			WG.PAGE_TITLE,			WG.token,			WG.touched,			this.toString,			summary,			minor,			success,			error		) } }) if(!window.WG){WG = {}} $.extend(WG, { SENTENCE_CLASS: "WG_sentence", NOTE_CLASS: "WG_snote", HEADER_CLASS: "WG_header", MARKUP_CLASS: "WG_markup", CONTEXT_MENU: $('<ul style="display:none" class="contextMenu"/>') .append($(' ')			.addClass('edit')			.append($('<a />') .attr('href', '#edit') .text('Edit sentence') )		)		.append($(' ')			.addClass('new_note')			.append($('<a />') .attr('href', '#new_note') .text('Insert note') )		)		.appendTo($('body')), SUB_NOTE_CLASS: "note_container", WAIT_TIME: 0, PAGE_TITLE: mw.config.get('wgPageName'), TALK_PAGE_TITLE: mw.config.get('wgFormattedNamespaces')[mw.config.get('wgNamespaceNumber')+1] + ":" + mw.config.get('wgTitle'), api: new WG.API, SUMMARY_SUFFIX: "(WG)", SUMMARY_MAX_LENGTH: 255 }) $.extend(WG, { load: function{ WG.api.pages.get(			WG.PAGE_TITLE,			function(markup, page){				WG.token = page.edittoken				WG.touched = page.touched				WG.parseAndLoad(markup)			},			function(error){				WG.console.error(error)			}		) },	parseAndLoad: function(markup){ WG.console.info("Parsing article content...") WG.chunks = new WG.Chunks((new WG.Chunker(markup)).popAll) //WG.console.info("Sending new markup of length " + WG.remarkuped.length + " to the API.") WG.api.pages.preview(			WG.PAGE_TITLE,			WG.chunks.remarkup(WG.SENTENCE_CLASS, WG.NOTE_CLASS, WG.HEADER_CLASS),			function(html){				WG.html = html				$(document).ready( function(e){ $("#bodyContent .mw-content-ltr").html(WG.html) /*$.contextMenu(							{								menu: WG.CONTEXT_MENU,								selector: $("span." + WG.SENTENCE_CLASS),								callback: function(action, el, pos){									switch(action){										case "edit":											WG.loadSentenceInteractor(el)											break;										case "new_note":											WG.loadNoteCreater(el)											break;									}								}							}						)*/ $("span." + WG.SENTENCE_CLASS) .hover(								function(e){									if(e.ctrlKey){										$(e.currentTarget).addClass("hover")									}								},								function(e){									$(e.currentTarget).removeClass("hover")								}							) .dblclick(								function(e){									WG.loadSentenceInteractor(e.currentTarget)								}							) /*$(							"div." + WG.HEADER_CLASS + " h2,"+							"div." + WG.HEADER_CLASS + " h3,"+							"div." + WG.HEADER_CLASS + " h4,"+							"div." + WG.HEADER_CLASS + " h5,"+							"div." + WG.HEADER_CLASS + " h6,"+							"div." + WG.HEADER_CLASS + " h7"							) .append(								$(" ")									.addClass(WG.MARKUP_CLASS)									.append("+ note")							)*/ var hash = window.location.hash.substring(1) WG.noteDrawer = new WG.NoteDrawer $.each(							$('span.' + WG.NOTE_CLASS),							function(i, chunkSpan){								var id = parseInt($(chunkSpan).attr('id').split("_")[1])								var span = $(chunkSpan).children($('span.' + WG.SUB_NOTE_CLASS))								var note = new WG.OldNote( WG.chunks.get(id, 'note'), span )								WG.noteDrawer.add(note)								if(note.id == hash){									note.show								}							}						) WG.afterLoad }				)			},			function(error){				WG.console.error(error)			}		) },	loadSentenceInteractor: function(e){ WG.lastSentenceInteractor = new WG.SentenceInteractor(e) },	loadNoteCreater: function(e){ var previousId = parseInt(e.attr('id').split("_")[1]) d = new Date var note = new WG.NewNote(previousId, WG.SUB_NOTE_CLASS) WG.lastNewNote = note note.span.insertAfter(e) WG.noteDrawer.add(note) },	console: new WG.HiddenConsole, error: function(message){ if(confirm(message + "\nWould you like to reload the page?")){ window.location.reload }	},	afterLoad: function{ if(window.setupPopups){ disablePopups setupPopups }	} })

WG.load //