User:PetraMagna/popups.js: Difference between revisions

From Blue Archive Wiki
Jump to navigation Jump to search
Content added Content deleted
(Created page with "→‎https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups: mw.loader.impl(function() { return ["ext.gadget.Navigation_popups@1dhpx", function($, jQuery, require, module) { $(function() { var pg = { api: {}, re: {}, ns: {}, string: {}, wiki: {}, user: {}, misc: {}, option: {}, optionDefault: {},...")
 
m (maybe this will do?)
 
(4 intermediate revisions by the same user not shown)
Line 1: Line 1:
/*https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups*/
/*Taken from https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups under CC-BY-SA 4.0*/
/*<!--*/
mw.loader.impl(function() {
$(function () {
return ["ext.gadget.Navigation_popups@1dhpx", function($, jQuery, require, module) {
//////////////////////////////////////////////////
$(function() {
// Globals
var pg = {
//
api: {},

re: {},
// Trying to shove as many of these as possible into the pg (popup globals) object
ns: {},
var pg = {
string: {},
api: {}, // MediaWiki API requests
wiki: {},
re: {}, // regexps
user: {},
ns: {}, // namespaces
misc: {},
string: {}, // translatable strings
option: {},
wiki: {}, // local site info
optionDefault: {},
user: {}, // current user info
flag: {},
misc: {}, // YUCK PHOOEY
cache: {},
option: {}, // options, see newOption etc
structures: {},
optionDefault: {}, // default option values
timer: {},
flag: {}, // misc flags
counter: {},
cache: {}, // page and image cache
current: {},
structures: {}, // navlink structures
fn: {},
timer: {}, // all sorts of timers (too damn many)
endoflist: null,
counter: {}, // .. and all sorts of counters
};
current: {}, // state info
if (window.pg && !(window.pg instanceof HTMLElement)) {
fn: {}, // functions
return;
endoflist: null,
}
};
window.pg = pg;

function setupTooltips(container, remove, force, popData) {
/* Bail if the gadget/script is being loaded twice */
log('setupTooltips, container=' + container + ', remove=' + remove);
/* An element with id "pg" would add a window.pg property, ignore such property */
if (!container) {
if (window.pg && !(window.pg instanceof HTMLElement)) {
if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) {
return;
document.editform.wpTextbox1.onmouseup = doSelectionPopup;
}
}

container = defaultPopupsContainer();
/* Export to global context */
}
window.pg = pg;
if (!remove && !force && container.ranSetupTooltipsAlready) {

return;
/// Local Variables: ///
}
/// mode:c ///
container.ranSetupTooltipsAlready = !remove;
/// End: ///
var anchors;
// ENDFILE: main.js
anchors = container.getElementsByTagName('A');

setupTooltipsLoop(anchors, 0, 250, 100, remove, popData);
// STARTFILE: actions.js
}
function setupTooltips(container, remove, force, popData) {
function defaultPopupsContainer() {
log('setupTooltips, container=' + container + ', remove=' + remove);
if (getValueOf('popupOnlyArticleLinks')) {
if (!container) {
return (document.querySelector('.skin-vector-2022 .vector-body') || document.getElementById('mw_content') || document.getElementById('content') || document.getElementById('article') || document);
// the main initial call
}
if (
return document;
getValueOf('popupOnEditSelection') &&
}
document &&
function setupTooltipsLoop(anchors, begin, howmany, sleep, remove, popData) {
document.editform &&
log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments));
document.editform.wpTextbox1
var finish = begin + howmany;
) {
var loopend = Math.min(finish, anchors.length);
document.editform.wpTextbox1.onmouseup = doSelectionPopup;
var j = loopend - begin;
}
log('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin + ', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove);
// article/content is a structure-dependent thing
var doTooltip = remove ? removeTooltip : addTooltip;
container = defaultPopupsContainer();
if (j > 0) {
}
do {

var a = anchors[loopend - j];
if (!remove && !force && container.ranSetupTooltipsAlready) {
if (typeof a === 'undefined' || !a || !a.href) {
return;
log('got null anchor at index ' + loopend - j);
}
continue;
container.ranSetupTooltipsAlready = !remove;
}

doTooltip(a, popData);
var anchors;
} while (--j);
anchors = container.getElementsByTagName('A');
}
setupTooltipsLoop(anchors, 0, 250, 100, remove, popData);
if (finish < anchors.length) {
}
setTimeout(function() {

setupTooltipsLoop(anchors, finish, howmany, sleep, remove, popData);
function defaultPopupsContainer() {
}, sleep);
if (getValueOf('popupOnlyArticleLinks')) {
} else {
return (
if (!remove && !getValueOf('popupTocLinks')) {
document.querySelector('.skin-vector-2022 .vector-body') ||
rmTocTooltips();
document.getElementById('mw_content') ||
}
document.getElementById('content') ||
pg.flag.finishedLoading = true;
document.getElementById('article') ||
}
document
}
);
function rmTocTooltips() {
}
var toc = document.getElementById('toc');
return document;
if (toc) {
}
var tocLinks = toc.getElementsByTagName('A');

var tocLen = tocLinks.length;
function setupTooltipsLoop(anchors, begin, howmany, sleep, remove, popData) {
for (var j = 0; j < tocLen; ++j) {
log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments));
removeTooltip(tocLinks[j], true);
var finish = begin + howmany;
}
var loopend = Math.min(finish, anchors.length);
}
var j = loopend - begin;
}
log(
function addTooltip(a, popData) {
'setupTooltips: anchors.length=' +
if (!isPopupLink(a)) {
anchors.length +
return;
', begin=' +
}
begin +
a.onmouseover = mouseOverWikiLink;
', howmany=' +
a.onmouseout = mouseOutWikiLink;
howmany +
a.onmousedown = killPopup;
', loopend=' +
a.hasPopup = true;
loopend +
a.popData = popData;
', remove=' +
}
remove
function removeTooltip(a) {
);
if (!a.hasPopup) {
var doTooltip = remove ? removeTooltip : addTooltip;
return;
// try a faster (?) loop construct
}
if (j > 0) {
a.onmouseover = null;
do {
a.onmouseout = null;
var a = anchors[loopend - j];
if (a.originalTitle) {
if (typeof a === 'undefined' || !a || !a.href) {
a.title = a.originalTitle;
log('got null anchor at index ' + loopend - j);
}
continue;
a.hasPopup = false;
}
}
doTooltip(a, popData);
function removeTitle(a) {
} while (--j);
if (!a.originalTitle) {
}
a.originalTitle = a.title;
if (finish < anchors.length) {
}
setTimeout(function () {
a.title = '';
setupTooltipsLoop(anchors, finish, howmany, sleep, remove, popData);
}
}, sleep);
function restoreTitle(a) {
} else {
if (a.title || !a.originalTitle) {
if (!remove && !getValueOf('popupTocLinks')) {
return;
rmTocTooltips();
}
}
a.title = a.originalTitle;
pg.flag.finishedLoading = true;
}
}
function registerHooks(np) {
}
var popupMaxWidth = getValueOf('popupMaxWidth');

if (typeof popupMaxWidth === 'number') {
// eliminate popups from the TOC
var setMaxWidth = function() {
// This also kills any onclick stuff that used to be going on in the toc
np.mainDiv.style.maxWidth = popupMaxWidth + 'px';
function rmTocTooltips() {
np.maxWidth = popupMaxWidth;
var toc = document.getElementById('toc');
};
if (toc) {
np.addHook(setMaxWidth, 'unhide', 'before');
var tocLinks = toc.getElementsByTagName('A');
}
var tocLen = tocLinks.length;
np.addHook(addPopupShortcuts, 'unhide', 'after');
for (var j = 0; j < tocLen; ++j) {
np.addHook(rmPopupShortcuts, 'hide', 'before');
removeTooltip(tocLinks[j], true);
}
}
function removeModifierKeyHandler(a) {
}
document.removeEventListener('keydown', a.modifierKeyHandler, false);
}
document.removeEventListener('keyup', a.modifierKeyHandler, false);

}
function mouseOverWikiLink(evt) {
function addTooltip(a, popData) {
if (!isPopupLink(a)) {
if (!evt && window.event) {
return;
evt = window.event;
}
}
a.onmouseover = mouseOverWikiLink;
if (getValueOf('popupModifier')) {
a.onmouseout = mouseOutWikiLink;
var action = getValueOf('popupModifierAction');
a.onmousedown = killPopup;
var key = action == 'disable' ? 'keyup' : 'keydown';
a.hasPopup = true;
var a = this;
a.popData = popData;
a.modifierKeyHandler = function(evt) {
}
mouseOverWikiLink2(a, evt);

}
function removeTooltip(a) {
;
if (!a.hasPopup) {
document.addEventListener(key, a.modifierKeyHandler, false);
return;
}
}
return mouseOverWikiLink2(this, evt);
a.onmouseover = null;
}
a.onmouseout = null;
function footnoteTarget(a) {
if (a.originalTitle) {
var aTitle = Title.fromAnchor(a);
a.title = a.originalTitle;
var anch = aTitle.anchor;
}
if (!/^(cite_note-|_note-|endnote)/.test(anch)) {
a.hasPopup = false;
return false;
}
}

var lTitle = Title.fromURL(location.href);
function removeTitle(a) {
if (lTitle.toString(true) !== aTitle.toString(true)) {
if (!a.originalTitle) {
return false;
a.originalTitle = a.title;
}
}
var el = document.getElementById(anch);
a.title = '';
while (el && typeof el.nodeName === 'string') {
}
var nt = el.nodeName.toLowerCase();

if (nt === 'li') {
function restoreTitle(a) {
return el;
if (a.title || !a.originalTitle) {
} else if (nt === 'body') {
return;
return false;
}
} else if (el.parentNode) {
a.title = a.originalTitle;
el = el.parentNode;
}
} else {

return false;
function registerHooks(np) {
}
var popupMaxWidth = getValueOf('popupMaxWidth');
}

return false;
if (typeof popupMaxWidth === 'number') {
}
function footnotePreview(x, navpop) {
var setMaxWidth = function () {
np.mainDiv.style.maxWidth = popupMaxWidth + 'px';
setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber);
np.maxWidth = popupMaxWidth;
}
};
function modifierPressed(evt) {
np.addHook(setMaxWidth, 'unhide', 'before');
var mod = getValueOf('popupModifier');
}
if (!mod) {
np.addHook(addPopupShortcuts, 'unhide', 'after');
return false;
np.addHook(rmPopupShortcuts, 'hide', 'before');
}
}
if (!evt && window.event) {

evt = window.event;
function removeModifierKeyHandler(a) {
}
//remove listeners for modifier key if any that were added in mouseOverWikiLink
return evt && mod && evt[mod.toLowerCase() + 'Key'];
document.removeEventListener('keydown', a.modifierKeyHandler, false);
}
document.removeEventListener('keyup', a.modifierKeyHandler, false);
function isCorrectModifier(a, evt) {
}
if (!getValueOf('popupModifier')) {

return true;
function mouseOverWikiLink(evt) {
}
if (!evt && window.event) {
var action = getValueOf('popupModifierAction');
evt = window.event;
return ((action == 'enable' && modifierPressed(evt)) || (action == 'disable' && !modifierPressed(evt)));
}
}

function mouseOverWikiLink2(a, evt) {
// if the modifier is needed, listen for it,
if (!isCorrectModifier(a, evt)) {
// we will remove the listener when we mouseout of this link or kill popup.
return;
if (getValueOf('popupModifier')) {
}
// if popupModifierAction = enable, we should popup when the modifier is pressed
if (getValueOf('removeTitles')) {
// if popupModifierAction = disable, we should popup unless the modifier is pressed
removeTitle(a);
var action = getValueOf('popupModifierAction');
}
var key = action == 'disable' ? 'keyup' : 'keydown';
if (a == pg.current.link && a.navpopup && a.navpopup.isVisible()) {
var a = this;
return;
a.modifierKeyHandler = function (evt) {
}
mouseOverWikiLink2(a, evt);
pg.current.link = a;
};
if (getValueOf('simplePopups') && !pg.option.popupStructure) {
document.addEventListener(key, a.modifierKeyHandler, false);
setDefault('popupStructure', 'original');
}
}

var article = new Title().fromAnchor(a);
return mouseOverWikiLink2(this, evt);
pg.current.article = article;
}
if (!a.navpopup) {

a.navpopup = newNavpopup(a, article);
/**
pg.current.linksHash[a.href] = a.navpopup;
* Gets the references list item that the provided footnote link targets. This
pg.current.links.push(a);
* is typically a li element within the ol.references element inside the reflist.
}
* @param {Element} a - A footnote link.
if (a.navpopup.pending === null || a.navpopup.pending !== 0) {
* @returns {Element|boolean} The targeted element, or false if one can't be found.
simplePopupContent(a, article);
*/
}
function footnoteTarget(a) {
a.navpopup.showSoonIfStable(a.navpopup.delay);
var aTitle = Title.fromAnchor(a);
clearInterval(pg.timer.checkPopupPosition);
// We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly
pg.timer.checkPopupPosition = setInterval(checkPopupPosition, 600);
var anch = aTitle.anchor;
if (getValueOf('simplePopups')) {
if (!/^(cite_note-|_note-|endnote)/.test(anch)) {
if (getValueOf('popupPreviewButton') && !a.simpleNoMore) {
return false;
var d = document.createElement('div');
}
d.className = 'popupPreviewButtonDiv';

var s = document.createElement('span');
var lTitle = Title.fromURL(location.href);
d.appendChild(s);
if (lTitle.toString(true) !== aTitle.toString(true)) {
s.className = 'popupPreviewButton';
return false;
s['on' + getValueOf('popupPreviewButtonEvent')] = function() {
}
a.simpleNoMore = true;

d.style.display = 'none';
var el = document.getElementById(anch);
nonsimplePopupContent(a, article);
while (el && typeof el.nodeName === 'string') {
}
var nt = el.nodeName.toLowerCase();
;
if (nt === 'li') {
s.innerHTML = popupString('show preview');
return el;
setPopupHTML(d, 'popupPreview', a.navpopup.idNumber);
} else if (nt === 'body') {
}
return false;
}
} else if (el.parentNode) {
if (a.navpopup.pending !== 0) {
el = el.parentNode;
nonsimplePopupContent(a, article);
} else {
}
return false;
}
}
function simplePopupContent(a, article) {
}
a.navpopup.hasPopupMenu = false;
return false;
a.navpopup.setInnerHTML(popupHTML(a));
}
fillEmptySpans({

navpopup: a.navpopup
function footnotePreview(x, navpop) {
});
setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber);
if (getValueOf('popupDraggable')) {
}
var dragHandle = getValueOf('popupDragHandle') || null;

if (dragHandle && dragHandle != 'all') {
function modifierPressed(evt) {
dragHandle += a.navpopup.idNumber;
var mod = getValueOf('popupModifier');
}
if (!mod) {
setTimeout(function() {
return false;
a.navpopup.makeDraggable(dragHandle);
}
}, 150);

}
if (!evt && window.event) {
if (getValueOf('popupRedlinkRemoval') && a.className == 'new') {
evt = window.event;
setPopupHTML('<br>' + popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber);
}
}

}
return evt && mod && evt[mod.toLowerCase() + 'Key'];
function debugData(navpopup) {
}
if (getValueOf('popupDebugging') && navpopup.idNumber) {

setPopupHTML('idNumber=' + navpopup.idNumber + ', pending=' + navpopup.pending, 'popupError', navpopup.idNumber);
// Checks if the correct modifier pressed/unpressed if needed
}
function isCorrectModifier(a, evt) {
}
if (!getValueOf('popupModifier')) {
function newNavpopup(a, article) {
return true;
var navpopup = new Navpopup();
}
navpopup.fuzz = 5;
// if popupModifierAction = enable, we should popup when the modifier is pressed
navpopup.delay = getValueOf('popupDelay') * 1000;
// if popupModifierAction = disable, we should popup unless the modifier is pressed
navpopup.idNumber = ++pg.idNumber;
var action = getValueOf('popupModifierAction');
navpopup.parentAnchor = a;
return (
navpopup.parentPopup = a.popData && a.popData.owner;
(action == 'enable' && modifierPressed(evt)) || (action == 'disable' && !modifierPressed(evt))
navpopup.article = article;
);
registerHooks(navpopup);
}
return navpopup;

}
function shouldShowNonSimple(a) {
function mouseOverWikiLink2(a, evt) {
if (!isCorrectModifier(a, evt)) {
return !getValueOf('simplePopups') || a.simpleNoMore;
return;
}
}
function shouldShow(a, option) {
if (getValueOf('removeTitles')) {
if (shouldShowNonSimple(a)) {
removeTitle(a);
return getValueOf(option);
}
} else {
if (a == pg.current.link && a.navpopup && a.navpopup.isVisible()) {
return typeof window[option] != 'undefined' && window[option];
return;
}
}
}
pg.current.link = a;
function nonsimplePopupContent(a, article) {

var diff = null
if (getValueOf('simplePopups') && !pg.option.popupStructure) {
, history = null;
// reset *default value* of popupStructure
var params = parseParams(a.href);
setDefault('popupStructure', 'original');
var oldid = typeof params.oldid == 'undefined' ? null : params.oldid;
}
if (shouldShow(a, 'popupPreviewDiffs')) {

diff = params.diff;
var article = new Title().fromAnchor(a);
}
// set global variable (ugh) to hold article (wikipage)
if (shouldShow(a, 'popupPreviewHistory')) {
pg.current.article = article;
history = params.action == 'history';

}
a.navpopup.pending = 0;
if (!a.navpopup) {
a.navpopup = newNavpopup(a, article);
var referenceElement = footnoteTarget(a);
pg.current.linksHash[a.href] = a.navpopup;
if (referenceElement) {
pg.current.links.push(a);
footnotePreview(referenceElement, a.navpopup);
}
} else if (diff || diff === 0) {
if (a.navpopup.pending === null || a.navpopup.pending !== 0) {
loadDiff(article, oldid, diff, a.navpopup);
// either fresh popups or those with unfinshed business are redone from scratch
} else if (history) {
simplePopupContent(a, article);
loadAPIPreview('history', article, a.navpopup);
}
} else if (shouldShowNonSimple(a) && pg.re.contribs.test(a.href)) {
a.navpopup.showSoonIfStable(a.navpopup.delay);
loadAPIPreview('contribs', article, a.navpopup);

} else if (shouldShowNonSimple(a) && pg.re.backlinks.test(a.href)) {
clearInterval(pg.timer.checkPopupPosition);
loadAPIPreview('backlinks', article, a.navpopup);
pg.timer.checkPopupPosition = setInterval(checkPopupPosition, 600);
} else if (article.namespaceId() == pg.nsImageId && (shouldShow(a, 'imagePopupsForImages') || !anchorContainsImage(a))) {

loadAPIPreview('imagepagepreview', article, a.navpopup);
if (getValueOf('simplePopups')) {
loadImage(article, a.navpopup);
if (getValueOf('popupPreviewButton') && !a.simpleNoMore) {
} else {
var d = document.createElement('div');
if (article.namespaceId() == pg.nsCategoryId && shouldShow(a, 'popupCategoryMembers')) {
d.className = 'popupPreviewButtonDiv';
loadAPIPreview('category', article, a.navpopup);
var s = document.createElement('span');
} else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) && shouldShow(a, 'popupUserInfo')) {
d.appendChild(s);
loadAPIPreview('userinfo', article, a.navpopup);
s.className = 'popupPreviewButton';
}
s['on' + getValueOf('popupPreviewButtonEvent')] = function () {
if (shouldShowNonSimple(a)) {
a.simpleNoMore = true;
startArticlePreview(article, oldid, a.navpopup);
d.style.display = 'none';
}
nonsimplePopupContent(a, article);
}
};
}
s.innerHTML = popupString('show preview');
function pendingNavpopTask(navpop) {
setPopupHTML(d, 'popupPreview', a.navpopup.idNumber);
if (navpop && navpop.pending === null) {
}
navpop.pending = 0;
}
}

++navpop.pending;
if (a.navpopup.pending !== 0) {
debugData(navpop);
nonsimplePopupContent(a, article);
}
}
function completedNavpopTask(navpop) {
}
if (navpop && navpop.pending) {

--navpop.pending;
// simplePopupContent: the content that do not require additional download
}
// (it is shown even when simplePopups is true)
debugData(navpop);
function simplePopupContent(a, article) {
}
/* FIXME hack */ a.navpopup.hasPopupMenu = false;
function startArticlePreview(article, oldid, navpop) {
a.navpopup.setInnerHTML(popupHTML(a));
navpop.redir = 0;
fillEmptySpans({ navpopup: a.navpopup });
loadPreview(article, oldid, navpop);

}
if (getValueOf('popupDraggable')) {
function loadPreview(article, oldid, navpop) {
var dragHandle = getValueOf('popupDragHandle') || null;
if (!navpop.redir) {
if (dragHandle && dragHandle != 'all') {
navpop.originalArticle = article;
dragHandle += a.navpopup.idNumber;
}
}
article.oldid = oldid;
setTimeout(function () {
loadAPIPreview('revision', article, navpop);
a.navpopup.makeDraggable(dragHandle);
}
}, 150);
function loadPreviewFromRedir(redirMatch, navpop) {
}
var target = new Title().fromWikiText(redirMatch[2]);

if (navpop.article.anchor) {
if (getValueOf('popupRedlinkRemoval') && a.className == 'new') {
target.anchor = navpop.article.anchor;
setPopupHTML('<br>' + popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber);
}
}
navpop.redir++;
}
navpop.redirTarget = target;

var warnRedir = redirLink(target, navpop.article);
function debugData(navpopup) {
setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber);
if (getValueOf('popupDebugging') && navpopup.idNumber) {
navpop.article = target;
setPopupHTML(
fillEmptySpans({
'idNumber=' + navpopup.idNumber + ', pending=' + navpopup.pending,
redir: true,
'popupError',
redirTarget: target,
navpopup: navpop
navpopup.idNumber
);
});
}
return loadPreview(target, null, navpop);
}
}

function insertPreview(download) {
function newNavpopup(a, article) {
if (!download.owner) {
var navpopup = new Navpopup();
return;
navpopup.fuzz = 5;
}
navpopup.delay = getValueOf('popupDelay') * 1000;
var redirMatch = pg.re.redirect.exec(download.data);
// increment global counter now
if (download.owner.redir === 0 && redirMatch) {
navpopup.idNumber = ++pg.idNumber;
loadPreviewFromRedir(redirMatch, download.owner);
navpopup.parentAnchor = a;
return;
navpopup.parentPopup = a.popData && a.popData.owner;
}
navpopup.article = article;
if (download.owner.visible || !getValueOf('popupLazyPreviews')) {
registerHooks(navpopup);
insertPreviewNow(download);
return navpopup;
} else {
}
var id = download.owner.redir ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK';

download.owner.addHook(function() {
// Should we show nonsimple context?
insertPreviewNow(download);
// If simplePopups is set to true, then we do not show nonsimple context,
return true;
// but if a bottom "show preview" was clicked we do show nonsimple context
}, 'unhide', 'after', id);
function shouldShowNonSimple(a) {
}
return !getValueOf('simplePopups') || a.simpleNoMore;
}
}
function insertPreviewNow(download) {

if (!download.owner) {
// Should we show nonsimple context govern by the option (e.g. popupUserInfo)?
return;
// If the user explicitly asked for nonsimple context by setting the option to true,
}
// then we show it even in nonsimple mode.
var wikiText = download.data;
function shouldShow(a, option) {
var navpop = download.owner;
if (shouldShowNonSimple(a)) {
var art = navpop.redirTarget || navpop.originalArticle;
return getValueOf(option);
makeFixDabs(wikiText, navpop);
} else {
if (getValueOf('popupSummaryData')) {
return typeof window[option] != 'undefined' && window[option];
getPageInfo(wikiText, download);
}
setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber);
}
}

var imagePage = '';
function nonsimplePopupContent(a, article) {
if (art.namespaceId() == pg.nsImageId) {
var diff = null,
imagePage = art.toString();
history = null;
} else {
var params = parseParams(a.href);
imagePage = getValidImageFromWikiText(wikiText);
var oldid = typeof params.oldid == 'undefined' ? null : params.oldid;
}
if (shouldShow(a, 'popupPreviewDiffs')) {
if (imagePage) {
diff = params.diff;
loadImage(Title.fromWikiText(imagePage), navpop);
}
}
if (shouldShow(a, 'popupPreviewHistory')) {
if (getValueOf('popupPreviews')) {
history = params.action == 'history';
insertArticlePreview(download, art, navpop);
}
}
a.navpopup.pending = 0;
}
var referenceElement = footnoteTarget(a);
function insertArticlePreview(download, art, navpop) {
if (referenceElement) {
if (download && typeof download.data == typeof '') {
footnotePreview(referenceElement, a.navpopup);
if (art.namespaceId() == pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) {
} else if (diff || diff === 0) {
var h = '<hr /><span style="font-family: monospace;">' + download.data.entify().split('\\n').join('<br />\\n') + '</span>';
loadDiff(article, oldid, diff, a.navpopup);
setPopupHTML(h, 'popupPreview', navpop.idNumber);
} else if (history) {
} else {
loadAPIPreview('history', article, a.navpopup);
var p = prepPreviewmaker(download.data, art, navpop);
} else if (shouldShowNonSimple(a) && pg.re.contribs.test(a.href)) {
p.showPreview();
loadAPIPreview('contribs', article, a.navpopup);
}
} else if (shouldShowNonSimple(a) && pg.re.backlinks.test(a.href)) {
}
loadAPIPreview('backlinks', article, a.navpopup);
}
} else if (
function prepPreviewmaker(data, article, navpop) {
// FIXME should be able to get all preview combinations with options
var d = anchorize(data, article.anchorString());
article.namespaceId() == pg.nsImageId &&
var urlBase = joinPath([pg.wiki.articlebase, article.urlString()]);
(shouldShow(a, 'imagePopupsForImages') || !anchorContainsImage(a))
var p = new Previewmaker(d,urlBase,navpop);
) {
return p;
loadAPIPreview('imagepagepreview', article, a.navpopup);
}
loadImage(article, a.navpopup);
function anchorize(d, anch) {
} else {
if (!anch) {
if (article.namespaceId() == pg.nsCategoryId && shouldShow(a, 'popupCategoryMembers')) {
return d;
loadAPIPreview('category', article, a.navpopup);
}
} else if (
var anchRe = RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*' + getValueOf('popupAnchorRegexp') + '\\s*(?:\\|[^|}]*)*?\\s*' + literalizeRegex(anch) + '\\s*(?:\\|[^}]*)?}})');
(article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) &&
var match = d.match(anchRe);
shouldShow(a, 'popupUserInfo')
if (match && match.length > 0 && match[0]) {
) {
return d.substring(d.indexOf(match[0]));
loadAPIPreview('userinfo', article, a.navpopup);
}
}
var lines = d.split('\n');
if (shouldShowNonSimple(a)) {
for (var i = 0; i < lines.length; ++i) {
startArticlePreview(article, oldid, a.navpopup);
lines[i] = lines[i].replace(RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{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('^[^=]*'), '');
}
}

}
function pendingNavpopTask(navpop) {
return d;
if (navpop && navpop.pending === null) {
}
navpop.pending = 0;
function killPopup() {
}
removeModifierKeyHandler(this);
++navpop.pending;
if (getValueOf('popupShortcutKeys')) {
debugData(navpop);
rmPopupShortcuts();
}
}

if (!pg) {
function completedNavpopTask(navpop) {
return;
if (navpop && navpop.pending) {
}
--navpop.pending;
if (pg.current.link && pg.current.link.navpopup) {
}
pg.current.link.navpopup.banish();
debugData(navpop);
}
}
pg.current.link = null;

abortAllDownloads();
function startArticlePreview(article, oldid, navpop) {
if (pg.timer.checkPopupPosition) {
navpop.redir = 0;
clearInterval(pg.timer.checkPopupPosition);
loadPreview(article, oldid, navpop);
pg.timer.checkPopupPosition = null;
}
}

return true;
function loadPreview(article, oldid, navpop) {
}
if (!navpop.redir) {
function Drag() {
navpop.originalArticle = article;
this.startCondition = null;
}
this.endHook = null;
article.oldid = oldid;
}
loadAPIPreview('revision', article, navpop);
Drag.prototype.fixE = function(e) {
}
if (typeof e == 'undefined') {

e = window.event;
function loadPreviewFromRedir(redirMatch, navpop) {
}
// redirMatch is a regex match
if (typeof e.layerX == 'undefined') {
var target = new Title().fromWikiText(redirMatch[2]);
e.layerX = e.offsetX;
// overwrite (or add) anchor from original target
}
// mediawiki does overwrite; eg [[User:Lupin/foo3#Done]]
if (typeof e.layerY == 'undefined') {
if (navpop.article.anchor) {
e.layerY = e.offsetY;
target.anchor = navpop.article.anchor;
}
}
return e;
navpop.redir++;
}
navpop.redirTarget = target;
;
var warnRedir = redirLink(target, navpop.article);
Drag.prototype.init = function(o, oRoot) {
setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber);
var dragObj = this;
navpop.article = target;
this.obj = o;
fillEmptySpans({ redir: true, redirTarget: target, navpopup: navpop });
o.onmousedown = function(e) {
return loadPreview(target, null, navpop);
dragObj.start.apply(dragObj, [e]);
}
}

;
function insertPreview(download) {
o.dragging = false;
if (!download.owner) {
o.popups_draggable = true;
return;
o.hmode = true;
}
o.vmode = true;

o.root = oRoot ? oRoot : o;
var redirMatch = pg.re.redirect.exec(download.data);
if (isNaN(parseInt(o.root.style.left, 10))) {
if (download.owner.redir === 0 && redirMatch) {
o.root.style.left = '0px';
loadPreviewFromRedir(redirMatch, download.owner);
}
return;
if (isNaN(parseInt(o.root.style.top, 10))) {
}
o.root.style.top = '0px';

}
if (download.owner.visible || !getValueOf('popupLazyPreviews')) {
o.root.onthisStart = function() {}
insertPreviewNow(download);
;
} else {
o.root.onthisEnd = function() {}
var id = download.owner.redir ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK';
;
download.owner.addHook(
o.root.onthis = function() {}
function () {
;
insertPreviewNow(download);
}
return true;
;
},
Drag.prototype.start = function(e) {
'unhide',
var o = this.obj;
'after',
e = this.fixE(e);
id
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);
function insertPreviewNow(download) {
o.root.onthisStart(x, y);
if (!download.owner) {
o.lastMouseX = e.clientX;
return;
o.lastMouseY = e.clientY;
}
var dragObj = this;
var wikiText = download.data;
o.onmousemoveDefault = document.onmousemove;
var navpop = download.owner;
o.dragging = true;
var art = navpop.redirTarget || navpop.originalArticle;
document.onmousemove = function(e) {

dragObj.drag.apply(dragObj, [e]);
makeFixDabs(wikiText, navpop);
}
if (getValueOf('popupSummaryData')) {
;
getPageInfo(wikiText, download);
document.onmouseup = function(e) {
setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber);
dragObj.end.apply(dragObj, [e]);
}
}

;
var imagePage = '';
return false;
if (art.namespaceId() == pg.nsImageId) {
}
imagePage = art.toString();
;
} else {
Drag.prototype.drag = function(e) {
imagePage = getValidImageFromWikiText(wikiText);
e = this.fixE(e);
}
var o = this.obj;
if (imagePage) {
var ey = e.clientY;
loadImage(Title.fromWikiText(imagePage), navpop);
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);
if (getValueOf('popupPreviews')) {
var nx, ny;
insertArticlePreview(download, art, navpop);
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';
function insertArticlePreview(download, art, navpop) {
this.obj.lastMouseX = ex;
if (download && typeof download.data == typeof '') {
this.obj.lastMouseY = ey;
if (art.namespaceId() == pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) {
this.obj.root.onthis(nx, ny);
// FIXME compare/consolidate with diff escaping code for wikitext
return false;
var h =
}
'<hr /><span style="font-family: monospace;">' +
;
download.data.entify().split('\\n').join('<br />\\n') +
Drag.prototype.end = function() {
'</span>';
document.onmousemove = this.obj.onmousemoveDefault;
setPopupHTML(h, 'popupPreview', navpop.idNumber);
document.onmouseup = null;
} else {
this.obj.dragging = false;
var p = prepPreviewmaker(download.data, art, navpop);
if (this.endHook) {
p.showPreview();
this.endHook(parseInt(this.obj.root.style[this.obj.hmode ? 'left' : 'right'], 10), parseInt(this.obj.root.style[this.obj.vmode ? 'top' : 'bottom'], 10));
}
}
}
}
}
;

pg.structures.original = {};
function prepPreviewmaker(data, article, navpop) {
pg.structures.original.popupLayout = function() {
// deal with tricksy anchors
return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupUserData', 'popupData', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks', ], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab', ];
var d = anchorize(data, article.anchorString());
}
var urlBase = joinPath([pg.wiki.articlebase, article.urlString()]);
;
var p = new Previewmaker(d, urlBase, navpop);
pg.structures.original.popupRedirSpans = function() {
return p;
return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks', ];
}
}

;
// Try to imitate the way mediawiki generates HTML anchors from section titles
pg.structures.original.popupTitle = function(x) {
function anchorize(d, anch) {
log('defaultstructure.popupTitle');
if (!anch) {
if (!getValueOf('popupNavLinks')) {
return d;
return navlinkStringToHTML('<b><<mainlink>></b>', x.article, x.params);
}
}
var anchRe = RegExp(
return '';
'(?:=+\\s*' +
}
literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') +
;
'\\s*=+|\\{\\{\\s*' +
pg.structures.original.popupTopLinks = function(x) {
getValueOf('popupAnchorRegexp') +
log('defaultstructure.popupTopLinks');
'\\s*(?:\\|[^|}]*)*?\\s*' +
if (getValueOf('popupNavLinks')) {
literalizeRegex(anch) +
return navLinksHTML(x.article, x.hint, x.params);
'\\s*(?:\\|[^}]*)?}})'
}
);
return '';
var match = d.match(anchRe);
}
if (match && match.length > 0 && match[0]) {
;
return d.substring(d.indexOf(match[0]));
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);
// now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom
}
var lines = d.split('\n');
;
for (var i = 0; i < lines.length; ++i) {
pg.structures.original.popupRedirTitle = pg.structures.original.popupTitle;
lines[i] = lines[i]
pg.structures.original.popupRedirTopLinks = pg.structures.original.popupTopLinks;
.replace(RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{2}', 'g'), '$2')
function copyStructure(oldStructure, newStructure) {
.replace(/'''([^'])/g, '$1')
pg.structures[newStructure] = {};
.replace(RegExp("''([^'])", 'g'), '$1');
for (var prop in pg.structures[oldStructure]) {
if (lines[i].match(anchRe)) {
pg.structures[newStructure][prop] = pg.structures[oldStructure][prop];
return d.split('\n').slice(i).join('\n').replace(RegExp('^[^=]*'), '');
}
}
}
}
copyStructure('original', 'nostalgia');
return d;
pg.structures.nostalgia.popupTopLinks = function(x) {
}
var str = '';

str += '<b><<mainlink|shortcut= >></b>';
function killPopup() {
str += 'if(user){<br><<contribs|shortcut=c>>';
removeModifierKeyHandler(this);
str += 'if(wikimedia){*<<count|shortcut=#>>}';
if (getValueOf('popupShortcutKeys')) {
str += 'if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}';
rmPopupShortcuts();
var editstr = '<<edit|shortcut=e>>';
}
var editOldidStr = 'if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}';
if (!pg) {
var historystr = '<<history|shortcut=h>>';
return;
var watchstr = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
}
str += '<br>if(talk){' + editOldidStr + '|<<new|shortcut=+>>' + '*' + historystr + '*' + watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';
if (pg.current.link && pg.current.link.navpopup) {
str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>';
pg.current.link.navpopup.banish();
str += 'if(admin){<br>}else{*}<<move|shortcut=m>>';
}
str += 'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>}';
pg.current.link = null;
return navlinkStringToHTML(str, x.article, x.params);
abortAllDownloads();
}
if (pg.timer.checkPopupPosition) {
;
clearInterval(pg.timer.checkPopupPosition);
pg.structures.nostalgia.popupRedirTopLinks = pg.structures.nostalgia.popupTopLinks;
pg.timer.checkPopupPosition = null;
copyStructure('original', 'fancy');
}
pg.structures.fancy.popupTitle = function(x) {
return true; // preserve default action
return navlinkStringToHTML('<font size=+0><<mainlink>></font>', x.article, x.params);
}
}
// ENDFILE: actions.js
;

pg.structures.fancy.popupTopLinks = function(x) {
// STARTFILE: domdrag.js
var hist = '<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>';
/**
var watch = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
@fileoverview
var move = '<<move|shortcut=m|move>>';
The {@link Drag} object, which enables objects to be dragged around.
return navlinkStringToHTML('if(talk){' + '<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' + '<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move + '}else{<<edit|shortcut=e>>*' + hist + '*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' + '*' + watch + '*' + move + '}<br>', x.article, x.params);

}
<pre>
;
*************************************************
pg.structures.fancy.popupOtherLinks = function(x) {
dom-drag.js
var admin = '<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>';
09.25.2001
var user = '<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}';
www.youngpup.net
user += 'if(ipuser){|<<arin>>}else{*<<email|shortcut=E|' + popupString('email') + '>>}if(admin){*<<block|shortcut=b>>}';
**************************************************
var normal = '<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>';
10.28.2001 - fixed minor bug where events
return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){' + admin + 'if(user){<br>}else{*}}' + normal, x.article, x.params);
sometimes fired off the handle, not the root.
}
*************************************************
;
Pared down, some hooks added by [[User:Lupin]]
pg.structures.fancy.popupRedirTitle = pg.structures.fancy.popupTitle;

pg.structures.fancy.popupRedirTopLinks = pg.structures.fancy.popupTopLinks;
Copyright Aaron Boodman.
pg.structures.fancy.popupRedirOtherLinks = pg.structures.fancy.popupOtherLinks;
Saying stupid things daily since March 2001.
copyStructure('fancy', 'fancy2');
</pre>
pg.structures.fancy2.popupTopLinks = function(x) {
*/
return '<br>' + pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$', 'i'), '');

}
/**
;
* Creates a new Drag object. This is used to make various DOM elements draggable.
pg.structures.fancy2.popupLayout = function() {
* @constructor
return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks', ], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab', ];
*/
}
function Drag() {
;
/**
copyStructure('original', 'menus');
* Condition to determine whether or not to drag. This function should take one parameter,
pg.structures.menus.popupLayout = function() {
* an Event. To disable this, set it to <code>null</code>.
return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks', ], 'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab', ];
* @type {Function}
}
*/
;
this.startCondition = null;
pg.structures.menus.popupTopLinks = function(x, shorter) {

var s = [];
/**
var dropclass = 'popup_drop';
* Hook to be run when the drag finishes. This is passed the final coordinates of the
var enddiv = '</div>';
* dragged object (two integers, x and y). To disables this, set it to <code>null</code>.
var hist = '<<history|shortcut=h>>';
* @type {Function}
if (!shorter) {
*/
hist = '<menurow>' + hist + '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>';
this.endHook = null;
}
}
var lastedit = '<<lastEdit|shortcut=/|show last edit>>';

var thank = 'if(diff){<<thank|send thanks>>}';
/**
var jsHistory = '<<lastContrib|last set of edits>><<sinceMe|changes since mine>>';
* Gets an event in a cross-browser manner.
var linkshere = '<<whatLinksHere|shortcut=l|what links here>>';
* @param {Event} e
var related = '<<relatedChanges|shortcut=r|related changes>>';
* @private
var search = '<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' + '|<<google|shortcut=G|web>></menurow>';
*/
var watch = '<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>';
Drag.prototype.fixE = function (e) {
var protect = '<menurow><<unprotect|unprotectShort>>|' + '<<protect|shortcut=p>>|<<protectlog|log>></menurow>';
if (typeof e == 'undefined') {
var del = '<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' + '<<deletelog|log>></menurow>';
e = window.event;
var move = '<<move|shortcut=m|move page>>';
}
var nullPurge = '<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>';
if (typeof e.layerX == 'undefined') {
var viewOptions = '<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>';
e.layerX = e.offsetX;
var editRow = 'if(oldid){' + '<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' + '<menurow><<revert|shortcut=v>>|<<undo>></menurow>' + '}else{<<edit|shortcut=e>>}';
}
var markPatrolled = 'if(rcid){<<markpatrolled|mark patrolled>>}';
if (typeof e.layerY == 'undefined') {
var newTopic = 'if(talk){<<new|shortcut=+|new topic>>}';
e.layerY = e.offsetY;
var protectDelete = 'if(admin){' + protect + del + '}';
}
if (getValueOf('popupActionsMenu')) {
return e;
s.push('<<mainlink>>*' + menuTitle(dropclass, 'actions'));
};
} else {

s.push('<div class="' + dropclass + '">' + '<<mainlink>>');
/**
}
* Initialises the Drag instance by telling it which object you want to be draggable, and what
s.push('<menu>');
* you want to drag it by.
s.push(editRow + markPatrolled + newTopic + hist + lastedit + thank);
* @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged.
if (!shorter) {
* @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted.
s.push(jsHistory);
*/
}
Drag.prototype.init = function (o, oRoot) {
s.push(move + linkshere + related);
var dragObj = this;
if (!shorter) {
this.obj = o;
s.push(nullPurge + search);
o.onmousedown = function (e) {
}
dragObj.start.apply(dragObj, [e]);
if (!shorter) {
};
s.push(viewOptions);
o.dragging = false;
}
o.popups_draggable = true;
s.push('<hr />' + watch + protectDelete);
o.hmode = true;
s.push('<hr />' + 'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' + 'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' + '<<newTalk|shortcut=+|new topic>>}</menu>' + enddiv);
o.vmode = true;
var email = '<<email|shortcut=E|email user>>';

var contribs = 'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' + 'if(admin){<menurow><<deletedContribs>></menurow>}';
o.root = oRoot ? oRoot : o;
s.push('if(user){*' + menuTitle(dropclass, 'user'));

s.push('<menu>');
if (isNaN(parseInt(o.root.style.left, 10))) {
s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>');
o.root.style.left = '0px';
s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' + '<<newUserTalk|shortcut=+|leave comment>>');
}
if (!shorter) {
if (isNaN(parseInt(o.root.style.top, 10))) {
s.push('if(ipuser){<<arin>>}else{' + email + '}');
o.root.style.top = '0px';
} else {
}
s.push('if(ipuser){}else{' + email + '}');

}
o.root.onthisStart = function () {};
s.push('<hr />' + contribs + '<<userlog|shortcut=L|user log>>');
o.root.onthisEnd = function () {};
s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}');
o.root.onthis = function () {};
s.push('if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}');
};
s.push('<<blocklog|shortcut=B|block log>>');

s.push('</menu>' + enddiv + '}');
/**
if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu) {
* Starts the drag.
x.navpop.hasPopupMenu = true;
* @private
s.push('*' + menuTitle(dropclass, 'popupsMenu') + '<menu>');
* @param {Event} e
s.push('<<togglePreviews|toggle previews>>');
*/
s.push('<<purgePopups|reset>>');
Drag.prototype.start = function (e) {
s.push('<<disablePopups|disable>>');
var o = this.obj; // = this;
s.push('</menu>' + enddiv);
e = this.fixE(e);
}
if (this.startCondition && !this.startCondition(e)) {
return navlinkStringToHTML(s.join(''), x.article, x.params);
return;
}
}
;
var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10);
function menuTitle(dropclass, s) {
var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10);
var text = popupString(s);
o.root.onthisStart(x, y);
var len = text.length;

return '<div class="' + dropclass + '" style="--navpop-m-len:' + len + 'ch">' + '<a href="#" noPopup=1>' + text + '</a>';
o.lastMouseX = e.clientX;
}
o.lastMouseY = e.clientY;
pg.structures.menus.popupRedirTitle = pg.structures.menus.popupTitle;

pg.structures.menus.popupRedirTopLinks = pg.structures.menus.popupTopLinks;
var dragObj = this;
copyStructure('menus', 'shortmenus');
o.onmousemoveDefault = document.onmousemove;
pg.structures.shortmenus.popupTopLinks = function(x) {
o.dragging = true;
return pg.structures.menus.popupTopLinks(x, true);
document.onmousemove = function (e) {
}
dragObj.drag.apply(dragObj, [e]);
;
};
pg.structures.shortmenus.popupRedirTopLinks = pg.structures.shortmenus.popupTopLinks;
document.onmouseup = function (e) {
pg.structures.lite = {};
dragObj.end.apply(dragObj, [e]);
pg.structures.lite.popupLayout = function() {
};
return ['popupTitle', 'popupPreview'];
return false;
}
};
;

pg.structures.lite.popupTitle = function(x) {
/**
log(x.article + ': structures.lite.popupTitle');
* Does the drag.
return '<div><span class="popup_mainlink"><b>' + x.article.toString() + '</b></span></div>';
* @param {Event} e
}
* @private
;
*/
function substitute(data, cmdBody) {
Drag.prototype.drag = function (e) {
var fromRe = RegExp(cmdBody.from, cmdBody.flags);
e = this.fixE(e);
return data.replace(fromRe, cmdBody.to);
var o = this.obj;
}

function execCmds(data, cmdList) {
var ey = e.clientY;
for (var i = 0; i < cmdList.length; ++i) {
var ex = e.clientX;
data = cmdList[i].action(data, cmdList[i]);
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);
return data;
var nx, ny;
}

function parseCmd(str) {
nx = x + (ex - o.lastMouseX) * (o.hmode ? 1 : -1);
if (!str.length) {
ny = y + (ey - o.lastMouseY) * (o.vmode ? 1 : -1);
return [];

}
this.obj.root.style[o.hmode ? 'left' : 'right'] = nx + 'px';
var p = false;
this.obj.root.style[o.vmode ? 'top' : 'bottom'] = ny + 'px';
switch (str.charAt(0)) {
this.obj.lastMouseX = ex;
case 's':
this.obj.lastMouseY = ey;
p = parseSubstitute(str);

break;
this.obj.root.onthis(nx, ny);
default:
return false;
return false;
};
}

if (p) {
/**
return [p].concat(parseCmd(p.remainder));
* Ends the drag.
}
* @private
return false;
*/
}
function unEscape(str, sep) {
Drag.prototype.end = function () {
document.onmousemove = this.obj.onmousemoveDefault;
return str.split('\\\\').join('\\').split('\\' + sep).join(sep).split('\\n').join('\n');
document.onmouseup = null;
}
this.obj.dragging = false;
function parseSubstitute(str) {
if (this.endHook) {
var from, to, flags, tmp;
this.endHook(
if (str.length < 4) {
parseInt(this.obj.root.style[this.obj.hmode ? 'left' : 'right'], 10),
return false;
parseInt(this.obj.root.style[this.obj.vmode ? 'top' : 'bottom'], 10)
}
);
var sep = str.charAt(1);
}
str = str.substring(2);
};
tmp = skipOver(str, sep);
// ENDFILE: domdrag.js
if (tmp) {

from = tmp.segment;
// STARTFILE: structures.js
str = tmp.remainder;
pg.structures.original = {};
} else {
pg.structures.original.popupLayout = function () {
return false;
return [
}
'popupError',
tmp = skipOver(str, sep);
'popupImage',
if (tmp) {
'popupTopLinks',
to = tmp.segment;
'popupTitle',
str = tmp.remainder;
'popupUserData',
} else {
'popupData',
return false;
'popupOtherLinks',
}
'popupRedir',
flags = '';
[
if (str.length) {
'popupWarnRedir',
tmp = skipOver(str, ';') || skipToEnd(str, ';');
'popupRedirTopLinks',
if (tmp) {
'popupRedirTitle',
flags = tmp.segment;
'popupRedirData',
str = tmp.remainder;
'popupRedirOtherLinks',
}
],
}
'popupMiscTools',
return {
['popupRedlink'],
action: substitute,
'popupPrePreviewSep',
from: from,
'popupPreview',
to: to,
'popupSecondPreview',
flags: flags,
'popupPreviewMore',
remainder: str,
'popupPostPreview',
};
'popupFixDab',
}
];
function skipOver(str, sep) {
};
var endSegment = findNext(str, sep);
pg.structures.original.popupRedirSpans = function () {
if (endSegment < 0) {
return false;
return [
'popupRedir',
}
'popupWarnRedir',
var segment = unEscape(str.substring(0, endSegment), sep);
'popupRedirTopLinks',
return {
'popupRedirTitle',
segment: segment,
'popupRedirData',
remainder: str.substring(endSegment + 1)
'popupRedirOtherLinks',
};
];
}
};
function skipToEnd(str, sep) {
pg.structures.original.popupTitle = function (x) {
return {
log('defaultstructure.popupTitle');
segment: str,
if (!getValueOf('popupNavLinks')) {
remainder: ''
return navlinkStringToHTML('<b><<mainlink>></b>', x.article, x.params);
};
}
}
return '';
function findNext(str, ch) {
};
for (var i = 0; i < str.length; ++i) {
pg.structures.original.popupTopLinks = function (x) {
if (str.charAt(i) == '\\') {
log('defaultstructure.popupTopLinks');
i += 2;
if (getValueOf('popupNavLinks')) {
}
return navLinksHTML(x.article, x.hint, x.params);
if (str.charAt(i) == ch) {
}
return i;
return '';
}
};
}
pg.structures.original.popupImage = function (x) {
return -1;
log('original.popupImage, x.article=' + x.article + ', x.navpop.idNumber=' + x.navpop.idNumber);
}
return imageHTML(x.article, x.navpop.idNumber);
function setCheckbox(param, box) {
};
var val = mw.util.getParamValue(param);
pg.structures.original.popupRedirTitle = pg.structures.original.popupTitle;
if (val) {
pg.structures.original.popupRedirTopLinks = pg.structures.original.popupTopLinks;
switch (val) {

case '1':
function copyStructure(oldStructure, newStructure) {
case 'yes':
pg.structures[newStructure] = {};
case 'true':
for (var prop in pg.structures[oldStructure]) {
box.checked = true;
pg.structures[newStructure][prop] = pg.structures[oldStructure][prop];
break;
}
case '0':
}
case 'no':

case 'false':
copyStructure('original', 'nostalgia');
box.checked = false;
pg.structures.nostalgia.popupTopLinks = function (x) {
}
var str = '';
}
str += '<b><<mainlink|shortcut= >></b>';
}

function autoEdit() {
// user links
setupPopups(function() {
// contribs - log - count - email - block
if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version')) {
// count only if applicable; block only if popupAdminLinks
return false;
str += 'if(user){<br><<contribs|shortcut=c>>';
}
str += 'if(wikimedia){*<<count|shortcut=#>>}';
if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken') === autoClickToken()) {
str += 'if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}';
pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action'));

}
// editing links
if (!document.editform) {
// talkpage -> edit|new - history - un|watch - article|edit
return false;
// other page -> edit - history - un|watch - talk|edit|new
}
var editstr = '<<edit|shortcut=e>>';
if (autoEdit.alreadyRan) {
var editOldidStr =
return false;
'if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' +
}
editstr +
autoEdit.alreadyRan = true;
'}';
var cmdString = mw.util.getParamValue('autoedit');
var historystr = '<<history|shortcut=h>>';
if (cmdString) {
var watchstr = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
try {

var editbox = document.editform.wpTextbox1;
str +=
var cmdList = parseCmd(cmdString);
'<br>if(talk){' +
var input = editbox.value;
editOldidStr +
var output = execCmds(input, cmdList);
'|<<new|shortcut=+>>' +
editbox.value = output;
'*' +
} catch (dang) {
historystr +
return;
'*' +
}
watchstr +
if (typeof wikEdUseWikEd != 'undefined') {
'*' +
if (wikEdUseWikEd === true) {
'<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
WikEdUpdateFrame();
'}else{' + // not a talk page
}
editOldidStr +
}
'*' +
}
historystr +
setCheckbox('autominor', document.editform.wpMinoredit);
'*' +
setCheckbox('autowatch', document.editform.wpWatchthis);
watchstr +
var rvid = mw.util.getParamValue('autorv');
'*' +
if (rvid) {
'<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';
var url = pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids=' + rvid;

startDownload(url, null, autoEdit2);
// misc links
} else {
str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>';
autoEdit2();
str += 'if(admin){<br>}else{*}<<move|shortcut=m>>';
}

});
// admin links
}
str +=
function autoEdit2(d) {
'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' +
var summary = mw.util.getParamValue('autosummary');
'<<undelete|undeleteShort>>|<<delete|shortcut=d>>}';
var summaryprompt = mw.util.getParamValue('autosummaryprompt');
return navlinkStringToHTML(str, x.article, x.params);
var summarynotice = '';
};
if (d && d.data && mw.util.getParamValue('autorv')) {
pg.structures.nostalgia.popupRedirTopLinks = pg.structures.nostalgia.popupTopLinks;
var s = getRvSummary(summary, d.data);

if (s === false) {
/** -- fancy -- **/
summaryprompt = true;
copyStructure('original', 'fancy');
summarynotice = popupString('Failed to get revision information, please edit manually.\n\n');
pg.structures.fancy.popupTitle = function (x) {
summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)', ]);
return navlinkStringToHTML('<font size=+0><<mainlink>></font>', x.article, x.params);
} else {
};
summary = s;
pg.structures.fancy.popupTopLinks = function (x) {
}
var hist =
}
'<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>';
if (summaryprompt) {
var watch = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
var txt = summarynotice + popupString('Enter a non-empty edit summary or press cancel to abort');
var move = '<<move|shortcut=m|move>>';
var response = prompt(txt, summary);
return navlinkStringToHTML(
if (response) {
'if(talk){' +
summary = response;
'<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' +
} else {
hist +
return;
'*' +
}
'<<article|shortcut=a>>|<<editArticle|edit>>' +
}
'*' +
if (summary) {
watch +
document.editform.wpSummary.value = summary;
'*' +
}
move +
setTimeout(autoEdit3, 100);
'}else{<<edit|shortcut=e>>*' +
}
hist +
function autoClickToken() {
'*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' +
return mw.user.sessionId();
'*' +
}
watch +
function autoEdit3() {
'*' +
if (mw.util.getParamValue('actoken') != autoClickToken()) {
move +
return;
'}<br>',
}
x.article,
var btn = mw.util.getParamValue('autoclick');
x.params
if (btn) {
);
if (document.editform && document.editform[btn]) {
};
var button = document.editform[btn];
pg.structures.fancy.popupOtherLinks = function (x) {
var msg = tprintf('The %s button has been automatically clicked. Please wait for the next page to load.', [button.value]);
var admin =
bannerMessage(msg);
'<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>';
document.title = '(' + document.title + ')';
var user = '<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}';
button.click();
user +=
} else {
'if(ipuser){|<<arin>>}else{*<<email|shortcut=E|' +
alert(tprintf('Could not find button %s. Please check the settings in your javascript file.', [btn, ]));
popupString('email') +
}
'>>}if(admin){*<<block|shortcut=b>>}';
}

}
var normal = '<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>';
function bannerMessage(s) {
return navlinkStringToHTML(
var headings = document.getElementsByTagName('h1');
'<br>if(user){' + user + '*}if(admin){' + admin + 'if(user){<br>}else{*}}' + normal,
if (headings) {
x.article,
var div = document.createElement('div');
x.params
div.innerHTML = '<font size=+1><b>' + pg.escapeQuotesHTML(s) + '</b></font>';
);
headings[0].parentNode.insertBefore(div, headings[0]);
};
}
pg.structures.fancy.popupRedirTitle = pg.structures.fancy.popupTitle;
}
pg.structures.fancy.popupRedirTopLinks = pg.structures.fancy.popupTopLinks;
function getRvSummary(template, json) {
pg.structures.fancy.popupRedirOtherLinks = pg.structures.fancy.popupOtherLinks;
try {

var o = getJsObj(json);
/** -- fancy2 -- **/
var edit = anyChild(o.query.pages).revisions[0];
// hack for [[User:MacGyverMagic]]
var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, '');
copyStructure('fancy', 'fancy2');
return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user, ]);
pg.structures.fancy2.popupTopLinks = function (x) {
} catch (badness) {
// hack out the <br> at the end and put one at the beginning
return false;
return '<br>' + pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$', 'i'), '');
}
};
}
function Downloader(url) {
pg.structures.fancy2.popupLayout = function () {
// move toplinks to after the title
if (typeof XMLHttpRequest != 'undefined') {
return [
this.http = new XMLHttpRequest();
'popupError',
}
'popupImage',
this.url = url;
'popupTitle',
this.id = null;
'popupUserData',
this.lastModified = null;
'popupData',
this.callbackFunction = null;
'popupTopLinks',
this.onFailure = null;
'popupOtherLinks',
this.aborted = false;
'popupRedir',
this.method = 'GET';
[
this.async = true;
'popupWarnRedir',
}
'popupRedirTopLinks',
new Downloader();
'popupRedirTitle',
Downloader.prototype.send = function(x) {
'popupRedirData',
if (!this.http) {
'popupRedirOtherLinks',
return null;
],
}
'popupMiscTools',
return this.http.send(x);
['popupRedlink'],
}
'popupPrePreviewSep',
;
'popupPreview',
Downloader.prototype.abort = function() {
'popupSecondPreview',
if (!this.http) {
'popupPreviewMore',
return null;
'popupPostPreview',
}
'popupFixDab',
this.aborted = true;
];
return this.http.abort();
};
}

;
/** -- menus -- **/
Downloader.prototype.getData = function() {
copyStructure('original', 'menus');
if (!this.http) {
pg.structures.menus.popupLayout = function () {
return null;
return [
}
'popupError',
return this.http.responseText;
'popupImage',
}
'popupTopLinks',
;
'popupTitle',
Downloader.prototype.setTarget = function() {
'popupOtherLinks',
if (!this.http) {
'popupRedir',
return null;
[
}
'popupWarnRedir',
this.http.open(this.method, this.url, this.async);
'popupRedirTopLinks',
this.http.setRequestHeader('Api-User-Agent', pg.api.userAgent);
'popupRedirTitle',
}
'popupRedirData',
;
'popupRedirOtherLinks',
Downloader.prototype.getReadyState = function() {
],
if (!this.http) {
'popupUserData',
return null;
'popupData',
}
'popupMiscTools',
return this.http.readyState;
['popupRedlink'],
}
'popupPrePreviewSep',
;
'popupPreview',
pg.misc.downloadsInProgress = {};
'popupSecondPreview',
Downloader.prototype.start = function() {
'popupPreviewMore',
if (!this.http) {
'popupPostPreview',
return;
'popupFixDab',
}
];
pg.misc.downloadsInProgress[this.id] = this;
};
this.http.send(null);

}
pg.structures.menus.popupTopLinks = function (x, shorter) {
;
// FIXME maybe this stuff should be cached
Downloader.prototype.getLastModifiedDate = function() {
var s = [];
if (!this.http) {
var dropclass = 'popup_drop';
return null;
var enddiv = '</div>';
}
var hist = '<<history|shortcut=h>>';
var lastmod = null;
if (!shorter) {
try {
hist = '<menurow>' + hist + '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>';
lastmod = this.http.getResponseHeader('Last-Modified');
}
} catch (err) {}
var lastedit = '<<lastEdit|shortcut=/|show last edit>>';
if (lastmod) {
var thank = 'if(diff){<<thank|send thanks>>}';
return new Date(lastmod);
var jsHistory = '<<lastContrib|last set of edits>><<sinceMe|changes since mine>>';
}
var linkshere = '<<whatLinksHere|shortcut=l|what links here>>';
return null;
var related = '<<relatedChanges|shortcut=r|related changes>>';
}
var search =
;
'<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' +
Downloader.prototype.setCallback = function(f) {
'|<<google|shortcut=G|web>></menurow>';
if (!this.http) {
var watch = '<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>';
return;
var protect =
}
'<menurow><<unprotect|unprotectShort>>|' +
this.http.onreadystatechange = f;
'<<protect|shortcut=p>>|<<protectlog|log>></menurow>';
}
var del =
;
'<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' + '<<deletelog|log>></menurow>';
Downloader.prototype.getStatus = function() {
var move = '<<move|shortcut=m|move page>>';
if (!this.http) {
var nullPurge = '<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>';
return null;
var viewOptions = '<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>';
}
var editRow =
return this.http.status;
'if(oldid){' +
}
'<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' +
;
'<menurow><<revert|shortcut=v>>|<<undo>></menurow>' +
function newDownload(url, id, callback, onfailure) {
'}else{<<edit|shortcut=e>>}';
var d = new Downloader(url);
var markPatrolled = 'if(rcid){<<markpatrolled|mark patrolled>>}';
if (!d.http) {
var newTopic = 'if(talk){<<new|shortcut=+|new topic>>}';
return 'ohdear';
var protectDelete = 'if(admin){' + protect + del + '}';
}

d.id = id;
if (getValueOf('popupActionsMenu')) {
d.setTarget();
s.push('<<mainlink>>*' + menuTitle(dropclass, 'actions'));
if (!onfailure) {
} else {
onfailure = 2;
s.push('<div class="' + dropclass + '">' + '<<mainlink>>');
}
}
var f = function() {
s.push('<menu>');
if (d.getReadyState() == 4) {
s.push(editRow + markPatrolled + newTopic + hist + lastedit + thank);
delete pg.misc.downloadsInProgress[this.id];
if (!shorter) {
try {
s.push(jsHistory);
if (d.getStatus() == 200) {
}
d.data = d.getData();
s.push(move + linkshere + related);
d.lastModified = d.getLastModifiedDate();
if (!shorter) {
callback(d);
s.push(nullPurge + search);
} else if (typeof onfailure == typeof 1) {
}
if (onfailure > 0) {
if (!shorter) {
newDownload(url, id, callback, onfailure - 1);
s.push(viewOptions);
}
}
} else if (typeof onfailure === 'function') {
s.push('<hr />' + watch + protectDelete);
onfailure(d, url, id, callback);
s.push(
}
'<hr />' +
} catch (somerr) {}
'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' +
}
'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' +
};
'<<newTalk|shortcut=+|new topic>>}</menu>' +
d.setCallback(f);
enddiv
return d;
);
}

function fakeDownload(url, id, callback, data, lastModified, owner) {
// user menu starts here
var d = newDownload(url, callback);
var email = '<<email|shortcut=E|email user>>';
d.owner = owner;
var contribs =
d.id = id;
'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' +
d.data = data;
'if(admin){<menurow><<deletedContribs>></menurow>}';
d.lastModified = lastModified;

return callback(d);
s.push('if(user){*' + menuTitle(dropclass, 'user'));
}
s.push('<menu>');
function startDownload(url, id, callback) {
s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>');
var d = newDownload(url, id, callback);
s.push(
if (typeof d == typeof '') {
'<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' +
return d;
'<<newUserTalk|shortcut=+|leave comment>>'
}
);
d.start();
if (!shorter) {
return d;
s.push('if(ipuser){<<arin>>}else{' + email + '}');
}
} else {
function abortAllDownloads() {
s.push('if(ipuser){}else{' + email + '}');
for (var x in pg.misc.downloadsInProgress) {
}
try {
s.push('<hr />' + contribs + '<<userlog|shortcut=L|user log>>');
pg.misc.downloadsInProgress[x].aborted = true;
s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}');
pg.misc.downloadsInProgress[x].abort();
s.push(
delete pg.misc.downloadsInProgress[x];
'if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}'
} catch (e) {}
);
}
s.push('<<blocklog|shortcut=B|block log>>');
}
s.push('</menu>' + enddiv + '}');
var Insta = {};

function setupLivePreview() {
// popups menu starts here
Insta.conf = {
if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) {
baseUrl: '',
x.navpop.hasPopupMenu = true;
user: {},
s.push('*' + menuTitle(dropclass, 'popupsMenu') + '<menu>');
wiki: {
s.push('<<togglePreviews|toggle previews>>');
lang: pg.wiki.lang,
s.push('<<purgePopups|reset>>');
interwiki: pg.wiki.interwiki,
s.push('<<disablePopups|disable>>');
default_thumb_width: 180,
s.push('</menu>' + enddiv);
},
}
paths: {
return navlinkStringToHTML(s.join(''), x.article, x.params);
articles: pg.wiki.articlePath + '/',
};
math: '/math/',

images: '//upload.wikimedia.org/wikipedia/en/',
function menuTitle(dropclass, s) {
images_fallback: '//upload.wikimedia.org/wikipedia/commons/',
var text = popupString(s); // i18n
},
var len = text.length;
locale: {
return '<div class="' + dropclass + '" style="--navpop-m-len:' + len + 'ch">' + '<a href="#" noPopup=1>' + text + '</a>';
user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId],
}
image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId],

category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId],
pg.structures.menus.popupRedirTitle = pg.structures.menus.popupTitle;
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', ],
pg.structures.menus.popupRedirTopLinks = pg.structures.menus.popupTopLinks;
},

};
copyStructure('menus', 'shortmenus');
Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian';
pg.structures.shortmenus.popupTopLinks = function (x) {
Insta.conf.user.signature = '[[' + Insta.conf.locale.user + ':' + Insta.conf.user.name + '|' + Insta.conf.user.name + ']]';
return pg.structures.menus.popupTopLinks(x, true);
Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|' + Insta.conf.locale.image + '):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)','i');
};
}
pg.structures.shortmenus.popupRedirTopLinks = pg.structures.shortmenus.popupTopLinks;
Insta.dump = function(from, to) {

if (typeof from == 'string') {
pg.structures.lite = {};
from = document.getElementById(from);
pg.structures.lite.popupLayout = function () {
}
return ['popupTitle', 'popupPreview'];
if (typeof to == 'string') {
};
to = document.getElementById(to);
pg.structures.lite.popupTitle = function (x) {
}
log(x.article + ': structures.lite.popupTitle');
to.innerHTML = this.convert(from.value);
//return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params);
}
return '<div><span class="popup_mainlink"><b>' + x.article.toString() + '</b></span></div>';
;
};
Insta.convert = function(wiki) {
// ENDFILE: structures.js
var ll = typeof wiki == 'string' ? wiki.replace(/\r/g, '').split(/\n/) : wiki, o = '', p = 0, r;

function remain() {
// STARTFILE: autoedit.js
return ll.length;
function substitute(data, cmdBody) {
}
// alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags);
function sh() {
var fromRe = RegExp(cmdBody.from, cmdBody.flags);
return ll.shift();
return data.replace(fromRe, cmdBody.to);
}
}
function ps(s) {

o += s;
function execCmds(data, cmdList) {
}
for (var i = 0; i < cmdList.length; ++i) {
function f() {
data = cmdList[i].action(data, cmdList[i]);
var i = 1, a = arguments, f = a[0], o = '', c, p;
}
for (; i < a.length; i++) {
return data;
if ((p = f.indexOf('?')) + 1) {
}
i -= c = f.charAt(p + 1) == '?' ? 1 : 0;

o += f.substring(0, p) + (c ? '?' : a[i]);
function parseCmd(str) {
f = f.substr(p + 1 + c);
// returns a list of commands
} else {
if (!str.length) {
break;
return [];
}
}
}
var p = false;
return o + f;
switch (str.charAt(0)) {
}
case 's':
function html_entities(s) {
p = parseSubstitute(str);
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
break;
}
default:
function htmlescape_text(s) {
return false;
return s.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/:/g, '&#58;').replace(/\[/g, '&#91;').replace(/]/g, '&#93;');
}
}
if (p) {
function htmlescape_attr(s) {
return [p].concat(parseCmd(p.remainder));
return htmlescape_text(s).replace(/'/g, '&#39;').replace(/"/g, '&quot;');
}
}
return false;
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)) {
// FIXME: Only used once here, confusing with native (and more widely-used) unescape, should probably be replaced
break;
// Then again, unescape is semi-soft-deprecated, so we should look into replacing that too
}
function unEscape(str, sep) {
}
return i;
return str
.split('\\\\')
}
.join('\\')
function compareLineStringOrReg(c) {
.split('\\' + sep)
return typeof c == 'string' ? ll[0] && ll[0].substr(0, c.length) == c : (r = ll[0] && ll[0].match(c));
.join(sep)
}
.split('\\n')
function compareLineString(c) {
.join('\n');
return ll[0] == c;
}
}

function charAtPoint(p) {
function parseSubstitute(str) {
return ll[0].charAt(p);
// takes a string like s/a/b/flags;othercmds and parses it
}

function endl(s) {
var from, to, flags, tmp;
ps(s);

sh();
if (str.length < 4) {
}
return false;
function parse_list() {
}
var prev = '';
var sep = str.charAt(1);
while (remain() && compareLineStringOrReg(/^([*#:;]+)(.*)$/)) {
str = str.substring(2);
var l_match = r;

sh();
tmp = skipOver(str, sep);
var ipos = str_imatch(prev, l_match[1]);
if (tmp) {
for (var prevPos = prev.length - 1; prevPos >= ipos; prevPos--) {
from = tmp.segment;
var pi = prev.charAt(prevPos);
str = tmp.remainder;
if (pi == '*') {
} else {
ps('</ul>');
return false;
} else if (pi == '#') {
}
ps('</ol>');

} else if ($.inArray(l_match[1].charAt(prevPos), ['', '*', '#'])) {
tmp = skipOver(str, sep);
ps('</dl>');
if (tmp) {
}
to = tmp.segment;
}
str = tmp.remainder;
for (var matchPos = ipos; matchPos < l_match[1].length; matchPos++) {
} else {
var li = l_match[1].charAt(matchPos);
return false;
if (li == '*') {
}
ps('<ul>');

} else if (li == '#') {
flags = '';
ps('<ol>');
if (str.length) {
} else if ($.inArray(prev.charAt(matchPos), ['', '*', '#'])) {
tmp = skipOver(str, ';') || skipToEnd(str, ';');
ps('<dl>');
if (tmp) {
}
flags = tmp.segment;
}
str = tmp.remainder;
switch (l_match[1].charAt(l_match[1].length - 1)) {
}
case '*':
}
case '#':

ps('<li>' + parse_inline_nowiki(l_match[2]));
return {
break;
action: substitute,
case ';':
from: from,
ps('<dt>');
to: to,
var dt_match = l_match[2].match(/(.*?)(:.*?)$/);
flags: flags,
if (dt_match) {
remainder: str,
ps(parse_inline_nowiki(dt_match[1]));
};
ll.unshift(dt_match[2]);
}
} else {

ps(parse_inline_nowiki(l_match[2]));
function skipOver(str, sep) {
}
var endSegment = findNext(str, sep);
break;
if (endSegment < 0) {
case ':':
return false;
ps('<dd>' + parse_inline_nowiki(l_match[2]));
}
}
var segment = unEscape(str.substring(0, endSegment), sep);
prev = l_match[1];
return { segment: segment, remainder: str.substring(endSegment + 1) };
}
}
for (var i = prev.length - 1; i >= 0; i--) {

ps(f('</?>', prev.charAt(i) == '*' ? 'ul' : prev.charAt(i) == '#' ? 'ol' : 'dl'));
/*eslint-disable*/
}
function skipToEnd(str, sep) {
}
return { segment: str, remainder: '' };
function parse_table() {
}
endl(f('<table>', compareLineStringOrReg(/^\{\|( .*)$/) ? r[1] : ''));
/*eslint-enable */
for (; remain(); ) {

if (compareLineStringOrReg('|')) {
function findNext(str, ch) {
switch (charAtPoint(1)) {
for (var i = 0; i < str.length; ++i) {
case '}':
if (str.charAt(i) == '\\') {
endl('</table>');
i += 2;
return;
}
case '-':
if (str.charAt(i) == ch) {
endl(f('<tr>', compareLineStringOrReg(/\|-*(.*)/)[1]));
return i;
break;
}
default:
}
parse_table_data();
return -1;
}
}
} else if (compareLineStringOrReg('!')) {

parse_table_data();
function setCheckbox(param, box) {
} else {
var val = mw.util.getParamValue(param);
sh();
if (val) {
}
switch (val) {
}
case '1':
}
case 'yes':
function parse_table_data() {
case 'true':
var td_line, match_i;
box.checked = true;
var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/);
break;
if (td_match[1] == '|+') {
case '0':
ps('<caption');
case 'no':
} else {
case 'false':
ps('<t' + (td_match[1] == '|' ? 'd' : 'h'));
box.checked = false;
}
}
if (typeof td_match[3] != 'undefined') {
}
match_i = 4;
}
} else {

match_i = 2;
function autoEdit() {
}
setupPopups(function () {
ps('>');
if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version')) {
if (td_match[1] != '|+') {
return false;
td_line = td_match[match_i].split(td_match[1] == '|' ? '||' : /(?:\|\||!!)/);
}
ps(parse_inline_nowiki(td_line.shift()));
if (
while (td_line.length) {
mw.util.getParamValue('autowatchlist') &&
ll.unshift(td_match[1] + td_line.pop());
mw.util.getParamValue('actoken') === autoClickToken()
}
) {
} else {
pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action'));
ps(parse_inline_nowiki(td_match[match_i]));
}
}
if (!document.editform) {
var tc = 0
return false;
, td = [];
}
while (remain()) {
if (autoEdit.alreadyRan) {
td.push(sh());
return false;
if (compareLineStringOrReg('|')) {
}
if (!tc) {
autoEdit.alreadyRan = true;
break;
var cmdString = mw.util.getParamValue('autoedit');
} else if (charAtPoint(1) == '}') {
if (cmdString) {
tc--;
try {
}
var editbox = document.editform.wpTextbox1;
} else if (!tc && compareLineStringOrReg('!')) {
var cmdList = parseCmd(cmdString);
break;
var input = editbox.value;
} else if (compareLineStringOrReg('{|')) {
var output = execCmds(input, cmdList);
tc++;
editbox.value = output;
}
} catch (dang) {
}
return;
if (td.length) {
}
ps(Insta.convert(td));
// wikEd user script compatibility
}
if (typeof wikEdUseWikEd != 'undefined') {
}
if (wikEdUseWikEd === true) {
function parse_pre() {
WikEdUpdateFrame();
ps('<pre>');
}
do {
}
endl(parse_inline_nowiki(ll[0].substring(1)) + '\n');
}
} while (remain() && compareLineStringOrReg(' '));
setCheckbox('autominor', document.editform.wpMinoredit);
ps('</pre>');
setCheckbox('autowatch', document.editform.wpWatchthis);
}

function parse_block_image() {
var rvid = mw.util.getParamValue('autorv');
ps(parse_image(sh()));
if (rvid) {
}
var url =
function parse_image(str) {
pg.wiki.apiwikibase +
var tag = str.substring(str.indexOf(':') + 1, str.length - 2);
'?action=query&format=json&formatversion=2&prop=revisions&revids=' +
var width;
rvid;
var attr = [], filename, caption = '';
startDownload(url, null, autoEdit2);
var thumb = 0
} else {
, frame = 0
autoEdit2();
, center = 0;
}
var align = '';
});
if (tag.match(/\|/)) {
}
var nesting = 0;

var last_attr;
function autoEdit2(d) {
for (var i = tag.length - 1; i > 0; i--) {
var summary = mw.util.getParamValue('autosummary');
if (tag.charAt(i) == '|' && !nesting) {
var summaryprompt = mw.util.getParamValue('autosummaryprompt');
last_attr = tag.substr(i + 1);
var summarynotice = '';
tag = tag.substring(0, i);
if (d && d.data && mw.util.getParamValue('autorv')) {
break;
var s = getRvSummary(summary, d.data);
} else {
if (s === false) {
switch (tag.substr(i - 1, 2)) {
summaryprompt = true;
case ']]':
summarynotice = popupString(
nesting++;
'Failed to get revision information, please edit manually.\n\n'
i--;
);
break;
summary = simplePrintf(summary, [
case '[[':
mw.util.getParamValue('autorv'),
nesting--;
'(unknown)',
i--;
'(unknown)',
}
]);
}
} else {
}
summary = s;
attr = tag.split(/\s*\|\s*/);
}
attr.push(last_attr);
}
filename = attr.shift();
if (summaryprompt) {
var w_match;
var txt =
for (; attr.length; attr.shift()) {
summarynotice + popupString('Enter a non-empty edit summary or press cancel to abort');
w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/);
var response = prompt(txt, summary);
if (w_match) {
if (response) {
width = w_match[1];
summary = response;
} else {
} else {
switch (attr[0]) {
return;
case 'thumb':
}
case 'thumbnail':
}
thumb = true;
if (summary) {
frame = true;
document.editform.wpSummary.value = summary;
break;
}
case 'frame':
// Attempt to avoid possible premature clicking of the save button
frame = true;
// (maybe delays in updates to the DOM are to blame?? or a red herring)
break;
setTimeout(autoEdit3, 100);
case 'none':
}
case 'right':

case 'left':
function autoClickToken() {
center = false;
return mw.user.sessionId();
align = attr[0];
}
break;

case 'center':
function autoEdit3() {
center = true;
if (mw.util.getParamValue('actoken') != autoClickToken()) {
align = 'none';
return;
break;
}
default:

if (attr.length == 1) {
var btn = mw.util.getParamValue('autoclick');
caption = attr[0];
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.',
} else {
[button.value]
filename = tag;
);
}
bannerMessage(msg);
return '';
document.title = '(' + document.title + ')';
}
button.click();
function parse_inline_nowiki(str) {
} else {
var start, lastend = 0;
alert(
var substart = 0, nestlev = 0, open, close, subloop;
tprintf('Could not find button %s. Please check the settings in your javascript file.', [
var html = '';
btn,
while ((start = str.indexOf('<nowiki>', substart)) != -1) {
])
html += parse_inline_wiki(str.substring(lastend, start));
);
start += 8;
}
substart = start;
}
subloop = true;
}
do {

open = str.indexOf('<nowiki>', substart);
function bannerMessage(s) {
close = str.indexOf('</nowiki>', substart);
var headings = document.getElementsByTagName('h1');
if (close <= open || open == -1) {
if (headings) {
if (close == -1) {
var div = document.createElement('div');
return html + html_entities(str.substr(start));
div.innerHTML = '<font size=+1><b>' + pg.escapeQuotesHTML(s) + '</b></font>';
}
headings[0].parentNode.insertBefore(div, headings[0]);
substart = close + 9;
}
if (nestlev) {
}
nestlev--;

} else {
function getRvSummary(template, json) {
lastend = substart;
try {
html += html_entities(str.substring(start, lastend - 9));
var o = getJsObj(json);
subloop = false;
var edit = anyChild(o.query.pages).revisions[0];
}
var timestamp = edit.timestamp
} else {
.split(/[A-Z]/g)
substart = open + 8;
.join(' ')
nestlev++;
.replace(/^ *| *$/g, '');
}
return simplePrintf(template, [
} while (subloop);
edit.revid,
}
timestamp,
return html + parse_inline_wiki(str.substr(lastend));
edit.userhidden ? '(hidden)' : edit.user,
}
]);
function parse_inline_images(str) {
} catch (badness) {
var start, substart = 0, nestlev = 0;
return false;
var loop, close, open, wiki, html;
}
while ((start = str.indexOf('[[', substart)) != -1) {
}
if (str.substr(start + 2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):', 'i'))) {

loop = true;
// ENDFILE: autoedit.js
substart = start;

do {
// STARTFILE: downloader.js
substart += 2;
/**
close = str.indexOf(']]', substart);
* @fileoverview
open = str.indexOf('[[', substart);
* {@link Downloader}, a xmlhttprequest wrapper, and helper functions.
if (close <= open || open == -1) {
*/
if (close == -1) {

return str;
/**
}
* Creates a new Downloader
substart = close;
* @constructor
if (nestlev) {
* @class The Downloader class. Create a new instance of this class to download stuff.
nestlev--;
* @param {String} url The url to download. This can be omitted and supplied later.
} else {
*/
wiki = str.substring(start, close + 2);
function Downloader(url) {
html = parse_image(wiki);
if (typeof XMLHttpRequest != 'undefined') {
str = str.replace(wiki, html);
this.http = new XMLHttpRequest();
substart = start + html.length;
}
loop = false;

}
/**
} else {
* The url to download
substart = open;
* @type {string}
nestlev++;
*/
}
this.url = url;
} while (loop);

} else {
/**
break;
* A universally unique ID number
}
* @type {number}
}
*/
return str;
this.id = null;
}

function parse_inline_formatting(str) {
/**
var em, st, i, li, o = '';
* Modification date, to be culled from the incoming headers
while ((i = str.indexOf("''", li)) + 1) {
* @type Date
o += str.substring(li, i);
* @private
li = i + 2;
*/
if (str.charAt(i + 2) == "'") {
this.lastModified = null;
li++;

st = !st;
/**
o += st ? '<strong>' : '</strong>';
* What to do when the download completes successfully
} else {
* @type {Function}
em = !em;
* @private
o += em ? '<em>' : '</em>';
*/
}
this.callbackFunction = null;
}

return o + str.substr(li);
/**
}
* What to do on failure
function parse_inline_wiki(str) {
* @type {Function}
str = parse_inline_images(str);
* @private
str = str.replace(/<(?:)math>(.*?)<\/math>/gi, '');
*/
var date = new Date();
this.onFailure = null;
var minutes = date.getUTCMinutes();

if (minutes < 10) {
/**
minutes = '0' + minutes;
* Flag set on <code>abort</code>
}
* @type {boolean}
date = f('?:?, ? ? ? (UTC)', date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear());
*/
str = str.replace(/~{5}(?!~)/g, date).replace(/~{4}(?!~)/g, Insta.conf.user.name + ' ' + date).replace(/~{3}(?!~)/g, Insta.conf.user.name).replace(RegExp('\\[\\[:((?:' + Insta.conf.locale.category + '|Image|File|' + Insta.conf.locale.image + '|' + Insta.conf.wiki.interwiki + '):[^|]*?)\\]\\](\\w*)', 'gi'), function($0, $1, $2) {
this.aborted = false;
return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));

}).replace(RegExp('\\[\\[(?:' + Insta.conf.locale.category + '|' + Insta.conf.wiki.interwiki + '):.*?\\]\\]', 'gi'), '').replace(RegExp('\\[\\[:((?:' + Insta.conf.locale.category + '|Image|File|' + Insta.conf.locale.image + '|' + Insta.conf.wiki.interwiki + '):.*?)\\|([^\\]]+?)\\]\\](\\w*)', 'gi'), function($0, $1, $2, $3) {
/**
return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));
* HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for
}).replace(/\[\[(\/[^|]*?)\]\]/g, function($0, $1) {
* possibilities.
return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1));
* @type {string}
}).replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function($0, $1, $2) {
*/
return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2));
this.method = 'GET';
}).replace(/\[\[([^[|]*?)\]\](\w*)/g, function($0, $1, $2) {
/**
return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));
Async flag.
}).replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function($0, $1, $2, $3) {
@type {boolean}
return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));
*/
}).replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function($0, $1, $2, $3) {
this.async = true;
return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1) + htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($2));
}
}).replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, function($0, $1, $2, $3, $4) {

return f("<a class='external' href='?:?'>?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4));
new Downloader();
}).replace(/\[http:\/\/(.*?)\]/g, function($0, $1) {

return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1));
/** Submits the http request. */
}).replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function($0, $1, $2, $3) {
Downloader.prototype.send = function (x) {
return f("<a class='external' href='?:?'>?:?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($1), htmlescape_text($2) + htmlescape_text($3));
if (!this.http) {
}).replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g, function($0, $1, $2, $3, $4) {
return null;
return f("?<a class='external' href='?:?'>?:?</a>", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4));
}
}).replace('__NOTOC__', '').replace('__NOINDEX__', '').replace('__INDEX__', '').replace('__NOEDITSECTION__', '');
return this.http.send(x);
return parse_inline_formatting(str);
};
}

for (; remain(); ) {
/** Aborts the download, setting the <code>aborted</code> field to true. */
if (compareLineStringOrReg(/^(={1,6})(.*)\1(.*)$/)) {
Downloader.prototype.abort = function () {
p = 0;
if (!this.http) {
endl(f('<h?>?</h?>?', r[1].length, parse_inline_nowiki(r[2]), r[1].length, r[3]));
return null;
} else if (compareLineStringOrReg(/^[*#:;]/)) {
}
p = 0;
this.aborted = true;
parse_list();
return this.http.abort();
} else if (compareLineStringOrReg(' ')) {
};
p = 0;

parse_pre();
/** Returns the downloaded data. */
} else if (compareLineStringOrReg('{|')) {
Downloader.prototype.getData = function () {
p = 0;
if (!this.http) {
parse_table();
return null;
} else if (compareLineStringOrReg(/^----+$/)) {
}
p = 0;
return this.http.responseText;
endl('<hr />');
};
} else if (compareLineStringOrReg(Insta.BLOCK_IMAGE)) {

p = 0;
/** Prepares the download. */
parse_block_image();
Downloader.prototype.setTarget = function () {
} else {
if (!this.http) {
if (compareLineString('')) {
return null;
p = remain() > 1 && ll[1] === '';
}
if (p) {
this.http.open(this.method, this.url, this.async);
endl('<p><br>');
this.http.setRequestHeader('Api-User-Agent', pg.api.userAgent);
}
};
} else {

if (!p) {
/** Gets the state of the download. */
ps('<p>');
Downloader.prototype.getReadyState = function () {
p = 1;
if (!this.http) {
}
return null;
ps(parse_inline_nowiki(ll[0]) + ' ');
}
}
return this.http.readyState;
sh();
};
}

}
pg.misc.downloadsInProgress = {};
return o;

}
/**
;
* Starts the download.
function wiki2html(txt, baseurl) {
* Note that setTarget {@link Downloader#setTarget} must be run first
Insta.conf.baseUrl = baseurl;
*/
return Insta.convert(txt);
Downloader.prototype.start = function () {
}
if (!this.http) {
function popupFilterPageSize(data) {
return;
return formatBytes(data.length);
}
}
pg.misc.downloadsInProgress[this.id] = this;
function popupFilterCountLinks(data) {
this.http.send(null);
var num = countLinks(data);
};
return String(num) + '&nbsp;' + (num != 1 ? popupString('wikiLinks') : popupString('wikiLink'));

}
/**
function popupFilterCountImages(data) {
* Gets the 'Last-Modified' date from the download headers.
var num = countImages(data);
* Should be run after the download completes.
return String(num) + '&nbsp;' + (num != 1 ? popupString('images') : popupString('image'));
* Returns <code>null</code> on failure.
}
* @return {Date}
function popupFilterCountCategories(data) {
*/
var num = countCategories(data);
Downloader.prototype.getLastModifiedDate = function () {
return (String(num) + '&nbsp;' + (num != 1 ? popupString('categories') : popupString('category')));
if (!this.http) {
}
return null;
function popupFilterLastModified(data, download) {
}
var lastmod = download.lastModified;
var lastmod = null;
var now = new Date();
try {
var age = now - lastmod;
lastmod = this.http.getResponseHeader('Last-Modified');
if (lastmod && getValueOf('popupLastModified')) {
} catch (err) {}
return tprintf('%s old', [formatAge(age)]).replace(RegExp(' ', 'g'), '&nbsp;');
if (lastmod) {
}
return new Date(lastmod);
return '';
}
}
return null;
function popupFilterWikibaseItem(data, download) {
};
return download.wikibaseItem ? tprintf('<a href="%s">%s</a>', [download.wikibaseRepo.replace(/\$1/g, download.wikibaseItem), download.wikibaseItem, ]) : '';

}
/**
function formatAge(age) {
* Sets the callback function.
var a = 0 + age
* @param {Function} f callback function, called as <code>f(this)</code> on success
, aa = a;
*/
var seclen = 1000;
Downloader.prototype.setCallback = function (f) {
var minlen = 60 * seclen;
if (!this.http) {
var hourlen = 60 * minlen;
return;
var daylen = 24 * hourlen;
}
var weeklen = 7 * daylen;
this.http.onreadystatechange = f;
var numweeks = (a - (a % weeklen)) / weeklen;
};
a = a - numweeks * weeklen;

var sweeks = addunit(numweeks, 'week');
Downloader.prototype.getStatus = function () {
var numdays = (a - (a % daylen)) / daylen;
if (!this.http) {
a = a - numdays * daylen;
return null;
var sdays = addunit(numdays, 'day');
}
var numhours = (a - (a % hourlen)) / hourlen;
return this.http.status;
a = a - numhours * hourlen;
};
var shours = addunit(numhours, 'hour');

var nummins = (a - (a % minlen)) / minlen;
//////////////////////////////////////////////////
a = a - nummins * minlen;
// helper functions
var smins = addunit(nummins, 'minute');

var numsecs = (a - (a % seclen)) / seclen;
/**
a = a - numsecs * seclen;
* Creates a new {@link Downloader} and prepares it for action.
var ssecs = addunit(numsecs, 'second');
* @param {String} url The url to download
if (aa > 4 * weeklen) {
* @param {number} id The ID of the {@link Downloader} object
return sweeks;
* @param {Function} callback The callback function invoked on success
}
* @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
if (aa > weeklen) {
*/
return sweeks + ' ' + sdays;
function newDownload(url, id, callback, onfailure) {
}
var d = new Downloader(url);
if (aa > daylen) {
if (!d.http) {
return sdays + ' ' + shours;
return 'ohdear';
}
}
if (aa > 6 * hourlen) {
d.id = id;
return shours;
d.setTarget();
}
if (!onfailure) {
if (aa > hourlen) {
onfailure = 2;
return shours + ' ' + smins;
}
}
var f = function () {
if (aa > 10 * minlen) {
if (d.getReadyState() == 4) {
return smins;
delete pg.misc.downloadsInProgress[this.id];
}
try {
if (aa > minlen) {
if (d.getStatus() == 200) {
return smins + ' ' + ssecs;
d.data = d.getData();
}
d.lastModified = d.getLastModifiedDate();
return ssecs;
callback(d);
}
} else if (typeof onfailure == typeof 1) {
function addunit(num, str) {
if (onfailure > 0) {
return String(num) + ' ' + (num != 1 ? popupString(str + 's') : popupString(str));
// retry
}
newDownload(url, id, callback, onfailure - 1);
function runPopupFilters(list, data, download) {
}
var ret = [];
} else if (typeof onfailure === 'function') {
for (var i = 0; i < list.length; ++i) {
onfailure(d, url, id, callback);
if (list[i] && typeof list[i] == 'function') {
}
var s = list[i](data, download, download.owner.article);
} catch (somerr) {
if (s) {
/* ignore it */
ret.push(s);
}
}
}
}
};
}
d.setCallback(f);
return ret;
return d;
}
}
function getPageInfo(data, download) {
/**
if (!data || data.length === 0) {
* Simulates a download from cached data.
return popupString('Empty page');
* The supplied data is put into a {@link Downloader} as if it had downloaded it.
}
* @param {String} url The url.
var popupFilters = getValueOf('popupFilters') || [];
* @param {number} id The ID.
var extraPopupFilters = getValueOf('extraPopupFilters') || [];
* @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>,
var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download);
* where <code>d</code> is the new {@link Downloader}.
var pageInfo = pageInfoArray.join(', ');
* @param {String} data The (cached) data.
if (pageInfo !== '') {
* @param {Date} lastModified The (cached) last modified date.
pageInfo = upcaseFirst(pageInfo);
*/
}
function fakeDownload(url, id, callback, data, lastModified, owner) {
return pageInfo;
var d = newDownload(url, callback);
}
d.owner = owner;
function countLinks(wikiText) {
d.id = id;
return wikiText.split('[[').length - 1;
d.data = data;
}
d.lastModified = lastModified;
function countImages(wikiText) {
return callback(d);
return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1);
}
}

function countCategories(wikiText) {
/**
return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1);
* Starts a download.
}
* @param {String} url The url to download
function popupFilterStubDetect(data, download, article) {
* @param {number} id The ID of the {@link Downloader} object
var counts = stubCount(data, article);
* @param {Function} callback The callback function invoked on success
if (counts.real) {
* @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
return popupString('stub');
*/
}
function startDownload(url, id, callback) {
if (counts.sect) {
var d = newDownload(url, id, callback);
return popupString('section stub');
if (typeof d == typeof '') {
}
return '';
return d;
}
}
d.start();
function popupFilterDisambigDetect(data, download, article) {
return d;
if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
}
return '';

}
/**
return isDisambig(data, article) ? popupString('disambig') : '';
* Aborts all downloads which have been started.
}
*/
function formatBytes(num) {
function abortAllDownloads() {
return num > 949 ? Math.round(num / 100) / 10 + popupString('kB') : num + '&nbsp;' + popupString('bytes');
for (var x in pg.misc.downloadsInProgress) {
}
try {
function Stringwrapper() {
pg.misc.downloadsInProgress[x].aborted = true;
this.indexOf = function(x) {
pg.misc.downloadsInProgress[x].abort();
return this.toString().indexOf(x);
delete pg.misc.downloadsInProgress[x];
}
} catch (e) {}
;
}
this.toString = function() {
}
return this.value;
// ENDFILE: downloader.js
}

;
// STARTFILE: livepreview.js
this.parenSplit = function(x) {
// TODO: location is often not correct (eg relative links in previews)
return this.toString().parenSplit(x);
// NOTE: removed md5 and image and math parsing. was broken, lots of bytes.
}
/**
;
* InstaView - a Mediawiki to HTML converter in JavaScript
this.substring = function(x, y) {
* Version 0.6.1
if (typeof y == 'undefined') {
* Copyright (C) Pedro Fayolle 2005-2006
return this.toString().substring(x);
* https://en.wikipedia.org/wiki/User:Pilaf
}
* Distributed under the BSD license
return this.toString().substring(x, y);
*
}
* Changelog:
;
*
this.split = function(x) {
* 0.6.1
return this.toString().split(x);
* - Fixed problem caused by \r characters
}
* - Improved inline formatting parser
;
*
this.replace = function(x, y) {
* 0.6
return this.toString().replace(x, y);
* - 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
function Title(val) {
* - Fixed bug in table headings handling
this.value = null;
* - Changed date format in signatures to reflect Mediawiki's
this.anchor = '';
* - Fixed handling of [[:Image:...]]
this.setUtf(val);
* - Updated MD5 function (hopefully it will work with UTF-8)
}
* - Fixed bug in handling of links inside images
Title.prototype = new Stringwrapper();
*
Title.prototype.toString = function(omitAnchor) {
* To do:
return this.value + (!omitAnchor && this.anchor ? '#' + this.anchorString() : '');
* - Better support for math tags
}
* - Full support for <nowiki>
;
* - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and
Title.prototype.anchorString = function() {
* bullet-proof)
if (!this.anchor) {
* - Support for templates (through AJAX)
return '';
* - Support for coloured links (AJAX)
}
*/
var split = this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/);

var len = split.length;
var Insta = {};
var value;

for (var j = 1; j < len; j += 2) {
function setupLivePreview() {
value = split[j].split('.').join('%');
// options
try {
Insta.conf = {
value = decodeURIComponent(value);
baseUrl: '',
} catch (e) {}

split[j] = value.split('_').join(' ');
user: {},
}

return split.join('');
wiki: {
}
lang: pg.wiki.lang,
;
interwiki: pg.wiki.interwiki,
Title.prototype.urlAnchor = function() {
default_thumb_width: 180,
var split = this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/');
},
var len = split.length;

for (var j = 1; j < len; j += 2) {
paths: {
split[j] = split[j].split('%').join('.');
articles: pg.wiki.articlePath + '/',
}
// Only used for Insta previews with images. (not in popups)
return split.join('');
math: '/math/',
}
images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname),
;
images_fallback: '//upload.wikimedia.org/wikipedia/commons/',
Title.prototype.anchorFromUtf = function(str) {
},
this.anchor = encodeURIComponent(str.split(' ').join('_')).split('%3A').join(':').split("'").join('%27').split('%').join('.');

}
locale: {
;
user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId],
Title.fromURL = function(h) {
image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId],
return new Title().fromURL(h);
category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId],
}
// shouldn't be used in popup previews, i think
;
months: [
Title.prototype.fromURL = function(h) {
'Jan',
if (typeof h != 'string') {
'Feb',
this.value = null;
'Mar',
return this;
'Apr',
}
'May',
var splitted = h.split('?');
'Jun',
splitted[0] = splitted[0].split('&').join('%26');
'Jul',
h = splitted.join('?');
'Aug',
var contribs = pg.re.contribs.exec(h);
'Sep',
if (contribs) {
'Oct',
if (contribs[1] == 'title=') {
'Nov',
contribs[3] = contribs[3].split('+').join(' ');
'Dec',
}
],
var u = new Title(contribs[3]);
},
this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace()));
};
return this;

}
// options with default values or backreferences
var email = pg.re.email.exec(h);
Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian';
if (email) {
Insta.conf.user.signature =
this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace()));
'[[' +
return this;
Insta.conf.locale.user +
}
':' +
var backlinks = pg.re.backlinks.exec(h);
Insta.conf.user.name +
if (backlinks) {
'|' +
this.setUtf(this.decodeNasties(new Title(backlinks[3])));
Insta.conf.user.name +
return this;
']]';
}
//Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/';
var specialdiff = pg.re.specialdiff.exec(h);

if (specialdiff) {
// define constants
this.setUtf(this.decodeNasties(new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff')));
Insta.BLOCK_IMAGE = new RegExp(
return this;
'^\\[\\[(?:File|Image|' +
}
Insta.conf.locale.image +
var m = pg.re.main.exec(h);
'):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)',
if (m === null) {
'i'
this.value = null;
);
} else {
}
var fromBotInterface = /[?](.+[&])?title=/.test(h);

if (fromBotInterface) {
Insta.dump = function (from, to) {
m[2] = m[2].split('+').join('_');
if (typeof from == 'string') {
}
from = document.getElementById(from);
var extracted = m[2] + (m[3] ? '#' + m[3] : '');
}
if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) {
if (typeof to == 'string') {
this.setUtf(decodeURIComponent(unescape(extracted)));
to = document.getElementById(to);
} else {
}
this.setUtf(this.decodeNasties(extracted));
to.innerHTML = this.convert(from.value);
}
};
}

return this;
Insta.convert = function (wiki) {
}
var ll = typeof wiki == 'string' ? wiki.replace(/\r/g, '').split(/\n/) : wiki, // lines of wikicode
;
o = '', // output
Title.prototype.decodeNasties = function(txt) {
p = 0, // para flag
try {
r; // result of passing a regexp to compareLineStringOrReg()
var ret = decodeURI(this.decodeEscapes(txt));

ret = ret.replace(/[_ ]*$/, '');
// some shorthands
return ret;
function remain() {
} catch (e) {
return txt;
return ll.length;
}
}
function sh() {
}
return ll.shift();
;
} // shift
Title.prototype.decodeEscapes = function(txt) {
function ps(s) {
var split = txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/);
o += s;
var len = split.length;
} // push
if (len === 1) {

return split[0].replace(/%(?![0-9a-fA-F][0-9a-fA-F])/g, '%25');
// similar to C's printf, uses ? as placeholders, ?? to escape question marks
}
function f() {
for (var i = 1; i < len; i = i + 2) {
var i = 1,
split[i] = decodeURIComponent(split[i]);
a = arguments,
}
f = a[0],
return split.join('');
o = '',
}
c,
;
p;
Title.fromAnchor = function(a) {
for (; i < a.length; i++) {
return new Title().fromAnchor(a);
if ((p = f.indexOf('?')) + 1) {
}
// allow character escaping
;
i -= c = f.charAt(p + 1) == '?' ? 1 : 0;
Title.prototype.fromAnchor = function(a) {
o += f.substring(0, p) + (c ? '?' : a[i]);
if (!a) {
f = f.substr(p + 1 + c);
this.value = null;
} else {
return this;
break;
}
}
return this.fromURL(a.href);
}
}
return o + f;
;
}
Title.fromWikiText = function(txt) {

return new Title().fromWikiText(txt);
function html_entities(s) {
}
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
;
}
Title.prototype.fromWikiText = function(txt) {

txt = myDecodeURI(txt);
// Wiki text parsing to html is a nightmare.
this.setUtf(txt);
// The below functions deliberately don't escape the ampersand since this would make it more
return this;
// 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
Title.prototype.hintValue = function() {
// characters to prevent further matching on the processed text.
if (!this.value) {
function htmlescape_text(s) {
return '';
return s
}
.replace(/</g, '&lt;')
return safeDecodeURI(this.value);
.replace(/>/g, '&gt;')
}
.replace(/:/g, '&#58;')
;
.replace(/\[/g, '&#91;')
Title.prototype.toUserName = function(withNs) {
.replace(/]/g, '&#93;');
if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) {
}
this.value = null;
function htmlescape_attr(s) {
return;
return htmlescape_text(s).replace(/'/g, '&#39;').replace(/"/g, '&quot;');
}
}
this.value = (withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') + this.stripNamespace().split('/')[0];

}
// return the first non matching character position between two strings
;
function str_imatch(a, b) {
Title.prototype.userName = function(withNs) {
for (var i = 0, l = Math.min(a.length, b.length); i < l; i++) {
var t = new Title(this.value);
if (a.charAt(i) != b.charAt(i)) {
t.toUserName(withNs);
break;
if (t.value) {
}
return t;
}
}
return null;
return i;
}
}

;
// compare current line against a string or regexp
Title.prototype.toTalkPage = function() {
// if passed a string it will compare only the first string.length characters
if (this.value === null) {
// if passed a regexp the result is stored in r
return null;
function compareLineStringOrReg(c) {
}
return typeof c == 'string'
var namespaceId = this.namespaceId();
? ll[0] && ll[0].substr(0, c.length) == c
if (namespaceId >= 0 && namespaceId % 2 === 0) {
: (r = ll[0] && ll[0].match(c));
var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId + 1];
}
if (typeof localizedNamespace !== 'undefined') {

if (localizedNamespace === '') {
function compareLineString(c) {
this.value = this.stripNamespace();
return ll[0] == c;
} else {
} // compare current line against a string
this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
function charAtPoint(p) {
}
return ll[0].charAt(p);
return this.value;
} // return char at pos p
}

}
function endl(s) {
this.value = null;
ps(s);
return null;
sh();
}
}
;

Title.prototype.namespace = function() {
function parse_list() {
return mw.config.get('wgFormattedNamespaces')[this.namespaceId()];
var prev = '';
}

;
while (remain() && compareLineStringOrReg(/^([*#:;]+)(.*)$/)) {
Title.prototype.namespaceId = function() {
var l_match = r;
var n = this.value.indexOf(':');

if (n < 0) {
sh();
return 0;

}
var ipos = str_imatch(prev, l_match[1]);
var namespaceId = mw.config.get('wgNamespaceIds')[this.value.substring(0, n).split(' ').join('_').toLowerCase()];

if (typeof namespaceId == 'undefined') {
// close uncontinued lists
return 0;
for (var prevPos = prev.length - 1; prevPos >= ipos; prevPos--) {
}
var pi = prev.charAt(prevPos);
return namespaceId;

}
if (pi == '*') {
;
ps('</ul>');
Title.prototype.talkPage = function() {
} else if (pi == '#') {
var t = new Title(this.value);
ps('</ol>');
t.toTalkPage();
}
if (t.value) {
return t;
// close a dl only if the new item is not a dl item (:, ; or empty)
else if ($.inArray(l_match[1].charAt(prevPos), ['', '*', '#'])) {
}
ps('</dl>');
return null;
}
}
}
;

Title.prototype.isTalkPage = function() {
// open new lists
if (this.talkPage() === null) {
for (var matchPos = ipos; matchPos < l_match[1].length; matchPos++) {
return true;
var li = l_match[1].charAt(matchPos);
}

return false;
if (li == '*') {
}
ps('<ul>');
;
} else if (li == '#') {
Title.prototype.toArticleFromTalkPage = function() {
ps('<ol>');
if (this.value === null) {
}
return null;
}
// open a new dl only if the prev item is not a dl item (:, ; or empty)
else if ($.inArray(prev.charAt(matchPos), ['', '*', '#'])) {
var namespaceId = this.namespaceId();
ps('<dl>');
if (namespaceId >= 0 && namespaceId % 2 == 1) {
}
var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId - 1];
}
if (typeof localizedNamespace !== 'undefined') {

if (localizedNamespace === '') {
switch (l_match[1].charAt(l_match[1].length - 1)) {
this.value = this.stripNamespace();
case '*':
} else {
case '#':
this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
ps('<li>' + parse_inline_nowiki(l_match[2]));
}
break;
return this.value;

}
case ';':
}
ps('<dt>');
this.value = null;

return null;
var dt_match = l_match[2].match(/(.*?)(:.*?)$/);
}

;
// handle ;dt :dd format
Title.prototype.articleFromTalkPage = function() {
if (dt_match) {
var t = new Title(this.value);
ps(parse_inline_nowiki(dt_match[1]));
t.toArticleFromTalkPage();
ll.unshift(dt_match[2]);
if (t.value) {
} else {
return t;
ps(parse_inline_nowiki(l_match[2]));
}
}
return null;
break;
}

;
case ':':
Title.prototype.articleFromTalkOrArticle = function() {
ps('<dd>' + parse_inline_nowiki(l_match[2]));
var t = new Title(this.value);
}
if (t.toArticleFromTalkPage()) {

return t;
prev = l_match[1];
}
}
return this;

}
// close remaining lists
;
for (var i = prev.length - 1; i >= 0; i--) {
Title.prototype.isIpUser = function() {
ps(f('</?>', prev.charAt(i) == '*' ? 'ul' : prev.charAt(i) == '#' ? 'ol' : 'dl'));
return pg.re.ipUser.test(this.userName());
}
}
}
;

Title.prototype.stripNamespace = function() {
function parse_table() {
var n = this.value.indexOf(':');
endl(f('<table>', compareLineStringOrReg(/^\{\|( .*)$/) ? r[1] : ''));
if (n < 0) {

return this.value;
for (; remain(); ) {
}
if (compareLineStringOrReg('|')) {
var namespaceId = this.namespaceId();
switch (charAtPoint(1)) {
if (namespaceId === pg.nsMainspaceId) {
case '}':
return this.value;
endl('</table>');
}
return;
return this.value.substring(n + 1);
case '-':
}
endl(f('<tr>', compareLineStringOrReg(/\|-*(.*)/)[1]));
;
break;
Title.prototype.setUtf = function(value) {
default:
if (!value) {
parse_table_data();
this.value = '';
}
return;
} else if (compareLineStringOrReg('!')) {
}
parse_table_data();
var anch = value.indexOf('#');
} else {
if (anch < 0) {
sh();
this.value = value.split('_').join(' ');
}
this.anchor = '';
}
return;
}
}

this.value = value.substring(0, anch).split('_').join(' ');
function parse_table_data() {
this.anchor = value.substring(anch + 1);
var td_line, match_i;
this.ns = null;

}
// 1: "|+", '|' or '+'
;
// 2: ??
Title.prototype.setUrl = function(urlfrag) {
// 3: attributes ??
var anch = urlfrag.indexOf('#');
// TODO: finish commenting this regexp
this.value = safeDecodeURI(urlfrag.substring(0, anch));
var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/);
this.anchor = this.value.substring(anch + 1);

}
if (td_match[1] == '|+') {
;
ps('<caption');
Title.prototype.append = function(x) {
} else {
this.setUtf(this.value + x);
ps('<t' + (td_match[1] == '|' ? 'd' : 'h'));
}
}
;

Title.prototype.urlString = function(x) {
if (typeof td_match[3] != 'undefined') {
if (!x) {
//ps(' ' + td_match[3])
x = {};
match_i = 4;
}
} else {
var v = this.toString(true);
match_i = 2;
if (!x.omitAnchor && this.anchor) {
}
v += '#' + this.urlAnchor();

}
ps('>');
if (!x.keepSpaces) {

v = v.split(' ').join('_');
if (td_match[1] != '|+') {
}
// use || or !! as a cell separator depending on context
return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B');
// NOTE: when split() is passed a regexp make sure to use non-capturing brackets
}
td_line = td_match[match_i].split(td_match[1] == '|' ? '||' : /(?:\|\||!!)/);
;

Title.prototype.removeAnchor = function() {
ps(parse_inline_nowiki(td_line.shift()));
return new Title(this.toString(true));

}
while (td_line.length) {
;
ll.unshift(td_match[1] + td_line.pop());
Title.prototype.toUrl = function() {
}
return pg.wiki.titlebase + this.urlString();
} else {
}
ps(parse_inline_nowiki(td_match[match_i]));
;
}
function parseParams(url) {

var specialDiff = pg.re.specialdiff.exec(url);
var tc = 0,
if (specialDiff) {
td = [];
var split = specialDiff[1].split('/');

if (split.length == 1) {
while (remain()) {
return {
td.push(sh());
oldid: split[0],
if (compareLineStringOrReg('|')) {
diff: 'prev'
if (!tc) {
};
break;
} else if (split.length == 2) {
}
return {
// we're at the outer-most level (no nested tables), skip to td parse
oldid: split[0],
else if (charAtPoint(1) == '}') {
diff: split[1]
tc--;
};
}
}
} else if (!tc && compareLineStringOrReg('!')) {
}
break;
var ret = {};
if (url.indexOf('?') == -1) {
} else if (compareLineStringOrReg('{|')) {
tc++;
return ret;
}
}
}
url = url.split('#')[0];

var s = url.split('?').slice(1).join();
if (td.length) {
var t = s.split('&');
ps(Insta.convert(td));
for (var i = 0; i < t.length; ++i) {
}
var z = t[i].split('=');
}
z.push(null);

ret[z[0]] = z[1];
function parse_pre() {
}
ps('<pre>');
if (ret.diff && typeof ret.oldid === 'undefined') {
do {
ret.oldid = 'prev';
endl(parse_inline_nowiki(ll[0].substring(1)) + '\n');
}
} while (remain() && compareLineStringOrReg(' '));
if (ret.oldid && (ret.oldid === 'prev' || ret.oldid === 'next' || ret.oldid === 'cur')) {
ps('</pre>');
var helper = ret.diff;
}
ret.diff = ret.oldid;

ret.oldid = helper;
function parse_block_image() {
}
ps(parse_image(sh()));
return ret;
}
}

function myDecodeURI(str) {
function parse_image(str) {
var ret;
// get what's in between "[[Image:" and "]]"
try {
var tag = str.substring(str.indexOf(':') + 1, str.length - 2);
ret = decodeURI(str.toString());
var width;
} catch (summat) {
var attr = [],
return str;
filename,
}
caption = '';
for (var i = 0; i < pg.misc.decodeExtras.length; ++i) {
var thumb = 0,
var from = pg.misc.decodeExtras[i].from;
frame = 0,
var to = pg.misc.decodeExtras[i].to;
center = 0;
ret = ret.split(from).join(to);
var align = '';
}

return ret;
if (tag.match(/\|/)) {
}
// manage nested links
function safeDecodeURI(str) {
var nesting = 0;
var ret = myDecodeURI(str);
var last_attr;
return ret || str;
}
for (var i = tag.length - 1; i > 0; i--) {
if (tag.charAt(i) == '|' && !nesting) {
function isDisambig(data, article) {
last_attr = tag.substr(i + 1);
if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
tag = tag.substring(0, i);
return false;
break;
}
} else {
return !article.isTalkPage() && pg.re.disambig.test(data);
switch (tag.substr(i - 1, 2)) {
}
case ']]':
function stubCount(data, article) {
nesting++;
if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
i--;
return false;
break;
}
case '[[':
var sectStub = 0;
nesting--;
var realStub = 0;
i--;
if (pg.re.stub.test(data)) {
}
var s = data.parenSplit(pg.re.stub);
}
for (var i = 1; i < s.length; i = i + 2) {
}
if (s[i]) {

++sectStub;
attr = tag.split(/\s*\|\s*/);
} else {
attr.push(last_attr);
++realStub;
filename = attr.shift();
}

}
var w_match;
}

return {
for (; attr.length; attr.shift()) {
real: realStub,
w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/);
sect: sectStub
if (w_match) {
};
width = w_match[1];
}
} else {
function isValidImageName(str) {
switch (attr[0]) {
return str.indexOf('{') == -1;
case 'thumb':
}
case 'thumbnail':
function isInStrippableNamespace(article) {
thumb = true;
return article.namespaceId() !== 0;
frame = true;
}
break;
function isInMainNamespace(article) {
case 'frame':
return article.namespaceId() === 0;
frame = true;
}
break;
function anchorContainsImage(a) {
case 'none':
if (a === null) {
case 'right':
return false;
case 'left':
}
center = false;
var kids = a.childNodes;
align = attr[0];
for (var i = 0; i < kids.length; ++i) {
break;
if (kids[i].nodeName == 'IMG') {
case 'center':
return true;
center = true;
}
align = 'none';
}
break;
return false;
default:
}
if (attr.length == 1) {
function isPopupLink(a) {
caption = attr[0];
if (!markNopopupSpanLinks.done) {
}
markNopopupSpanLinks();
}
}
}
if (a.inNopopupSpan) {
}
return false;
} else {
}
filename = tag;
if (a.onmousedown || a.getAttribute('nopopup')) {
}
return false;

}
return '';
var h = a.href;
}
if (h === document.location.href + '#') {

return false;
function parse_inline_nowiki(str) {
}
var start,
if (!pg.re.basenames.test(h)) {
lastend = 0;
return false;
var substart = 0,
}
nestlev = 0,
if (!pg.re.urlNoPopup.test(h)) {
open,
return true;
close,
}
subloop;
return ((pg.re.email.test(h) || pg.re.contribs.test(h) || pg.re.backlinks.test(h) || pg.re.specialdiff.test(h)) && h.indexOf('&limit=') == -1);
var html = '';
}

function markNopopupSpanLinks() {
while ((start = str.indexOf('<nowiki>', substart)) != -1) {
if (!getValueOf('popupOnlyArticleLinks')) {
html += parse_inline_wiki(str.substring(lastend, start));
fixVectorMenuPopups();
start += 8;
}
substart = start;
var s = $('.nopopups').toArray();
subloop = true;
for (var i = 0; i < s.length; ++i) {
do {
var as = s[i].getElementsByTagName('a');
open = str.indexOf('<nowiki>', substart);
for (var j = 0; j < as.length; ++j) {
close = str.indexOf('</nowiki>', substart);
as[j].inNopopupSpan = true;
if (close <= open || open == -1) {
}
if (close == -1) {
}
return html + html_entities(str.substr(start));
markNopopupSpanLinks.done = true;
}
}
substart = close + 9;
function fixVectorMenuPopups() {
if (nestlev) {
$('nav.vector-menu h3:first a:first').prop('inNopopupSpan', true);
nestlev--;
}
} else {
function getPageWithCaching(url, onComplete, owner) {
lastend = substart;
log('getPageWithCaching, url=' + url);
html += html_entities(str.substring(start, lastend - 9));
var i = findInPageCache(url);
subloop = false;
var d;
}
if (i > -1) {
} else {
d = fakeDownload(url, owner.idNumber, onComplete, pg.cache.pages[i].data, pg.cache.pages[i].lastModified, owner);
substart = open + 8;
} else {
nestlev++;
d = getPage(url, onComplete, owner);
}
if (d && owner && owner.addDownload) {
} while (subloop);
owner.addDownload(d);
}
d.owner = owner;

}
return html + parse_inline_wiki(str.substr(lastend));
}
}
}

function getPage(url, onComplete, owner) {
function parse_inline_images(str) {
log('getPage');
var start,
var callback = function(d) {
substart = 0,
if (!d.aborted) {
nestlev = 0;
addPageToCache(d);
var loop, close, open, wiki, html;
onComplete(d);

}
while ((start = str.indexOf('[[', substart)) != -1) {
};
if (
return startDownload(url, owner.idNumber, callback);
str.substr(start + 2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):', 'i'))
}
) {
function findInPageCache(url) {
loop = true;
for (var i = 0; i < pg.cache.pages.length; ++i) {
substart = start;
if (url == pg.cache.pages[i].url) {
do {
return i;
substart += 2;
}
close = str.indexOf(']]', substart);
}
open = str.indexOf('[[', substart);
return -1;
if (close <= open || open == -1) {
}
if (close == -1) {
function addPageToCache(download) {
return str;
log('addPageToCache ' + download.url);
}
var page = {
substart = close;
url: download.url,
if (nestlev) {
data: download.data,
nestlev--;
lastModified: download.lastModified,
} else {
};
wiki = str.substring(start, close + 2);
return pg.cache.pages.push(page);
html = parse_image(wiki);
}
str = str.replace(wiki, html);
if (String('abc'.split(/(b)/)) != 'a,b,c') {
substart = start + html.length;
String.prototype.parenSplit = function(re) {
loop = false;
re = nonGlobalRegex(re);
}
var s = this;
} else {
var m = re.exec(s);
substart = open;
var ret = [];
nestlev++;
while (m && s) {
}
for (var i = 0; i < m.length; ++i) {
} while (loop);
if (typeof m[i] == 'undefined') {
} else {
m[i] = '';
break;
}
}
}
}
ret.push(s.substring(0, m.index));

ret = ret.concat(m.slice(1));
return str;
s = s.substring(m.index + m[0].length);
}
m = re.exec(s);

}
// the output of this function doesn't respect the FILO structure of HTML
ret.push(s);
// but since most browsers can handle it I'll save myself the hassle
return ret;
function parse_inline_formatting(str) {
}
var em,
;
st,
} else {
i,
String.prototype.parenSplit = function(re) {
li,
return this.split(re);
o = '';
}
while ((i = str.indexOf("''", li)) + 1) {
;
o += str.substring(li, i);
String.prototype.parenSplit.isNative = true;
li = i + 2;
}
if (str.charAt(i + 2) == "'") {
function nonGlobalRegex(re) {
li++;
var s = re.toString();
st = !st;
var flags = '';
o += st ? '<strong>' : '</strong>';
for (var j = s.length; s.charAt(j) != '/'; --j) {
} else {
if (s.charAt(j) != 'g') {
em = !em;
flags += s.charAt(j);
o += em ? '<em>' : '</em>';
}
}
}
}
var t = s.substring(1, j);
return o + str.substr(li);
return RegExp(t, flags);
}
}

function getJsObj(json) {
function parse_inline_wiki(str) {
try {
str = parse_inline_images(str);
var json_ret = JSON.parse(json);

if (json_ret.warnings) {
// math
for (var w = 0; w < json_ret.warnings.length; w++) {
str = str.replace(/<(?:)math>(.*?)<\/math>/gi, '');
if (json_ret.warnings[w]['*']) {

log(json_ret.warnings[w]['*']);
// Build a Mediawiki-formatted date string
} else {
var date = new Date();
log(json_ret.warnings[w].warnings);
var minutes = date.getUTCMinutes();
}
if (minutes < 10) {
}
minutes = '0' + minutes;
} else if (json_ret.error) {
}
errlog(json_ret.error.code + ': ' + json_ret.error.info);
date = f(
}
'?:?, ? ? ? (UTC)',
return json_ret;
date.getUTCHours(),
} catch (someError) {
minutes,
errlog('Something went wrong with getJsObj, json=' + json);
date.getUTCDate(),
return 1;
Insta.conf.locale.months[date.getUTCMonth()],
}
date.getUTCFullYear()
}
);
function anyChild(obj) {

for (var p in obj) {
// text formatting
return obj[p];
str =
}
str
return null;
// signatures
}
.replace(/~{5}(?!~)/g, date)
function upcaseFirst(str) {
.replace(/~{4}(?!~)/g, Insta.conf.user.name + ' ' + date)
if (typeof str != typeof '' || str === '') {
.replace(/~{3}(?!~)/g, Insta.conf.user.name)
return '';
// [[:Category:...]], [[:Image:...]], etc...
}
.replace(
return str.charAt(0).toUpperCase() + str.substring(1);
RegExp(
}
'\\[\\[:((?:' +
function findInArray(arr, foo) {
Insta.conf.locale.category +
if (!arr || !arr.length) {
'|Image|File|' +
return -1;
Insta.conf.locale.image +
}
'|' +
var len = arr.length;
Insta.conf.wiki.interwiki +
for (var i = 0; i < len; ++i) {
'):[^|]*?)\\]\\](\\w*)',
if (arr[i] == foo) {
'gi'
return i;
),
}
function ($0, $1, $2) {
}
return -1;
return f(
"<a href='?'>?</a>",
}
Insta.conf.paths.articles + htmlescape_attr($1),
function nextOne(array, value) {
htmlescape_text($1) + htmlescape_text($2)
var i = findInArray(array, value);
);
if (i < 0) {
}
return null;
)
}
// remove straight category and interwiki tags
return array[i + 1];
.replace(
}
RegExp(
function literalizeRegex(str) {
'\\[\\[(?:' +
return mw.util.escapeRegExp(str);
Insta.conf.locale.category +
}
'|' +
String.prototype.entify = function() {
Insta.conf.wiki.interwiki +
return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;').split('"').join('&quot;');
'):.*?\\]\\]',
}
'gi'
;
),
function removeNulls(val) {
''
return val !== null;
)
}
// [[:Category:...|Links]], [[:Image:...|Links]], etc...
function joinPath(list) {
.replace(
return list.filter(removeNulls).join('/');
RegExp(
}
'\\[\\[:((?:' +
function simplePrintf(str, subs) {
Insta.conf.locale.category +
if (!str || !subs) {
'|Image|File|' +
return str;
Insta.conf.locale.image +
}
'|' +
var ret = [];
Insta.conf.wiki.interwiki +
var s = str.parenSplit(/(%s|\$[0-9]+)/);
'):.*?)\\|([^\\]]+?)\\]\\](\\w*)',
var i = 0;
'gi'
do {
),
ret.push(s.shift());
function ($0, $1, $2, $3) {
if (!s.length) {
return f(
break;
"<a href='?'>?</a>",
}
Insta.conf.paths.articles + htmlescape_attr($1),
var cmd = s.shift();
htmlescape_text($2) + htmlescape_text($3)
if (cmd == '%s') {
);
if (i < subs.length) {
}
ret.push(subs[i]);
)
} else {
// [[/Relative links]]
ret.push(cmd);
.replace(/\[\[(\/[^|]*?)\]\]/g, function ($0, $1) {
}
return f(
++i;
"<a href='?'>?</a>",
} else {
Insta.conf.baseUrl + htmlescape_attr($1),
var j = parseInt(cmd.replace('$', ''), 10) - 1;
htmlescape_text($1)
if (j > -1 && j < subs.length) {
);
ret.push(subs[j]);
})
} else {
// [[/Replaced|Relative links]]
ret.push(cmd);
.replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function ($0, $1, $2) {
}
return f(
}
"<a href='?'>?</a>",
} while (s.length > 0);
Insta.conf.baseUrl + htmlescape_attr($1),
return ret.join('');
htmlescape_text($2)
}
);
function isString(x) {
})
return typeof x === 'string' || x instanceof String;
// [[Common links]]
}
.replace(/\[\[([^[|]*?)\]\](\w*)/g, function ($0, $1, $2) {
function isNumber(x) {
return f(
return typeof x === 'number' || x instanceof Number;
"<a href='?'>?</a>",
}
Insta.conf.paths.articles + htmlescape_attr($1),
function isRegExp(x) {
htmlescape_text($1) + htmlescape_text($2)
return x instanceof RegExp;
);
}
})
function isArray(x) {
// [[Replaced|Links]]
return x instanceof Array;
.replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function ($0, $1, $2, $3) {
}
return f(
function isObject(x) {
"<a href='?'>?</a>",
return x instanceof Object;
Insta.conf.paths.articles + htmlescape_attr($1),
}
htmlescape_text($2) + htmlescape_text($3)
function isFunction(x) {
);
return !isRegExp(x) && (typeof x === 'function' || x instanceof Function);
})
}
// [[Stripped:Namespace|Namespace]]
function repeatString(s, mult) {
.replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function ($0, $1, $2, $3) {
var ret = '';
return f(
for (var i = 0; i < mult; ++i) {
"<a href='?'>?</a>",
ret += s;
Insta.conf.paths.articles +
}
htmlescape_attr($1) +
return ret;
htmlescape_attr($2) +
}
htmlescape_attr($3),
function zeroFill(s, min) {
htmlescape_text($2)
min = min || 2;
);
var t = s.toString();
})
return repeatString('0', min - t.length) + t;
// External links
}
.replace(
function map(f, o) {
/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g,
if (isArray(o)) {
function ($0, $1, $2, $3, $4) {
return map_array(f, o);
return f(
}
"<a class='external' href='?:?'>?</a>",
return map_object(f, o);
htmlescape_attr($1),
}
htmlescape_attr($2) + htmlescape_attr($3),
function map_array(f, o) {
htmlescape_text($4)
var ret = [];
);
for (var i = 0; i < o.length; ++i) {
}
ret.push(f(o[i]));
)
}
.replace(/\[http:\/\/(.*?)\]/g, function ($0, $1) {
return ret;
return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1));
}
})
function map_object(f, o) {
.replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function ($0, $1, $2, $3) {
var ret = {};
return f(
for (var i in o) {
"<a class='external' href='?:?'>?:?</a>",
ret[o] = f(o[i]);
htmlescape_attr($1),
}
htmlescape_attr($2) + htmlescape_attr($3),
return ret;
htmlescape_text($1),
}
htmlescape_text($2) + htmlescape_text($3)
pg.escapeQuotesHTML = function(text) {
);
return text.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
})
}
.replace(
;
/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g,
pg.unescapeQuotesHTML = function(html) {
function ($0, $1, $2, $3, $4) {
var txt = document.createElement('textarea');
return f(
txt.innerHTML = html;
"?<a class='external' href='?:?'>?:?</a>",
return txt.value;
htmlescape_text($1),
}
htmlescape_attr($2),
;
htmlescape_attr($3) + htmlescape_attr($4),
function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) {
htmlescape_text($2),
log('retargetDab: newTarget=' + newTarget + ' oldTarget=' + oldTarget);
htmlescape_text($3) + htmlescape_text($4)
return changeLinkTargetLink({
);
newTarget: newTarget,
}
text: newTarget.split(' ').join('&nbsp;'),
)
hint: tprintf('disambigHint', [newTarget]),
.replace('__NOTOC__', '')
summary: simplePrintf(getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget, ]),
.replace('__NOINDEX__', '')
clickButton: getValueOf('popupDabsAutoClick'),
.replace('__INDEX__', '')
minor: true,
.replace('__NOEDITSECTION__', '')
oldTarget: oldTarget,
;
watch: getValueOf('popupWatchDisambiggedPages'),
return parse_inline_formatting(str);
title: titleToEdit,
}
});

}
// begin parsing
function listLinks(wikitext, oldTarget, titleToEdit) {
for (; remain(); ) {
var reg = RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi');
if (compareLineStringOrReg(/^(={1,6})(.*)\1(.*)$/)) {
var ret = [];
p = 0;
var splitted = wikitext.parenSplit(reg);
endl(f('<h?>?</h?>?', r[1].length, parse_inline_nowiki(r[2]), r[1].length, r[3]));
var omitRegex = RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory');
} else if (compareLineStringOrReg(/^[*#:;]/)) {
var friendlyCurrentArticleName = oldTarget.toString();
p = 0;
var wikPos = getValueOf('popupDabWiktionary');
parse_list();
for (var i = 1; i < splitted.length; i = i + 3) {
} else if (compareLineStringOrReg(' ')) {
if (typeof splitted[i] == typeof 'string' && splitted[i].length > 0 && !omitRegex.test(splitted[i])) {
p = 0;
ret.push(retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit));
parse_pre();
}
} else if (compareLineStringOrReg('{|')) {
}
p = 0;
ret = rmDupesFromSortedList(ret.sort());
parse_table();
if (wikPos) {
} else if (compareLineStringOrReg(/^----+$/)) {
var wikTarget = 'wiktionary:' + friendlyCurrentArticleName.replace(RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1');
p = 0;
var meth;
endl('<hr />');
if (wikPos.toLowerCase() == 'first') {
} else if (compareLineStringOrReg(Insta.BLOCK_IMAGE)) {
meth = 'unshift';
p = 0;
} else {
parse_block_image();
meth = 'push';
} else {
}
// handle paragraphs
ret[meth](retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit));
if (compareLineString('')) {
}
p = remain() > 1 && ll[1] === '';
ret.push(changeLinkTargetLink({
if (p) {
newTarget: null,
endl('<p><br>');
text: popupString('remove this link').split(' ').join('&nbsp;'),
}
hint: popupString('remove all links to this disambig page from this article'),
} else {
clickButton: getValueOf('popupDabsAutoClick'),
if (!p) {
oldTarget: oldTarget,
ps('<p>');
summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]),
p = 1;
watch: getValueOf('popupWatchDisambiggedPages'),
}
title: titleToEdit,
ps(parse_inline_nowiki(ll[0]) + ' ');
}));
}
return ret;

}
sh();
function rmDupesFromSortedList(list) {
}
var ret = [];
}
for (var i = 0; i < list.length; ++i) {

if (ret.length === 0 || list[i] != ret[ret.length - 1]) {
return o;
ret.push(list[i]);
};
}

}
function wiki2html(txt, baseurl) {
return ret;
Insta.conf.baseUrl = baseurl;
}
return Insta.convert(txt);
function makeFixDab(data, navpop) {
}
var titleToEdit = navpop.parentPopup && navpop.parentPopup.article.toString();
// ENDFILE: livepreview.js
var list = listLinks(data, navpop.originalArticle, titleToEdit);

if (list.length === 0) {
// STARTFILE: pageinfo.js
log('listLinks returned empty list');
function popupFilterPageSize(data) {
return null;
return formatBytes(data.length);
}
}
var html = '<hr />' + popupString('Click to disambiguate this link to:') + '<br>';

html += list.join(', ');
function popupFilterCountLinks(data) {
return html;
var num = countLinks(data);
}
return String(num) + '&nbsp;' + (num != 1 ? popupString('wikiLinks') : popupString('wikiLink'));
function makeFixDabs(wikiText, navpop) {
}
if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) && Title.fromURL(location.href).namespaceId() != pg.nsSpecialId && navpop.article.talkPage()) {

setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber);
function popupFilterCountImages(data) {
}
var num = countImages(data);
}
return String(num) + '&nbsp;' + (num != 1 ? popupString('images') : popupString('image'));
function popupRedlinkHTML(article) {
}
return changeLinkTargetLink({

newTarget: null,
function popupFilterCountCategories(data) {
text: popupString('remove this link').split(' ').join('&nbsp;'),
var num = countCategories(data);
hint: popupString('remove all links to this page from this article'),
return (
clickButton: getValueOf('popupRedlinkAutoClick'),
String(num) + '&nbsp;' + (num != 1 ? popupString('categories') : popupString('category'))
oldTarget: article.toString(),
);
summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()]),
}
});

}
function popupFilterLastModified(data, download) {
function setPopupHTML(str, elementId, popupId, onSuccess, append) {
var lastmod = download.lastModified;
if (typeof popupId === 'undefined') {
var now = new Date();
popupId = pg.idNumber;
var age = now - lastmod;
}
if (lastmod && getValueOf('popupLastModified')) {
var popupElement = document.getElementById(elementId + popupId);
return tprintf('%s old', [formatAge(age)]).replace(RegExp(' ', 'g'), '&nbsp;');
if (popupElement) {
}
if (!append) {
return '';
popupElement.innerHTML = '';
}
}

if (isString(str)) {
function popupFilterWikibaseItem(data, download) {
popupElement.innerHTML += str;
return download.wikibaseItem
} else {
? tprintf('<a href="%s">%s</a>', [
popupElement.appendChild(str);
download.wikibaseRepo.replace(/\$1/g, download.wikibaseItem),
}
download.wikibaseItem,
if (onSuccess) {
])
onSuccess();
: '';
}
}
setTimeout(checkPopupPosition, 100);

return true;
function formatAge(age) {
} else {
// coerce into a number
setTimeout(function() {
var a = 0 + age,
setPopupHTML(str, elementId, popupId, onSuccess);
aa = a;
}, 600);

}
var seclen = 1000;
return null;
var minlen = 60 * seclen;
}
var hourlen = 60 * minlen;
function setPopupTrailer(str, id) {
var daylen = 24 * hourlen;
return setPopupHTML(str, 'popupData', id);
var weeklen = 7 * daylen;
}

function fillEmptySpans(args) {
var numweeks = (a - (a % weeklen)) / weeklen;
var redir = true;
a = a - numweeks * weeklen;
var rcid;
var sweeks = addunit(numweeks, 'week');
if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) {
var numdays = (a - (a % daylen)) / daylen;
redir = false;
a = a - numdays * daylen;
}
var sdays = addunit(numdays, 'day');
var a = args.navpopup.parentAnchor;
var numhours = (a - (a % hourlen)) / hourlen;
var article, hint = null, oldid = null, params = {};
a = a - numhours * hourlen;
if (redir && typeof args.redirTarget == typeof {}) {
var shours = addunit(numhours, 'hour');
article = args.redirTarget;
var nummins = (a - (a % minlen)) / minlen;
} else {
a = a - nummins * minlen;
article = new Title().fromAnchor(a);
var smins = addunit(nummins, 'minute');
hint = a.originalTitle || article.hintValue();
var numsecs = (a - (a % seclen)) / seclen;
params = parseParams(a.href);
a = a - numsecs * seclen;
oldid = getValueOf('popupHistoricalLinks') ? params.oldid : null;
var ssecs = addunit(numsecs, 'second');
rcid = params.rcid;

}
if (aa > 4 * weeklen) {
var x = {
return sweeks;
article: article,
}
hint: hint,
if (aa > weeklen) {
oldid: oldid,
return sweeks + ' ' + sdays;
rcid: rcid,
}
navpop: args.navpopup,
if (aa > daylen) {
params: params,
return sdays + ' ' + shours;
};
}
var structure = pg.structures[getValueOf('popupStructure')];
if (aa > 6 * hourlen) {
if (typeof structure != 'object') {
return shours;
setPopupHTML('popupError', 'Unknown structure (this should never happen): ' + pg.option.popupStructure, args.navpopup.idNumber);
}
return;
if (aa > hourlen) {
}
return shours + ' ' + smins;
var spans = flatten(pg.misc.layout);
}
var numspans = spans.length;
if (aa > 10 * minlen) {
var redirs = pg.misc.redirSpans;
return smins;
for (var i = 0; i < numspans; ++i) {
}
var found = redirs && redirs.indexOf(spans[i]) !== -1;
if (aa > minlen) {
if ((found && !redir) || (!found && redir)) {
return smins + ' ' + ssecs;
continue;
}
}
return ssecs;
var structurefn = structure[spans[i]];
}
if (structurefn === undefined) {

continue;
function addunit(num, str) {
}
return String(num) + ' ' + (num != 1 ? popupString(str + 's') : popupString(str));
var setfn = setPopupHTML;
}
if (getValueOf('popupActiveNavlinks') && (spans[i].indexOf('popupTopLinks') === 0 || spans[i].indexOf('popupRedirTopLinks') === 0)) {

setfn = setPopupTipsAndHTML;
function runPopupFilters(list, data, download) {
}
var ret = [];
switch (typeof structurefn) {
for (var i = 0; i < list.length; ++i) {
case 'function':
if (list[i] && typeof list[i] == 'function') {
log('running ' + spans[i] + '({article:' + x.article + ', hint:' + x.hint + ', oldid: ' + x.oldid + '})');
var s = list[i](data, download, download.owner.article);
setfn(structurefn(x), spans[i], args.navpopup.idNumber);
if (s) {
break;
ret.push(s);
case 'string':
}
setfn(structurefn, spans[i], args.navpopup.idNumber);
}
break;
}
default:
return ret;
errlog('unknown thing with label ' + spans[i] + ' (span index was ' + i + ')');
}
break;

}
function getPageInfo(data, download) {
}
if (!data || data.length === 0) {
}
return popupString('Empty page');
function flatten(list, start) {
}
var ret = [];

if (typeof start == 'undefined') {
var popupFilters = getValueOf('popupFilters') || [];
start = 0;
var extraPopupFilters = getValueOf('extraPopupFilters') || [];
}
var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download);
for (var i = start; i < list.length; ++i) {

if (typeof list[i] == typeof []) {
var pageInfo = pageInfoArray.join(', ');
return ret.concat(flatten(list[i])).concat(flatten(list, i + 1));
if (pageInfo !== '') {
} else {
pageInfo = upcaseFirst(pageInfo);
ret.push(list[i]);
}
}
return pageInfo;
}
}
return ret;

}
// this could be improved!
function popupHTML(a) {
function countLinks(wikiText) {
getValueOf('popupStructure');
return wikiText.split('[[').length - 1;
var structure = pg.structures[pg.option.popupStructure];
}
if (typeof structure != 'object') {

pg.option.popupStructure = pg.optionDefault.popupStructure;
// if N = # matches, n = # brackets, then
return popupHTML(a);
// String.parenSplit(regex) intersperses the N+1 split elements
}
// with Nn other elements. So total length is
if (typeof structure.popupLayout != 'function') {
// L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1).
return 'Bad layout';

}
function countImages(wikiText) {
pg.misc.layout = structure.popupLayout();
return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1);
if (typeof structure.popupRedirSpans === 'function') {
}
pg.misc.redirSpans = structure.popupRedirSpans();

} else {
function countCategories(wikiText) {
pg.misc.redirSpans = [];
return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1);
}
}
return makeEmptySpans(pg.misc.layout, a.navpopup);

}
function makeEmptySpans(list, navpop) {
function popupFilterStubDetect(data, download, article) {
var counts = stubCount(data, article);
var ret = '';
if (counts.real) {
for (var i = 0; i < list.length; ++i) {
return popupString('stub');
if (typeof list[i] == typeof '') {
}
ret += emptySpanHTML(list[i], navpop.idNumber, 'div');
if (counts.sect) {
} else if (typeof list[i] == typeof [] && list[i].length > 0) {
return popupString('section stub');
ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop));
}
} else if (typeof list[i] == typeof {} && list[i].nodeType) {
return '';
ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType);
}
}

}
function popupFilterDisambigDetect(data, download, article) {
return ret;
if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
}
return '';
function emptySpanHTML(name, id, tag, classname) {
}
tag = tag || 'span';
return isDisambig(data, article) ? popupString('disambig') : '';
if (!classname) {
}
classname = emptySpanHTML.classAliases[name];

}
function formatBytes(num) {
classname = classname || name;
return num > 949
if (name == getValueOf('popupDragHandle')) {
? Math.round(num / 100) / 10 + popupString('kB')
classname += ' popupDragHandle';
: num + '&nbsp;' + popupString('bytes');
}
}
return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]);
// ENDFILE: pageinfo.js
}

emptySpanHTML.classAliases = {
// STARTFILE: titles.js
popupSecondPreview: 'popupPreview'
/**
};
* @fileoverview Defines the {@link Title} class, and associated crufty functions.
function imageHTML(article, idNumber) {

return simplePrintf('<a id="popupImageLink$1">' + '<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' + '</a>', [idNumber]);
* <code>Title</code> deals with article titles and their various
}
* forms. {@link Stringwrapper} is the parent class of
function popTipsSoonFn(id, when, popData) {
* <code>Title</code>, which exists simply to make things a little
if (!when) {
* neater.
when = 250;
*/
}

var popTips = function() {
/**
setupTooltips(document.getElementById(id), false, true, popData);
* Creates a new Stringwrapper.
};
* @constructor
return function() {

setTimeout(popTips, when, popData);
* @class the Stringwrapper class. This base class is not really
}
* useful on its own; it just wraps various common string operations.
;
*/
}
function Stringwrapper() {
function setPopupTipsAndHTML(html, divname, idnumber, popData) {
/**
setPopupHTML(html, divname, idnumber, getValueOf('popupSubpopups') ? popTipsSoonFn(divname + idnumber, null, popData) : null);
* Wrapper for this.toString().indexOf()
}
* @param {String} x
function fuzzyCursorOffMenus(x, y, fuzz, parent) {
* @type {number}
if (!parent) {
*/
return null;
this.indexOf = function (x) {
}
return this.toString().indexOf(x);
var uls = parent.getElementsByTagName('ul');
};
for (var i = 0; i < uls.length; ++i) {
/**
if (uls[i].className == 'popup_menu') {
* Returns this.value.
if (uls[i].offsetWidth > 0) {
* @type {string}
return false;
*/
}
this.toString = function () {
}
return this.value;
}
};
return true;
/**
}
* Wrapper for {@link String#parenSplit} applied to this.toString()
function checkPopupPosition() {
* @param {RegExp} x
if (pg.current.link && pg.current.link.navpopup) {
* @type {Array}
pg.current.link.navpopup.limitHorizontalPosition();
*/
}
this.parenSplit = function (x) {
}
return this.toString().parenSplit(x);
function mouseOutWikiLink() {
};
var a = this;
/**
removeModifierKeyHandler(a);
* Wrapper for this.toString().substring()
if (a.navpopup === null || typeof a.navpopup === 'undefined') {
* @param {String} x
return;
* @param {String} y (optional)
}
* @type {string}
if (!a.navpopup.isVisible()) {
*/
a.navpopup.banish();
this.substring = function (x, y) {
return;
if (typeof y == 'undefined') {
}
return this.toString().substring(x);
restoreTitle(a);
}
Navpopup.tracker.addHook(posCheckerHook(a.navpopup));
return this.toString().substring(x, y);
}
};
function posCheckerHook(navpop) {
/**
return function() {
* Wrapper for this.toString().split()
if (!navpop.isVisible()) {
* @param {String} x
return true;
* @type {Array}
}
*/
if (Navpopup.tracker.dirty) {
this.split = function (x) {
return false;
return this.toString().split(x);
}
};
var x = Navpopup.tracker.x
/**
, y = Navpopup.tracker.y;
* Wrapper for this.toString().replace()
var mouseOverNavpop = navpop.isWithin(x, y, navpop.fuzz, navpop.mainDiv) || !fuzzyCursorOffMenus(x, y, navpop.fuzz, navpop.mainDiv);
* @param {String} x
var t = getValueOf('popupHideDelay');
* @param {String} y
if (t) {
* @type {string}
t = t * 1000;
*/
}
this.replace = function (x, y) {
if (!t) {
return this.toString().replace(x, y);
if (!mouseOverNavpop) {
};
if (navpop.parentAnchor) {
}
restoreTitle(navpop.parentAnchor);

}
/**
navpop.banish();
* Creates a new <code>Title</code>.
return true;
* @constructor
}
*
return false;
* @class The Title class. Holds article titles and converts them into
}
* various forms. Also deals with anchors, by which we mean the bits
var d = Number(new Date());
* of the article URL after a # character, representing locations
if (!navpop.mouseLeavingTime) {
* within an article.
navpop.mouseLeavingTime = d;
*
return false;
* @param {String} value The initial value to assign to the
}
* article. This must be the canonical title (see {@link
if (mouseOverNavpop) {
* Title#value}. Omit this in the constructor and use another function
navpop.mouseLeavingTime = null;
* to set the title if this is unavailable.
return false;
*/
}
function Title(val) {
if (d - navpop.mouseLeavingTime > t) {
/**
navpop.mouseLeavingTime = null;
* The canonical article title. This must be in UTF-8 with no
navpop.banish();
* entities, escaping or nasties. Also, underscores should be
return true;
* replaced with spaces.
}
* @type {string}
return false;
* @private
}
*/
;
this.value = null;
}

function runStopPopupTimer(navpop) {
/**
if (!navpop.stopPopupTimer) {
* The canonical form of the anchor. This should be exactly as
navpop.stopPopupTimer = setInterval(posCheckerHook(navpop), 500);
* it appears in the URL, i.e. with the .C3.0A bits in.
navpop.addHook(function() {
* @type {string}
clearInterval(navpop.stopPopupTimer);
*/
}, 'hide', 'before');
this.anchor = '';
}

}
this.setUtf(val);
function Previewmaker(wikiText, baseUrl, owner) {
}
this.originalData = wikiText;
Title.prototype = new Stringwrapper();
this.baseUrl = baseUrl;
/**
this.owner = owner;
* Returns the canonical representation of the article title, optionally without anchor.
this.maxCharacters = getValueOf('popupMaxPreviewCharacters');
* @param {boolean} omitAnchor
this.maxSentences = getValueOf('popupMaxPreviewSentences');
* @fixme Decide specs for anchor
this.setData();
* @return String The article title and the anchor.
}
*/
Previewmaker.prototype.setData = function() {
Title.prototype.toString = function (omitAnchor) {
var maxSize = Math.max(10000, 2 * this.maxCharacters);
return this.value + (!omitAnchor && this.anchor ? '#' + this.anchorString() : '');
this.data = this.originalData.substring(0, maxSize);
};
}
Title.prototype.anchorString = function () {
;
if (!this.anchor) {
Previewmaker.prototype.killComments = function() {
return '';
this.data = this.data.replace(RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), '');
}
}
var split = this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/);
;
var len = split.length;
Previewmaker.prototype.killDivs = function() {
var value;
this.data = this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>', 'gi'), '');
}
for (var j = 1; j < len; j += 2) {
// FIXME s/decodeURI/decodeURIComponent/g ?
;
value = split[j].split('.').join('%');
Previewmaker.prototype.killGalleries = function() {
try {
this.data = this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>', 'gi'), '');
value = decodeURIComponent(value);
}
} catch (e) {
;
// cannot decode
Previewmaker.prototype.kill = function(opening, closing, subopening, subclosing, repl) {
}
var oldk = this.data;
split[j] = value.split('_').join(' ');
var k = this.killStuff(this.data, opening, closing, subopening, subclosing, repl);
}
while (k.length < oldk.length) {
return split.join('');
oldk = k;
};
k = this.killStuff(k, opening, closing, subopening, subclosing, repl);
Title.prototype.urlAnchor = function () {
}
var split = this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/');
this.data = k;
var len = split.length;
}
;
for (var j = 1; j < len; j += 2) {
split[j] = split[j].split('%').join('.');
Previewmaker.prototype.killStuff = function(txt, opening, closing, subopening, subclosing, repl) {
}
var op = this.makeRegexp(opening);
return split.join('');
var cl = this.makeRegexp(closing, '^');
};
var sb = subopening ? this.makeRegexp(subopening, '^') : null;
Title.prototype.anchorFromUtf = function (str) {
var sc = subclosing ? this.makeRegexp(subclosing, '^') : cl;
this.anchor = encodeURIComponent(str.split(' ').join('_'))
if (!op || !cl) {
.split('%3A')
alert('Navigation Popups error: op or cl is null! something is wrong.');
.join(':')
return;
.split("'")
}
.join('%27')
if (!op.test(txt)) {
.split('%')
return txt;
.join('.');
}
};
var ret = '';
Title.fromURL = function (h) {
var opResult = op.exec(txt);
return new Title().fromURL(h);
ret = txt.substring(0, opResult.index);
};
txt = txt.substring(opResult.index + opResult[0].length);
Title.prototype.fromURL = function (h) {
var depth = 1;
if (typeof h != 'string') {
while (txt.length > 0) {
this.value = null;
var removal = 0;
return this;
if (depth == 1 && cl.test(txt)) {
}
depth--;

removal = cl.exec(txt)[0].length;
// NOTE : playing with decodeURI, encodeURI, escape, unescape,
} else if (depth > 1 && sc.test(txt)) {
// we seem to be able to replicate the IE borked encoding
depth--;

removal = sc.exec(txt)[0].length;
// IE doesn't do this new-fangled utf-8 thing.
} else if (sb && sb.test(txt)) {
// and it's worse than that.
depth++;
// IE seems to treat the query string differently to the rest of the url
removal = sb.exec(txt)[0].length;
// the query is treated as bona-fide utf8, but the first bit of the url is pissed around with
}

if (!removal) {
// we fix up & for all browsers, just in case.
removal = 1;
var splitted = h.split('?');
}
splitted[0] = splitted[0].split('&').join('%26');
txt = txt.substring(removal);

if (depth === 0) {
h = splitted.join('?');
break;

}
var contribs = pg.re.contribs.exec(h);
}
if (contribs) {
return ret + (repl || '') + txt;
if (contribs[1] == 'title=') {
}
contribs[3] = contribs[3].split('+').join(' ');
;
}
Previewmaker.prototype.makeRegexp = function(x, prefix, suffix) {
var u = new Title(contribs[3]);
prefix = prefix || '';
this.setUtf(
suffix = suffix || '';
this.decodeNasties(
var reStr = '';
mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace()
var flags = '';
)
if (isString(x)) {
);
reStr = prefix + literalizeRegex(x) + suffix;
return this;
} else if (isRegExp(x)) {
}
var s = x.toString().substring(1);

var sp = s.split('/');
var email = pg.re.email.exec(h);
flags = sp[sp.length - 1];
if (email) {
sp[sp.length - 1] = '';
this.setUtf(
s = sp.join('/');
this.decodeNasties(
s = s.substring(0, s.length - 1);
mw.config.get('wgFormattedNamespaces')[pg.nsUserId] +
reStr = prefix + s + suffix;
':' +
} else {
new Title(email[3]).stripNamespace()
log('makeRegexp failed');
)
}
);
log('makeRegexp: got reStr=' + reStr + ', flags=' + flags);
return this;
return RegExp(reStr, flags);
}
}

;
var backlinks = pg.re.backlinks.exec(h);
Previewmaker.prototype.killBoxTemplates = function() {
if (backlinks) {
this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{');
this.setUtf(this.decodeNasties(new Title(backlinks[3])));
this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{');
return this;
}
}
;

Previewmaker.prototype.killTemplates = function() {
//A dummy title object for a Special:Diff link.
this.kill('{{', '}}', '{', '}', ' ');
var specialdiff = pg.re.specialdiff.exec(h);
}
if (specialdiff) {
;
this.setUtf(
Previewmaker.prototype.killTables = function() {
this.decodeNasties(
this.kill('{|', /[|]}\s*/, '{|');
new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff')
this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i);
)
this.data = this.data.replace(RegExp('^[|].*$', 'mg'), '');
);
}
return this;
;
}
Previewmaker.prototype.killImages = function() {

var forbiddenNamespaceAliases = [];
// no more special cases to check --
jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
// hopefully it's not a disguised user-related or specially treated special page
if (_namespaceId != pg.nsImageId && _namespaceId != pg.nsCategoryId) {
// Includes references
return;
var m = pg.re.main.exec(h);
}
if (m === null) {
forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]'));
this.value = null;
});
} else {
this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'), /\]\]\s*/, '[', ']');
var fromBotInterface = /[?](.+[&])?title=/.test(h);
}
if (fromBotInterface) {
;
m[2] = m[2].split('+').join('_');
Previewmaker.prototype.killHTML = function() {
}
this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i);
var extracted = m[2] + (m[3] ? '#' + m[3] : '');
this.data = this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n');
if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) {
var splitted = this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/);
// Fix Safari issue
var len = splitted.length;
// Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3.
for (var i = 1; i < len; i = i + 2) {
this.setUtf(decodeURIComponent(unescape(extracted)));
switch (splitted[i]) {
} else {
case '<nowiki>':
this.setUtf(this.decodeNasties(extracted));
case '</nowiki>':
}
case '<blockquote>':
}
case '</blockquote>':
return this;
break;
};
default:
Title.prototype.decodeNasties = function (txt) {
splitted[i] = '';
// myDecodeURI uses decodeExtras, which removes _,
}
// thus ruining citations previews, which are formated as "cite_note-1"
}
try {
this.data = splitted.join('');
var ret = decodeURI(this.decodeEscapes(txt));
}
ret = ret.replace(/[_ ]*$/, '');
;
return ret;
Previewmaker.prototype.killChunks = function() {
} catch (e) {
var italicChunkRegex = new RegExp("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+",'g');
return txt; // cannot decode
this.data = this.data.replace(italicChunkRegex, '\n');
}
}
};
;
// Decode valid %-encodings, otherwise escape them
Previewmaker.prototype.mopup = function() {
Title.prototype.decodeEscapes = function (txt) {
this.data = this.data.replace(RegExp('^-{4,}', 'mg'), '');
var split = txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/);
this.data = this.data.replace(RegExp('(^|\\n) *:[^\\n]*', 'g'), '');
var len = split.length;
this.data = this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'), '');
// No %-encoded items found, so replace the literal %
}
if (len === 1) {
;
return split[0].replace(/%(?![0-9a-fA-F][0-9a-fA-F])/g, '%25');
Previewmaker.prototype.firstBit = function() {
}
var d = this.data;
for (var i = 1; i < len; i = i + 2) {
if (getValueOf('popupPreviewCutHeadings')) {
split[i] = decodeURIComponent(split[i]);
this.data = this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 ');
}
this.data = this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n');
return split.join('');
this.data = this.data.replace(RegExp('^[\\s\\n]*'), '');
};
var stuff = RegExp('^([^\\n]|\\n[^\\n\\s])*').exec(this.data);
Title.fromAnchor = function (a) {
if (stuff) {
return new Title().fromAnchor(a);
d = stuff[0];
};
}
Title.prototype.fromAnchor = function (a) {
if (!getValueOf('popupPreviewFirstParOnly')) {
if (!a) {
d = this.data;
this.value = null;
}
return this;
d = d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n');
}
}
return this.fromURL(a.href);
d = d.parenSplit(RegExp('([!?.]+["' + "'" + ']*\\s)', 'g'));
};
d[0] = d[0].replace(RegExp('^\\s*'), '');
Title.fromWikiText = function (txt) {
var notSentenceEnds = RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i');
return new Title().fromWikiText(txt);
d = this.fixSentenceEnds(d, notSentenceEnds);
};
this.fullLength = d.join('').length;
Title.prototype.fromWikiText = function (txt) {
var n = this.maxSentences;
// FIXME - testing needed
var dd = this.firstSentences(d, n);
txt = myDecodeURI(txt);
do {
this.setUtf(txt);
dd = this.firstSentences(d, n);
return this;
--n;
};
} while (dd.length > this.maxCharacters && n !== 0);
Title.prototype.hintValue = function () {
this.data = dd;
if (!this.value) {
}
return '';
;
}
Previewmaker.prototype.fixSentenceEnds = function(strs, reg) {
return safeDecodeURI(this.value);
for (var i = 0; i < strs.length - 2; ++i) {
};
if (reg.test(strs[i])) {
Title.prototype.toUserName = function (withNs) {
var a = [];
if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) {
for (var j = 0; j < strs.length; ++j) {
this.value = null;
if (j < i) {
return;
a[j] = strs[j];
}
}
this.value =
if (j == i) {
(withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') +
a[i] = strs[i] + strs[i + 1] + strs[i + 2];
this.stripNamespace().split('/')[0];
}
};
if (j > i + 2) {
Title.prototype.userName = function (withNs) {
a[j - 2] = strs[j];
var t = new Title(this.value);
}
t.toUserName(withNs);
}
if (t.value) {
return this.fixSentenceEnds(a, reg);
return t;
}
}
}
return strs;
return null;
};
}
Title.prototype.toTalkPage = function () {
;
// convert article to a talk page, or if we can't, return null
Previewmaker.prototype.firstSentences = function(strs, howmany) {
// In other words: return null if this ALREADY IS a talk page
var t = strs.slice(0, 2 * howmany);
// and return the corresponding talk page otherwise
return t.join('');
//
}
// Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces
;
// * All discussion namespaces have odd-integer indices
Previewmaker.prototype.killBadWhitespace = function() {
// * The discussion namespace index for a specific namespace with index n is n + 1
this.data = this.data.replace(RegExp("^ *'+ *$", 'gm'), '');
if (this.value === null) {
}
return null;
;
}
Previewmaker.prototype.makePreview = function() {

if (this.owner.article.namespaceId() != pg.nsTemplateId && this.owner.article.namespaceId() != pg.nsImageId) {
var namespaceId = this.namespaceId();
this.killComments();
if (namespaceId >= 0 && namespaceId % 2 === 0) {
this.killDivs();
//non-special and subject namespace
this.killGalleries();
var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId + 1];
this.killBoxTemplates();
if (typeof localizedNamespace !== 'undefined') {
if (getValueOf('popupPreviewKillTemplates')) {
if (localizedNamespace === '') {
this.killTemplates();
this.value = this.stripNamespace();
} else {
} else {
this.killMultilineTemplates();
this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
}
}
this.killTables();
return this.value;
this.killImages();
}
this.killHTML();
}
this.killChunks();

this.mopup();
this.value = null;
this.firstBit();
return null;
this.killBadWhitespace();
};
} else {
// Return canonical, localized namespace
this.killHTML();
Title.prototype.namespace = function () {
}
return mw.config.get('wgFormattedNamespaces')[this.namespaceId()];
this.html = wiki2html(this.data, this.baseUrl);
};
this.fixHTML();
Title.prototype.namespaceId = function () {
this.stripLongTemplates();
var n = this.value.indexOf(':');
}
if (n < 0) {
;
return 0;
Previewmaker.prototype.esWiki2HtmlPart = function(data) {
} //mainspace
var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi;
var namespaceId =
reLinks.lastIndex = 0;
mw.config.get('wgNamespaceIds')[
var match;
this.value.substring(0, n).split(' ').join('_').toLowerCase()
var result = '';
];
var postfixIndex = 0;
if (typeof namespaceId == 'undefined') {
while ((match = reLinks.exec(data))) {
return 0;
result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) + '<a href="' + Insta.conf.paths.articles + pg.escapeQuotesHTML(match[1]) + '">' + pg.escapeQuotesHTML((match[2] ? match[2] : match[1]) + match[3]) + '</a>';
} //mainspace
postfixIndex = reLinks.lastIndex;
return namespaceId;
}
};
result += pg.escapeQuotesHTML(data.substring(postfixIndex));
Title.prototype.talkPage = function () {
return result;
var t = new Title(this.value);
}
t.toTalkPage();
;
if (t.value) {
Previewmaker.prototype.editSummaryPreview = function() {
return t;
var reAes = /\/\* *(.*?) *\*\//g;
}
reAes.lastIndex = 0;
return null;
var match;
};
match = reAes.exec(this.data);
Title.prototype.isTalkPage = function () {
if (match) {
if (this.talkPage() === null) {
var prefix = this.data.substring(0, match.index - 1);
return true;
var section = match[1];
}
var postfix = this.data.substring(reAes.lastIndex);
return false;
var start = "<span class='autocomment'>";
};
var end = '</span>';
Title.prototype.toArticleFromTalkPage = function () {
if (prefix.length > 0) {
//largely copy/paste from toTalkPage above.
start = this.esWiki2HtmlPart(prefix) + ' ' + start + '- ';
if (this.value === null) {
}
return null;
if (postfix.length > 0) {
}
end = ': ' + end + this.esWiki2HtmlPart(postfix);

}
var namespaceId = this.namespaceId();
var t = new Title().fromURL(this.baseUrl);
if (namespaceId >= 0 && namespaceId % 2 == 1) {
t.anchorFromUtf(section);
//non-special and talk namespace
var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor);
var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId - 1];
return (start + '<a href="' + sectionLink + '">&rarr;</a> ' + pg.escapeQuotesHTML(section) + end);
if (typeof localizedNamespace !== 'undefined') {
}
if (localizedNamespace === '') {
return this.esWiki2HtmlPart(this.data);
this.value = this.stripNamespace();
}
} else {
;
this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
function previewSteps(txt) {
}
try {
return this.value;
txt = txt || document.editform.wpTextbox1.value;
}
} catch (err) {
}
if (pg.cache.pages.length > 0) {

txt = pg.cache.pages[pg.cache.pages.length - 1].data;
this.value = null;
} else {
return null;
alert('provide text or use an edit page');
};
}
Title.prototype.articleFromTalkPage = function () {
}
var t = new Title(this.value);
txt = txt.substring(0, 10000);
t.toArticleFromTalkPage();
var base = pg.wiki.articlebase + Title.fromURL(document.location.href).urlString();
if (t.value) {
var p = new Previewmaker(txt,base,pg.current.link.navpopup);
return t;
if (this.owner.article.namespaceId() != pg.nsTemplateId) {
}
p.killComments();
return null;
if (!confirm('done killComments(). Continue?\n---\n' + p.data)) {
};
return;
Title.prototype.articleFromTalkOrArticle = function () {
}
var t = new Title(this.value);
p.killDivs();
if (t.toArticleFromTalkPage()) {
if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) {
return t;
return;
}
}
return this;
p.killGalleries();
};
if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) {
Title.prototype.isIpUser = function () {
return;
return pg.re.ipUser.test(this.userName());
}
};
p.killBoxTemplates();
Title.prototype.stripNamespace = function () {
if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) {
// returns a string, not a Title
return;
var n = this.value.indexOf(':');
}
if (n < 0) {
if (getValueOf('popupPreviewKillTemplates')) {
return this.value;
p.killTemplates();
}
if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) {
var namespaceId = this.namespaceId();
return;
if (namespaceId === pg.nsMainspaceId) {
}
return this.value;
} else {
}
p.killMultilineTemplates();
return this.value.substring(n + 1);
if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) {
};
return;
Title.prototype.setUtf = function (value) {
}
if (!value) {
}
this.value = '';
p.killTables();
return;
if (!confirm('done killTables(). Continue?\n---\n' + p.data)) {
}
return;
var anch = value.indexOf('#');
}
if (anch < 0) {
p.killImages();
this.value = value.split('_').join(' ');
if (!confirm('done killImages(). Continue?\n---\n' + p.data)) {
this.anchor = '';
return;
return;
}
}
p.killHTML();
this.value = value.substring(0, anch).split('_').join(' ');
if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) {
this.anchor = value.substring(anch + 1);
return;
this.ns = null; // wait until namespace() is called
}
};
p.killChunks();
Title.prototype.setUrl = function (urlfrag) {
if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) {
var anch = urlfrag.indexOf('#');
return;
this.value = safeDecodeURI(urlfrag.substring(0, anch));
}
this.anchor = this.value.substring(anch + 1);
p.mopup();
};
if (!confirm('done mopup(). Continue?\n---\n' + p.data)) {
Title.prototype.append = function (x) {
return;
this.setUtf(this.value + x);
}
};
p.firstBit();
Title.prototype.urlString = function (x) {
if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) {
if (!x) {
return;
x = {};
}
}
p.killBadWhitespace();
var v = this.toString(true);
if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) {
if (!x.omitAnchor && this.anchor) {
return;
v += '#' + this.urlAnchor();
}
}
}
if (!x.keepSpaces) {
p.html = wiki2html(p.data, base);
v = v.split(' ').join('_');
p.fixHTML();
}
if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) {
return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B');
return;
};
}
Title.prototype.removeAnchor = function () {
p.stripLongTemplates();
return new Title(this.toString(true));
if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) {
};
return;
Title.prototype.toUrl = function () {
}
return pg.wiki.titlebase + this.urlString();
alert('finished preview - end result follows.\n---\n' + p.html);
};
}

Previewmaker.prototype.fixHTML = function() {
function parseParams(url) {
if (!this.html) {
var specialDiff = pg.re.specialdiff.exec(url);
return;
if (specialDiff) {
}
var split = specialDiff[1].split('/');
var ret = this.html;
if (split.length == 1) {
ret = ret.replace(RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'), '$1%3F$2');
return { oldid: split[0], diff: 'prev' };
ret = ret.replace(RegExp("(<a href='" + pg.wiki.articlePath + "/[^']*)[?](.*?')", 'g'), '$1%3F$2');
} else if (split.length == 2) {
this.html = ret;
return { oldid: split[0], diff: split[1] };
}
}
;
}
Previewmaker.prototype.showPreview = function() {

this.makePreview();
var ret = {};
if (typeof this.html != typeof '') {
if (url.indexOf('?') == -1) {
return;
return ret;
}
}
if (RegExp('^\\s*$').test(this.html)) {
url = url.split('#')[0];
return;
var s = url.split('?').slice(1).join();
}
var t = s.split('&');
setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber);
for (var i = 0; i < t.length; ++i) {
setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, {
var z = t[i].split('=');
owner: this.owner,
z.push(null);
});
ret[z[0]] = z[1];
var more = this.fullLength > this.data.length ? this.moreLink() : '';
}
setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber);
//Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki
}
if (ret.diff && typeof ret.oldid === 'undefined') {
;
ret.oldid = 'prev';
Previewmaker.prototype.moreLink = function() {
}
var a = document.createElement('a');
//Documentation seems to say something different, but oldid can also accept prev/next, and
a.className = 'popupMoreLink';
//Echo is emitting such URLs. Simple fixup during parameter decoding:
a.innerHTML = popupString('more...');
if (ret.oldid && (ret.oldid === 'prev' || ret.oldid === 'next' || ret.oldid === 'cur')) {
var savedThis = this;
var helper = ret.diff;
a.onclick = function() {
ret.diff = ret.oldid;
savedThis.maxCharacters += 2000;
ret.oldid = helper;
savedThis.maxSentences += 20;
}
savedThis.setData();
return ret;
savedThis.showPreview();
}
}

;
// (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup)
return a;
// (b) change spaces to underscores
}
// (c) encodeURI (just the straight one, no pg.re.urlNoPopup)
;

Previewmaker.prototype.stripLongTemplates = function() {
function myDecodeURI(str) {
this.html = this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), '');
var ret;
this.html = this.html.split('\n').join(' ');
// FIXME decodeURIComponent??
this.html = this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]', 'gi'), '');
try {
}
ret = decodeURI(str.toString());
;
} catch (summat) {
Previewmaker.prototype.killMultilineTemplates = function() {
return str;
this.kill('{{{', '}}}');
}
this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{');
for (var i = 0; i < pg.misc.decodeExtras.length; ++i) {
}
var from = pg.misc.decodeExtras[i].from;
;
var to = pg.misc.decodeExtras[i].to;
function loadAPIPreview(queryType, article, navpop) {
ret = ret.split(from).join(to);
var art = new Title(article).urlString();
}
var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';
return ret;
var htmlGenerator = function() {
}
alert('invalid html generator');

};
function safeDecodeURI(str) {
var usernameart = '';
var ret = myDecodeURI(str);
switch (queryType) {
return ret || str;
case 'history':
}
url += 'titles=' + art + '&prop=revisions&rvlimit=' + getValueOf('popupHistoryPreviewLimit');

htmlGenerator = APIhistoryPreviewHTML;
///////////
break;
// TESTS //
case 'category':
///////////
url += 'list=categorymembers&cmtitle=' + art;

htmlGenerator = APIcategoryPreviewHTML;
function isDisambig(data, article) {
break;
if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
case 'userinfo':
return false;
var username = new Title(article).userName();
}
usernameart = encodeURIComponent(username);
return !article.isTalkPage() && pg.re.disambig.test(data);
if (pg.re.ipUser.test(username)) {
}
url += 'list=blocks&bkprop=range|restrictions&bkip=' + usernameart;

} else {
function stubCount(data, article) {
url += 'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + usernameart + '&meta=globaluserinfo&guiprop=groups|unattached&guiuser=' + usernameart + '&uclimit=1&ucprop=timestamp&ucuser=' + usernameart;
if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
}
return false;
htmlGenerator = APIuserInfoPreviewHTML;
}
break;
var sectStub = 0;
case 'contribs':
var realStub = 0;
usernameart = encodeURIComponent(new Title(article).userName());
if (pg.re.stub.test(data)) {
url += 'list=usercontribs&ucuser=' + usernameart + '&uclimit=' + getValueOf('popupContribsPreviewLimit');
var s = data.parenSplit(pg.re.stub);
htmlGenerator = APIcontribsPreviewHTML;
break;
for (var i = 1; i < s.length; i = i + 2) {
if (s[i]) {
case 'imagepagepreview':
++sectStub;
var trail = '';
} else {
if (getValueOf('popupImageLinks')) {
++realStub;
trail = '&list=imageusage&iutitle=' + art;
}
}
}
url += 'titles=' + art + '&prop=revisions|imageinfo&rvslots=main&rvprop=content' + trail;
}
htmlGenerator = APIimagepagePreviewHTML;
return { real: realStub, sect: sectStub };
break;
}
case 'backlinks':

url += 'list=backlinks&bltitle=' + art;
function isValidImageName(str) {
htmlGenerator = APIbacklinksPreviewHTML;
// extend as needed...
break;
return str.indexOf('{') == -1;
case 'revision':
}
if (article.oldid) {

url += 'revids=' + article.oldid;
function isInStrippableNamespace(article) {
} else {
// Does the namespace allow subpages
url += 'titles=' + article.removeAnchor().urlString();
// Note, would be better if we had access to wgNamespacesWithSubpages
}
return article.namespaceId() !== 0;
url += '&prop=revisions|pageprops|info|images|categories&meta=wikibase&rvslots=main&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max';
}
htmlGenerator = APIrevisionPreviewHTML;

break;
function isInMainNamespace(article) {
}
return article.namespaceId() === 0;
pendingNavpopTask(navpop);
}
var callback = function(d) {

log('callback of API functions was hit');
function anchorContainsImage(a) {
if (queryType === 'userinfo') {
// iterate over children of anchor a
fetchUserGroupNames(d.data).then(function() {
// see if any are images
showAPIPreview(queryType, htmlGenerator(article, d, navpop), navpop.idNumber, navpop, d);
if (a === null) {
});
return false;
return;
}
}
var kids = a.childNodes;
showAPIPreview(queryType, htmlGenerator(article, d, navpop), navpop.idNumber, navpop, d);
for (var i = 0; i < kids.length; ++i) {
};
if (kids[i].nodeName == 'IMG') {
var go = function() {
return true;
getPageWithCaching(url, callback, navpop);
}
return true;
}
};
return false;
if (navpop.visible || !getValueOf('popupLazyDownloads')) {
}
go();
function isPopupLink(a) {
} else {
// NB for performance reasons, TOC links generally return true
navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_' + queryType + '_QUERY_DATA');
// they should be stripped out later
}

}
if (!markNopopupSpanLinks.done) {
function linkList(list) {
markNopopupSpanLinks();
list.sort(function(x, y) {
}
return x == y ? 0 : x < y ? -1 : 1;
if (a.inNopopupSpan) {
});
return false;
var buf = [];
}
for (var i = 0; i < list.length; ++i) {

buf.push(wikiLink({
// FIXME is this faster inline?
article: new Title(list[i]),
if (a.onmousedown || a.getAttribute('nopopup')) {
text: list[i].split(' ').join('&nbsp;'),
return false;
action: 'view',
}
}));
var h = a.href;
}
if (h === document.location.href + '#') {
return buf.join(', ');
return false;
}
}
function getTimeOffset() {
if (!pg.re.basenames.test(h)) {
var tz = mw.user.options.get('timecorrection');
return false;
if (tz) {
}
if (tz.indexOf('|') > -1) {
if (!pg.re.urlNoPopup.test(h)) {
return parseInt(tz.split('|')[1], 10);
return true;
}
}
}
return 0;
return (
(pg.re.email.test(h) ||
}
pg.re.contribs.test(h) ||
function getTimeZone() {
pg.re.backlinks.test(h) ||
if (!pg.user.timeZone) {
pg.re.specialdiff.test(h)) &&
var tz = mw.user.options.get('timecorrection');
h.indexOf('&limit=') == -1
pg.user.timeZone = 'UTC';
);
if (tz) {
}
var tzComponents = tz.split('|');

if (tzComponents.length === 3 && tzComponents[0] === 'ZoneInfo') {
function markNopopupSpanLinks() {
pg.user.timeZone = tzComponents[2];
if (!getValueOf('popupOnlyArticleLinks')) {
} else {
fixVectorMenuPopups();
errlog('Unexpected timezone information: ' + tz);
}
}

}
var s = $('.nopopups').toArray();
}
for (var i = 0; i < s.length; ++i) {
return pg.user.timeZone;
var as = s[i].getElementsByTagName('a');
}
for (var j = 0; j < as.length; ++j) {
function useTimeOffset() {
as[j].inNopopupSpan = true;
if (typeof Intl.DateTimeFormat.prototype.formatToParts === 'undefined') {
}
return true;
}
}

var tz = mw.user.options.get('timecorrection');
markNopopupSpanLinks.done = true;
if (tz && tz.indexOf('ZoneInfo|') === -1) {
}
return true;

}
function fixVectorMenuPopups() {
return false;
$('nav.vector-menu h3:first a:first').prop('inNopopupSpan', true);
}
}
function getLocales() {
// ENDFILE: titles.js
if (!pg.user.locales) {

var userLanguage = document.querySelector('html').getAttribute('lang');
// STARTFILE: getpage.js
if (getValueOf('popupLocale')) {
//////////////////////////////////////////////////
userLanguage = getValueOf('popupLocale');
// Wiki-specific downloading
} else if (userLanguage === 'en') {
//
if (getMWDateFormat() === 'mdy') {

userLanguage = 'en-US';
// Schematic for a getWiki call
} else {
//
userLanguage = 'en-GB';
}
// getPageWithCaching
// |
}
// false | true
pg.user.locales = Intl.DateTimeFormat.supportedLocalesOf([userLanguage, navigator.language]);
// getPage<-[findPictureInCache]->-onComplete(a fake download)
}
// \.
return pg.user.locales;
// (async)->addPageToCache(download)->-onComplete(download)
}

function getMWDateFormat() {
// check cache to see if page exists
return mw.user.options.get('date');

}
function editPreviewTable(article, h, reallyContribs) {
function getPageWithCaching(url, onComplete, owner) {
log('getPageWithCaching, url=' + url);
var html = ['<table>'];
var i = findInPageCache(url);
var day = null;
var d;
var curart = article;
if (i > -1) {
var page = null;
d = fakeDownload(
var makeFirstColumnLinks;
url,
if (reallyContribs) {
owner.idNumber,
makeFirstColumnLinks = function(currentRevision) {
onComplete,
var result = '(';
pg.cache.pages[i].data,
result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&diff=prev' + '&oldid=' + currentRevision.revid + '">' + popupString('diff') + '</a>';
pg.cache.pages[i].lastModified,
result += '&nbsp;|&nbsp;';
owner
result += '<a href="' + pg.wiki.titlebase + new Title(currentRevision.title).urlString() + '&action=history">' + popupString('hist') + '</a>';
);
result += ')';
} else {
return result;
d = getPage(url, onComplete, owner);
}
if (d && owner && owner.addDownload) {
;
owner.addDownload(d);
} else {
d.owner = owner;
var firstRevid = h[0].revid;
}
makeFirstColumnLinks = function(currentRevision) {
}
var result = '(';
}
result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=' + firstRevid + '&oldid=' + currentRevision.revid + '">' + popupString('cur') + '</a>';

result += '&nbsp;|&nbsp;';
function getPage(url, onComplete, owner) {
result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&diff=prev&oldid=' + currentRevision.revid + '">' + popupString('last') + '</a>';
log('getPage');
result += ')';
var callback = function (d) {
return result;
if (!d.aborted) {
}
addPageToCache(d);
;
onComplete(d);
}
}
for (var i = 0; i < h.length; ++i) {
};
if (reallyContribs) {
return startDownload(url, owner.idNumber, callback);
page = h[i].title;
}
curart = new Title(page);

}
function findInPageCache(url) {
var minor = h[i].minor ? '<b>m </b>' : '';
for (var i = 0; i < pg.cache.pages.length; ++i) {
var editDate = new Date(h[i].timestamp);
if (url == pg.cache.pages[i].url) {
var thisDay = formattedDate(editDate);
return i;
var thisTime = formattedTime(editDate);
}
if (thisDay == day) {
}
thisDay = '';
return -1;
} else {
}
day = thisDay;

}
function addPageToCache(download) {
if (thisDay) {
log('addPageToCache ' + download.url);
html.push('<tr><td colspan=3><span class="popup_history_date">' + thisDay + '</span></td></tr>');
var page = {
}
url: download.url,
html.push('<tr class="popup_history_row_' + (i % 2 ? 'odd' : 'even') + '">');
data: download.data,
html.push('<td>' + makeFirstColumnLinks(h[i]) + '</td>');
lastModified: download.lastModified,
html.push('<td>' + '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() + '&oldid=' + h[i].revid + '">' + thisTime + '</a></td>');
};
var col3url = ''
return pg.cache.pages.push(page);
, col3txt = '';
}
if (!reallyContribs) {
// ENDFILE: getpage.js
var user = h[i].user;

if (!h[i].userhidden) {
// STARTFILE: parensplit.js
if (pg.re.ipUser.test(user)) {
//////////////////////////////////////////////////
col3url = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Contributions&target=' + new Title(user).urlString();
// parenSplit
} else {

col3url = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(user).urlString();
// String.prototype.parenSplit should do what ECMAscript says String.prototype.split does,
}
// interspersing paren matches (regex capturing groups) between the split elements.
col3txt = pg.escapeQuotesHTML(user);
// i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c']
} else {

col3url = getValueOf('popupRevDelUrl');
if (String('abc'.split(/(b)/)) != 'a,b,c') {
col3txt = pg.escapeQuotesHTML(popupString('revdel'));
// broken String.split, e.g. konq, IE < 10
}
String.prototype.parenSplit = function (re) {
} else {
re = nonGlobalRegex(re);
col3url = pg.wiki.titlebase + curart.urlString();
var s = this;
col3txt = pg.escapeQuotesHTML(page);
var m = re.exec(s);
}
var ret = [];
html.push('<td>' + (reallyContribs ? minor : '') + '<a href="' + col3url + '">' + col3txt + '</a></td>');
while (m && s) {
var comment = '';
// without the following loop, we have
var c = h[i].comment || (typeof h[i].slots !== 'undefined' ? h[i].slots.main.content : null);
// 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/)
if (c) {
for (var i = 0; i < m.length; ++i) {
comment = new Previewmaker(c,new Title(curart).toUrl()).editSummaryPreview();
if (typeof m[i] == 'undefined') {
} else if (h[i].commenthidden) {
m[i] = '';
comment = popupString('revdel');
}
}
}
html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>');
ret.push(s.substring(0, m.index));
html.push('</tr>');
ret = ret.concat(m.slice(1));
html = [html.join('')];
s = s.substring(m.index + m[0].length);
}
m = re.exec(s);
html.push('</table>');
}
return html.join('');
ret.push(s);
}
return ret;
function adjustDate(d, offset) {
};
var o = offset * 60 * 1000;
} else {
return new Date(Number(d) + o);
String.prototype.parenSplit = function (re) {
}
return this.split(re);
function convertTimeZone(date, timeZone) {
};
return new Date(date.toLocaleString('en-US', {
String.prototype.parenSplit.isNative = true;
timeZone: timeZone
}
}));

}
function formattedDateTime(date) {
function nonGlobalRegex(re) {
var s = re.toString();
if (useTimeOffset()) {
var flags = '';
return formattedDate(date) + ' ' + formattedTime(date);
for (var j = s.length; s.charAt(j) != '/'; --j) {
}
if (getMWDateFormat() === 'ISO 8601') {
if (s.charAt(j) != 'g') {
flags += s.charAt(j);
var d2 = convertTimeZone(date, getTimeZone());
}
return (map(zeroFill, [d2.getFullYear(), d2.getMonth() + 1, d2.getDate()]).join('-') + 'T' + map(zeroFill, [d2.getHours(), d2.getMinutes(), d2.getSeconds()]).join(':'));
}
}
var t = s.substring(1, j);
var options = getValueOf('popupDateTimeFormatterOptions');
return RegExp(t, flags);
options['timeZone'] = getTimeZone();
}
return date.toLocaleString(getLocales(), options);
// ENDFILE: parensplit.js
}

function formattedDate(date) {
// STARTFILE: tools.js
if (useTimeOffset()) {
// IE madness with encoding
var d2 = adjustDate(date, getTimeOffset());
// ========================
return map(zeroFill, [d2.getUTCFullYear(), d2.getUTCMonth() + 1, d2.getUTCDate()]).join('-');
//
}
// suppose throughout that the page is in utf8, like wikipedia
if (getMWDateFormat() === 'ISO 8601') {
//
var d2 = convertTimeZone(date, getTimeZone());
// if a is an anchor DOM element and a.href should consist of
return map(zeroFill, [d2.getFullYear(), d2.getMonth() + 1, d2.getDate()]).join('-');
//
}
// http://host.name.here/wiki/foo?bar=baz
var options = getValueOf('popupDateFormatterOptions');
//
options['timeZone'] = getTimeZone();
// then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie))
return date.toLocaleDateString(getLocales(), options);
// but IE gives bar=baz correctly as plain utf8
}
//
function formattedTime(date) {
// ---------------------------------
if (useTimeOffset()) {
//
var d2 = adjustDate(date, getTimeOffset());
// IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here.
return map(zeroFill, [d2.getUTCHours(), d2.getUTCMinutes(), d2.getUTCSeconds()]).join(':');
//
}
// ---------------------------------
if (getMWDateFormat() === 'ISO 8601') {
//
var d2 = convertTimeZone(date, getTimeZone());
// summat else
return map(zeroFill, [d2.getHours(), d2.getMinutes(), d2.getSeconds()]).join(':');

}
// Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm
var options = getValueOf('popupTimeFormatterOptions');

options['timeZone'] = getTimeZone();
function getJsObj(json) {
return date.toLocaleTimeString(getLocales(), options);
try {
}
var json_ret = JSON.parse(json);
function fetchUserGroupNames(userinfoResponse) {
if (json_ret.warnings) {
var queryObj = getJsObj(userinfoResponse).query;
for (var w = 0; w < json_ret.warnings.length; w++) {
var user = anyChild(queryObj.users);
if (json_ret.warnings[w]['*']) {
var messages = [];
log(json_ret.warnings[w]['*']);
if (user.groups) {
} else {
user.groups.forEach(function(groupName) {
log(json_ret.warnings[w].warnings);
if (['*', 'user', 'autoconfirmed', 'extendedconfirmed', 'named'].indexOf(groupName) === -1) {
}
messages.push('group-' + groupName + '-member');
}
}
} else if (json_ret.error) {
});
errlog(json_ret.error.code + ': ' + json_ret.error.info);
}
}
if (queryObj.globaluserinfo && queryObj.globaluserinfo.groups) {
return json_ret;
queryObj.globaluserinfo.groups.forEach(function(groupName) {
} catch (someError) {
messages.push('group-' + groupName + '-member');
errlog('Something went wrong with getJsObj, json=' + json);
});
return 1;
}
}
return getMwApi().loadMessagesIfMissing(messages);
}
}

function showAPIPreview(queryType, html, id, navpop, download) {
function anyChild(obj) {
var target = 'popupPreview';
for (var p in obj) {
completedNavpopTask(navpop);
return obj[p];
switch (queryType) {
}
case 'imagelinks':
return null;
case 'category':
}
target = 'popupPostPreview';

break;
function upcaseFirst(str) {
case 'userinfo':
if (typeof str != typeof '' || str === '') {
target = 'popupUserData';
return '';
break;
}
case 'revision':
return str.charAt(0).toUpperCase() + str.substring(1);
insertPreview(download);
}
return;

}
function findInArray(arr, foo) {
setPopupTipsAndHTML(html, target, id);
if (!arr || !arr.length) {
}
return -1;
function APIrevisionPreviewHTML(article, download) {
}
try {
var len = arr.length;
var jsObj = getJsObj(download.data);
for (var i = 0; i < len; ++i) {
var page = anyChild(jsObj.query.pages);
if (arr[i] == foo) {
if (page.missing) {
return i;
download.owner = null;
}
return;
}
}
return -1;
var content = page && page.revisions && page.revisions[0] && page.revisions[0].slots && page.revisions[0].slots.main && page.revisions[0].slots.main.contentmodel === 'wikitext' ? page.revisions[0].slots.main.content : null;
}
if (typeof content === 'string') {

download.data = content;
/* eslint-disable no-unused-vars */
download.lastModified = new Date(page.revisions[0].timestamp);
function nextOne(array, value) {
}
// NB if the array has two consecutive entries equal
if (page.pageprops.wikibase_item) {
// then this will loop on successive calls
download.wikibaseItem = page.pageprops.wikibase_item;
var i = findInArray(array, value);
download.wikibaseRepo = jsObj.query.wikibase.repo.url.base + jsObj.query.wikibase.repo.url.articlepath;
if (i < 0) {
}
return null;
} catch (someError) {
}
return 'Revision preview failed :(';
return array[i + 1];
}
}
}
/* eslint-enable no-unused-vars */
function APIbacklinksPreviewHTML(article, download) {

try {
function literalizeRegex(str) {
var jsObj = getJsObj(download.data);
return mw.util.escapeRegExp(str);
var list = jsObj.query.backlinks;
}
var html = [];

if (!list) {
String.prototype.entify = function () {
return popupString('No backlinks found');
//var shy='&shy;';
}
return this.split('&')
for (var i = 0; i < list.length; i++) {
.join('&amp;')
var t = new Title(list[i].title);
.split('<')
html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t.toString().entify() + '</a>');
.join('&lt;')
}
.split('>')
html = html.join(', ');
.join('&gt;' /*+shy*/)
if (jsObj['continue'] && jsObj['continue'].blcontinue) {
.split('"')
html += popupString(' and more');
.join('&quot;');
}
};
return html;

} catch (someError) {
// Array filter function
return 'backlinksPreviewHTML went wonky';
function removeNulls(val) {
}
return val !== null;
}
}
pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) {

log('APIsharedImagePagePreviewHTML');
function joinPath(list) {
var popupid = obj.requestid;
return list.filter(removeNulls).join('/');
if (obj.query && obj.query.pages) {
}
var page = anyChild(obj.query.pages);

var content = page && page.revisions && page.revisions[0] && page.revisions[0].slots && page.revisions[0].slots.main && page.revisions[0].slots.main.contentmodel === 'wikitext' ? page.revisions[0].slots.main.content : null;
function simplePrintf(str, subs) {
if (typeof content === 'string' && pg && pg.current && pg.current.link && pg.current.link.navpopup) {
if (!str || !subs) {
var p = new Previewmaker(content,pg.current.link.navpopup.article,pg.current.link.navpopup);
return str;
p.makePreview();
}
setPopupHTML(p.html, 'popupSecondPreview', popupid);
var ret = [];
}
var s = str.parenSplit(/(%s|\$[0-9]+)/);
}
var i = 0;
}
do {
;
ret.push(s.shift());
function APIimagepagePreviewHTML(article, download, navpop) {
if (!s.length) {
try {
break;
var jsObj = getJsObj(download.data);
}
var page = anyChild(jsObj.query.pages);
var cmd = s.shift();
var content = page && page.revisions && page.revisions[0] && page.revisions[0].slots && page.revisions[0].slots.main && page.revisions[0].slots.main.contentmodel === 'wikitext' ? page.revisions[0].slots.main.content : null;
if (cmd == '%s') {
var ret = '';
if (i < subs.length) {
var alt = '';
ret.push(subs[i]);
try {
} else {
alt = navpop.parentAnchor.childNodes[0].alt;
ret.push(cmd);
} catch (e) {}
}
if (alt) {
++i;
ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt);
} else {
}
var j = parseInt(cmd.replace('$', ''), 10) - 1;
if (typeof content === 'string') {
if (j > -1 && j < subs.length) {
var p = prepPreviewmaker(content, article, navpop);
ret.push(subs[j]);
p.makePreview();
} else {
if (p.html) {
ret.push(cmd);
ret += '<hr />' + p.html;
}
}
}
if (getValueOf('popupSummaryData')) {
} while (s.length > 0);
var info = getPageInfo(content, download);
return ret.join('');
log(info);
}
setPopupTrailer(info, navpop.idNumber);

}
/* eslint-disable no-unused-vars */
}
function isString(x) {
if (page && page.imagerepository == 'shared') {
return typeof x === 'string' || x instanceof String;
var art = new Title(article);
}
var encart = encodeURIComponent('File:' + art.stripNamespace());

var shared_url = pg.wiki.apicommonsbase + '?format=json&formatversion=2' + '&callback=pg.fn.APIsharedImagePagePreviewHTML' + '&requestid=' + navpop.idNumber + '&action=query&prop=revisions&rvslots=main&rvprop=content&titles=' + encart;
function isNumber(x) {
ret = ret + '<hr />' + popupString('Image from Commons') + ': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' + popupString('Description page') + '</a>';
return typeof x === 'number' || x instanceof Number;
mw.loader.load(shared_url);
}
}

showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article, download), navpop.idNumber, download);
function isRegExp(x) {
return ret;
return x instanceof RegExp;
} catch (someError) {
}
return 'API imagepage preview failed :(';

}
function isArray(x) {
}
return x instanceof Array;
function APIimagelinksPreviewHTML(article, download) {
}
try {

var jsobj = getJsObj(download.data);
function isObject(x) {
var list = jsobj.query.imageusage;
return x instanceof Object;
if (list) {
}
var ret = [];

for (var i = 0; i < list.length; i++) {
function isFunction(x) {
ret.push(list[i].title);
return !isRegExp(x) && (typeof x === 'function' || x instanceof Function);
}
}
if (ret.length === 0) {
/* eslint-enable no-unused-vars */
return popupString('No image links found');

}
function repeatString(s, mult) {
return '<h2>' + popupString('File links') + '</h2>' + linkList(ret);
var ret = '';
} else {
for (var i = 0; i < mult; ++i) {
return popupString('No image links found');
ret += s;
}
}
} catch (someError) {
return ret;
return 'Image links preview generation failed :(';
}
}

}
function APIcategoryPreviewHTML(article, download) {
function zeroFill(s, min) {
min = min || 2;
try {
var t = s.toString();
var jsobj = getJsObj(download.data);
return repeatString('0', min - t.length) + t;
var list = jsobj.query.categorymembers;
}
var ret = [];

for (var p = 0; p < list.length; p++) {
function map(f, o) {
ret.push(list[p].title);
if (isArray(o)) {
}
return map_array(f, o);
if (ret.length === 0) {
}
return popupString('Empty category');
return map_object(f, o);
}
}
ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' + linkList(ret);
function map_array(f, o) {
if (jsobj['continue'] && jsobj['continue'].cmcontinue) {
var ret = [];
ret += popupString(' and more');
for (var i = 0; i < o.length; ++i) {
}
ret.push(f(o[i]));
return ret;
}
} catch (someError) {
return ret;
return 'Category preview failed :(';
}
}
function map_object(f, o) {
}
var ret = {};
function APIuserInfoPreviewHTML(article, download) {
for (var i in o) {
var ret = [];
ret[o] = f(o[i]);
var queryobj = {};
}
try {
return ret;
queryobj = getJsObj(download.data).query;
}
} catch (someError) {

return 'Userinfo preview failed :(';
pg.escapeQuotesHTML = function (text) {
}
return text
var user = anyChild(queryobj.users);
.replace(/&/g, '&amp;')
if (user) {
.replace(/"/g, '&quot;')
var globaluserinfo = queryobj.globaluserinfo;
.replace(/</g, '&lt;')
if (user.invalid === '') {
.replace(/>/g, '&gt;');
ret.push(popupString('Invalid user'));
};
} else if (user.missing === '') {

ret.push(popupString('Not a registered username'));
pg.unescapeQuotesHTML = function (html) {
}
// From https://stackoverflow.com/a/7394787
if (user.blockedby) {
// This seems to be implemented correctly on all major browsers now, so we
if (user.blockpartial) {
// don't have to make our own function.
ret.push('<b>' + popupString('Has blocks') + '</b>');
var txt = document.createElement('textarea');
} else {
txt.innerHTML = html;
ret.push('<b>' + popupString('BLOCKED') + '</b>');
return txt.value;
}
};
}

if (globaluserinfo && ('locked'in globaluserinfo || 'hidden'in globaluserinfo)) {
// ENDFILE: tools.js
var lockedSulAccountIsAttachedToThis = true;

for (var i = 0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) {
// STARTFILE: dab.js
if (globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname')) {
//////////////////////////////////////////////////
lockedSulAccountIsAttachedToThis = false;
// Dab-fixing code
break;
//
}

}
function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) {
if (lockedSulAccountIsAttachedToThis) {
log('retargetDab: newTarget=' + newTarget + ' oldTarget=' + oldTarget);
if ('locked'in globaluserinfo) {
return changeLinkTargetLink({
ret.push('<b><i>' + popupString('LOCKED') + '</i></b>');
newTarget: newTarget,
}
text: newTarget.split(' ').join('&nbsp;'),
if ('hidden'in globaluserinfo) {
hint: tprintf('disambigHint', [newTarget]),
ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>');
summary: simplePrintf(getValueOf('popupFixDabsSummary'), [
}
friendlyCurrentArticleName,
}
newTarget,
}
]),
if (getValueOf('popupShowGender') && user.gender) {
clickButton: getValueOf('popupDabsAutoClick'),
switch (user.gender) {
minor: true,
case 'male':
oldTarget: oldTarget,
ret.push(popupString('he/him') + ' · ');
watch: getValueOf('popupWatchDisambiggedPages'),
break;
title: titleToEdit,
case 'female':
});
ret.push(popupString('she/her') + ' · ');
}
break;

}
function listLinks(wikitext, oldTarget, titleToEdit) {
}
// mediawiki strips trailing spaces, so we do the same
if (user.groups) {
// testcase: https://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633
user.groups.forEach(function(groupName) {
var reg = RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi');
if (['*', 'user', 'autoconfirmed', 'extendedconfirmed', 'named'].indexOf(groupName) === -1) {
var ret = [];
ret.push(pg.escapeQuotesHTML(mw.message('group-' + groupName + '-member', user.gender).text()));
var splitted = wikitext.parenSplit(reg);
}
// ^[a-z]+ should match interwiki links, hopefully (case-insensitive)
});
// and ^[a-z]* should match those and [[:Category...]] style links too
}
var omitRegex = RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory');
if (globaluserinfo && globaluserinfo.groups) {
var friendlyCurrentArticleName = oldTarget.toString();
globaluserinfo.groups.forEach(function(groupName) {
var wikPos = getValueOf('popupDabWiktionary');
ret.push('<i>' + pg.escapeQuotesHTML(mw.message('group-' + groupName + '-member', user.gender).text()) + '</i>');

});
}
for (var i = 1; i < splitted.length; i = i + 3) {
if (
if (user.registration) {
typeof splitted[i] == typeof 'string' &&
ret.push(pg.escapeQuotesHTML((user.editcount ? user.editcount : '0') + popupString(' edits since: ') + (user.registration ? formattedDate(new Date(user.registration)) : '')));
splitted[i].length > 0 &&
}
!omitRegex.test(splitted[i])
}
) {
if (queryobj.usercontribs && queryobj.usercontribs.length) {
ret.push(retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit));
ret.push(popupString('last edit on ') + formattedDate(new Date(queryobj.usercontribs[0].timestamp)));
} /* if */
}
} /* for loop */
if (queryobj.blocks) {

ret.push(popupString('IP user'));
ret = rmDupesFromSortedList(ret.sort());
for (var l = 0; l < queryobj.blocks.length; l++) {

var rbstr = queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK';
if (wikPos) {
rbstr = !Array.isArray(queryobj.blocks[l].restrictions) ? 'Has ' + rbstr.toLowerCase() + 's' : rbstr + 'ED';
var wikTarget =
ret.push('<b>' + popupString(rbstr) + '</b>');
'wiktionary:' +
}
friendlyCurrentArticleName.replace(RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1');
}

for (var m = 0; m < ret.length - 1; m++) {
var meth;
if (ret[m].length > 3 && ret[m].substring(ret[m].length - 3) === ' · ') {
if (wikPos.toLowerCase() == 'first') {
ret[m] = ret[m] + ret[m + 1];
meth = 'unshift';
ret.splice(m + 1, 1);
} else {
m--;
meth = 'push';
}
}
}

ret = '<hr />' + ret.join(', ');
ret[meth](retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit));
return ret;
}
}

function APIcontribsPreviewHTML(article, download, navpop) {
ret.push(
return APIhistoryPreviewHTML(article, download, navpop, true);
changeLinkTargetLink({
}
newTarget: null,
function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) {
text: popupString('remove this link').split(' ').join('&nbsp;'),
try {
hint: popupString('remove all links to this disambig page from this article'),
var jsobj = getJsObj(download.data);
clickButton: getValueOf('popupDabsAutoClick'),
var edits = [];
oldTarget: oldTarget,
if (reallyContribs) {
summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]),
edits = jsobj.query.usercontribs;
watch: getValueOf('popupWatchDisambiggedPages'),
} else {
title: titleToEdit,
edits = anyChild(jsobj.query.pages).revisions;
})
}
);
var ret = editPreviewTable(article, edits, reallyContribs);
return ret;
return ret;
}
} catch (someError) {

return 'History preview failed :-(';
function rmDupesFromSortedList(list) {
}
var ret = [];
}
for (var i = 0; i < list.length; ++i) {
function setupDebugging() {
if (ret.length === 0 || list[i] != ret[ret.length - 1]) {
if (window.popupDebug) {
ret.push(list[i]);
window.log = function(x) {
}
window.console.log(x);
}
}
return ret;
;
}
window.errlog = function(x) {

window.console.error(x);
function makeFixDab(data, navpop) {
}
// grab title from parent popup if there is one; default exists in changeLinkTargetLink
;
var titleToEdit = navpop.parentPopup && navpop.parentPopup.article.toString();
log('Initializing logger');
var list = listLinks(data, navpop.originalArticle, titleToEdit);
} else {
if (list.length === 0) {
window.log = function() {}
log('listLinks returned empty list');
;
return null;
window.errlog = function() {}
}
;
var html = '<hr />' + popupString('Click to disambiguate this link to:') + '<br>';
}
html += list.join(', ');
}
return html;
function loadImage(image, navpop) {
}
if (typeof image.stripNamespace != 'function') {

alert('loadImages bad');
function makeFixDabs(wikiText, navpop) {
}
if (
if (!getValueOf('popupImages')) {
getValueOf('popupFixDabs') &&
return;
isDisambig(wikiText, navpop.article) &&
}
Title.fromURL(location.href).namespaceId() != pg.nsSpecialId &&
if (!isValidImageName(image)) {
navpop.article.talkPage()
return false;
) {
}
setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber);
var art = image.urlString();
}
var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query';
}
url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge');

url += '&titles=' + art;
function popupRedlinkHTML(article) {
pendingNavpopTask(navpop);
return changeLinkTargetLink({
var callback = function(d) {
newTarget: null,
popupsInsertImage(navpop.idNumber, navpop, d);
text: popupString('remove this link').split(' ').join('&nbsp;'),
};
hint: popupString('remove all links to this page from this article'),
var go = function() {
clickButton: getValueOf('popupRedlinkAutoClick'),
getPageWithCaching(url, callback, navpop);
oldTarget: article.toString(),
return true;
summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()]),
};
});
if (navpop.visible || !getValueOf('popupLazyDownloads')) {
}
go();
// ENDFILE: dab.js
} else {

navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA');
// STARTFILE: htmloutput.js
}

}
// this has to use a timer loop as we don't know if the DOM element exists when we want to set the text
function popupsInsertImage(id, navpop, download) {
function setPopupHTML(str, elementId, popupId, onSuccess, append) {
log('popupsInsertImage');
if (typeof popupId === 'undefined') {
var imageinfo;
//console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100));
try {
popupId = pg.idNumber;
var jsObj = getJsObj(download.data);
}
var imagepage = anyChild(jsObj.query.pages);

if (typeof imagepage.imageinfo === 'undefined') {
var popupElement = document.getElementById(elementId + popupId);
return;
if (popupElement) {
}
if (!append) {
imageinfo = imagepage.imageinfo[0];
popupElement.innerHTML = '';
} catch (someError) {
}
log('popupsInsertImage failed :(');
if (isString(str)) {
return;
popupElement.innerHTML += str;
}
} else {
var popupImage = document.getElementById('popupImg' + id);
popupElement.appendChild(str);
if (!popupImage) {
}
log('could not find insertion point for image');
if (onSuccess) {
return;
onSuccess();
}
}
popupImage.width = getValueOf('popupImageSize');
setTimeout(checkPopupPosition, 100);
popupImage.style.display = 'inline';
return true;
if (imageinfo.thumburl) {
} else {
popupImage.src = imageinfo.thumburl;
// call this function again in a little while...
} else if (imageinfo.mime.indexOf('image') === 0) {
setTimeout(function () {
popupImage.src = imageinfo.url;
setPopupHTML(str, elementId, popupId, onSuccess);
log('a thumb could not be found, using original image');
}, 600);
} else {
}
log("fullsize imagethumb, but not sure if it's an image");
return null;
}
}
var a = document.getElementById('popupImageLink' + id);

if (a === null) {
function setPopupTrailer(str, id) {
return null;
return setPopupHTML(str, 'popupData', id);
}
}
switch (getValueOf('popupThumbAction')) {

case 'imagepage':
// args.navpopup is mandatory
if (pg.current.article.namespaceId() != pg.nsImageId) {
// optional: args.redir, args.redirTarget
a.href = imageinfo.descriptionurl;
// FIXME: ye gods, this is ugly stuff
popTipsSoonFn('popupImage' + id)();
function fillEmptySpans(args) {
break;
// if redir is present and true then redirTarget is mandatory
}
var redir = true;
case 'sizetoggle':
var rcid;
a.onclick = toggleSize;
if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) {
a.title = popupString('Toggle image size');
redir = false;
return;
}
case 'linkfull':
var a = args.navpopup.parentAnchor;
a.href = imageinfo.url;

a.title = popupString('Open full-size image');
var article,
return;
hint = null,
}
oldid = null,
}
params = {};
function toggleSize() {
if (redir && typeof args.redirTarget == typeof {}) {
var imgContainer = this;
article = args.redirTarget;
if (!imgContainer) {
//hint=article.hintValue();
alert('imgContainer is null :/');
} else {
return;
article = new Title().fromAnchor(a);
}
hint = a.originalTitle || article.hintValue();
var img = imgContainer.firstChild;
params = parseParams(a.href);
if (!img) {
oldid = getValueOf('popupHistoricalLinks') ? params.oldid : null;
alert('img is null :/');
rcid = params.rcid;
return;
}
}
var x = {
if (!img.style.width || img.style.width === '') {
article: article,
img.style.width = '100%';
hint: hint,
} else {
oldid: oldid,
img.style.width = '';
rcid: rcid,
}
navpop: args.navpopup,
}
params: params,
function getValidImageFromWikiText(wikiText) {
};
var matched = null;

var match;
var structure = pg.structures[getValueOf('popupStructure')];
var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1, RegExp('^<!--[^[]*popup', 'i'));
if (typeof structure != 'object') {
while ((match = pg.re.image.exec(t))) {
setPopupHTML(
var m = match[2] || match[6];
'popupError',
if (isValidImageName(m)) {
'Unknown structure (this should never happen): ' + pg.option.popupStructure,
matched = m;
args.navpopup.idNumber
break;
);
}
return;
}
}
pg.re.image.lastIndex = 0;
var spans = flatten(pg.misc.layout);
if (!matched) {
var numspans = spans.length;
return null;
var redirs = pg.misc.redirSpans;
}

return mw.config.get('wgFormattedNamespaces')[pg.nsImageId] + ':' + upcaseFirst(matched);
}
for (var i = 0; i < numspans; ++i) {
var found = redirs && redirs.indexOf(spans[i]) !== -1;
function removeMatchesUnless(str, re1, parencount, re2) {
//log('redir='+redir+', found='+found+', spans[i]='+spans[i]);
var split = str.parenSplit(re1);
if ((found && !redir) || (!found && redir)) {
var c = parencount + 1;
//log('skipping this set of the loop');
for (var i = 0; i < split.length; ++i) {
continue;
if (i % c === 0 || re2.test(split[i])) {
}
continue;
var structurefn = structure[spans[i]];
}
if (structurefn === undefined) {
split[i] = '';
// nothing to do for this structure part
}
continue;
return split.join('');
}
}
var setfn = setPopupHTML;
function setNamespaces() {
if (
pg.nsSpecialId = -1;
getValueOf('popupActiveNavlinks') &&
pg.nsMainspaceId = 0;
(spans[i].indexOf('popupTopLinks') === 0 || spans[i].indexOf('popupRedirTopLinks') === 0)
pg.nsImageId = 6;
) {
pg.nsUserId = 2;
setfn = setPopupTipsAndHTML;
pg.nsUsertalkId = 3;
}
pg.nsCategoryId = 14;
switch (typeof structurefn) {
pg.nsTemplateId = 10;
case 'function':
}
log(
function setRedirs() {
'running ' +
var r = 'redirect';
spans[i] +
var R = 'REDIRECT';
'({article:' +
var redirLists = {
x.article +
ar: [R, 'تحويل'],
', hint:' +
be: [r, 'перанакіраваньне'],
x.hint +
bg: [r, 'пренасочване', 'виж'],
', oldid: ' +
bs: [r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI'],
x.oldid +
bn: [R, 'পুনর্নির্দেশ'],
'})'
cs: [R, 'PŘESMĚRUJ'],
);
cy: [r, 'ail-cyfeirio'],
setfn(structurefn(x), spans[i], args.navpopup.idNumber);
de: [R, 'WEITERLEITUNG'],
break;
el: [R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'],
case 'string':
eo: [R, 'ALIDIREKTU', 'ALIDIREKTI'],
setfn(structurefn, spans[i], args.navpopup.idNumber);
es: [R, 'REDIRECCIÓN'],
break;
et: [r, 'suuna'],
default:
ga: [r, 'athsheoladh'],
errlog('unknown thing with label ' + spans[i] + ' (span index was ' + i + ')');
gl: [r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'],
break;
he: [R, 'הפניה'],
}
hu: [R, 'ÁTIRÁNYÍTÁS'],
}
is: [r, 'tilvísun', 'TILVÍSUN'],
}
it: [R, 'RINVIA', 'Rinvia'],

ja: [R, '転送'],
// flatten an array
mk: [r, 'пренасочување', 'види'],
function flatten(list, start) {
nds: [r, 'wiederleiden'],
var ret = [];
'nds-nl': [R, 'DEURVERWIEZING', 'DUURVERWIEZING'],
if (typeof start == 'undefined') {
nl: [R, 'DOORVERWIJZING'],
start = 0;
nn: [r, 'omdiriger'],
}
pl: [R, 'PATRZ', 'PRZEKIERUJ', 'TAM'],
for (var i = start; i < list.length; ++i) {
pt: [R, 'redir'],
if (typeof list[i] == typeof []) {
ru: [R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР'],
return ret.concat(flatten(list[i])).concat(flatten(list, i + 1));
sk: [r, 'presmeruj'],
} else {
sr: [r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI'],
ret.push(list[i]);
tr: [R, 'YÖNLENDİRME', 'yönlendirme', 'YÖNLENDİR', 'yönlendir'],
}
tt: [R, 'yünältü', 'перенаправление', 'перенапр'],
}
uk: [R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР'],
return ret;
vi: [r, 'đổi'],
}
yi: [R, 'ווייטערפירן'],

zh: [R, '重定向'],
// Generate html for whole popup
};
function popupHTML(a) {
var redirList = redirLists[pg.wiki.lang] || [r, R];
getValueOf('popupStructure');
pg.re.redirect = RegExp('^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)', 'i');
var structure = pg.structures[pg.option.popupStructure];
}
if (typeof structure != 'object') {
function setInterwiki() {
//return 'Unknown structure: '+pg.option.popupStructure;
if (pg.wiki.wikimedia) {
// override user choice
pg.wiki.interwiki = 'aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu';
pg.option.popupStructure = pg.optionDefault.popupStructure;
pg.re.interwiki = RegExp('^' + pg.wiki.interwiki + ':');
return popupHTML(a);
} else {
}
pg.wiki.interwiki = null;
if (typeof structure.popupLayout != 'function') {
pg.re.interwiki = RegExp('^$');
return 'Bad layout';
}
}
}
pg.misc.layout = structure.popupLayout();
function nsRe(namespaceId) {
if (typeof structure.popupRedirSpans === 'function') {
var imageNamespaceVariants = [];
pg.misc.redirSpans = structure.popupRedirSpans();
jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
} else {
if (_namespaceId != namespaceId) {
pg.misc.redirSpans = [];
return;
}
}
return makeEmptySpans(pg.misc.layout, a.navpopup);
_localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc);
}
imageNamespaceVariants.push(mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]'));

imageNamespaceVariants.push(mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc)));
function makeEmptySpans(list, navpop) {
});
var ret = '';
return '(?:' + imageNamespaceVariants.join('|') + ')';
}
for (var i = 0; i < list.length; ++i) {
if (typeof list[i] == typeof '') {
function nsReImage() {
ret += emptySpanHTML(list[i], navpop.idNumber, 'div');
return nsRe(pg.nsImageId);
} else if (typeof list[i] == typeof [] && list[i].length > 0) {
}
ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop));
function getEditboxSelection() {
} else if (typeof list[i] == typeof {} && list[i].nodeType) {
var editbox;
ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType);
try {
}
editbox = document.editform.wpTextbox1;
}
} catch (dang) {
return ret;
return;
}
}

if (document.selection) {
function emptySpanHTML(name, id, tag, classname) {
return document.selection.createRange().text;
tag = tag || 'span';
}
if (!classname) {
var selStart = editbox.selectionStart;
classname = emptySpanHTML.classAliases[name];
var selEnd = editbox.selectionEnd;
}
return editbox.value.substring(selStart, selEnd);
classname = classname || name;
}
if (name == getValueOf('popupDragHandle')) {
function doSelectionPopup() {
classname += ' popupDragHandle';
var sel = getEditboxSelection();
}
var open = sel.indexOf('[[');
return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]);
var pipe = sel.indexOf('|');
}
var close = sel.indexOf(']]');
emptySpanHTML.classAliases = { popupSecondPreview: 'popupPreview' };
if (open == -1 || (pipe == -1 && close == -1)) {

return;
// generate html for popup image
}
// <a id="popupImageLinkn"><img id="popupImagen">
if ((pipe != -1 && open > pipe) || (close != -1 && open > close)) {
// where n=idNumber
return;
function imageHTML(article, idNumber) {
}
return simplePrintf(
var article = new Title(sel.substring(open + 2, pipe < 0 ? close : pipe));
'<a id="popupImageLink$1">' +
if (getValueOf('popupOnEditSelection') == 'boxpreview') {
'<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' +
return doSeparateSelectionPopup(sel, article);
'</a>',
}
[idNumber]
if (close > 0 && sel.substring(close + 2).indexOf('[[') >= 0) {
);
return;
}
}

var a = document.createElement('a');
function popTipsSoonFn(id, when, popData) {
a.href = pg.wiki.titlebase + article.urlString();
if (!when) {
mouseOverWikiLink2(a);
when = 250;
if (a.navpopup) {
}
a.navpopup.addHook(function() {
var popTips = function () {
runStopPopupTimer(a.navpopup);
setupTooltips(document.getElementById(id), false, true, popData);
}, 'unhide', 'after');
};
}
return function () {
}
setTimeout(popTips, when, popData);
function doSeparateSelectionPopup(str, article) {
};
var div = document.getElementById('selectionPreview');
}
if (!div) {

div = document.createElement('div');
function setPopupTipsAndHTML(html, divname, idnumber, popData) {
div.id = 'selectionPreview';
setPopupHTML(
try {
html,
var box = document.editform.wpTextbox1;
divname,
box.parentNode.insertBefore(div, box);
idnumber,
} catch (error) {
getValueOf('popupSubpopups') ? popTipsSoonFn(divname + idnumber, null, popData) : null
return;
);
}
}
}
// ENDFILE: htmloutput.js
var p = prepPreviewmaker(str, article, newNavpopup(document.createElement('a'), article));

p.makePreview();
// STARTFILE: mouseout.js
if (p.html) {
//////////////////////////////////////////////////
div.innerHTML = p.html;
// fuzzy checks
}

div.ranSetupTooltipsAlready = false;
function fuzzyCursorOffMenus(x, y, fuzz, parent) {
popTipsSoonFn('selectionPreview')();
if (!parent) {
}
return null;
function Mousetracker() {
}
this.loopDelay = 400;
var uls = parent.getElementsByTagName('ul');
this.timer = null;
for (var i = 0; i < uls.length; ++i) {
this.active = false;
if (uls[i].className == 'popup_menu') {
this.dirty = true;
if (uls[i].offsetWidth > 0) {
this.hooks = [];
return false;
}
}
Mousetracker.prototype.addHook = function(f) {
} // else {document.title+='.';}
this.hooks.push(f);
}
}
return true;
;
}
Mousetracker.prototype.runHooks = function() {

if (!this.hooks || !this.hooks.length) {
function checkPopupPosition() {
return;
// stop the popup running off the right of the screen
}
// FIXME avoid pg.current.link
var remove = false;
if (pg.current.link && pg.current.link.navpopup) {
var removeObj = {};
pg.current.link.navpopup.limitHorizontalPosition();
var x = this.x
}
, y = this.y
}
, len = this.hooks.length;

for (var i = 0; i < len; ++i) {
function mouseOutWikiLink() {
if (this.hooks[i](x, y) === true) {
//console ('mouseOutWikiLink');
remove = true;
var a = this;
removeObj[i] = true;

}
removeModifierKeyHandler(a);
}

if (remove) {
if (a.navpopup === null || typeof a.navpopup === 'undefined') {
this.removeHooks(removeObj);
return;
}
}
}
if (!a.navpopup.isVisible()) {
;
a.navpopup.banish();
Mousetracker.prototype.removeHooks = function(removeObj) {
return;
var newHooks = [];
}
var len = this.hooks.length;
restoreTitle(a);
for (var i = 0; i < len; ++i) {
Navpopup.tracker.addHook(posCheckerHook(a.navpopup));
if (!removeObj[i]) {
}
newHooks.push(this.hooks[i]);

}
function posCheckerHook(navpop) {
}
return function () {
this.hooks = newHooks;
if (!navpop.isVisible()) {
}
return true; /* remove this hook */
;
}
Mousetracker.prototype.track = function(e) {
if (Navpopup.tracker.dirty) {
e = e || window.event;
return false;
var x, y;
}
if (e) {
var x = Navpopup.tracker.x,
if (e.pageX) {
y = Navpopup.tracker.y;
x = e.pageX;
var mouseOverNavpop =
y = e.pageY;
navpop.isWithin(x, y, navpop.fuzz, navpop.mainDiv) ||
} else if (typeof e.clientX != 'undefined') {
!fuzzyCursorOffMenus(x, y, navpop.fuzz, navpop.mainDiv);
var left, top, docElt = document.documentElement;

if (docElt) {
// FIXME it'd be prettier to do this internal to the Navpopup objects
left = docElt.scrollLeft;
var t = getValueOf('popupHideDelay');
}
if (t) {
left = left || document.body.scrollLeft || document.scrollLeft || 0;
t = t * 1000;
if (docElt) {
}
top = docElt.scrollTop;
if (!t) {
}
if (!mouseOverNavpop) {
top = top || document.body.scrollTop || document.scrollTop || 0;
if (navpop.parentAnchor) {
x = e.clientX + left;
restoreTitle(navpop.parentAnchor);
y = e.clientY + top;
}
} else {
navpop.banish();
return;
return true; /* remove this hook */
}
}
this.setPosition(x, y);
return false;
}
}
}
// we have a hide delay set
;
var d = Number(new Date());
Mousetracker.prototype.setPosition = function(x, y) {
if (!navpop.mouseLeavingTime) {
this.x = x;
navpop.mouseLeavingTime = d;
this.y = y;
return false;
if (this.dirty || this.hooks.length === 0) {
}
this.dirty = false;
if (mouseOverNavpop) {
return;
navpop.mouseLeavingTime = null;
}
return false;
if (typeof this.lastHook_x != 'number') {
}
this.lastHook_x = -100;
if (d - navpop.mouseLeavingTime > t) {
this.lastHook_y = -100;
navpop.mouseLeavingTime = null;
}
navpop.banish();
var diff = (this.lastHook_x - x) * (this.lastHook_y - y);
return true; /* remove this hook */
diff = diff >= 0 ? diff : -diff;
}
if (diff > 1) {
return false;
this.lastHook_x = x;
};
this.lastHook_y = y;
}
if (this.dirty) {

this.dirty = false;
function runStopPopupTimer(navpop) {
} else {
// at this point, we should have left the link but remain within the popup
this.runHooks();
// so we call this function again until we leave the popup.
}
if (!navpop.stopPopupTimer) {
}
navpop.stopPopupTimer = setInterval(posCheckerHook(navpop), 500);
}
navpop.addHook(
;
function () {
Mousetracker.prototype.enable = function() {
clearInterval(navpop.stopPopupTimer);
if (this.active) {
},
return;
'hide',
}
'before'
this.active = true;
);
this.savedHandler = document.onmousemove;
}
var savedThis = this;
}
document.onmousemove = function(e) {
// ENDFILE: mouseout.js
savedThis.track.apply(savedThis, [e]);

}
// STARTFILE: previewmaker.js
;
/**
if (this.loopDelay) {
* @fileoverview
this.timer = setInterval(function() {
* Defines the {@link Previewmaker} object, which generates short previews from wiki markup.
savedThis.runHooks();
*/
}, this.loopDelay);

}
/**
}
* Creates a new Previewmaker
;
* @constructor
Mousetracker.prototype.disable = function() {
* @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext.
if (!this.active) {
* @param {String} wikiText The Wikitext source of the page we wish to preview.
return;
* @param {String} baseUrl The url we should prepend when creating relative urls.
}
* @param {Navpopup} owner The navpop associated to this preview generator
if (typeof this.savedHandler === 'function') {
*/
document.onmousemove = this.savedHandler;
function Previewmaker(wikiText, baseUrl, owner) {
} else {
/** The wikitext which is manipulated to generate the preview. */
delete document.onmousemove;
this.originalData = wikiText;
}
this.baseUrl = baseUrl;
if (this.timer) {
this.owner = owner;
clearInterval(this.timer);

}
this.maxCharacters = getValueOf('popupMaxPreviewCharacters');
this.active = false;
this.maxSentences = getValueOf('popupMaxPreviewSentences');
}

;
this.setData();
function Navpopup() {
}
this.uid = Navpopup.uid++;

this.visible = false;
Previewmaker.prototype.setData = function () {
this.noshow = false;
var maxSize = Math.max(10000, 2 * this.maxCharacters);
this.hooks = {
this.data = this.originalData.substring(0, maxSize);
create: [],
};
unhide: [],

hide: [],
/**
};
* Remove HTML comments
this.hookIds = {};
* @private
this.downloads = [];
*/
this.pending = null;
Previewmaker.prototype.killComments = function () {
this.fuzz = 5;
// this also kills one trailing newline, eg [[diamyo]]
this.constrained = true;
this.data = this.data.replace(
this.width = 0;
RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'),
this.height = 0;
''
this.mainDiv = null;
);
this.createMainDiv();
};
}

Navpopup.uid = 0;
/**
Navpopup.prototype.isVisible = function() {
* @private
return this.visible;
*/
}
Previewmaker.prototype.killDivs = function () {
;
// say goodbye, divs (can be nested, so use * not *?)
Navpopup.prototype.reposition = function(x, y, noLimitHor) {
this.data = this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>', 'gi'), '');
log('reposition(' + x + ',' + y + ',' + noLimitHor + ')');
};
if (typeof x != 'undefined' && x !== null) {

this.left = x;
/**
}
* @private
if (typeof y != 'undefined' && y !== null) {
*/
this.top = y;
Previewmaker.prototype.killGalleries = function () {
}
this.data = this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>', 'gi'), '');
if (typeof this.left != 'undefined' && typeof this.top != 'undefined') {
};
this.mainDiv.style.left = this.left + 'px';

this.mainDiv.style.top = this.top + 'px';
/**
}
* @private
if (!noLimitHor) {
*/
this.limitHorizontalPosition();
Previewmaker.prototype.kill = function (opening, closing, subopening, subclosing, repl) {
}
var oldk = this.data;
}
var k = this.killStuff(this.data, opening, closing, subopening, subclosing, repl);
;
while (k.length < oldk.length) {
Navpopup.prototype.limitHorizontalPosition = function() {
oldk = k;
if (!this.constrained || this.tooWide) {
k = this.killStuff(k, opening, closing, subopening, subclosing, repl);
return;
}
}
this.data = k;
this.updateDimensions();
};
var x = this.left;

var w = this.width;
/**
var cWidth = document.body.clientWidth;
* @private
if (x + w >= cWidth || (x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width && x > cWidth - this.maxWidth)) {
*/
this.mainDiv.style.left = '-10000px';
Previewmaker.prototype.killStuff = function (
this.mainDiv.style.width = this.maxWidth + 'px';
txt,
var naturalWidth = parseInt(this.mainDiv.offsetWidth, 10);
opening,
var newLeft = cWidth - naturalWidth - 1;
closing,
if (newLeft < 0) {
subopening,
newLeft = 0;
subclosing,
this.tooWide = true;
repl
}
) {
log('limitHorizontalPosition: moving to (' + newLeft + ',' + this.top + ');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth);
var op = this.makeRegexp(opening);
this.reposition(newLeft, null, true);
var cl = this.makeRegexp(closing, '^');
}
var sb = subopening ? this.makeRegexp(subopening, '^') : null;
}
var sc = subclosing ? this.makeRegexp(subclosing, '^') : cl;
;
if (!op || !cl) {
Navpopup.highest = 1000;
alert('Navigation Popups error: op or cl is null! something is wrong.');
Navpopup.prototype.raise = function() {
return;
this.mainDiv.style.zIndex = Navpopup.highest + 1;
}
++Navpopup.highest;
if (!op.test(txt)) {
}
return txt;
;
}
Navpopup.prototype.show = function() {
var ret = '';
if (this.noshow) {
var opResult = op.exec(txt);
return;
ret = txt.substring(0, opResult.index);
}
txt = txt.substring(opResult.index + opResult[0].length);
this.reposition();
var depth = 1;
this.raise();
while (txt.length > 0) {
this.unhide();
var removal = 0;
}
if (depth == 1 && cl.test(txt)) {
;
depth--;
Navpopup.prototype.showSoonIfStable = function(time) {
removal = cl.exec(txt)[0].length;
log('showSoonIfStable, time=' + time);
} else if (depth > 1 && sc.test(txt)) {
if (this.visible) {
depth--;
return;
removal = sc.exec(txt)[0].length;
}
} else if (sb && sb.test(txt)) {
this.noshow = false;
depth++;
this.stable_x = -10000;
removal = sb.exec(txt)[0].length;
this.stable_y = -10000;
}
var stableShow = function() {
if (!removal) {
log('stableShow called');
removal = 1;
var new_x = Navpopup.tracker.x
}
, new_y = Navpopup.tracker.y;
txt = txt.substring(removal);
var dx = savedThis.stable_x - new_x
if (depth === 0) {
, dy = savedThis.stable_y - new_y;
break;
var fuzz2 = 0;
}
if (dx * dx <= fuzz2 && dy * dy <= fuzz2) {
}
log('mouse is stable');
return ret + (repl || '') + txt;
clearInterval(savedThis.showSoonStableTimer);
};
savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]);

savedThis.show.apply(savedThis, []);
/**
savedThis.limitHorizontalPosition.apply(savedThis, []);
* @private
return;
*/
}
Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) {
savedThis.stable_x = new_x;
prefix = prefix || '';
savedThis.stable_y = new_y;
suffix = suffix || '';
};
var savedThis = this;
var reStr = '';
var flags = '';
this.showSoonStableTimer = setInterval(stableShow, time / 2);
if (isString(x)) {
}
reStr = prefix + literalizeRegex(x) + suffix;
;
} else if (isRegExp(x)) {
Navpopup.prototype.banish = function() {
var s = x.toString().substring(1);
log('banish called');
var sp = s.split('/');
this.noshow = true;
flags = sp[sp.length - 1];
if (this.showSoonStableTimer) {
sp[sp.length - 1] = '';
log('clearing showSoonStableTimer');
s = sp.join('/');
clearInterval(this.showSoonStableTimer);
s = s.substring(0, s.length - 1);
}
reStr = prefix + s + suffix;
this.hide();
} else {
}
log('makeRegexp failed');
;
}
Navpopup.prototype.runHooks = function(key, when) {

if (!this.hooks[key]) {
log('makeRegexp: got reStr=' + reStr + ', flags=' + flags);
return;
return RegExp(reStr, flags);
}
};
var keyHooks = this.hooks[key];

var len = keyHooks.length;
/**
for (var i = 0; i < len; ++i) {
* @private
if (keyHooks[i] && keyHooks[i].when == when) {
*/
if (keyHooks[i].hook.apply(this, [])) {
Previewmaker.prototype.killBoxTemplates = function () {
if (keyHooks[i].hookId) {
// taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general
delete this.hookIds[keyHooks[i].hookId];
// also, have float_begin, ... float_end
}
this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{');
keyHooks[i] = null;

}
// infoboxes etc
}
// from [[User:Zyxw/popups.js]]: kill frames too
}
this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{');
}
};
;

Navpopup.prototype.addHook = function(hook, key, when, uid) {
/** <!--
when = when || 'after';
* @private
if (!this.hooks[key]) {
*/
return;
Previewmaker.prototype.killTemplates = function () {
}
this.kill('{{', '}}', '{', '}', ' ');
var hookId = null;
};
if (uid) {

hookId = [key, when, uid].join('|');
/** -->
if (this.hookIds[hookId]) {
* @private
return;
*/
}
Previewmaker.prototype.killTables = function () {
this.hookIds[hookId] = true;
// tables are bad, too
}
// this can be slow, but it's an inprovement over a browser hang
this.hooks[key].push({
// torture test: [[Comparison_of_Intel_Central_Processing_Units]]
hook: hook,
this.kill('{|', /[|]}\s*/, '{|');
when: when,
this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i);
hookId: hookId
// remove lines starting with a pipe for the hell of it (?)
});
this.data = this.data.replace(RegExp('^[|].*$', 'mg'), '');
}
};
;

Navpopup.prototype.createMainDiv = function() {
/**
if (this.mainDiv) {
* @private
return;
*/
}
Previewmaker.prototype.killImages = function () {
this.runHooks('create', 'before');
var forbiddenNamespaceAliases = [];
var mainDiv = document.createElement('div');
jQuery.each(mw.config.get('wgNamespaceIds'), function (_localizedNamespaceLc, _namespaceId) {
var savedThis = this;
if (_namespaceId != pg.nsImageId && _namespaceId != pg.nsCategoryId) {
mainDiv.onclick = function(e) {
return;
savedThis.onclickHandler(e);
}
}
forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments!
;
});
mainDiv.className = this.className ? this.className : 'navpopup_maindiv';

mainDiv.id = mainDiv.className + this.uid;
// images and categories are a nono
mainDiv.style.position = 'absolute';
this.kill(
mainDiv.style.minWidth = '350px';
RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'),
mainDiv.style.display = 'none';
/\]\]\s*/,
mainDiv.className = 'navpopup';
'[',
mainDiv.navpopup = this;
']'
this.mainDiv = mainDiv;
);
document.body.appendChild(mainDiv);
};
this.runHooks('create', 'after');

}
/**
;
* @private
Navpopup.prototype.onclickHandler = function() {
*/
this.raise();
Previewmaker.prototype.killHTML = function () {
}
// kill <ref ...>...</ref>
;
this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i);
Navpopup.prototype.makeDraggable = function(handleName) {

if (!this.mainDiv) {
// let's also delete entire lines starting with <. it's worth a try.
this.createMainDiv();
this.data = this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n');
}

var drag = new Drag();
// and those pesky html tags, but not <nowiki> or <blockquote>
if (!handleName) {
var splitted = this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/);
drag.startCondition = function(e) {
var len = splitted.length;
try {
if (!e.shiftKey) {
for (var i = 1; i < len; i = i + 2) {
switch (splitted[i]) {
return false;
case '<nowiki>':
}
case '</nowiki>':
} catch (err) {
case '<blockquote>':
return false;
case '</blockquote>':
}
break;
return true;
default:
}
splitted[i] = '';
;
}
}
}
var dragHandle;
this.data = splitted.join('');
if (handleName) {
};
dragHandle = document.getElementById(handleName);

}
/**
if (!dragHandle) {
* @private
dragHandle = this.mainDiv;
*/
}
Previewmaker.prototype.killChunks = function () {
var np = this;
// heuristics alert
drag.endHook = function(x, y) {
// chunks of italic text? you crazy, man?
Navpopup.tracker.dirty = true;
var italicChunkRegex = new RegExp(
np.reposition(x, y);
"((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+",
}
'g'
;
);
drag.init(dragHandle, this.mainDiv);
// keep stuff separated, though, so stick in \n (fixes [[Union Jack]]?
}
this.data = this.data.replace(italicChunkRegex, '\n');
;
};
Navpopup.prototype.hide = function() {

this.runHooks('hide', 'before');
/**
this.abortDownloads();
* @private
if (typeof this.visible != 'undefined' && this.visible) {
*/
this.mainDiv.style.display = 'none';
Previewmaker.prototype.mopup = function () {
this.visible = false;
// we simply *can't* be doing with horizontal rules right now
}
this.data = this.data.replace(RegExp('^-{4,}', 'mg'), '');
this.runHooks('hide', 'after');

}
// no indented lines
;
this.data = this.data.replace(RegExp('(^|\\n) *:[^\\n]*', 'g'), '');
Navpopup.prototype.unhide = function() {

this.runHooks('unhide', 'before');
// replace __TOC__, __NOTOC__ and whatever else there is
if (typeof this.visible != 'undefined' && !this.visible) {
// this'll probably do
this.mainDiv.style.display = 'inline';
this.data = this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'), '');
this.visible = true;
};
}

this.runHooks('unhide', 'after');
/**
}
* @private
;
*/
Navpopup.prototype.setInnerHTML = function(html) {
Previewmaker.prototype.firstBit = function () {
this.mainDiv.innerHTML = html;
// dont't be givin' me no subsequent paragraphs, you hear me?
}
/// first we "normalize" section headings, removing whitespace after, adding before
;
var d = this.data;
Navpopup.prototype.updateDimensions = function() {

this.width = parseInt(this.mainDiv.offsetWidth, 10);
if (getValueOf('popupPreviewCutHeadings')) {
this.height = parseInt(this.mainDiv.offsetHeight, 10);
this.data = this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 ');
}
/// then we want to get rid of paragraph breaks whose text ends badly
;
this.data = this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n');
Navpopup.prototype.isWithin = function(x, y) {

if (!this.visible) {
this.data = this.data.replace(RegExp('^[\\s\\n]*'), '');
return false;
var stuff = RegExp('^([^\\n]|\\n[^\\n\\s])*').exec(this.data);
}
if (stuff) {
this.updateDimensions();
d = stuff[0];
var fuzz = this.fuzz || 0;
}
return (x + fuzz >= this.left && x - fuzz <= this.left + this.width && y + fuzz >= this.top && y - fuzz <= this.top + this.height);
if (!getValueOf('popupPreviewFirstParOnly')) {
}
d = this.data;
;
}
Navpopup.prototype.addDownload = function(download) {

if (!download) {
/// now put \n\n after sections so that bullets and numbered lists work
return;
d = d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n');
}
}
this.downloads.push(download);

}
// Split sentences. Superfluous sentences are RIGHT OUT.
;
// note: exactly 1 set of parens here needed to make the slice work
Navpopup.prototype.abortDownloads = function() {
d = d.parenSplit(RegExp('([!?.]+["' + "'" + ']*\\s)', 'g'));
for (var i = 0; i < this.downloads.length; ++i) {
// leading space is bad, mmkay?
var d = this.downloads[i];
d[0] = d[0].replace(RegExp('^\\s*'), '');
if (d && d.abort) {

d.abort();
var notSentenceEnds = RegExp(
}
'([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$',
}
'i'
this.downloads = [];
);
}
d = this.fixSentenceEnds(d, notSentenceEnds);
;

Navpopup.tracker = new Mousetracker();
this.fullLength = d.join('').length;
function delFmt(x) {
var n = this.maxSentences;
if (!x.length) {
var dd = this.firstSentences(d, n);
return '';

}
do {
return "<del class='popupDiff'>" + x.join('') + '</del>';
dd = this.firstSentences(d, n);
}
--n;
function insFmt(x) {
} while (dd.length > this.maxCharacters && n !== 0);
if (!x.length) {

return '';
this.data = dd;
}
};
return "<ins class='popupDiff'>" + x.join('') + '</ins>';

}
/**
function countCrossings(a, b, i, eject) {
* @private
if (!b[i].row && b[i].row !== 0) {
*/
return -1;
Previewmaker.prototype.fixSentenceEnds = function (strs, reg) {
}
// take an array of strings, strs
var count = 0;
// join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg
for (var j = 0; j < a.length; ++j) {

if (!a[j].row && a[j].row !== 0) {
for (var i = 0; i < strs.length - 2; ++i) {
continue;
if (reg.test(strs[i])) {
}
var a = [];
if ((j - b[i].row) * (i - a[j].row) > 0) {
for (var j = 0; j < strs.length; ++j) {
if (eject) {
if (j < i) {
return true;
a[j] = strs[j];
}
}
count++;
if (j == i) {
}
a[i] = strs[i] + strs[i + 1] + strs[i + 2];
}
}
return count;
}
if (j > i + 2) {
a[j - 2] = strs[j];
function shortenDiffString(str, context) {
}
var re = RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)');
}
var splitted = str.parenSplit(re);
return this.fixSentenceEnds(a, reg);
var ret = [''];
}
for (var i = 0; i < splitted.length; i += 2) {
}
if (splitted[i].length < 2 * context) {
return strs;
ret[ret.length - 1] += splitted[i];
};
if (i + 1 < splitted.length) {

ret[ret.length - 1] += splitted[i + 1];
/**
}
* @private
continue;
*/
} else {
Previewmaker.prototype.firstSentences = function (strs, howmany) {
if (i > 0) {
var t = strs.slice(0, 2 * howmany);
ret[ret.length - 1] += splitted[i].substring(0, context);
return t.join('');
}
};
if (i + 1 < splitted.length) {

ret.push(splitted[i].substring(splitted[i].length - context) + splitted[i + 1]);
/**
}
* @private
}
*/
}
Previewmaker.prototype.killBadWhitespace = function () {
while (ret.length > 0 && !ret[0]) {
// also cleans up isolated '''', eg [[Suntory Sungoliath]]
ret = ret.slice(1);
this.data = this.data.replace(RegExp("^ *'+ *$", 'gm'), '');
}
};
return ret;

}
/**
function diffString(o, n, simpleSplit) {
* Runs the various methods to generate the preview.
var splitRe = RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)');
* The preview is stored in the <code>html</html> field.
var out, i, oSplitted, nSplitted;
* @private
if (simpleSplit) {
*/
oSplitted = o.split(/\b/);
Previewmaker.prototype.makePreview = function () {
nSplitted = n.split(/\b/);
if (
} else {
this.owner.article.namespaceId() != pg.nsTemplateId &&
oSplitted = o.parenSplit(splitRe);
this.owner.article.namespaceId() != pg.nsImageId
nSplitted = n.parenSplit(splitRe);
) {
}
this.killComments();
for (i = 0; i < oSplitted.length; ++i) {
this.killDivs();
oSplitted[i] = oSplitted[i].entify();
this.killGalleries();
}
this.killBoxTemplates();
for (i = 0; i < nSplitted.length; ++i) {

nSplitted[i] = nSplitted[i].entify();
if (getValueOf('popupPreviewKillTemplates')) {
}
this.killTemplates();
out = diff(oSplitted, nSplitted);
} else {
var str = '';
this.killMultilineTemplates();
var acc = [];
}
var maxOutputPair = 0;
this.killTables();
for (i = 0; i < out.n.length; ++i) {
this.killImages();
if (out.n[i].paired) {
this.killHTML();
if (maxOutputPair > out.n[i].row) {
this.killChunks();
out.o[out.n[i].row] = out.o[out.n[i].row].text;
this.mopup();
out.n[i] = out.n[i].text;

}
this.firstBit();
if (maxOutputPair < out.n[i].row) {
this.killBadWhitespace();
maxOutputPair = out.n[i].row;
} else {
}
this.killHTML();
}
}
}
this.html = wiki2html(this.data, this.baseUrl); // needs livepreview
for (i = 0; i < out.o.length && !out.o[i].paired; ++i) {
this.fixHTML();
acc.push(out.o[i]);
this.stripLongTemplates();
}
};
str += delFmt(acc);

acc = [];
/**
for (i = 0; i < out.n.length; ++i) {
* @private
while (i < out.n.length && !out.n[i].paired) {
*/
acc.push(out.n[i++]);
Previewmaker.prototype.esWiki2HtmlPart = function (data) {
}
var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink
str += insFmt(acc);
reLinks.lastIndex = 0; //reset regex
acc = [];

if (i < out.n.length) {
var match;
str += out.n[i].text;
var result = '';
var m = out.n[i].row + 1;
var postfixIndex = 0;
while (m < out.o.length && !out.o[m].paired) {
while ((match = reLinks.exec(data))) {
acc.push(out.o[m++]);
//match all wikilinks
}
//FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases.
str += delFmt(acc);
result +=
acc = [];
pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) +
}
'<a href="' +
}
Insta.conf.paths.articles +
return str;
pg.escapeQuotesHTML(match[1]) +
}
'">' +
var jsReservedProperties = RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' + '|eval|hasOwnProperty|propertyIsEnumerable' + '|to(Source|String|LocaleString)|(un)?watch|valueOf)$');
pg.escapeQuotesHTML((match[2] ? match[2] : match[1]) + match[3]) +
function diffBugAlert(word) {
'</a>';
if (!diffBugAlert.list[word]) {
postfixIndex = reLinks.lastIndex;
diffBugAlert.list[word] = 1;
}
alert('Bad word: ' + word + '\n\nPlease report this bug.');
//append the rest
}
result += pg.escapeQuotesHTML(data.substring(postfixIndex));
}

diffBugAlert.list = {};
return result;
function makeDiffHashtable(src) {
};
var ret = {};
Previewmaker.prototype.editSummaryPreview = function () {
for (var i = 0; i < src.length; i++) {
var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker
if (jsReservedProperties.test(src[i])) {
reAes.lastIndex = 0; //reset regex
src[i] += '<!-- -->';

}
var match;
if (!ret[src[i]]) {

ret[src[i]] = [];
match = reAes.exec(this.data);
}
if (match) {
try {
//we have a section link. Split it, process it, combine it.
ret[src[i]].push(i);
var prefix = this.data.substring(0, match.index - 1);
} catch (err) {
var section = match[1];
diffBugAlert(src[i]);
var postfix = this.data.substring(reAes.lastIndex);
}

}
var start = "<span class='autocomment'>";
return ret;
var end = '</span>';
}
if (prefix.length > 0) {
function diff(o, n) {
start = this.esWiki2HtmlPart(prefix) + ' ' + start + '- ';
var ns = makeDiffHashtable(n);
}
var os = makeDiffHashtable(o);
if (postfix.length > 0) {
var i;
end = ': ' + end + this.esWiki2HtmlPart(postfix);
for (i in ns) {
}
if (ns[i].length == 1 && os[i] && os[i].length == 1) {

n[ns[i][0]] = {
var t = new Title().fromURL(this.baseUrl);
text: n[ns[i][0]],
t.anchorFromUtf(section);
row: os[i][0],
var sectionLink =
paired: true
Insta.conf.paths.articles +
};
pg.escapeQuotesHTML(t.toString(true)) +
o[os[i][0]] = {
'#' +
text: o[os[i][0]],
pg.escapeQuotesHTML(t.anchor);
row: ns[i][0],
return (
paired: true
start + '<a href="' + sectionLink + '">&rarr;</a> ' + pg.escapeQuotesHTML(section) + end
};
);
}
}
}

for (i = 0; i < n.length - 1; i++) {
//else there's no section link, htmlify the whole thing.
if (n[i].paired && !n[i + 1].paired && n[i].row + 1 < o.length && !o[n[i].row + 1].paired && n[i + 1] == o[n[i].row + 1]) {
return this.esWiki2HtmlPart(this.data);
n[i + 1] = {
};
text: n[i + 1],

row: n[i].row + 1,
/** Test function for debugging preview problems one step at a time. */
paired: true
/*eslint-disable */
};
function previewSteps(txt) {
o[n[i].row + 1] = {
try {
text: o[n[i].row + 1],
txt = txt || document.editform.wpTextbox1.value;
row: i + 1,
} catch (err) {
paired: true
if (pg.cache.pages.length > 0) {
};
txt = pg.cache.pages[pg.cache.pages.length - 1].data;
}
} else {
}
alert('provide text or use an edit page');
for (i = n.length - 1; i > 0; i--) {
}
if (n[i].paired && !n[i - 1].paired && n[i].row > 0 && !o[n[i].row - 1].paired && n[i - 1] == o[n[i].row - 1]) {
}
n[i - 1] = {
txt = txt.substring(0, 10000);
text: n[i - 1],
var base = pg.wiki.articlebase + Title.fromURL(document.location.href).urlString();
row: n[i].row - 1,
var p = new Previewmaker(txt, base, pg.current.link.navpopup);
paired: true
if (this.owner.article.namespaceId() != pg.nsTemplateId) {
};
p.killComments();
o[n[i].row - 1] = {
if (!confirm('done killComments(). Continue?\n---\n' + p.data)) {
text: o[n[i].row - 1],
return;
row: i - 1,
}
paired: true
p.killDivs();
};
if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) {
}
return;
}
}
return {
p.killGalleries();
o: o,
if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) {
n: n
return;
};
}
}
p.killBoxTemplates();
function setSiteInfo() {
if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) {
if (window.popupLocalDebug) {
return;
pg.wiki.hostname = 'en.wikipedia.org';
}
} else {

pg.wiki.hostname = location.hostname;
if (getValueOf('popupPreviewKillTemplates')) {
}
p.killTemplates();
pg.wiki.wikimedia = RegExp('(wiki([pm]edia|source|books|news|quote|versity|species|voyage|data)|metawiki|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname);
if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) {
pg.wiki.wikia = RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname);
return;
pg.wiki.isLocal = RegExp('^localhost').test(pg.wiki.hostname);
}
pg.wiki.commons = pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org' ? 'commons.wikimedia.org' : null;
} else {
pg.wiki.lang = mw.config.get('wgContentLanguage');
p.killMultilineTemplates();
var port = location.port ? ':' + location.port : '';
if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) {
pg.wiki.sitebase = pg.wiki.hostname + port;
return;
}
}
function setUserInfo() {
}
var params = {

action: 'query',
p.killTables();
list: 'users',
if (!confirm('done killTables(). Continue?\n---\n' + p.data)) {
ususers: mw.config.get('wgUserName'),
return;
usprop: 'rights',
}
};
p.killImages();
pg.user.canReview = false;
if (!confirm('done killImages(). Continue?\n---\n' + p.data)) {
if (getValueOf('popupReview')) {
return;
getMwApi().get(params).done(function(data) {
}
var rights = data.query.users[0].rights;
p.killHTML();
pg.user.canReview = rights.indexOf('review') !== -1;
if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) {
});
return;
}
}
}
p.killChunks();
function fetchSpecialPageNames() {
if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) {
var params = {
return;
action: 'query',
}
meta: 'siteinfo',
p.mopup();
siprop: 'specialpagealiases',
if (!confirm('done mopup(). Continue?\n---\n' + p.data)) {
formatversion: 2,
return;
uselang: 'content',
}
maxage: 3600,

};
p.firstBit();
return getMwApi().get(params).then(function(data) {
if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) {
pg.wiki.specialpagealiases = data.query.specialpagealiases;
return;
});
}
}
p.killBadWhitespace();
function setTitleBase() {
if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) {
var protocol = window.popupLocalDebug ? 'http:' : location.protocol;
return;
pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, '');
}
pg.wiki.botInterfacePath = mw.config.get('wgScript');
}
pg.wiki.APIPath = mw.config.get('wgScriptPath') + '/api.php';

var titletail = pg.wiki.botInterfacePath + '?title=';
p.html = wiki2html(p.data, base); // needs livepreview
pg.wiki.titlebase = protocol + '//' + pg.wiki.sitebase + titletail;
p.fixHTML();
pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath;
if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) {
pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath;
return;
pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath;
}
pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath;
p.stripLongTemplates();
pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath;
if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) {
pg.re.basenames = RegExp('^(' + map(literalizeRegex, [pg.wiki.titlebase, pg.wiki.articlebase, ]).join('|') + ')');
return;
}
}
function setMainRegex() {
alert('finished preview - end result follows.\n---\n' + p.html);
var reStart = '[^:]*://';
}
var preTitles = literalizeRegex(mw.config.get('wgScriptPath')) + '/(?:index[.]php|wiki[.]phtml)[?]title=';
/*eslint-enable */
preTitles += '|' + literalizeRegex(pg.wiki.articlePath + '/');

var reEnd = '(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?';
/**
pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd);
* Works around livepreview bugs.
}
* @private
function buildSpecialPageGroup(specialPageObj) {
*/
var variants = [];
Previewmaker.prototype.fixHTML = function () {
variants.push(mw.util.escapeRegExp(specialPageObj['realname']));
if (!this.html) {
variants.push(mw.util.escapeRegExp(encodeURI(specialPageObj['realname'])));
return;
specialPageObj.aliases.forEach(function(alias) {
}
variants.push(mw.util.escapeRegExp(alias));

variants.push(mw.util.escapeRegExp(encodeURI(alias)));
var ret = this.html;
});

return variants.join('|');
// fix question marks in wiki links
}
// maybe this'll break some stuff :-(
function setRegexps() {
ret = ret.replace(
setMainRegex();
RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'),
var sp = nsRe(pg.nsSpecialId);
'$1%3F$2'
pg.re.urlNoPopup = RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)');
);
pg.wiki.specialpagealiases.forEach(function(specialpage) {
ret = ret.replace(
if (specialpage.realname === 'Contributions') {
RegExp("(<a href='" + pg.wiki.articlePath + "/[^']*)[?](.*?')", 'g'),
pg.re.contribs = RegExp('(title=|/)' + sp + '(?:%3A|:)(?:' + buildSpecialPageGroup(specialpage) + ')' + '(&target=|/|/' + nsRe(pg.nsUserId) + ':)(.*)', 'i');
'$1%3F$2'
} else if (specialpage.realname === 'Diff') {
);
pg.re.specialdiff = RegExp('/' + sp + '(?:%3A|:)(?:' + buildSpecialPageGroup(specialpage) + ')' + '/([^?#]*)', 'i');
// FIXME fix up % too
} else if (specialpage.realname === 'Emailuser') {

pg.re.email = RegExp('(title=|/)' + sp + '(?:%3A|:)(?:' + buildSpecialPageGroup(specialpage) + ')' + '(&target=|/|/(?:' + nsRe(pg.nsUserId) + ':)?)(.*)', 'i');
this.html = ret;
} else if (specialpage.realname === 'Whatlinkshere') {
};
pg.re.backlinks = RegExp('(title=|/)' + sp + '(?:%3A|:)(?:' + buildSpecialPageGroup(specialpage) + ')' + '(&target=|/)([^&]*)', 'i');

}
/**
});
* Generates the preview and displays it in the current popup.
var im = nsReImage();

pg.re.image = RegExp('(^|\\[\\[)' + im + ': *([^|\\]]*[^|\\] ])' + '([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' + '(' + getValueOf('popupImageVarsRegexp') + ')' + ' *= *(?:\\[\\[ *)?(?:' + im + ':)?' + '([^|]*?)(?:\\]\\])? *[|]? *\\n', 'img');
* Does nothing if the generated preview is invalid or consists of whitespace only.
pg.re.imageBracketCount = 6;
* Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true.
pg.re.category = RegExp('\\[\\[' + nsRe(pg.nsCategoryId) + ': *([^|\\]]*[^|\\] ]) *', 'i');
*/
pg.re.categoryBracketCount = 1;
Previewmaker.prototype.showPreview = function () {
pg.re.ipUser = RegExp('^' + '(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' + '|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' + '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$');
this.makePreview();
pg.re.stub = RegExp(getValueOf('popupStubRegexp'), 'im');
if (typeof this.html != typeof '') {
pg.re.disambig = RegExp(getValueOf('popupDabRegexp'), 'im');
return;
pg.re.oldid = RegExp('[?&]oldid=([^&]*)');
}
pg.re.diff = RegExp('[?&]diff=([^&]*)');
if (RegExp('^\\s*$').test(this.html)) {
}
return;
function setupCache() {
}
pg.cache.pages = [];
setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber);
}
setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, {
function setMisc() {
owner: this.owner,
pg.current.link = null;
});
pg.current.links = [];
var more = this.fullLength > this.data.length ? this.moreLink() : '';
pg.current.linksHash = {};
setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber);
setupCache();
};
pg.timer.checkPopupPosition = null;

pg.counter.loop = 0;
/**
pg.idNumber = 0;
* @private
pg.misc.decodeExtras = [{
*/
from: '%2C',
Previewmaker.prototype.moreLink = function () {
to: ','
var a = document.createElement('a');
}, {
a.className = 'popupMoreLink';
from: '_',
a.innerHTML = popupString('more...');
to: ' '
var savedThis = this;
}, {
a.onclick = function () {
from: '%24',
savedThis.maxCharacters += 2000;
to: '$'
savedThis.maxSentences += 20;
}, {
savedThis.setData();
from: '%26',
savedThis.showPreview();
to: '&'
};
}, ];
return a;
}
};
function getMwApi() {

if (!pg.api.client) {
/**
pg.api.userAgent = 'Navigation popups/1.0 (' + mw.config.get('wgServerName') + ')';
* @private
pg.api.client = new mw.Api({
*/
ajax: {
Previewmaker.prototype.stripLongTemplates = function () {
headers: {
// operates on the HTML!
'Api-User-Agent': pg.api.userAgent,
this.html = this.html.replace(
},
RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'),
},
''
});
);
}
this.html = this.html.split('\n').join(' '); // workaround for <pre> templates
return pg.api.client;
this.html = this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]', 'gi'), '');
}
};
function setupPopups(callback) {

if (setupPopups.completed) {
/**
if (typeof callback === 'function') {
* @private
callback();
*/
}
Previewmaker.prototype.killMultilineTemplates = function () {
return;
this.kill('{{{', '}}}');
}
this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{');
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'mediawiki.user', 'user.options', 'mediawiki.jqueryMsg', ]).then(fetchSpecialPageNames).then(function() {
};
setupDebugging();
// ENDFILE: previewmaker.js
setSiteInfo();

setTitleBase();
// STARTFILE: querypreview.js
setOptions();
function loadAPIPreview(queryType, article, navpop) {
setUserInfo();
var art = new Title(article).urlString();
setNamespaces();
var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';
setInterwiki();
var htmlGenerator = function (/*a, d*/) {
setRegexps();
alert('invalid html generator');
setRedirs();
};
setMisc();
var usernameart = '';
setupLivePreview();
switch (queryType) {
setupTooltips();
case 'history':
log('In setupPopups(), just called setupTooltips()');
url +=
Navpopup.tracker.enable();
'titles=' + art + '&prop=revisions&rvlimit=' + getValueOf('popupHistoryPreviewLimit');
setupPopups.completed = true;
htmlGenerator = APIhistoryPreviewHTML;
if (typeof callback === 'function') {
break;
callback();
case 'category':
}
url += 'list=categorymembers&cmtitle=' + art;
});
htmlGenerator = APIcategoryPreviewHTML;
}
break;
function defaultNavlinkSpec() {
case 'userinfo':
var str = '';
var username = new Title(article).userName();
str += '<b><<mainlink|shortcut= >></b>';
usernameart = encodeURIComponent(username);
if (getValueOf('popupLastEditLink')) {
if (pg.re.ipUser.test(username)) {
str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}';
url += 'list=blocks&bkprop=range|restrictions&bkip=' + usernameart;
}
} else {
str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>';
url +=
str += 'if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}';
'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' +
str += 'if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}';
usernameart +
var editstr = '<<edit|shortcut=e>>';
'&meta=globaluserinfo&guiprop=groups|unattached&guiuser=' +
var editOldidStr = 'if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}';
usernameart +
var historystr = '<<history|shortcut=h>>|<<editors|shortcut=E|>>';
'&uclimit=1&ucprop=timestamp&ucuser=' +
var watchstr = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
usernameart;
str += '<br>if(talk){' + editOldidStr + '|<<new|shortcut=+>>' + '*' + historystr + '*' + watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' + '}else{' + editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';
}
str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';
htmlGenerator = APIuserInfoPreviewHTML;
str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}';
break;
return str;
case 'contribs':
}
usernameart = encodeURIComponent(new Title(article).userName());
function navLinksHTML(article, hint, params) {
url +=
var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>';
'list=usercontribs&ucuser=' +
return navlinkStringToHTML(str, article, params);
usernameart +
}
'&uclimit=' +
function expandConditionalNavlinkString(s, article, z, recursionCount) {
getValueOf('popupContribsPreviewLimit');
var oldid = z.oldid
htmlGenerator = APIcontribsPreviewHTML;
, rcid = z.rcid
break;
, diff = z.diff;
case 'imagepagepreview':
if (typeof recursionCount != typeof 0) {
var trail = '';
recursionCount = 0;
if (getValueOf('popupImageLinks')) {
}
trail = '&list=imageusage&iutitle=' + art;
var conditionalSplitRegex = RegExp('(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i');
}
var splitted = s.parenSplit(conditionalSplitRegex);
url += 'titles=' + art + '&prop=revisions|imageinfo&rvslots=main&rvprop=content' + trail;
var numParens = 5;
htmlGenerator = APIimagepagePreviewHTML;
var ret = splitted[0];
break;
for (var i = 1; i < splitted.length; i = i + numParens + 1) {
case 'backlinks':
var testString = splitted[i + 2 - 1];
url += 'list=backlinks&bltitle=' + art;
var trueString = splitted[i + 3 - 1];
htmlGenerator = APIbacklinksPreviewHTML;
var falseString = splitted[i + 5 - 1];
break;
if (typeof falseString == 'undefined' || !falseString) {
case 'revision':
falseString = '';
if (article.oldid) {
}
url += 'revids=' + article.oldid;
var testResult = null;
} else {
switch (testString) {
url += 'titles=' + article.removeAnchor().urlString();
case 'user':
}
testResult = !!article.userName();
url +=
break;
'&prop=revisions|pageprops|info|images|categories&meta=wikibase&rvslots=main&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max';
case 'talk':
htmlGenerator = APIrevisionPreviewHTML;
testResult = !article.talkPage();
break;
break;
}
case 'admin':
pendingNavpopTask(navpop);
testResult = !!getValueOf('popupAdminLinks');
var callback = function (d) {
break;
log('callback of API functions was hit');
case 'oldid':
if (queryType === 'userinfo') {
testResult = !!(typeof oldid != 'undefined' && oldid);
// We need to do another API request
break;
fetchUserGroupNames(d.data).then(function () {
case 'rcid':
showAPIPreview(queryType, htmlGenerator(article, d, navpop), navpop.idNumber, navpop, d);
testResult = !!(typeof rcid != 'undefined' && rcid);
});
break;
return;
case 'ipuser':
}
testResult = !!article.isIpUser();
showAPIPreview(queryType, htmlGenerator(article, d, navpop), navpop.idNumber, navpop, d);
break;
};
case 'mainspace_en':
var go = function () {
testResult = isInMainNamespace(article) && pg.wiki.hostname == 'en.wikipedia.org';
getPageWithCaching(url, callback, navpop);
break;
return true;
case 'wikimedia':
};
testResult = !!pg.wiki.wikimedia;

break;
if (navpop.visible || !getValueOf('popupLazyDownloads')) {
case 'diff':
go();
testResult = !!(typeof diff != 'undefined' && diff);
} else {
break;
navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_' + queryType + '_QUERY_DATA');
}
}
switch (testResult) {
}
case null:

ret += splitted[i];
function linkList(list) {
break;
list.sort(function (x, y) {
case true:
ret += trueString;
return x == y ? 0 : x < y ? -1 : 1;
});
break;
var buf = [];
case false:
for (var i = 0; i < list.length; ++i) {
ret += falseString;
buf.push(
break;
wikiLink({
}
article: new Title(list[i]),
ret += splitted[i + numParens];
text: list[i].split(' ').join('&nbsp;'),
}
action: 'view',
if (conditionalSplitRegex.test(ret) && recursionCount < 10) {
})
return expandConditionalNavlinkString(ret, article, z, recursionCount + 1);
);
}
}
return ret;
return buf.join(', ');
}
}
function navlinkStringToArray(s, article, params) {

s = expandConditionalNavlinkString(s, article, params);
function getTimeOffset() {
var splitted = s.parenSplit(RegExp('<<(.*?)>>'));
var tz = mw.user.options.get('timecorrection');
var ret = [];

for (var i = 0; i < splitted.length; ++i) {
if (i % 2) {
if (tz) {
if (tz.indexOf('|') > -1) {
var t = new navlinkTag();
// New format
var ss = splitted[i].split('|');
return parseInt(tz.split('|')[1], 10);
t.id = ss[0];
}
for (var j = 1; j < ss.length; ++j) {
}
var sss = ss[j].split('=');
return 0;
if (sss.length > 1) {
}
t[sss[0]] = sss[1];

} else {
function getTimeZone() {
t.text = popupString(sss[0]);
if (!pg.user.timeZone) {
}
var tz = mw.user.options.get('timecorrection');
}
pg.user.timeZone = 'UTC';
t.article = article;

var oldid = params.oldid
if (tz) {
, rcid = params.rcid
var tzComponents = tz.split('|');
, diff = params.diff;
if (tzComponents.length === 3 && tzComponents[0] === 'ZoneInfo') {
if (typeof oldid !== 'undefined' && oldid !== null) {
pg.user.timeZone = tzComponents[2];
t.oldid = oldid;
} else {
}
errlog('Unexpected timezone information: ' + tz);
if (typeof rcid !== 'undefined' && rcid !== null) {
}
t.rcid = rcid;
}
}
}
if (typeof diff !== 'undefined' && diff !== null) {
return pg.user.timeZone;
t.diff = diff;
}
}

if (!t.text && t.id !== 'mainlink') {
/**
t.text = popupString(t.id);
* Should we use an offset or can we use proper timezones
}
*/
ret.push(t);
function useTimeOffset() {
} else {
if (typeof Intl.DateTimeFormat.prototype.formatToParts === 'undefined') {
ret.push(splitted[i]);
// IE 11
}
return true;
}
}
return ret;
var tz = mw.user.options.get('timecorrection');
}
if (tz && tz.indexOf('ZoneInfo|') === -1) {
function navlinkSubstituteHTML(s) {
// System| Default system time, default for users who didn't configure timezone
return s.split('*').join(getValueOf('popupNavLinkSeparator')).split('<menurow>').join('<li class="popup_menu_row">').split('</menurow>').join('</li>').split('<menu>').join('<ul class="popup_menu">').split('</menu>').join('</ul>');
// Offset| Manual defined offset by user
}
return true;
function navlinkDepth(magic, s) {
}
return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length;
return false;
}
}
function navlinkStringToHTML(s, article, params) {

var p = navlinkStringToArray(s, article, params);
/**
var html = '';
* Array of locales for the purpose of javascript locale based formatting
var menudepth = 0;
* Filters down to those supported by the browser. Empty [] === System's default locale
var menurowdepth = 0;
*/
for (var i = 0; i < p.length; ++i) {
function getLocales() {
if (typeof p[i] == typeof '') {
if (!pg.user.locales) {
html += navlinkSubstituteHTML(p[i]);
var userLanguage = document.querySelector('html').getAttribute('lang'); // make sure we have HTML locale
menudepth += navlinkDepth('menu', p[i]);
if (getValueOf('popupLocale')) {
menurowdepth += navlinkDepth('menurow', p[i]);
userLanguage = getValueOf('popupLocale');
} else if (typeof p[i].type != 'undefined' && p[i].type == 'navlinkTag') {
} else if (userLanguage === 'en') {
if (menudepth > 0 && menurowdepth === 0) {
// en.wp tends to treat this as international english / unspecified
html += '<li class="popup_menu_item">' + p[i].html() + '</li>';
// but we have more specific settings in user options
} else {
if (getMWDateFormat() === 'mdy') {
html += p[i].html();
userLanguage = 'en-US';
}
} else {
}
userLanguage = 'en-GB';
}
}
return html;
}
}
pg.user.locales = Intl.DateTimeFormat.supportedLocalesOf([userLanguage, navigator.language]);
function navlinkTag() {
}
this.type = 'navlinkTag';
return pg.user.locales;
}
}
navlinkTag.prototype.html = function() {

this.getNewWin();
/**
this.getPrintFunction();
* Retrieve configured MW date format for this user
var html = '';
* These can be
var opening, closing;
* default
var tagType = 'span';
* dmy: time, dmy
if (!tagType) {
* mdy: time, mdy
opening = '';
* ymd: time, ymd
closing = '';
* dmyt: dmy, time
} else {
* dmyts: dmy, time + seconds
opening = '<' + tagType + ' class="popup_' + this.id + '">';
* ISO 8601: YYYY-MM-DDThh:mm:ss (local time)
closing = '</' + tagType + '>';
*
}
* This isn't too useful for us, as JS doesn't have formatters to match these private specifiers
if (typeof this.print != 'function') {
*/
errlog('Oh dear - invalid print function for a navlinkTag, id=' + this.id);
function getMWDateFormat() {
} else {
return mw.user.options.get('date');
html = this.print(this);
}
if (typeof html != typeof '') {

html = '';
/**
} else if (typeof this.shortcut != 'undefined') {
* Creates a HTML table that's shown in the history and user-contribs popups.
html = addPopupShortcut(html, this.shortcut);
* @param {Object[]} h - a list of revisions, returned from the API
}
* @param {boolean} reallyContribs - true only if we're displaying user contributions
}
*/
return opening + html + closing;
function editPreviewTable(article, h, reallyContribs) {
}
var html = ['<table>'];
;
var day = null;
navlinkTag.prototype.getNewWin = function() {
var curart = article;
getValueOf('popupLinksNewWindow');
var page = null;
if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') {

this.newWin = null;
var makeFirstColumnLinks;
}
if (reallyContribs) {
this.newWin = pg.option.popupLinksNewWindow[this.id];
// We're showing user contributions, so make (diff | hist) links
}
makeFirstColumnLinks = function (currentRevision) {
;
var result = '(';
navlinkTag.prototype.getPrintFunction = function() {
result +=
if (typeof this.id != typeof '' || typeof this.article != typeof {}) {
'<a href="' +
return;
pg.wiki.titlebase +
}
new Title(currentRevision.title).urlString() +
this.noPopup = 1;
'&diff=prev' +
switch (this.id) {
'&oldid=' +
case 'contribs':
currentRevision.revid +
case 'history':
'">' +
case 'whatLinksHere':
popupString('diff') +
case 'userPage':
'</a>';
case 'monobook':
result += '&nbsp;|&nbsp;';
case 'userTalk':
result +=
case 'talk':
'<a href="' +
case 'article':
pg.wiki.titlebase +
case 'lastEdit':
new Title(currentRevision.title).urlString() +
this.noPopup = null;
'&action=history">' +
}
popupString('hist') +
switch (this.id) {
'</a>';
case 'email':
result += ')';
case 'contribs':
return result;
case 'block':
};
case 'unblock':
} else {
case 'userlog':
// It's a regular history page, so make (cur | last) links
case 'userSpace':
var firstRevid = h[0].revid;
case 'deletedContribs':
makeFirstColumnLinks = function (currentRevision) {
this.article = this.article.userName();
var result = '(';
}
result +=
switch (this.id) {
'<a href="' +
case 'userTalk':
pg.wiki.titlebase +
case 'newUserTalk':
new Title(curart).urlString() +
case 'editUserTalk':
'&diff=' +
case 'userPage':
firstRevid +
case 'monobook':
'&oldid=' +
case 'editMonobook':
currentRevision.revid +
case 'blocklog':
'">' +
this.article = this.article.userName(true);
popupString('cur') +
case 'pagelog':
'</a>';
case 'deletelog':
result += '&nbsp;|&nbsp;';
case 'protectlog':
result +=
delete this.oldid;
'<a href="' +
}
pg.wiki.titlebase +
if (this.id == 'editMonobook' || this.id == 'monobook') {
new Title(curart).urlString() +
this.article.append('/monobook.js');
'&diff=prev&oldid=' +
}
currentRevision.revid +
if (this.id != 'mainlink') {
'">' +
this.article = this.article.removeAnchor();
popupString('last') +
}
'</a>';
switch (this.id) {
result += ')';
case 'undelete':
return result;
this.print = specialLink;
};
this.specialpage = 'Undelete';
}
this.sep = '/';

break;
for (var i = 0; i < h.length; ++i) {
case 'whatLinksHere':
if (reallyContribs) {
this.print = specialLink;
page = h[i].title;
this.specialpage = 'Whatlinkshere';
curart = new Title(page);
break;
}
case 'relatedChanges':
var minor = h[i].minor ? '<b>m </b>' : '';
this.print = specialLink;
var editDate = new Date(h[i].timestamp);
this.specialpage = 'Recentchangeslinked';
var thisDay = formattedDate(editDate);
break;
var thisTime = formattedTime(editDate);
case 'move':
if (thisDay == day) {
this.print = specialLink;
thisDay = '';
this.specialpage = 'Movepage';
} else {
break;
day = thisDay;
case 'contribs':
}
this.print = specialLink;
if (thisDay) {
this.specialpage = 'Contributions';
html.push(
break;
'<tr><td colspan=3><span class="popup_history_date">' + thisDay + '</span></td></tr>'
case 'deletedContribs':
);
this.print = specialLink;
}
this.specialpage = 'Deletedcontributions';
html.push('<tr class="popup_history_row_' + (i % 2 ? 'odd' : 'even') + '">');
break;
html.push('<td>' + makeFirstColumnLinks(h[i]) + '</td>');
case 'email':
html.push(
this.print = specialLink;
'<td>' +
this.specialpage = 'EmailUser';
'<a href="' +
this.sep = '/';
pg.wiki.titlebase +
break;
new Title(curart).urlString() +
case 'block':
'&oldid=' +
this.print = specialLink;
h[i].revid +
this.specialpage = 'Blockip';
'">' +
this.sep = '&ip=';
thisTime +
break;
'</a></td>'
case 'unblock':
);
this.print = specialLink;
var col3url = '',
this.specialpage = 'Ipblocklist';
col3txt = '';
this.sep = '&action=unblock&ip=';
if (!reallyContribs) {
break;
var user = h[i].user;
case 'userlog':
if (!h[i].userhidden) {
this.print = specialLink;
if (pg.re.ipUser.test(user)) {
this.specialpage = 'Log';
col3url =
this.sep = '&user=';
pg.wiki.titlebase +
break;
mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] +
case 'blocklog':
':Contributions&target=' +
this.print = specialLink;
new Title(user).urlString();
this.specialpage = 'Log';
} else {
this.sep = '&type=block&page=';
col3url =
break;
pg.wiki.titlebase +
case 'pagelog':
mw.config.get('wgFormattedNamespaces')[pg.nsUserId] +
this.print = specialLink;
':' +
this.specialpage = 'Log';
new Title(user).urlString();
this.sep = '&page=';
}
break;
col3txt = pg.escapeQuotesHTML(user);
case 'protectlog':
} else {
this.print = specialLink;
col3url = getValueOf('popupRevDelUrl');
this.specialpage = 'Log';
col3txt = pg.escapeQuotesHTML(popupString('revdel'));
this.sep = '&type=protect&page=';
}
break;
} else {
case 'deletelog':
col3url = pg.wiki.titlebase + curart.urlString();
this.print = specialLink;
col3txt = pg.escapeQuotesHTML(page);
this.specialpage = 'Log';
}
this.sep = '&type=delete&page=';
html.push(
break;
'<td>' +
case 'userSpace':
(reallyContribs ? minor : '') +
this.print = specialLink;
'<a href="' +
this.specialpage = 'PrefixIndex';
col3url +
this.sep = '&namespace=2&prefix=';
'">' +
break;
col3txt +
case 'search':
'</a></td>'
this.print = specialLink;
);
this.specialpage = 'Search';
var comment = '';
this.sep = '&fulltext=Search&search=';
var c = h[i].comment || ( typeof h[i].slots !== 'undefined' ? h[i].slots.main.content : null );
break;
if (c) {
case 'thank':
comment = new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview();
this.print = specialLink;
} else if (h[i].commenthidden) {
this.specialpage = 'Thanks';
comment = popupString('revdel');
this.sep = '/';
}
this.article.value = this.diff !== 'prev' ? this.diff : this.oldid;
html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>');
break;
html.push('</tr>');
case 'unwatch':
html = [html.join('')];
case 'watch':
}
this.print = magicWatchLink;
html.push('</table>');
this.action = this.id + '&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken=' + autoClickToken();
return html.join('');
break;
}
case 'history':

case 'historyfeed':
function adjustDate(d, offset) {
case 'unprotect':
// offset is in minutes
case 'protect':
var o = offset * 60 * 1000;
this.print = wikiLink;
return new Date(Number(d) + o);
this.action = this.id;
}
break;

case 'delete':
/**
this.print = wikiLink;
* This relies on the Date parser understanding en-US dates,
this.action = 'delete';
* which is pretty safe assumption, but not perfect.
if (this.article.namespaceId() == pg.nsImageId) {
*/
var img = this.article.stripNamespace();
function convertTimeZone(date, timeZone) {
this.action += '&image=' + img;
return new Date(date.toLocaleString('en-US', { timeZone: timeZone }));
}
}
break;

case 'markpatrolled':
function formattedDateTime(date) {
case 'edit':
// fallback for IE11 and unknown timezones
delete this.oldid;
if (useTimeOffset()) {
case 'view':
return formattedDate(date) + ' ' + formattedTime(date);
case 'purge':
}
case 'render':

this.print = wikiLink;
if (getMWDateFormat() === 'ISO 8601') {
this.action = this.id;
var d2 = convertTimeZone(date, getTimeZone());
break;
return (
case 'raw':
map(zeroFill, [d2.getFullYear(), d2.getMonth() + 1, d2.getDate()]).join('-') +
this.print = wikiLink;
'T' +
this.action = 'raw';
map(zeroFill, [d2.getHours(), d2.getMinutes(), d2.getSeconds()]).join(':')
break;
);
case 'new':
}
this.print = wikiLink;

this.action = 'edit&section=new';
var options = getValueOf('popupDateTimeFormatterOptions');
break;
options['timeZone'] = getTimeZone();
case 'mainlink':
return date.toLocaleString(getLocales(), options);
if (typeof this.text == 'undefined') {
}
this.text = this.article.toString().entify();

}
function formattedDate(date) {
if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) {
// fallback for IE11 and unknown timezones
var s = this.text.split('/');
if (useTimeOffset()) {
this.text = s[s.length - 1];
// we adjust the UTC time, so we print the adjusted UTC, but not really UTC values
if (this.text === '' && s.length > 1) {
var d2 = adjustDate(date, getTimeOffset());
this.text = s[s.length - 2];
return map(zeroFill, [d2.getUTCFullYear(), d2.getUTCMonth() + 1, d2.getUTCDate()]).join('-');
}
}
}

this.print = titledWikiLink;
if (getMWDateFormat() === 'ISO 8601') {
if (typeof this.title === 'undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') {
var d2 = convertTimeZone(date, getTimeZone());
this.title = safeDecodeURI(pg.current.link.originalTitle ? pg.current.link.originalTitle : this.article);
return map(zeroFill, [d2.getFullYear(), d2.getMonth() + 1, d2.getDate()]).join('-');
if (typeof this.oldid !== 'undefined' && this.oldid) {
}
this.title = tprintf('Revision %s of %s', [this.oldid, this.title]);

}
var options = getValueOf('popupDateFormatterOptions');
}
options['timeZone'] = getTimeZone();
this.action = 'view';
return date.toLocaleDateString(getLocales(), options);
break;
}
case 'userPage':

case 'article':
function formattedTime(date) {
case 'monobook':
// fallback for IE11 and unknown timezones
case 'editMonobook':
if (useTimeOffset()) {
case 'editArticle':
// we adjust the UTC time, so we print the adjusted UTC, but not really UTC values
delete this.oldid;
var d2 = adjustDate(date, getTimeOffset());
this.article = this.article.articleFromTalkOrArticle();
return map(zeroFill, [d2.getUTCHours(), d2.getUTCMinutes(), d2.getUTCSeconds()]).join(':');
this.print = wikiLink;
}
if (this.id.indexOf('edit') === 0) {

this.action = 'edit';
if (getMWDateFormat() === 'ISO 8601') {
} else {
var d2 = convertTimeZone(date, getTimeZone());
this.action = 'view';
return map(zeroFill, [d2.getHours(), d2.getMinutes(), d2.getSeconds()]).join(':');
}
}
break;

case 'userTalk':
var options = getValueOf('popupTimeFormatterOptions');
case 'talk':
options['timeZone'] = getTimeZone();
this.article = this.article.talkPage();
return date.toLocaleTimeString(getLocales(), options);
delete this.oldid;
}
this.print = wikiLink;

this.action = 'view';
// Get the proper groupnames for the technicalgroups
break;
function fetchUserGroupNames(userinfoResponse) {
case 'arin':
var queryObj = getJsObj(userinfoResponse).query;
this.print = arinLink;
var user = anyChild(queryObj.users);
break;
var messages = [];
case 'count':
if (user.groups) {
this.print = editCounterLink;
user.groups.forEach(function (groupName) {
break;
if (['*', 'user', 'autoconfirmed', 'extendedconfirmed', 'named'].indexOf(groupName) === -1) {
case 'google':
messages.push('group-' + groupName + '-member');
this.print = googleLink;
}
break;
});
case 'editors':
}
this.print = editorListLink;
if (queryObj.globaluserinfo && queryObj.globaluserinfo.groups) {
break;
queryObj.globaluserinfo.groups.forEach(function (groupName) {
case 'globalsearch':
messages.push('group-' + groupName + '-member');
this.print = globalSearchLink;
});
break;
}
case 'lastEdit':
return getMwApi().loadMessagesIfMissing(messages);
this.print = titledDiffLink;
}
this.title = popupString('Show the last edit');

this.from = 'prev';
function showAPIPreview(queryType, html, id, navpop, download) {
this.to = 'cur';
// DJ: done
break;
var target = 'popupPreview';
case 'oldEdit':
completedNavpopTask(navpop);
this.print = titledDiffLink;

this.title = popupString('Show the edit made to get revision') + ' ' + this.oldid;
switch (queryType) {
this.from = 'prev';
case 'imagelinks':
this.to = this.oldid;
case 'category':
break;
target = 'popupPostPreview';
case 'editOld':
break;
this.print = wikiLink;
case 'userinfo':
this.action = 'edit';
target = 'popupUserData';
break;
break;
case 'undo':
case 'revision':
this.print = wikiLink;
insertPreview(download);
this.action = 'edit&undo=';
return;
break;
}
case 'revert':
setPopupTipsAndHTML(html, target, id);
this.print = wikiLink;
}
this.action = 'revert';

break;
function APIrevisionPreviewHTML(article, download) {
case 'nullEdit':
try {
this.print = wikiLink;
var jsObj = getJsObj(download.data);
this.action = 'nullEdit';
var page = anyChild(jsObj.query.pages);
break;
if (page.missing) {
case 'diffCur':
// TODO we need to fix this proper later on
this.print = titledDiffLink;
download.owner = null;
this.title = tprintf('Show changes since revision %s', [this.oldid]);
return;
this.from = this.oldid;
}
this.to = 'cur';
var content =
break;
page && page.revisions && page.revisions[0] &&
case 'editUserTalk':
page.revisions[0].slots && page.revisions[0].slots.main &&
case 'editTalk':
page.revisions[0].slots.main.contentmodel === 'wikitext'
delete this.oldid;
? page.revisions[0].slots.main.content
this.article = this.article.talkPage();
: null;
this.action = 'edit';
if (typeof content === 'string') {
this.print = wikiLink;
download.data = content;
break;
download.lastModified = new Date(page.revisions[0].timestamp);
case 'newUserTalk':
}
case 'newTalk':
if (page.pageprops.wikibase_item) {
this.article = this.article.talkPage();
download.wikibaseItem = page.pageprops.wikibase_item;
this.action = 'edit&section=new';
download.wikibaseRepo = jsObj.query.wikibase.repo.url.base
this.print = wikiLink;
+ jsObj.query.wikibase.repo.url.articlepath;
break;
}
case 'lastContrib':
} catch (someError) {
case 'sinceMe':
return 'Revision preview failed :(';
this.print = magicHistoryLink;
}
break;
}
case 'togglePreviews':

this.text = popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews');
function APIbacklinksPreviewHTML(article, download /*, navpop*/) {
case 'disablePopups':
try {
case 'purgePopups':
var jsObj = getJsObj(download.data);
this.print = popupMenuLink;
var list = jsObj.query.backlinks;
break;

default:
var html = [];
this.print = function() {
if (!list) {
return 'Unknown navlink type: ' + String(this.id);
return popupString('No backlinks found');
}
}
;
for (var i = 0; i < list.length; i++) {
}
var t = new Title(list[i].title);
}
html.push(
;
'<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t.toString().entify() + '</a>'
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) {
html = html.join(', ');
return;
if (jsObj['continue'] && jsObj['continue'].blcontinue) {
}
html += popupString(' and more');
if (keyCode == 27) {
}
killPopup();
return false;
return html;
} catch (someError) {
}
return 'backlinksPreviewHTML went wonky';
var letter = String.fromCharCode(keyCode);
}
var links = pg.current.link.navpopup.mainDiv.getElementsByTagName('A');
}
var startLink = 0;

var i, j;
pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) {
if (popupHandleKeypress.lastPopupLinkSelected) {
log('APIsharedImagePagePreviewHTML');
for (i = 0; i < links.length; ++i) {
var popupid = obj.requestid;
if (links[i] == popupHandleKeypress.lastPopupLinkSelected) {
if (obj.query && obj.query.pages) {
startLink = i;
var page = anyChild(obj.query.pages);
}
var content =
}
page && page.revisions && page.revisions[0] &&
}
page.revisions[0].slots && page.revisions[0].slots.main &&
for (j = 0; j < links.length; ++j) {
page.revisions[0].slots.main.contentmodel === 'wikitext'
i = (startLink + j + 1) % links.length;
? page.revisions[0].slots.main.content
if (links[i].getAttribute('popupkey') == letter) {
: null;
if (evt && evt.preventDefault) {
if (
evt.preventDefault();
typeof content === 'string' &&
}
pg &&
links[i].focus();
pg.current &&
popupHandleKeypress.lastPopupLinkSelected = links[i];
pg.current.link &&
return false;
pg.current.link.navpopup
}
) {
}
/* Not entirely safe, but the best we can do */
if (document.oldPopupOnkeypress) {
var p = new Previewmaker(
return document.oldPopupOnkeypress(evt);
content,
}
pg.current.link.navpopup.article,
return true;
pg.current.link.navpopup
}
);
function addPopupShortcuts() {
p.makePreview();
if (document.onkeypress != popupHandleKeypress) {
setPopupHTML(p.html, 'popupSecondPreview', popupid);
document.oldPopupOnkeypress = document.onkeypress;
}
}
}
document.onkeypress = popupHandleKeypress;
};
}

function rmPopupShortcuts() {
function APIimagepagePreviewHTML(article, download, navpop) {
popupHandleKeypress.lastPopupLinkSelected = null;
try {
try {
var jsObj = getJsObj(download.data);
if (document.oldPopupOnkeypress && document.oldPopupOnkeypress == popupHandleKeypress) {
var page = anyChild(jsObj.query.pages);
document.onkeypress = null;
var content =
return;
page && page.revisions && page.revisions[0] &&
}
page.revisions[0].slots && page.revisions[0].slots.main &&
document.onkeypress = document.oldPopupOnkeypress;
page.revisions[0].slots.main.contentmodel === 'wikitext'
} catch (nasties) {}
? page.revisions[0].slots.main.content
}
: null;
function addLinkProperty(html, property) {
var i = html.indexOf('>');
var ret = '';
var alt = '';
if (i < 0) {
try {
return html;
alt = navpop.parentAnchor.childNodes[0].alt;
}
} catch (e) {}
return html.substring(0, i) + ' ' + property + html.substring(i);
if (alt) {
}
ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt);
function addPopupShortcut(html, key) {
}
if (!getValueOf('popupShortcutKeys')) {
if (typeof content === 'string') {
return html;
var p = prepPreviewmaker(content, article, navpop);
}
p.makePreview();
var ret = addLinkProperty(html, 'popupkey="' + key + '"');
if (key == ' ') {
if (p.html) {
ret += '<hr />' + p.html;
key = popupString('spacebar');
}
}
if (getValueOf('popupSummaryData')) {
return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'), '$1$2$3 [' + key + ']$4');
var info = getPageInfo(content, download);
}
log(info);
function loadDiff(article, oldid, diff, navpop) {
setPopupTrailer(info, navpop.idNumber);
navpop.diffData = {
}
oldRev: {},
}
newRev: {}
if (page && page.imagerepository == 'shared') {
};
var art = new Title(article);
mw.loader.using('mediawiki.api').then(function() {
var encart = encodeURIComponent('File:' + art.stripNamespace());
var api = getMwApi();
var shared_url =
var params = {
pg.wiki.apicommonsbase +
action: 'compare',
'?format=json&formatversion=2' +
prop: 'ids|title',
'&callback=pg.fn.APIsharedImagePagePreviewHTML' +
};
'&requestid=' +
params.fromtitle = article.toString();
navpop.idNumber +
switch (diff) {
'&action=query&prop=revisions&rvslots=main&rvprop=content&titles=' +
case 'cur':
encart;
switch (oldid) {

case null:
ret =
case '':
ret +
case 'prev':
'<hr />' +
params.torelative = 'prev';
popupString('Image from Commons') +
break;
': <a href="' +
default:
pg.wiki.commonsbase +
params.fromrev = oldid;
'?title=' +
params.torelative = 'cur';
encart +
break;
'">' +
}
popupString('Description page') +
break;
'</a>';
case 'prev':
mw.loader.load(shared_url);
if (oldid && oldid !== 'cur') {
}
params.fromrev = oldid;
showAPIPreview(
}
'imagelinks',
params.torelative = 'prev';
APIimagelinksPreviewHTML(article, download),
break;
navpop.idNumber,
case 'next':
download
params.fromrev = oldid || 0;
);
params.torelative = 'next';
return ret;
break;
} catch (someError) {
default:
return 'API imagepage preview failed :(';
params.fromrev = oldid || 0;
}
params.torev = diff || 0;
}
break;

}
function APIimagelinksPreviewHTML(article, download) {
api.get(params).then(function(data) {
try {
navpop.diffData.oldRev.revid = data.compare.fromrevid;
var jsobj = getJsObj(download.data);
navpop.diffData.newRev.revid = data.compare.torevid;
var list = jsobj.query.imageusage;
addReviewLink(navpop, 'popupMiscTools');
if (list) {
var go = function() {
var ret = [];
pendingNavpopTask(navpop);
for (var i = 0; i < list.length; i++) {
var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';
ret.push(list[i].title);
url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid;
}
url += '&prop=revisions&rvslots=main&rvprop=ids|timestamp|content';
if (ret.length === 0) {
getPageWithCaching(url, doneDiff, navpop);
return popupString('No image links found');
return true;
}
};
return '<h2>' + popupString('File links') + '</h2>' + linkList(ret);
if (navpop.visible || !getValueOf('popupLazyDownloads')) {
} else {
go();
return popupString('No image links found');
} else {
}
navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS');
} catch (someError) {
}
return 'Image links preview generation failed :(';
});
}
});
}
}

function addReviewLink(navpop, target) {
function APIcategoryPreviewHTML(article, download) {
if (!pg.user.canReview) {
try {
return;
var jsobj = getJsObj(download.data);
}
var list = jsobj.query.categorymembers;
if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) {
var ret = [];
return;
for (var p = 0; p < list.length; p++) {
}
ret.push(list[p].title);
var params = {
}
action: 'query',
if (ret.length === 0) {
prop: 'info|flagged',
return popupString('Empty category');
revids: navpop.diffData.oldRev.revid,
}
formatversion: 2,
ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' + linkList(ret);
};
if (jsobj['continue'] && jsobj['continue'].cmcontinue) {
getMwApi().get(params).then(function(data) {
ret += popupString(' and more');
var stable_revid = (data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid) || 0;
}
if (stable_revid == navpop.diffData.oldRev.revid) {
return ret;
var a = document.createElement('a');
} catch (someError) {
a.innerHTML = popupString('mark patrolled');
return 'Category preview failed :(';
a.title = popupString('markpatrolledHint');
}
a.onclick = function() {
}
var params = {

action: 'review',
function APIuserInfoPreviewHTML(article, download) {
revid: navpop.diffData.newRev.revid,
var ret = [];
comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid, ]),
var queryobj = {};
};
try {
getMwApi().postWithToken('csrf', params).done(function() {
queryobj = getJsObj(download.data).query;
a.style.display = 'none';
} catch (someError) {
}).fail(function() {
return 'Userinfo preview failed :(';
alert(popupString('Could not marked this edit as patrolled'));
}
});

}
var user = anyChild(queryobj.users);
;
if (user) {
setPopupHTML(a, target, navpop.idNumber, null, true);
var globaluserinfo = queryobj.globaluserinfo;
}
if (user.invalid === '') {
});
ret.push(popupString('Invalid user'));
}
} else if (user.missing === '') {
function doneDiff(download) {
ret.push(popupString('Not a registered username'));
if (!download.owner || !download.owner.diffData) {
}
return;
if (user.blockedby) {
}
if (user.blockpartial) {
var navpop = download.owner;
ret.push('<b>' + popupString('Has blocks') + '</b>');
completedNavpopTask(navpop);
} else {
var pages, revisions = [];
ret.push('<b>' + popupString('BLOCKED') + '</b>');
try {
}
pages = getJsObj(download.data).query.pages;
}
for (var i = 0; i < pages.length; i++) {
if (globaluserinfo && ('locked' in globaluserinfo || 'hidden' in globaluserinfo)) {
revisions = revisions.concat(pages[i].revisions);
var lockedSulAccountIsAttachedToThis = true;
}
for (i = 0; i < revisions.length; i++) {
for (var i = 0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) {
if (globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname')) {
if (revisions[i].revid == navpop.diffData.oldRev.revid) {
lockedSulAccountIsAttachedToThis = false;
navpop.diffData.oldRev.revision = revisions[i];
break;
} else if (revisions[i].revid == navpop.diffData.newRev.revid) {
}
navpop.diffData.newRev.revision = revisions[i];
}
}
if (lockedSulAccountIsAttachedToThis) {
}
if ('locked' in globaluserinfo) {
} catch (someError) {
ret.push('<b><i>' + popupString('LOCKED') + '</i></b>');
errlog('Could not get diff');
}
}
if ('hidden' in globaluserinfo) {
insertDiff(navpop);
ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>');
}
}
function rmBoringLines(a, b, context) {
}
if (typeof context == 'undefined') {
}
context = 2;
if (getValueOf('popupShowGender') && user.gender) {
}
switch (user.gender) {
var aa = []
case 'male':
, aaa = [];
ret.push(popupString('he/him') + ' · ');
var bb = []
break;
, bbb = [];
case 'female':
var i, j;
ret.push(popupString('she/her') + ' · ');
for (i = 0; i < a.length; ++i) {
break;
if (!a[i].paired) {
}
aa[i] = 1;
}
} else if (countCrossings(b, a, i, true)) {
if (user.groups) {
aa[i] = 1;
user.groups.forEach(function (groupName) {
bb[a[i].row] = 1;
if (['*', 'user', 'autoconfirmed', 'extendedconfirmed', 'named'].indexOf(groupName) === -1) {
}
ret.push(
}
pg.escapeQuotesHTML(mw.message('group-' + groupName + '-member', user.gender).text())
for (i = 0; i < b.length; ++i) {
);
if (bb[i] == 1) {
}
continue;
});
}
}
if (!b[i].paired) {
if (globaluserinfo && globaluserinfo.groups) {
bb[i] = 1;
globaluserinfo.groups.forEach(function (groupName) {
}
ret.push(
}
'<i>' +
for (i = 0; i < b.length; ++i) {
pg.escapeQuotesHTML(
if (bb[i] == 1) {
mw.message('group-' + groupName + '-member', user.gender).text()
for (j = Math.max(0, i - context); j < Math.min(b.length, i + context); ++j) {
) +
if (!bb[j]) {
'</i>'
bb[j] = 1;
);
aa[b[j].row] = 0.5;
});
}
}
}
if (user.registration) {
}
ret.push(
}
pg.escapeQuotesHTML(
for (i = 0; i < a.length; ++i) {
(user.editcount ? user.editcount : '0') +
if (aa[i] == 1) {
popupString(' edits since: ') +
for (j = Math.max(0, i - context); j < Math.min(a.length, i + context); ++j) {
(user.registration ? formattedDate(new Date(user.registration)) : '')
if (!aa[j]) {
)
aa[j] = 1;
);
bb[a[j].row] = 0.5;
}
}
}
}

}
if (queryobj.usercontribs && queryobj.usercontribs.length) {
}
ret.push(
for (i = 0; i < bb.length; ++i) {
popupString('last edit on ') + formattedDate(new Date(queryobj.usercontribs[0].timestamp))
if (bb[i] > 0) {
);
if (b[i].paired) {
}
bbb.push(b[i].text);

} else {
if (queryobj.blocks) {
bbb.push(b[i]);
ret.push(popupString('IP user')); //we only request list=blocks for IPs
}
for (var l = 0; l < queryobj.blocks.length; l++) {
}
var rbstr =
}
queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK';
for (i = 0; i < aa.length; ++i) {
rbstr = !Array.isArray(queryobj.blocks[l].restrictions)
if (aa[i] > 0) {
? 'Has ' + rbstr.toLowerCase() + 's'
if (a[i].paired) {
: rbstr + 'ED';
aaa.push(a[i].text);
ret.push('<b>' + popupString(rbstr) + '</b>');
} else {
}
aaa.push(a[i]);
}
}

}
// if any element of ret ends with ' · ', merge it with the next element to avoid
}
// the .join(', ') call inserting a comma after it
return {
for (var m = 0; m < ret.length - 1; m++) {
a: aaa,
if (ret[m].length > 3 && ret[m].substring(ret[m].length - 3) === ' · ') {
b: bbb
ret[m] = ret[m] + ret[m + 1];
};
ret.splice(m + 1, 1); // delete element at index m+1
}
m--;
function stripOuterCommonLines(a, b, context) {
}
var i = 0;
}
while (i < a.length && i < b.length && a[i] == b[i]) {

++i;
ret = '<hr />' + ret.join(', ');
}
return ret;
var j = a.length - 1;
}
var k = b.length - 1;

while (j >= 0 && k >= 0 && a[j] == b[k]) {
function APIcontribsPreviewHTML(article, download, navpop) {
--j;
return APIhistoryPreviewHTML(article, download, navpop, true);
--k;
}
}

return {
function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) {
a: a.slice(Math.max(0, i - 1 - context), Math.min(a.length + 1, j + context + 1)),
try {
b: b.slice(Math.max(0, i - 1 - context), Math.min(b.length + 1, k + context + 1)),
var jsobj = getJsObj(download.data);
};
var edits = [];
}
if (reallyContribs) {
function insertDiff(navpop) {
edits = jsobj.query.usercontribs;
var oldlines = navpop.diffData.oldRev.revision.slots.main.content.split('\n');
} else {
var newlines = navpop.diffData.newRev.revision.slots.main.content.split('\n');
edits = anyChild(jsobj.query.pages).revisions;
var inner = stripOuterCommonLines(oldlines, newlines, getValueOf('popupDiffContextLines'));
}
oldlines = inner.a;

newlines = inner.b;
var ret = editPreviewTable(article, edits, reallyContribs);
var truncated = false;
return ret;
getValueOf('popupDiffMaxLines');
} catch (someError) {
if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) {
return 'History preview failed :-(';
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;
// ENDFILE: querypreview.js
}

var lineDiff = diff(oldlines, newlines);
// STARTFILE: debug.js
var lines2 = rmBoringLines(lineDiff.o, lineDiff.n);
////////////////////////////////////////////////////////////////////
var oldlines2 = lines2.a;
// Debugging functions
var newlines2 = lines2.b;
////////////////////////////////////////////////////////////////////
var simpleSplit = !String.prototype.parenSplit.isNative;

var html = '<hr />';
function setupDebugging() {
if (getValueOf('popupDiffDates')) {
if (window.popupDebug) {
html += diffDatesTable(navpop);
// popupDebug is set from .version
html += '<hr />';
window.log = function (x) {
}
//if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; };
html += shortenDiffString(diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit), getValueOf('popupDiffContextCharacters')).join('<hr />');
window.console.log(x);
setPopupTipsAndHTML(html.split('\n').join('<br>') + (truncated ? '<hr /><b>' + popupString('Diff truncated for performance reasons') + '</b>' : ''), 'popupPreview', navpop.idNumber);
};
}
function diffDatesTable(navpop) {
window.errlog = function (x) {
window.console.error(x);
var html = '<table class="popup_diff_dates">';
};
html += diffDatesTableRow(navpop.diffData.newRev.revision, tprintf('New revision'));
log('Initializing logger');
html += diffDatesTableRow(navpop.diffData.oldRev.revision, tprintf('Old revision'));
} else {
html += '</table>';
window.log = function () {};
return html;
window.errlog = function () {};
}
}
function diffDatesTableRow(revision, label) {
}
var txt = '';
// ENDFILE: debug.js
var lastModifiedDate = new Date(revision.timestamp);

txt = formattedDateTime(lastModifiedDate);
// STARTFILE: images.js
var revlink = generalLink({

url: mw.config.get('wgScript') + '?oldid=' + revision.revid,
// load image of type Title.
text: label,
function loadImage(image, navpop) {
title: label,
if (typeof image.stripNamespace != 'function') {
});
alert('loadImages bad');
return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [revlink, txt]);
}
}
// API call to retrieve image info.
function titledDiffLink(l) {

return titledWikiLink({
if (!getValueOf('popupImages')) {
article: l.article,
return;
action: l.to + '&oldid=' + l.from,
}
newWin: l.newWin,
if (!isValidImageName(image)) {
noPopup: l.noPopup,
return false;
text: l.text,
}
title: l.title,

actionName: 'diff',
var art = image.urlString();
});

}
var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query';
function wikiLink(l) {
url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge');
if (!(typeof l.article == typeof {} && typeof l.action == typeof '' && typeof l.text == typeof '')) {
url += '&titles=' + art;
return null;

}
pendingNavpopTask(navpop);
if (typeof l.oldid == 'undefined') {
var callback = function (d) {
l.oldid = null;
popupsInsertImage(navpop.idNumber, navpop, d);
}
};
var savedOldid = l.oldid;
var go = function () {
if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) {
getPageWithCaching(url, callback, navpop);
l.oldid = null;
return true;
}
};
var hint = popupString(l.action + 'Hint');
if (navpop.visible || !getValueOf('popupLazyDownloads')) {
var oldidData = [l.oldid, safeDecodeURI(l.article)];
go();
var revisionString = tprintf('revision %s of %s', oldidData);
} else {
log('revisionString=' + revisionString);
navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA');
switch (l.action) {
}
case 'edit&section=new':
}
hint = popupString('newSectionHint');

break;
function popupsInsertImage(id, navpop, download) {
case 'edit&undo=':
log('popupsInsertImage');
if (l.diff && l.diff != 'prev' && savedOldid) {
var imageinfo;
l.action += l.diff + '&undoafter=' + savedOldid;
try {
} else if (savedOldid) {
var jsObj = getJsObj(download.data);
l.action += savedOldid;
var imagepage = anyChild(jsObj.query.pages);
}
if (typeof imagepage.imageinfo === 'undefined') {
hint = popupString('undoHint');
return;
break;
}
case 'raw&ctype=text/css':
imageinfo = imagepage.imageinfo[0];
hint = popupString('rawHint');
} catch (someError) {
break;
log('popupsInsertImage failed :(');
case 'revert':
return;
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') {
var popupImage = document.getElementById('popupImg' + id);
l.action += '&direction=prev';
if (!popupImage) {
revisionString = tprintf('the revision prior to revision %s of %s', oldidData);
log('could not find insertion point for image');
}
return;
if (getValueOf('popupRevertSummaryPrompt')) {
}
l.action += '&autosummaryprompt=true';

}
if (getValueOf('popupMinorReverts')) {
popupImage.width = getValueOf('popupImageSize');
popupImage.style.display = 'inline';
l.action += '&autominor=true';

}
// Set the source for the image.
log('revisionString is now ' + revisionString);
if (imageinfo.thumburl) {
break;
popupImage.src = imageinfo.thumburl;
case 'nullEdit':
} else if (imageinfo.mime.indexOf('image') === 0) {
l.action = 'edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null';
popupImage.src = imageinfo.url;
break;
log('a thumb could not be found, using original image');
case 'historyfeed':
} else {
l.action = 'history&feed=rss';
log("fullsize imagethumb, but not sure if it's an image");
break;
}
case 'markpatrolled':

l.action = 'markpatrolled&rcid=' + l.rcid;
var a = document.getElementById('popupImageLink' + id);
}
if (a === null) {
if (hint) {
return null;
if (l.oldid) {
}
hint = simplePrintf(hint, [revisionString]);

} else {
// Determine the action of the surrouding imagelink.
hint = simplePrintf(hint, [safeDecodeURI(l.article)]);
switch (getValueOf('popupThumbAction')) {
}
case 'imagepage':
} else {
if (pg.current.article.namespaceId() != pg.nsImageId) {
hint = safeDecodeURI(l.article + '&action=' + l.action) + l.oldid ? '&oldid=' + l.oldid : '';
a.href = imageinfo.descriptionurl;
}
// FIXME: unreliable pg.idNumber
return titledWikiLink({
popTipsSoonFn('popupImage' + id)();
article: l.article,
break;
action: l.action,
}
text: l.text,
/* falls through */
newWin: l.newWin,
case 'sizetoggle':
title: hint,
a.onclick = toggleSize;
oldid: l.oldid,
a.title = popupString('Toggle image size');
noPopup: l.noPopup,
return;
onclick: l.onclick,
case 'linkfull':
});
a.href = imageinfo.url;
}
a.title = popupString('Open full-size image');
function revertSummary(oldid, diff) {
return;
var ret = '';
}
if (diff == 'prev') {
}
ret = getValueOf('popupQueriedRevertToPreviousSummary');

} else {
// Toggles the image between inline small and navpop fullwidth.
ret = getValueOf('popupQueriedRevertSummary');
// It's the same image, no actual sizechange occurs, only display width.
}
function toggleSize() {
return ret + '&autorv=' + oldid;
var imgContainer = this;
}
if (!imgContainer) {
function titledWikiLink(l) {
alert('imgContainer is null :/');
if (typeof l.article == 'undefined' || typeof l.action == 'undefined') {
return;
errlog('got undefined article or action in titledWikiLink');
}
return null;
var img = imgContainer.firstChild;
}
if (!img) {
var base = pg.wiki.titlebase + l.article.urlString();
alert('img is null :/');
var url = base;
return;
if (typeof l.actionName == 'undefined' || !l.actionName) {
}
l.actionName = 'action';

}
if (!img.style.width || img.style.width === '') {
if (l.action != 'view') {
img.style.width = '100%';
url = base + '&' + l.actionName + '=' + l.action;
} else {
}
img.style.width = '';
if (typeof l.oldid != 'undefined' && l.oldid) {
}
url += '&oldid=' + l.oldid;
}
}

var cssClass = pg.misc.defaultNavlinkClassname;
// Returns one title of an image from wikiText.
if (typeof l.className != 'undefined' && l.className) {
function getValidImageFromWikiText(wikiText) {
cssClass = l.className;
// nb in pg.re.image we're interested in the second bracketed expression
}
// this may change if the regex changes :-(
return generalNavLink({
//var match=pg.re.image.exec(wikiText);
url: url,
var matched = null;
newWin: l.newWin,
var match;
title: typeof l.title != 'undefined' ? l.title : null,
// strip html comments, used by evil bots :-(
text: typeof l.text != 'undefined' ? l.text : null,
var t = removeMatchesUnless(
className: cssClass,
wikiText,
noPopup: l.noPopup,
RegExp('(<!--[\\s\\S]*?-->)'),
onclick: l.onclick,
1,
});
RegExp('^<!--[^[]*popup', 'i')
}
);
pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) {

getHistoryInfo(wikipage, function(x) {
while ((match = pg.re.image.exec(t))) {
processLastContribInfo(x, {
// now find a sane image name - exclude templates by seeking {
page: wikipage,
var m = match[2] || match[6];
newWin: newWin
if (isValidImageName(m)) {
});
matched = m;
});
break;
}
}
;
}
function processLastContribInfo(info, stuff) {
pg.re.image.lastIndex = 0;
if (!info.edits || !info.edits.length) {
if (!matched) {
alert('Popups: an odd thing happened. Please retry.');
return null;
return;
}
}
return mw.config.get('wgFormattedNamespaces')[pg.nsImageId] + ':' + upcaseFirst(matched);
if (!info.firstNewEditor) {
}
alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor, info.edits.length, ]));

return;
function removeMatchesUnless(str, re1, parencount, re2) {
}
var split = str.parenSplit(re1);
var newUrl = pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid=' + info.firstNewEditor.oldid;
var c = parencount + 1;
displayUrl(newUrl, stuff.newWin);
for (var i = 0; i < split.length; ++i) {
}
if (i % c === 0 || re2.test(split[i])) {
pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) {
continue;
getHistoryInfo(wikipage, function(x) {
}
processDiffSinceMyEdit(x, {
split[i] = '';
page: wikipage,
}
newWin: newWin
return split.join('');
});
}
});

}
// ENDFILE: images.js
;

function processDiffSinceMyEdit(info, stuff) {
// STARTFILE: namespaces.js
if (!info.edits || !info.edits.length) {
// Set up namespaces and other non-strings.js localization
alert('Popups: something fishy happened. Please try again.');
// (currently that means redirs too)
return;

}
function setNamespaces() {
var friendlyName = stuff.page.split('_').join(' ');
pg.nsSpecialId = -1;
if (!info.myLastEdit) {
pg.nsMainspaceId = 0;
alert(tprintf("Couldn't find an edit by %s\nin the last %s edits to\n%s", [info.userName, getValueOf('popupHistoryLimit'), friendlyName, ]));
pg.nsImageId = 6;
return;
pg.nsUserId = 2;
}
pg.nsUsertalkId = 3;
if (info.myLastEdit.index === 0) {
pg.nsCategoryId = 14;
alert(tprintf('%s seems to be the last editor to the page %s', [info.userName, friendlyName]));
pg.nsTemplateId = 10;
return;
}
}

var newUrl = pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid=' + info.myLastEdit.oldid;
function setRedirs() {
displayUrl(newUrl, stuff.newWin);
var r = 'redirect';
}
var R = 'REDIRECT';
function displayUrl(url, newWin) {
var redirLists = {
if (newWin) {
ar: [R, 'تحويل'],
window.open(url);
be: [r, 'перанакіраваньне'],
} else {
bg: [r, 'пренасочване', 'виж'],
document.location = url;
bs: [r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI'],
}
bn: [R, 'পুনর্নির্দেশ'],
}
cs: [R, 'PŘESMĚRUJ'],
pg.fn.purgePopups = function purgePopups() {
cy: [r, 'ail-cyfeirio'],
processAllPopups(true);
de: [R, 'WEITERLEITUNG'],
setupCache();
el: [R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'],
pg.option = {};
eo: [R, 'ALIDIREKTU', 'ALIDIREKTI'],
abortAllDownloads();
es: [R, 'REDIRECCIÓN'],
}
et: [r, 'suuna'],
;
ga: [r, 'athsheoladh'],
function processAllPopups(nullify, banish) {
gl: [r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'],
for (var i = 0; pg.current.links && i < pg.current.links.length; ++i) {
he: [R, 'הפניה'],
if (!pg.current.links[i].navpopup) {
hu: [R, 'ÁTIRÁNYÍTÁS'],
continue;
is: [r, 'tilvísun', 'TILVÍSUN'],
}
it: [R, 'RINVIA', 'Rinvia'],
if (nullify || banish) {
ja: [R, '転送'],
pg.current.links[i].navpopup.banish();
mk: [r, 'пренасочување', 'види'],
}
nds: [r, 'wiederleiden'],
pg.current.links[i].simpleNoMore = false;
'nds-nl': [R, 'DEURVERWIEZING', 'DUURVERWIEZING'],
if (nullify) {
nl: [R, 'DOORVERWIJZING'],
pg.current.links[i].navpopup = null;
nn: [r, 'omdiriger'],
}
pl: [R, 'PATRZ', 'PRZEKIERUJ', 'TAM'],
}
pt: [R, 'redir'],
}
ru: [R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР'],
pg.fn.disablePopups = function disablePopups() {
sk: [r, 'presmeruj'],
processAllPopups(false, true);
sr: [r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI'],
setupTooltips(null, true);
tr: [R, 'YÖNLENDİRME', 'yönlendirme', 'YÖNLENDİR', 'yönlendir'],
}
tt: [R, 'yünältü', 'перенаправление', 'перенапр'],
;
uk: [R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР'],
pg.fn.togglePreviews = function togglePreviews() {
vi: [r, 'đổi'],
processAllPopups(true, true);
yi: [R, 'ווייטערפירן'],
pg.option.simplePopups = !pg.option.simplePopups;
zh: [R, '重定向'], // no comma
abortAllDownloads();
};
}
var redirList = redirLists[pg.wiki.lang] || [r, R];
;
// Mediawiki is very tolerant about what comes after the #redirect at the start
function magicWatchLink(l) {
pg.re.redirect = RegExp(
l.onclick = simplePrintf("pg.fn.modifyWatchlist('%s','%s');return false;", [l.article.toString(true).split('\\').join('\\\\').split("'").join("\\'"), this.id, ]);
'^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)',
return wikiLink(l);
'i'
}
);
pg.fn.modifyWatchlist = function modifyWatchlist(title, action) {
}
var reqData = {

action: 'watch',
function setInterwiki() {
formatversion: 2,
if (pg.wiki.wikimedia) {
titles: title,
// From https://meta.wikimedia.org/wiki/List_of_Wikipedias
uselang: mw.config.get('wgUserLanguage'),
//en.wikipedia.org/w/api.php?action=sitematrix&format=json&smtype=language&smlangprop=code&formatversion=2
};
pg.wiki.interwiki =
if (action === 'unwatch') {
'aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu';
reqData.unwatch = true;
pg.re.interwiki = RegExp('^' + pg.wiki.interwiki + ':');
}
} else {
var mwTitle = mw.Title.newFromText(title);
pg.wiki.interwiki = null;
var messageName;
pg.re.interwiki = RegExp('^$');
if (mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1) {
}
messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk';
}
} else {

messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext';
// return a regexp pattern matching all variants to write the given namespace
}
function nsRe(namespaceId) {
$.when(getMwApi().postWithToken('watch', reqData), getMwApi().loadMessagesIfMissing([messageName])).done(function() {
var imageNamespaceVariants = [];
mw.notify(mw.message(messageName, title).parseDom());
jQuery.each(mw.config.get('wgNamespaceIds'), function (_localizedNamespaceLc, _namespaceId) {
});
if (_namespaceId != namespaceId) {
}
return;
;
}
function magicHistoryLink(l) {
_localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc);
var jsUrl = ''
imageNamespaceVariants.push(
, title = ''
mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]')
, onClick = '';
);
switch (l.id) {
imageNamespaceVariants.push(mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc)));
case 'lastContrib':
});
onClick = simplePrintf("pg.fn.getLastContrib('%s',%s)", [l.article.toString(true).split('\\').join('\\\\').split("'").join("\\'"), l.newWin, ]);

title = popupString('lastContribHint');
return '(?:' + imageNamespaceVariants.join('|') + ')';
break;
}
case 'sinceMe':

onClick = simplePrintf("pg.fn.getDiffSinceMyEdit('%s',%s)", [l.article.toString(true).split('\\').join('\\\\').split("'").join("\\'"), l.newWin, ]);
function nsReImage() {
title = popupString('sinceMeHint');
return nsRe(pg.nsImageId);
break;
}
}
// ENDFILE: namespaces.js
jsUrl = 'javascript:' + onClick;

onClick += ';return false;';
// STARTFILE: selpop.js
return generalNavLink({
function getEditboxSelection() {
url: jsUrl,
// see http://www.webgurusforum.com/8/12/0
newWin: false,
var editbox;
title: title,
try {
text: l.text,
editbox = document.editform.wpTextbox1;
noPopup: l.noPopup,
} catch (dang) {
onclick: onClick,
return;
});
}
}
// IE, Opera
function popupMenuLink(l) {
if (document.selection) {
var jsUrl = simplePrintf('javascript:pg.fn.%s()', [l.id]);
return document.selection.createRange().text;
var title = popupString(simplePrintf('%sHint', [l.id]));
}
var onClick = simplePrintf('pg.fn.%s();return false;', [l.id]);
// Mozilla
return generalNavLink({
var selStart = editbox.selectionStart;
url: jsUrl,
var selEnd = editbox.selectionEnd;
newWin: false,
return editbox.value.substring(selStart, selEnd);
title: title,
}
text: l.text,

noPopup: l.noPopup,
function doSelectionPopup() {
onclick: onClick,
// popup if the selection looks like [[foo|anything afterwards at all
});
// or [[foo|bar]]text without ']]'
}
// or [[foo|bar]]
function specialLink(l) {
var sel = getEditboxSelection();
if (typeof l.specialpage == 'undefined' || !l.specialpage) {
var open = sel.indexOf('[[');
return null;
var pipe = sel.indexOf('|');
}
var close = sel.indexOf(']]');
var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':' + l.specialpage;
if (typeof l.sep == 'undefined' || l.sep === null) {
if (open == -1 || (pipe == -1 && close == -1)) {
return;
l.sep = '&target=';
}
}
if ((pipe != -1 && open > pipe) || (close != -1 && open > close)) {
var article = l.article.urlString({
return;
keepSpaces: l.specialpage == 'Search',
}
});
var article = new Title(sel.substring(open + 2, pipe < 0 ? close : pipe));
var hint = popupString(l.specialpage + 'Hint');
if (getValueOf('popupOnEditSelection') == 'boxpreview') {
switch (l.specialpage) {
return doSeparateSelectionPopup(sel, article);
case 'Log':
}
switch (l.sep) {
if (close > 0 && sel.substring(close + 2).indexOf('[[') >= 0) {
case '&user=':
return;
hint = popupString('userLogHint');
}
break;
var a = document.createElement('a');
case '&type=block&page=':
a.href = pg.wiki.titlebase + article.urlString();
hint = popupString('blockLogHint');
mouseOverWikiLink2(a);
break;
if (a.navpopup) {
case '&page=':
a.navpopup.addHook(
hint = popupString('pageLogHint');
function () {
break;
runStopPopupTimer(a.navpopup);
case '&type=protect&page=':
},
hint = popupString('protectLogHint');
'unhide',
break;
'after'
case '&type=delete&page=':
);
hint = popupString('deleteLogHint');
}
break;
}
default:

log('Unknown log type, sep=' + l.sep);
function doSeparateSelectionPopup(str, article) {
hint = 'Missing hint (FIXME)';
var div = document.getElementById('selectionPreview');
}
if (!div) {
break;
div = document.createElement('div');
case 'PrefixIndex':
div.id = 'selectionPreview';
article += '/';
try {
break;
var box = document.editform.wpTextbox1;
}
box.parentNode.insertBefore(div, box);
if (hint) {
} catch (error) {
hint = simplePrintf(hint, [safeDecodeURI(l.article)]);
return;
} else {
}
hint = safeDecodeURI(l.specialpage + ':' + l.article);
}
}
var p = prepPreviewmaker(str, article, newNavpopup(document.createElement('a'), article));
var url = base + l.sep + article;
p.makePreview();
return generalNavLink({
if (p.html) {
url: url,
div.innerHTML = p.html;
title: hint,
}
text: l.text,
div.ranSetupTooltipsAlready = false;
newWin: l.newWin,
popTipsSoonFn('selectionPreview')();
noPopup: l.noPopup,
}
});
// ENDFILE: selpop.js
}

function generalLink(link) {
// STARTFILE: navpopup.js
if (typeof link.url == 'undefined') {
/**
return null;
* @fileoverview Defines two classes: {@link Navpopup} and {@link Mousetracker}.
}
*
var elem = document.createElement('a');
* <code>Navpopup</code> describes popups: when they appear, where, what
elem.href = link.url;
* they look like and so on.
elem.title = link.title;
*
elem.setAttribute('onclick', link.onclick);
* <code>Mousetracker</code> "captures" the mouse using
if (link.noPopup) {
* <code>document.onmousemove</code>.
elem.setAttribute('noPopup', '1');
*/
}

var newWin;
/**
if (typeof link.newWin == 'undefined' || link.newWin === null) {
* Creates a new Mousetracker.
newWin = getValueOf('popupNewWindows');
* @constructor
} else {
* @class The Mousetracker class. This monitors mouse movements and manages associated hooks.
newWin = link.newWin;
*/
}
function Mousetracker() {
if (newWin) {
/**
elem.target = '_blank';
* Interval to regularly run the hooks anyway, in milliseconds.
}
* @type {number}
if (link.className) {
*/
elem.className = link.className;
this.loopDelay = 400;
}

elem.innerText = pg.unescapeQuotesHTML(link.text);
/**
return elem.outerHTML;
* Timer for the loop.
}
* @type Timer
function appendParamsToLink(linkstr, params) {
*/
var sp = linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i'));
this.timer = null;
if (sp.length < 2) {

return null;
/**
}
* Flag - are we switched on?
var ret = sp.shift() + sp.shift();
* @type {boolean}
ret += '&' + params + '"';
*/
ret += sp.join('');
this.active = false;
return ret;

}
/**
function changeLinkTargetLink(x) {
* Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position?
if (x.newTarget) {
*/
log('changeLinkTargetLink: newTarget=' + x.newTarget);
this.dirty = true;
}

if (x.oldTarget !== decodeURIComponent(x.oldTarget)) {
/**
log('This might be an input problem: ' + x.oldTarget);
* Array of hook functions.
}
* @private
var cA = mw.util.escapeRegExp(x.oldTarget);
* @type {Array}
var chs = cA.charAt(0).toUpperCase();
*/
chs = '[' + chs + chs.toLowerCase() + ']';
this.hooks = [];
var currentArticleRegexBit = chs + cA.substring(1);
}
currentArticleRegexBit = currentArticleRegexBit.split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)').split('\\(').join('(?:%28|\\()').split('\\)').join('(?:%29|\\))');

currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*';
/**
var title = x.title || mw.config.get('wgPageName').split('_').join(' ');
* Adds a hook, to be called when we get events.
var lk = titledWikiLink({
* @param {Function} f A function which is called as
article: new Title(title),
* <code>f(x,y)</code>. It should return <code>true</code> when it
newWin: x.newWin,
* wants to be removed, and <code>false</code> otherwise.
action: 'edit',
*/
text: x.text,
Mousetracker.prototype.addHook = function (f) {
title: x.hint,
this.hooks.push(f);
className: 'popup_change_title_link',
};
});

var cmd = '';
/**
if (x.newTarget) {
* Runs hooks, passing them the x
var t = x.newTarget;
* and y coords of the mouse. Hook functions that return true are
var s = mw.util.escapeRegExp(x.newTarget);
* passed to {@link Mousetracker#removeHooks} for removal.
if (x.alsoChangeLabel) {
* @private
cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + ']]~g;';
*/
cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;';
Mousetracker.prototype.runHooks = function () {
cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g';
if (!this.hooks || !this.hooks.length) {
} else {
return;
cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + '|$1]]~g;';
}
cmd += 's~\\[\\[' + currentArticleRegexBit + '[|]~[[' + t + '|~g;';
//log('Mousetracker.runHooks; we got some hooks to run');
cmd += 's~\\[\\[' + s + '\\|' + s + '\\]\\]~[[' + t + ']]~g';
var remove = false;
}
var removeObj = {};
} else {
// this method gets called a LOT -
cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~$1~g;';
// pre-cache some variables
cmd += 's~\\[\\[' + currentArticleRegexBit + '[|](.*?)\\]\\]~$2~g';
var x = this.x,
}
y = this.y,
cmd = 'autoedit=' + encodeURIComponent(cmd);
len = this.hooks.length;
cmd += '&autoclick=' + encodeURIComponent(x.clickButton) + '&actoken=' + encodeURIComponent(autoClickToken());

cmd += x.minor === null ? '' : '&autominor=' + encodeURIComponent(x.minor);
for (var i = 0; i < len; ++i) {
cmd += x.watch === null ? '' : '&autowatch=' + encodeURIComponent(x.watch);
//~ run the hook function, and remove it if it returns true
cmd += '&autosummary=' + encodeURIComponent(x.summary);
if (this.hooks[i](x, y) === true) {
cmd += '&autoimpl=' + encodeURIComponent(popupString('autoedit_version'));
remove = true;
return appendParamsToLink(lk, cmd);
removeObj[i] = true;
}
}
function redirLink(redirMatch, article) {
}
var ret = '';
if (remove) {
if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) {
this.removeHooks(removeObj);
ret += '<hr />';
}
if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) {
};
ret += popupString('Redirects to: (Fix ');

log('redirLink: newTarget=' + redirMatch);
/**
ret += addPopupShortcut(changeLinkTargetLink({
* Removes hooks.
newTarget: redirMatch,
* @private
text: popupString('target'),
* @param {Object} removeObj An object whose keys are the index
hint: popupString('Fix this redirect, changing just the link target'),
* numbers of functions for removal, with values that evaluate to true
summary: simplePrintf(getValueOf('popupFixRedirsSummary'), [article.toString(), redirMatch, ]),
*/
oldTarget: article.toString(),
Mousetracker.prototype.removeHooks = function (removeObj) {
clickButton: getValueOf('popupRedirAutoClick'),
var newHooks = [];
minor: true,
var len = this.hooks.length;
watch: getValueOf('popupWatchRedirredPages'),
for (var i = 0; i < len; ++i) {
}), 'R');
if (!removeObj[i]) {
ret += popupString(' or ');
newHooks.push(this.hooks[i]);
ret += addPopupShortcut(changeLinkTargetLink({
}
newTarget: redirMatch,
}
text: popupString('target & label'),
this.hooks = newHooks;
hint: popupString('Fix this redirect, changing the link target and label'),
};
summary: simplePrintf(getValueOf('popupFixRedirsSummary'), [article.toString(), redirMatch, ]),

oldTarget: article.toString(),
/**
clickButton: getValueOf('popupRedirAutoClick'),
* Event handler for mouse wiggles.
minor: true,
* We simply grab the event, set x and y and run the hooks.
watch: getValueOf('popupWatchRedirredPages'),
* This makes the cpu all hot and bothered :-(
alsoChangeLabel: true,
* @private
}), 'R');
* @param {Event} e Mousemove event
ret += popupString(')');
*/
} else {
Mousetracker.prototype.track = function (e) {
ret += popupString('Redirects') + popupString(' to ');
//~ Apparently this is needed in IE.
}
e = e || window.event;
return ret;
var x, y;
} else {
if (e) {
return ('<br> ' + popupString('Redirects') + popupString(' to ') + titledWikiLink({
if (e.pageX) {
article: new Title().fromWikiText(redirMatch),
x = e.pageX;
action: 'view',
y = e.pageY;
text: safeDecodeURI(redirMatch),
} else if (typeof e.clientX != 'undefined') {
title: popupString('Bypass redirect'),
var left,
}));
top,
}
docElt = document.documentElement;
}

function arinLink(l) {
if (docElt) {
if (!saneLinkCheck(l)) {
left = docElt.scrollLeft;
return
}
null;
left = left || document.body.scrollLeft || document.scrollLeft || 0;
}

if (!l.article.isIpUser() || !pg.wiki.wikimedia) {
if (docElt) {
return null;
top = docElt.scrollTop;
}
}
var uN = l.article.userName();
top = top || document.body.scrollTop || document.scrollTop || 0;
return generalNavLink({

url: 'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN),
x = e.clientX + left;
newWin: l.newWin,
y = e.clientY + top;
title: tprintf('Look up %s in ARIN whois database', [uN]),
} else {
text: l.text,
return;
noPopup: 1,
}
});
this.setPosition(x, y);
}
}
function toolDbName(cookieStyle) {
};
var ret = mw.config.get('wgDBname');

if (!cookieStyle) {
/**
ret += '_p';
* Sets the x and y coordinates stored and takes appropriate action,
}
* running hooks as appropriate.
return ret;
* @param {number} x, y Screen coordinates to set
}
*/
function saneLinkCheck(l) {
Mousetracker.prototype.setPosition = function (x, y) {
if (typeof l.article != typeof {} || typeof l.text != typeof '') {
this.x = x;
return false;
this.y = y;
}
if (this.dirty || this.hooks.length === 0) {
return true;
this.dirty = false;
}
return;
function editCounterLink(l) {
}
if (!saneLinkCheck(l)) {
if (typeof this.lastHook_x != 'number') {
return null;
this.lastHook_x = -100;
}
this.lastHook_y = -100;
if (!pg.wiki.wikimedia) {
}
return null;
var diff = (this.lastHook_x - x) * (this.lastHook_y - y);
}
diff = diff >= 0 ? diff : -diff;
var uN = l.article.userName();
if (diff > 1) {
var tool = getValueOf('popupEditCounterTool');
this.lastHook_x = x;
var url;
this.lastHook_y = y;
var defaultToolUrl = 'https://xtools.wmflabs.org/ec?user=$1&project=$2.$3&uselang=' + mw.config.get('wgUserLanguage');
if (this.dirty) {
switch (tool) {
this.dirty = false;
case 'custom':
} else {
url = simplePrintf(getValueOf('popupEditCounterUrl'), [encodeURIComponent(uN), toolDbName(), ]);
this.runHooks();
break;
}
case 'soxred':
}
case 'kate':
};
case 'interiot':

case 'supercount':
/**
default:
* Sets things in motion, unless they are already that is, registering an event handler on
var theWiki = pg.wiki.hostname.split('.');
* <code>document.onmousemove</code>. A half-hearted attempt is made to preserve the old event
url = simplePrintf(defaultToolUrl, [encodeURIComponent(uN), theWiki[0], theWiki[1]]);
* handler if there is one.
}
*/
return generalNavLink({
Mousetracker.prototype.enable = function () {
url: url,
if (this.active) {
title: tprintf('editCounterLinkHint', [uN]),
return;
newWin: l.newWin,
}
text: l.text,
this.active = true;
noPopup: 1,
//~ Save the current handler for mousemove events. This isn't too
});
//~ robust, of course.
}
this.savedHandler = document.onmousemove;
function globalSearchLink(l) {
//~ Gotta save @tt{this} again for the closure, and use apply for
if (!saneLinkCheck(l)) {
//~ the member function.
return null;
var savedThis = this;
}
document.onmousemove = function (e) {
var base = 'https://global-search.toolforge.org/?uselang=' + mw.config.get('wgUserLanguage') + '&q=';
savedThis.track.apply(savedThis, [e]);
var article = l.article.urlString({
};
keepSpaces: true
if (this.loopDelay) {
});
this.timer = setInterval(function () {
return generalNavLink({
//log('loop delay in mousetracker is working');
url: base + article,
savedThis.runHooks();
newWin: l.newWin,
}, this.loopDelay);
title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]),
}
text: l.text,
};
noPopup: 1,

});
/**
}
* Disables the tracker, removing the event handler.
function googleLink(l) {
*/
if (!saneLinkCheck(l)) {
Mousetracker.prototype.disable = function () {
return null;
if (!this.active) {
}
return;
var base = 'https://www.google.com/search?q=';
}
var article = l.article.urlString({
if (typeof this.savedHandler === 'function') {
keepSpaces: true
document.onmousemove = this.savedHandler;
});
} else {
return generalNavLink({
delete document.onmousemove;
url: base + '%22' + article + '%22',
}
newWin: l.newWin,
if (this.timer) {
title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]),
clearInterval(this.timer);
text: l.text,
}
noPopup: 1,
this.active = false;
});
};
}

function editorListLink(l) {
/**
if (!saneLinkCheck(l)) {
* Creates a new Navpopup.
return null;
}
* Gets a UID for the popup and
* @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable.
var article = l.article.articleFromTalkPage() || l.article;
* @constructor
var url = 'https://xtools.wmflabs.org/articleinfo/' + encodeURI(pg.wiki.hostname) + '/' + article.urlString() + '?uselang=' + mw.config.get('wgUserLanguage');
* @class The Navpopup class. This generates popup hints, and does some management of them.
return generalNavLink({
*/
url: url,
function Navpopup(/*init*/) {
title: tprintf('editorListHint', [article]),
//alert('new Navpopup(init)');
newWin: l.newWin,

text: l.text,
/**
noPopup: 1,
* UID for each Navpopup instance.
});
* Read-only.
}
* @type {number}
function generalNavLink(l) {
*/
l.className = l.className === null ? 'popupNavLink' : l.className;
this.uid = Navpopup.uid++;
return generalLink(l);

}
/**
function getHistoryInfo(wikipage, whatNext) {
* Read-only flag for current visibility of the popup.
log('getHistoryInfo');
* @type {boolean}
getHistory(wikipage, whatNext ? function(d) {
* @private
whatNext(processHistory(d));
*/
}
this.visible = false;
: processHistory);

}
/** Flag to be set when we want to cancel a previous request to
function getHistory(wikipage, onComplete) {
* show the popup in a little while.
log('getHistory');
* @private
var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' + new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit');
* @type {boolean}
log('getHistory: url=' + url);
*/
return startDownload(url, pg.idNumber + 'history', onComplete);
this.noshow = false;
}

function processHistory(download) {
/** Categorised list of hooks.
var jsobj = getJsObj(download.data);
* @see #runHooks
try {
* @see #addHook
var revisions = anyChild(jsobj.query.pages).revisions;
* @private
var edits = [];
* @type {Object}
for (var i = 0; i < revisions.length; ++i) {
*/
edits.push({
this.hooks = {
oldid: revisions[i].revid,
create: [],
editor: revisions[i].user
unhide: [],
});
hide: [],
}
};
log('processed ' + edits.length + ' edits');

return finishProcessHistory(edits, mw.config.get('wgUserName'));
/**
} catch (someError) {
* list of unique IDs of hook functions, to avoid duplicates
log('Something went wrong with JSON business');
* @private
return finishProcessHistory([]);
*/
}
this.hookIds = {};
}

function finishProcessHistory(edits, userName) {
/** List of downloads associated with the popup.
var histInfo = {};
* @private
histInfo.edits = edits;
* @type {Array}
histInfo.userName = userName;
*/
for (var i = 0; i < edits.length; ++i) {
this.downloads = [];
if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor == userName) {

histInfo.myLastEdit = {
/**
index: i,
* Number of uncompleted downloads.
oldid: edits[i].oldid,
* @type {number}
previd: i === 0 ? null : edits[i - 1].oldid,
*/
};
this.pending = null;
}

if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor != edits[0].editor) {
/**
histInfo.firstNewEditor = {
* Tolerance in pixels when detecting whether the mouse has left the popup.
index: i,
* @type {number}
oldid: edits[i].oldid,
*/
previd: i === 0 ? null : edits[i - 1].oldid,
this.fuzz = 5;
};

}
/**
}
* Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position.
return histInfo;
* @type {boolean}
}
*/
function defaultize(x) {
this.constrained = true;
if (pg.option[x] === null || typeof pg.option[x] == 'undefined') {

if (typeof window[x] != 'undefined') {
/**
pg.option[x] = window[x];
* The popup width in pixels.
} else {
* @private
pg.option[x] = pg.optionDefault[x];
* @type {number}
}
*/
}
this.width = 0;
}

function newOption(x, def) {
/**
pg.optionDefault[x] = def;
* The popup width in pixels.
}
* @private
function setDefault(x, def) {
* @type {number}
return newOption(x, def);
*/
}
this.height = 0;
function getValueOf(varName) {

defaultize(varName);
/**
return pg.option[varName];
* The main content DIV element.
}
* @type HTMLDivElement
function useDefaultOptions() {
*/
for (var p in pg.optionDefault) {
this.mainDiv = null;
pg.option[p] = pg.optionDefault[p];
this.createMainDiv();
if (typeof window[p] != 'undefined') {

delete window[p];
// if (!init || typeof init.popups_draggable=='undefined' || init.popups_draggable) {
}
// this.makeDraggable(true);
}
// }
}
}
function setOptions() {

var userIsSysop = false;
/**
if (mw.config.get('wgUserGroups')) {
* A UID for each Navpopup. This constructor property is just a counter.
for (var g = 0; g < mw.config.get('wgUserGroups').length; ++g) {
* @type {number}
if (mw.config.get('wgUserGroups')[g] == 'sysop') {
* @private
userIsSysop = true;
*/
}
Navpopup.uid = 0;
}

}
/**
newOption('popupDelay', 0.5);
* Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible.
newOption('popupHideDelay', 0.5);
* @type {boolean}
newOption('simplePopups', false);
*/
newOption('popupStructure', 'shortmenus');
Navpopup.prototype.isVisible = function () {
newOption('popupActionsMenu', true);
return this.visible;
newOption('popupSetupMenu', true);
};
newOption('popupAdminLinks', userIsSysop);

newOption('popupShortcutKeys', false);
/**
newOption('popupHistoricalLinks', true);
* Repositions popup using CSS style.
newOption('popupOnlyArticleLinks', true);
* @private
newOption('removeTitles', true);
* @param {number} x x-coordinate (px)
newOption('popupMaxWidth', 350);
* @param {number} y y-coordinate (px)
newOption('popupSimplifyMainLink', true);
* @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition}
newOption('popupAppendRedirNavLinks', true);
*/
newOption('popupTocLinks', false);
Navpopup.prototype.reposition = function (x, y, noLimitHor) {
newOption('popupSubpopups', true);
log('reposition(' + x + ',' + y + ',' + noLimitHor + ')');
newOption('popupDragHandle', false);
if (typeof x != 'undefined' && x !== null) {
newOption('popupLazyPreviews', true);
this.left = x;
newOption('popupLazyDownloads', true);
}
newOption('popupAllDabsStubs', false);
if (typeof y != 'undefined' && y !== null) {
newOption('popupDebugging', false);
this.top = y;
newOption('popupActiveNavlinks', true);
}
newOption('popupModifier', false);
if (typeof this.left != 'undefined' && typeof this.top != 'undefined') {
newOption('popupModifierAction', 'enable');
this.mainDiv.style.left = this.left + 'px';
newOption('popupDraggable', true);
this.mainDiv.style.top = this.top + 'px';
newOption('popupReview', false);
}
newOption('popupLocale', false);
if (!noLimitHor) {
newOption('popupDateTimeFormatterOptions', {
this.limitHorizontalPosition();
year: 'numeric',
}
month: 'long',
//console.log('navpop'+this.uid+' - (left,top)=(' + this.left + ',' + this.top + '), css=('
day: 'numeric',
//+ this.mainDiv.style.left + ',' + this.mainDiv.style.top + ')');
hour12: false,
};
hour: '2-digit',

minute: '2-digit',
/**
second: '2-digit',
* Prevents popups from being in silly locations. Hopefully.
});
* Should not be run if {@link #constrained} is true.
newOption('popupDateFormatterOptions', {
* @private
year: 'numeric',
*/
month: 'long',
Navpopup.prototype.limitHorizontalPosition = function () {
day: 'numeric',
if (!this.constrained || this.tooWide) {
});
return;
newOption('popupTimeFormatterOptions', {
}
hour12: false,
this.updateDimensions();
hour: '2-digit',
var x = this.left;
minute: '2-digit',
var w = this.width;
second: '2-digit',
var cWidth = document.body.clientWidth;
});

newOption('popupImages', true);
// log('limitHorizontalPosition: x='+x+
newOption('imagePopupsForImages', true);
// ', this.left=' + this.left +
newOption('popupNeverGetThumbs', false);
// ', this.width=' + this.width +
newOption('popupThumbAction', 'imagepage');
// ', cWidth=' + cWidth);
newOption('popupImageSize', 60);

newOption('popupImageSizeLarge', 200);
if (
newOption('popupFixRedirs', false);
x + w >= cWidth ||
newOption('popupRedirAutoClick', 'wpDiff');
(x > 0 &&
newOption('popupFixDabs', false);
this.maxWidth &&
newOption('popupDabsAutoClick', 'wpDiff');
this.width < this.maxWidth &&
newOption('popupRevertSummaryPrompt', false);
this.height > this.width &&
newOption('popupMinorReverts', false);
x > cWidth - this.maxWidth)
newOption('popupRedlinkRemoval', false);
) {
newOption('popupRedlinkAutoClick', 'wpDiff');
// This is a very nasty hack. There has to be a better way!
newOption('popupWatchDisambiggedPages', null);
// We find the "natural" width of the div by positioning it at the far left
newOption('popupWatchRedirredPages', null);
// then reset it so that it should be flush right (well, nearly)
newOption('popupDabWiktionary', 'last');
this.mainDiv.style.left = '-10000px';
newOption('popupNavLinks', true);
this.mainDiv.style.width = this.maxWidth + 'px';
newOption('popupNavLinkSeparator', ' &sdot; ');
var naturalWidth = parseInt(this.mainDiv.offsetWidth, 10);
newOption('popupLastEditLink', true);
var newLeft = cWidth - naturalWidth - 1;
newOption('popupEditCounterTool', 'supercount');
if (newLeft < 0) {
newOption('popupEditCounterUrl', '');
newLeft = 0;
newOption('popupPreviews', true);
this.tooWide = true;
newOption('popupSummaryData', true);
} // still unstable for really wide popups?
newOption('popupMaxPreviewSentences', 5);
log(
newOption('popupMaxPreviewCharacters', 600);
'limitHorizontalPosition: moving to (' +
newOption('popupLastModified', true);
newLeft +
newOption('popupPreviewKillTemplates', true);
',' +
newOption('popupPreviewRawTemplates', true);
this.top +
newOption('popupPreviewFirstParOnly', true);
');' +
newOption('popupPreviewCutHeadings', true);
' naturalWidth=' +
newOption('popupPreviewButton', false);
naturalWidth +
newOption('popupPreviewButtonEvent', 'click');
', clientWidth=' +
newOption('popupPreviewDiffs', true);
cWidth
newOption('popupDiffMaxLines', 100);
);
newOption('popupDiffContextLines', 2);
this.reposition(newLeft, null, true);
newOption('popupDiffContextCharacters', 40);
}
newOption('popupDiffDates', true);
};
newOption('popupDiffDatePrinter', 'toLocaleString');

newOption('popupReviewedSummary', popupString('defaultpopupReviewedSummary'));
/**
newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary'));
* Counter indicating the z-order of the "highest" popup.
newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary'));
* We start the z-index at 1000 so that popups are above everything
newOption('popupRevertSummary', popupString('defaultpopupRevertSummary'));
* else on the screen.
newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary'));
* @private
newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary'));
* @type {number}
newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary'));
*/
newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary'));
Navpopup.highest = 1000;
newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary'));

newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary'));
/**
newOption('popupHistoryLimit', 50);
* Brings popup to the top of the z-order.
newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect, popupFilterPageSize, popupFilterCountLinks, popupFilterCountImages, popupFilterCountCategories, popupFilterLastModified, popupFilterWikibaseItem, ]);
* We increment the {@link #highest} property of the contructor here.
newOption('extraPopupFilters', []);
* @private
newOption('popupOnEditSelection', 'cursor');
*/
newOption('popupPreviewHistory', true);
Navpopup.prototype.raise = function () {
newOption('popupImageLinks', true);
this.mainDiv.style.zIndex = Navpopup.highest + 1;
newOption('popupCategoryMembers', true);
++Navpopup.highest;
newOption('popupUserInfo', true);
};
newOption('popupHistoryPreviewLimit', 25);

newOption('popupContribsPreviewLimit', 25);
/**
newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion');
* Shows the popup provided {@link #noshow} is not true.
newOption('popupShowGender', true);
* Updates the position, brings the popup to the top of the z-order and unhides it.
newOption('popupNewWindows', false);
*/
newOption('popupLinksNewWindow', {
Navpopup.prototype.show = function () {
lastContrib: true,
//document.title+='s';
sinceMe: true
if (this.noshow) {
});
return;
newOption('popupDabRegexp', 'disambiguation\\}\\}|\\{\\{\\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?');
//document.title+='t';
newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub');
this.reposition();
newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo');
this.raise();
}
this.unhide();
pg.string = {
};
article: 'article',

category: 'category',
/**
categories: 'categories',
* Checks to see if the mouse pointer has
image: 'image',
* stabilised (checking every <code>time</code>/2 milliseconds) and runs the
images: 'images',
* {@link #show} method if it has.
stub: 'stub',
* @param {number} time The minimum time (ms) before the popup may be shown.
'section stub': 'section stub',
*/
'Empty page': 'Empty page',
Navpopup.prototype.showSoonIfStable = function (time) {
kB: 'kB',
log('showSoonIfStable, time=' + time);
bytes: 'bytes',
if (this.visible) {
day: 'day',
return;
days: 'days',
}
hour: 'hour',
this.noshow = false;
hours: 'hours',

minute: 'minute',
//~ initialize these variables so that we never run @tt{show} after
minutes: 'minutes',
//~ just half the time
second: 'second',
this.stable_x = -10000;
seconds: 'seconds',
this.stable_y = -10000;
week: 'week',

weeks: 'weeks',
var stableShow = function () {
search: 'search',
log('stableShow called');
SearchHint: 'Find English Wikipedia articles containing %s',
var new_x = Navpopup.tracker.x,
web: 'web',
new_y = Navpopup.tracker.y;
global: 'global',
var dx = savedThis.stable_x - new_x,
globalSearchHint: 'Search across Wikipedias in different languages for %s',
dy = savedThis.stable_y - new_y;
googleSearchHint: 'Google for %s',
var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz;
actions: 'actions',
//document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] ';
popupsMenu: 'popups',
if (dx * dx <= fuzz2 && dy * dy <= fuzz2) {
togglePreviewsHint: 'Toggle preview generation in popups on this page',
log('mouse is stable');
'enable previews': 'enable previews',
clearInterval(savedThis.showSoonStableTimer);
'disable previews': 'disable previews',
savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]);
'toggle previews': 'toggle previews',
savedThis.show.apply(savedThis, []);
'show preview': 'show preview',
savedThis.limitHorizontalPosition.apply(savedThis, []);
reset: 'reset',
return;
'more...': 'more...',
}
disable: 'disable popups',
savedThis.stable_x = new_x;
disablePopupsHint: 'Disable popups on this page. Reload page to re-enable.',
savedThis.stable_y = new_y;
historyfeedHint: 'RSS feed of recent changes to this page',
};
purgePopupsHint: 'Reset popups, clearing all cached popup data.',
var savedThis = this;
PopupsHint: 'Reset popups, clearing all cached popup data.',
this.showSoonStableTimer = setInterval(stableShow, time / 2);
spacebar: 'space',
};
view: 'view',

'view article': 'view article',
/**
viewHint: 'Go to %s',
* Sets the {@link #noshow} flag and hides the popup. This should be called
talk: 'talk',
* when the mouse leaves the link before
'talk page': 'talk page',
* (or after) it's actually been displayed.
'this&nbsp;revision': 'this&nbsp;revision',
*/
'revision %s of %s': 'revision %s of %s',
Navpopup.prototype.banish = function () {
'Revision %s of %s': 'Revision %s of %s',
log('banish called');
'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s',
// hide and prevent showing with showSoon in the future
'Toggle image size': 'Click to toggle image size',
this.noshow = true;
del: 'del',
if (this.showSoonStableTimer) {
delete: 'delete',
log('clearing showSoonStableTimer');
deleteHint: 'Delete %s',
clearInterval(this.showSoonStableTimer);
undeleteShort: 'un',
}
UndeleteHint: 'Show the deletion history for %s',
this.hide();
protect: 'protect',
};
protectHint: 'Restrict editing rights to %s',

unprotectShort: 'un',
/**
unprotectHint: 'Allow %s to be edited by anyone again',
* Runs hooks added with {@link #addHook}.
'send thanks': 'send thanks',
* @private
ThanksHint: 'Send a thank you notification to this user',
* @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
move: 'move',
* @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
'move page': 'move page',
*/
MovepageHint: 'Change the title of %s',
Navpopup.prototype.runHooks = function (key, when) {
edit: 'edit',
if (!this.hooks[key]) {
'edit article': 'edit article',
return;
editHint: 'Change the content of %s',
}
'edit talk': 'edit talk',
var keyHooks = this.hooks[key];
new: 'new',
var len = keyHooks.length;
'new topic': 'new topic',
for (var i = 0; i < len; ++i) {
newSectionHint: 'Start a new section on %s',
if (keyHooks[i] && keyHooks[i].when == when) {
'null edit': 'null edit',
if (keyHooks[i].hook.apply(this, [])) {
nullEditHint: 'Submit an edit to %s, making no changes ',
// remove the hook
hist: 'hist',
if (keyHooks[i].hookId) {
history: 'history',
delete this.hookIds[keyHooks[i].hookId];
historyHint: 'List the changes made to %s',
}
last: 'prev',
keyHooks[i] = null;
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',
* Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the
'last set of edits': 'latest edits',
* Navpopup instance, and no arguments.
lastContribHint: 'Show the net effect of changes made by the last editor',
* @param {Function} hook The hook function. Functions that return true are deleted.
cur: 'cur',
* @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
diffCur: 'diffCur',
* @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
'Show changes since revision %s': 'Show changes since revision %s',
* @param {String} uid A truthy string identifying the hook function; if it matches another hook
'%s old': '%s old',
* in this position, it won't be added again.
oldEdit: 'oldEdit',
*/
purge: 'purge',
Navpopup.prototype.addHook = function (hook, key, when, uid) {
purgeHint: 'Demand a fresh copy of %s',
when = when || 'after';
raw: 'source',
if (!this.hooks[key]) {
rawHint: 'Download the source of %s',
return;
render: 'simple',
}
renderHint: 'Show a plain HTML version of %s',
// if uid is specified, don't add duplicates
'Show the edit made to get revision': 'Show the edit made to get revision',
var hookId = null;
sinceMe: 'sinceMe',
if (uid) {
'changes since mine': 'diff my edit',
hookId = [key, when, uid].join('|');
sinceMeHint: 'Show changes since my last edit',
if (this.hookIds[hookId]) {
"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",
return;
eds: 'eds',
}
editors: 'editors',
this.hookIds[hookId] = true;
editorListHint: 'List the users who have edited %s',
}
related: 'related',
this.hooks[key].push({ hook: hook, when: when, hookId: hookId });
relatedChanges: 'relatedChanges',
};
'related changes': 'related changes',

RecentchangeslinkedHint: 'Show changes in articles related to %s',
/**
editOld: 'editOld',
* Creates the main DIV element, which contains all the actual popup content.
rv: 'rv',
* Runs hooks with key 'create'.
revert: 'revert',
* @private
revertHint: 'Revert to %s',
*/
defaultpopupReviewedSummary: 'Accepted by reviewing the [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
Navpopup.prototype.createMainDiv = function () {
defaultpopupRedlinkSummary: 'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
if (this.mainDiv) {
defaultpopupFixDabsSummary: 'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
return;
defaultpopupFixRedirsSummary: 'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
}
defaultpopupExtendedRevertSummary: 'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
this.runHooks('create', 'before');
defaultpopupRevertToPreviousSummary: 'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
var mainDiv = document.createElement('div');
defaultpopupRevertSummary: 'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',

defaultpopupQueriedRevertToPreviousSummary: 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
var savedThis = this;
defaultpopupQueriedRevertSummary: 'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
mainDiv.onclick = function (e) {
defaultpopupRmDabLinkSummary: 'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
savedThis.onclickHandler(e);
Redirects: 'Redirects',
};
' to ': ' to ',
mainDiv.className = this.className ? this.className : 'navpopup_maindiv';
'Bypass redirect': 'Bypass redirect',
mainDiv.id = mainDiv.className + this.uid;
'Fix this redirect': 'Fix this redirect',

disambig: 'disambig',
mainDiv.style.position = 'absolute';
disambigHint: 'Disambiguate this link to [[%s]]',
mainDiv.style.minWidth = '350px';
'Click to disambiguate this link to:': 'Click to disambiguate this link to:',
mainDiv.style.display = 'none';
'remove this link': 'remove this link',
mainDiv.className = 'navpopup';
'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',
// easy access to javascript object through DOM functions
mainlink: 'mainlink',
mainDiv.navpopup = this;
wikiLink: 'wikiLink',

wikiLinks: 'wikiLinks',
this.mainDiv = mainDiv;
'links here': 'links here',
document.body.appendChild(mainDiv);
whatLinksHere: 'whatLinksHere',
this.runHooks('create', 'after');
'what links here': 'what links here',
};
WhatlinkshereHint: 'List the pages that are hyperlinked to %s',

unwatchShort: 'un',
/**
watchThingy: 'watch',
* Calls the {@link #raise} method.
watchHint: 'Add %s to my watchlist',
* @private
unwatchHint: 'Remove %s from my watchlist',
*/
'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits',
Navpopup.prototype.onclickHandler = function (/*e*/) {
'%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s',
this.raise();
rss: 'rss',
};
'Diff truncated for performance reasons': 'Diff truncated for performance reasons',

'Old revision': 'Old revision',
/**
'New revision': 'New revision',
* Makes the popup draggable, using a {@link Drag} object.
'Something went wrong :-(': 'Something went wrong :-(',
* @private
'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent',
*/
'Unknown date': 'Unknown date',
Navpopup.prototype.makeDraggable = function (handleName) {
'Empty category': 'Empty category',
if (!this.mainDiv) {
'Category members (%s shown)': 'Category members (%s shown)',
this.createMainDiv();
'No image links found': 'No image links found',
}
'File links': 'File links',
var drag = new Drag();
'No image found': 'No image found',
if (!handleName) {
'Image from Commons': 'Image from Commons',
drag.startCondition = function (e) {
'Description page': 'Description page',
try {
'Alt text:': 'Alt text:',
if (!e.shiftKey) {
revdel: 'Hidden revision',
return false;
user: 'user',
}
'user&nbsp;page': 'user&nbsp;page',
} catch (err) {
'user talk': 'user talk',
return false;
'edit user talk': 'edit user talk',
}
'leave comment': 'leave comment',
return true;
email: 'email',
};
'email user': 'email user',
}
EmailuserHint: 'Send an email to %s',
var dragHandle;
space: 'space',
if (handleName) {
PrefixIndexHint: 'Show pages in the userspace of %s',
dragHandle = document.getElementById(handleName);
count: 'count',
}
'edit counter': 'edit counter',
if (!dragHandle) {
editCounterLinkHint: 'Count the contributions made by %s',
dragHandle = this.mainDiv;
contribs: 'contribs',
}
contributions: 'contributions',
var np = this;
deletedContribs: 'deleted contributions',
drag.endHook = function (x, y) {
DeletedcontributionsHint: 'List deleted edits made by %s',
Navpopup.tracker.dirty = true;
ContributionsHint: 'List the contributions made by %s',
np.reposition(x, y);
log: 'log',
};
'user log': 'user log',
drag.init(dragHandle, this.mainDiv);
userLogHint: "Show %s's user log",
};
arin: 'ARIN lookup',

'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database',
/**
unblockShort: 'un',
* Hides the popup using CSS. Runs hooks with key 'hide'.
block: 'block',
* Sets {@link #visible} appropriately.
'block user': 'block user',
* {@link #banish} should be called externally instead of this method.
IpblocklistHint: 'Unblock %s',
* @private
BlockipHint: 'Prevent %s from editing',
*/
'block log': 'block log',
Navpopup.prototype.hide = function () {
blockLogHint: 'Show the block log for %s',
this.runHooks('hide', 'before');
protectLogHint: 'Show the protection log for %s',
this.abortDownloads();
pageLogHint: 'Show the page log for %s',
if (typeof this.visible != 'undefined' && this.visible) {
deleteLogHint: 'Show the deletion log for %s',
this.mainDiv.style.display = 'none';
'Invalid %s %s': 'The option %s is invalid: %s',
this.visible = false;
'No backlinks found': 'No backlinks found',
}
' and more': ' and more',
this.runHooks('hide', 'after');
undo: 'undo',
};
undoHint: 'undo this edit',

'Download preview data': 'Download preview data',
/**
'Invalid or IP user': 'Invalid or IP user',
* Shows the popup using CSS. Runs hooks with key 'unhide'.
'Not a registered username': 'Not a registered username',
* Sets {@link #visible} appropriately. {@link #show} should be called externally instead of this method.
BLOCKED: 'BLOCKED',
* @private
'Has blocks': 'Has blocks',
*/
' edits since: ': ' edits since: ',
Navpopup.prototype.unhide = function () {
'last edit on ': 'last edit on ',
this.runHooks('unhide', 'before');
'he/him': 'he/him',
if (typeof this.visible != 'undefined' && !this.visible) {
'she/her': 'she/her',
this.mainDiv.style.display = 'inline';
'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort',
this.visible = true;
'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.',
this.runHooks('unhide', 'after');
'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.',
};
'Open full-size image': 'Open full-size image',

zxy: 'zxy',
/**
autoedit_version: 'np20140416',
* Sets the <code>innerHTML</code> attribute of the main div containing the popup content.
};
* @param {String} html The HTML to set.
function popupString(str) {
*/
if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) {
Navpopup.prototype.setInnerHTML = function (html) {
return popupStrings[str];
this.mainDiv.innerHTML = html;
}
};
if (pg.string[str]) {

return pg.string[str];
/**
}
* Updates the {@link #width} and {@link #height} attributes with the CSS properties.
return str;
* @private
}
*/
function tprintf(str, subs) {
Navpopup.prototype.updateDimensions = function () {
if (typeof subs != typeof []) {
this.width = parseInt(this.mainDiv.offsetWidth, 10);
subs = [subs];
this.height = parseInt(this.mainDiv.offsetHeight, 10);
}
};
return simplePrintf(popupString(str), subs);

}
/**
if (document.readyState == 'complete') {
* Checks if the point (x,y) is within {@link #fuzz} of the
autoEdit();
* {@link #mainDiv}.
} else {
* @param {number} x x-coordinate (px)
$(window).on('load', autoEdit);
* @param {number} y y-coordinate (px)
}
* @type {boolean}
(function() {
*/
var once = true;
Navpopup.prototype.isWithin = function (x, y) {
function dynamicContentHandler($content) {
//~ If we're not even visible, no point should be considered as
if ($content.attr('id') == 'mw-content-text') {
//~ being within the popup.
if (once) {
if (!this.visible) {
once = false;
return false;
return;
}
}
this.updateDimensions();
}
var fuzz = this.fuzz || 0;
function registerHooksForVisibleNavpops() {
//~ Use a simple box metric here.
for (var i = 0; pg.current.links && i < pg.current.links.length; ++i) {
return (
var navpop = pg.current.links[i].navpopup;
x + fuzz >= this.left &&
if (!navpop || !navpop.isVisible()) {
x - fuzz <= this.left + this.width &&
continue;
y + fuzz >= this.top &&
}
y - fuzz <= this.top + this.height
Navpopup.tracker.addHook(posCheckerHook(navpop));
);
}
};
}

function doIt() {
/**
registerHooksForVisibleNavpops();
* Adds a download to {@link #downloads}.
$content.each(function() {
* @param {Downloader} download
this.ranSetupTooltipsAlready = false;
*/
setupTooltips(this);
Navpopup.prototype.addDownload = function (download) {
});
if (!download) {
}
return;
setupPopups(doIt);
}
}
this.downloads.push(download);
mw.hook('wikipage.content').add(dynamicContentHandler);
};
mw.hook('ext.echo.overlay.beforeShowingOverlay').add(function($overlay) {

dynamicContentHandler($overlay.find('.mw-echo-state'));
/**
});
* Aborts the downloads listed in {@link #downloads}.
}
* @see Downloader#abort
)();
*/
});
Navpopup.prototype.abortDownloads = function () {
}
for (var i = 0; i < this.downloads.length; ++i) {
, {
var d = this.downloads[i];
"css": [".popupMoreLink{display:block;text-align:right;cursor:pointer}ins.popupDiff{background:#afe}del.popupDiff{background:#ffe6e6}#selectionPreview{border:2px solid #ddd;background-color:#eef;padding:6px}.navpopup{border:1px solid #bbb;background-color:#fff;padding:10px;padding-bottom:5px;font-size:11px;box-shadow:0 3px 8px rgba(50,50,50,0.35);word-wrap:break-word}.navpopup hr{color:#aaa !important;background-color:#aaa !important} .popupDrag{background-color:#ffbe20;height:5px;margin-top:-5px;margin-bottom:5px}.popupDragHandle{cursor:move;position:relative} .popup_menu{display:none;position:absolute;left:0;margin:0;margin-top:1.4em;line-height:1.25em;top:0;z-index:2;width:10em;background:white;border:1px solid grey;padding:0 !important;margin-left:-6px;border-width:1px 1px 1px 6px}.popup_menu li{ list-style:none;margin:0;padding:0}.popup_menu a{display:block;padding:3px;color:#555}.popup_menu_row a{display:inline-block}.popup_menu_row{color:#aaa}.popup_drop{display:inline;position:relative}.popup_drop a,.popup_drop a:visited{padding:3px;margin:0;font-weight:bold;color:#0645ad}.popup_drop:hover .popup_menu,.popup_drop .popup_menu:hover{display:inline;background:white;padding:2px;color:#555}.popup_drop:hover{background:#ccf;color:#44f} .popup_menu a:hover{background:grey;color:#fff;text-decoration:none}.popup_mainlink{font-size:140%;font-weight:bold}.popup_mainlink a{color:#000}a.popup_change_title_link{color:#152}.popup_diff_dates{font-style:italic;background:none}.popup_menu_item a{display:block}.popup_history_row_even{background:#eee}.popup_history_date{font-weight:bold;font-size:120%}.popup_history_row_odd,.popup_history_row_even{display:flex}.popup_history_row_even td:nth-child(3),.popup_history_row_odd td:nth-child(3){flex:3;word-break:break-word}.popup_history_row_even td:nth-child(4),.popup_history_row_odd td:nth-child(4){flex:7;word-break:break-word}.popup_history_row_even \u003E td:not(:last-child),.popup_history_row_odd \u003E td:not(:last-child){margin-right:2px} .popupPreview a.extiw,.popupPreview a.extiw:active{color:#36b;background:none;padding:0}.popupPreview .external{color:#36b} .popupPreview .plainlinks a{background:none !important;padding:0 !important} .popup_menu:hover{box-shadow:0 0 5px 5px rgba(179,179,255,0.3) } .popup_menu::before{content:'';display:block;position:absolute;height:1.5em;top:-1.4em;left:-1.2em;width:calc(2 * var(--navpop-m-len,6ch));transform:perspective(1px) rotateX(1deg)} .popup_menu::before{z-index:1}.popup_menu li{position:relative;z-index:2} .popup_menu li::before{content:'';display:block;position:absolute;top:0;height:calc(100% + .5em);left:-1.7em;width:1.7em}.popup_menu li::after{content:'';display:block;position:absolute;top:0;height:calc(100% + .5em);left:calc(10em - 1px); width:1em}"]
if (d && d.abort) {
}];
d.abort();
}
}
this.downloads = [];
};

/**
* A {@link Mousetracker} instance which is a property of the constructor (pseudo-global).
*/
Navpopup.tracker = new Mousetracker();
// ENDFILE: navpopup.js

// STARTFILE: diff.js
/*
* Javascript Diff Algorithm
* By John Resig (http://ejohn.org/) and [[:en:User:Lupin]]
*
* More Info:
* http://ejohn.org/projects/javascript-diff-algorithm/
*/

function delFmt(x) {
if (!x.length) {
return '';
}
return "<del class='popupDiff'>" + x.join('') + '</del>';
}

function insFmt(x) {
if (!x.length) {
return '';
}
return "<ins class='popupDiff'>" + x.join('') + '</ins>';
}

function countCrossings(a, b, i, eject) {
// count the crossings on the edge starting at b[i]
if (!b[i].row && b[i].row !== 0) {
return -1;
}
var count = 0;
for (var j = 0; j < a.length; ++j) {
if (!a[j].row && a[j].row !== 0) {
continue;
}
if ((j - b[i].row) * (i - a[j].row) > 0) {
if (eject) {
return true;
}
count++;
}
}
return count;
}

function shortenDiffString(str, context) {
var re = RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)');
var splitted = str.parenSplit(re);
var ret = [''];
for (var i = 0; i < splitted.length; i += 2) {
if (splitted[i].length < 2 * context) {
ret[ret.length - 1] += splitted[i];
if (i + 1 < splitted.length) {
ret[ret.length - 1] += splitted[i + 1];
}
continue;
} else {
if (i > 0) {
ret[ret.length - 1] += splitted[i].substring(0, context);
}
if (i + 1 < splitted.length) {
ret.push(splitted[i].substring(splitted[i].length - context) + splitted[i + 1]);
}
}
}
while (ret.length > 0 && !ret[0]) {
ret = ret.slice(1);
}
return ret;
}

function diffString(o, n, simpleSplit) {
var splitRe = RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)');

// We need to split the strings o and n first, and entify() the parts
// individually, so that the HTML entities are never cut apart. (AxelBoldt)
var out, i, oSplitted, nSplitted;
if (simpleSplit) {
oSplitted = o.split(/\b/);
nSplitted = n.split(/\b/);
} else {
oSplitted = o.parenSplit(splitRe);
nSplitted = n.parenSplit(splitRe);
}
for (i = 0; i < oSplitted.length; ++i) {
oSplitted[i] = oSplitted[i].entify();
}
for (i = 0; i < nSplitted.length; ++i) {
nSplitted[i] = nSplitted[i].entify();
}

out = diff(oSplitted, nSplitted);
var str = '';
var acc = []; // accumulator for prettier output

// crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out
// this doesn't always do things optimally but it should be fast enough
var maxOutputPair = 0;
for (i = 0; i < out.n.length; ++i) {
if (out.n[i].paired) {
if (maxOutputPair > out.n[i].row) {
// tangle - delete pairing
out.o[out.n[i].row] = out.o[out.n[i].row].text;
out.n[i] = out.n[i].text;
}
if (maxOutputPair < out.n[i].row) {
maxOutputPair = out.n[i].row;
}
}
}

// output the stuff preceding the first paired old line
for (i = 0; i < out.o.length && !out.o[i].paired; ++i) {
acc.push(out.o[i]);
}
str += delFmt(acc);
acc = [];

// main loop
for (i = 0; i < out.n.length; ++i) {
// output unpaired new "lines"
while (i < out.n.length && !out.n[i].paired) {
acc.push(out.n[i++]);
}
str += insFmt(acc);
acc = [];
if (i < out.n.length) {
// this new "line" is paired with the (out.n[i].row)th old "line"
str += out.n[i].text;
// output unpaired old rows starting after this new line's partner
var m = out.n[i].row + 1;
while (m < out.o.length && !out.o[m].paired) {
acc.push(out.o[m++]);
}
str += delFmt(acc);
acc = [];
}
}
return str;
}

// see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object
// FIXME: use obj.hasOwnProperty instead of this kludge!
var jsReservedProperties = RegExp(
'^(constructor|prototype|__((define|lookup)[GS]etter)__' +
'|eval|hasOwnProperty|propertyIsEnumerable' +
'|to(Source|String|LocaleString)|(un)?watch|valueOf)$'
);

function diffBugAlert(word) {
if (!diffBugAlert.list[word]) {
diffBugAlert.list[word] = 1;
alert('Bad word: ' + word + '\n\nPlease report this bug.');
}
}

diffBugAlert.list = {};

function makeDiffHashtable(src) {
var ret = {};
for (var i = 0; i < src.length; i++) {
if (jsReservedProperties.test(src[i])) {
src[i] += '<!-- -->';
}
if (!ret[src[i]]) {
ret[src[i]] = [];
}
try {
ret[src[i]].push(i);
} catch (err) {
diffBugAlert(src[i]);
}
}
return ret;
}

function diff(o, n) {
// pass 1: make hashtable ns with new rows as keys
var ns = makeDiffHashtable(n);

// pass 2: make hashtable os with old rows as keys
var os = makeDiffHashtable(o);

// pass 3: pair unique new rows and matching unique old rows
var i;
for (i in ns) {
if (ns[i].length == 1 && os[i] && os[i].length == 1) {
n[ns[i][0]] = { text: n[ns[i][0]], row: os[i][0], paired: true };
o[os[i][0]] = { text: o[os[i][0]], row: ns[i][0], paired: true };
}
}

// pass 4: pair matching rows immediately following paired rows (not necessarily unique)
for (i = 0; i < n.length - 1; i++) {
if (
n[i].paired &&
!n[i + 1].paired &&
n[i].row + 1 < o.length &&
!o[n[i].row + 1].paired &&
n[i + 1] == o[n[i].row + 1]
) {
n[i + 1] = { text: n[i + 1], row: n[i].row + 1, paired: true };
o[n[i].row + 1] = { text: o[n[i].row + 1], row: i + 1, paired: true };
}
}

// pass 5: pair matching rows immediately preceding paired rows (not necessarily unique)
for (i = n.length - 1; i > 0; i--) {
if (
n[i].paired &&
!n[i - 1].paired &&
n[i].row > 0 &&
!o[n[i].row - 1].paired &&
n[i - 1] == o[n[i].row - 1]
) {
n[i - 1] = { text: n[i - 1], row: n[i].row - 1, paired: true };
o[n[i].row - 1] = { text: o[n[i].row - 1], row: i - 1, paired: true };
}
}

return { o: o, n: n };
}
// ENDFILE: diff.js

// STARTFILE: init.js
function setSiteInfo() {
if (window.popupLocalDebug) {
pg.wiki.hostname = 'en.wikipedia.org';
} else {
pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?)
}
pg.wiki.wikimedia = RegExp(
'(wiki([pm]edia|source|books|news|quote|versity|species|voyage|data)|metawiki|wiktionary|mediawiki)[.]org'
).test(pg.wiki.hostname);
pg.wiki.wikia = RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname);
pg.wiki.isLocal = RegExp('^localhost').test(pg.wiki.hostname);
pg.wiki.commons =
pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org'
? 'commons.wikimedia.org'
: null;
pg.wiki.lang = mw.config.get('wgContentLanguage');
var port = location.port ? ':' + location.port : '';
pg.wiki.sitebase = pg.wiki.hostname + port;
}

function setUserInfo() {
var params = {
action: 'query',
list: 'users',
ususers: mw.config.get('wgUserName'),
usprop: 'rights',
};

pg.user.canReview = false;
if (getValueOf('popupReview')) {
getMwApi()
.get(params)
.done(function (data) {
var rights = data.query.users[0].rights;
pg.user.canReview = rights.indexOf('review') !== -1; // TODO: Should it be a getValueOf('ReviewRight') ?
});
}
}

function fetchSpecialPageNames() {
var params = {
action: 'query',
meta: 'siteinfo',
siprop: 'specialpagealiases',
formatversion: 2,
// cache for an hour
uselang: 'content',
maxage: 3600,
};
return getMwApi()
.get(params)
.then(function (data) {
pg.wiki.specialpagealiases = data.query.specialpagealiases;
});
}

function setTitleBase() {
var protocol = window.popupLocalDebug ? 'http:' : location.protocol;
pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ''); // as in http://some.thing.com/wiki/Article
pg.wiki.botInterfacePath = mw.config.get('wgScript');
pg.wiki.APIPath = mw.config.get('wgScriptPath') + '/api.php';
// default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo

var titletail = pg.wiki.botInterfacePath + '?title=';
//var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']);

// other sites may need to add code here to set titletail depending on how their urls work

pg.wiki.titlebase = protocol + '//' + pg.wiki.sitebase + titletail;
//pg.wiki.titlebase2 = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]);
pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath;
pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath;
pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath;
pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath;
pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath;
pg.re.basenames = RegExp(
'^(' +
map(literalizeRegex, [
pg.wiki.titlebase, //pg.wiki.titlebase2,
pg.wiki.articlebase,
]).join('|') +
')'
);
}

//////////////////////////////////////////////////
// Global regexps

function setMainRegex() {
var reStart = '[^:]*://';
var preTitles =
literalizeRegex(mw.config.get('wgScriptPath')) + '/(?:index[.]php|wiki[.]phtml)[?]title=';
preTitles += '|' + literalizeRegex(pg.wiki.articlePath + '/');

var reEnd = '(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?';
pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd);
}

function buildSpecialPageGroup(specialPageObj) {
var variants = [];
variants.push(mw.util.escapeRegExp(specialPageObj['realname']));
variants.push(mw.util.escapeRegExp(encodeURI(specialPageObj['realname'])));
specialPageObj.aliases.forEach(function (alias) {
variants.push(mw.util.escapeRegExp(alias));
variants.push(mw.util.escapeRegExp(encodeURI(alias)));
});
return variants.join('|');
}

function setRegexps() {
setMainRegex();
var sp = nsRe(pg.nsSpecialId);
pg.re.urlNoPopup = RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)');

pg.wiki.specialpagealiases.forEach(function (specialpage) {
if (specialpage.realname === 'Contributions') {
pg.re.contribs = RegExp(
'(title=|/)' +
sp +
'(?:%3A|:)(?:' +
buildSpecialPageGroup(specialpage) +
')' +
'(&target=|/|/' +
nsRe(pg.nsUserId) +
':)(.*)',
'i'
);
} else if (specialpage.realname === 'Diff') {
pg.re.specialdiff = RegExp(
'/' + sp + '(?:%3A|:)(?:' + buildSpecialPageGroup(specialpage) + ')' + '/([^?#]*)',
'i'
);
} else if (specialpage.realname === 'Emailuser') {
pg.re.email = RegExp(
'(title=|/)' +
sp +
'(?:%3A|:)(?:' +
buildSpecialPageGroup(specialpage) +
')' +
'(&target=|/|/(?:' +
nsRe(pg.nsUserId) +
':)?)(.*)',
'i'
);
} else if (specialpage.realname === 'Whatlinkshere') {
pg.re.backlinks = RegExp(
'(title=|/)' +
sp +
'(?:%3A|:)(?:' +
buildSpecialPageGroup(specialpage) +
')' +
'(&target=|/)([^&]*)',
'i'
);
}
});

var im = nsReImage();
// note: tries to get images in infobox templates too, e.g. movie pages, album pages etc
// (^|\[\[)image: *([^|\]]*[^|\] ]) *
// (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)?
// $4 = 120 as in 120px
pg.re.image = RegExp(
'(^|\\[\\[)' +
im +
': *([^|\\]]*[^|\\] ])' +
'([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' +
'(' +
getValueOf('popupImageVarsRegexp') +
')' +
' *= *(?:\\[\\[ *)?(?:' +
im +
':)?' +
'([^|]*?)(?:\\]\\])? *[|]? *\\n',
'img'
);
pg.re.imageBracketCount = 6;

pg.re.category = RegExp('\\[\\[' + nsRe(pg.nsCategoryId) + ': *([^|\\]]*[^|\\] ]) *', 'i');
pg.re.categoryBracketCount = 1;

pg.re.ipUser = RegExp(
'^' +
// IPv6
'(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' +
// IPv4
'|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' +
'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$'
);

pg.re.stub = RegExp(getValueOf('popupStubRegexp'), 'im');
pg.re.disambig = RegExp(getValueOf('popupDabRegexp'), 'im');

// FIXME replace with general parameter parsing function, this is daft
pg.re.oldid = RegExp('[?&]oldid=([^&]*)');
pg.re.diff = RegExp('[?&]diff=([^&]*)');
}

//////////////////////////////////////////////////
// miscellany

function setupCache() {
// page caching
pg.cache.pages = [];
}

function setMisc() {
pg.current.link = null;
pg.current.links = [];
pg.current.linksHash = {};

setupCache();

pg.timer.checkPopupPosition = null;
pg.counter.loop = 0;

// ids change with each popup: popupImage0, popupImage1 etc
pg.idNumber = 0;

// for myDecodeURI
pg.misc.decodeExtras = [
{ from: '%2C', to: ',' },
{ from: '_', to: ' ' },
{ from: '%24', to: '$' },
{ from: '%26', to: '&' }, // no ,
];
}

function getMwApi() {
if (!pg.api.client) {
pg.api.userAgent = 'Navigation popups/1.0 (' + mw.config.get('wgServerName') + ')';
pg.api.client = new mw.Api({
ajax: {
headers: {
'Api-User-Agent': pg.api.userAgent,
},
},
});
}
return pg.api.client;
}

// We need a callback since this might end up asynchronous because of
// the mw.loader.using() call.
function setupPopups(callback) {
if (setupPopups.completed) {
if (typeof callback === 'function') {
callback();
}
return;
}
// These dependencies should alse be enforced from the gadget,
// but not everyone loads this as a gadget, so double check
mw.loader
.using([
'mediawiki.util',
'mediawiki.api',
'mediawiki.user',
'user.options',
'mediawiki.jqueryMsg',
])
.then(fetchSpecialPageNames)
.then(function () {
// NB translatable strings should be set up first (strings.js)
// basics
setupDebugging();
setSiteInfo();
setTitleBase();
setOptions(); // see options.js
setUserInfo();

// namespaces etc
setNamespaces();
setInterwiki();

// regexps
setRegexps();
setRedirs();

// other stuff
setMisc();
setupLivePreview();

// main deal here
setupTooltips();
log('In setupPopups(), just called setupTooltips()');
Navpopup.tracker.enable();

setupPopups.completed = true;
if (typeof callback === 'function') {
callback();
}
});
}
// ENDFILE: init.js

// STARTFILE: navlinks.js
//////////////////////////////////////////////////
// navlinks... let the fun begin
//

function defaultNavlinkSpec() {
var str = '';
str += '<b><<mainlink|shortcut= >></b>';
if (getValueOf('popupLastEditLink')) {
str +=
'*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}';
}

// user links
// contribs - log - count - email - block
// count only if applicable; block only if popupAdminLinks
str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>';
str += 'if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}';
str +=
'if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}';

// editing links
// talkpage -> edit|new - history - un|watch - article|edit
// other page -> edit - history - un|watch - talk|edit|new
var editstr = '<<edit|shortcut=e>>';
var editOldidStr =
'if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' +
editstr +
'}';
var historystr = '<<history|shortcut=h>>|<<editors|shortcut=E|>>';
var watchstr = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';

str +=
'<br>if(talk){' +
editOldidStr +
'|<<new|shortcut=+>>' +
'*' +
historystr +
'*' +
watchstr +
'*' +
'<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
'}else{' + // not a talk page
editOldidStr +
'*' +
historystr +
'*' +
watchstr +
'*' +
'<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';

// misc links
str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';

// admin links
str +=
'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' +
'<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}';
return str;
}

function navLinksHTML(article, hint, params) {
//oldid, rcid) {
var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>';
// BAM
return navlinkStringToHTML(str, article, params);
}

function expandConditionalNavlinkString(s, article, z, recursionCount) {
var oldid = z.oldid,
rcid = z.rcid,
diff = z.diff;
// nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out)
if (typeof recursionCount != typeof 0) {
recursionCount = 0;
}
var conditionalSplitRegex = RegExp(
//(1 if \\( (2 2) \\) {(3 3)} (4 else {(5 5)} 4)1)
'(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))',
'i'
);
var splitted = s.parenSplit(conditionalSplitRegex);
// $1: whole conditional
// $2: test condition
// $3: true expansion
// $4: else clause (possibly empty)
// $5: false expansion (possibly null)
var numParens = 5;
var ret = splitted[0];
for (var i = 1; i < splitted.length; i = i + numParens + 1) {
var testString = splitted[i + 2 - 1];
var trueString = splitted[i + 3 - 1];
var falseString = splitted[i + 5 - 1];
if (typeof falseString == 'undefined' || !falseString) {
falseString = '';
}
var testResult = null;

switch (testString) {
case 'user':
testResult = !!article.userName();
break;
case 'talk':
testResult = !article.talkPage(); // talkPage converts _articles_ to talkPages
break;
case 'admin':
testResult = !!getValueOf('popupAdminLinks');
break;
case 'oldid':
testResult = !!(typeof oldid != 'undefined' && oldid);
break;
case 'rcid':
testResult = !!(typeof rcid != 'undefined' && rcid);
break;
case 'ipuser':
testResult = !!article.isIpUser();
break;
case 'mainspace_en':
testResult = isInMainNamespace(article) && pg.wiki.hostname == 'en.wikipedia.org';
break;
case 'wikimedia':
testResult = !!pg.wiki.wikimedia;
break;
case 'diff':
testResult = !!(typeof diff != 'undefined' && diff);
break;
}

switch (testResult) {
case null:
ret += splitted[i];
break;
case true:
ret += trueString;
break;
case false:
ret += falseString;
break;
}

// append non-conditional string
ret += splitted[i + numParens];
}
if (conditionalSplitRegex.test(ret) && recursionCount < 10) {
return expandConditionalNavlinkString(ret, article, z, recursionCount + 1);
}
return ret;
}

function navlinkStringToArray(s, article, params) {
s = expandConditionalNavlinkString(s, article, params);
var splitted = s.parenSplit(RegExp('<<(.*?)>>'));
var ret = [];
for (var i = 0; i < splitted.length; ++i) {
if (i % 2) {
// i odd, so s is a tag
var t = new navlinkTag();
var ss = splitted[i].split('|');
t.id = ss[0];
for (var j = 1; j < ss.length; ++j) {
var sss = ss[j].split('=');
if (sss.length > 1) {
t[sss[0]] = sss[1];
} else {
// no assignment (no "="), so treat this as a title (overwriting the last one)
t.text = popupString(sss[0]);
}
}
t.article = article;
var oldid = params.oldid,
rcid = params.rcid,
diff = params.diff;
if (typeof oldid !== 'undefined' && oldid !== null) {
t.oldid = oldid;
}
if (typeof rcid !== 'undefined' && rcid !== null) {
t.rcid = rcid;
}
if (typeof diff !== 'undefined' && diff !== null) {
t.diff = diff;
}
if (!t.text && t.id !== 'mainlink') {
t.text = popupString(t.id);
}
ret.push(t);
} else {
// plain HTML
ret.push(splitted[i]);
}
}
return ret;
}

function navlinkSubstituteHTML(s) {
return s
.split('*')
.join(getValueOf('popupNavLinkSeparator'))
.split('<menurow>')
.join('<li class="popup_menu_row">')
.split('</menurow>')
.join('</li>')
.split('<menu>')
.join('<ul class="popup_menu">')
.split('</menu>')
.join('</ul>');
}

function navlinkDepth(magic, s) {
return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length;
}

// navlinkString: * becomes the separator
// <<foo|bar=baz|fubar>> 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 += '<li class="popup_menu_item">' + p[i].html() + '</li>';
} 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 = '</' + tagType + '>';
}
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&section=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&section=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: ' + String(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 "<a href=...>...</a> 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
/**
* Load diff data.
*
* lets jump through hoops to find the rev ids we need to retrieve
*
* @param {Title} article
* @param {String} oldid
* @param {String} diff
* @param {Navpopup} navpop
*/
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',
};
params.fromtitle = article.toString();

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 && oldid !== 'cur') {
params.fromrev = oldid;
}
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&rvslots=main&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,
]),
};
getMwApi()
.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.slots.main.content.split('\n');
var newlines = navpop.diffData.newRev.revision.slots.main.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 = '<hr />';
if (getValueOf('popupDiffDates')) {
html += diffDatesTable(navpop);
html += '<hr />';
}
html += shortenDiffString(
diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),
getValueOf('popupDiffContextCharacters')
).join('<hr />');
setPopupTipsAndHTML(
html.split('\n').join('<br>') +
(truncated
? '<hr /><b>' + popupString('Diff truncated for performance reasons') + '</b>'
: ''),
'popupPreview',
navpop.idNumber
);
}

function diffDatesTable(navpop) {
var html = '<table class="popup_diff_dates">';
html += diffDatesTableRow(navpop.diffData.newRev.revision, tprintf('New revision'));
html += diffDatesTableRow(navpop.diffData.oldRev.revision, tprintf('Old revision'));
html += '</table>';
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('<tr><td>%s</td><td>%s</td></tr>', [revlink, txt]);
}
// ENDFILE: diffpreview.js

// STARTFILE: links.js
/////////////////////
// LINK GENERATION //
/////////////////////

// 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&section=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),
getMwApi().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,
});
}

/**
* Builds a link from a object representing a link
* @param {object} link
* @param {string} link.url URL
* @param {string} link.text The text to show for a link
* @param {string} link.title Title of the link, this shows up
* when you hover over the link
* @param {boolean} link.newWin Should open in a new Window
* @param {number} link.noPopup Should nest new popups from link (0 or 1)
* @param {string} link.onclick
* @returns {string|null} null if no url is given
*/
function generalLink(link) {
if (typeof link.url == 'undefined') {
return null;
}

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

elem.href = link.url;
elem.title = link.title;
// The onclick event adds raw JS in textual form to the HTML.
// TODO: We should look into removing this, and/or auditing what gets sent.
elem.setAttribute( 'onclick', link.onclick );

if ( link.noPopup ) {
elem.setAttribute('noPopup', '1' );
}

var newWin;
if (typeof link.newWin == 'undefined' || link.newWin === null) {
newWin = getValueOf('popupNewWindows');
} else {
newWin = link.newWin;
}
if (newWin) {
elem.target = '_blank';
}
if (link.className) {
elem.className = link.className;
}
elem.innerText = pg.unescapeQuotesHTML(link.text);

return elem.outerHTML;
}

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)\]\]~[[Computer-aided%20design|$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 + '\\]\\]~[[' + t + '|$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 += '<hr />';

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 (
'<br> ' +
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 = 'https://xtools.wmflabs.org/ec?user=$1&project=$2.$3&uselang=' + mw.config.get('wgUserLanguage');

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 = 'https://global-search.toolforge.org/?uselang=' + mw.config.get('wgUserLanguage') + '&q=';
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', ' &sdot; ');
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,
popupFilterWikibaseItem,
]);
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',
'disambiguation\\}\\}|\\{\\{\\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&nbsp;revision': 'this&nbsp;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 [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
defaultpopupRedlinkSummary:
'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
defaultpopupFixDabsSummary:
'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
defaultpopupFixRedirsSummary:
'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
defaultpopupExtendedRevertSummary:
'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
defaultpopupRevertToPreviousSummary:
'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
defaultpopupRevertSummary:
'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
defaultpopupQueriedRevertToPreviousSummary:
'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
defaultpopupQueriedRevertSummary:
'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
defaultpopupRmDabLinkSummary:
'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|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&nbsp;page': 'user&nbsp;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'));
});
})();
const ss = ".popupMoreLink{display:block;text-align:right;cursor:pointer}ins.popupDiff{background:#afe}del.popupDiff{background:#ffe6e6}#selectionPreview{border:2px solid #ddd;background-color:#eef;padding:6px}.navpopup{border:1px solid #bbb;background-color:#fff;padding:10px;padding-bottom:5px;font-size:11px;box-shadow:0 3px 8px rgba(50,50,50,0.35);word-wrap:break-word}.navpopup hr{color:#aaa !important;background-color:#aaa !important} .popupDrag{background-color:#ffbe20;height:5px;margin-top:-5px;margin-bottom:5px}.popupDragHandle{cursor:move;position:relative} .popup_menu{display:none;position:absolute;left:0;margin:0;margin-top:1.4em;line-height:1.25em;top:0;z-index:2;width:10em;background:white;border:1px solid grey;padding:0 !important;margin-left:-6px;border-width:1px 1px 1px 6px}.popup_menu li{ list-style:none;margin:0;padding:0}.popup_menu a{display:block;padding:3px;color:#555}.popup_menu_row a{display:inline-block}.popup_menu_row{color:#aaa}.popup_drop{display:inline;position:relative}.popup_drop a,.popup_drop a:visited{padding:3px;margin:0;font-weight:bold;color:#0645ad}.popup_drop:hover .popup_menu,.popup_drop .popup_menu:hover{display:inline;background:white;padding:2px;color:#555}.popup_drop:hover{background:#ccf;color:#44f} .popup_menu a:hover{background:grey;color:#fff;text-decoration:none}.popup_mainlink{font-size:140%;font-weight:bold}.popup_mainlink a{color:#000}a.popup_change_title_link{color:#152}.popup_diff_dates{font-style:italic;background:none}.popup_menu_item a{display:block}.popup_history_row_even{background:#eee}.popup_history_date{font-weight:bold;font-size:120%}.popup_history_row_odd,.popup_history_row_even{display:flex}.popup_history_row_even td:nth-child(3),.popup_history_row_odd td:nth-child(3){flex:3;word-break:break-word}.popup_history_row_even td:nth-child(4),.popup_history_row_odd td:nth-child(4){flex:7;word-break:break-word}.popup_history_row_even \u003E td:not(:last-child),.popup_history_row_odd \u003E td:not(:last-child){margin-right:2px} .popupPreview a.extiw,.popupPreview a.extiw:active{color:#36b;background:none;padding:0}.popupPreview .external{color:#36b} .popupPreview .plainlinks a{background:none !important;padding:0 !important} .popup_menu:hover{box-shadow:0 0 5px 5px rgba(179,179,255,0.3) } .popup_menu::before{content:'';display:block;position:absolute;height:1.5em;top:-1.4em;left:-1.2em;width:calc(2 * var(--navpop-m-len,6ch));transform:perspective(1px) rotateX(1deg)} .popup_menu::before{z-index:1}.popup_menu li{position:relative;z-index:2} .popup_menu li::before{content:'';display:block;position:absolute;top:0;height:calc(100% + .5em);left:-1.7em;width:1.7em}.popup_menu li::after{content:'';display:block;position:absolute;top:0;height:calc(100% + .5em);left:calc(10em - 1px); width:1em}";
var styleSheet = document.createElement("style");
styleSheet.innerText = ss;
document.head.appendChild(styleSheet);
});
});
/*-->*/

Latest revision as of 05:29, 27 April 2024

/*Taken from https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups under CC-BY-SA 4.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.querySelector('.skin-vector-2022 .vector-body') ||
				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('<hr />' + 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('<br>' + 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 =
					'<hr /><span style="font-family: monospace;">' +
					download.data.entify().split('\\n').join('<br />\\n') +
					'</span>';
				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 [[bar|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}([^|\\]]*?[|])?(.*?)[\\]]{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.

	<pre>
	*************************************************
	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.
	</pre>
	*/

	/**
	 * 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 <code>null</code>.
		 * @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 <code>null</code>.
		 * @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 <code>oRoot</code> is dragged.
	 * @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> 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('<b><<mainlink>></b>', 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 += '<b><<mainlink|shortcut= >></b>';

		// user links
		// contribs - log - count - email - block
		// count only if applicable; block only if popupAdminLinks
		str += 'if(user){<br><<contribs|shortcut=c>>';
		str += 'if(wikimedia){*<<count|shortcut=#>>}';
		str += 'if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}';

		// editing links
		// talkpage   -> edit|new - history - un|watch - article|edit
		// other page -> edit - history - un|watch - talk|edit|new
		var editstr = '<<edit|shortcut=e>>';
		var editOldidStr =
			'if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' +
			editstr +
			'}';
		var historystr = '<<history|shortcut=h>>';
		var watchstr = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';

		str +=
			'<br>if(talk){' +
			editOldidStr +
			'|<<new|shortcut=+>>' +
			'*' +
			historystr +
			'*' +
			watchstr +
			'*' +
			'<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
			'}else{' + // not a talk page
			editOldidStr +
			'*' +
			historystr +
			'*' +
			watchstr +
			'*' +
			'<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';

		// misc links
		str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>';
		str += 'if(admin){<br>}else{*}<<move|shortcut=m>>';

		// admin links
		str +=
			'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' +
			'<<undelete|undeleteShort>>|<<delete|shortcut=d>>}';
		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('<font size=+0><<mainlink>></font>', x.article, x.params);
	};
	pg.structures.fancy.popupTopLinks = function (x) {
		var hist =
			'<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>';
		var watch = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
		var move = '<<move|shortcut=m|move>>';
		return navlinkStringToHTML(
			'if(talk){' +
				'<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' +
				hist +
				'*' +
				'<<article|shortcut=a>>|<<editArticle|edit>>' +
				'*' +
				watch +
				'*' +
				move +
				'}else{<<edit|shortcut=e>>*' +
				hist +
				'*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' +
				'*' +
				watch +
				'*' +
				move +
				'}<br>',
			x.article,
			x.params
		);
	};
	pg.structures.fancy.popupOtherLinks = function (x) {
		var admin =
			'<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>';
		var user = '<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}';
		user +=
			'if(ipuser){|<<arin>>}else{*<<email|shortcut=E|' +
			popupString('email') +
			'>>}if(admin){*<<block|shortcut=b>>}';

		var normal = '<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>';
		return navlinkStringToHTML(
			'<br>if(user){' + user + '*}if(admin){' + admin + 'if(user){<br>}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 <br> at the end and put one at the beginning
		return '<br>' + pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$', '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 dropclass = 'popup_drop';
		var enddiv = '</div>';
		var hist = '<<history|shortcut=h>>';
		if (!shorter) {
			hist = '<menurow>' + hist + '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>';
		}
		var lastedit = '<<lastEdit|shortcut=/|show last edit>>';
		var thank = 'if(diff){<<thank|send thanks>>}';
		var jsHistory = '<<lastContrib|last set of edits>><<sinceMe|changes since mine>>';
		var linkshere = '<<whatLinksHere|shortcut=l|what links here>>';
		var related = '<<relatedChanges|shortcut=r|related changes>>';
		var search =
			'<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' +
			'|<<google|shortcut=G|web>></menurow>';
		var watch = '<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>';
		var protect =
			'<menurow><<unprotect|unprotectShort>>|' +
			'<<protect|shortcut=p>>|<<protectlog|log>></menurow>';
		var del =
			'<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' + '<<deletelog|log>></menurow>';
		var move = '<<move|shortcut=m|move page>>';
		var nullPurge = '<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>';
		var viewOptions = '<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>';
		var editRow =
			'if(oldid){' +
			'<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' +
			'<menurow><<revert|shortcut=v>>|<<undo>></menurow>' +
			'}else{<<edit|shortcut=e>>}';
		var markPatrolled = 'if(rcid){<<markpatrolled|mark patrolled>>}';
		var newTopic = 'if(talk){<<new|shortcut=+|new topic>>}';
		var protectDelete = 'if(admin){' + protect + del + '}';

		if (getValueOf('popupActionsMenu')) {
			s.push('<<mainlink>>*' + menuTitle(dropclass, 'actions'));
		} else {
			s.push('<div class="' + dropclass + '">' + '<<mainlink>>');
		}
		s.push('<menu>');
		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('<hr />' + watch + protectDelete);
		s.push(
			'<hr />' +
				'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' +
				'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' +
				'<<newTalk|shortcut=+|new topic>>}</menu>' +
				enddiv
		);

		// user menu starts here
		var email = '<<email|shortcut=E|email user>>';
		var contribs =
			'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' +
			'if(admin){<menurow><<deletedContribs>></menurow>}';

		s.push('if(user){*' + menuTitle(dropclass, 'user'));
		s.push('<menu>');
		s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>');
		s.push(
			'<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' +
				'<<newUserTalk|shortcut=+|leave comment>>'
		);
		if (!shorter) {
			s.push('if(ipuser){<<arin>>}else{' + email + '}');
		} else {
			s.push('if(ipuser){}else{' + email + '}');
		}
		s.push('<hr />' + contribs + '<<userlog|shortcut=L|user log>>');
		s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}');
		s.push(
			'if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}'
		);
		s.push('<<blocklog|shortcut=B|block log>>');
		s.push('</menu>' + enddiv + '}');

		// popups menu starts here
		if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) {
			x.navpop.hasPopupMenu = true;
			s.push('*' + menuTitle(dropclass, 'popupsMenu') + '<menu>');
			s.push('<<togglePreviews|toggle previews>>');
			s.push('<<purgePopups|reset>>');
			s.push('<<disablePopups|disable>>');
			s.push('</menu>' + enddiv);
		}
		return navlinkStringToHTML(s.join(''), x.article, x.params);
	};

	function menuTitle(dropclass, s) {
		var text = popupString(s); // i18n
		var len = text.length;
		return '<div class="' + dropclass + '" style="--navpop-m-len:' + len + 'ch">' + '<a href="#" noPopup=1>' + text + '</a>';
	}

	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('<b><<mainlink>></b>',x.article,x.params);
		return '<div><span class="popup_mainlink"><b>' + x.article.toString() + '</b></span></div>';
	};
	// 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 = '<font size=+1><b>' + pg.escapeQuotesHTML(s) + '</b></font>';
			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 {number}
		 */
		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 <code>abort</code>
		 * @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 <code>aborted</code> 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 <code>null</code> 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 <code>f(this)</code> 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 {number} 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 {number} id The ID.
	 * @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>,
	 * where <code>d</code> 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 {number} 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 <nowiki>
	 * - 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.locale.user +
			':' +
			Insta.conf.user.name +
			'|' +
			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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
		}

		// 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, '&lt;')
				.replace(/>/g, '&gt;')
				.replace(/:/g, '&#58;')
				.replace(/\[/g, '&#91;')
				.replace(/]/g, '&#93;');
		}
		function htmlescape_attr(s) {
			return htmlescape_text(s).replace(/'/g, '&#39;').replace(/"/g, '&quot;');
		}

		// 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('</ul>');
					} else if (pi == '#') {
						ps('</ol>');
					}
					// close a dl only if the new item is not a dl item (:, ; or empty)
					else if ($.inArray(l_match[1].charAt(prevPos), ['', '*', '#'])) {
						ps('</dl>');
					}
				}

				// open new lists
				for (var matchPos = ipos; matchPos < l_match[1].length; matchPos++) {
					var li = l_match[1].charAt(matchPos);

					if (li == '*') {
						ps('<ul>');
					} else if (li == '#') {
						ps('<ol>');
					}
					// open a new dl only if the prev item is not a dl item (:, ; or empty)
					else if ($.inArray(prev.charAt(matchPos), ['', '*', '#'])) {
						ps('<dl>');
					}
				}

				switch (l_match[1].charAt(l_match[1].length - 1)) {
					case '*':
					case '#':
						ps('<li>' + parse_inline_nowiki(l_match[2]));
						break;

					case ';':
						ps('<dt>');

						var dt_match = l_match[2].match(/(.*?)(:.*?)$/);

						// handle ;dt :dd format
						if (dt_match) {
							ps(parse_inline_nowiki(dt_match[1]));
							ll.unshift(dt_match[2]);
						} else {
							ps(parse_inline_nowiki(l_match[2]));
						}
						break;

					case ':':
						ps('<dd>' + parse_inline_nowiki(l_match[2]));
				}

				prev = l_match[1];
			}

			// close remaining lists
			for (var i = prev.length - 1; i >= 0; i--) {
				ps(f('</?>', prev.charAt(i) == '*' ? 'ul' : prev.charAt(i) == '#' ? 'ol' : 'dl'));
			}
		}

		function parse_table() {
			endl(f('<table>', compareLineStringOrReg(/^\{\|( .*)$/) ? r[1] : ''));

			for (; remain(); ) {
				if (compareLineStringOrReg('|')) {
					switch (charAtPoint(1)) {
						case '}':
							endl('</table>');
							return;
						case '-':
							endl(f('<tr>', compareLineStringOrReg(/\|-*(.*)/)[1]));
							break;
						default:
							parse_table_data();
					}
				} else if (compareLineStringOrReg('!')) {
					parse_table_data();
				} else {
					sh();
				}
			}
		}

		function parse_table_data() {
			var td_line, match_i;

			// 1: "|+", '|' or '+'
			// 2: ??
			// 3: attributes ??
			// TODO: finish commenting this regexp
			var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/);

			if (td_match[1] == '|+') {
				ps('<caption');
			} else {
				ps('<t' + (td_match[1] == '|' ? 'd' : 'h'));
			}

			if (typeof td_match[3] != 'undefined') {
				//ps(' ' + td_match[3])
				match_i = 4;
			} else {
				match_i = 2;
			}

			ps('>');

			if (td_match[1] != '|+') {
				// use || or !! as a cell separator depending on context
				// NOTE: when split() is passed a regexp make sure to use non-capturing brackets
				td_line = td_match[match_i].split(td_match[1] == '|' ? '||' : /(?:\|\||!!)/);

				ps(parse_inline_nowiki(td_line.shift()));

				while (td_line.length) {
					ll.unshift(td_match[1] + td_line.pop());
				}
			} else {
				ps(parse_inline_nowiki(td_match[match_i]));
			}

			var tc = 0,
				td = [];

			while (remain()) {
				td.push(sh());
				if (compareLineStringOrReg('|')) {
					if (!tc) {
						break;
					}
					// we're at the outer-most level (no nested tables), skip to td parse
					else if (charAtPoint(1) == '}') {
						tc--;
					}
				} else if (!tc && compareLineStringOrReg('!')) {
					break;
				} else if (compareLineStringOrReg('{|')) {
					tc++;
				}
			}

			if (td.length) {
				ps(Insta.convert(td));
			}
		}

		function parse_pre() {
			ps('<pre>');
			do {
				endl(parse_inline_nowiki(ll[0].substring(1)) + '\n');
			} while (remain() && compareLineStringOrReg(' '));
			ps('</pre>');
		}

		function parse_block_image() {
			ps(parse_image(sh()));
		}

		function parse_image(str) {
			// get what's in between "[[Image:" and "]]"
			var tag = str.substring(str.indexOf(':') + 1, str.length - 2);
			var width;
			var attr = [],
				filename,
				caption = '';
			var thumb = 0,
				frame = 0,
				center = 0;
			var align = '';

			if (tag.match(/\|/)) {
				// manage nested links
				var nesting = 0;
				var last_attr;
				for (var i = tag.length - 1; i > 0; i--) {
					if (tag.charAt(i) == '|' && !nesting) {
						last_attr = tag.substr(i + 1);
						tag = tag.substring(0, i);
						break;
					} else {
						switch (tag.substr(i - 1, 2)) {
							case ']]':
								nesting++;
								i--;
								break;
							case '[[':
								nesting--;
								i--;
						}
					}
				}

				attr = tag.split(/\s*\|\s*/);
				attr.push(last_attr);
				filename = attr.shift();

				var w_match;

				for (; attr.length; attr.shift()) {
					w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/);
					if (w_match) {
						width = w_match[1];
					} else {
						switch (attr[0]) {
							case 'thumb':
							case 'thumbnail':
								thumb = true;
								frame = true;
								break;
							case 'frame':
								frame = true;
								break;
							case 'none':
							case 'right':
							case 'left':
								center = false;
								align = attr[0];
								break;
							case 'center':
								center = true;
								align = 'none';
								break;
							default:
								if (attr.length == 1) {
									caption = attr[0];
								}
						}
					}
				}
			} else {
				filename = tag;
			}

			return '';
		}

		function parse_inline_nowiki(str) {
			var start,
				lastend = 0;
			var substart = 0,
				nestlev = 0,
				open,
				close,
				subloop;
			var html = '';

			while ((start = str.indexOf('<nowiki>', substart)) != -1) {
				html += parse_inline_wiki(str.substring(lastend, start));
				start += 8;
				substart = start;
				subloop = true;
				do {
					open = str.indexOf('<nowiki>', substart);
					close = str.indexOf('</nowiki>', substart);
					if (close <= open || open == -1) {
						if (close == -1) {
							return html + html_entities(str.substr(start));
						}
						substart = close + 9;
						if (nestlev) {
							nestlev--;
						} else {
							lastend = substart;
							html += html_entities(str.substring(start, lastend - 9));
							subloop = false;
						}
					} else {
						substart = open + 8;
						nestlev++;
					}
				} while (subloop);
			}

			return html + parse_inline_wiki(str.substr(lastend));
		}

		function parse_inline_images(str) {
			var start,
				substart = 0,
				nestlev = 0;
			var loop, close, open, wiki, html;

			while ((start = str.indexOf('[[', substart)) != -1) {
				if (
					str.substr(start + 2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):', 'i'))
				) {
					loop = true;
					substart = start;
					do {
						substart += 2;
						close = str.indexOf(']]', substart);
						open = str.indexOf('[[', substart);
						if (close <= open || open == -1) {
							if (close == -1) {
								return str;
							}
							substart = close;
							if (nestlev) {
								nestlev--;
							} else {
								wiki = str.substring(start, close + 2);
								html = parse_image(wiki);
								str = str.replace(wiki, html);
								substart = start + html.length;
								loop = false;
							}
						} else {
							substart = open;
							nestlev++;
						}
					} while (loop);
				} else {
					break;
				}
			}

			return str;
		}

		// the output of this function doesn't respect the FILO structure of HTML
		// but since most browsers can handle it I'll save myself the hassle
		function parse_inline_formatting(str) {
			var em,
				st,
				i,
				li,
				o = '';
			while ((i = str.indexOf("''", li)) + 1) {
				o += str.substring(li, i);
				li = i + 2;
				if (str.charAt(i + 2) == "'") {
					li++;
					st = !st;
					o += st ? '<strong>' : '</strong>';
				} else {
					em = !em;
					o += em ? '<em>' : '</em>';
				}
			}
			return o + str.substr(li);
		}

		function parse_inline_wiki(str) {
			str = parse_inline_images(str);

			// math
			str = str.replace(/<(?:)math>(.*?)<\/math>/gi, '');

			// Build a Mediawiki-formatted date string
			var date = new Date();
			var minutes = date.getUTCMinutes();
			if (minutes < 10) {
				minutes = '0' + minutes;
			}
			date = f(
				'?:?, ? ? ? (UTC)',
				date.getUTCHours(),
				minutes,
				date.getUTCDate(),
				Insta.conf.locale.months[date.getUTCMonth()],
				date.getUTCFullYear()
			);

			// text formatting
			str =
				str
					// signatures
					.replace(/~{5}(?!~)/g, date)
					.replace(/~{4}(?!~)/g, Insta.conf.user.name + ' ' + date)
					.replace(/~{3}(?!~)/g, Insta.conf.user.name)
					// [[:Category:...]], [[:Image:...]], etc...
					.replace(
						RegExp(
							'\\[\\[:((?:' +
								Insta.conf.locale.category +
								'|Image|File|' +
								Insta.conf.locale.image +
								'|' +
								Insta.conf.wiki.interwiki +
								'):[^|]*?)\\]\\](\\w*)',
							'gi'
						),
						function ($0, $1, $2) {
							return f(
								"<a href='?'>?</a>",
								Insta.conf.paths.articles + htmlescape_attr($1),
								htmlescape_text($1) + htmlescape_text($2)
							);
						}
					)
					// remove straight category and interwiki tags
					.replace(
						RegExp(
							'\\[\\[(?:' +
								Insta.conf.locale.category +
								'|' +
								Insta.conf.wiki.interwiki +
								'):.*?\\]\\]',
							'gi'
						),
						''
					)
					// [[:Category:...|Links]], [[:Image:...|Links]], etc...
					.replace(
						RegExp(
							'\\[\\[:((?:' +
								Insta.conf.locale.category +
								'|Image|File|' +
								Insta.conf.locale.image +
								'|' +
								Insta.conf.wiki.interwiki +
								'):.*?)\\|([^\\]]+?)\\]\\](\\w*)',
							'gi'
						),
						function ($0, $1, $2, $3) {
							return f(
								"<a href='?'>?</a>",
								Insta.conf.paths.articles + htmlescape_attr($1),
								htmlescape_text($2) + htmlescape_text($3)
							);
						}
					)
					// [[/Relative links]]
					.replace(/\[\[(\/[^|]*?)\]\]/g, function ($0, $1) {
						return f(
							"<a href='?'>?</a>",
							Insta.conf.baseUrl + htmlescape_attr($1),
							htmlescape_text($1)
						);
					})
					// [[/Replaced|Relative links]]
					.replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function ($0, $1, $2) {
						return f(
							"<a href='?'>?</a>",
							Insta.conf.baseUrl + htmlescape_attr($1),
							htmlescape_text($2)
						);
					})
					// [[Common links]]
					.replace(/\[\[([^[|]*?)\]\](\w*)/g, function ($0, $1, $2) {
						return f(
							"<a href='?'>?</a>",
							Insta.conf.paths.articles + htmlescape_attr($1),
							htmlescape_text($1) + htmlescape_text($2)
						);
					})
					// [[Replaced|Links]]
					.replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function ($0, $1, $2, $3) {
						return f(
							"<a href='?'>?</a>",
							Insta.conf.paths.articles + htmlescape_attr($1),
							htmlescape_text($2) + htmlescape_text($3)
						);
					})
					// [[Stripped:Namespace|Namespace]]
					.replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function ($0, $1, $2, $3) {
						return f(
							"<a href='?'>?</a>",
							Insta.conf.paths.articles +
								htmlescape_attr($1) +
								htmlescape_attr($2) +
								htmlescape_attr($3),
							htmlescape_text($2)
						);
					})
					// External links
					.replace(
						/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g,
						function ($0, $1, $2, $3, $4) {
							return f(
								"<a class='external' href='?:?'>?</a>",
								htmlescape_attr($1),
								htmlescape_attr($2) + htmlescape_attr($3),
								htmlescape_text($4)
							);
						}
					)
					.replace(/\[http:\/\/(.*?)\]/g, function ($0, $1) {
						return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1));
					})
					.replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function ($0, $1, $2, $3) {
						return f(
							"<a class='external' href='?:?'>?:?</a>",
							htmlescape_attr($1),
							htmlescape_attr($2) + htmlescape_attr($3),
							htmlescape_text($1),
							htmlescape_text($2) + htmlescape_text($3)
						);
					})
					.replace(
						/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g,
						function ($0, $1, $2, $3, $4) {
							return f(
								"?<a class='external' href='?:?'>?:?</a>",
								htmlescape_text($1),
								htmlescape_attr($2),
								htmlescape_attr($3) + htmlescape_attr($4),
								htmlescape_text($2),
								htmlescape_text($3) + htmlescape_text($4)
							);
						}
					)
					.replace('__NOTOC__', '')
					.replace('__NOINDEX__', '')
					.replace('__INDEX__', '')
					.replace('__NOEDITSECTION__', '')
			;
			return parse_inline_formatting(str);
		}

		// begin parsing
		for (; remain(); ) {
			if (compareLineStringOrReg(/^(={1,6})(.*)\1(.*)$/)) {
				p = 0;
				endl(f('<h?>?</h?>?', r[1].length, parse_inline_nowiki(r[2]), r[1].length, r[3]));
			} else if (compareLineStringOrReg(/^[*#:;]/)) {
				p = 0;
				parse_list();
			} else if (compareLineStringOrReg(' ')) {
				p = 0;
				parse_pre();
			} else if (compareLineStringOrReg('{|')) {
				p = 0;
				parse_table();
			} else if (compareLineStringOrReg(/^----+$/)) {
				p = 0;
				endl('<hr />');
			} else if (compareLineStringOrReg(Insta.BLOCK_IMAGE)) {
				p = 0;
				parse_block_image();
			} else {
				// handle paragraphs
				if (compareLineString('')) {
					p = remain() > 1 && ll[1] === '';
					if (p) {
						endl('<p><br>');
					}
				} else {
					if (!p) {
						ps('<p>');
						p = 1;
					}
					ps(parse_inline_nowiki(ll[0]) + ' ');
				}

				sh();
			}
		}

		return o;
	};

	function wiki2html(txt, baseurl) {
		Insta.conf.baseUrl = baseurl;
		return Insta.convert(txt);
	}
	// ENDFILE: livepreview.js

	// STARTFILE: pageinfo.js
	function popupFilterPageSize(data) {
		return formatBytes(data.length);
	}

	function popupFilterCountLinks(data) {
		var num = countLinks(data);
		return String(num) + '&nbsp;' + (num != 1 ? popupString('wikiLinks') : popupString('wikiLink'));
	}

	function popupFilterCountImages(data) {
		var num = countImages(data);
		return String(num) + '&nbsp;' + (num != 1 ? popupString('images') : popupString('image'));
	}

	function popupFilterCountCategories(data) {
		var num = countCategories(data);
		return (
			String(num) + '&nbsp;' + (num != 1 ? popupString('categories') : popupString('category'))
		);
	}

	function popupFilterLastModified(data, download) {
		var lastmod = download.lastModified;
		var now = new Date();
		var age = now - lastmod;
		if (lastmod && getValueOf('popupLastModified')) {
			return tprintf('%s old', [formatAge(age)]).replace(RegExp(' ', 'g'), '&nbsp;');
		}
		return '';
	}

	function popupFilterWikibaseItem(data, download) {
		return download.wikibaseItem
			? tprintf('<a href="%s">%s</a>', [
				download.wikibaseRepo.replace(/\$1/g, download.wikibaseItem),
				download.wikibaseItem,
			])
			: '';
	}

	function formatAge(age) {
		// coerce into a number
		var a = 0 + age,
			aa = a;

		var seclen = 1000;
		var minlen = 60 * seclen;
		var hourlen = 60 * minlen;
		var daylen = 24 * hourlen;
		var weeklen = 7 * daylen;

		var numweeks = (a - (a % weeklen)) / weeklen;
		a = a - numweeks * weeklen;
		var sweeks = addunit(numweeks, 'week');
		var numdays = (a - (a % daylen)) / daylen;
		a = a - numdays * daylen;
		var sdays = addunit(numdays, 'day');
		var numhours = (a - (a % hourlen)) / hourlen;
		a = a - numhours * hourlen;
		var shours = addunit(numhours, 'hour');
		var nummins = (a - (a % minlen)) / minlen;
		a = a - nummins * minlen;
		var smins = addunit(nummins, 'minute');
		var numsecs = (a - (a % seclen)) / seclen;
		a = a - numsecs * seclen;
		var ssecs = addunit(numsecs, 'second');

		if (aa > 4 * weeklen) {
			return sweeks;
		}
		if (aa > weeklen) {
			return sweeks + ' ' + sdays;
		}
		if (aa > daylen) {
			return sdays + ' ' + shours;
		}
		if (aa > 6 * hourlen) {
			return shours;
		}
		if (aa > hourlen) {
			return shours + ' ' + smins;
		}
		if (aa > 10 * minlen) {
			return smins;
		}
		if (aa > minlen) {
			return smins + ' ' + ssecs;
		}
		return ssecs;
	}

	function addunit(num, str) {
		return String(num) + ' ' + (num != 1 ? popupString(str + 's') : popupString(str));
	}

	function runPopupFilters(list, data, download) {
		var ret = [];
		for (var i = 0; i < list.length; ++i) {
			if (list[i] && typeof list[i] == 'function') {
				var s = list[i](data, download, download.owner.article);
				if (s) {
					ret.push(s);
				}
			}
		}
		return ret;
	}

	function getPageInfo(data, download) {
		if (!data || data.length === 0) {
			return popupString('Empty page');
		}

		var popupFilters = getValueOf('popupFilters') || [];
		var extraPopupFilters = getValueOf('extraPopupFilters') || [];
		var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download);

		var pageInfo = pageInfoArray.join(', ');
		if (pageInfo !== '') {
			pageInfo = upcaseFirst(pageInfo);
		}
		return pageInfo;
	}

	// this could be improved!
	function countLinks(wikiText) {
		return wikiText.split('[[').length - 1;
	}

	// if N = # matches, n = # brackets, then
	// String.parenSplit(regex) intersperses the N+1 split elements
	// with Nn other elements. So total length is
	// L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1).

	function countImages(wikiText) {
		return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1);
	}

	function countCategories(wikiText) {
		return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1);
	}

	function popupFilterStubDetect(data, download, article) {
		var counts = stubCount(data, article);
		if (counts.real) {
			return popupString('stub');
		}
		if (counts.sect) {
			return popupString('section stub');
		}
		return '';
	}

	function popupFilterDisambigDetect(data, download, article) {
		if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
			return '';
		}
		return isDisambig(data, article) ? popupString('disambig') : '';
	}

	function formatBytes(num) {
		return num > 949
			? Math.round(num / 100) / 10 + popupString('kB')
			: num + '&nbsp;' + popupString('bytes');
	}
	// ENDFILE: pageinfo.js

	// STARTFILE: titles.js
	/**
	 * @fileoverview Defines the {@link Title} class, and associated crufty functions.

	 * <code>Title</code> deals with article titles and their various
	 * forms.  {@link Stringwrapper} is the parent class of
	 * <code>Title</code>, which exists simply to make things a little
	 * neater.
	 */

	/**
	 * Creates a new Stringwrapper.
	 * @constructor

	 * @class the Stringwrapper class. This base class is not really
	 * useful on its own; it just wraps various common string operations.
	 */
	function Stringwrapper() {
		/**
		 * Wrapper for this.toString().indexOf()
		 * @param {String} x
		 * @type {number}
		 */
		this.indexOf = function (x) {
			return this.toString().indexOf(x);
		};
		/**
		 * Returns this.value.
		 * @type {string}
		 */
		this.toString = function () {
			return this.value;
		};
		/**
		 * Wrapper for {@link String#parenSplit} applied to this.toString()
		 * @param {RegExp} x
		 * @type {Array}
		 */
		this.parenSplit = function (x) {
			return this.toString().parenSplit(x);
		};
		/**
		 * Wrapper for this.toString().substring()
		 * @param {String} x
		 * @param {String} y (optional)
		 * @type {string}
		 */
		this.substring = function (x, y) {
			if (typeof y == 'undefined') {
				return this.toString().substring(x);
			}
			return this.toString().substring(x, y);
		};
		/**
		 * Wrapper for this.toString().split()
		 * @param {String} x
		 * @type {Array}
		 */
		this.split = function (x) {
			return this.toString().split(x);
		};
		/**
		 * Wrapper for this.toString().replace()
		 * @param {String} x
		 * @param {String} y
		 * @type {string}
		 */
		this.replace = function (x, y) {
			return this.toString().replace(x, y);
		};
	}

	/**
	 * Creates a new <code>Title</code>.
	 * @constructor
	 *
	 * @class The Title class. Holds article titles and converts them into
	 * various forms. Also deals with anchors, by which we mean the bits
	 * of the article URL after a # character, representing locations
	 * within an article.
	 *
	 * @param {String} value The initial value to assign to the
	 * article. This must be the canonical title (see {@link
	 * Title#value}. Omit this in the constructor and use another function
	 * to set the title if this is unavailable.
	 */
	function Title(val) {
		/**
		 * The canonical article title. This must be in UTF-8 with no
		 * entities, escaping or nasties. Also, underscores should be
		 * replaced with spaces.
		 * @type {string}
		 * @private
		 */
		this.value = null;

		/**
		 * The canonical form of the anchor. This should be exactly as
		 * it appears in the URL, i.e. with the .C3.0A bits in.
		 * @type {string}
		 */
		this.anchor = '';

		this.setUtf(val);
	}
	Title.prototype = new Stringwrapper();
	/**
	 * Returns the canonical representation of the article title, optionally without anchor.
	 * @param {boolean} omitAnchor
	 * @fixme Decide specs for anchor
	 * @return String The article title and the anchor.
	 */
	Title.prototype.toString = function (omitAnchor) {
		return this.value + (!omitAnchor && this.anchor ? '#' + this.anchorString() : '');
	};
	Title.prototype.anchorString = function () {
		if (!this.anchor) {
			return '';
		}
		var split = this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/);
		var len = split.length;
		var value;
		for (var j = 1; j < len; j += 2) {
			// FIXME s/decodeURI/decodeURIComponent/g ?
			value = split[j].split('.').join('%');
			try {
				value = decodeURIComponent(value);
			} catch (e) {
				// cannot decode
			}
			split[j] = value.split('_').join(' ');
		}
		return split.join('');
	};
	Title.prototype.urlAnchor = function () {
		var split = this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/');
		var len = split.length;
		for (var j = 1; j < len; j += 2) {
			split[j] = split[j].split('%').join('.');
		}
		return split.join('');
	};
	Title.prototype.anchorFromUtf = function (str) {
		this.anchor = encodeURIComponent(str.split(' ').join('_'))
			.split('%3A')
			.join(':')
			.split("'")
			.join('%27')
			.split('%')
			.join('.');
	};
	Title.fromURL = function (h) {
		return new Title().fromURL(h);
	};
	Title.prototype.fromURL = function (h) {
		if (typeof h != 'string') {
			this.value = null;
			return this;
		}

		// NOTE : playing with decodeURI, encodeURI, escape, unescape,
		// we seem to be able to replicate the IE borked encoding

		// IE doesn't do this new-fangled utf-8 thing.
		// and it's worse than that.
		// IE seems to treat the query string differently to the rest of the url
		// the query is treated as bona-fide utf8, but the first bit of the url is pissed around with

		// we fix up & for all browsers, just in case.
		var splitted = h.split('?');
		splitted[0] = splitted[0].split('&').join('%26');

		h = splitted.join('?');

		var contribs = pg.re.contribs.exec(h);
		if (contribs) {
			if (contribs[1] == 'title=') {
				contribs[3] = contribs[3].split('+').join(' ');
			}
			var u = new Title(contribs[3]);
			this.setUtf(
				this.decodeNasties(
					mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace()
				)
			);
			return this;
		}

		var email = pg.re.email.exec(h);
		if (email) {
			this.setUtf(
				this.decodeNasties(
					mw.config.get('wgFormattedNamespaces')[pg.nsUserId] +
						':' +
						new Title(email[3]).stripNamespace()
				)
			);
			return this;
		}

		var backlinks = pg.re.backlinks.exec(h);
		if (backlinks) {
			this.setUtf(this.decodeNasties(new Title(backlinks[3])));
			return this;
		}

		//A dummy title object for a Special:Diff link.
		var specialdiff = pg.re.specialdiff.exec(h);
		if (specialdiff) {
			this.setUtf(
				this.decodeNasties(
					new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff')
				)
			);
			return this;
		}

		// no more special cases to check --
		// hopefully it's not a disguised user-related or specially treated special page
		// Includes references
		var m = pg.re.main.exec(h);
		if (m === null) {
			this.value = null;
		} else {
			var fromBotInterface = /[?](.+[&])?title=/.test(h);
			if (fromBotInterface) {
				m[2] = m[2].split('+').join('_');
			}
			var extracted = m[2] + (m[3] ? '#' + m[3] : '');
			if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) {
				// Fix Safari issue
				// Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3.
				this.setUtf(decodeURIComponent(unescape(extracted)));
			} else {
				this.setUtf(this.decodeNasties(extracted));
			}
		}
		return this;
	};
	Title.prototype.decodeNasties = function (txt) {
		// myDecodeURI uses decodeExtras, which removes _,
		// thus ruining citations previews, which are formated as "cite_note-1"
		try {
			var ret = decodeURI(this.decodeEscapes(txt));
			ret = ret.replace(/[_ ]*$/, '');
			return ret;
		} catch (e) {
			return txt; // cannot decode
		}
	};
	// Decode valid %-encodings, otherwise escape them
	Title.prototype.decodeEscapes = function (txt) {
		var split = txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/);
		var len = split.length;
		// No %-encoded items found, so replace the literal %
		if (len === 1) {
			return split[0].replace(/%(?![0-9a-fA-F][0-9a-fA-F])/g, '%25');
		}
		for (var i = 1; i < len; i = i + 2) {
			split[i] = decodeURIComponent(split[i]);
		}
		return split.join('');
	};
	Title.fromAnchor = function (a) {
		return new Title().fromAnchor(a);
	};
	Title.prototype.fromAnchor = function (a) {
		if (!a) {
			this.value = null;
			return this;
		}
		return this.fromURL(a.href);
	};
	Title.fromWikiText = function (txt) {
		return new Title().fromWikiText(txt);
	};
	Title.prototype.fromWikiText = function (txt) {
		// FIXME - testing needed
		txt = myDecodeURI(txt);
		this.setUtf(txt);
		return this;
	};
	Title.prototype.hintValue = function () {
		if (!this.value) {
			return '';
		}
		return safeDecodeURI(this.value);
	};
	Title.prototype.toUserName = function (withNs) {
		if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) {
			this.value = null;
			return;
		}
		this.value =
			(withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') +
			this.stripNamespace().split('/')[0];
	};
	Title.prototype.userName = function (withNs) {
		var t = new Title(this.value);
		t.toUserName(withNs);
		if (t.value) {
			return t;
		}
		return null;
	};
	Title.prototype.toTalkPage = function () {
		// convert article to a talk page, or if we can't, return null
		// In other words: return null if this ALREADY IS a talk page
		// and return the corresponding talk page otherwise
		//
		// Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces
		// * All discussion namespaces have odd-integer indices
		// * The discussion namespace index for a specific namespace with index n is n + 1
		if (this.value === null) {
			return null;
		}

		var namespaceId = this.namespaceId();
		if (namespaceId >= 0 && namespaceId % 2 === 0) {
			//non-special and subject namespace
			var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId + 1];
			if (typeof localizedNamespace !== 'undefined') {
				if (localizedNamespace === '') {
					this.value = this.stripNamespace();
				} else {
					this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
				}
				return this.value;
			}
		}

		this.value = null;
		return null;
	};
	// Return canonical, localized namespace
	Title.prototype.namespace = function () {
		return mw.config.get('wgFormattedNamespaces')[this.namespaceId()];
	};
	Title.prototype.namespaceId = function () {
		var n = this.value.indexOf(':');
		if (n < 0) {
			return 0;
		} //mainspace
		var namespaceId =
			mw.config.get('wgNamespaceIds')[
				this.value.substring(0, n).split(' ').join('_').toLowerCase()
			];
		if (typeof namespaceId == 'undefined') {
			return 0;
		} //mainspace
		return namespaceId;
	};
	Title.prototype.talkPage = function () {
		var t = new Title(this.value);
		t.toTalkPage();
		if (t.value) {
			return t;
		}
		return null;
	};
	Title.prototype.isTalkPage = function () {
		if (this.talkPage() === null) {
			return true;
		}
		return false;
	};
	Title.prototype.toArticleFromTalkPage = function () {
		//largely copy/paste from toTalkPage above.
		if (this.value === null) {
			return null;
		}

		var namespaceId = this.namespaceId();
		if (namespaceId >= 0 && namespaceId % 2 == 1) {
			//non-special and talk namespace
			var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId - 1];
			if (typeof localizedNamespace !== 'undefined') {
				if (localizedNamespace === '') {
					this.value = this.stripNamespace();
				} else {
					this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
				}
				return this.value;
			}
		}

		this.value = null;
		return null;
	};
	Title.prototype.articleFromTalkPage = function () {
		var t = new Title(this.value);
		t.toArticleFromTalkPage();
		if (t.value) {
			return t;
		}
		return null;
	};
	Title.prototype.articleFromTalkOrArticle = function () {
		var t = new Title(this.value);
		if (t.toArticleFromTalkPage()) {
			return t;
		}
		return this;
	};
	Title.prototype.isIpUser = function () {
		return pg.re.ipUser.test(this.userName());
	};
	Title.prototype.stripNamespace = function () {
		// returns a string, not a Title
		var n = this.value.indexOf(':');
		if (n < 0) {
			return this.value;
		}
		var namespaceId = this.namespaceId();
		if (namespaceId === pg.nsMainspaceId) {
			return this.value;
		}
		return this.value.substring(n + 1);
	};
	Title.prototype.setUtf = function (value) {
		if (!value) {
			this.value = '';
			return;
		}
		var anch = value.indexOf('#');
		if (anch < 0) {
			this.value = value.split('_').join(' ');
			this.anchor = '';
			return;
		}
		this.value = value.substring(0, anch).split('_').join(' ');
		this.anchor = value.substring(anch + 1);
		this.ns = null; // wait until namespace() is called
	};
	Title.prototype.setUrl = function (urlfrag) {
		var anch = urlfrag.indexOf('#');
		this.value = safeDecodeURI(urlfrag.substring(0, anch));
		this.anchor = this.value.substring(anch + 1);
	};
	Title.prototype.append = function (x) {
		this.setUtf(this.value + x);
	};
	Title.prototype.urlString = function (x) {
		if (!x) {
			x = {};
		}
		var v = this.toString(true);
		if (!x.omitAnchor && this.anchor) {
			v += '#' + this.urlAnchor();
		}
		if (!x.keepSpaces) {
			v = v.split(' ').join('_');
		}
		return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B');
	};
	Title.prototype.removeAnchor = function () {
		return new Title(this.toString(true));
	};
	Title.prototype.toUrl = function () {
		return pg.wiki.titlebase + this.urlString();
	};

	function parseParams(url) {
		var specialDiff = pg.re.specialdiff.exec(url);
		if (specialDiff) {
			var split = specialDiff[1].split('/');
			if (split.length == 1) {
				return { oldid: split[0], diff: 'prev' };
			} else if (split.length == 2) {
				return { oldid: split[0], diff: split[1] };
			}
		}

		var ret = {};
		if (url.indexOf('?') == -1) {
			return ret;
		}
		url = url.split('#')[0];
		var s = url.split('?').slice(1).join();
		var t = s.split('&');
		for (var i = 0; i < t.length; ++i) {
			var z = t[i].split('=');
			z.push(null);
			ret[z[0]] = z[1];
		}
		//Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki
		if (ret.diff && typeof ret.oldid === 'undefined') {
			ret.oldid = 'prev';
		}
		//Documentation seems to say something different, but oldid can also accept prev/next, and
		//Echo is emitting such URLs. Simple fixup during parameter decoding:
		if (ret.oldid && (ret.oldid === 'prev' || ret.oldid === 'next' || ret.oldid === 'cur')) {
			var helper = ret.diff;
			ret.diff = ret.oldid;
			ret.oldid = helper;
		}
		return ret;
	}

	// (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup)
	// (b) change spaces to underscores
	// (c) encodeURI (just the straight one, no pg.re.urlNoPopup)

	function myDecodeURI(str) {
		var ret;
		// FIXME decodeURIComponent??
		try {
			ret = decodeURI(str.toString());
		} catch (summat) {
			return str;
		}
		for (var i = 0; i < pg.misc.decodeExtras.length; ++i) {
			var from = pg.misc.decodeExtras[i].from;
			var to = pg.misc.decodeExtras[i].to;
			ret = ret.split(from).join(to);
		}
		return ret;
	}

	function safeDecodeURI(str) {
		var ret = myDecodeURI(str);
		return ret || str;
	}

	///////////
	// TESTS //
	///////////

	function isDisambig(data, article) {
		if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
			return false;
		}
		return !article.isTalkPage() && pg.re.disambig.test(data);
	}

	function stubCount(data, article) {
		if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
			return false;
		}
		var sectStub = 0;
		var realStub = 0;
		if (pg.re.stub.test(data)) {
			var s = data.parenSplit(pg.re.stub);
			for (var i = 1; i < s.length; i = i + 2) {
				if (s[i]) {
					++sectStub;
				} else {
					++realStub;
				}
			}
		}
		return { real: realStub, sect: sectStub };
	}

	function isValidImageName(str) {
		// extend as needed...
		return str.indexOf('{') == -1;
	}

	function isInStrippableNamespace(article) {
		// Does the namespace allow subpages
		// Note, would be better if we had access to wgNamespacesWithSubpages
		return article.namespaceId() !== 0;
	}

	function isInMainNamespace(article) {
		return article.namespaceId() === 0;
	}

	function anchorContainsImage(a) {
		// iterate over children of anchor a
		// see if any are images
		if (a === null) {
			return false;
		}
		var kids = a.childNodes;
		for (var i = 0; i < kids.length; ++i) {
			if (kids[i].nodeName == 'IMG') {
				return true;
			}
		}
		return false;
	}
	function isPopupLink(a) {
		// NB for performance reasons, TOC links generally return true
		// they should be stripped out later

		if (!markNopopupSpanLinks.done) {
			markNopopupSpanLinks();
		}
		if (a.inNopopupSpan) {
			return false;
		}

		// FIXME is this faster inline?
		if (a.onmousedown || a.getAttribute('nopopup')) {
			return false;
		}
		var h = a.href;
		if (h === document.location.href + '#') {
			return false;
		}
		if (!pg.re.basenames.test(h)) {
			return false;
		}
		if (!pg.re.urlNoPopup.test(h)) {
			return true;
		}
		return (
			(pg.re.email.test(h) ||
				pg.re.contribs.test(h) ||
				pg.re.backlinks.test(h) ||
				pg.re.specialdiff.test(h)) &&
			h.indexOf('&limit=') == -1
		);
	}

	function markNopopupSpanLinks() {
		if (!getValueOf('popupOnlyArticleLinks')) {
			fixVectorMenuPopups();
		}

		var s = $('.nopopups').toArray();
		for (var i = 0; i < s.length; ++i) {
			var as = s[i].getElementsByTagName('a');
			for (var j = 0; j < as.length; ++j) {
				as[j].inNopopupSpan = true;
			}
		}

		markNopopupSpanLinks.done = true;
	}

	function fixVectorMenuPopups() {
		$('nav.vector-menu h3:first a:first').prop('inNopopupSpan', true);
	}
	// ENDFILE: titles.js

	// STARTFILE: getpage.js
	//////////////////////////////////////////////////
	// Wiki-specific downloading
	//

	// Schematic for a getWiki call
	//
	//             getPageWithCaching
	//					|
	//	   false		|		  true
	// getPage<-[findPictureInCache]->-onComplete(a fake download)
	//   \.
	//	 (async)->addPageToCache(download)->-onComplete(download)

	// check cache to see if page exists

	function getPageWithCaching(url, onComplete, owner) {
		log('getPageWithCaching, url=' + url);
		var i = findInPageCache(url);
		var d;
		if (i > -1) {
			d = fakeDownload(
				url,
				owner.idNumber,
				onComplete,
				pg.cache.pages[i].data,
				pg.cache.pages[i].lastModified,
				owner
			);
		} else {
			d = getPage(url, onComplete, owner);
			if (d && owner && owner.addDownload) {
				owner.addDownload(d);
				d.owner = owner;
			}
		}
	}

	function getPage(url, onComplete, owner) {
		log('getPage');
		var callback = function (d) {
			if (!d.aborted) {
				addPageToCache(d);
				onComplete(d);
			}
		};
		return startDownload(url, owner.idNumber, callback);
	}

	function findInPageCache(url) {
		for (var i = 0; i < pg.cache.pages.length; ++i) {
			if (url == pg.cache.pages[i].url) {
				return i;
			}
		}
		return -1;
	}

	function addPageToCache(download) {
		log('addPageToCache ' + download.url);
		var page = {
			url: download.url,
			data: download.data,
			lastModified: download.lastModified,
		};
		return pg.cache.pages.push(page);
	}
	// ENDFILE: getpage.js

	// STARTFILE: parensplit.js
	//////////////////////////////////////////////////
	// parenSplit

	// String.prototype.parenSplit should do what ECMAscript says String.prototype.split does,
	// interspersing paren matches (regex capturing groups) between the split elements.
	// i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c']

	if (String('abc'.split(/(b)/)) != 'a,b,c') {
		// broken String.split, e.g. konq, IE < 10
		String.prototype.parenSplit = function (re) {
			re = nonGlobalRegex(re);
			var s = this;
			var m = re.exec(s);
			var ret = [];
			while (m && s) {
				// without the following loop, we have
				// 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/)
				for (var i = 0; i < m.length; ++i) {
					if (typeof m[i] == 'undefined') {
						m[i] = '';
					}
				}
				ret.push(s.substring(0, m.index));
				ret = ret.concat(m.slice(1));
				s = s.substring(m.index + m[0].length);
				m = re.exec(s);
			}
			ret.push(s);
			return ret;
		};
	} else {
		String.prototype.parenSplit = function (re) {
			return this.split(re);
		};
		String.prototype.parenSplit.isNative = true;
	}

	function nonGlobalRegex(re) {
		var s = re.toString();
		var flags = '';
		for (var j = s.length; s.charAt(j) != '/'; --j) {
			if (s.charAt(j) != 'g') {
				flags += s.charAt(j);
			}
		}
		var t = s.substring(1, j);
		return RegExp(t, flags);
	}
	// ENDFILE: parensplit.js

	// STARTFILE: tools.js
	// IE madness with encoding
	// ========================
	//
	// suppose throughout that the page is in utf8, like wikipedia
	//
	// if a is an anchor DOM element and a.href should consist of
	//
	// http://host.name.here/wiki/foo?bar=baz
	//
	// then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie))
	// but IE gives bar=baz correctly as plain utf8
	//
	// ---------------------------------
	//
	// IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here.
	//
	// ---------------------------------
	//
	// summat else

	// Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm

	function getJsObj(json) {
		try {
			var json_ret = JSON.parse(json);
			if (json_ret.warnings) {
				for (var w = 0; w < json_ret.warnings.length; w++) {
					if (json_ret.warnings[w]['*']) {
						log(json_ret.warnings[w]['*']);
					} else {
						log(json_ret.warnings[w].warnings);
					}
				}
			} else if (json_ret.error) {
				errlog(json_ret.error.code + ': ' + json_ret.error.info);
			}
			return json_ret;
		} catch (someError) {
			errlog('Something went wrong with getJsObj, json=' + json);
			return 1;
		}
	}

	function anyChild(obj) {
		for (var p in obj) {
			return obj[p];
		}
		return null;
	}

	function upcaseFirst(str) {
		if (typeof str != typeof '' || str === '') {
			return '';
		}
		return str.charAt(0).toUpperCase() + str.substring(1);
	}

	function findInArray(arr, foo) {
		if (!arr || !arr.length) {
			return -1;
		}
		var len = arr.length;
		for (var i = 0; i < len; ++i) {
			if (arr[i] == foo) {
				return i;
			}
		}
		return -1;
	}

	/* eslint-disable no-unused-vars */
	function nextOne(array, value) {
		// NB if the array has two consecutive entries equal
		//	then this will loop on successive calls
		var i = findInArray(array, value);
		if (i < 0) {
			return null;
		}
		return array[i + 1];
	}
	/* eslint-enable no-unused-vars */

	function literalizeRegex(str) {
		return mw.util.escapeRegExp(str);
	}

	String.prototype.entify = function () {
		//var shy='&shy;';
		return this.split('&')
			.join('&amp;')
			.split('<')
			.join('&lt;')
			.split('>')
			.join('&gt;' /*+shy*/)
			.split('"')
			.join('&quot;');
	};

	// Array filter function
	function removeNulls(val) {
		return val !== null;
	}

	function joinPath(list) {
		return list.filter(removeNulls).join('/');
	}

	function simplePrintf(str, subs) {
		if (!str || !subs) {
			return str;
		}
		var ret = [];
		var s = str.parenSplit(/(%s|\$[0-9]+)/);
		var i = 0;
		do {
			ret.push(s.shift());
			if (!s.length) {
				break;
			}
			var cmd = s.shift();
			if (cmd == '%s') {
				if (i < subs.length) {
					ret.push(subs[i]);
				} else {
					ret.push(cmd);
				}
				++i;
			} else {
				var j = parseInt(cmd.replace('$', ''), 10) - 1;
				if (j > -1 && j < subs.length) {
					ret.push(subs[j]);
				} else {
					ret.push(cmd);
				}
			}
		} while (s.length > 0);
		return ret.join('');
	}

	/* eslint-disable no-unused-vars */
	function isString(x) {
		return typeof x === 'string' || x instanceof String;
	}

	function isNumber(x) {
		return typeof x === 'number' || x instanceof Number;
	}

	function isRegExp(x) {
		return x instanceof RegExp;
	}

	function isArray(x) {
		return x instanceof Array;
	}

	function isObject(x) {
		return x instanceof Object;
	}

	function isFunction(x) {
		return !isRegExp(x) && (typeof x === 'function' || x instanceof Function);
	}
	/* eslint-enable no-unused-vars */

	function repeatString(s, mult) {
		var ret = '';
		for (var i = 0; i < mult; ++i) {
			ret += s;
		}
		return ret;
	}

	function zeroFill(s, min) {
		min = min || 2;
		var t = s.toString();
		return repeatString('0', min - t.length) + t;
	}

	function map(f, o) {
		if (isArray(o)) {
			return map_array(f, o);
		}
		return map_object(f, o);
	}
	function map_array(f, o) {
		var ret = [];
		for (var i = 0; i < o.length; ++i) {
			ret.push(f(o[i]));
		}
		return ret;
	}
	function map_object(f, o) {
		var ret = {};
		for (var i in o) {
			ret[o] = f(o[i]);
		}
		return ret;
	}

	pg.escapeQuotesHTML = function (text) {
		return text
			.replace(/&/g, '&amp;')
			.replace(/"/g, '&quot;')
			.replace(/</g, '&lt;')
			.replace(/>/g, '&gt;');
	};

	pg.unescapeQuotesHTML = function (html) {
		// From https://stackoverflow.com/a/7394787
		// This seems to be implemented correctly on all major browsers now, so we
		// don't have to make our own function.
		var txt = document.createElement('textarea');
		txt.innerHTML = html;
		return txt.value;
	};

	// ENDFILE: tools.js

	// STARTFILE: dab.js
	//////////////////////////////////////////////////
	// Dab-fixing code
	//

	function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) {
		log('retargetDab: newTarget=' + newTarget + ' oldTarget=' + oldTarget);
		return changeLinkTargetLink({
			newTarget: newTarget,
			text: newTarget.split(' ').join('&nbsp;'),
			hint: tprintf('disambigHint', [newTarget]),
			summary: simplePrintf(getValueOf('popupFixDabsSummary'), [
				friendlyCurrentArticleName,
				newTarget,
			]),
			clickButton: getValueOf('popupDabsAutoClick'),
			minor: true,
			oldTarget: oldTarget,
			watch: getValueOf('popupWatchDisambiggedPages'),
			title: titleToEdit,
		});
	}

	function listLinks(wikitext, oldTarget, titleToEdit) {
		// mediawiki strips trailing spaces, so we do the same
		// testcase: https://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633
		var reg = RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi');
		var ret = [];
		var splitted = wikitext.parenSplit(reg);
		// ^[a-z]+ should match interwiki links, hopefully (case-insensitive)
		// and ^[a-z]* should match those and [[:Category...]] style links too
		var omitRegex = RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory');
		var friendlyCurrentArticleName = oldTarget.toString();
		var wikPos = getValueOf('popupDabWiktionary');

		for (var i = 1; i < splitted.length; i = i + 3) {
			if (
				typeof splitted[i] == typeof 'string' &&
				splitted[i].length > 0 &&
				!omitRegex.test(splitted[i])
			) {
				ret.push(retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit));
			} /* if */
		} /* for loop */

		ret = rmDupesFromSortedList(ret.sort());

		if (wikPos) {
			var wikTarget =
				'wiktionary:' +
				friendlyCurrentArticleName.replace(RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1');

			var meth;
			if (wikPos.toLowerCase() == 'first') {
				meth = 'unshift';
			} else {
				meth = 'push';
			}

			ret[meth](retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit));
		}

		ret.push(
			changeLinkTargetLink({
				newTarget: null,
				text: popupString('remove this link').split(' ').join('&nbsp;'),
				hint: popupString('remove all links to this disambig page from this article'),
				clickButton: getValueOf('popupDabsAutoClick'),
				oldTarget: oldTarget,
				summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]),
				watch: getValueOf('popupWatchDisambiggedPages'),
				title: titleToEdit,
			})
		);
		return ret;
	}

	function rmDupesFromSortedList(list) {
		var ret = [];
		for (var i = 0; i < list.length; ++i) {
			if (ret.length === 0 || list[i] != ret[ret.length - 1]) {
				ret.push(list[i]);
			}
		}
		return ret;
	}

	function makeFixDab(data, navpop) {
		// grab title from parent popup if there is one; default exists in changeLinkTargetLink
		var titleToEdit = navpop.parentPopup && navpop.parentPopup.article.toString();
		var list = listLinks(data, navpop.originalArticle, titleToEdit);
		if (list.length === 0) {
			log('listLinks returned empty list');
			return null;
		}
		var html = '<hr />' + popupString('Click to disambiguate this link to:') + '<br>';
		html += list.join(', ');
		return html;
	}

	function makeFixDabs(wikiText, navpop) {
		if (
			getValueOf('popupFixDabs') &&
			isDisambig(wikiText, navpop.article) &&
			Title.fromURL(location.href).namespaceId() != pg.nsSpecialId &&
			navpop.article.talkPage()
		) {
			setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber);
		}
	}

	function popupRedlinkHTML(article) {
		return changeLinkTargetLink({
			newTarget: null,
			text: popupString('remove this link').split(' ').join('&nbsp;'),
			hint: popupString('remove all links to this page from this article'),
			clickButton: getValueOf('popupRedlinkAutoClick'),
			oldTarget: article.toString(),
			summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()]),
		});
	}
	// ENDFILE: dab.js

	// STARTFILE: htmloutput.js

	// this has to use a timer loop as we don't know if the DOM element exists when we want to set the text
	function setPopupHTML(str, elementId, popupId, onSuccess, append) {
		if (typeof popupId === 'undefined') {
			//console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100));
			popupId = pg.idNumber;
		}

		var popupElement = document.getElementById(elementId + popupId);
		if (popupElement) {
			if (!append) {
				popupElement.innerHTML = '';
			}
			if (isString(str)) {
				popupElement.innerHTML += str;
			} else {
				popupElement.appendChild(str);
			}
			if (onSuccess) {
				onSuccess();
			}
			setTimeout(checkPopupPosition, 100);
			return true;
		} else {
			// call this function again in a little while...
			setTimeout(function () {
				setPopupHTML(str, elementId, popupId, onSuccess);
			}, 600);
		}
		return null;
	}

	function setPopupTrailer(str, id) {
		return setPopupHTML(str, 'popupData', id);
	}

	// args.navpopup is mandatory
	// optional: args.redir, args.redirTarget
	// FIXME: ye gods, this is ugly stuff
	function fillEmptySpans(args) {
		// if redir is present and true then redirTarget is mandatory
		var redir = true;
		var rcid;
		if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) {
			redir = false;
		}
		var a = args.navpopup.parentAnchor;

		var article,
			hint = null,
			oldid = null,
			params = {};
		if (redir && typeof args.redirTarget == typeof {}) {
			article = args.redirTarget;
			//hint=article.hintValue();
		} else {
			article = new Title().fromAnchor(a);
			hint = a.originalTitle || article.hintValue();
			params = parseParams(a.href);
			oldid = getValueOf('popupHistoricalLinks') ? params.oldid : null;
			rcid = params.rcid;
		}
		var x = {
			article: article,
			hint: hint,
			oldid: oldid,
			rcid: rcid,
			navpop: args.navpopup,
			params: params,
		};

		var structure = pg.structures[getValueOf('popupStructure')];
		if (typeof structure != 'object') {
			setPopupHTML(
				'popupError',
				'Unknown structure (this should never happen): ' + pg.option.popupStructure,
				args.navpopup.idNumber
			);
			return;
		}
		var spans = flatten(pg.misc.layout);
		var numspans = spans.length;
		var redirs = pg.misc.redirSpans;

		for (var i = 0; i < numspans; ++i) {
			var found = redirs && redirs.indexOf(spans[i]) !== -1;
			//log('redir='+redir+', found='+found+', spans[i]='+spans[i]);
			if ((found && !redir) || (!found && redir)) {
				//log('skipping this set of the loop');
				continue;
			}
			var structurefn = structure[spans[i]];
			if (structurefn === undefined) {
				// nothing to do for this structure part
				continue;
			}
			var setfn = setPopupHTML;
			if (
				getValueOf('popupActiveNavlinks') &&
				(spans[i].indexOf('popupTopLinks') === 0 || spans[i].indexOf('popupRedirTopLinks') === 0)
			) {
				setfn = setPopupTipsAndHTML;
			}
			switch (typeof structurefn) {
				case 'function':
					log(
						'running ' +
							spans[i] +
							'({article:' +
							x.article +
							', hint:' +
							x.hint +
							', oldid: ' +
							x.oldid +
							'})'
					);
					setfn(structurefn(x), spans[i], args.navpopup.idNumber);
					break;
				case 'string':
					setfn(structurefn, spans[i], args.navpopup.idNumber);
					break;
				default:
					errlog('unknown thing with label ' + spans[i] + ' (span index was ' + i + ')');
					break;
			}
		}
	}

	// flatten an array
	function flatten(list, start) {
		var ret = [];
		if (typeof start == 'undefined') {
			start = 0;
		}
		for (var i = start; i < list.length; ++i) {
			if (typeof list[i] == typeof []) {
				return ret.concat(flatten(list[i])).concat(flatten(list, i + 1));
			} else {
				ret.push(list[i]);
			}
		}
		return ret;
	}

	// Generate html for whole popup
	function popupHTML(a) {
		getValueOf('popupStructure');
		var structure = pg.structures[pg.option.popupStructure];
		if (typeof structure != 'object') {
			//return 'Unknown structure: '+pg.option.popupStructure;
			// override user choice
			pg.option.popupStructure = pg.optionDefault.popupStructure;
			return popupHTML(a);
		}
		if (typeof structure.popupLayout != 'function') {
			return 'Bad layout';
		}
		pg.misc.layout = structure.popupLayout();
		if (typeof structure.popupRedirSpans === 'function') {
			pg.misc.redirSpans = structure.popupRedirSpans();
		} else {
			pg.misc.redirSpans = [];
		}
		return makeEmptySpans(pg.misc.layout, a.navpopup);
	}

	function makeEmptySpans(list, navpop) {
		var ret = '';
		for (var i = 0; i < list.length; ++i) {
			if (typeof list[i] == typeof '') {
				ret += emptySpanHTML(list[i], navpop.idNumber, 'div');
			} else if (typeof list[i] == typeof [] && list[i].length > 0) {
				ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop));
			} else if (typeof list[i] == typeof {} && list[i].nodeType) {
				ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType);
			}
		}
		return ret;
	}

	function emptySpanHTML(name, id, tag, classname) {
		tag = tag || 'span';
		if (!classname) {
			classname = emptySpanHTML.classAliases[name];
		}
		classname = classname || name;
		if (name == getValueOf('popupDragHandle')) {
			classname += ' popupDragHandle';
		}
		return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]);
	}
	emptySpanHTML.classAliases = { popupSecondPreview: 'popupPreview' };

	// generate html for popup image
	// <a id="popupImageLinkn"><img id="popupImagen">
	// where n=idNumber
	function imageHTML(article, idNumber) {
		return simplePrintf(
			'<a id="popupImageLink$1">' +
				'<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' +
				'</a>',
			[idNumber]
		);
	}

	function popTipsSoonFn(id, when, popData) {
		if (!when) {
			when = 250;
		}
		var popTips = function () {
			setupTooltips(document.getElementById(id), false, true, popData);
		};
		return function () {
			setTimeout(popTips, when, popData);
		};
	}

	function setPopupTipsAndHTML(html, divname, idnumber, popData) {
		setPopupHTML(
			html,
			divname,
			idnumber,
			getValueOf('popupSubpopups') ? popTipsSoonFn(divname + idnumber, null, popData) : null
		);
	}
	// ENDFILE: htmloutput.js

	// STARTFILE: mouseout.js
	//////////////////////////////////////////////////
	// fuzzy checks

	function fuzzyCursorOffMenus(x, y, fuzz, parent) {
		if (!parent) {
			return null;
		}
		var uls = parent.getElementsByTagName('ul');
		for (var i = 0; i < uls.length; ++i) {
			if (uls[i].className == 'popup_menu') {
				if (uls[i].offsetWidth > 0) {
					return false;
				}
			} // else {document.title+='.';}
		}
		return true;
	}

	function checkPopupPosition() {
		// stop the popup running off the right of the screen
		// FIXME avoid pg.current.link
		if (pg.current.link && pg.current.link.navpopup) {
			pg.current.link.navpopup.limitHorizontalPosition();
		}
	}

	function mouseOutWikiLink() {
		//console ('mouseOutWikiLink');
		var a = this;

		removeModifierKeyHandler(a);

		if (a.navpopup === null || typeof a.navpopup === 'undefined') {
			return;
		}
		if (!a.navpopup.isVisible()) {
			a.navpopup.banish();
			return;
		}
		restoreTitle(a);
		Navpopup.tracker.addHook(posCheckerHook(a.navpopup));
	}

	function posCheckerHook(navpop) {
		return function () {
			if (!navpop.isVisible()) {
				return true; /* remove this hook */
			}
			if (Navpopup.tracker.dirty) {
				return false;
			}
			var x = Navpopup.tracker.x,
				y = Navpopup.tracker.y;
			var mouseOverNavpop =
				navpop.isWithin(x, y, navpop.fuzz, navpop.mainDiv) ||
				!fuzzyCursorOffMenus(x, y, navpop.fuzz, navpop.mainDiv);

			// FIXME it'd be prettier to do this internal to the Navpopup objects
			var t = getValueOf('popupHideDelay');
			if (t) {
				t = t * 1000;
			}
			if (!t) {
				if (!mouseOverNavpop) {
					if (navpop.parentAnchor) {
						restoreTitle(navpop.parentAnchor);
					}
					navpop.banish();
					return true; /* remove this hook */
				}
				return false;
			}
			// we have a hide delay set
			var d = Number(new Date());
			if (!navpop.mouseLeavingTime) {
				navpop.mouseLeavingTime = d;
				return false;
			}
			if (mouseOverNavpop) {
				navpop.mouseLeavingTime = null;
				return false;
			}
			if (d - navpop.mouseLeavingTime > t) {
				navpop.mouseLeavingTime = null;
				navpop.banish();
				return true; /* remove this hook */
			}
			return false;
		};
	}

	function runStopPopupTimer(navpop) {
		// at this point, we should have left the link but remain within the popup
		// so we call this function again until we leave the popup.
		if (!navpop.stopPopupTimer) {
			navpop.stopPopupTimer = setInterval(posCheckerHook(navpop), 500);
			navpop.addHook(
				function () {
					clearInterval(navpop.stopPopupTimer);
				},
				'hide',
				'before'
			);
		}
	}
	// ENDFILE: mouseout.js

	// STARTFILE: previewmaker.js
	/**
	 * @fileoverview
	 * Defines the {@link Previewmaker} object, which generates short previews from wiki markup.
	 */

	/**
	 * Creates a new Previewmaker
	 * @constructor
	 * @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext.
	 * @param {String} wikiText The Wikitext source of the page we wish to preview.
	 * @param {String} baseUrl The url we should prepend when creating relative urls.
	 * @param {Navpopup} owner The navpop associated to this preview generator
	 */
	function Previewmaker(wikiText, baseUrl, owner) {
		/** The wikitext which is manipulated to generate the preview. */
		this.originalData = wikiText;
		this.baseUrl = baseUrl;
		this.owner = owner;

		this.maxCharacters = getValueOf('popupMaxPreviewCharacters');
		this.maxSentences = getValueOf('popupMaxPreviewSentences');

		this.setData();
	}

	Previewmaker.prototype.setData = function () {
		var maxSize = Math.max(10000, 2 * this.maxCharacters);
		this.data = this.originalData.substring(0, maxSize);
	};

	/**
	 * Remove HTML comments
	 * @private
	 */
	Previewmaker.prototype.killComments = function () {
		// this also kills one trailing newline, eg [[diamyo]]
		this.data = this.data.replace(
			RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'),
			''
		);
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.killDivs = function () {
		// say goodbye, divs (can be nested, so use * not *?)
		this.data = this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>', 'gi'), '');
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.killGalleries = function () {
		this.data = this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>', 'gi'), '');
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.kill = function (opening, closing, subopening, subclosing, repl) {
		var oldk = this.data;
		var k = this.killStuff(this.data, opening, closing, subopening, subclosing, repl);
		while (k.length < oldk.length) {
			oldk = k;
			k = this.killStuff(k, opening, closing, subopening, subclosing, repl);
		}
		this.data = k;
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.killStuff = function (
		txt,
		opening,
		closing,
		subopening,
		subclosing,
		repl
	) {
		var op = this.makeRegexp(opening);
		var cl = this.makeRegexp(closing, '^');
		var sb = subopening ? this.makeRegexp(subopening, '^') : null;
		var sc = subclosing ? this.makeRegexp(subclosing, '^') : cl;
		if (!op || !cl) {
			alert('Navigation Popups error: op or cl is null! something is wrong.');
			return;
		}
		if (!op.test(txt)) {
			return txt;
		}
		var ret = '';
		var opResult = op.exec(txt);
		ret = txt.substring(0, opResult.index);
		txt = txt.substring(opResult.index + opResult[0].length);
		var depth = 1;
		while (txt.length > 0) {
			var removal = 0;
			if (depth == 1 && cl.test(txt)) {
				depth--;
				removal = cl.exec(txt)[0].length;
			} else if (depth > 1 && sc.test(txt)) {
				depth--;
				removal = sc.exec(txt)[0].length;
			} else if (sb && sb.test(txt)) {
				depth++;
				removal = sb.exec(txt)[0].length;
			}
			if (!removal) {
				removal = 1;
			}
			txt = txt.substring(removal);
			if (depth === 0) {
				break;
			}
		}
		return ret + (repl || '') + txt;
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) {
		prefix = prefix || '';
		suffix = suffix || '';
		var reStr = '';
		var flags = '';
		if (isString(x)) {
			reStr = prefix + literalizeRegex(x) + suffix;
		} else if (isRegExp(x)) {
			var s = x.toString().substring(1);
			var sp = s.split('/');
			flags = sp[sp.length - 1];
			sp[sp.length - 1] = '';
			s = sp.join('/');
			s = s.substring(0, s.length - 1);
			reStr = prefix + s + suffix;
		} else {
			log('makeRegexp failed');
		}

		log('makeRegexp: got reStr=' + reStr + ', flags=' + flags);
		return RegExp(reStr, flags);
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.killBoxTemplates = function () {
		// taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general
		// also, have float_begin, ... float_end
		this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{');

		// infoboxes etc
		// from [[User:Zyxw/popups.js]]: kill frames too
		this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{');
	};

	/** <!--
	 * @private
	 */
	Previewmaker.prototype.killTemplates = function () {
		this.kill('{{', '}}', '{', '}', ' ');
	};

	/** -->
	 * @private
	 */
	Previewmaker.prototype.killTables = function () {
		// tables are bad, too
		// this can be slow, but it's an inprovement over a browser hang
		// torture test: [[Comparison_of_Intel_Central_Processing_Units]]
		this.kill('{|', /[|]}\s*/, '{|');
		this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i);
		// remove lines starting with a pipe for the hell of it (?)
		this.data = this.data.replace(RegExp('^[|].*$', 'mg'), '');
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.killImages = function () {
		var forbiddenNamespaceAliases = [];
		jQuery.each(mw.config.get('wgNamespaceIds'), function (_localizedNamespaceLc, _namespaceId) {
			if (_namespaceId != pg.nsImageId && _namespaceId != pg.nsCategoryId) {
				return;
			}
			forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments!
		});

		// images and categories are a nono
		this.kill(
			RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'),
			/\]\]\s*/,
			'[',
			']'
		);
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.killHTML = function () {
		// kill <ref ...>...</ref>
		this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i);

		// let's also delete entire lines starting with <. it's worth a try.
		this.data = this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n');

		// and those pesky html tags, but not <nowiki> or <blockquote>
		var splitted = this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/);
		var len = splitted.length;
		for (var i = 1; i < len; i = i + 2) {
			switch (splitted[i]) {
				case '<nowiki>':
				case '</nowiki>':
				case '<blockquote>':
				case '</blockquote>':
					break;
				default:
					splitted[i] = '';
			}
		}
		this.data = splitted.join('');
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.killChunks = function () {
		// heuristics alert
		// chunks of italic text? you crazy, man?
		var italicChunkRegex = new RegExp(
			"((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+",
			'g'
		);
		// keep stuff separated, though, so stick in \n (fixes [[Union Jack]]?
		this.data = this.data.replace(italicChunkRegex, '\n');
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.mopup = function () {
		// we simply *can't* be doing with horizontal rules right now
		this.data = this.data.replace(RegExp('^-{4,}', 'mg'), '');

		// no indented lines
		this.data = this.data.replace(RegExp('(^|\\n) *:[^\\n]*', 'g'), '');

		// replace __TOC__, __NOTOC__ and whatever else there is
		// this'll probably do
		this.data = this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'), '');
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.firstBit = function () {
		// dont't be givin' me no subsequent paragraphs, you hear me?
		/// first we "normalize" section headings, removing whitespace after, adding before
		var d = this.data;

		if (getValueOf('popupPreviewCutHeadings')) {
			this.data = this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 ');
			/// then we want to get rid of paragraph breaks whose text ends badly
			this.data = this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n');

			this.data = this.data.replace(RegExp('^[\\s\\n]*'), '');
			var stuff = RegExp('^([^\\n]|\\n[^\\n\\s])*').exec(this.data);
			if (stuff) {
				d = stuff[0];
			}
			if (!getValueOf('popupPreviewFirstParOnly')) {
				d = this.data;
			}

			/// now put \n\n after sections so that bullets and numbered lists work
			d = d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n');
		}

		// Split sentences. Superfluous sentences are RIGHT OUT.
		// note: exactly 1 set of parens here needed to make the slice work
		d = d.parenSplit(RegExp('([!?.]+["' + "'" + ']*\\s)', 'g'));
		// leading space is bad, mmkay?
		d[0] = d[0].replace(RegExp('^\\s*'), '');

		var notSentenceEnds = RegExp(
			'([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$',
			'i'
		);
		d = this.fixSentenceEnds(d, notSentenceEnds);

		this.fullLength = d.join('').length;
		var n = this.maxSentences;
		var dd = this.firstSentences(d, n);

		do {
			dd = this.firstSentences(d, n);
			--n;
		} while (dd.length > this.maxCharacters && n !== 0);

		this.data = dd;
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.fixSentenceEnds = function (strs, reg) {
		// take an array of strings, strs
		// join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg

		for (var i = 0; i < strs.length - 2; ++i) {
			if (reg.test(strs[i])) {
				var a = [];
				for (var j = 0; j < strs.length; ++j) {
					if (j < i) {
						a[j] = strs[j];
					}
					if (j == i) {
						a[i] = strs[i] + strs[i + 1] + strs[i + 2];
					}
					if (j > i + 2) {
						a[j - 2] = strs[j];
					}
				}
				return this.fixSentenceEnds(a, reg);
			}
		}
		return strs;
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.firstSentences = function (strs, howmany) {
		var t = strs.slice(0, 2 * howmany);
		return t.join('');
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.killBadWhitespace = function () {
		// also cleans up isolated '''', eg [[Suntory Sungoliath]]
		this.data = this.data.replace(RegExp("^ *'+ *$", 'gm'), '');
	};

	/**
	 * Runs the various methods to generate the preview.
	 * The preview is stored in the <code>html</html> field.
	 * @private
	 */
	Previewmaker.prototype.makePreview = function () {
		if (
			this.owner.article.namespaceId() != pg.nsTemplateId &&
			this.owner.article.namespaceId() != pg.nsImageId
		) {
			this.killComments();
			this.killDivs();
			this.killGalleries();
			this.killBoxTemplates();

			if (getValueOf('popupPreviewKillTemplates')) {
				this.killTemplates();
			} else {
				this.killMultilineTemplates();
			}
			this.killTables();
			this.killImages();
			this.killHTML();
			this.killChunks();
			this.mopup();

			this.firstBit();
			this.killBadWhitespace();
		} else {
			this.killHTML();
		}
		this.html = wiki2html(this.data, this.baseUrl); // needs livepreview
		this.fixHTML();
		this.stripLongTemplates();
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.esWiki2HtmlPart = function (data) {
		var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink
		reLinks.lastIndex = 0; //reset regex

		var match;
		var result = '';
		var postfixIndex = 0;
		while ((match = reLinks.exec(data))) {
			//match all wikilinks
			//FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases.
			result +=
				pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) +
				'<a href="' +
				Insta.conf.paths.articles +
				pg.escapeQuotesHTML(match[1]) +
				'">' +
				pg.escapeQuotesHTML((match[2] ? match[2] : match[1]) + match[3]) +
				'</a>';
			postfixIndex = reLinks.lastIndex;
		}
		//append the rest
		result += pg.escapeQuotesHTML(data.substring(postfixIndex));

		return result;
	};
	Previewmaker.prototype.editSummaryPreview = function () {
		var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker
		reAes.lastIndex = 0; //reset regex

		var match;

		match = reAes.exec(this.data);
		if (match) {
			//we have a section link. Split it, process it, combine it.
			var prefix = this.data.substring(0, match.index - 1);
			var section = match[1];
			var postfix = this.data.substring(reAes.lastIndex);

			var start = "<span class='autocomment'>";
			var end = '</span>';
			if (prefix.length > 0) {
				start = this.esWiki2HtmlPart(prefix) + ' ' + start + '- ';
			}
			if (postfix.length > 0) {
				end = ': ' + end + this.esWiki2HtmlPart(postfix);
			}

			var t = new Title().fromURL(this.baseUrl);
			t.anchorFromUtf(section);
			var sectionLink =
				Insta.conf.paths.articles +
				pg.escapeQuotesHTML(t.toString(true)) +
				'#' +
				pg.escapeQuotesHTML(t.anchor);
			return (
				start + '<a href="' + sectionLink + '">&rarr;</a> ' + pg.escapeQuotesHTML(section) + end
			);
		}

		//else there's no section link, htmlify the whole thing.
		return this.esWiki2HtmlPart(this.data);
	};

	/** Test function for debugging preview problems one step at a time. */
	/*eslint-disable */
	function previewSteps(txt) {
		try {
			txt = txt || document.editform.wpTextbox1.value;
		} catch (err) {
			if (pg.cache.pages.length > 0) {
				txt = pg.cache.pages[pg.cache.pages.length - 1].data;
			} else {
				alert('provide text or use an edit page');
			}
		}
		txt = txt.substring(0, 10000);
		var base = pg.wiki.articlebase + Title.fromURL(document.location.href).urlString();
		var p = new Previewmaker(txt, base, pg.current.link.navpopup);
		if (this.owner.article.namespaceId() != pg.nsTemplateId) {
			p.killComments();
			if (!confirm('done killComments(). Continue?\n---\n' + p.data)) {
				return;
			}
			p.killDivs();
			if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) {
				return;
			}
			p.killGalleries();
			if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) {
				return;
			}
			p.killBoxTemplates();
			if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) {
				return;
			}

			if (getValueOf('popupPreviewKillTemplates')) {
				p.killTemplates();
				if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) {
					return;
				}
			} else {
				p.killMultilineTemplates();
				if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) {
					return;
				}
			}

			p.killTables();
			if (!confirm('done killTables(). Continue?\n---\n' + p.data)) {
				return;
			}
			p.killImages();
			if (!confirm('done killImages(). Continue?\n---\n' + p.data)) {
				return;
			}
			p.killHTML();
			if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) {
				return;
			}
			p.killChunks();
			if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) {
				return;
			}
			p.mopup();
			if (!confirm('done mopup(). Continue?\n---\n' + p.data)) {
				return;
			}

			p.firstBit();
			if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) {
				return;
			}
			p.killBadWhitespace();
			if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) {
				return;
			}
		}

		p.html = wiki2html(p.data, base); // needs livepreview
		p.fixHTML();
		if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) {
			return;
		}
		p.stripLongTemplates();
		if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) {
			return;
		}
		alert('finished preview - end result follows.\n---\n' + p.html);
	}
	/*eslint-enable */

	/**
	 * Works around livepreview bugs.
	 * @private
	 */
	Previewmaker.prototype.fixHTML = function () {
		if (!this.html) {
			return;
		}

		var ret = this.html;

		// fix question marks in wiki links
		// maybe this'll break some stuff :-(
		ret = ret.replace(
			RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'),
			'$1%3F$2'
		);
		ret = ret.replace(
			RegExp("(<a href='" + pg.wiki.articlePath + "/[^']*)[?](.*?')", 'g'),
			'$1%3F$2'
		);
		// FIXME fix up % too

		this.html = ret;
	};

	/**
	 * Generates the preview and displays it in the current popup.

	 * Does nothing if the generated preview is invalid or consists of whitespace only.
	 * Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true.
	 */
	Previewmaker.prototype.showPreview = function () {
		this.makePreview();
		if (typeof this.html != typeof '') {
			return;
		}
		if (RegExp('^\\s*$').test(this.html)) {
			return;
		}
		setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber);
		setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, {
			owner: this.owner,
		});
		var more = this.fullLength > this.data.length ? this.moreLink() : '';
		setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber);
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.moreLink = function () {
		var a = document.createElement('a');
		a.className = 'popupMoreLink';
		a.innerHTML = popupString('more...');
		var savedThis = this;
		a.onclick = function () {
			savedThis.maxCharacters += 2000;
			savedThis.maxSentences += 20;
			savedThis.setData();
			savedThis.showPreview();
		};
		return a;
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.stripLongTemplates = function () {
		// operates on the HTML!
		this.html = this.html.replace(
			RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'),
			''
		);
		this.html = this.html.split('\n').join(' '); // workaround for <pre> templates
		this.html = this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]', 'gi'), '');
	};

	/**
	 * @private
	 */
	Previewmaker.prototype.killMultilineTemplates = function () {
		this.kill('{{{', '}}}');
		this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{');
	};
	// ENDFILE: previewmaker.js

	// STARTFILE: querypreview.js
	function loadAPIPreview(queryType, article, navpop) {
		var art = new Title(article).urlString();
		var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';
		var htmlGenerator = function (/*a, d*/) {
			alert('invalid html generator');
		};
		var usernameart = '';
		switch (queryType) {
			case 'history':
				url +=
					'titles=' + art + '&prop=revisions&rvlimit=' + getValueOf('popupHistoryPreviewLimit');
				htmlGenerator = APIhistoryPreviewHTML;
				break;
			case 'category':
				url += 'list=categorymembers&cmtitle=' + art;
				htmlGenerator = APIcategoryPreviewHTML;
				break;
			case 'userinfo':
				var username = new Title(article).userName();
				usernameart = encodeURIComponent(username);
				if (pg.re.ipUser.test(username)) {
					url += 'list=blocks&bkprop=range|restrictions&bkip=' + usernameart;
				} else {
					url +=
						'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' +
						usernameart +
						'&meta=globaluserinfo&guiprop=groups|unattached&guiuser=' +
						usernameart +
						'&uclimit=1&ucprop=timestamp&ucuser=' +
						usernameart;
				}
				htmlGenerator = APIuserInfoPreviewHTML;
				break;
			case 'contribs':
				usernameart = encodeURIComponent(new Title(article).userName());
				url +=
					'list=usercontribs&ucuser=' +
					usernameart +
					'&uclimit=' +
					getValueOf('popupContribsPreviewLimit');
				htmlGenerator = APIcontribsPreviewHTML;
				break;
			case 'imagepagepreview':
				var trail = '';
				if (getValueOf('popupImageLinks')) {
					trail = '&list=imageusage&iutitle=' + art;
				}
				url += 'titles=' + art + '&prop=revisions|imageinfo&rvslots=main&rvprop=content' + trail;
				htmlGenerator = APIimagepagePreviewHTML;
				break;
			case 'backlinks':
				url += 'list=backlinks&bltitle=' + art;
				htmlGenerator = APIbacklinksPreviewHTML;
				break;
			case 'revision':
				if (article.oldid) {
					url += 'revids=' + article.oldid;
				} else {
					url += 'titles=' + article.removeAnchor().urlString();
				}
				url +=
					'&prop=revisions|pageprops|info|images|categories&meta=wikibase&rvslots=main&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max';
				htmlGenerator = APIrevisionPreviewHTML;
				break;
		}
		pendingNavpopTask(navpop);
		var callback = function (d) {
			log('callback of API functions was hit');
			if (queryType === 'userinfo') {
				// We need to do another API request
				fetchUserGroupNames(d.data).then(function () {
					showAPIPreview(queryType, htmlGenerator(article, d, navpop), navpop.idNumber, navpop, d);
				});
				return;
			}
			showAPIPreview(queryType, htmlGenerator(article, d, navpop), navpop.idNumber, navpop, d);
		};
		var go = function () {
			getPageWithCaching(url, callback, navpop);
			return true;
		};

		if (navpop.visible || !getValueOf('popupLazyDownloads')) {
			go();
		} else {
			navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_' + queryType + '_QUERY_DATA');
		}
	}

	function linkList(list) {
		list.sort(function (x, y) {
			return x == y ? 0 : x < y ? -1 : 1;
		});
		var buf = [];
		for (var i = 0; i < list.length; ++i) {
			buf.push(
				wikiLink({
					article: new Title(list[i]),
					text: list[i].split(' ').join('&nbsp;'),
					action: 'view',
				})
			);
		}
		return buf.join(', ');
	}

	function getTimeOffset() {
		var tz = mw.user.options.get('timecorrection');

		if (tz) {
			if (tz.indexOf('|') > -1) {
				// New format
				return parseInt(tz.split('|')[1], 10);
			}
		}
		return 0;
	}

	function getTimeZone() {
		if (!pg.user.timeZone) {
			var tz = mw.user.options.get('timecorrection');
			pg.user.timeZone = 'UTC';

			if (tz) {
				var tzComponents = tz.split('|');
				if (tzComponents.length === 3 && tzComponents[0] === 'ZoneInfo') {
					pg.user.timeZone = tzComponents[2];
				} else {
					errlog('Unexpected timezone information: ' + tz);
				}
			}
		}
		return pg.user.timeZone;
	}

	/**
	 * Should we use an offset or can we use proper timezones
	 */
	function useTimeOffset() {
		if (typeof Intl.DateTimeFormat.prototype.formatToParts === 'undefined') {
			// IE 11
			return true;
		}
		var tz = mw.user.options.get('timecorrection');
		if (tz && tz.indexOf('ZoneInfo|') === -1) {
			// System| Default system time, default for users who didn't configure timezone
			// Offset| Manual defined offset by user
			return true;
		}
		return false;
	}

	/**
	 * Array of locales for the purpose of javascript locale based formatting
	 * Filters down to those supported by the browser. Empty [] === System's default locale
	 */
	function getLocales() {
		if (!pg.user.locales) {
			var userLanguage = document.querySelector('html').getAttribute('lang'); // make sure we have HTML locale
			if (getValueOf('popupLocale')) {
				userLanguage = getValueOf('popupLocale');
			} else if (userLanguage === 'en') {
				// en.wp tends to treat this as international english / unspecified
				// but we have more specific settings in user options
				if (getMWDateFormat() === 'mdy') {
					userLanguage = 'en-US';
				} else {
					userLanguage = 'en-GB';
				}
			}
			pg.user.locales = Intl.DateTimeFormat.supportedLocalesOf([userLanguage, navigator.language]);
		}
		return pg.user.locales;
	}

	/**
	 * Retrieve configured MW date format for this user
	 * These can be
	 * default
	 * dmy: time, dmy
	 * mdy: time, mdy
	 * ymd: time, ymd
	 * dmyt: dmy, time
	 * dmyts: dmy, time + seconds
	 * ISO 8601: YYYY-MM-DDThh:mm:ss (local time)
	 *
	 * This isn't too useful for us, as JS doesn't have formatters to match these private specifiers
	 */
	function getMWDateFormat() {
		return mw.user.options.get('date');
	}

	/**
	 * Creates a HTML table that's shown in the history and user-contribs popups.
	 * @param {Object[]} h - a list of revisions, returned from the API
	 * @param {boolean} reallyContribs - true only if we're displaying user contributions
	 */
	function editPreviewTable(article, h, reallyContribs) {
		var html = ['<table>'];
		var day = null;
		var curart = article;
		var page = null;

		var makeFirstColumnLinks;
		if (reallyContribs) {
			// We're showing user contributions, so make (diff | hist) links
			makeFirstColumnLinks = function (currentRevision) {
				var result = '(';
				result +=
					'<a href="' +
					pg.wiki.titlebase +
					new Title(currentRevision.title).urlString() +
					'&diff=prev' +
					'&oldid=' +
					currentRevision.revid +
					'">' +
					popupString('diff') +
					'</a>';
				result += '&nbsp;|&nbsp;';
				result +=
					'<a href="' +
					pg.wiki.titlebase +
					new Title(currentRevision.title).urlString() +
					'&action=history">' +
					popupString('hist') +
					'</a>';
				result += ')';
				return result;
			};
		} else {
			// It's a regular history page, so make (cur | last) links
			var firstRevid = h[0].revid;
			makeFirstColumnLinks = function (currentRevision) {
				var result = '(';
				result +=
					'<a href="' +
					pg.wiki.titlebase +
					new Title(curart).urlString() +
					'&diff=' +
					firstRevid +
					'&oldid=' +
					currentRevision.revid +
					'">' +
					popupString('cur') +
					'</a>';
				result += '&nbsp;|&nbsp;';
				result +=
					'<a href="' +
					pg.wiki.titlebase +
					new Title(curart).urlString() +
					'&diff=prev&oldid=' +
					currentRevision.revid +
					'">' +
					popupString('last') +
					'</a>';
				result += ')';
				return result;
			};
		}

		for (var i = 0; i < h.length; ++i) {
			if (reallyContribs) {
				page = h[i].title;
				curart = new Title(page);
			}
			var minor = h[i].minor ? '<b>m </b>' : '';
			var editDate = new Date(h[i].timestamp);
			var thisDay = formattedDate(editDate);
			var thisTime = formattedTime(editDate);
			if (thisDay == day) {
				thisDay = '';
			} else {
				day = thisDay;
			}
			if (thisDay) {
				html.push(
					'<tr><td colspan=3><span class="popup_history_date">' + thisDay + '</span></td></tr>'
				);
			}
			html.push('<tr class="popup_history_row_' + (i % 2 ? 'odd' : 'even') + '">');
			html.push('<td>' + makeFirstColumnLinks(h[i]) + '</td>');
			html.push(
				'<td>' +
					'<a href="' +
					pg.wiki.titlebase +
					new Title(curart).urlString() +
					'&oldid=' +
					h[i].revid +
					'">' +
					thisTime +
					'</a></td>'
			);
			var col3url = '',
				col3txt = '';
			if (!reallyContribs) {
				var user = h[i].user;
				if (!h[i].userhidden) {
					if (pg.re.ipUser.test(user)) {
						col3url =
							pg.wiki.titlebase +
							mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] +
							':Contributions&target=' +
							new Title(user).urlString();
					} else {
						col3url =
							pg.wiki.titlebase +
							mw.config.get('wgFormattedNamespaces')[pg.nsUserId] +
							':' +
							new Title(user).urlString();
					}
					col3txt = pg.escapeQuotesHTML(user);
				} else {
					col3url = getValueOf('popupRevDelUrl');
					col3txt = pg.escapeQuotesHTML(popupString('revdel'));
				}
			} else {
				col3url = pg.wiki.titlebase + curart.urlString();
				col3txt = pg.escapeQuotesHTML(page);
			}
			html.push(
				'<td>' +
					(reallyContribs ? minor : '') +
					'<a href="' +
					col3url +
					'">' +
					col3txt +
					'</a></td>'
			);
			var comment = '';
			var c = h[i].comment || ( typeof h[i].slots !== 'undefined' ? h[i].slots.main.content : null );
			if (c) {
				comment = new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview();
			} else if (h[i].commenthidden) {
				comment = popupString('revdel');
			}
			html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>');
			html.push('</tr>');
			html = [html.join('')];
		}
		html.push('</table>');
		return html.join('');
	}

	function adjustDate(d, offset) {
		// offset is in minutes
		var o = offset * 60 * 1000;
		return new Date(Number(d) + o);
	}

	/**
	 * This relies on the Date parser understanding en-US dates,
	 * which is pretty safe assumption, but not perfect.
	 */
	function convertTimeZone(date, timeZone) {
		return new Date(date.toLocaleString('en-US', { timeZone: timeZone }));
	}

	function formattedDateTime(date) {
		// fallback for IE11 and unknown timezones
		if (useTimeOffset()) {
			return formattedDate(date) + ' ' + formattedTime(date);
		}

		if (getMWDateFormat() === 'ISO 8601') {
			var d2 = convertTimeZone(date, getTimeZone());
			return (
				map(zeroFill, [d2.getFullYear(), d2.getMonth() + 1, d2.getDate()]).join('-') +
				'T' +
				map(zeroFill, [d2.getHours(), d2.getMinutes(), d2.getSeconds()]).join(':')
			);
		}

		var options = getValueOf('popupDateTimeFormatterOptions');
		options['timeZone'] = getTimeZone();
		return date.toLocaleString(getLocales(), options);
	}

	function formattedDate(date) {
		// fallback for IE11 and unknown timezones
		if (useTimeOffset()) {
			// we adjust the UTC time, so we print the adjusted UTC, but not really UTC values
			var d2 = adjustDate(date, getTimeOffset());
			return map(zeroFill, [d2.getUTCFullYear(), d2.getUTCMonth() + 1, d2.getUTCDate()]).join('-');
		}

		if (getMWDateFormat() === 'ISO 8601') {
			var d2 = convertTimeZone(date, getTimeZone());
			return map(zeroFill, [d2.getFullYear(), d2.getMonth() + 1, d2.getDate()]).join('-');
		}

		var options = getValueOf('popupDateFormatterOptions');
		options['timeZone'] = getTimeZone();
		return date.toLocaleDateString(getLocales(), options);
	}

	function formattedTime(date) {
		// fallback for IE11 and unknown timezones
		if (useTimeOffset()) {
			// we adjust the UTC time, so we print the adjusted UTC, but not really UTC values
			var d2 = adjustDate(date, getTimeOffset());
			return map(zeroFill, [d2.getUTCHours(), d2.getUTCMinutes(), d2.getUTCSeconds()]).join(':');
		}

		if (getMWDateFormat() === 'ISO 8601') {
			var d2 = convertTimeZone(date, getTimeZone());
			return map(zeroFill, [d2.getHours(), d2.getMinutes(), d2.getSeconds()]).join(':');
		}

		var options = getValueOf('popupTimeFormatterOptions');
		options['timeZone'] = getTimeZone();
		return date.toLocaleTimeString(getLocales(), options);
	}

	// Get the proper groupnames for the technicalgroups
	function fetchUserGroupNames(userinfoResponse) {
		var queryObj = getJsObj(userinfoResponse).query;
		var user = anyChild(queryObj.users);
		var messages = [];
		if (user.groups) {
			user.groups.forEach(function (groupName) {
				if (['*', 'user', 'autoconfirmed', 'extendedconfirmed', 'named'].indexOf(groupName) === -1) {
					messages.push('group-' + groupName + '-member');
				}
			});
		}
		if (queryObj.globaluserinfo && queryObj.globaluserinfo.groups) {
			queryObj.globaluserinfo.groups.forEach(function (groupName) {
				messages.push('group-' + groupName + '-member');
			});
		}
		return getMwApi().loadMessagesIfMissing(messages);
	}

	function showAPIPreview(queryType, html, id, navpop, download) {
		// DJ: done
		var target = 'popupPreview';
		completedNavpopTask(navpop);

		switch (queryType) {
			case 'imagelinks':
			case 'category':
				target = 'popupPostPreview';
				break;
			case 'userinfo':
				target = 'popupUserData';
				break;
			case 'revision':
				insertPreview(download);
				return;
		}
		setPopupTipsAndHTML(html, target, id);
	}

	function APIrevisionPreviewHTML(article, download) {
		try {
			var jsObj = getJsObj(download.data);
			var page = anyChild(jsObj.query.pages);
			if (page.missing) {
				// TODO we need to fix this proper later on
				download.owner = null;
				return;
			}
			var content =
				page && page.revisions && page.revisions[0] &&
				page.revisions[0].slots && page.revisions[0].slots.main &&
				page.revisions[0].slots.main.contentmodel === 'wikitext'
					? page.revisions[0].slots.main.content
					: null;
			if (typeof content === 'string') {
				download.data = content;
				download.lastModified = new Date(page.revisions[0].timestamp);
			}
			if (page.pageprops.wikibase_item) {
				download.wikibaseItem = page.pageprops.wikibase_item;
				download.wikibaseRepo = jsObj.query.wikibase.repo.url.base
										+ jsObj.query.wikibase.repo.url.articlepath;
			}
		} catch (someError) {
			return 'Revision preview failed :(';
		}
	}

	function APIbacklinksPreviewHTML(article, download /*, navpop*/) {
		try {
			var jsObj = getJsObj(download.data);
			var list = jsObj.query.backlinks;

			var html = [];
			if (!list) {
				return popupString('No backlinks found');
			}
			for (var i = 0; i < list.length; i++) {
				var t = new Title(list[i].title);
				html.push(
					'<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t.toString().entify() + '</a>'
				);
			}
			html = html.join(', ');
			if (jsObj['continue'] && jsObj['continue'].blcontinue) {
				html += popupString(' and more');
			}
			return html;
		} catch (someError) {
			return 'backlinksPreviewHTML went wonky';
		}
	}

	pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) {
		log('APIsharedImagePagePreviewHTML');
		var popupid = obj.requestid;
		if (obj.query && obj.query.pages) {
			var page = anyChild(obj.query.pages);
			var content =
				page && page.revisions && page.revisions[0] &&
				page.revisions[0].slots && page.revisions[0].slots.main &&
				page.revisions[0].slots.main.contentmodel === 'wikitext'
					? page.revisions[0].slots.main.content
					: null;
			if (
				typeof content === 'string' &&
				pg &&
				pg.current &&
				pg.current.link &&
				pg.current.link.navpopup
			) {
				/* Not entirely safe, but the best we can do */
				var p = new Previewmaker(
					content,
					pg.current.link.navpopup.article,
					pg.current.link.navpopup
				);
				p.makePreview();
				setPopupHTML(p.html, 'popupSecondPreview', popupid);
			}
		}
	};

	function APIimagepagePreviewHTML(article, download, navpop) {
		try {
			var jsObj = getJsObj(download.data);
			var page = anyChild(jsObj.query.pages);
			var content =
				page && page.revisions && page.revisions[0] &&
				page.revisions[0].slots && page.revisions[0].slots.main &&
				page.revisions[0].slots.main.contentmodel === 'wikitext'
					? page.revisions[0].slots.main.content
					: null;
			var ret = '';
			var alt = '';
			try {
				alt = navpop.parentAnchor.childNodes[0].alt;
			} catch (e) {}
			if (alt) {
				ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt);
			}
			if (typeof content === 'string') {
				var p = prepPreviewmaker(content, article, navpop);
				p.makePreview();
				if (p.html) {
					ret += '<hr />' + p.html;
				}
				if (getValueOf('popupSummaryData')) {
					var info = getPageInfo(content, download);
					log(info);
					setPopupTrailer(info, navpop.idNumber);
				}
			}
			if (page && page.imagerepository == 'shared') {
				var art = new Title(article);
				var encart = encodeURIComponent('File:' + art.stripNamespace());
				var shared_url =
					pg.wiki.apicommonsbase +
					'?format=json&formatversion=2' +
					'&callback=pg.fn.APIsharedImagePagePreviewHTML' +
					'&requestid=' +
					navpop.idNumber +
					'&action=query&prop=revisions&rvslots=main&rvprop=content&titles=' +
					encart;

				ret =
					ret +
					'<hr />' +
					popupString('Image from Commons') +
					': <a href="' +
					pg.wiki.commonsbase +
					'?title=' +
					encart +
					'">' +
					popupString('Description page') +
					'</a>';
				mw.loader.load(shared_url);
			}
			showAPIPreview(
				'imagelinks',
				APIimagelinksPreviewHTML(article, download),
				navpop.idNumber,
				download
			);
			return ret;
		} catch (someError) {
			return 'API imagepage preview failed :(';
		}
	}

	function APIimagelinksPreviewHTML(article, download) {
		try {
			var jsobj = getJsObj(download.data);
			var list = jsobj.query.imageusage;
			if (list) {
				var ret = [];
				for (var i = 0; i < list.length; i++) {
					ret.push(list[i].title);
				}
				if (ret.length === 0) {
					return popupString('No image links found');
				}
				return '<h2>' + popupString('File links') + '</h2>' + linkList(ret);
			} else {
				return popupString('No image links found');
			}
		} catch (someError) {
			return 'Image links preview generation failed :(';
		}
	}

	function APIcategoryPreviewHTML(article, download) {
		try {
			var jsobj = getJsObj(download.data);
			var list = jsobj.query.categorymembers;
			var ret = [];
			for (var p = 0; p < list.length; p++) {
				ret.push(list[p].title);
			}
			if (ret.length === 0) {
				return popupString('Empty category');
			}
			ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' + linkList(ret);
			if (jsobj['continue'] && jsobj['continue'].cmcontinue) {
				ret += popupString(' and more');
			}
			return ret;
		} catch (someError) {
			return 'Category preview failed :(';
		}
	}

	function APIuserInfoPreviewHTML(article, download) {
		var ret = [];
		var queryobj = {};
		try {
			queryobj = getJsObj(download.data).query;
		} catch (someError) {
			return 'Userinfo preview failed :(';
		}

		var user = anyChild(queryobj.users);
		if (user) {
			var globaluserinfo = queryobj.globaluserinfo;
			if (user.invalid === '') {
				ret.push(popupString('Invalid user'));
			} else if (user.missing === '') {
				ret.push(popupString('Not a registered username'));
			}
			if (user.blockedby) {
				if (user.blockpartial) {
					ret.push('<b>' + popupString('Has blocks') + '</b>');
				} else {
					ret.push('<b>' + popupString('BLOCKED') + '</b>');
				}
			}
			if (globaluserinfo && ('locked' in globaluserinfo || 'hidden' in globaluserinfo)) {
				var lockedSulAccountIsAttachedToThis = true;
				for (var i = 0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) {
					if (globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname')) {
						lockedSulAccountIsAttachedToThis = false;
						break;
					}
				}
				if (lockedSulAccountIsAttachedToThis) {
					if ('locked' in globaluserinfo) {
						ret.push('<b><i>' + popupString('LOCKED') + '</i></b>');
					}
					if ('hidden' in globaluserinfo) {
						ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>');
					}
				}
			}
			if (getValueOf('popupShowGender') && user.gender) {
				switch (user.gender) {
					case 'male':
						ret.push(popupString('he/him') + ' · ');
						break;
					case 'female':
						ret.push(popupString('she/her') + ' · ');
						break;
				}
			}
			if (user.groups) {
				user.groups.forEach(function (groupName) {
					if (['*', 'user', 'autoconfirmed', 'extendedconfirmed', 'named'].indexOf(groupName) === -1) {
						ret.push(
							pg.escapeQuotesHTML(mw.message('group-' + groupName + '-member', user.gender).text())
						);
					}
				});
			}
			if (globaluserinfo && globaluserinfo.groups) {
				globaluserinfo.groups.forEach(function (groupName) {
					ret.push(
						'<i>' +
							pg.escapeQuotesHTML(
								mw.message('group-' + groupName + '-member', user.gender).text()
							) +
							'</i>'
					);
				});
			}
			if (user.registration) {
				ret.push(
					pg.escapeQuotesHTML(
						(user.editcount ? user.editcount : '0') +
							popupString(' edits since: ') +
							(user.registration ? formattedDate(new Date(user.registration)) : '')
					)
				);
			}
		}

		if (queryobj.usercontribs && queryobj.usercontribs.length) {
			ret.push(
				popupString('last edit on ') + formattedDate(new Date(queryobj.usercontribs[0].timestamp))
			);
		}

		if (queryobj.blocks) {
			ret.push(popupString('IP user')); //we only request list=blocks for IPs
			for (var l = 0; l < queryobj.blocks.length; l++) {
				var rbstr =
					queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK';
				rbstr = !Array.isArray(queryobj.blocks[l].restrictions)
					? 'Has ' + rbstr.toLowerCase() + 's'
					: rbstr + 'ED';
				ret.push('<b>' + popupString(rbstr) + '</b>');
			}
		}

		// if any element of ret ends with ' · ', merge it with the next element to avoid
		// the .join(', ') call inserting a comma after it
		for (var m = 0; m < ret.length - 1; m++) {
			if (ret[m].length > 3 && ret[m].substring(ret[m].length - 3) === ' · ') {
				ret[m] = ret[m] + ret[m + 1];
				ret.splice(m + 1, 1); // delete element at index m+1
				m--;
			}
		}

		ret = '<hr />' + ret.join(', ');
		return ret;
	}

	function APIcontribsPreviewHTML(article, download, navpop) {
		return APIhistoryPreviewHTML(article, download, navpop, true);
	}

	function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) {
		try {
			var jsobj = getJsObj(download.data);
			var edits = [];
			if (reallyContribs) {
				edits = jsobj.query.usercontribs;
			} else {
				edits = anyChild(jsobj.query.pages).revisions;
			}

			var ret = editPreviewTable(article, edits, reallyContribs);
			return ret;
		} catch (someError) {
			return 'History preview failed :-(';
		}
	}

	// ENDFILE: querypreview.js

	// STARTFILE: debug.js
	////////////////////////////////////////////////////////////////////
	// Debugging functions
	////////////////////////////////////////////////////////////////////

	function setupDebugging() {
		if (window.popupDebug) {
			// popupDebug is set from .version
			window.log = function (x) {
				//if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; };
				window.console.log(x);
			};
			window.errlog = function (x) {
				window.console.error(x);
			};
			log('Initializing logger');
		} else {
			window.log = function () {};
			window.errlog = function () {};
		}
	}
	// ENDFILE: debug.js

	// STARTFILE: images.js

	// load image of type Title.
	function loadImage(image, navpop) {
		if (typeof image.stripNamespace != 'function') {
			alert('loadImages bad');
		}
		// API call to retrieve image info.

		if (!getValueOf('popupImages')) {
			return;
		}
		if (!isValidImageName(image)) {
			return false;
		}

		var art = image.urlString();

		var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query';
		url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge');
		url += '&titles=' + art;

		pendingNavpopTask(navpop);
		var callback = function (d) {
			popupsInsertImage(navpop.idNumber, navpop, d);
		};
		var go = function () {
			getPageWithCaching(url, callback, navpop);
			return true;
		};
		if (navpop.visible || !getValueOf('popupLazyDownloads')) {
			go();
		} else {
			navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA');
		}
	}

	function popupsInsertImage(id, navpop, download) {
		log('popupsInsertImage');
		var imageinfo;
		try {
			var jsObj = getJsObj(download.data);
			var imagepage = anyChild(jsObj.query.pages);
			if (typeof imagepage.imageinfo === 'undefined') {
				return;
			}
			imageinfo = imagepage.imageinfo[0];
		} catch (someError) {
			log('popupsInsertImage failed :(');
			return;
		}

		var popupImage = document.getElementById('popupImg' + id);
		if (!popupImage) {
			log('could not find insertion point for image');
			return;
		}

		popupImage.width = getValueOf('popupImageSize');
		popupImage.style.display = 'inline';

		// Set the source for the image.
		if (imageinfo.thumburl) {
			popupImage.src = imageinfo.thumburl;
		} else if (imageinfo.mime.indexOf('image') === 0) {
			popupImage.src = imageinfo.url;
			log('a thumb could not be found, using original image');
		} else {
			log("fullsize imagethumb, but not sure if it's an image");
		}

		var a = document.getElementById('popupImageLink' + id);
		if (a === null) {
			return null;
		}

		// Determine the action of the surrouding imagelink.
		switch (getValueOf('popupThumbAction')) {
			case 'imagepage':
				if (pg.current.article.namespaceId() != pg.nsImageId) {
					a.href = imageinfo.descriptionurl;
					// FIXME: unreliable pg.idNumber
					popTipsSoonFn('popupImage' + id)();
					break;
				}
			/* falls through */
			case 'sizetoggle':
				a.onclick = toggleSize;
				a.title = popupString('Toggle image size');
				return;
			case 'linkfull':
				a.href = imageinfo.url;
				a.title = popupString('Open full-size image');
				return;
		}
	}

	// Toggles the image between inline small and navpop fullwidth.
	// It's the same image, no actual sizechange occurs, only display width.
	function toggleSize() {
		var imgContainer = this;
		if (!imgContainer) {
			alert('imgContainer is null :/');
			return;
		}
		var img = imgContainer.firstChild;
		if (!img) {
			alert('img is null :/');
			return;
		}

		if (!img.style.width || img.style.width === '') {
			img.style.width = '100%';
		} else {
			img.style.width = '';
		}
	}

	// Returns one title of an image from wikiText.
	function getValidImageFromWikiText(wikiText) {
		// nb in pg.re.image we're interested in the second bracketed expression
		// this may change if the regex changes :-(
		//var match=pg.re.image.exec(wikiText);
		var matched = null;
		var match;
		// strip html comments, used by evil bots :-(
		var t = removeMatchesUnless(
			wikiText,
			RegExp('(<!--[\\s\\S]*?-->)'),
			1,
			RegExp('^<!--[^[]*popup', 'i')
		);

		while ((match = pg.re.image.exec(t))) {
			// now find a sane image name - exclude templates by seeking {
			var m = match[2] || match[6];
			if (isValidImageName(m)) {
				matched = m;
				break;
			}
		}
		pg.re.image.lastIndex = 0;
		if (!matched) {
			return null;
		}
		return mw.config.get('wgFormattedNamespaces')[pg.nsImageId] + ':' + upcaseFirst(matched);
	}

	function removeMatchesUnless(str, re1, parencount, re2) {
		var split = str.parenSplit(re1);
		var c = parencount + 1;
		for (var i = 0; i < split.length; ++i) {
			if (i % c === 0 || re2.test(split[i])) {
				continue;
			}
			split[i] = '';
		}
		return split.join('');
	}

	// ENDFILE: images.js

	// STARTFILE: namespaces.js
	// Set up namespaces and other non-strings.js localization
	// (currently that means redirs too)

	function setNamespaces() {
		pg.nsSpecialId = -1;
		pg.nsMainspaceId = 0;
		pg.nsImageId = 6;
		pg.nsUserId = 2;
		pg.nsUsertalkId = 3;
		pg.nsCategoryId = 14;
		pg.nsTemplateId = 10;
	}

	function setRedirs() {
		var r = 'redirect';
		var R = 'REDIRECT';
		var redirLists = {
			ar: [R, 'تحويل'],
			be: [r, 'перанакіраваньне'],
			bg: [r, 'пренасочване', 'виж'],
			bs: [r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI'],
			bn: [R, 'পুনর্নির্দেশ'],
			cs: [R, 'PŘESMĚRUJ'],
			cy: [r, 'ail-cyfeirio'],
			de: [R, 'WEITERLEITUNG'],
			el: [R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'],
			eo: [R, 'ALIDIREKTU', 'ALIDIREKTI'],
			es: [R, 'REDIRECCIÓN'],
			et: [r, 'suuna'],
			ga: [r, 'athsheoladh'],
			gl: [r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'],
			he: [R, 'הפניה'],
			hu: [R, 'ÁTIRÁNYÍTÁS'],
			is: [r, 'tilvísun', 'TILVÍSUN'],
			it: [R, 'RINVIA', 'Rinvia'],
			ja: [R, '転送'],
			mk: [r, 'пренасочување', 'види'],
			nds: [r, 'wiederleiden'],
			'nds-nl': [R, 'DEURVERWIEZING', 'DUURVERWIEZING'],
			nl: [R, 'DOORVERWIJZING'],
			nn: [r, 'omdiriger'],
			pl: [R, 'PATRZ', 'PRZEKIERUJ', 'TAM'],
			pt: [R, 'redir'],
			ru: [R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР'],
			sk: [r, 'presmeruj'],
			sr: [r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI'],
			tr: [R, 'YÖNLENDİRME', 'yönlendirme', 'YÖNLENDİR', 'yönlendir'],
			tt: [R, 'yünältü', 'перенаправление', 'перенапр'],
			uk: [R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР'],
			vi: [r, 'đổi'],
			yi: [R, 'ווייטערפירן'],
			zh: [R, '重定向'], // no comma
		};
		var redirList = redirLists[pg.wiki.lang] || [r, R];
		// Mediawiki is very tolerant about what comes after the #redirect at the start
		pg.re.redirect = RegExp(
			'^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)',
			'i'
		);
	}

	function setInterwiki() {
		if (pg.wiki.wikimedia) {
			// From https://meta.wikimedia.org/wiki/List_of_Wikipedias
			//en.wikipedia.org/w/api.php?action=sitematrix&format=json&smtype=language&smlangprop=code&formatversion=2
			pg.wiki.interwiki =
				'aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu';
			pg.re.interwiki = RegExp('^' + pg.wiki.interwiki + ':');
		} else {
			pg.wiki.interwiki = null;
			pg.re.interwiki = RegExp('^$');
		}
	}

	// return a regexp pattern matching all variants to write the given namespace
	function nsRe(namespaceId) {
		var imageNamespaceVariants = [];
		jQuery.each(mw.config.get('wgNamespaceIds'), function (_localizedNamespaceLc, _namespaceId) {
			if (_namespaceId != namespaceId) {
				return;
			}
			_localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc);
			imageNamespaceVariants.push(
				mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]')
			);
			imageNamespaceVariants.push(mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc)));
		});

		return '(?:' + imageNamespaceVariants.join('|') + ')';
	}

	function nsReImage() {
		return nsRe(pg.nsImageId);
	}
	// ENDFILE: namespaces.js

	// STARTFILE: selpop.js
	function getEditboxSelection() {
		// see http://www.webgurusforum.com/8/12/0
		var editbox;
		try {
			editbox = document.editform.wpTextbox1;
		} catch (dang) {
			return;
		}
		// IE, Opera
		if (document.selection) {
			return document.selection.createRange().text;
		}
		// Mozilla
		var selStart = editbox.selectionStart;
		var selEnd = editbox.selectionEnd;
		return editbox.value.substring(selStart, selEnd);
	}

	function doSelectionPopup() {
		// popup if the selection looks like [[foo|anything afterwards at all
		// or [[foo|bar]]text without ']]'
		// or [[foo|bar]]
		var sel = getEditboxSelection();
		var open = sel.indexOf('[[');
		var pipe = sel.indexOf('|');
		var close = sel.indexOf(']]');
		if (open == -1 || (pipe == -1 && close == -1)) {
			return;
		}
		if ((pipe != -1 && open > pipe) || (close != -1 && open > close)) {
			return;
		}
		var article = new Title(sel.substring(open + 2, pipe < 0 ? close : pipe));
		if (getValueOf('popupOnEditSelection') == 'boxpreview') {
			return doSeparateSelectionPopup(sel, article);
		}
		if (close > 0 && sel.substring(close + 2).indexOf('[[') >= 0) {
			return;
		}
		var a = document.createElement('a');
		a.href = pg.wiki.titlebase + article.urlString();
		mouseOverWikiLink2(a);
		if (a.navpopup) {
			a.navpopup.addHook(
				function () {
					runStopPopupTimer(a.navpopup);
				},
				'unhide',
				'after'
			);
		}
	}

	function doSeparateSelectionPopup(str, article) {
		var div = document.getElementById('selectionPreview');
		if (!div) {
			div = document.createElement('div');
			div.id = 'selectionPreview';
			try {
				var box = document.editform.wpTextbox1;
				box.parentNode.insertBefore(div, box);
			} catch (error) {
				return;
			}
		}
		var p = prepPreviewmaker(str, article, newNavpopup(document.createElement('a'), article));
		p.makePreview();
		if (p.html) {
			div.innerHTML = p.html;
		}
		div.ranSetupTooltipsAlready = false;
		popTipsSoonFn('selectionPreview')();
	}
	// ENDFILE: selpop.js

	// STARTFILE: navpopup.js
	/**
	 * @fileoverview  Defines two classes: {@link Navpopup} and {@link Mousetracker}.
	 *
	 * <code>Navpopup</code> describes popups: when they appear, where, what
	 * they look like and so on.
	 *
	 * <code>Mousetracker</code> "captures" the mouse using
	 * <code>document.onmousemove</code>.
	 */

	/**
	 * Creates a new Mousetracker.
	 * @constructor
	 * @class The Mousetracker class. This monitors mouse movements and manages associated hooks.
	 */
	function Mousetracker() {
		/**
		 * Interval to regularly run the hooks anyway, in milliseconds.
		 * @type {number}
		 */
		this.loopDelay = 400;

		/**
		 * Timer for the loop.
		 * @type Timer
		 */
		this.timer = null;

		/**
		 * Flag - are we switched on?
		 * @type {boolean}
		 */
		this.active = false;

		/**
		 * Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position?
		 */
		this.dirty = true;

		/**
		 * Array of hook functions.
		 * @private
		 * @type {Array}
		 */
		this.hooks = [];
	}

	/**
	 * Adds a hook, to be called when we get events.
	 * @param {Function} f A function which is called as
	 * <code>f(x,y)</code>. It should return <code>true</code> when it
	 * wants to be removed, and <code>false</code> otherwise.
	 */
	Mousetracker.prototype.addHook = function (f) {
		this.hooks.push(f);
	};

	/**
	 * Runs hooks, passing them the x
	 * and y coords of the mouse.  Hook functions that return true are
	 * passed to {@link Mousetracker#removeHooks} for removal.
	 * @private
	 */
	Mousetracker.prototype.runHooks = function () {
		if (!this.hooks || !this.hooks.length) {
			return;
		}
		//log('Mousetracker.runHooks; we got some hooks to run');
		var remove = false;
		var removeObj = {};
		// this method gets called a LOT -
		// pre-cache some variables
		var x = this.x,
			y = this.y,
			len = this.hooks.length;

		for (var i = 0; i < len; ++i) {
			//~ run the hook function, and remove it if it returns true
			if (this.hooks[i](x, y) === true) {
				remove = true;
				removeObj[i] = true;
			}
		}
		if (remove) {
			this.removeHooks(removeObj);
		}
	};

	/**
	 * Removes hooks.
	 * @private
	 * @param {Object} removeObj An object whose keys are the index
	 * numbers of functions for removal, with values that evaluate to true
	 */
	Mousetracker.prototype.removeHooks = function (removeObj) {
		var newHooks = [];
		var len = this.hooks.length;
		for (var i = 0; i < len; ++i) {
			if (!removeObj[i]) {
				newHooks.push(this.hooks[i]);
			}
		}
		this.hooks = newHooks;
	};

	/**
	 * Event handler for mouse wiggles.
	 * We simply grab the event, set x and y and run the hooks.
	 * This makes the cpu all hot and bothered :-(
	 * @private
	 * @param {Event} e Mousemove event
	 */
	Mousetracker.prototype.track = function (e) {
		//~ Apparently this is needed in IE.
		e = e || window.event;
		var x, y;
		if (e) {
			if (e.pageX) {
				x = e.pageX;
				y = e.pageY;
			} else if (typeof e.clientX != 'undefined') {
				var left,
					top,
					docElt = document.documentElement;

				if (docElt) {
					left = docElt.scrollLeft;
				}
				left = left || document.body.scrollLeft || document.scrollLeft || 0;

				if (docElt) {
					top = docElt.scrollTop;
				}
				top = top || document.body.scrollTop || document.scrollTop || 0;

				x = e.clientX + left;
				y = e.clientY + top;
			} else {
				return;
			}
			this.setPosition(x, y);
		}
	};

	/**
	 * Sets the x and y coordinates stored and takes appropriate action,
	 * running hooks as appropriate.
	 * @param {number} x, y Screen coordinates to set
	 */
	Mousetracker.prototype.setPosition = function (x, y) {
		this.x = x;
		this.y = y;
		if (this.dirty || this.hooks.length === 0) {
			this.dirty = false;
			return;
		}
		if (typeof this.lastHook_x != 'number') {
			this.lastHook_x = -100;
			this.lastHook_y = -100;
		}
		var diff = (this.lastHook_x - x) * (this.lastHook_y - y);
		diff = diff >= 0 ? diff : -diff;
		if (diff > 1) {
			this.lastHook_x = x;
			this.lastHook_y = y;
			if (this.dirty) {
				this.dirty = false;
			} else {
				this.runHooks();
			}
		}
	};

	/**
	 * Sets things in motion, unless they are already that is, registering an event handler on
	 * <code>document.onmousemove</code>. A half-hearted attempt is made to preserve the old event
	 * handler if there is one.
	 */
	Mousetracker.prototype.enable = function () {
		if (this.active) {
			return;
		}
		this.active = true;
		//~ Save the current handler for mousemove events. This isn't too
		//~ robust, of course.
		this.savedHandler = document.onmousemove;
		//~ Gotta save @tt{this} again for the closure, and use apply for
		//~ the member function.
		var savedThis = this;
		document.onmousemove = function (e) {
			savedThis.track.apply(savedThis, [e]);
		};
		if (this.loopDelay) {
			this.timer = setInterval(function () {
				//log('loop delay in mousetracker is working');
				savedThis.runHooks();
			}, this.loopDelay);
		}
	};

	/**
	 * Disables the tracker, removing the event handler.
	 */
	Mousetracker.prototype.disable = function () {
		if (!this.active) {
			return;
		}
		if (typeof this.savedHandler === 'function') {
			document.onmousemove = this.savedHandler;
		} else {
			delete document.onmousemove;
		}
		if (this.timer) {
			clearInterval(this.timer);
		}
		this.active = false;
	};

	/**
	 * Creates a new Navpopup.
	 * Gets a UID for the popup and
	 * @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable.
	 * @constructor
	 * @class The Navpopup class. This generates popup hints, and does some management of them.
	 */
	function Navpopup(/*init*/) {
		//alert('new Navpopup(init)');

		/**
		 * UID for each Navpopup instance.
		 * Read-only.
		 * @type {number}
		 */
		this.uid = Navpopup.uid++;

		/**
		 * Read-only flag for current visibility of the popup.
		 * @type {boolean}
		 * @private
		 */
		this.visible = false;

		/** Flag to be set when we want to cancel a previous request to
		 * show the popup in a little while.
		 * @private
		 * @type {boolean}
		 */
		this.noshow = false;

		/** Categorised list of hooks.
		 * @see #runHooks
		 * @see #addHook
		 * @private
		 * @type {Object}
		 */
		this.hooks = {
			create: [],
			unhide: [],
			hide: [],
		};

		/**
		 * list of unique IDs of hook functions, to avoid duplicates
		 * @private
		 */
		this.hookIds = {};

		/** List of downloads associated with the popup.
		 * @private
		 * @type {Array}
		 */
		this.downloads = [];

		/**
		 * Number of uncompleted downloads.
		 * @type {number}
		 */
		this.pending = null;

		/**
		 * Tolerance in pixels when detecting whether the mouse has left the popup.
		 * @type {number}
		 */
		this.fuzz = 5;

		/**
		 * Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position.
		 * @type {boolean}
		 */
		this.constrained = true;

		/**
		 * The popup width in pixels.
		 * @private
		 * @type {number}
		 */
		this.width = 0;

		/**
		 * The popup width in pixels.
		 * @private
		 * @type {number}
		 */
		this.height = 0;

		/**
		 * The main content DIV element.
		 * @type HTMLDivElement
		 */
		this.mainDiv = null;
		this.createMainDiv();

		//	if (!init || typeof init.popups_draggable=='undefined' || init.popups_draggable) {
		//		this.makeDraggable(true);
		//	}
	}

	/**
	 * A UID for each Navpopup. This constructor property is just a counter.
	 * @type {number}
	 * @private
	 */
	Navpopup.uid = 0;

	/**
	 * Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible.
	 * @type {boolean}
	 */
	Navpopup.prototype.isVisible = function () {
		return this.visible;
	};

	/**
	 * Repositions popup using CSS style.
	 * @private
	 * @param {number} x x-coordinate (px)
	 * @param {number} y y-coordinate (px)
	 * @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition}
	 */
	Navpopup.prototype.reposition = function (x, y, noLimitHor) {
		log('reposition(' + x + ',' + y + ',' + noLimitHor + ')');
		if (typeof x != 'undefined' && x !== null) {
			this.left = x;
		}
		if (typeof y != 'undefined' && y !== null) {
			this.top = y;
		}
		if (typeof this.left != 'undefined' && typeof this.top != 'undefined') {
			this.mainDiv.style.left = this.left + 'px';
			this.mainDiv.style.top = this.top + 'px';
		}
		if (!noLimitHor) {
			this.limitHorizontalPosition();
		}
		//console.log('navpop'+this.uid+' - (left,top)=(' + this.left + ',' + this.top + '), css=('
		//+ this.mainDiv.style.left + ',' + this.mainDiv.style.top + ')');
	};

	/**
	 * Prevents popups from being in silly locations. Hopefully.
	 * Should not be run if {@link #constrained} is true.
	 * @private
	 */
	Navpopup.prototype.limitHorizontalPosition = function () {
		if (!this.constrained || this.tooWide) {
			return;
		}
		this.updateDimensions();
		var x = this.left;
		var w = this.width;
		var cWidth = document.body.clientWidth;

		//	log('limitHorizontalPosition: x='+x+
		//			', this.left=' + this.left +
		//			', this.width=' + this.width +
		//			', cWidth=' + cWidth);

		if (
			x + w >= cWidth ||
			(x > 0 &&
				this.maxWidth &&
				this.width < this.maxWidth &&
				this.height > this.width &&
				x > cWidth - this.maxWidth)
		) {
			// This is a very nasty hack. There has to be a better way!
			// We find the "natural" width of the div by positioning it at the far left
			// then reset it so that it should be flush right (well, nearly)
			this.mainDiv.style.left = '-10000px';
			this.mainDiv.style.width = this.maxWidth + 'px';
			var naturalWidth = parseInt(this.mainDiv.offsetWidth, 10);
			var newLeft = cWidth - naturalWidth - 1;
			if (newLeft < 0) {
				newLeft = 0;
				this.tooWide = true;
			} // still unstable for really wide popups?
			log(
				'limitHorizontalPosition: moving to (' +
					newLeft +
					',' +
					this.top +
					');' +
					' naturalWidth=' +
					naturalWidth +
					', clientWidth=' +
					cWidth
			);
			this.reposition(newLeft, null, true);
		}
	};

	/**
	 * Counter indicating the z-order of the "highest" popup.
	 * We start the z-index at 1000 so that popups are above everything
	 * else on the screen.
	 * @private
	 * @type {number}
	 */
	Navpopup.highest = 1000;

	/**
	 * Brings popup to the top of the z-order.
	 * We increment the {@link #highest} property of the contructor here.
	 * @private
	 */
	Navpopup.prototype.raise = function () {
		this.mainDiv.style.zIndex = Navpopup.highest + 1;
		++Navpopup.highest;
	};

	/**
	 * Shows the popup provided {@link #noshow} is not true.
	 * Updates the position, brings the popup to the top of the z-order and unhides it.
	 */
	Navpopup.prototype.show = function () {
		//document.title+='s';
		if (this.noshow) {
			return;
		}
		//document.title+='t';
		this.reposition();
		this.raise();
		this.unhide();
	};

	/**
	 * Checks to see if the mouse pointer has
	 * stabilised (checking every <code>time</code>/2 milliseconds) and runs the
	 * {@link #show} method if it has.
	 * @param {number} time The minimum time (ms) before the popup may be shown.
	 */
	Navpopup.prototype.showSoonIfStable = function (time) {
		log('showSoonIfStable, time=' + time);
		if (this.visible) {
			return;
		}
		this.noshow = false;

		//~ initialize these variables so that we never run @tt{show} after
		//~ just half the time
		this.stable_x = -10000;
		this.stable_y = -10000;

		var stableShow = function () {
			log('stableShow called');
			var new_x = Navpopup.tracker.x,
				new_y = Navpopup.tracker.y;
			var dx = savedThis.stable_x - new_x,
				dy = savedThis.stable_y - new_y;
			var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz;
			//document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] ';
			if (dx * dx <= fuzz2 && dy * dy <= fuzz2) {
				log('mouse is stable');
				clearInterval(savedThis.showSoonStableTimer);
				savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]);
				savedThis.show.apply(savedThis, []);
				savedThis.limitHorizontalPosition.apply(savedThis, []);
				return;
			}
			savedThis.stable_x = new_x;
			savedThis.stable_y = new_y;
		};
		var savedThis = this;
		this.showSoonStableTimer = setInterval(stableShow, time / 2);
	};

	/**
	 * Sets the {@link #noshow} flag and hides the popup. This should be called
	 * when the mouse leaves the link before
	 * (or after) it's actually been displayed.
	 */
	Navpopup.prototype.banish = function () {
		log('banish called');
		// hide and prevent showing with showSoon in the future
		this.noshow = true;
		if (this.showSoonStableTimer) {
			log('clearing showSoonStableTimer');
			clearInterval(this.showSoonStableTimer);
		}
		this.hide();
	};

	/**
	 * Runs hooks added with {@link #addHook}.
	 * @private
	 * @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
	 * @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
	 */
	Navpopup.prototype.runHooks = function (key, when) {
		if (!this.hooks[key]) {
			return;
		}
		var keyHooks = this.hooks[key];
		var len = keyHooks.length;
		for (var i = 0; i < len; ++i) {
			if (keyHooks[i] && keyHooks[i].when == when) {
				if (keyHooks[i].hook.apply(this, [])) {
					// remove the hook
					if (keyHooks[i].hookId) {
						delete this.hookIds[keyHooks[i].hookId];
					}
					keyHooks[i] = null;
				}
			}
		}
	};

	/**
	 * Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the
	 * Navpopup instance, and no arguments.
	 * @param {Function} hook The hook function. Functions that return true are deleted.
	 * @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
	 * @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
	 * @param {String} uid A truthy string identifying the hook function; if it matches another hook
	 * in this position, it won't be added again.
	 */
	Navpopup.prototype.addHook = function (hook, key, when, uid) {
		when = when || 'after';
		if (!this.hooks[key]) {
			return;
		}
		// if uid is specified, don't add duplicates
		var hookId = null;
		if (uid) {
			hookId = [key, when, uid].join('|');
			if (this.hookIds[hookId]) {
				return;
			}
			this.hookIds[hookId] = true;
		}
		this.hooks[key].push({ hook: hook, when: when, hookId: hookId });
	};

	/**
	 * Creates the main DIV element, which contains all the actual popup content.
	 * Runs hooks with key 'create'.
	 * @private
	 */
	Navpopup.prototype.createMainDiv = function () {
		if (this.mainDiv) {
			return;
		}
		this.runHooks('create', 'before');
		var mainDiv = document.createElement('div');

		var savedThis = this;
		mainDiv.onclick = function (e) {
			savedThis.onclickHandler(e);
		};
		mainDiv.className = this.className ? this.className : 'navpopup_maindiv';
		mainDiv.id = mainDiv.className + this.uid;

		mainDiv.style.position = 'absolute';
		mainDiv.style.minWidth = '350px';
		mainDiv.style.display = 'none';
		mainDiv.className = 'navpopup';

		// easy access to javascript object through DOM functions
		mainDiv.navpopup = this;

		this.mainDiv = mainDiv;
		document.body.appendChild(mainDiv);
		this.runHooks('create', 'after');
	};

	/**
	 * Calls the {@link #raise} method.
	 * @private
	 */
	Navpopup.prototype.onclickHandler = function (/*e*/) {
		this.raise();
	};

	/**
	 * Makes the popup draggable, using a {@link Drag} object.
	 * @private
	 */
	Navpopup.prototype.makeDraggable = function (handleName) {
		if (!this.mainDiv) {
			this.createMainDiv();
		}
		var drag = new Drag();
		if (!handleName) {
			drag.startCondition = function (e) {
				try {
					if (!e.shiftKey) {
						return false;
					}
				} catch (err) {
					return false;
				}
				return true;
			};
		}
		var dragHandle;
		if (handleName) {
			dragHandle = document.getElementById(handleName);
		}
		if (!dragHandle) {
			dragHandle = this.mainDiv;
		}
		var np = this;
		drag.endHook = function (x, y) {
			Navpopup.tracker.dirty = true;
			np.reposition(x, y);
		};
		drag.init(dragHandle, this.mainDiv);
	};

	/**
	 * Hides the popup using CSS. Runs hooks with key 'hide'.
	 * Sets {@link #visible} appropriately.
	 * {@link #banish} should be called externally instead of this method.
	 * @private
	 */
	Navpopup.prototype.hide = function () {
		this.runHooks('hide', 'before');
		this.abortDownloads();
		if (typeof this.visible != 'undefined' && this.visible) {
			this.mainDiv.style.display = 'none';
			this.visible = false;
		}
		this.runHooks('hide', 'after');
	};

	/**
	 * Shows the popup using CSS. Runs hooks with key 'unhide'.
	 * Sets {@link #visible} appropriately.   {@link #show} should be called externally instead of this method.
	 * @private
	 */
	Navpopup.prototype.unhide = function () {
		this.runHooks('unhide', 'before');
		if (typeof this.visible != 'undefined' && !this.visible) {
			this.mainDiv.style.display = 'inline';
			this.visible = true;
		}
		this.runHooks('unhide', 'after');
	};

	/**
	 * Sets the <code>innerHTML</code> attribute of the main div containing the popup content.
	 * @param {String} html The HTML to set.
	 */
	Navpopup.prototype.setInnerHTML = function (html) {
		this.mainDiv.innerHTML = html;
	};

	/**
	 * Updates the {@link #width} and {@link #height} attributes with the CSS properties.
	 * @private
	 */
	Navpopup.prototype.updateDimensions = function () {
		this.width = parseInt(this.mainDiv.offsetWidth, 10);
		this.height = parseInt(this.mainDiv.offsetHeight, 10);
	};

	/**
	 * Checks if the point (x,y) is within {@link #fuzz} of the
	 * {@link #mainDiv}.
	 * @param {number} x x-coordinate (px)
	 * @param {number} y y-coordinate (px)
	 * @type {boolean}
	 */
	Navpopup.prototype.isWithin = function (x, y) {
		//~ If we're not even visible, no point should be considered as
		//~ being within the popup.
		if (!this.visible) {
			return false;
		}
		this.updateDimensions();
		var fuzz = this.fuzz || 0;
		//~ Use a simple box metric here.
		return (
			x + fuzz >= this.left &&
			x - fuzz <= this.left + this.width &&
			y + fuzz >= this.top &&
			y - fuzz <= this.top + this.height
		);
	};

	/**
	 * Adds a download to {@link #downloads}.
	 * @param {Downloader} download
	 */
	Navpopup.prototype.addDownload = function (download) {
		if (!download) {
			return;
		}
		this.downloads.push(download);
	};

	/**
	 * Aborts the downloads listed in {@link #downloads}.
	 * @see Downloader#abort
	 */
	Navpopup.prototype.abortDownloads = function () {
		for (var i = 0; i < this.downloads.length; ++i) {
			var d = this.downloads[i];
			if (d && d.abort) {
				d.abort();
			}
		}
		this.downloads = [];
	};

	/**
	 * A {@link Mousetracker} instance which is a property of the constructor (pseudo-global).
	 */
	Navpopup.tracker = new Mousetracker();
	// ENDFILE: navpopup.js

	// STARTFILE: diff.js
	/*
	 * Javascript Diff Algorithm
	 *  By John Resig (http://ejohn.org/) and [[:en:User:Lupin]]
	 *
	 * More Info:
	 *  http://ejohn.org/projects/javascript-diff-algorithm/
	 */

	function delFmt(x) {
		if (!x.length) {
			return '';
		}
		return "<del class='popupDiff'>" + x.join('') + '</del>';
	}

	function insFmt(x) {
		if (!x.length) {
			return '';
		}
		return "<ins class='popupDiff'>" + x.join('') + '</ins>';
	}

	function countCrossings(a, b, i, eject) {
		// count the crossings on the edge starting at b[i]
		if (!b[i].row && b[i].row !== 0) {
			return -1;
		}
		var count = 0;
		for (var j = 0; j < a.length; ++j) {
			if (!a[j].row && a[j].row !== 0) {
				continue;
			}
			if ((j - b[i].row) * (i - a[j].row) > 0) {
				if (eject) {
					return true;
				}
				count++;
			}
		}
		return count;
	}

	function shortenDiffString(str, context) {
		var re = RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)');
		var splitted = str.parenSplit(re);
		var ret = [''];
		for (var i = 0; i < splitted.length; i += 2) {
			if (splitted[i].length < 2 * context) {
				ret[ret.length - 1] += splitted[i];
				if (i + 1 < splitted.length) {
					ret[ret.length - 1] += splitted[i + 1];
				}
				continue;
			} else {
				if (i > 0) {
					ret[ret.length - 1] += splitted[i].substring(0, context);
				}
				if (i + 1 < splitted.length) {
					ret.push(splitted[i].substring(splitted[i].length - context) + splitted[i + 1]);
				}
			}
		}
		while (ret.length > 0 && !ret[0]) {
			ret = ret.slice(1);
		}
		return ret;
	}

	function diffString(o, n, simpleSplit) {
		var splitRe = RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)');

		//  We need to split the strings o and n first, and entify() the parts
		//  individually, so that the HTML entities are never cut apart. (AxelBoldt)
		var out, i, oSplitted, nSplitted;
		if (simpleSplit) {
			oSplitted = o.split(/\b/);
			nSplitted = n.split(/\b/);
		} else {
			oSplitted = o.parenSplit(splitRe);
			nSplitted = n.parenSplit(splitRe);
		}
		for (i = 0; i < oSplitted.length; ++i) {
			oSplitted[i] = oSplitted[i].entify();
		}
		for (i = 0; i < nSplitted.length; ++i) {
			nSplitted[i] = nSplitted[i].entify();
		}

		out = diff(oSplitted, nSplitted);
		var str = '';
		var acc = []; // accumulator for prettier output

		// crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out
		// this doesn't always do things optimally but it should be fast enough
		var maxOutputPair = 0;
		for (i = 0; i < out.n.length; ++i) {
			if (out.n[i].paired) {
				if (maxOutputPair > out.n[i].row) {
					// tangle - delete pairing
					out.o[out.n[i].row] = out.o[out.n[i].row].text;
					out.n[i] = out.n[i].text;
				}
				if (maxOutputPair < out.n[i].row) {
					maxOutputPair = out.n[i].row;
				}
			}
		}

		// output the stuff preceding the first paired old line
		for (i = 0; i < out.o.length && !out.o[i].paired; ++i) {
			acc.push(out.o[i]);
		}
		str += delFmt(acc);
		acc = [];

		// main loop
		for (i = 0; i < out.n.length; ++i) {
			// output unpaired new "lines"
			while (i < out.n.length && !out.n[i].paired) {
				acc.push(out.n[i++]);
			}
			str += insFmt(acc);
			acc = [];
			if (i < out.n.length) {
				// this new "line" is paired with the (out.n[i].row)th old "line"
				str += out.n[i].text;
				// output unpaired old rows starting after this new line's partner
				var m = out.n[i].row + 1;
				while (m < out.o.length && !out.o[m].paired) {
					acc.push(out.o[m++]);
				}
				str += delFmt(acc);
				acc = [];
			}
		}
		return str;
	}

	// see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object
	// FIXME: use obj.hasOwnProperty instead of this kludge!
	var jsReservedProperties = RegExp(
		'^(constructor|prototype|__((define|lookup)[GS]etter)__' +
			'|eval|hasOwnProperty|propertyIsEnumerable' +
			'|to(Source|String|LocaleString)|(un)?watch|valueOf)$'
	);

	function diffBugAlert(word) {
		if (!diffBugAlert.list[word]) {
			diffBugAlert.list[word] = 1;
			alert('Bad word: ' + word + '\n\nPlease report this bug.');
		}
	}

	diffBugAlert.list = {};

	function makeDiffHashtable(src) {
		var ret = {};
		for (var i = 0; i < src.length; i++) {
			if (jsReservedProperties.test(src[i])) {
				src[i] += '<!-- -->';
			}
			if (!ret[src[i]]) {
				ret[src[i]] = [];
			}
			try {
				ret[src[i]].push(i);
			} catch (err) {
				diffBugAlert(src[i]);
			}
		}
		return ret;
	}

	function diff(o, n) {
		// pass 1: make hashtable ns with new rows as keys
		var ns = makeDiffHashtable(n);

		// pass 2: make hashtable os with old rows as keys
		var os = makeDiffHashtable(o);

		// pass 3: pair unique new rows and matching unique old rows
		var i;
		for (i in ns) {
			if (ns[i].length == 1 && os[i] && os[i].length == 1) {
				n[ns[i][0]] = { text: n[ns[i][0]], row: os[i][0], paired: true };
				o[os[i][0]] = { text: o[os[i][0]], row: ns[i][0], paired: true };
			}
		}

		// pass 4: pair matching rows immediately following paired rows (not necessarily unique)
		for (i = 0; i < n.length - 1; i++) {
			if (
				n[i].paired &&
				!n[i + 1].paired &&
				n[i].row + 1 < o.length &&
				!o[n[i].row + 1].paired &&
				n[i + 1] == o[n[i].row + 1]
			) {
				n[i + 1] = { text: n[i + 1], row: n[i].row + 1, paired: true };
				o[n[i].row + 1] = { text: o[n[i].row + 1], row: i + 1, paired: true };
			}
		}

		// pass 5: pair matching rows immediately preceding paired rows (not necessarily unique)
		for (i = n.length - 1; i > 0; i--) {
			if (
				n[i].paired &&
				!n[i - 1].paired &&
				n[i].row > 0 &&
				!o[n[i].row - 1].paired &&
				n[i - 1] == o[n[i].row - 1]
			) {
				n[i - 1] = { text: n[i - 1], row: n[i].row - 1, paired: true };
				o[n[i].row - 1] = { text: o[n[i].row - 1], row: i - 1, paired: true };
			}
		}

		return { o: o, n: n };
	}
	// ENDFILE: diff.js

	// STARTFILE: init.js
	function setSiteInfo() {
		if (window.popupLocalDebug) {
			pg.wiki.hostname = 'en.wikipedia.org';
		} else {
			pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?)
		}
		pg.wiki.wikimedia = RegExp(
			'(wiki([pm]edia|source|books|news|quote|versity|species|voyage|data)|metawiki|wiktionary|mediawiki)[.]org'
		).test(pg.wiki.hostname);
		pg.wiki.wikia = RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname);
		pg.wiki.isLocal = RegExp('^localhost').test(pg.wiki.hostname);
		pg.wiki.commons =
			pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org'
				? 'commons.wikimedia.org'
				: null;
		pg.wiki.lang = mw.config.get('wgContentLanguage');
		var port = location.port ? ':' + location.port : '';
		pg.wiki.sitebase = pg.wiki.hostname + port;
	}

	function setUserInfo() {
		var params = {
			action: 'query',
			list: 'users',
			ususers: mw.config.get('wgUserName'),
			usprop: 'rights',
		};

		pg.user.canReview = false;
		if (getValueOf('popupReview')) {
			getMwApi()
				.get(params)
				.done(function (data) {
					var rights = data.query.users[0].rights;
					pg.user.canReview = rights.indexOf('review') !== -1; // TODO: Should it be a getValueOf('ReviewRight') ?
				});
		}
	}

	function fetchSpecialPageNames() {
		var params = {
			action: 'query',
			meta: 'siteinfo',
			siprop: 'specialpagealiases',
			formatversion: 2,
			// cache for an hour
			uselang: 'content',
			maxage: 3600,
		};
		return getMwApi()
			.get(params)
			.then(function (data) {
				pg.wiki.specialpagealiases = data.query.specialpagealiases;
			});
	}

	function setTitleBase() {
		var protocol = window.popupLocalDebug ? 'http:' : location.protocol;
		pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ''); // as in http://some.thing.com/wiki/Article
		pg.wiki.botInterfacePath = mw.config.get('wgScript');
		pg.wiki.APIPath = mw.config.get('wgScriptPath') + '/api.php';
		// default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo

		var titletail = pg.wiki.botInterfacePath + '?title=';
		//var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']);

		// other sites may need to add code here to set titletail depending on how their urls work

		pg.wiki.titlebase = protocol + '//' + pg.wiki.sitebase + titletail;
		//pg.wiki.titlebase2  = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]);
		pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath;
		pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath;
		pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath;
		pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath;
		pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath;
		pg.re.basenames = RegExp(
			'^(' +
				map(literalizeRegex, [
					pg.wiki.titlebase, //pg.wiki.titlebase2,
					pg.wiki.articlebase,
				]).join('|') +
				')'
		);
	}

	//////////////////////////////////////////////////
	// Global regexps

	function setMainRegex() {
		var reStart = '[^:]*://';
		var preTitles =
			literalizeRegex(mw.config.get('wgScriptPath')) + '/(?:index[.]php|wiki[.]phtml)[?]title=';
		preTitles += '|' + literalizeRegex(pg.wiki.articlePath + '/');

		var reEnd = '(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?';
		pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd);
	}

	function buildSpecialPageGroup(specialPageObj) {
		var variants = [];
		variants.push(mw.util.escapeRegExp(specialPageObj['realname']));
		variants.push(mw.util.escapeRegExp(encodeURI(specialPageObj['realname'])));
		specialPageObj.aliases.forEach(function (alias) {
			variants.push(mw.util.escapeRegExp(alias));
			variants.push(mw.util.escapeRegExp(encodeURI(alias)));
		});
		return variants.join('|');
	}

	function setRegexps() {
		setMainRegex();
		var sp = nsRe(pg.nsSpecialId);
		pg.re.urlNoPopup = RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)');

		pg.wiki.specialpagealiases.forEach(function (specialpage) {
			if (specialpage.realname === 'Contributions') {
				pg.re.contribs = RegExp(
					'(title=|/)' +
						sp +
						'(?:%3A|:)(?:' +
						buildSpecialPageGroup(specialpage) +
						')' +
						'(&target=|/|/' +
						nsRe(pg.nsUserId) +
						':)(.*)',
					'i'
				);
			} else if (specialpage.realname === 'Diff') {
				pg.re.specialdiff = RegExp(
					'/' + sp + '(?:%3A|:)(?:' + buildSpecialPageGroup(specialpage) + ')' + '/([^?#]*)',
					'i'
				);
			} else if (specialpage.realname === 'Emailuser') {
				pg.re.email = RegExp(
					'(title=|/)' +
						sp +
						'(?:%3A|:)(?:' +
						buildSpecialPageGroup(specialpage) +
						')' +
						'(&target=|/|/(?:' +
						nsRe(pg.nsUserId) +
						':)?)(.*)',
					'i'
				);
			} else if (specialpage.realname === 'Whatlinkshere') {
				pg.re.backlinks = RegExp(
					'(title=|/)' +
						sp +
						'(?:%3A|:)(?:' +
						buildSpecialPageGroup(specialpage) +
						')' +
						'(&target=|/)([^&]*)',
					'i'
				);
			}
		});

		var im = nsReImage();
		// note: tries to get images in infobox templates too, e.g. movie pages, album pages etc
		//					  (^|\[\[)image: *([^|\]]*[^|\] ]) *
		//					  (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)?
		//														$4 = 120 as in 120px
		pg.re.image = RegExp(
			'(^|\\[\\[)' +
				im +
				': *([^|\\]]*[^|\\] ])' +
				'([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' +
				'(' +
				getValueOf('popupImageVarsRegexp') +
				')' +
				' *= *(?:\\[\\[ *)?(?:' +
				im +
				':)?' +
				'([^|]*?)(?:\\]\\])? *[|]? *\\n',
			'img'
		);
		pg.re.imageBracketCount = 6;

		pg.re.category = RegExp('\\[\\[' + nsRe(pg.nsCategoryId) + ': *([^|\\]]*[^|\\] ]) *', 'i');
		pg.re.categoryBracketCount = 1;

		pg.re.ipUser = RegExp(
			'^' +
				// IPv6
				'(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' +
				// IPv4
				'|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' +
				'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$'
		);

		pg.re.stub = RegExp(getValueOf('popupStubRegexp'), 'im');
		pg.re.disambig = RegExp(getValueOf('popupDabRegexp'), 'im');

		// FIXME replace with general parameter parsing function, this is daft
		pg.re.oldid = RegExp('[?&]oldid=([^&]*)');
		pg.re.diff = RegExp('[?&]diff=([^&]*)');
	}

	//////////////////////////////////////////////////
	// miscellany

	function setupCache() {
		// page caching
		pg.cache.pages = [];
	}

	function setMisc() {
		pg.current.link = null;
		pg.current.links = [];
		pg.current.linksHash = {};

		setupCache();

		pg.timer.checkPopupPosition = null;
		pg.counter.loop = 0;

		// ids change with each popup: popupImage0, popupImage1 etc
		pg.idNumber = 0;

		// for myDecodeURI
		pg.misc.decodeExtras = [
			{ from: '%2C', to: ',' },
			{ from: '_', to: ' ' },
			{ from: '%24', to: '$' },
			{ from: '%26', to: '&' }, // no ,
		];
	}

	function getMwApi() {
		if (!pg.api.client) {
			pg.api.userAgent = 'Navigation popups/1.0 (' + mw.config.get('wgServerName') + ')';
			pg.api.client = new mw.Api({
				ajax: {
					headers: {
						'Api-User-Agent': pg.api.userAgent,
					},
				},
			});
		}
		return pg.api.client;
	}

	// We need a callback since this might end up asynchronous because of
	// the mw.loader.using() call.
	function setupPopups(callback) {
		if (setupPopups.completed) {
			if (typeof callback === 'function') {
				callback();
			}
			return;
		}
		// These dependencies should alse be enforced from the gadget,
		// but not everyone loads this as a gadget, so double check
		mw.loader
			.using([
				'mediawiki.util',
				'mediawiki.api',
				'mediawiki.user',
				'user.options',
				'mediawiki.jqueryMsg',
			])
			.then(fetchSpecialPageNames)
			.then(function () {
				// NB translatable strings should be set up first (strings.js)
				// basics
				setupDebugging();
				setSiteInfo();
				setTitleBase();
				setOptions(); // see options.js
				setUserInfo();

				// namespaces etc
				setNamespaces();
				setInterwiki();

				// regexps
				setRegexps();
				setRedirs();

				// other stuff
				setMisc();
				setupLivePreview();

				// main deal here
				setupTooltips();
				log('In setupPopups(), just called setupTooltips()');
				Navpopup.tracker.enable();

				setupPopups.completed = true;
				if (typeof callback === 'function') {
					callback();
				}
			});
	}
	// ENDFILE: init.js

	// STARTFILE: navlinks.js
	//////////////////////////////////////////////////
	// navlinks... let the fun begin
	//

	function defaultNavlinkSpec() {
		var str = '';
		str += '<b><<mainlink|shortcut= >></b>';
		if (getValueOf('popupLastEditLink')) {
			str +=
				'*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}';
		}

		// user links
		// contribs - log - count - email - block
		// count only if applicable; block only if popupAdminLinks
		str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>';
		str += 'if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}';
		str +=
			'if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}';

		// editing links
		// talkpage   -> edit|new - history - un|watch - article|edit
		// other page -> edit - history - un|watch - talk|edit|new
		var editstr = '<<edit|shortcut=e>>';
		var editOldidStr =
			'if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' +
			editstr +
			'}';
		var historystr = '<<history|shortcut=h>>|<<editors|shortcut=E|>>';
		var watchstr = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';

		str +=
			'<br>if(talk){' +
			editOldidStr +
			'|<<new|shortcut=+>>' +
			'*' +
			historystr +
			'*' +
			watchstr +
			'*' +
			'<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
			'}else{' + // not a talk page
			editOldidStr +
			'*' +
			historystr +
			'*' +
			watchstr +
			'*' +
			'<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';

		// misc links
		str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';

		// admin links
		str +=
			'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' +
			'<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}';
		return str;
	}

	function navLinksHTML(article, hint, params) {
		//oldid, rcid) {
		var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>';
		// BAM
		return navlinkStringToHTML(str, article, params);
	}

	function expandConditionalNavlinkString(s, article, z, recursionCount) {
		var oldid = z.oldid,
			rcid = z.rcid,
			diff = z.diff;
		// nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out)
		if (typeof recursionCount != typeof 0) {
			recursionCount = 0;
		}
		var conditionalSplitRegex = RegExp(
			//(1	 if	\\(	(2	2)	\\)	  {(3	3)}  (4   else	  {(5	 5)}  4)1)
			'(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))',
			'i'
		);
		var splitted = s.parenSplit(conditionalSplitRegex);
		// $1: whole conditional
		// $2: test condition
		// $3: true expansion
		// $4: else clause (possibly empty)
		// $5: false expansion (possibly null)
		var numParens = 5;
		var ret = splitted[0];
		for (var i = 1; i < splitted.length; i = i + numParens + 1) {
			var testString = splitted[i + 2 - 1];
			var trueString = splitted[i + 3 - 1];
			var falseString = splitted[i + 5 - 1];
			if (typeof falseString == 'undefined' || !falseString) {
				falseString = '';
			}
			var testResult = null;

			switch (testString) {
				case 'user':
					testResult = !!article.userName();
					break;
				case 'talk':
					testResult = !article.talkPage(); // talkPage converts _articles_ to talkPages
					break;
				case 'admin':
					testResult = !!getValueOf('popupAdminLinks');
					break;
				case 'oldid':
					testResult = !!(typeof oldid != 'undefined' && oldid);
					break;
				case 'rcid':
					testResult = !!(typeof rcid != 'undefined' && rcid);
					break;
				case 'ipuser':
					testResult = !!article.isIpUser();
					break;
				case 'mainspace_en':
					testResult = isInMainNamespace(article) && pg.wiki.hostname == 'en.wikipedia.org';
					break;
				case 'wikimedia':
					testResult = !!pg.wiki.wikimedia;
					break;
				case 'diff':
					testResult = !!(typeof diff != 'undefined' && diff);
					break;
			}

			switch (testResult) {
				case null:
					ret += splitted[i];
					break;
				case true:
					ret += trueString;
					break;
				case false:
					ret += falseString;
					break;
			}

			// append non-conditional string
			ret += splitted[i + numParens];
		}
		if (conditionalSplitRegex.test(ret) && recursionCount < 10) {
			return expandConditionalNavlinkString(ret, article, z, recursionCount + 1);
		}
		return ret;
	}

	function navlinkStringToArray(s, article, params) {
		s = expandConditionalNavlinkString(s, article, params);
		var splitted = s.parenSplit(RegExp('<<(.*?)>>'));
		var ret = [];
		for (var i = 0; i < splitted.length; ++i) {
			if (i % 2) {
				// i odd, so s is a tag
				var t = new navlinkTag();
				var ss = splitted[i].split('|');
				t.id = ss[0];
				for (var j = 1; j < ss.length; ++j) {
					var sss = ss[j].split('=');
					if (sss.length > 1) {
						t[sss[0]] = sss[1];
					} else {
						// no assignment (no "="), so treat this as a title (overwriting the last one)
						t.text = popupString(sss[0]);
					}
				}
				t.article = article;
				var oldid = params.oldid,
					rcid = params.rcid,
					diff = params.diff;
				if (typeof oldid !== 'undefined' && oldid !== null) {
					t.oldid = oldid;
				}
				if (typeof rcid !== 'undefined' && rcid !== null) {
					t.rcid = rcid;
				}
				if (typeof diff !== 'undefined' && diff !== null) {
					t.diff = diff;
				}
				if (!t.text && t.id !== 'mainlink') {
					t.text = popupString(t.id);
				}
				ret.push(t);
			} else {
				// plain HTML
				ret.push(splitted[i]);
			}
		}
		return ret;
	}

	function navlinkSubstituteHTML(s) {
		return s
			.split('*')
			.join(getValueOf('popupNavLinkSeparator'))
			.split('<menurow>')
			.join('<li class="popup_menu_row">')
			.split('</menurow>')
			.join('</li>')
			.split('<menu>')
			.join('<ul class="popup_menu">')
			.split('</menu>')
			.join('</ul>');
	}

	function navlinkDepth(magic, s) {
		return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length;
	}

	// navlinkString: * becomes the separator
	//				<<foo|bar=baz|fubar>> 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 += '<li class="popup_menu_item">' + p[i].html() + '</li>';
				} 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 = '</' + tagType + '>';
		}
		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&section=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&section=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: ' + String(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 "<a href=...>...</a> 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
	/**
	 * Load diff data.
	 *
	 * lets jump through hoops to find the rev ids we need to retrieve
	 *
	 * @param {Title} article
	 * @param {String} oldid
	 * @param {String} diff
	 * @param {Navpopup} navpop
	 */
	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',
			};
			params.fromtitle = article.toString();

			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 && oldid !== 'cur') {
						params.fromrev = oldid;
					}
					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&rvslots=main&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,
							]),
						};
						getMwApi()
							.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.slots.main.content.split('\n');
		var newlines = navpop.diffData.newRev.revision.slots.main.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 = '<hr />';
		if (getValueOf('popupDiffDates')) {
			html += diffDatesTable(navpop);
			html += '<hr />';
		}
		html += shortenDiffString(
			diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),
			getValueOf('popupDiffContextCharacters')
		).join('<hr />');
		setPopupTipsAndHTML(
			html.split('\n').join('<br>') +
				(truncated
					? '<hr /><b>' + popupString('Diff truncated for performance reasons') + '</b>'
					: ''),
			'popupPreview',
			navpop.idNumber
		);
	}

	function diffDatesTable(navpop) {
		var html = '<table class="popup_diff_dates">';
		html += diffDatesTableRow(navpop.diffData.newRev.revision, tprintf('New revision'));
		html += diffDatesTableRow(navpop.diffData.oldRev.revision, tprintf('Old revision'));
		html += '</table>';
		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('<tr><td>%s</td><td>%s</td></tr>', [revlink, txt]);
	}
	// ENDFILE: diffpreview.js

	// STARTFILE: links.js
	/////////////////////
	// LINK GENERATION //
	/////////////////////

	// 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&section=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),
			getMwApi().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,
		});
	}

	/**
	 * Builds a link from a object representing a link
	 * @param {object} link
	 * @param {string} link.url URL
	 * @param {string} link.text The text to show for a link
	 * @param {string} link.title Title of the link, this shows up 
	 * when you hover over the link
	 * @param {boolean} link.newWin Should open in a new Window
	 * @param {number} link.noPopup Should nest new popups from link (0 or 1)
	 * @param {string} link.onclick
	 * @returns {string|null} null if no url is given
	 */
	function generalLink(link) {
		if (typeof link.url == 'undefined') {
			return null;
		}

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

		elem.href = link.url;
		elem.title = link.title;
		// The onclick event adds raw JS in textual form to the HTML.
		// TODO: We should look into removing this, and/or auditing what gets sent.
		elem.setAttribute( 'onclick', link.onclick );

		if ( link.noPopup ) {
			elem.setAttribute('noPopup', '1' );
		}

		var newWin;
		if (typeof link.newWin == 'undefined' || link.newWin === null) {
			newWin = getValueOf('popupNewWindows');
		} else {
			newWin = link.newWin;
		}
		if (newWin) {
			elem.target = '_blank';
		}
		if (link.className) {
			elem.className = link.className;
		}
		elem.innerText = pg.unescapeQuotesHTML(link.text);

		return elem.outerHTML;
	}

	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)\]\]~[[Computer-aided%20design|$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 + '\\]\\]~[[' + t + '|$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 += '<hr />';

			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 (
				'<br> ' +
				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 = 'https://xtools.wmflabs.org/ec?user=$1&project=$2.$3&uselang=' + mw.config.get('wgUserLanguage');

		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 = 'https://global-search.toolforge.org/?uselang=' + mw.config.get('wgUserLanguage') + '&q=';
		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', ' &sdot; ');
		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,
			popupFilterWikibaseItem,
		]);
		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',
			'disambiguation\\}\\}|\\{\\{\\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&nbsp;revision': 'this&nbsp;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 [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
		defaultpopupRedlinkSummary:
			'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
		defaultpopupFixDabsSummary:
			'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
		defaultpopupFixRedirsSummary:
			'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
		defaultpopupExtendedRevertSummary:
			'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
		defaultpopupRevertToPreviousSummary:
			'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
		defaultpopupRevertSummary:
			'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
		defaultpopupQueriedRevertToPreviousSummary:
			'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
		defaultpopupQueriedRevertSummary:
			'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
		defaultpopupRmDabLinkSummary:
			'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|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&nbsp;page': 'user&nbsp;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'));
		});
	})();
	const ss = ".popupMoreLink{display:block;text-align:right;cursor:pointer}ins.popupDiff{background:#afe}del.popupDiff{background:#ffe6e6}#selectionPreview{border:2px solid #ddd;background-color:#eef;padding:6px}.navpopup{border:1px solid #bbb;background-color:#fff;padding:10px;padding-bottom:5px;font-size:11px;box-shadow:0 3px 8px rgba(50,50,50,0.35);word-wrap:break-word}.navpopup hr{color:#aaa !important;background-color:#aaa !important} .popupDrag{background-color:#ffbe20;height:5px;margin-top:-5px;margin-bottom:5px}.popupDragHandle{cursor:move;position:relative}  .popup_menu{display:none;position:absolute;left:0;margin:0;margin-top:1.4em;line-height:1.25em;top:0;z-index:2;width:10em;background:white;border:1px solid grey;padding:0 !important;margin-left:-6px;border-width:1px 1px 1px 6px}.popup_menu li{ list-style:none;margin:0;padding:0}.popup_menu a{display:block;padding:3px;color:#555}.popup_menu_row a{display:inline-block}.popup_menu_row{color:#aaa}.popup_drop{display:inline;position:relative}.popup_drop a,.popup_drop a:visited{padding:3px;margin:0;font-weight:bold;color:#0645ad}.popup_drop:hover .popup_menu,.popup_drop .popup_menu:hover{display:inline;background:white;padding:2px;color:#555}.popup_drop:hover{background:#ccf;color:#44f} .popup_menu a:hover{background:grey;color:#fff;text-decoration:none}.popup_mainlink{font-size:140%;font-weight:bold}.popup_mainlink a{color:#000}a.popup_change_title_link{color:#152}.popup_diff_dates{font-style:italic;background:none}.popup_menu_item a{display:block}.popup_history_row_even{background:#eee}.popup_history_date{font-weight:bold;font-size:120%}.popup_history_row_odd,.popup_history_row_even{display:flex}.popup_history_row_even td:nth-child(3),.popup_history_row_odd td:nth-child(3){flex:3;word-break:break-word}.popup_history_row_even td:nth-child(4),.popup_history_row_odd td:nth-child(4){flex:7;word-break:break-word}.popup_history_row_even \u003E td:not(:last-child),.popup_history_row_odd \u003E td:not(:last-child){margin-right:2px} .popupPreview a.extiw,.popupPreview a.extiw:active{color:#36b;background:none;padding:0}.popupPreview .external{color:#36b} .popupPreview .plainlinks a{background:none !important;padding:0 !important}  .popup_menu:hover{box-shadow:0 0 5px 5px rgba(179,179,255,0.3) } .popup_menu::before{content:'';display:block;position:absolute;height:1.5em;top:-1.4em;left:-1.2em;width:calc(2 * var(--navpop-m-len,6ch));transform:perspective(1px) rotateX(1deg)} .popup_menu::before{z-index:1}.popup_menu li{position:relative;z-index:2} .popup_menu li::before{content:'';display:block;position:absolute;top:0;height:calc(100% + .5em);left:-1.7em;width:1.7em}.popup_menu li::after{content:'';display:block;position:absolute;top:0;height:calc(100% + .5em);left:calc(10em - 1px); width:1em}";
	var styleSheet = document.createElement("style");
	styleSheet.innerText = ss;
	document.head.appendChild(styleSheet);
});
/*-->*/