MediaWiki:Gadget-popups.js/sandbox

// STARTFILE: main.js

// **********************************************************************

// ** **

// ** changes to this file affect many users. **

// ** please discuss on the talk page before editing **

// ** **

// **********************************************************************

// ** **

// ** if you do edit this file, be sure that your editor recognizes it **

// ** as utf8, or the weird and wonderful characters in the namespaces **

// ** below will be completely broken. You can check with the show **

// ** changes button before submitting the edit. **

// ** test: مدیا מיוחד Мэдыя **

// ** **

// **********************************************************************

/* eslint-env browser */

/* global $, jQuery, mw, window */

// Fix later

/* global log, errlog, popupStrings, wikEdUseWikEd, WikEdUpdateFrame */

/* eslint no-mixed-spaces-and-tabs: 0, no-empty: 0 */

$(function() {

//////////////////////////////////////////////////

// Globals

//

// Trying to shove as many of these as possible into the pg (popup globals) object

var pg = {

api: {}, // MediaWiki API requests

re: {}, // regexps

ns: {}, // namespaces

string: {}, // translatable strings

wiki: {}, // local site info

user: {}, // current user info

misc: {}, // YUCK PHOOEY

option: {}, // options, see newOption etc

optionDefault: {}, // default option values

flag: {}, // misc flags

cache: {}, // page and image cache

structures: {}, // navlink structures

timer: {}, // all sorts of timers (too damn many)

counter: {}, // .. and all sorts of counters

current: {}, // state info

fn: {}, // functions

endoflist: null

};

/* Bail if the gadget/script is being loaded twice */

/* An element with id "pg" would add a window.pg property, ignore such property */

if (window.pg && !(window.pg instanceof HTMLElement)) {

return;

}

/* Export to global context */

window.pg = pg;

/// Local Variables: ///

/// mode:c ///

/// End: ///

// ENDFILE: main.js

// STARTFILE: actions.js

function setupTooltips(container, remove, force, popData) {

log('setupTooltips, container=' + container + ', remove=' + remove);

if (!container) {

//

// the main initial call

if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) {

document.editform.wpTextbox1.onmouseup = doSelectionPopup;

}

//

// article/content is a structure-dependent thing

container = defaultPopupsContainer();

}

if (!remove && !force && container.ranSetupTooltipsAlready) {

return;

}

container.ranSetupTooltipsAlready = !remove;

var anchors;

anchors = container.getElementsByTagName('A');

setupTooltipsLoop(anchors, 0, 250, 100, remove, popData);

}

function defaultPopupsContainer() {

if (getValueOf('popupOnlyArticleLinks')) {

return document.getElementById('mw_content') ||

document.getElementById('content') ||

document.getElementById('article') || document;

}

return document;

}

function setupTooltipsLoop(anchors, begin, howmany, sleep, remove, popData) {

log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments));

var finish = begin + howmany;

var loopend = Math.min(finish, anchors.length);

var j = loopend - begin;

log('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin +

', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove);

var doTooltip = remove ? removeTooltip : addTooltip;

// try a faster (?) loop construct

if (j > 0) {

do {

var a = anchors[loopend - j];

if (typeof a === 'undefined' || !a || !a.href) {

log('got null anchor at index ' + loopend - j);

continue;

}

doTooltip(a, popData);

} while (--j);

}

if (finish < anchors.length) {

setTimeout(function() {

setupTooltipsLoop(anchors, finish, howmany, sleep, remove, popData);

},

sleep);

} else {

if (!remove && !getValueOf('popupTocLinks')) {

rmTocTooltips();

}

pg.flag.finishedLoading = true;

}

}

// eliminate popups from the TOC

// This also kills any onclick stuff that used to be going on in the toc

function rmTocTooltips() {

var toc = document.getElementById('toc');

if (toc) {

var tocLinks = toc.getElementsByTagName('A');

var tocLen = tocLinks.length;

for (var j = 0; j < tocLen; ++j) {

removeTooltip(tocLinks[j], true);

}

}

}

function addTooltip(a, popData) {

if (!isPopupLink(a)) {

return;

}

a.onmouseover = mouseOverWikiLink;

a.onmouseout = mouseOutWikiLink;

a.onmousedown = killPopup;

a.hasPopup = true;

a.popData = popData;

}

function removeTooltip(a) {

if (!a.hasPopup) {

return;

}

a.onmouseover = null;

a.onmouseout = null;

if (a.originalTitle) {

a.title = a.originalTitle;

}

a.hasPopup = false;

}

function removeTitle(a) {

if (!a.originalTitle) {

a.originalTitle = a.title;

}

a.title = '';

}

function restoreTitle(a) {

if (a.title || !a.originalTitle) {

return;

}

a.title = a.originalTitle;

}

function registerHooks(np) {

var popupMaxWidth = getValueOf('popupMaxWidth');

if (typeof popupMaxWidth === 'number') {

var setMaxWidth = function() {

np.mainDiv.style.maxWidth = popupMaxWidth + 'px';

np.maxWidth = popupMaxWidth;

};

np.addHook(setMaxWidth, 'unhide', 'before');

}

//

np.addHook(addPopupShortcuts, 'unhide', 'after');

np.addHook(rmPopupShortcuts, 'hide', 'before');

//

}

function removeModifierKeyHandler(a) {

//remove listeners for modifier key if any that were added in mouseOverWikiLink

document.removeEventListener('keydown', a.modifierKeyHandler, false);

document.removeEventListener('keyup', a.modifierKeyHandler, false);

}

function mouseOverWikiLink(evt) {

if (!evt && window.event) {

evt = window.event;

}

// if the modifier is needed, listen for it,

// we will remove the listener when we mouseout of this link or kill popup.

if (getValueOf('popupModifier')) {

// if popupModifierAction = enable, we should popup when the modifier is pressed

// if popupModifierAction = disable, we should popup unless the modifier is pressed

var action = getValueOf('popupModifierAction');

var key = action == 'disable' ? 'keyup' : 'keydown';

var a = this;

a.modifierKeyHandler = function(evt) {

mouseOverWikiLink2(a, evt);

};

document.addEventListener(key, a.modifierKeyHandler, false);

}

return mouseOverWikiLink2(this, evt);

}

/**

* Gets the references list item that the provided footnote link targets. This

* is typically a li element within the ol.references element inside the reflist.

* @param {Element} a - A footnote link.

* @returns {Element|boolean} The targeted element, or false if one can't be found.

*/

function footnoteTarget(a) {

var aTitle = Title.fromAnchor(a);

// We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly

var anch = aTitle.anchor;

if (!/^(cite_note-|_note-|endnote)/.test(anch)) {

return false;

}

var lTitle = Title.fromURL(location.href);

if (lTitle.toString(true) !== aTitle.toString(true)) {

return false;

}

var el = document.getElementById(anch);

while (el && typeof el.nodeName === 'string') {

var nt = el.nodeName.toLowerCase();

if (nt === 'li') {

return el;

} else if (nt === 'body') {

return false;

} else if (el.parentNode) {

el = el.parentNode;

} else {

return false;

}

}

return false;

}

function footnotePreview(x, navpop) {

setPopupHTML('


' + x.innerHTML, 'popupPreview', navpop.idNumber);

}

function modifierPressed(evt) {

var mod = getValueOf('popupModifier');

if (!mod) {

return false;

}

if (!evt && window.event) {

evt = window.event;

}

return (evt && mod && evt[mod.toLowerCase() + 'Key']);

}

// Checks if the correct modifier pressed/unpressed if needed

function isCorrectModifier(a, evt) {

if (!getValueOf('popupModifier')) {

return true;

}

// if popupModifierAction = enable, we should popup when the modifier is pressed

// if popupModifierAction = disable, we should popup unless the modifier is pressed

var action = getValueOf('popupModifierAction');

return (action == 'enable' && modifierPressed(evt) ||

action == 'disable' && !modifierPressed(evt));

}

function mouseOverWikiLink2(a, evt) {

if (!isCorrectModifier(a, evt)) {

return;

}

if (getValueOf('removeTitles')) {

removeTitle(a);

}

if (a == pg.current.link && a.navpopup && a.navpopup.isVisible()) {

return;

}

pg.current.link = a;

if (getValueOf('simplePopups') && !pg.option.popupStructure) {

// reset *default value* of popupStructure

setDefault('popupStructure', 'original');

}

var article = (new Title()).fromAnchor(a);

// set global variable (ugh) to hold article (wikipage)

pg.current.article = article;

if (!a.navpopup) {

a.navpopup = newNavpopup(a, article);

pg.current.linksHash[a.href] = a.navpopup;

pg.current.links.push(a);

}

if (a.navpopup.pending === null || a.navpopup.pending !== 0) {

// either fresh popups or those with unfinshed business are redone from scratch

simplePopupContent(a, article);

}

a.navpopup.showSoonIfStable(a.navpopup.delay);

clearInterval(pg.timer.checkPopupPosition);

pg.timer.checkPopupPosition = setInterval(checkPopupPosition, 600);

if (getValueOf('simplePopups')) {

if (getValueOf('popupPreviewButton') && !a.simpleNoMore) {

var d = document.createElement('div');

d.className = 'popupPreviewButtonDiv';

var s = document.createElement('span');

d.appendChild(s);

s.className = 'popupPreviewButton';

s['on' + getValueOf('popupPreviewButtonEvent')] = function() {

a.simpleNoMore = true;

d.style.display = "none";

nonsimplePopupContent(a, article);

};

s.innerHTML = popupString('show preview');

setPopupHTML(d, 'popupPreview', a.navpopup.idNumber);

}

}

if (a.navpopup.pending !== 0) {

nonsimplePopupContent(a, article);

}

}

// simplePopupContent: the content that do not require additional download

// (it is shown even when simplePopups is true)

function simplePopupContent(a, article) {

/* FIXME hack */

a.navpopup.hasPopupMenu = false;

a.navpopup.setInnerHTML(popupHTML(a));

fillEmptySpans({

navpopup: a.navpopup

});

if (getValueOf('popupDraggable')) {

var dragHandle = getValueOf('popupDragHandle') || null;

if (dragHandle && dragHandle != 'all') {

dragHandle += a.navpopup.idNumber;

}

setTimeout(function() {

a.navpopup.makeDraggable(dragHandle);

}, 150);

}

//

if (getValueOf('popupRedlinkRemoval') && a.className == 'new') {

setPopupHTML('
' + popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber);

}

//

}

function debugData(navpopup) {

if (getValueOf('popupDebugging') && navpopup.idNumber) {

setPopupHTML('idNumber=' + navpopup.idNumber + ', pending=' + navpopup.pending,

'popupError', navpopup.idNumber);

}

}

function newNavpopup(a, article) {

var navpopup = new Navpopup();

navpopup.fuzz = 5;

navpopup.delay = getValueOf('popupDelay') * 1000;

// increment global counter now

navpopup.idNumber = ++pg.idNumber;

navpopup.parentAnchor = a;

navpopup.parentPopup = (a.popData && a.popData.owner);

navpopup.article = article;

registerHooks(navpopup);

return navpopup;

}

// Should we show nonsimple context?

// If simplePopups is set to true, then we do not show nonsimple context,

// but if a bottom "show preview" was clicked we do show nonsimple context

function shouldShowNonSimple(a) {

return !getValueOf('simplePopups') || a.simpleNoMore;

}

// Should we show nonsimple context govern by the option (e.g. popupUserInfo)?

// If the user explicitly asked for nonsimple context by setting the option to true,

// then we show it even in nonsimple mode.

function shouldShow(a, option) {

if (shouldShowNonSimple(a)) {

return getValueOf(option);

} else {

return (typeof window[option] != 'undefined') && window[option];

}

}

function nonsimplePopupContent(a, article) {

var diff = null,

history = null;

var params = parseParams(a.href);

var oldid = (typeof params.oldid == 'undefined' ? null : params.oldid);

//

if (shouldShow(a, 'popupPreviewDiffs')) {

diff = params.diff;

}

if (shouldShow(a, 'popupPreviewHistory')) {

history = (params.action == 'history');

}

//

a.navpopup.pending = 0;

var referenceElement = footnoteTarget(a);

if (referenceElement) {

footnotePreview(referenceElement, a.navpopup);

//

} else if (diff || diff === 0) {

loadDiff(article, oldid, diff, a.navpopup);

} else if (history) {

loadAPIPreview('history', article, a.navpopup);

} else if (shouldShowNonSimple(a) && pg.re.contribs.test(a.href)) {

loadAPIPreview('contribs', article, a.navpopup);

} else if (shouldShowNonSimple(a) && pg.re.backlinks.test(a.href)) {

loadAPIPreview('backlinks', article, a.navpopup);

} else if ( // FIXME should be able to get all preview combinations with options

article.namespaceId() == pg.nsImageId &&

(shouldShow(a, 'imagePopupsForImages') || !anchorContainsImage(a))

) {

loadAPIPreview('imagepagepreview', article, a.navpopup);

loadImage(article, a.navpopup);

//

} else {

if (article.namespaceId() == pg.nsCategoryId &&

shouldShow(a, 'popupCategoryMembers')) {

loadAPIPreview('category', article, a.navpopup);

} else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) &&

shouldShow(a, 'popupUserInfo')) {

loadAPIPreview('userinfo', article, a.navpopup);

}

if (shouldShowNonSimple(a)) startArticlePreview(article, oldid, a.navpopup);

}

}

function pendingNavpopTask(navpop) {

if (navpop && navpop.pending === null) {

navpop.pending = 0;

}

++navpop.pending;

debugData(navpop);

}

function completedNavpopTask(navpop) {

if (navpop && navpop.pending) {

--navpop.pending;

}

debugData(navpop);

}

function startArticlePreview(article, oldid, navpop) {

navpop.redir = 0;

loadPreview(article, oldid, navpop);

}

function loadPreview(article, oldid, navpop) {

if (!navpop.redir) {

navpop.originalArticle = article;

}

article.oldid = oldid;

loadAPIPreview('revision', article, navpop);

}

function loadPreviewFromRedir(redirMatch, navpop) {

// redirMatch is a regex match

var target = new Title().fromWikiText(redirMatch[2]);

// overwrite (or add) anchor from original target

// mediawiki does overwrite; eg User:Lupin/foo3#Done

if (navpop.article.anchor) {

target.anchor = navpop.article.anchor;

}

navpop.redir++;

navpop.redirTarget = target;

//

var warnRedir = redirLink(target, navpop.article);

setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber);

//

navpop.article = target;

fillEmptySpans({

redir: true,

redirTarget: target,

navpopup: navpop

});

return loadPreview(target, null, navpop);

}

function insertPreview(download) {

if (!download.owner) {

return;

}

var redirMatch = pg.re.redirect.exec(download.data);

if (download.owner.redir === 0 && redirMatch) {

loadPreviewFromRedir(redirMatch, download.owner);

return;

}

if (download.owner.visible || !getValueOf('popupLazyPreviews')) {

insertPreviewNow(download);

} else {

var id = (download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK';

download.owner.addHook(function() {

insertPreviewNow(download);

return true;

},

'unhide', 'after', id);

}

}

function insertPreviewNow(download) {

if (!download.owner) {

return;

}

var wikiText = download.data;

var navpop = download.owner;

var art = navpop.redirTarget || navpop.originalArticle;

//

makeFixDabs(wikiText, navpop);

if (getValueOf('popupSummaryData')) {

getPageInfo(wikiText, download);

setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber);

}

var imagePage = '';

if (art.namespaceId() == pg.nsImageId) {

imagePage = art.toString();

} else {

imagePage = getValidImageFromWikiText(wikiText);

}

if (imagePage) {

loadImage(Title.fromWikiText(imagePage), navpop);

}

//

if (getValueOf('popupPreviews')) {

insertArticlePreview(download, art, navpop);

}

}

function insertArticlePreview(download, art, navpop) {

if (download && typeof download.data == typeof '') {

if (art.namespaceId() == pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) {

// FIXME compare/consolidate with diff escaping code for wikitext

var h = '


' + download.data.entify().split('\\n').join('
\\n') + '
';

setPopupHTML(h, 'popupPreview', navpop.idNumber);

} else {

var p = prepPreviewmaker(download.data, art, navpop);

p.showPreview();

}

}

}

function prepPreviewmaker(data, article, navpop) {

// deal with tricksy anchors

var d = anchorize(data, article.anchorString());

var urlBase = joinPath([pg.wiki.articlebase, article.urlString()]);

var p = new Previewmaker(d, urlBase, navpop);

return p;

}

// Try to imitate the way mediawiki generates HTML anchors from section titles

function anchorize(d, anch) {

if (!anch) {

return d;

}

var anchRe = RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*' + getValueOf('popupAnchorRegexp') + '\\s*(?:\\|[^|}]*)*?\\s*' + literalizeRegex(anch) + '\\s*(?:\\|[^}]*)?}})');

var match = d.match(anchRe);

if (match && match.length > 0 && match[0]) {

return d.substring(d.indexOf(match[0]));

}

// now try to deal with == foo baz boom == -> #foo_baz_boom

var lines = d.split('\n');

for (var i = 0; i < lines.length; ++i) {

lines[i] = lines[i].replace(RegExp('\\*?[|])?(.*?)[\\]]{2}', 'g'), '$2')

.replace(/'([^'])/g, '$1').replace(RegExp("([^'])", 'g'), '$1');

if (lines[i].match(anchRe)) {

return d.split('\n').slice(i).join('\n').replace(RegExp('^[^=]*'), '');

}

}

return d;

}

function killPopup() {

removeModifierKeyHandler(this);

if (getValueOf('popupShortcutKeys')) {

rmPopupShortcuts();

}

if (!pg) {

return;

}

if (pg.current.link && pg.current.link.navpopup) {

pg.current.link.navpopup.banish();

}

pg.current.link = null;

abortAllDownloads();

if (pg.timer.checkPopupPosition) {

clearInterval(pg.timer.checkPopupPosition);

pg.timer.checkPopupPosition = null;

}

return true; // preserve default action

}

// ENDFILE: actions.js

// STARTFILE: domdrag.js

/**

@fileoverview

The {@link Drag} object, which enables objects to be dragged around.

*************************************************

dom-drag.js

09.25.2001

www.youngpup.net

**************************************************

10.28.2001 - fixed minor bug where events

sometimes fired off the handle, not the root.

*************************************************

Pared down, some hooks added by User:Lupin

Copyright Aaron Boodman.

Saying stupid things daily since March 2001.

*/

/**

Creates a new Drag object. This is used to make various DOM elements draggable.

@constructor

*/

function Drag() {

/**

Condition to determine whether or not to drag. This function should take one parameter, an Event.

To disable this, set it to null.

@type Function

*/

this.startCondition = null;

/**

Hook to be run when the drag finishes. This is passed the final coordinates of

the dragged object (two integers, x and y). To disables this, set it to null.

@type Function

*/

this.endHook = null;

}

/**

Gets an event in a cross-browser manner.

@param {Event} e

@private

*/

Drag.prototype.fixE = function(e) {

if (typeof e == 'undefined') {

e = window.event;

}

if (typeof e.layerX == 'undefined') {

e.layerX = e.offsetX;

}

if (typeof e.layerY == 'undefined') {

e.layerY = e.offsetY;

}

return e;

};

/**

Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by.

@param {DOMElement} o The "handle" by which oRoot is dragged.

@param {DOMElement} oRoot The object which moves when o is dragged, or o if omitted.

*/

Drag.prototype.init = function(o, oRoot) {

var dragObj = this;

this.obj = o;

o.onmousedown = function(e) {

dragObj.start.apply(dragObj, [e]);

};

o.dragging = false;

o.popups_draggable = true;

o.hmode = true;

o.vmode = true;

o.root = oRoot ? oRoot : o;

if (isNaN(parseInt(o.root.style.left, 10))) {

o.root.style.left = "0px";

}

if (isNaN(parseInt(o.root.style.top, 10))) {

o.root.style.top = "0px";

}

o.root.onthisStart = function() {};

o.root.onthisEnd = function() {};

o.root.onthis = function() {};

};

/**

Starts the drag.

@private

@param {Event} e

*/

Drag.prototype.start = function(e) {

var o = this.obj; // = this;

e = this.fixE(e);

if (this.startCondition && !this.startCondition(e)) {

return;

}

var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10);

var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10);

o.root.onthisStart(x, y);

o.lastMouseX = e.clientX;

o.lastMouseY = e.clientY;

var dragObj = this;

o.onmousemoveDefault = document.onmousemove;

o.dragging = true;

document.onmousemove = function(e) {

dragObj.drag.apply(dragObj, [e]);

};

document.onmouseup = function(e) {

dragObj.end.apply(dragObj, [e]);

};

return false;

};

/**

Does the drag.

@param {Event} e

@private

*/

Drag.prototype.drag = function(e) {

e = this.fixE(e);

var o = this.obj;

var ey = e.clientY;

var ex = e.clientX;

var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10);

var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10);

var nx, ny;

nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));

ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));

this.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";

this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";

this.obj.lastMouseX = ex;

this.obj.lastMouseY = ey;

this.obj.root.onthis(nx, ny);

return false;

};

/**

Ends the drag.

@private

*/

Drag.prototype.end = function() {

document.onmousemove = this.obj.onmousemoveDefault;

document.onmouseup = null;

this.obj.dragging = false;

if (this.endHook) {

this.endHook(parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10),

parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10));

}

};

// ENDFILE: domdrag.js

// STARTFILE: structures.js

//

pg.structures.original = {};

pg.structures.original.popupLayout = function() {

return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle',

'popupUserData', 'popupData', 'popupOtherLinks',

'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks',

'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'

],

'popupMiscTools', ['popupRedlink'],

'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'

];

};

pg.structures.original.popupRedirSpans = function() {

return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks',

'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'

];

};

pg.structures.original.popupTitle = function(x) {

log('defaultstructure.popupTitle');

if (!getValueOf('popupNavLinks')) {

return navlinkStringToHTML('<>', x.article, x.params);

}

return '';

};

pg.structures.original.popupTopLinks = function(x) {

log('defaultstructure.popupTopLinks');

if (getValueOf('popupNavLinks')) {

return navLinksHTML(x.article, x.hint, x.params);

}

return '';

};

pg.structures.original.popupImage = function(x) {

log('original.popupImage, x.article=' + x.article + ', x.navpop.idNumber=' + x.navpop.idNumber);

return imageHTML(x.article, x.navpop.idNumber);

};

pg.structures.original.popupRedirTitle = pg.structures.original.popupTitle;

pg.structures.original.popupRedirTopLinks = pg.structures.original.popupTopLinks;

function copyStructure(oldStructure, newStructure) {

pg.structures[newStructure] = {};

for (var prop in pg.structures[oldStructure]) {

pg.structures[newStructure][prop] = pg.structures[oldStructure][prop];

}

}

copyStructure('original', 'nostalgia');

pg.structures.nostalgia.popupTopLinks = function(x) {

var str = '';

str += '<>';

// user links

// contribs - log - count - email - block

// count only if applicable; block only if popupAdminLinks

str += 'if(user){
<>';

str += 'if(wikimedia){*<>}';

str += 'if(ipuser){}else{*<>}if(admin){*<>}}';

// editing links

// talkpage -> edit|new - history - un|watch - article|edit

// other page -> edit - history - un|watch - talk|edit|new

var editstr = '<>';

var editOldidStr = 'if(oldid){<>|<>|<>}else{' +

editstr + '}';

var historystr = '<>';

var watchstr = '<>|<>';

str += '
if(talk){' +

editOldidStr + '|<>' + '*' + historystr + '*' + watchstr + '*' +

'<>|<>' +

'}else{' + // not a talk page

editOldidStr + '*' + historystr + '*' + watchstr + '*' +

'<>|<>|<>}';

// misc links

str += '
<>*<>';

str += 'if(admin){
}else{*}<>';

// admin links

str += 'if(admin){*<>|<>*' +

'<>|<>}';

return navlinkStringToHTML(str, x.article, x.params);

};

pg.structures.nostalgia.popupRedirTopLinks = pg.structures.nostalgia.popupTopLinks;

/** -- fancy -- **/

copyStructure('original', 'fancy');

pg.structures.fancy.popupTitle = function(x) {

return navlinkStringToHTML('<>', x.article, x.params);

};

pg.structures.fancy.popupTopLinks = function(x) {

var hist = '<>|<>|<>';

var watch = '<>|<>';

var move = '<>';

return navlinkStringToHTML('if(talk){' +

'<>|<>*' + hist + '*' +

'<>|<>' + '*' + watch + '*' + move +

'}else{<>*' + hist +

'*<>|<>|<>' +

'*' + watch + '*' + move + '}
', x.article, x.params);

};

pg.structures.fancy.popupOtherLinks = function(x) {

var admin = '<>|<>*<>|<>';

var user = '<>if(wikimedia){|<>}';

user += 'if(ipuser){|<>}else{*<

popupString('email') + '>>}if(admin){*<>}';

var normal = '<>*<>';

return navlinkStringToHTML('
if(user){' + user + '*}if(admin){' + admin + 'if(user){
}else{*}}' + normal,

x.article, x.params);

};

pg.structures.fancy.popupRedirTitle = pg.structures.fancy.popupTitle;

pg.structures.fancy.popupRedirTopLinks = pg.structures.fancy.popupTopLinks;

pg.structures.fancy.popupRedirOtherLinks = pg.structures.fancy.popupOtherLinks;

/** -- fancy2 -- **/

// hack for User:MacGyverMagic

copyStructure('fancy', 'fancy2');

pg.structures.fancy2.popupTopLinks = function(x) { // hack out the
at the end and put one at the beginning

return '
' + pg.structures.fancy.popupTopLinks(x).replace(RegExp('
$', 'i'), '');

};

pg.structures.fancy2.popupLayout = function() { // move toplinks to after the title

return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks',

'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],

'popupMiscTools', ['popupRedlink'],

'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'

];

};

/** -- menus -- **/

copyStructure('original', 'menus');

pg.structures.menus.popupLayout = function() {

return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks',

'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],

'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'],

'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'

];

};

pg.structures.menus.popupTopLinks = function(x, shorter) {

// FIXME maybe this stuff should be cached

var s = [];

var dropdiv = '

';

var hist = '<>';

if (!shorter) {

hist = '' + hist +

'|<>|<>';

}

var lastedit = '<>';

var thank = 'if(diff){<>}';

var jsHistory = '<><>';

var linkshere = '<>';

var related = '<>';

var search = '<>if(wikimedia){|<>}' +

'|<>';

var watch = '<>|<>';

var protect = '<>|' +

'<>|<>';

var del = '<>|<>|' +

'<>';

var move = '<>';

var nullPurge = '<>|<>';

var viewOptions = '<>|<>|<>';

var editRow = 'if(oldid){' +

'<>|<>' +

'<>|<>' + '}else{<>}';

var markPatrolled = 'if(rcid){<>}';

var newTopic = 'if(talk){<>}';

var protectDelete = 'if(admin){' + protect + del + '}';

if (getValueOf('popupActionsMenu')) {

s.push('<>*' + dropdiv + menuTitle('actions'));

} else {

s.push(dropdiv + '<>');

}

s.push('

');

s.push(editRow + markPatrolled + newTopic + hist + lastedit + thank);

if (!shorter) {

s.push(jsHistory);

}

s.push(move + linkshere + related);

if (!shorter) {

s.push(nullPurge + search);

}

if (!shorter) {

s.push(viewOptions);

}

s.push('


' + watch + protectDelete);

s.push('


' +

'if(talk){<><>}' +

'else{<><>' +

'<>}

' + enddiv);

// user menu starts here

var email = '<>';

var contribs = 'if(wikimedia){}<>if(wikimedia){}' +

'if(admin){<>}';

s.push('if(user){*' + dropdiv + menuTitle('user'));

s.push('

');

s.push('<>|<>');

s.push('<><>' +

'<>');

if (!shorter) {

s.push('if(ipuser){<>}else{' + email + '}');

} else {

s.push('if(ipuser){}else{' + email + '}');

}

s.push('


' + contribs + '<>');

s.push('if(wikimedia){<>}');

s.push('if(admin){<>|<>}');

s.push('<>');

s.push('

' + enddiv + '}');

// popups menu starts here

if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */ ) {

x.navpop.hasPopupMenu = true;

s.push('*' + dropdiv + menuTitle('popupsMenu') + '

');

s.push('<>');

s.push('<>');

s.push('<>');

s.push('

' + enddiv);

}

return navlinkStringToHTML(s.join(''), x.article, x.params);

};

function menuTitle(s) {

return '' + popupString(s) + '';

}

pg.structures.menus.popupRedirTitle = pg.structures.menus.popupTitle;

pg.structures.menus.popupRedirTopLinks = pg.structures.menus.popupTopLinks;

copyStructure('menus', 'shortmenus');

pg.structures.shortmenus.popupTopLinks = function(x) {

return pg.structures.menus.popupTopLinks(x, true);

};

pg.structures.shortmenus.popupRedirTopLinks = pg.structures.shortmenus.popupTopLinks;

//

pg.structures.lite = {};

pg.structures.lite.popupLayout = function() {

return ['popupTitle', 'popupPreview'];

};

pg.structures.lite.popupTitle = function(x) {

log(x.article + ': structures.lite.popupTitle');

//return navlinkStringToHTML('<>',x.article,x.params);

return '

' + x.article.toString() + '
';

};

// ENDFILE: structures.js

// STARTFILE: autoedit.js

//

function substitute(data, cmdBody) {

// alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags);

var fromRe = RegExp(cmdBody.from, cmdBody.flags);

return data.replace(fromRe, cmdBody.to);

}

function execCmds(data, cmdList) {

for (var i = 0; i < cmdList.length; ++i) {

data = cmdList[i].action(data, cmdList[i]);

}

return data;

}

function parseCmd(str) {

// returns a list of commands

if (!str.length) {

return [];

}

var p = false;

switch (str.charAt(0)) {

case 's':

p = parseSubstitute(str);

break;

default:

return false;

}

if (p) {

return [p].concat(parseCmd(p.remainder));

}

return false;

}

// FIXME: Only used once here, confusing with native (and more widely-used) unescape, should probably be replaced

// Then again, unescape is semi-soft-deprecated, so we should look into replacing that too

function unEscape(str, sep) {

return str.split('\\\\').join('\\').split('\\' + sep).join(sep).split('\\n').join('\n');

}

function parseSubstitute(str) {

// takes a string like s/a/b/flags;othercmds and parses it

var from, to, flags, tmp;

if (str.length < 4) {

return false;

}

var sep = str.charAt(1);

str = str.substring(2);

tmp = skipOver(str, sep);

if (tmp) {

from = tmp.segment;

str = tmp.remainder;

} else {

return false;

}

tmp = skipOver(str, sep);

if (tmp) {

to = tmp.segment;

str = tmp.remainder;

} else {

return false;

}

flags = '';

if (str.length) {

tmp = skipOver(str, ';') || skipToEnd(str, ';');

if (tmp) {

flags = tmp.segment;

str = tmp.remainder;

}

}

return {

action: substitute,

from: from,

to: to,

flags: flags,

remainder: str

};

}

function skipOver(str, sep) {

var endSegment = findNext(str, sep);

if (endSegment < 0) {

return false;

}

var segment = unEscape(str.substring(0, endSegment), sep);

return {

segment: segment,

remainder: str.substring(endSegment + 1)

};

}

/*eslint-disable*/

function skipToEnd(str, sep) {

return {

segment: str,

remainder: ''

};

}

/*eslint-enable */

function findNext(str, ch) {

for (var i = 0; i < str.length; ++i) {

if (str.charAt(i) == '\\') {

i += 2;

}

if (str.charAt(i) == ch) {

return i;

}

}

return -1;

}

function setCheckbox(param, box) {

var val = mw.util.getParamValue(param);

if (val) {

switch (val) {

case '1':

case 'yes':

case 'true':

box.checked = true;

break;

case '0':

case 'no':

case 'false':

box.checked = false;

}

}

}

function autoEdit() {

setupPopups(function() {

if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version')) {

return false;

}

if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken') === autoClickToken()) {

pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action'));

}

if (!document.editform) {

return false;

}

if (autoEdit.alreadyRan) {

return false;

}

autoEdit.alreadyRan = true;

var cmdString = mw.util.getParamValue('autoedit');

if (cmdString) {

try {

var editbox = document.editform.wpTextbox1;

var cmdList = parseCmd(cmdString);

var input = editbox.value;

var output = execCmds(input, cmdList);

editbox.value = output;

} catch (dang) {

return;

}

// wikEd user script compatibility

if (typeof(wikEdUseWikEd) != 'undefined') {

if (wikEdUseWikEd === true) {

WikEdUpdateFrame();

}

}

}

setCheckbox('autominor', document.editform.wpMinoredit);

setCheckbox('autowatch', document.editform.wpWatchthis);

var rvid = mw.util.getParamValue('autorv');

if (rvid) {

var url = pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids=' + rvid;

startDownload(url, null, autoEdit2);

} else {

autoEdit2();

}

});

}

function autoEdit2(d) {

var summary = mw.util.getParamValue('autosummary');

var summaryprompt = mw.util.getParamValue('autosummaryprompt');

var summarynotice = '';

if (d && d.data && mw.util.getParamValue('autorv')) {

var s = getRvSummary(summary, d.data);

if (s === false) {

summaryprompt = true;

summarynotice = popupString('Failed to get revision information, please edit manually.\n\n');

summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']);

} else {

summary = s;

}

}

if (summaryprompt) {

var txt = summarynotice +

popupString('Enter a non-empty edit summary or press cancel to abort');

var response = prompt(txt, summary);

if (response) {

summary = response;

} else {

return;

}

}

if (summary) {

document.editform.wpSummary.value = summary;

}

// Attempt to avoid possible premature clicking of the save button

// (maybe delays in updates to the DOM are to blame?? or a red herring)

setTimeout(autoEdit3, 100);

}

function autoClickToken() {

return mw.user.sessionId();

}

function autoEdit3() {

if (mw.util.getParamValue('actoken') != autoClickToken()) {

return;

}

var btn = mw.util.getParamValue('autoclick');

if (btn) {

if (document.editform && document.editform[btn]) {

var button = document.editform[btn];

var msg = tprintf('The %s button has been automatically clicked. Please wait for the next page to load.',

[button.value]);

bannerMessage(msg);

document.title = '(' + document.title + ')';

button.click();

} else {

alert(tprintf('Could not find button %s. Please check the settings in your javascript file.',

[btn]));

}

}

}

function bannerMessage(s) {

var headings = document.getElementsByTagName('h1');

if (headings) {

var div = document.createElement('div');

div.innerHTML = '' + pg.escapeQuotesHTML(s) + '';

headings[0].parentNode.insertBefore(div, headings[0]);

}

}

function getRvSummary(template, json) {

try {

var o = getJsObj(json);

var edit = anyChild(o.query.pages).revisions[0];

var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, '');

return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user]);

} catch (badness) {

return false;

}

}

//

// ENDFILE: autoedit.js

// STARTFILE: downloader.js

/**

@fileoverview

{@link Downloader}, a xmlhttprequest wrapper, and helper functions.

*/

/**

Creates a new Downloader

@constructor

@class The Downloader class. Create a new instance of this class to download stuff.

@param {String} url The url to download. This can be omitted and supplied later.

*/

function Downloader(url) {

if (typeof XMLHttpRequest != 'undefined') {

this.http = new XMLHttpRequest();

}

/**

The url to download

@type String

*/

this.url = url;

/**

A universally unique ID number

@type integer

*/

this.id = null;

/**

Modification date, to be culled from the incoming headers

@type Date

@private

*/

this.lastModified = null;

/**

What to do when the download completes successfully

@type Function

@private

*/

this.callbackFunction = null;

/**

What to do on failure

@type Function

@private

*/

this.onFailure = null;

/**

Flag set on abort

@type boolean

*/

this.aborted = false;

/**

HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities.

@type String

*/

this.method = 'GET';

/**

Async flag.

@type boolean

*/

this.async = true;

}

new Downloader();

/** Submits the http request. */

Downloader.prototype.send = function(x) {

if (!this.http) {

return null;

}

return this.http.send(x);

};

/** Aborts the download, setting the aborted field to true. */

Downloader.prototype.abort = function() {

if (!this.http) {

return null;

}

this.aborted = true;

return this.http.abort();

};

/** Returns the downloaded data. */

Downloader.prototype.getData = function() {

if (!this.http) {

return null;

}

return this.http.responseText;

};

/** Prepares the download. */

Downloader.prototype.setTarget = function() {

if (!this.http) {

return null;

}

this.http.open(this.method, this.url, this.async);

this.http.setRequestHeader('Api-User-Agent', pg.api.userAgent);

};

/** Gets the state of the download. */

Downloader.prototype.getReadyState = function() {

if (!this.http) {

return null;

}

return this.http.readyState;

};

pg.misc.downloadsInProgress = {};

/** Starts the download.

Note that setTarget {@link Downloader#setTarget} must be run first

*/

Downloader.prototype.start = function() {

if (!this.http) {

return;

}

pg.misc.downloadsInProgress[this.id] = this;

this.http.send(null);

};

/** Gets the 'Last-Modified' date from the download headers.

Should be run after the download completes.

Returns null on failure.

@return {Date}

*/

Downloader.prototype.getLastModifiedDate = function() {

if (!this.http) {

return null;

}

var lastmod = null;

try {

lastmod = this.http.getResponseHeader('Last-Modified');

} catch (err) {}

if (lastmod) {

return new Date(lastmod);

}

return null;

};

/** Sets the callback function.

@param {Function} f callback function, called as f(this) on success

*/

Downloader.prototype.setCallback = function(f) {

if (!this.http) {

return;

}

this.http.onreadystatechange = f;

};

Downloader.prototype.getStatus = function() {

if (!this.http) {

return null;

}

return this.http.status;

};

//////////////////////////////////////////////////

// helper functions

/** Creates a new {@link Downloader} and prepares it for action.

@param {String} url The url to download

@param {integer} id The ID of the {@link Downloader} object

@param {Function} callback The callback function invoked on success

@return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser

*/

function newDownload(url, id, callback, onfailure) {

var d = new Downloader(url);

if (!d.http) {

return 'ohdear';

}

d.id = id;

d.setTarget();

if (!onfailure) {

onfailure = 2;

}

var f = function() {

if (d.getReadyState() == 4) {

delete pg.misc.downloadsInProgress[this.id];

try {

if (d.getStatus() == 200) {

d.data = d.getData();

d.lastModified = d.getLastModifiedDate();

callback(d);

} else if (typeof onfailure == typeof 1) {

if (onfailure > 0) {

// retry

newDownload(url, id, callback, onfailure - 1);

}

} else if (typeof onfailure === "function") {

onfailure(d, url, id, callback);

}

} catch (somerr) {

/* ignore it */ }

}

};

d.setCallback(f);

return d;

}

/** Simulates a download from cached data.

The supplied data is put into a {@link Downloader} as if it had downloaded it.

@param {String} url The url.

@param {integer} id The ID.

@param {Function} callback The callback, which is invoked immediately as callback(d),

where d is the new {@link Downloader}.

@param {String} data The (cached) data.

@param {Date} lastModified The (cached) last modified date.

*/

function fakeDownload(url, id, callback, data, lastModified, owner) {

var d = newDownload(url, callback);

d.owner = owner;

d.id = id;

d.data = data;

d.lastModified = lastModified;

return callback(d);

}

/**

Starts a download.

@param {String} url The url to download

@param {integer} id The ID of the {@link Downloader} object

@param {Function} callback The callback function invoked on success

@return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser

*/

function startDownload(url, id, callback) {

var d = newDownload(url, id, callback);

if (typeof d == typeof '') {

return d;

}

d.start();

return d;

}

/**

Aborts all downloads which have been started.

*/

function abortAllDownloads() {

for (var x in pg.misc.downloadsInProgress) {

try {

pg.misc.downloadsInProgress[x].aborted = true;

pg.misc.downloadsInProgress[x].abort();

delete pg.misc.downloadsInProgress[x];

} catch (e) {}

}

}

// ENDFILE: downloader.js

// STARTFILE: livepreview.js

// TODO: location is often not correct (eg relative links in previews)

// NOTE: removed md5 and image and math parsing. was broken, lots of bytes.

/**

* InstaView - a Mediawiki to HTML converter in JavaScript

* Version 0.6.1

* Copyright (C) Pedro Fayolle 2005-2006

* https://en.wikipedia.org/wiki/User:Pilaf

* Distributed under the BSD license

*

* Changelog:

*

* 0.6.1

* - Fixed problem caused by \r characters

* - Improved inline formatting parser

*

* 0.6

* - Changed name to InstaView

* - Some major code reorganizations and factored out some common functions

* - Handled conversion of relative links (i.e. /foo)

* - Fixed misrendering of adjacent definition list items

* - Fixed bug in table headings handling

* - Changed date format in signatures to reflect Mediawiki's

* - Fixed handling of :Image:...

* - Updated MD5 function (hopefully it will work with UTF-8)

* - Fixed bug in handling of links inside images

*

* To do:

* - Better support for math tags

* - Full support for

* - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof)

* - Support for templates (through AJAX)

* - Support for coloured links (AJAX)

*/

var Insta = {};

function setupLivePreview() {

// options

Insta.conf = {

baseUrl: '',

user: {},

wiki: {

lang: pg.wiki.lang,

interwiki: pg.wiki.interwiki,

default_thumb_width: 180

},

paths: {

articles: pg.wiki.articlePath + '/',

// Only used for Insta previews with images. (not in popups)

math: '/math/',

images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname),

images_fallback: '//upload.wikimedia.org/wikipedia/commons/',

},

locale: {

user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId],

image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId],

category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId],

// shouldn't be used in popup previews, i think

months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']

}

};

// options with default values or backreferences

Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian';

Insta.conf.user.signature = '' + Insta.conf.user.name + '';

//Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/';

// define constants

Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|' + Insta.conf.locale.image +

'):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i');

}

Insta.dump = function(from, to) {

if (typeof from == 'string') {

from = document.getElementById(from);

}

if (typeof to == 'string') {

to = document.getElementById(to);

}

to.innerHTML = this.convert(from.value);

};

Insta.convert = function(wiki) {

var ll = (typeof wiki == 'string') ? wiki.replace(/\r/g, '').split(/\n/) : wiki, // lines of wikicode

o = '', // output

p = 0, // para flag

r; // result of passing a regexp to compareLineStringOrReg()

// some shorthands

function remain() {

return ll.length;

}

function sh() {

return ll.shift();

} // shift

function ps(s) {

o += s;

} // push

// similar to C's printf, uses ? as placeholders, ?? to escape question marks

function f() {

var i = 1,

a = arguments,

f = a[0],

o = '',

c, p;

for (; i < a.length; i++) {

if ((p = f.indexOf('?')) + 1) {

// allow character escaping

i -= c = f.charAt(p + 1) == '?' ? 1 : 0;

o += f.substring(0, p) + (c ? '?' : a[i]);

f = f.substr(p + 1 + c);

} else {

break;

}

}

return o + f;

}

function html_entities(s) {

return s.replace(/&/g, "&").replace(//g, ">");

}

// Wiki text parsing to html is a nightmare.

// The below functions deliberately don't escape the ampersand since this would make it more difficult,

// and we don't absolutely need to for how we need it.

// This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML.

// Browsers should all be able to handle it though.

// We also escape significant wikimarkup characters to prevent further matching on the processed text

function htmlescape_text(s) {

return s.replace(//g, ">").replace(/:/g, ":").replace(/\[/g, "[").replace(/]/g, "]");

}

function htmlescape_attr(s) {

return htmlescape_text(s).replace(/'/g, "'").replace(/"/g, """);

}

// return the first non matching character position between two strings

function str_imatch(a, b) {

for (var i = 0, l = Math.min(a.length, b.length); i < l; i++) {

if (a.charAt(i) != b.charAt(i)) {

break;

}

}

return i;

}

// compare current line against a string or regexp

// if passed a string it will compare only the first string.length characters

// if passed a regexp the result is stored in r

function compareLineStringOrReg(c) {

return (typeof c == 'string') ? (ll[0] && ll[0].substr(0, c.length) == c) : (r = ll[0] && ll[0].match(c));

}

function compareLineString(c) {

return ll[0] == c;

} // compare current line against a string

function charAtPoint(p) {

return ll[0].charAt(p);

} // return char at pos p

function endl(s) {

ps(s);

sh();

}

function parse_list() {

var prev = '';

while (remain() && compareLineStringOrReg(/^([*#:;]+)(.*)$/)) {

var l_match = r;

sh();

var ipos = str_imatch(prev, l_match[1]);

// close uncontinued lists

for (var prevPos = prev.length - 1; prevPos >= ipos; prevPos--) {

var pi = prev.charAt(prevPos);

if (pi == '*') {

ps('');

} else if (pi == '#') {

ps('');

}

// close a dl only if the new item is not a dl item (:, ; or empty)

else if ($.inArray(l_match[1].charAt(prevPos), ['', '*', '#'])) {

ps('');

}

}

// open new lists

for (var matchPos = ipos; matchPos < l_match[1].length; matchPos++) {

var li = l_match[1].charAt(matchPos);

if (li == '*') {

ps('

');

}

function navlinkDepth(magic, s) {

return s.split('<' + magic + '>').length - s.split('').length;

}

// navlinkString: * becomes the separator

// <> becomes a foo-link with attribute bar='baz'

// and visible text 'fubar'

// if(test){...} and if(test){...}else{...} work too (nested ok)

function navlinkStringToHTML(s, article, params) {

//limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article));

var p = navlinkStringToArray(s, article, params);

var html = '';

var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it

var menurowdepth = 0;

for (var i = 0; i < p.length; ++i) {

if (typeof p[i] == typeof '') {

html += navlinkSubstituteHTML(p[i]);

menudepth += navlinkDepth('menu', p[i]);

menurowdepth += navlinkDepth('menurow', p[i]);

// if (menudepth === 0) {

// tagType='span';

// } else if (menurowdepth === 0) {

// tagType='li';

// } else {

// tagType = null;

// }

} else if (typeof p[i].type != 'undefined' && p[i].type == 'navlinkTag') {

if (menudepth > 0 && menurowdepth === 0) {

html += '

';

} else {

html += p[i].html();

}

}

}

return html;

}

function navlinkTag() {

this.type = 'navlinkTag';

}

navlinkTag.prototype.html = function() {

this.getNewWin();

this.getPrintFunction();

var html = '';

var opening, closing;

var tagType = 'span';

if (!tagType) {

opening = '';

closing = '';

} else {

opening = '<' + tagType + ' class="popup_' + this.id + '">';

closing = '';

}

if (typeof this.print != 'function') {

errlog('Oh dear - invalid print function for a navlinkTag, id=' + this.id);

} else {

html = this.print(this);

if (typeof html != typeof '') {

html = '';

} else if (typeof this.shortcut != 'undefined') html = addPopupShortcut(html, this.shortcut);

}

return opening + html + closing;

};

navlinkTag.prototype.getNewWin = function() {

getValueOf('popupLinksNewWindow');

if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') {

this.newWin = null;

}

this.newWin = pg.option.popupLinksNewWindow[this.id];

};

navlinkTag.prototype.getPrintFunction = function() { //think about this some more

// this.id and this.article should already be defined

if (typeof this.id != typeof '' || typeof this.article != typeof {}) {

return;

}

this.noPopup = 1;

switch (this.id) {

case 'contribs':

case 'history':

case 'whatLinksHere':

case 'userPage':

case 'monobook':

case 'userTalk':

case 'talk':

case 'article':

case 'lastEdit':

this.noPopup = null;

}

switch (this.id) {

case 'email':

case 'contribs':

case 'block':

case 'unblock':

case 'userlog':

case 'userSpace':

case 'deletedContribs':

this.article = this.article.userName();

}

switch (this.id) {

case 'userTalk':

case 'newUserTalk':

case 'editUserTalk':

case 'userPage':

case 'monobook':

case 'editMonobook':

case 'blocklog':

this.article = this.article.userName(true);

/* fall through */

case 'pagelog':

case 'deletelog':

case 'protectlog':

delete this.oldid;

}

if (this.id == 'editMonobook' || this.id == 'monobook') {

this.article.append('/monobook.js');

}

if (this.id != 'mainlink') {

// FIXME anchor handling should be done differently with Title object

this.article = this.article.removeAnchor();

// if (typeof this.text=='undefined') this.text=popupString(this.id);

}

switch (this.id) {

case 'undelete':

this.print = specialLink;

this.specialpage = 'Undelete';

this.sep = '/';

break;

case 'whatLinksHere':

this.print = specialLink;

this.specialpage = 'Whatlinkshere';

break;

case 'relatedChanges':

this.print = specialLink;

this.specialpage = 'Recentchangeslinked';

break;

case 'move':

this.print = specialLink;

this.specialpage = 'Movepage';

break;

case 'contribs':

this.print = specialLink;

this.specialpage = 'Contributions';

break;

case 'deletedContribs':

this.print = specialLink;

this.specialpage = 'Deletedcontributions';

break;

case 'email':

this.print = specialLink;

this.specialpage = 'EmailUser';

this.sep = '/';

break;

case 'block':

this.print = specialLink;

this.specialpage = 'Blockip';

this.sep = '&ip=';

break;

case 'unblock':

this.print = specialLink;

this.specialpage = 'Ipblocklist';

this.sep = '&action=unblock&ip=';

break;

case 'userlog':

this.print = specialLink;

this.specialpage = 'Log';

this.sep = '&user=';

break;

case 'blocklog':

this.print = specialLink;

this.specialpage = 'Log';

this.sep = '&type=block&page=';

break;

case 'pagelog':

this.print = specialLink;

this.specialpage = 'Log';

this.sep = '&page=';

break;

case 'protectlog':

this.print = specialLink;

this.specialpage = 'Log';

this.sep = '&type=protect&page=';

break;

case 'deletelog':

this.print = specialLink;

this.specialpage = 'Log';

this.sep = '&type=delete&page=';

break;

case 'userSpace':

this.print = specialLink;

this.specialpage = 'PrefixIndex';

this.sep = '&namespace=2&prefix=';

break;

case 'search':

this.print = specialLink;

this.specialpage = 'Search';

this.sep = '&fulltext=Search&search=';

break;

case 'thank':

this.print = specialLink;

this.specialpage = 'Thanks';

this.sep = '/';

this.article.value = (this.diff !== 'prev' ? this.diff : this.oldid);

break;

case 'unwatch':

case 'watch':

this.print = magicWatchLink;

this.action = this.id + '&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken=' + autoClickToken();

break;

case 'history':

case 'historyfeed':

case 'unprotect':

case 'protect':

this.print = wikiLink;

this.action = this.id;

break;

case 'delete':

this.print = wikiLink;

this.action = 'delete';

if (this.article.namespaceId() == pg.nsImageId) {

var img = this.article.stripNamespace();

this.action += '&image=' + img;

}

break;

case 'markpatrolled':

case 'edit': // editOld should keep the oldid, but edit should not.

delete this.oldid;

/* fall through */

case 'view':

case 'purge':

case 'render':

this.print = wikiLink;

this.action = this.id;

break;

case 'raw':

this.print = wikiLink;

this.action = 'raw';

break;

case 'new':

this.print = wikiLink;

this.action = 'edit§ion=new';

break;

case 'mainlink':

if (typeof this.text == 'undefined') {

this.text = this.article.toString().entify();

}

if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) {

// only show the /subpage part of the title text

var s = this.text.split('/');

this.text = s[s.length - 1];

if (this.text === '' && s.length > 1) {

this.text = s[s.length - 2];

}

}

this.print = titledWikiLink;

if (typeof this.title === 'undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') {

this.title = safeDecodeURI((pg.current.link.originalTitle) ? pg.current.link.originalTitle : this.article);

if (typeof this.oldid !== 'undefined' && this.oldid) {

this.title = tprintf('Revision %s of %s', [this.oldid, this.title]);

}

}

this.action = 'view';

break;

case 'userPage':

case 'article':

case 'monobook':

case 'editMonobook':

case 'editArticle':

delete this.oldid;

//alert(this.id+'\n'+this.article + '\n'+ typeof this.article);

this.article = this.article.articleFromTalkOrArticle();

//alert(this.id+'\n'+this.article + '\n'+ typeof this.article);

this.print = wikiLink;

if (this.id.indexOf('edit') === 0) {

this.action = 'edit';

} else {

this.action = 'view';

}

break;

case 'userTalk':

case 'talk':

this.article = this.article.talkPage();

delete this.oldid;

this.print = wikiLink;

this.action = 'view';

break;

case 'arin':

this.print = arinLink;

break;

case 'count':

this.print = editCounterLink;

break;

case 'google':

this.print = googleLink;

break;

case 'editors':

this.print = editorListLink;

break;

case 'globalsearch':

this.print = globalSearchLink;

break;

case 'lastEdit':

this.print = titledDiffLink;

this.title = popupString('Show the last edit');

this.from = 'prev';

this.to = 'cur';

break;

case 'oldEdit':

this.print = titledDiffLink;

this.title = popupString('Show the edit made to get revision') + ' ' + this.oldid;

this.from = 'prev';

this.to = this.oldid;

break;

case 'editOld':

this.print = wikiLink;

this.action = 'edit';

break;

case 'undo':

this.print = wikiLink;

this.action = 'edit&undo=';

break;

case 'revert':

this.print = wikiLink;

this.action = 'revert';

break;

case 'nullEdit':

this.print = wikiLink;

this.action = 'nullEdit';

break;

case 'diffCur':

this.print = titledDiffLink;

this.title = tprintf('Show changes since revision %s', [this.oldid]);

this.from = this.oldid;

this.to = 'cur';

break;

case 'editUserTalk':

case 'editTalk':

delete this.oldid;

this.article = this.article.talkPage();

this.action = 'edit';

this.print = wikiLink;

break;

case 'newUserTalk':

case 'newTalk':

this.article = this.article.talkPage();

this.action = 'edit§ion=new';

this.print = wikiLink;

break;

case 'lastContrib':

case 'sinceMe':

this.print = magicHistoryLink;

break;

case 'togglePreviews':

this.text = popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews');

/* fall through */

case 'disablePopups':

case 'purgePopups':

this.print = popupMenuLink;

break;

default:

this.print = function() {

return 'Unknown navlink type: ' + this.id + '';

};

}

};

//

// end navlinks

//////////////////////////////////////////////////

//

// ENDFILE: navlinks.js

// STARTFILE: shortcutkeys.js

//

function popupHandleKeypress(evt) {

var keyCode = window.event ? window.event.keyCode : (evt.keyCode ? evt.keyCode : evt.which);

if (!keyCode || !pg.current.link || !pg.current.link.navpopup) {

return;

}

if (keyCode == 27) { // escape

killPopup();

return false; // swallow keypress

}

var letter = String.fromCharCode(keyCode);

var links = pg.current.link.navpopup.mainDiv.getElementsByTagName('A');

var startLink = 0;

var i, j;

if (popupHandleKeypress.lastPopupLinkSelected) {

for (i = 0; i < links.length; ++i) {

if (links[i] == popupHandleKeypress.lastPopupLinkSelected) {

startLink = i;

}

}

}

for (j = 0; j < links.length; ++j) {

i = (startLink + j + 1) % links.length;

if (links[i].getAttribute('popupkey') == letter) {

if (evt && evt.preventDefault) evt.preventDefault();

links[i].focus();

popupHandleKeypress.lastPopupLinkSelected = links[i];

return false; // swallow keypress

}

}

// pass keypress on

if (document.oldPopupOnkeypress) {

return document.oldPopupOnkeypress(evt);

}

return true;

}

function addPopupShortcuts() {

if (document.onkeypress != popupHandleKeypress) {

document.oldPopupOnkeypress = document.onkeypress;

}

document.onkeypress = popupHandleKeypress;

}

function rmPopupShortcuts() {

popupHandleKeypress.lastPopupLinkSelected = null;

try {

if (document.oldPopupOnkeypress && document.oldPopupOnkeypress == popupHandleKeypress) {

// panic

document.onkeypress = null; //function () {};

return;

}

document.onkeypress = document.oldPopupOnkeypress;

} catch (nasties) {

/* IE goes here */ }

}

function addLinkProperty(html, property) {

// take "... and add a property

// not sophisticated at all, easily broken

var i = html.indexOf('>');

if (i < 0) {

return html;

}

return html.substring(0, i) + ' ' + property + html.substring(i);

}

function addPopupShortcut(html, key) {

if (!getValueOf('popupShortcutKeys')) {

return html;

}

var ret = addLinkProperty(html, 'popupkey="' + key + '"');

if (key == ' ') {

key = popupString('spacebar');

}

return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'), '$1$2$3 [' + key + ']$4');

}

//

// ENDFILE: shortcutkeys.js

// STARTFILE: diffpreview.js

//

//lets jump through hoops to find the rev ids we need to retrieve

function loadDiff(article, oldid, diff, navpop) {

navpop.diffData = {

oldRev: {},

newRev: {}

};

mw.loader.using('mediawiki.api').then(function() {

var api = getMwApi();

var params = {

action: 'compare',

prop: 'ids|title'

};

if (article.title) {

params.fromtitle = article.title;

}

switch (diff) {

case 'cur':

switch (oldid) {

case null:

case '':

case 'prev':

// this can only work if we have the title

// cur -> prev

params.torelative = 'prev';

break;

default:

params.fromrev = oldid;

params.torelative = 'cur';

break;

}

break;

case 'prev':

if (oldid) {

params.fromrev = oldid;

} else {

params.fromtitle;

}

params.torelative = 'prev';

break;

case 'next':

params.fromrev = oldid || 0;

params.torelative = 'next';

break;

default:

params.fromrev = oldid || 0;

params.torev = diff || 0;

break;

}

api.get(params).then(function(data) {

navpop.diffData.oldRev.revid = data.compare.fromrevid;

navpop.diffData.newRev.revid = data.compare.torevid;

addReviewLink(navpop, 'popupMiscTools');

var go = function() {

pendingNavpopTask(navpop);

var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';

url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid;

url += '&prop=revisions&rvprop=ids|timestamp|content';

getPageWithCaching(url, doneDiff, navpop);

return true; // remove hook once run

};

if (navpop.visible || !getValueOf('popupLazyDownloads')) {

go();

} else {

navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS');

}

});

});

}

// Put a "mark patrolled" link to an element target

// TODO: Allow patrol a revision, as well as a diff

function addReviewLink(navpop, target) {

if (!pg.user.canReview) return;

// If 'newRev' is older than 'oldRev' than it could be confusing, so we do not show the review link.

if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) return;

var params = {

action: 'query',

prop: 'info|flagged',

revids: navpop.diffData.oldRev.revid,

formatversion: 2

};

getMwApi().get(params).then(function(data) {

var stable_revid = data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid || 0;

// The diff can be reviewed if the old version is the last reviewed version

// TODO: Other possible conditions that we may want to implement instead of this one:

// * old version is patrolled and the new version is not patrolled

// * old version is patrolled and the new version is more recent than the last reviewed version

if (stable_revid == navpop.diffData.oldRev.revid) {

var a = document.createElement('a');

a.innerHTML = popupString('mark patrolled');

a.title = popupString('markpatrolledHint');

a.onclick = function() {

var params = {

action: 'review',

revid: navpop.diffData.newRev.revid,

comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid])

};

api.postWithToken('csrf', params).done(function() {

a.style.display = "none";

// TODO: Update current page and other already constructed popups

}).fail(function() {

alert(popupString('Could not marked this edit as patrolled'));

});

};

setPopupHTML(a, target, navpop.idNumber, null, true);

}

});

}

function doneDiff(download) {

if (!download.owner || !download.owner.diffData) {

return;

}

var navpop = download.owner;

completedNavpopTask(navpop);

var pages, revisions = [];

try {

// Process the downloads

pages = getJsObj(download.data).query.pages;

for (var i = 0; i < pages.length; i++) {

revisions = revisions.concat(pages[i].revisions);

}

for (i = 0; i < revisions.length; i++) {

if (revisions[i].revid == navpop.diffData.oldRev.revid) {

navpop.diffData.oldRev.revision = revisions[i];

} else if (revisions[i].revid == navpop.diffData.newRev.revid) {

navpop.diffData.newRev.revision = revisions[i];

}

}

} catch (someError) {

errlog('Could not get diff');

}

insertDiff(navpop);

}

function rmBoringLines(a, b, context) {

if (typeof context == 'undefined') {

context = 2;

}

// this is fairly slow... i think it's quicker than doing a word-based diff from the off, though

var aa = [],

aaa = [];

var bb = [],

bbb = [];

var i, j;

// first, gather all disconnected nodes in a and all crossing nodes in a and b

for (i = 0; i < a.length; ++i) {

if (!a[i].paired) {

aa[i] = 1;

} else if (countCrossings(b, a, i, true)) {

aa[i] = 1;

bb[a[i].row] = 1;

}

}

// pick up remaining disconnected nodes in b

for (i = 0; i < b.length; ++i) {

if (bb[i] == 1) {

continue;

}

if (!b[i].paired) {

bb[i] = 1;

}

}

// another pass to gather context: we want the neighbours of included nodes which are not yet included

// we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass

for (i = 0; i < b.length; ++i) {

if (bb[i] == 1) {

for (j = Math.max(0, i - context); j < Math.min(b.length, i + context); ++j) {

if (!bb[j]) {

bb[j] = 1;

aa[b[j].row] = 0.5;

}

}

}

}

for (i = 0; i < a.length; ++i) {

if (aa[i] == 1) {

for (j = Math.max(0, i - context); j < Math.min(a.length, i + context); ++j) {

if (!aa[j]) {

aa[j] = 1;

bb[a[j].row] = 0.5;

}

}

}

}

for (i = 0; i < bb.length; ++i) {

if (bb[i] > 0) { // it's a row we need

if (b[i].paired) {

bbb.push(b[i].text);

} // joined; partner should be in aa

else {

bbb.push(b[i]);

}

}

}

for (i = 0; i < aa.length; ++i) {

if (aa[i] > 0) { // it's a row we need

if (a[i].paired) {

aaa.push(a[i].text);

} // joined; partner should be in aa

else {

aaa.push(a[i]);

}

}

}

return {

a: aaa,

b: bbb

};

}

function stripOuterCommonLines(a, b, context) {

var i = 0;

while (i < a.length && i < b.length && a[i] == b[i]) {

++i;

}

var j = a.length - 1;

var k = b.length - 1;

while (j >= 0 && k >= 0 && a[j] == b[k]) {

--j;

--k;

}

return {

a: a.slice(Math.max(0, i - 1 - context), Math.min(a.length + 1, j + context + 1)),

b: b.slice(Math.max(0, i - 1 - context), Math.min(b.length + 1, k + context + 1))

};

}

function insertDiff(navpop) {

// for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff

// FIXME: sometimes this gives misleading diffs as distant chunks are squashed together

var oldlines = navpop.diffData.oldRev.revision.content.split('\n');

var newlines = navpop.diffData.newRev.revision.content.split('\n');

var inner = stripOuterCommonLines(oldlines, newlines, getValueOf('popupDiffContextLines'));

oldlines = inner.a;

newlines = inner.b;

var truncated = false;

getValueOf('popupDiffMaxLines');

if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) {

// truncate

truncated = true;

inner = stripOuterCommonLines(oldlines.slice(0, pg.option.popupDiffMaxLines),

newlines.slice(0, pg.option.popupDiffMaxLines),

pg.option.popupDiffContextLines);

oldlines = inner.a;

newlines = inner.b;

}

var lineDiff = diff(oldlines, newlines);

var lines2 = rmBoringLines(lineDiff.o, lineDiff.n);

var oldlines2 = lines2.a;

var newlines2 = lines2.b;

var simpleSplit = !String.prototype.parenSplit.isNative;

var html = '


';

if (getValueOf('popupDiffDates')) {

html += diffDatesTable(navpop);

html += '


';

}

html += shortenDiffString(

diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),

getValueOf('popupDiffContextCharacters')).join('


');

setPopupTipsAndHTML(html.split('\n').join('
') +

(truncated ? '


' + popupString('Diff truncated for performance reasons') + '' : ''),

'popupPreview', navpop.idNumber);

}

function diffDatesTable(navpop) {

var html = '

';

html += diffDatesTableRow(navpop.diffData.newRev.revision, tprintf('New revision'));

html += diffDatesTableRow(navpop.diffData.oldRev.revision, tprintf('Old revision'));

html += '

';

return html;

}

function diffDatesTableRow(revision, label) {

var txt = '';

var lastModifiedDate = new Date(revision.timestamp);

txt = formattedDateTime(lastModifiedDate);

var revlink = generalLink({

url: mw.config.get('wgScript') + '?oldid=' + revision.revid,

text: label,

title: label

});

return simplePrintf('%s%s', [revlink, txt]);

}

//

// ENDFILE: diffpreview.js

// STARTFILE: links.js

//

/////////////////////

// LINK GENERATION //

/////////////////////

// titledDiffLink --> titledWikiLink --> generalLink

// wikiLink --> titledWikiLink --> generalLink

// editCounterLink --> generalLink

// TODO Make these functions return Element objects, not just raw HTML strings.

function titledDiffLink(l) { // article, text, title, from, to) {

return titledWikiLink({

article: l.article,

action: l.to + '&oldid=' + l.from,

newWin: l.newWin,

noPopup: l.noPopup,

text: l.text,

title: l.title,

/* hack: no oldid here */

actionName: 'diff'

});

}

function wikiLink(l) {

//{article:article, action:action, text:text, oldid, newid}) {

if (!(typeof l.article == typeof {} &&

typeof l.action == typeof '' &&

typeof l.text == typeof '')) return null;

if (typeof l.oldid == 'undefined') {

l.oldid = null;

}

var savedOldid = l.oldid;

if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) {

l.oldid = null;

}

var hint = popupString(l.action + 'Hint'); // revertHint etc etc etc

var oldidData = [l.oldid, safeDecodeURI(l.article)];

var revisionString = tprintf('revision %s of %s', oldidData);

log('revisionString=' + revisionString);

switch (l.action) {

case 'edit§ion=new':

hint = popupString('newSectionHint');

break;

case 'edit&undo=':

if (l.diff && l.diff != 'prev' && savedOldid) {

l.action += l.diff + '&undoafter=' + savedOldid;

} else if (savedOldid) {

l.action += savedOldid;

}

hint = popupString('undoHint');

break;

case 'raw&ctype=text/css':

hint = popupString('rawHint');

break;

case 'revert':

var p = parseParams(pg.current.link.href);

l.action = 'edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff);

if (p.diff == 'prev') {

l.action += '&direction=prev';

revisionString = tprintf('the revision prior to revision %s of %s', oldidData);

}

if (getValueOf('popupRevertSummaryPrompt')) {

l.action += '&autosummaryprompt=true';

}

if (getValueOf('popupMinorReverts')) {

l.action += '&autominor=true';

}

log('revisionString is now ' + revisionString);

break;

case 'nullEdit':

l.action = 'edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null';

break;

case 'historyfeed':

l.action = 'history&feed=rss';

break;

case 'markpatrolled':

l.action = 'markpatrolled&rcid=' + l.rcid;

}

if (hint) {

if (l.oldid) {

hint = simplePrintf(hint, [revisionString]);

} else {

hint = simplePrintf(hint, [safeDecodeURI(l.article)]);

}

} else {

hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid=' + l.oldid : '';

}

return titledWikiLink({

article: l.article,

action: l.action,

text: l.text,

newWin: l.newWin,

title: hint,

oldid: l.oldid,

noPopup: l.noPopup,

onclick: l.onclick

});

}

function revertSummary(oldid, diff) {

var ret = '';

if (diff == 'prev') {

ret = getValueOf('popupQueriedRevertToPreviousSummary');

} else {

ret = getValueOf('popupQueriedRevertSummary');

}

return ret + '&autorv=' + oldid;

}

function titledWikiLink(l) {

// possible properties of argument:

// article, action, text, title, oldid, actionName, className, noPopup

// oldid = null is fine here

// article and action are mandatory args

if (typeof l.article == 'undefined' || typeof l.action == 'undefined') {

errlog('got undefined article or action in titledWikiLink');

return null;

}

var base = pg.wiki.titlebase + l.article.urlString();

var url = base;

if (typeof l.actionName == 'undefined' || !l.actionName) {

l.actionName = 'action';

}

// no need to add &action=view, and this confuses anchors

if (l.action != 'view') {

url = base + '&' + l.actionName + '=' + l.action;

}

if (typeof l.oldid != 'undefined' && l.oldid) {

url += '&oldid=' + l.oldid;

}

var cssClass = pg.misc.defaultNavlinkClassname;

if (typeof l.className != 'undefined' && l.className) {

cssClass = l.className;

}

return generalNavLink({

url: url,

newWin: l.newWin,

title: (typeof l.title != 'undefined') ? l.title : null,

text: (typeof l.text != 'undefined') ? l.text : null,

className: cssClass,

noPopup: l.noPopup,

onclick: l.onclick

});

}

pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) {

getHistoryInfo(wikipage, function(x) {

processLastContribInfo(x, {

page: wikipage,

newWin: newWin

});

});

};

function processLastContribInfo(info, stuff) {

if (!info.edits || !info.edits.length) {

alert('Popups: an odd thing happened. Please retry.');

return;

}

if (!info.firstNewEditor) {

alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor, info.edits.length]));

return;

}

var newUrl = pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid=' + info.firstNewEditor.oldid;

displayUrl(newUrl, stuff.newWin);

}

pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) {

getHistoryInfo(wikipage, function(x) {

processDiffSinceMyEdit(x, {

page: wikipage,

newWin: newWin

});

});

};

function processDiffSinceMyEdit(info, stuff) {

if (!info.edits || !info.edits.length) {

alert('Popups: something fishy happened. Please try again.');

return;

}

var friendlyName = stuff.page.split('_').join(' ');

if (!info.myLastEdit) {

alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s',

[info.userName, getValueOf('popupHistoryLimit'), friendlyName]));

return;

}

if (info.myLastEdit.index === 0) {

alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName]));

return;

}

var newUrl = pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid=' + info.myLastEdit.oldid;

displayUrl(newUrl, stuff.newWin);

}

function displayUrl(url, newWin) {

if (newWin) {

window.open(url);

} else {

document.location = url;

}

}

pg.fn.purgePopups = function purgePopups() {

processAllPopups(true);

setupCache(); // deletes all cached items (not browser cached, though...)

pg.option = {};

abortAllDownloads();

};

function processAllPopups(nullify, banish) {

for (var i = 0; pg.current.links && i < pg.current.links.length; ++i) {

if (!pg.current.links[i].navpopup) {

continue;

}

if (nullify || banish) pg.current.links[i].navpopup.banish();

pg.current.links[i].simpleNoMore = false;

if (nullify) pg.current.links[i].navpopup = null;

}

}

pg.fn.disablePopups = function disablePopups() {

processAllPopups(false, true);

setupTooltips(null, true);

};

pg.fn.togglePreviews = function togglePreviews() {

processAllPopups(true, true);

pg.option.simplePopups = !pg.option.simplePopups;

abortAllDownloads();

};

function magicWatchLink(l) {

//Yuck!! Would require a thorough redesign to add this as a click event though ...

l.onclick = simplePrintf('pg.fn.modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id]);

return wikiLink(l);

}

pg.fn.modifyWatchlist = function modifyWatchlist(title, action) {

var reqData = {

'action': 'watch',

'formatversion': 2,

'titles': title,

'uselang': mw.config.get('wgUserLanguage')

};

if (action === 'unwatch') reqData.unwatch = true;

// Load the Addedwatchtext or Removedwatchtext message and show it

var mwTitle = mw.Title.newFromText(title);

var messageName;

if (mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1) {

messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk';

} else {

messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext';

}

$.when(

getMwApi().postWithToken('watch', reqData),

mw.loader.using(['mediawiki.api', 'mediawiki.jqueryMsg']).then(function() {

return api.loadMessagesIfMissing([messageName]);

})

).done(function() {

mw.notify(mw.message(messageName, title).parseDom());

});

};

function magicHistoryLink(l) {

// FIXME use onclick change href trick to sort this out instead of window.open

var jsUrl = '',

title = '',

onClick = '';

switch (l.id) {

case 'lastContrib':

onClick = simplePrintf('pg.fn.getLastContrib(\'%s\',%s)',

[l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);

title = popupString('lastContribHint');

break;

case 'sinceMe':

onClick = simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)',

[l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);

title = popupString('sinceMeHint');

break;

}

jsUrl = 'javascript:' + onClick; // jshint ignore:line

onClick += ';return false;';

return generalNavLink({

url: jsUrl,

newWin: false, // can't have new windows with JS links, I think

title: title,

text: l.text,

noPopup: l.noPopup,

onclick: onClick

});

}

function popupMenuLink(l) {

var jsUrl = simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line

var title = popupString(simplePrintf('%sHint', [l.id]));

var onClick = simplePrintf('pg.fn.%s();return false;', [l.id]);

return generalNavLink({

url: jsUrl,

newWin: false,

title: title,

text: l.text,

noPopup: l.noPopup,

onclick: onClick

});

}

function specialLink(l) {

// properties: article, specialpage, text, sep

if (typeof l.specialpage == 'undefined' || !l.specialpage) return null;

var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':' + l.specialpage;

if (typeof l.sep == 'undefined' || l.sep === null) l.sep = '&target=';

var article = l.article.urlString({

keepSpaces: l.specialpage == 'Search'

});

var hint = popupString(l.specialpage + 'Hint');

switch (l.specialpage) {

case 'Log':

switch (l.sep) {

case '&user=':

hint = popupString('userLogHint');

break;

case '&type=block&page=':

hint = popupString('blockLogHint');

break;

case '&page=':

hint = popupString('pageLogHint');

break;

case '&type=protect&page=':

hint = popupString('protectLogHint');

break;

case '&type=delete&page=':

hint = popupString('deleteLogHint');

break;

default:

log('Unknown log type, sep=' + l.sep);

hint = 'Missing hint (FIXME)';

}

break;

case 'PrefixIndex':

article += '/';

break;

}

if (hint) hint = simplePrintf(hint, [safeDecodeURI(l.article)]);

else hint = safeDecodeURI(l.specialpage + ':' + l.article);

var url = base + l.sep + article;

return generalNavLink({

url: url,

title: hint,

text: l.text,

newWin: l.newWin,

noPopup: l.noPopup

});

}

function generalLink(l) {

// l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick

if (typeof l.url == 'undefined') return null;

// only quotation marks in the url can screw us up now... I think

var url = l.url.split('"').join('%22');

var ret = '

if (typeof l.title != 'undefined' && l.title) {

ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"';

}

if (typeof l.onclick != 'undefined' && l.onclick) {

ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"';

}

if (l.noPopup) {

ret += ' noPopup=1';

}

var newWin;

if (typeof l.newWin == 'undefined' || l.newWin === null) {

newWin = getValueOf('popupNewWindows');

} else {

newWin = l.newWin;

}

if (newWin) {

ret += ' target="_blank"';

}

if (typeof l.className != 'undefined' && l.className) {

ret += ' class="' + l.className + '"';

}

ret += '>';

if (typeof l.text == typeof '') {

// We need to HTML-escape this to avoid XSS, but we also want to

// display any existing HTML entities correctly, so unescape it first.

// For example, the display text of the user page menu item is defined

// as "user page", so we need to unescape first to avoid it being

// escaped to "user&nbsp;page".

ret += pg.escapeQuotesHTML(pg.unescapeQuotesHTML(l.text));

}

ret += '';

return ret;

}

function appendParamsToLink(linkstr, params) {

var sp = linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i'));

if (sp.length < 2) return null;

var ret = sp.shift() + sp.shift();

ret += '&' + params + '"';

ret += sp.join('');

return ret;

}

function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional), alsoChangeLabel {

if (x.newTarget) {

log('changeLinkTargetLink: newTarget=' + x.newTarget);

}

if (x.oldTarget !== decodeURIComponent(x.oldTarget)) {

log('This might be an input problem: ' + x.oldTarget);

}

// FIXME: first character of page title as well as namespace should be case insensitive

// eg :category:X1 and :Category:X1 are equivalent

// this'll break if charAt(0) is nasty

var cA = mw.util.escapeRegExp(x.oldTarget);

var chs = cA.charAt(0).toUpperCase();

chs = '[' + chs + chs.toLowerCase() + ']';

var currentArticleRegexBit = chs + cA.substring(1);

currentArticleRegexBit = currentArticleRegexBit

.split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)')

.split('\\(').join('(?:%28|\\()')

.split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ?

// leading and trailing space should be ignored, and anchor bits optional:

currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*';

// e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s*

// autoedit=s~\[\[([Cc]ad)\]\]~$1~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g

var title = x.title || mw.config.get('wgPageName').split('_').join(' ');

var lk = titledWikiLink({

article: new Title(title),

newWin: x.newWin,

action: 'edit',

text: x.text,

title: x.hint,

className: 'popup_change_title_link'

});

var cmd = '';

if (x.newTarget) {

// escape '&' and other nasties

var t = x.newTarget;

var s = mw.util.escapeRegExp(x.newTarget);

if (x.alsoChangeLabel) {

cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~' + t + '~g;';

cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;';

cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~' + t + '~g';

} else {

cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~$1~g;';

cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;';

cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~' + t + '~g';

}

} else {

cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~$1~g;';

cmd += 's~\\[\\[' + currentArticleRegexBit + '[|](.*?)\\]\\]~$2~g';

}

// Build query

cmd = 'autoedit=' + encodeURIComponent(cmd);

cmd += '&autoclick=' + encodeURIComponent(x.clickButton) + '&actoken=' + encodeURIComponent(autoClickToken());

cmd += (x.minor === null) ? '' : '&autominor=' + encodeURIComponent(x.minor);

cmd += (x.watch === null) ? '' : '&autowatch=' + encodeURIComponent(x.watch);

cmd += '&autosummary=' + encodeURIComponent(x.summary);

cmd += '&autoimpl=' + encodeURIComponent(popupString('autoedit_version'));

return appendParamsToLink(lk, cmd);

}

function redirLink(redirMatch, article) {

// NB redirMatch is in wikiText

var ret = '';

if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) {

ret += '


';

if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) {

ret += popupString('Redirects to: (Fix ');

log('redirLink: newTarget=' + redirMatch);

ret += addPopupShortcut(changeLinkTargetLink({

newTarget: redirMatch,

text: popupString('target'),

hint: popupString('Fix this redirect, changing just the link target'),

summary: simplePrintf(getValueOf('popupFixRedirsSummary'), [article.toString(), redirMatch]),

oldTarget: article.toString(),

clickButton: getValueOf('popupRedirAutoClick'),

minor: true,

watch: getValueOf('popupWatchRedirredPages')

}), 'R');

ret += popupString(' or ');

ret += addPopupShortcut(changeLinkTargetLink({

newTarget: redirMatch,

text: popupString('target & label'),

hint: popupString('Fix this redirect, changing the link target and label'),

summary: simplePrintf(getValueOf('popupFixRedirsSummary'), [article.toString(), redirMatch]),

oldTarget: article.toString(),

clickButton: getValueOf('popupRedirAutoClick'),

minor: true,

watch: getValueOf('popupWatchRedirredPages'),

alsoChangeLabel: true

}), 'R');

ret += popupString(')');

} else ret += popupString('Redirects') + popupString(' to ');

return ret;

} else return '
' + popupString('Redirects') + popupString(' to ') +

titledWikiLink({

article: new Title().fromWikiText(redirMatch),

action: 'view',

/* FIXME: newWin */

text: safeDecodeURI(redirMatch),

title: popupString('Bypass redirect')

});

}

function arinLink(l) {

if (!saneLinkCheck(l)) {

return null;

}

if (!l.article.isIpUser() || !pg.wiki.wikimedia) return null;

var uN = l.article.userName();

return generalNavLink({

url: 'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN),

newWin: l.newWin,

title: tprintf('Look up %s in ARIN whois database', [uN]),

text: l.text,

noPopup: 1

});

}

function toolDbName(cookieStyle) {

var ret = mw.config.get('wgDBname');

if (!cookieStyle) {

ret += '_p';

}

return ret;

}

function saneLinkCheck(l) {

if (typeof l.article != typeof {} || typeof l.text != typeof '') {

return false;

}

return true;

}

function editCounterLink(l) {

if (!saneLinkCheck(l)) return null;

if (!pg.wiki.wikimedia) return null;

var uN = l.article.userName();

var tool = getValueOf('popupEditCounterTool');

var url;

var defaultToolUrl = '//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3';

switch (tool) {

case 'custom':

url = simplePrintf(getValueOf('popupEditCounterUrl'), [encodeURIComponent(uN), toolDbName()]);

break;

case 'soxred': // no longer available

case 'kate': // no longer available

case 'interiot': // no longer available

/* fall through */

case 'supercount':

default:

var theWiki = pg.wiki.hostname.split('.');

url = simplePrintf(defaultToolUrl, [encodeURIComponent(uN), theWiki[0], theWiki[1]]);

}

return generalNavLink({

url: url,

title: tprintf('editCounterLinkHint', [uN]),

newWin: l.newWin,

text: l.text,

noPopup: 1

});

}

function globalSearchLink(l) {

if (!saneLinkCheck(l)) return null;

var base = 'http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search=';

var article = l.article.urlString({

keepSpaces: true

});

return generalNavLink({

url: base + article,

newWin: l.newWin,

title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]),

text: l.text,

noPopup: 1

});

}

function googleLink(l) {

if (!saneLinkCheck(l)) return null;

var base = 'https://www.google.com/search?q=';

var article = l.article.urlString({

keepSpaces: true

});

return generalNavLink({

url: base + '%22' + article + '%22',

newWin: l.newWin,

title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]),

text: l.text,

noPopup: 1

});

}

function editorListLink(l) {

if (!saneLinkCheck(l)) return null;

var article = l.article.articleFromTalkPage() || l.article;

var url = 'https://xtools.wmflabs.org/articleinfo/' +

encodeURI(pg.wiki.hostname) + '/' +

article.urlString() +

'?uselang=' + mw.config.get('wgUserLanguage');

return generalNavLink({

url: url,

title: tprintf('editorListHint', [article]),

newWin: l.newWin,

text: l.text,

noPopup: 1

});

}

function generalNavLink(l) {

l.className = (l.className === null) ? 'popupNavLink' : l.className;

return generalLink(l);

}

//////////////////////////////////////////////////

// magic history links

//

function getHistoryInfo(wikipage, whatNext) {

log('getHistoryInfo');

getHistory(wikipage, whatNext ? function(d) {

whatNext(processHistory(d));

} : processHistory);

}

// FIXME eliminate pg.idNumber ... how? :-(

function getHistory(wikipage, onComplete) {

log('getHistory');

var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' +

new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit');

log('getHistory: url=' + url);

return startDownload(url, pg.idNumber + 'history', onComplete);

}

function processHistory(download) {

var jsobj = getJsObj(download.data);

try {

var revisions = anyChild(jsobj.query.pages).revisions;

var edits = [];

for (var i = 0; i < revisions.length; ++i) {

edits.push({

oldid: revisions[i].revid,

editor: revisions[i].user

});

}

log('processed ' + edits.length + ' edits');

return finishProcessHistory(edits, mw.config.get('wgUserName'));

} catch (someError) {

log('Something went wrong with JSON business');

return finishProcessHistory([]);

}

}

function finishProcessHistory(edits, userName) {

var histInfo = {};

histInfo.edits = edits;

histInfo.userName = userName;

for (var i = 0; i < edits.length; ++i) {

if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor == userName) {

histInfo.myLastEdit = {

index: i,

oldid: edits[i].oldid,

previd: (i === 0 ? null : edits[i - 1].oldid)

};

}

if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor != edits[0].editor) {

histInfo.firstNewEditor = {

index: i,

oldid: edits[i].oldid,

previd: (i === 0 ? null : edits[i - 1].oldid)

};

}

}

//pg.misc.historyInfo=histInfo;

return histInfo;

}

//

// ENDFILE: links.js

// STARTFILE: options.js

//////////////////////////////////////////////////

// options

// check for existing value, else use default

function defaultize(x) {

if (pg.option[x] === null || typeof pg.option[x] == 'undefined') {

if (typeof window[x] != 'undefined') pg.option[x] = window[x];

else pg.option[x] = pg.optionDefault[x];

}

}

function newOption(x, def) {

pg.optionDefault[x] = def;

}

function setDefault(x, def) {

return newOption(x, def);

}

function getValueOf(varName) {

defaultize(varName);

return pg.option[varName];

}

/*eslint-disable */

function useDefaultOptions() { // for testing

for (var p in pg.optionDefault) {

pg.option[p] = pg.optionDefault[p];

if (typeof window[p] != 'undefined') {

delete window[p];

}

}

}

/*eslint-enable */

function setOptions() {

// user-settable parameters and defaults

var userIsSysop = false;

if (mw.config.get('wgUserGroups')) {

for (var g = 0; g < mw.config.get('wgUserGroups').length; ++g) {

if (mw.config.get('wgUserGroups')[g] == "sysop")

userIsSysop = true;

}

}

// Basic options

newOption('popupDelay', 0.5);

newOption('popupHideDelay', 0.5);

newOption('simplePopups', false);

newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true

newOption('popupActionsMenu', true);

newOption('popupSetupMenu', true);

newOption('popupAdminLinks', userIsSysop);

newOption('popupShortcutKeys', false);

newOption('popupHistoricalLinks', true);

newOption('popupOnlyArticleLinks', true);

newOption('removeTitles', true);

newOption('popupMaxWidth', 350);

newOption('popupSimplifyMainLink', true);

newOption('popupAppendRedirNavLinks', true);

newOption('popupTocLinks', false);

newOption('popupSubpopups', true);

newOption('popupDragHandle', false /* 'popupTopLinks'*/ );

newOption('popupLazyPreviews', true);

newOption('popupLazyDownloads', true);

newOption('popupAllDabsStubs', false);

newOption('popupDebugging', false);

newOption('popupActiveNavlinks', true);

newOption('popupModifier', false); // ctrl, shift, alt or meta

newOption('popupModifierAction', 'enable'); // or 'disable'

newOption('popupDraggable', true);

newOption('popupReview', false);

newOption('popupLocale', false);

newOption('popupDateTimeFormatterOptions', {

year: 'numeric',

month: 'long',

day: 'numeric',

hour12: false,

hour: '2-digit',

minute: '2-digit',

second: '2-digit'

});

newOption('popupDateFormatterOptions', {

year: 'numeric',

month: 'long',

day: 'numeric'

});

newOption('popupTimeFormatterOptions', {

hour12: false,

hour: '2-digit',

minute: '2-digit',

second: '2-digit'

});

//

// images

newOption('popupImages', true);

newOption('imagePopupsForImages', true);

newOption('popupNeverGetThumbs', false);

//newOption('popupImagesToggleSize', true);

newOption('popupThumbAction', 'imagepage'); //'sizetoggle');

newOption('popupImageSize', 60);

newOption('popupImageSizeLarge', 200);

// redirs, dabs, reversion

newOption('popupFixRedirs', false);

newOption('popupRedirAutoClick', 'wpDiff');

newOption('popupFixDabs', false);

newOption('popupDabsAutoClick', 'wpDiff');

newOption('popupRevertSummaryPrompt', false);

newOption('popupMinorReverts', false);

newOption('popupRedlinkRemoval', false);

newOption('popupRedlinkAutoClick', 'wpDiff');

newOption('popupWatchDisambiggedPages', null);

newOption('popupWatchRedirredPages', null);

newOption('popupDabWiktionary', 'last');

// navlinks

newOption('popupNavLinks', true);

newOption('popupNavLinkSeparator', ' ⋅ ');

newOption('popupLastEditLink', true);

newOption('popupEditCounterTool', 'supercount');

newOption('popupEditCounterUrl', '');

//

// previews etc

newOption('popupPreviews', true);

newOption('popupSummaryData', true);

newOption('popupMaxPreviewSentences', 5);

newOption('popupMaxPreviewCharacters', 600);

newOption('popupLastModified', true);

newOption('popupPreviewKillTemplates', true);

newOption('popupPreviewRawTemplates', true);

newOption('popupPreviewFirstParOnly', true);

newOption('popupPreviewCutHeadings', true);

newOption('popupPreviewButton', false);

newOption('popupPreviewButtonEvent', 'click');

//

// diffs

newOption('popupPreviewDiffs', true);

newOption('popupDiffMaxLines', 100);

newOption('popupDiffContextLines', 2);

newOption('popupDiffContextCharacters', 40);

newOption('popupDiffDates', true);

newOption('popupDiffDatePrinter', 'toLocaleString'); // no longer in use

// edit summaries. God, these are ugly.

newOption('popupReviewedSummary', popupString('defaultpopupReviewedSummary'));

newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary'));

newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary'));

newOption('popupRevertSummary', popupString('defaultpopupRevertSummary'));

newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary'));

newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary'));

newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary'));

newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary'));

newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary'));

newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary'));

//

// misc

newOption('popupHistoryLimit', 50);

//

newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect,

popupFilterPageSize, popupFilterCountLinks,

popupFilterCountImages, popupFilterCountCategories,

popupFilterLastModified

]);

newOption('extraPopupFilters', []);

newOption('popupOnEditSelection', 'cursor');

newOption('popupPreviewHistory', true);

newOption('popupImageLinks', true);

newOption('popupCategoryMembers', true);

newOption('popupUserInfo', true);

newOption('popupHistoryPreviewLimit', 25);

newOption('popupContribsPreviewLimit', 25);

newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion');

newOption('popupShowGender', true);

//

// new windows

newOption('popupNewWindows', false);

newOption('popupLinksNewWindow', {

'lastContrib': true,

'sinceMe': true

});

// regexps

newOption('popupDabRegexp', '\\{\\{\\s*(d(ab|isamb(ig(uation)?)?)|(((geo|hn|road?|school|number)dis)|[234][lc][acw]|(road|ship)index))\\s*(\\|[^}]*)?\\}\\}|is a .*disambiguation.*page');

newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template

newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub');

newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo');

}

// ENDFILE: options.js

// STARTFILE: strings.js

//

//////////////////////////////////////////////////

// Translatable strings

//////////////////////////////////////////////////

//

// See instructions at

// https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation

pg.string = {

/////////////////////////////////////

// summary data, searching etc.

/////////////////////////////////////

'article': 'article',

'category': 'category',

'categories': 'categories',

'image': 'image',

'images': 'images',

'stub': 'stub',

'section stub': 'section stub',

'Empty page': 'Empty page',

'kB': 'kB',

'bytes': 'bytes',

'day': 'day',

'days': 'days',

'hour': 'hour',

'hours': 'hours',

'minute': 'minute',

'minutes': 'minutes',

'second': 'second',

'seconds': 'seconds',

'week': 'week',

'weeks': 'weeks',

'search': 'search',

'SearchHint': 'Find English Wikipedia articles containing %s',

'web': 'web',

'global': 'global',

'globalSearchHint': 'Search across Wikipedias in different languages for %s',

'googleSearchHint': 'Google for %s',

/////////////////////////////////////

// article-related actions and info

// (some actions also apply to user pages)

/////////////////////////////////////

'actions': 'actions', ///// view articles and view talk

'popupsMenu': 'popups',

'togglePreviewsHint': 'Toggle preview generation in popups on this page',

'enable previews': 'enable previews',

'disable previews': 'disable previews',

'toggle previews': 'toggle previews',

'show preview': 'show preview',

'reset': 'reset',

'more...': 'more...',

'disable': 'disable popups',

'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.',

'historyfeedHint': 'RSS feed of recent changes to this page',

'purgePopupsHint': 'Reset popups, clearing all cached popup data.',

'PopupsHint': 'Reset popups, clearing all cached popup data.',

'spacebar': 'space',

'view': 'view',

'view article': 'view article',

'viewHint': 'Go to %s',

'talk': 'talk',

'talk page': 'talk page',

'this revision': 'this revision',

'revision %s of %s': 'revision %s of %s',

'Revision %s of %s': 'Revision %s of %s',

'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s',

'Toggle image size': 'Click to toggle image size',

'del': 'del', ///// delete, protect, move

'delete': 'delete',

'deleteHint': 'Delete %s',

'undeleteShort': 'un',

'UndeleteHint': 'Show the deletion history for %s',

'protect': 'protect',

'protectHint': 'Restrict editing rights to %s',

'unprotectShort': 'un',

'unprotectHint': 'Allow %s to be edited by anyone again',

'send thanks': 'send thanks',

'ThanksHint': 'Send a thank you notification to this user',

'move': 'move',

'move page': 'move page',

'MovepageHint': 'Change the title of %s',

'edit': 'edit', ///// edit articles and talk

'edit article': 'edit article',

'editHint': 'Change the content of %s',

'edit talk': 'edit talk',

'new': 'new',

'new topic': 'new topic',

'newSectionHint': 'Start a new section on %s',

'null edit': 'null edit',

'nullEditHint': 'Submit an edit to %s, making no changes ',

'hist': 'hist', ///// history, diffs, editors, related

'history': 'history',

'historyHint': 'List the changes made to %s',

'last': 'prev', // For labelling the previous revision in history pages; the key is "last" for backwards compatibility

'lastEdit': 'lastEdit',

'mark patrolled': 'mark patrolled',

'markpatrolledHint': 'Mark this edit as patrolled',

'Could not marked this edit as patrolled': 'Could not marked this edit as patrolled',

'show last edit': 'most recent edit',

'Show the last edit': 'Show the effects of the most recent change',

'lastContrib': 'lastContrib',

'last set of edits': 'latest edits',

'lastContribHint': 'Show the net effect of changes made by the last editor',

'cur': 'cur',

'diffCur': 'diffCur',

'Show changes since revision %s': 'Show changes since revision %s',

'%s old': '%s old', // as in 4 weeks old

'oldEdit': 'oldEdit',

'purge': 'purge',

'purgeHint': 'Demand a fresh copy of %s',

'raw': 'source',

'rawHint': 'Download the source of %s',

'render': 'simple',

'renderHint': 'Show a plain HTML version of %s',

'Show the edit made to get revision': 'Show the edit made to get revision',

'sinceMe': 'sinceMe',

'changes since mine': 'diff my edit',

'sinceMeHint': 'Show changes since my last edit',

'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s',

'eds': 'eds',

'editors': 'editors',

'editorListHint': 'List the users who have edited %s',

'related': 'related',

'relatedChanges': 'relatedChanges',

'related changes': 'related changes',

'RecentchangeslinkedHint': 'Show changes in articles related to %s',

'editOld': 'editOld', ///// edit old version, or revert

'rv': 'rv',

'revert': 'revert',

'revertHint': 'Revert to %s',

'defaultpopupReviewedSummary': 'Accepted by reviewing the difference between this version and previously accepted version using popups',

'defaultpopupRedlinkSummary': 'Removing link to empty page %s using popups',

'defaultpopupFixDabsSummary': 'Disambiguate %s to %s using popups',

'defaultpopupFixRedirsSummary': 'Redirect bypass from %s to %s using popups',

'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using popups',

'defaultpopupRevertToPreviousSummary': 'Revert to the revision prior to revision %s using popups',

'defaultpopupRevertSummary': 'Revert to revision %s using popups',

'defaultpopupQueriedRevertToPreviousSummary': 'Revert to the revision prior to revision $1 dated $2 by $3 using popups',

'defaultpopupQueriedRevertSummary': 'Revert to revision $1 dated $2 by $3 using popups',

'defaultpopupRmDabLinkSummary': 'Remove link to dab page %s using popups',

'Redirects': 'Redirects', // as in Redirects to ...

' to ': ' to ', // as in Redirects to ...

'Bypass redirect': 'Bypass redirect',

'Fix this redirect': 'Fix this redirect',

'disambig': 'disambig', ///// add or remove dab etc.

'disambigHint': 'Disambiguate this link to %s',

'Click to disambiguate this link to:': 'Click to disambiguate this link to:',

'remove this link': 'remove this link',

'remove all links to this page from this article': 'remove all links to this page from this article',

'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article',

'mainlink': 'mainlink', ///// links, watch, unwatch

'wikiLink': 'wikiLink',

'wikiLinks': 'wikiLinks',

'links here': 'links here',

'whatLinksHere': 'whatLinksHere',

'what links here': 'what links here',

'WhatlinkshereHint': 'List the pages that are hyperlinked to %s',

'unwatchShort': 'un',

'watchThingy': 'watch', // called watchThingy because {}.watch is a function

'watchHint': 'Add %s to my watchlist',

'unwatchHint': 'Remove %s from my watchlist',

'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits',

'%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s',

'rss': 'rss',

/////////////////////////////////////

// diff previews

/////////////////////////////////////

'Diff truncated for performance reasons': 'Diff truncated for performance reasons',

'Old revision': 'Old revision',

'New revision': 'New revision',

'Something went wrong :-(': 'Something went wrong :-(',

'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent',

'Unknown date': 'Unknown date',

/////////////////////////////////////

// other special previews

/////////////////////////////////////

'Empty category': 'Empty category',

'Category members (%s shown)': 'Category members (%s shown)',

'No image links found': 'No image links found',

'File links': 'File links',

'No image found': 'No image found',

'Image from Commons': 'Image from Commons',

'Description page': 'Description page',

'Alt text:': 'Alt text:',

'revdel': 'Hidden revision',

/////////////////////////////////////

// user-related actions and info

/////////////////////////////////////

'user': 'user', ///// user page, talk, email, space

'user page': 'user page',

'user talk': 'user talk',

'edit user talk': 'edit user talk',

'leave comment': 'leave comment',

'email': 'email',

'email user': 'email user',

'EmailuserHint': 'Send an email to %s',

'space': 'space', // short form for userSpace link

'PrefixIndexHint': 'Show pages in the userspace of %s',

'count': 'count', ///// contributions, log

'edit counter': 'edit counter',

'editCounterLinkHint': 'Count the contributions made by %s',

'contribs': 'contribs',

'contributions': 'contributions',

'deletedContribs': 'deleted contributions',

'DeletedcontributionsHint': 'List deleted edits made by %s',

'ContributionsHint': 'List the contributions made by %s',

'log': 'log',

'user log': 'user log',

'userLogHint': 'Show %s\'s user log',

'arin': 'ARIN lookup', ///// ARIN lookup, block user or IP

'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database',

'unblockShort': 'un',

'block': 'block',

'block user': 'block user',

'IpblocklistHint': 'Unblock %s',

'BlockipHint': 'Prevent %s from editing',

'block log': 'block log',

'blockLogHint': 'Show the block log for %s',

'protectLogHint': 'Show the protection log for %s',

'pageLogHint': 'Show the page log for %s',

'deleteLogHint': 'Show the deletion log for %s',

'Invalid %s %s': 'The option %s is invalid: %s',

'No backlinks found': 'No backlinks found',

' and more': ' and more',

'undo': 'undo',

'undoHint': 'undo this edit',

'Download preview data': 'Download preview data',

'Invalid or IP user': 'Invalid or IP user',

'Not a registered username': 'Not a registered username',

'BLOCKED': 'BLOCKED',

'Has blocks': 'Has blocks',

' edits since: ': ' edits since: ',

'last edit on ': 'last edit on ',

'he/him': 'he/him',

'she/her': 'she/her',

/////////////////////////////////////

// Autoediting

/////////////////////////////////////

'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort',

'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n',

'The %s button has been automatically clicked. Please wait for the next page to load.': 'The %s button has been automatically clicked. Please wait for the next page to load.',

'Could not find button %s. Please check the settings in your javascript file.': 'Could not find button %s. Please check the settings in your javascript file.',

/////////////////////////////////////

// Popups setup

/////////////////////////////////////

'Open full-size image': 'Open full-size image',

'zxy': 'zxy',

'autoedit_version': 'np20140416'

};

function popupString(str) {

if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) {

return popupStrings[str];

}

if (pg.string[str]) {

return pg.string[str];

}

return str;

}

function tprintf(str, subs) {

if (typeof subs != typeof []) {

subs = [subs];

}

return simplePrintf(popupString(str), subs);

}

//

// ENDFILE: strings.js

// STARTFILE: run.js

////////////////////////////////////////////////////////////////////

// Run things

////////////////////////////////////////////////////////////////////

// For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some.

// The old addOnloadHook did something similar to the below

if (document.readyState == "complete")

autoEdit(); //will setup popups

else

$(window).on('load', autoEdit);

// Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout.

(function() {

var once = true;

function dynamicContentHandler($content) {

// Try to detect the hook fired on initial page load and disregard

// it, we already hook to onload (possibly to different parts of

// page - it's configurable) and running twice might be bad. Ugly…

if ($content.attr('id') == 'mw-content-text') {

if (once) {

once = false;

return;

}

}

function registerHooksForVisibleNavpops() {

for (var i = 0; pg.current.links && i < pg.current.links.length; ++i) {

var navpop = pg.current.links[i].navpopup;

if (!navpop || !navpop.isVisible()) {

continue;

}

Navpopup.tracker.addHook(posCheckerHook(navpop));

}

}

function doIt() {

registerHooksForVisibleNavpops();

$content.each(function() {

this.ranSetupTooltipsAlready = false;

setupTooltips(this);

});

}

setupPopups(doIt);

}

// This hook is also fired after page load.

mw.hook('wikipage.content').add(dynamicContentHandler);

mw.hook('ext.echo.overlay.beforeShowingOverlay').add(function($overlay) {

dynamicContentHandler($overlay.find(".mw-echo-state"));

});

})();

});

// ENDFILE: run.js