MediaWiki:Gadget-popups.js and MediaWiki talk:Gadget-popups.js/sandbox: Difference between pages

(Difference between pages)
Page 1
Page 2
Content deleted Content added
changed my mind. this has @file so should be /**
 
disable category in comment
 
Line 14: Line 14:
// ** **
// ** **
// **********************************************************************
// **********************************************************************
/* eslint-env browser */
/* global $, jQuery, mw, window */

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


$(function () {
$(function () {
//////////////////////////////////////////////////
//////////////////////////////////////////////////
// Globals
// Globals
//
//


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


function popupsReady() {
/* Bail if the gadget/script is being loaded twice */
if (!pg.flag) { return false; }
/* An element with id "pg" would add a window.pg property, ignore such property */
if (!pg.flag.finishedLoading) { return false; }
if (window.pg && !(window.pg instanceof HTMLElement)) {
return;
return true;
}

/// 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) {
//<NOLITE>
// the main initial call
if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) {
document.editform.wpTextbox1.onmouseup=doSelectionPopup;
}
//</NOLITE>
// article/content is a structure-dependent thing
container = defaultPopupsContainer();
}
}


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


var anchors;
/// Local Variables: ///
anchors=container.getElementsByTagName('A');
/// mode:c ///
setupTooltipsLoop(anchors, 0, 250, 100, remove, popData);
/// End: ///
}
// ENDFILE: main.js


function defaultPopupsContainer() {
// STARTFILE: actions.js
if (getValueOf('popupOnlyArticleLinks')) {
function setupTooltips(container, remove, force, popData) {
return document.getElementById('mw_content') ||
log('setupTooltips, container=' + container + ', remove=' + remove);
document.getElementById('content') ||
if (!container) {
document.getElementById('article') || document;
// the main initial call
}
if (
return document;
getValueOf('popupOnEditSelection') &&
}
document &&

document.editform &&
function setupTooltipsLoop(anchors,begin,howmany,sleep, remove, popData) {
document.editform.wpTextbox1
log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments));
) {
var finish=begin+howmany;
document.editform.wpTextbox1.onmouseup = doSelectionPopup;
var loopend = 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);
// article/content is a structure-dependent thing
} while (--j);
container = defaultPopupsContainer();
}
}
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
if (!remove && !force && container.ranSetupTooltipsAlready) {
// This also kills any onclick stuff that used to be going on in the toc
return;
function rmTocTooltips() {
var toc=document.getElementById('toc');
if (toc) {
var tocLinks=toc.getElementsByTagName('A');
var tocLen = tocLinks.length;
for (j=0; j<tocLen; ++j) {
removeTooltip(tocLinks[j], true);
}
}
container.ranSetupTooltipsAlready = !remove;

var anchors;
anchors = container.getElementsByTagName('A');
setupTooltipsLoop(anchors, 0, 250, 100, remove, popData);
}
}
}

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 defaultPopupsContainer() {
function removeTitle(a) {
if (!a.originalTitle) {
if (getValueOf('popupOnlyArticleLinks')) {
a.originalTitle=a.title;
return (
document.querySelector('.skin-vector-2022 .vector-body') ||
document.getElementById('mw_content') ||
document.getElementById('content') ||
document.getElementById('article') ||
document
);
}
return document;
}
}
a.title='';
}

function restoreTitle(a) {
if ( a.title || !a.originalTitle ) { return; }
a.title = a.originalTitle;
}


function registerHooks(np) {
function setupTooltipsLoop(anchors, begin, howmany, sleep, remove, popData) {
var popupMaxWidth=getValueOf('popupMaxWidth');
log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments));

var finish = begin + howmany;
if (typeof popupMaxWidth === 'number') {
var loopend = Math.min(finish, anchors.length);
var j = loopend - begin;
var setMaxWidth = function () {
np.mainDiv.style.maxWidth = popupMaxWidth + 'px';
log(
np.maxWidth = popupMaxWidth;
'setupTooltips: anchors.length=' +

anchors.length +
', begin=' +
try {
begin +
// hack for IE
// see http://www.svendtofte.com/code/max_width_in_ie/
', howmany=' +
// use setExpression as documented here on msdn: http://tinyurl dot com/dqljn
howmany +
', loopend=' +
if (np.mainDiv.style.setExpression) {
loopend +
np.mainDiv.style.setExpression(
', remove=' +
'width', 'document.body.clientWidth > ' +
remove
popupMaxWidth + ' ? "' +popupMaxWidth + 'px": "auto"');
);
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();
}
}
catch (errors) {
pg.flag.finishedLoading = true;
errlog( "Running on IE8 are we not?: " + errors );
}
}

// 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);
}
}
}
};
np.addHook(setMaxWidth, 'unhide', 'before');
}
}
//<NOLITE>
np.addHook(addPopupShortcuts, 'unhide', 'after');
np.addHook(rmPopupShortcuts, 'hide', 'before');
//</NOLITE>
}


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) {
function mouseOverWikiLink(evt) {
if (!evt && window.event) {evt=window.event;}
if (!a.hasPopup) {
return mouseOverWikiLink2(this, evt);
return;
}
}
a.onmouseover = null;
a.onmouseout = null;
if (a.originalTitle) {
a.title = a.originalTitle;
}
a.hasPopup = false;
}


/**
function removeTitle(a) {
* Gets the references list item that the provided footnote link targets. This
if (!a.originalTitle) {
* is typically a li element within the ol.references element inside the reflist.
a.originalTitle = a.title;
* @param {Element} a - A footnote link.
}
* @returns {Element|boolean} The targeted element, or false if one can't be found.
a.title = '';
}
*/
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);
function restoreTitle(a) {
if ( lTitle.toString(true) !== aTitle.toString(true) ) { return false; }
if (a.title || !a.originalTitle) {

return;
var el=document.getElementById(anch);
}
while ( el && typeof el.nodeName === 'string') {
a.title = a.originalTitle;
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 registerHooks(np) {
function footnotePreview(x, navpop) {
setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber);
var popupMaxWidth = getValueOf('popupMaxWidth');
}


// var modid=0;
if (typeof popupMaxWidth === 'number') {
// if(!window.opera) { window.opera={postError: console.log}; }
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) {
function modifierKeyHandler(a) {
return function(evt) {
//remove listeners for modifier key if any that were added in mouseOverWikiLink
// opera.postError('modifierKeyHandler called' + (++modid));
document.removeEventListener('keydown', a.modifierKeyHandler, false);
// opera.postError(''+evt + modid);
document.removeEventListener('keyup', a.modifierKeyHandler, false);
// for (var i in evt) {
}
// opera.postError('' + modid + ' ' + i + ' ' + evt[i]);
// }
// opera.postError(''+evt.ctrlKey + modid);
var mod=getValueOf('popupModifier');
if (!mod) { return true; }


if (!evt && window.event) {evt=window.event;}
function mouseOverWikiLink(evt) {
// opera.postError('And now....'+modid);
if (!evt && window.event) {
// opera.postError(''+evt+modid);
evt = window.event;
// opera.postError(''+evt.ctrlKey+modid);
}


var modPressed = modifierPressed(evt);
// if the modifier is needed, listen for it,
var action = getValueOf('popupModifierAction');
// 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);
}


// FIXME: probable bug - modifierPressed should be modPressed below?
return mouseOverWikiLink2(this, evt);
if ( action === 'disable' && modifierPressed ) { return true; }
}
if ( action === 'enable' && !modifierPressed ) { return true; }


mouseOverWikiLink2(a, 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.
* @return {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;
}


function modifierPressed(evt) {
var lTitle = Title.fromURL(location.href);
var mod=getValueOf('popupModifier');
if (lTitle.toString(true) !== aTitle.toString(true)) {
return false;
if (!mod) { return false; }
}


if (!evt && window.event) {evt=window.event;}
var el = document.getElementById(anch);
// opera.postError('And now....'+modid);
while (el && typeof el.nodeName === 'string') {
// opera.postError(''+evt+modid);
var nt = el.nodeName.toLowerCase();
// opera.postError(''+evt.ctrlKey+modid);
if (nt === 'li') {
return el;
} else if (nt === 'body') {
return false;
} else if (el.parentNode) {
el = el.parentNode;
} else {
return false;
}
}
return false;
}


return ( evt && mod && evt[mod.toLowerCase() + 'Key'] );
function footnotePreview(x, navpop) {

setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber);
}
}


function modifierPressed(evt) {
function dealWithModifier(a,evt) {
var mod = getValueOf('popupModifier');
if (!getValueOf('popupModifier')) { return false; }
var action = getValueOf('popupModifierAction');
if (!mod) {
if ( action == 'enable' && !modifierPressed(evt) ||
return false;
action == 'disable' && modifierPressed(evt) ) {
// if the modifier is needed and not pressed, listen for it until
// we mouseout of this link.
restoreTitle(a);
var addHandler='addEventListener';
var rmHandler='removeEventListener';
var on='';
if (!document.addEventListener) {
addHandler='attachEvent';
rmHandler='detachEvent';
on='on';
}
if (!document[addHandler]) { // forget it
return;
}
}


a.modifierKeyHandler=modifierKeyHandler(a);
if (!evt && window.event) {

evt = window.event;
switch (action) {
case 'enable':
document[addHandler](on+'keydown', a.modifierKeyHandler, false);
a[addHandler](on+'mouseout', function() {
document[rmHandler](on+'keydown',
a.modifierKeyHandler, false);
}, true);
break;
case 'disable':
document[addHandler](on+'keyup', a.modifierKeyHandler, false);
}
}


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


function mouseOverWikiLink2(a, evt) {
// Checks if the correct modifier pressed/unpressed if needed
function isCorrectModifier(a, evt) {
if (dealWithModifier(a,evt)) { return; }
if (!getValueOf('popupModifier')) {
if ( getValueOf('removeTitles') ) { removeTitle(a); }
if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; }
return true;
pg.current.link=a;
}

// if popupModifierAction = enable, we should popup when the modifier is pressed
if (getValueOf('simplePopups') && pg.option.popupStructure === null) {
// if popupModifierAction = disable, we should popup unless the modifier is pressed
// reset *default value* of popupStructure
var action = getValueOf('popupModifierAction');
setDefault('popupStructure', 'original');
return (
(action == 'enable' && modifierPressed(evt)) || (action == 'disable' && !modifierPressed(evt))
);
}
}


var article=(new Title()).fromAnchor(a);
function mouseOverWikiLink2(a, evt) {
// set global variable (ugh) to hold article (wikipage)
if (!isCorrectModifier(a, evt)) {
pg.current.article = article;
return;
}
if (getValueOf('removeTitles')) {
removeTitle(a);
}
if (a == pg.current.link && a.navpopup && a.navpopup.isVisible()) {
return;
}
pg.current.link = a;


if (!a.navpopup) {
if (getValueOf('simplePopups') && !pg.option.popupStructure) {
// FIXME: this doesn't behave well if you mouse out of a popup
// reset *default value* of popupStructure
// directly into a link with the same href
setDefault('popupStructure', 'original');
if (pg.current.linksHash[a.href] && false) {
a.navpopup = pg.current.linksHash[a.href];
}
}
else {

a.navpopup=newNavpopup(a, article);
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.linksHash[a.href] = a.navpopup;
pg.current.links.push(a);
pg.current.links.push(a);
}
}
}
if (a.navpopup.pending === null || a.navpopup.pending !== 0) {
if (a.navpopup.pending === null || a.navpopup.pending !== 0) {
// either fresh popups or those with unfinshed business are redone from scratch
// either fresh popups or those with unfinshed business are redone from scratch
simplePopupContent(a, article);
simplePopupContent(a, article);
}
}
a.navpopup.showSoonIfStable(a.navpopup.delay);
a.navpopup.showSoonIfStable(a.navpopup.delay);


getValueOf('popupInitialWidth');
clearInterval(pg.timer.checkPopupPosition);
pg.timer.checkPopupPosition = setInterval(checkPopupPosition, 600);


clearInterval(pg.timer.checkPopupPosition);
if (getValueOf('simplePopups')) {
pg.timer.checkPopupPosition=setInterval(checkPopupPosition, 600);
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(getValueOf('simplePopups')) {
if (a.navpopup.pending !== 0) {
if (getValueOf('popupPreviewButton') && !a.simpleNoMore) {
nonsimplePopupContent(a, article);
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;
nonsimplePopupContent(a,article);
};
s.innerHTML=popupString('show preview');
setPopupHTML(d, 'popupPreview', a.navpopup.idNumber);
}
}
return;
}
}


if (a.navpopup.pending !== 0 ) {
// simplePopupContent: the content that do not require additional download
nonsimplePopupContent(a, article);
// (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 });


// simplePopupContent: the content that is shown even when simplePopups is true
if (getValueOf('popupDraggable')) {
function simplePopupContent(a, article) {
var dragHandle = getValueOf('popupDragHandle') || null;
/* FIXME hack */ a.navpopup.hasPopupMenu=false;
if (dragHandle && dragHandle != 'all') {
dragHandle += a.navpopup.idNumber;
a.navpopup.setInnerHTML(popupHTML(a));
fillEmptySpans({navpopup:a.navpopup});
}
setTimeout(function () {
a.navpopup.makeDraggable(dragHandle);
}, 150);
}


if (getValueOf('popupRedlinkRemoval') && a.className == 'new') {
if (getValueOf('popupDraggable'))
{
setPopupHTML('<br>' + popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber);
var dragHandle = getValueOf('popupDragHandle') || null;
if (dragHandle && dragHandle != 'all') {
dragHandle += a.navpopup.idNumber;
}
}
setTimeout(function(){a.navpopup.makeDraggable(dragHandle);}, 150);
}
}


//<NOLITE>
function debugData(navpopup) {
if (getValueOf('popupDebugging') && navpopup.idNumber) {
if (getValueOf('popupRedlinkRemoval') && a.className=='new') {
setPopupHTML('<br>'+popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber);
setPopupHTML(
'idNumber=' + navpopup.idNumber + ', pending=' + navpopup.pending,
'popupError',
navpopup.idNumber
);
}
}
}
//</NOLITE>
}


function newNavpopup(a, article) {
function debugData(navpopup) {
if(getValueOf('popupDebugging') && navpopup.idNumber) {
var navpopup = new Navpopup();
setPopupHTML('idNumber='+navpopup.idNumber + ', pending=' + navpopup.pending,
navpopup.fuzz = 5;
'popupError', navpopup.idNumber);
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;
}
}
}


function newNavpopup(a, article) {
// Should we show nonsimple context?
var navpopup = new Navpopup();
// If simplePopups is set to true, then we do not show nonsimple context,
navpopup.fuzz=5;
// but if a bottom "show preview" was clicked we do show nonsimple context
navpopup.delay=getValueOf('popupDelay')*1000;
function shouldShowNonSimple(a) {
// increment global counter now
return !getValueOf('simplePopups') || a.simpleNoMore;
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 govern by the option (e.g. popupUserInfo)?
function nonsimplePopupContent(a, article) {
// If the user explicitly asked for nonsimple context by setting the option to true,
var diff=null, history=null;
// then we show it even in nonsimple mode.
var params=parseParams(a.href);
function shouldShow(a, option) {
var oldid=(typeof params.oldid=='undefined' ? null : params.oldid);
if (shouldShowNonSimple(a)) {
//<NOLITE>
return getValueOf(option);
if(getValueOf('popupPreviewDiffs')) {
} else {
diff=params.diff;
return typeof window[option] != 'undefined' && window[option];
}
}
}
if(getValueOf('popupPreviewHistory')) {

history=(params.action=='history');
function nonsimplePopupContent(a, article) {
}
var diff = null,
//</NOLITE>
history = null;
a.navpopup.pending=0;
var params = parseParams(a.href);
var referenceElement = footnoteTarget(a);
var oldid = typeof params.oldid == 'undefined' ? null : params.oldid;
if (referenceElement) {
if (shouldShow(a, 'popupPreviewDiffs')) {
footnotePreview(referenceElement, a.navpopup);
diff = params.diff;
//<NOLITE>
}
} else if ( diff || diff === 0 ) {
if (shouldShow(a, 'popupPreviewHistory')) {
loadDiff(article, oldid, diff, a.navpopup);
history = params.action == 'history';
} else if ( history ) {
}
a.navpopup.pending = 0;
loadAPIPreview('history', article, a.navpopup);
} else if ( pg.re.contribs.test(a.href) ) {
var referenceElement = footnoteTarget(a);
loadAPIPreview('contribs', article, a.navpopup);
if (referenceElement) {
} else if ( pg.re.backlinks.test(a.href) ) {
footnotePreview(referenceElement, a.navpopup);
loadAPIPreview('backlinks', article, a.navpopup);
} else if (diff || diff === 0) {
} else if ( // FIXME should be able to get all preview combinations with options
loadDiff(article, oldid, diff, a.navpopup);
article.namespaceId()==pg.nsImageId &&
} else if (history) {
( getValueOf('imagePopupsForImages') || ! anchorContainsImage(a) )
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);
loadAPIPreview('imagepagepreview', article, a.navpopup);
loadImage(article, a.navpopup);
loadImage(article, a.navpopup);
//</NOLITE>
} else {
} else {
if (article.namespaceId() == pg.nsCategoryId && shouldShow(a, 'popupCategoryMembers')) {
if (article.namespaceId() == pg.nsCategoryId &&
loadAPIPreview('category', article, a.navpopup);
getValueOf('popupCategoryMembers')) {
} else if (
loadAPIPreview('category', article, a.navpopup);
(article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) &&
} else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) &&
shouldShow(a, 'popupUserInfo')
getValueOf('popupUserInfo')) {
) {
loadAPIPreview('userinfo', article, a.navpopup);
loadAPIPreview('userinfo', article, a.navpopup);
}
if (shouldShowNonSimple(a)) {
startArticlePreview(article, oldid, a.navpopup);
}
}
}
startArticlePreview(article, oldid, a.navpopup);
}
}
}


function pendingNavpopTask(navpop) {
function pendingNavpopTask(navpop) {
if (navpop && navpop.pending === null) {
if (navpop && navpop.pending === null) { navpop.pending=0; }
navpop.pending = 0;
++navpop.pending;
debugData(navpop);
}
}
++navpop.pending;
debugData(navpop);
}


function completedNavpopTask(navpop) {
function completedNavpopTask(navpop) {
if (navpop && navpop.pending) {
if (navpop && navpop.pending) { --navpop.pending; }
--navpop.pending;
debugData(navpop);
}
}
debugData(navpop);
}


function startArticlePreview(article, oldid, navpop) {
function startArticlePreview(article, oldid, navpop) {
navpop.redir = 0;
navpop.redir=0;
loadPreview(article, oldid, navpop);
loadPreview(article, oldid, navpop);
}

function loadPreview(article, oldid, navpop) {
pendingNavpopTask(navpop);
if (!navpop.redir) { navpop.originalArticle=article; }
if (!navpop.visible && getValueOf('popupLazyDownloads')) {
var id=(navpop.redir) ? 'DOWNLOAD_PREVIEW_REDIR_HOOK' : 'DOWNLOAD_PREVIEW_HOOK';
navpop.addHook(function() {
getWiki(article, insertPreview, oldid, navpop);
return true; }, 'unhide', 'before', id);
} else {
getWiki(article, insertPreview, oldid, navpop);
}
}
}


function loadPreview(article, oldid, navpop) {
function loadPreviewFromRedir(redirMatch, navpop) {
// redirMatch is a regex match
if (!navpop.redir) {
var target = new Title().fromWikiText(redirMatch[2]);
navpop.originalArticle = article;
// overwrite (or add) anchor from original target
}
// mediawiki does overwrite; eg [[User:Lupin/foo3#Done]]
article.oldid = oldid;
if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; }
loadAPIPreview('revision', article, navpop);
var trailingRubbish=redirMatch[4];
navpop.redir++;
navpop.redirTarget=target;
//<NOLITE>
var warnRedir = redirLink(target, navpop.article);
setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber);
//</NOLITE>
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) {
completedNavpopTask(download.owner);
loadPreviewFromRedir(redirMatch, download.owner);
return;
}
}


if (download.owner.visible || !getValueOf('popupLazyPreviews')) {
function loadPreviewFromRedir(redirMatch, navpop) {
insertPreviewNow(download);
// redirMatch is a regex match
} else {
var target = new Title().fromWikiText(redirMatch[2]);
var id=(download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK';
// overwrite (or add) anchor from original target
download.owner.addHook( function(){insertPreviewNow(download); return true;},
// mediawiki does overwrite; eg [[User:Lupin/foo3#Done]]
'unhide', 'after', id );
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) {
function insertPreviewNow(download) {
if (!download.owner) {
if (!download.owner) { return; }
var wikiText=download.data;
return;
var navpop=download.owner;
}
completedNavpopTask(navpop);
var art=navpop.redirTarget || navpop.originalArticle;


//<NOLITE>
var redirMatch = pg.re.redirect.exec(download.data);
makeFixDabs(wikiText, navpop);
if (download.owner.redir === 0 && redirMatch) {
if (getValueOf('popupSummaryData')) {
loadPreviewFromRedir(redirMatch, download.owner);
var info=getPageInfo(wikiText, download);
return;
setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber);
}

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
);
}
}
}


var imagePage='';
function insertPreviewNow(download) {
if (art.namespaceId()==pg.nsImageId) { imagePage=art.toString(); }
if (!download.owner) {
else { imagePage=getValidImageFromWikiText(wikiText); }
return;
if(imagePage) { loadImage(Title.fromWikiText(imagePage), navpop); }
}
//</NOLITE>
var wikiText = download.data;
var navpop = download.owner;
var art = navpop.redirTarget || navpop.originalArticle;


if (getValueOf('popupPreviews')) { insertArticlePreview(download, art, navpop); }
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);
}


function insertArticlePreview(download, art, navpop) {
if (getValueOf('popupPreviews')) {
if (download && typeof download.data == typeof ''){
insertArticlePreview(download, art, navpop);
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 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) {
function prepPreviewmaker(data, article, navpop) {
// deal with tricksy anchors
// deal with tricksy anchors
var d = anchorize(data, article.anchorString());
var d=anchorize(data, article.anchorString());
var urlBase = joinPath([pg.wiki.articlebase, article.urlString()]);
var urlBase=joinPath([pg.wiki.articlebase, article.urlString()]);
var p = new Previewmaker(d, urlBase, navpop);
var p=new Previewmaker(d, urlBase, navpop);
return p;
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]));
}


// Try to imitate the way mediawiki generates HTML anchors from section titles
// now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom
function anchorize(d, anch) {
var lines = d.split('\n');
if (!anch) { return d; }
for (var i = 0; i < lines.length; ++i) {
var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?\}\})');
lines[i] = lines[i]
var match=d.match(anchRe);
.replace(/[[]{2}([^|\]]*?[|])?(.*?)[\]]{2}/g, '$2')
if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); }
.replace(/'''([^'])/g, '$1')

.replace(/''([^'])/g, '$1');
// now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom
if (lines[i].match(anchRe)) {
return d.split('\n').slice(i).join('\n').replace(/^[^=]*/, '');
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;
}
}
return d;
}


function killPopup() {
function killPopup() {
if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); }
removeModifierKeyHandler(this);
if (!pg) { return; }
if (getValueOf('popupShortcutKeys')) {
if (pg.current.link && pg.current.link.navpopup) { pg.current.link.navpopup.banish(); }
rmPopupShortcuts();
pg.current.link=null;
}
abortAllDownloads();
if (!pg) {
if (pg.timer.checkPopupPosition) {
return;
clearInterval(pg.timer.checkPopupPosition);
}
pg.timer.checkPopupPosition=null;
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
}
}
return true; // preserve default action
// ENDFILE: actions.js
}
// ENDFILE: actions.js
// STARTFILE: domdrag.js
/**
@fileoverview
The {@link Drag} object, which enables objects to be dragged around.


<pre>
// STARTFILE: domdrag.js
*************************************************
/**
dom-drag.js
* @file
09.25.2001
* The {@link Drag} object, which enables objects to be dragged around.
www.youngpup.net
*
**************************************************
* <pre>
10.28.2001 - fixed minor bug where events
* *************************************************
sometimes fired off the handle, not the root.
* dom-drag.js
*************************************************
* 09.25.2001
Pared down, some hooks added by [[User:Lupin]]
* 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>
*/


Copyright Aaron Boodman.
/**
Saying stupid things daily since March 2001.
* Creates a new Drag object. This is used to make various DOM elements draggable.
</pre>
* @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;
}


/**
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.
* Gets an event in a cross-browser manner.
To disable this, set it to <code>null</code>.
* @param {Event} e
* @private
@type Function
*/
*/
this.startCondition = null;
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;
};

/**
/**
Hook to be run when the drag finishes. This is passed the final coordinates of
* Initialises the Drag instance by telling it which object you want to be draggable, and what
the dragged object (two integers, x and y). To disables this, set it to <code>null</code>.
* you want to drag it by.
@type Function
* @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.
this.endHook = null;
*/
}
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 || o;
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';
}


if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left = "0px"; }
o.root.onthisStart = function () {};
if (isNaN(parseInt(o.root.style.top, 10))) { o.root.style.top = "0px"; }
o.root.onthisEnd = function () {};
o.root.onthis = function () {};
};


o.root.onthisStart = function(){};
/**
o.root.onthisEnd = function(){};
* Starts the drag.
o.root.onthis = function(){};
* @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;
Starts the drag.
o.lastMouseY = e.clientY;
@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;
var dragObj = this;
o.lastMouseY = e.clientY;
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;
};


var dragObj = this;
/**
o.onmousemoveDefault = document.onmousemove;
* Does the drag.
o.dragging = true;
* @param {Event} e
document.onmousemove = function(e) { dragObj.drag.apply( dragObj, [e] ); };
* @private
document.onmouseup = function(e) { dragObj.end.apply( dragObj, [e] ); };
*/
return false;
Drag.prototype.drag = function (e) {
};
e = this.fixE(e);
/**
var o = this.obj;
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 ey = e.clientY;
var ex = e.clientX;
var ex = e.clientX;
var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10);
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 x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10 );
var nx, ny;
var nx, ny;


nx = x + (ex - o.lastMouseX) * (o.hmode ? 1 : -1);
nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
ny = y + (ey - o.lastMouseY) * (o.vmode ? 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.hmode ? "left" : "right"] = nx + "px";
this.obj.root.style[o.vmode ? 'top' : 'bottom'] = ny + 'px';
this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
this.obj.lastMouseX = ex;
this.obj.lastMouseX = ex;
this.obj.lastMouseY = ey;
this.obj.lastMouseY = ey;


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


/**
/**
* Ends the drag.
Ends the drag.
* @private
@private
*/
*/
Drag.prototype.end = function () {
Drag.prototype.end = function() {
document.onmousemove = this.obj.onmousemoveDefault;
document.onmousemove=this.obj.onmousemoveDefault;
document.onmouseup = null;
document.onmouseup = null;
this.obj.dragging = false;
this.obj.dragging = false;
if (this.endHook) {
if (this.endHook) {
this.endHook( parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10),
this.endHook(
parseInt(this.obj.root.style[this.obj.hmode ? 'left' : 'right'], 10),
parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10));
}
parseInt(this.obj.root.style[this.obj.vmode ? 'top' : 'bottom'], 10)
};
);
// ENDFILE: domdrag.js
}
// STARTFILE: structures.js
};
//<NOLITE>
// ENDFILE: domdrag.js
pg.structures.original={};
pg.structures.original.popupLayout=function () {
return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle',
'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;


// 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) {
function copyStructure(oldStructure, newStructure) {
pg.structures[newStructure] = {};
pg.structures[newStructure]={};
for (var prop in pg.structures[oldStructure]) {
for (var prop in pg.structures[oldStructure]) {
pg.structures[newStructure][prop] = pg.structures[oldStructure][prop];
pg.structures[newStructure][prop]=pg.structures[oldStructure][prop];
}
}
}
}


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


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


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


str +=
str += '<br>if(talk){' +
editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' +
'<br>if(talk){' +
'<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
editOldidStr +
'}else{' + // not a talk page
'|<<new|shortcut=+>>' +
editOldidStr + '*' + historystr + '*' + watchstr + '*' +
'*' +
'<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';
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
// misc links
str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>';
str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>';
str += 'if(admin){<br>}else{*}<<move|shortcut=m>>';
str += 'if(admin){<br>}else{*}<<move|shortcut=m>>';


// admin links
// admin links
str += 'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' +
str +=
'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' +
'<<undelete|undeleteShort>>|<<delete|shortcut=d>>}';
return navlinkStringToHTML(str, x.article, x.params);
'<<undelete|undeleteShort>>|<<delete|shortcut=d>>}';
};
return navlinkStringToHTML(str, x.article, x.params);
pg.structures.nostalgia.popupRedirTopLinks=pg.structures.nostalgia.popupTopLinks;
};
pg.structures.nostalgia.popupRedirTopLinks = pg.structures.nostalgia.popupTopLinks;


/** -- fancy -- **/
/** -- fancy -- **/
copyStructure('original', 'fancy');
copyStructure('original', 'fancy');
pg.structures.fancy.popupTitle = function (x) {
pg.structures.fancy.popupTitle=function (x) {
return navlinkStringToHTML('<font size=+0><<mainlink>></font>', x.article, x.params);
return navlinkStringToHTML('<font size=+0><<mainlink>></font>',x.article,x.params);
};
};
pg.structures.fancy.popupTopLinks = function (x) {
pg.structures.fancy.popupTopLinks=function(x) {
var hist='<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>if(mainspace_en){|<<editors|shortcut=E|eds>>}';
var hist =
'<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>';
var watch='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
var watch = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
var move='<<move|shortcut=m|move>>';
return navlinkStringToHTML('if(talk){' +
var move = '<<move|shortcut=m|move>>';
'<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' +
return navlinkStringToHTML(
'<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move +
'if(talk){' +
'<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' +
'}else{<<edit|shortcut=e>>*' + hist +
'*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' +
hist +
'*' + watch + '*' + move+'}<br>', x.article, x.params);
'*' +
};
'<<article|shortcut=a>>|<<editArticle|edit>>' +
pg.structures.fancy.popupOtherLinks=function(x) {
'*' +
var admin='<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>';
watch +
var user='<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}';
'*' +
user+='if(ipuser){|<<arin>>}else{*<<email|shortcut=E|'+
move +
'}else{<<edit|shortcut=e>>*' +
popupString('email')+'>>}if(admin){*<<block|shortcut=b>>}';
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>>';
var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>';
return navlinkStringToHTML(
return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal,
x.article, x.params);
'<br>if(user){' + user + '*}if(admin){' + admin + 'if(user){<br>}else{*}}' + normal,
};
x.article,
pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle;
x.params
pg.structures.fancy.popupRedirTopLinks=pg.structures.fancy.popupTopLinks;
);
pg.structures.fancy.popupRedirOtherLinks=pg.structures.fancy.popupOtherLinks;
};
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(/<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 -- **/
/** -- fancy2 -- **/
// hack for [[User:MacGyverMagic]]
copyStructure('original', 'menus');
copyStructure('fancy', 'fancy2');
pg.structures.menus.popupLayout = function () {
pg.structures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning
return [
return '<br>'+pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$','i'),'');
'popupError',
};
'popupImage',
pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title
'popupTopLinks',
return ['popupError', 'popupImage', 'popupTitle', 'popupData', 'popupTopLinks', 'popupOtherLinks',
'popupTitle',
'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
'popupOtherLinks',
'popupMiscTools', ['popupRedlink'],
'popupRedir',
'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
[
};
'popupWarnRedir',
'popupRedirTopLinks',
'popupRedirTitle',
'popupRedirData',
'popupRedirOtherLinks',
],
'popupUserData',
'popupData',
'popupMiscTools',
['popupRedlink'],
'popupPrePreviewSep',
'popupPreview',
'popupSecondPreview',
'popupPreviewMore',
'popupPostPreview',
'popupFixDab',
];
};


/** -- menus -- **/
pg.structures.menus.popupTopLinks = function (x, shorter) {
copyStructure('original', 'menus');
// FIXME maybe this stuff should be cached
pg.structures.menus.popupLayout=function () {
var s = [];
return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks',
var dropclass = 'popup_drop';
'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
var enddiv = '</div>';
'popupData', 'popupMiscTools', ['popupRedlink'],
var hist = '<<history|shortcut=h>>';
'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
if (!shorter) {
};
hist = '<menurow>' + hist + '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>';
function toggleSticky(uid) {
}
var popDiv=document.getElementById('navpopup_maindiv'+uid);
var lastedit = '<<lastEdit|shortcut=/|show last edit>>';
if (!popDiv) { return; }
var thank = 'if(diff){<<thank|send thanks>>}';
if (!popDiv.navpopup.sticky) { popDiv.navpopup.stick(); }
var jsHistory = '<<lastContrib|last set of edits>><<sinceMe|changes since mine>>';
else {
var linkshere = '<<whatLinksHere|shortcut=l|what links here>>';
popDiv.navpopup.unstick();
var related = '<<relatedChanges|shortcut=r|related changes>>';
popDiv.navpopup.hide();
var search =
}
'<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' +
}
'|<<google|shortcut=G|web>></menurow>';
pg.structures.menus.popupTopLinks = function (x, shorter) {
var watch = '<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>';
// FIXME maybe this stuff should be cached
var protect =
var s=[];
'<menurow><<unprotect|unprotectShort>>|' +
var dropdiv='<div class="popup_drop">';
'<<protect|shortcut=p>>|<<protectlog|log>></menurow>';
var del =
var enddiv='</div>';
var endspan='</span>';
'<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>></menurow>';
var move = '<<move|shortcut=m|move page>>';
var hist='<<history|shortcut=h>>';
if (!shorter) { hist = '<menurow>' + hist +
var nullPurge = '<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>';
var viewOptions = '<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>';
'|<<historyfeed|rss>>if(mainspace_en){|<<editors|shortcut=E>>}</menurow>'; }
var lastedit='<<lastEdit|shortcut=/|show last edit>>';
var editRow =
var thank='if(diff){<<thank|send thanks>>}';
'if(oldid){' +
var jsHistory='<<lastContrib|last set of edits>><<sinceMe|changes since mine>>';
'<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' +
'<menurow><<revert|shortcut=v>>|<<undo>></menurow>' +
var linkshere='<<whatLinksHere|shortcut=l|what links here>>';
'}else{<<edit|shortcut=e>>}';
var related='<<relatedChanges|shortcut=r|related changes>>';
var search='<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' +
var markPatrolled = 'if(rcid){<<markpatrolled|mark patrolled>>}';
var newTopic = 'if(talk){<<new|shortcut=+|new topic>>}';
'|<<google|shortcut=G|web>></menurow>';
var watch='<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>';
var protectDelete = 'if(admin){' + protect + del + '}';
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')) {
if (getValueOf('popupActionsMenu')) {
s.push('<<mainlink>>*' + menuTitle(dropclass, 'actions'));
s.push( '<<mainlink>>*' + dropdiv + menuTitle('actions'));
} else {
} else {
s.push('<div class="' + dropclass + '"><<mainlink>>');
s.push( dropdiv + '<<mainlink>>');
}
}
s.push('<menu>');
s.push( '<menu>');
s.push(editRow + markPatrolled + newTopic + hist + lastedit + thank);
s.push( editRow + markPatrolled + newTopic + hist + lastedit + thank );
if (!shorter) {
if (!shorter) { s.push(jsHistory); }
s.push(jsHistory);
s.push( move + linkshere + related);
if (!shorter) { s.push(nullPurge + search); }
}
if (!shorter) { s.push(viewOptions); }
s.push(move + linkshere + related);
s.push('<hr />' + watch + protectDelete);
if (!shorter) {
s.push(nullPurge + search);
s.push('<hr />' +
'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' +
}
'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' +
if (!shorter) {
'<<newTalk|shortcut=+|new topic>>}</menu>' + enddiv);
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
// user menu starts here
var email = '<<email|shortcut=E|email user>>';
var email='<<email|shortcut=E|email user>>';
var contribs= 'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' +
var contribs =
'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' +
'if(admin){<menurow><<deletedContribs>></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 + '}');


s.push('if(user){*' + dropdiv + menuTitle('user'));
// popups menu starts here
s.push('<menu>');
if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) {
s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>');
x.navpop.hasPopupMenu = true;
s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' +
s.push('*' + menuTitle(dropclass, 'popupsMenu') + '<menu>');
s.push('<<togglePreviews|toggle previews>>');
'<<newUserTalk|shortcut=+|leave comment>>');
if(!shorter) { s.push( 'if(ipuser){<<arin>>}else{' + email + '}' ); }
s.push('<<purgePopups|reset>>');
s.push('<<disablePopups|disable>>');
else { s.push( 'if(ipuser){}else{' + email + '}' ); }
s.push('</menu>' + enddiv);
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>}');
return navlinkStringToHTML(s.join(''), x.article, x.params);
s.push('<<blocklog|shortcut=B|block log>>' + getValueOf('popupExtraUserMenu'));
};
s.push('</menu>' + enddiv + '}');


// popups menu starts here
function menuTitle(dropclass, s) {
if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) {
var text = popupString(s); // i18n
x.navpop.hasPopupMenu=true;
var len = text.length;
s.push('*' + dropdiv + menuTitle('popupsMenu') + '<menu>');
return '<div class="' + dropclass + '" style="--navpop-m-len:' + len + 'ch"><a href="#" noPopup=1>' + text + '</a>';
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(s) {
pg.structures.menus.popupRedirTitle = pg.structures.menus.popupTitle;
return '<a href="#" noPopup=1>' + popupString(s) + '</a>';
pg.structures.menus.popupRedirTopLinks = pg.structures.menus.popupTopLinks;
}


pg.structures.menus.popupRedirTitle=pg.structures.menus.popupTitle;
copyStructure('menus', 'shortmenus');
pg.structures.shortmenus.popupTopLinks = function (x) {
pg.structures.menus.popupRedirTopLinks=pg.structures.menus.popupTopLinks;
return pg.structures.menus.popupTopLinks(x, true);
};
pg.structures.shortmenus.popupRedirTopLinks = pg.structures.shortmenus.popupTopLinks;


copyStructure('menus', 'shortmenus');
pg.structures.lite = {};
pg.structures.lite.popupLayout = function () {
pg.structures.shortmenus.popupTopLinks=function(x) {
return pg.structures.menus.popupTopLinks(x,true);
return ['popupTitle', 'popupPreview'];
};
};
pg.structures.shortmenus.popupRedirTopLinks=pg.structures.shortmenus.popupTopLinks;
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


copyStructure('shortmenus', 'dabshortmenus');
// STARTFILE: autoedit.js
pg.structures.dabshortmenus.popupLayout=function () {
function substitute(data, cmdBody) {
return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks',
// alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags);
'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
var fromRe = RegExp(cmdBody.from, cmdBody.flags);
'popupData', 'popupMiscTools', ['popupRedlink'], 'popupFixDab',
return data.replace(fromRe, cmdBody.to);
'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview'];
}
};


copyStructure('menus', 'dabmenus');
function execCmds(data, cmdList) {
pg.structures.dabmenus.popupLayout=pg.structures.dabshortmenus.popupLayout;
for (var i = 0; i < cmdList.length; ++i) {

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

}
//</NOLITE>
return data;
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
//<NOLITE>
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) {
function parseCmd(str) {
// returns a list of commands
// returns a list of commands
if (!str.length) {
if (!str.length) { return []; }
var p=false;
return [];
switch (str.charAt(0)) {
}
case 's':
var p = false;
p=parseSubstitute(str);
switch (str.charAt(0)) {
break;
case 's':
default:
p = parseSubstitute(str);
break;
default:
return false;
}
if (p) {
return [p].concat(parseCmd(p.remainder));
}
return false;
return false;
}
}
if (p) { return [p].concat(parseCmd(p.remainder)); }
return false;
}


function unEscape(str, sep) {
// FIXME: Only used once here, confusing with native (and more widely-used) unescape, should probably be replaced
return str.split('\\\\').join('\\').split('\\'+sep).join(sep).split('\\n').join('\n');
// 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


function parseSubstitute(str) {
var from, to, flags, tmp;
// 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);


if (str.length<4) { return false; }
tmp = skipOver(str, sep);
var sep=str.charAt(1);
if (tmp) {
str=str.substring(2);
from = tmp.segment;
str = tmp.remainder;
} else {
return false;
}


tmp = skipOver(str, sep);
tmp=skipOver(str,sep);
if (tmp) { from=tmp.segment; str=tmp.remainder; }
if (tmp) {
else { return false; }
to = tmp.segment;
str = tmp.remainder;
} else {
return false;
}


tmp=skipOver(str,sep);
flags = '';
if (tmp) { to=tmp.segment; str=tmp.remainder; }
if (str.length) {
else { return false; }
tmp = skipOver(str, ';') || skipToEnd(str, ';');
if (tmp) {
flags = tmp.segment;
str = tmp.remainder;
}
}


flags='';
return {
if (str.length) {
action: substitute,
tmp=skipOver(str,';') || skipToEnd(str, ';');
from: from,
if (tmp) {flags=tmp.segment; str=tmp.remainder; }
to: to,
flags: flags,
remainder: str,
};
}
}


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;
function skipOver(str,sep) {
}
var segment = unEscape(str.substring(0, endSegment), sep);
var endSegment=findNext(str,sep);
if (endSegment<0) { return false; }
return { segment: segment, remainder: str.substring(endSegment + 1) };
var segment=unEscape(str.substring(0,endSegment), sep);
}
return {segment: segment, remainder: str.substring(endSegment+1)};
}

function skipToEnd(str,sep) {
return {segment: str, remainder: ''};
}


function findNext(str, ch) {
/*eslint-disable*/
function skipToEnd(str, sep) {
for (var i=0; i<str.length; ++i) {
if (str.charAt(i)=='\\') { i+=2; }
return { segment: str, remainder: '' };
if (str.charAt(i)==ch) { return i; }
}
}
return -1;
/*eslint-enable */
}


function findNext(str, ch) {
function setCheckbox(param, box) {
var val=mw.util.getParamValue(param);
for (var i = 0; i < str.length; ++i) {
if (str.charAt(i) == '\\') {
if (val) {
i += 2;
switch (val) {
case '1': case 'yes': case 'true':
}
box.checked=true;
if (str.charAt(i) == ch) {
return i;
break;
case '0': case 'no': case 'false':
}
box.checked=false;
}
}
return -1;
}
}
}


function setCheckbox(param, box) {
function autoEdit() {
if (!setupPopups.completed) { setupPopups(); }
var val = mw.util.getParamValue(param);
if (!mw.config.get('wgEnableAPI') || mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; }
if (val) {
if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) {
switch (val) {
pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action'));
case '1':
}
case 'yes':
if (!document.editform) { return false; }
case 'true':
if (autoEdit.alreadyRan) { return false; }
box.checked = true;
autoEdit.alreadyRan=true;
break;
var cmdString=mw.util.getParamValue('autoedit');
case '0':
if (cmdString) {
case 'no':
try {
case 'false':
var editbox=document.editform.wpTextbox1;
box.checked = false;
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');
function autoEdit() {
if (rvid) {
setupPopups(function () {
var url=pg.wiki.apiwikibase + '?action=query&format=json&prop=revisions&revids='+rvid;
if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version')) {
startDownload(url, null, autoEdit2);
return false;
} else { autoEdit2(); }
}
}
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);


function autoEdit2(d) {
var rvid = mw.util.getParamValue('autorv');
var summary=mw.util.getParamValue('autosummary');
if (rvid) {
var summaryprompt=mw.util.getParamValue('autosummaryprompt');
var url =
var summarynotice='';
pg.wiki.apiwikibase +
if (d && d.data && mw.util.getParamValue('autorv')) {
'?action=query&format=json&formatversion=2&prop=revisions&revids=' +
var s = getRvSummary(summary, d.data);
rvid;
if (s === false) {
startDownload(url, null, autoEdit2);
summaryprompt=true;
} else {
summarynotice=popupString('Failed to get revision information, please edit manually.\n\n');
autoEdit2();
summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']);
}
} else { summary = s; }
});
}
}
if (summaryprompt) {

var txt= summarynotice +
function autoEdit2(d) {
popupString('Enter a non-empty edit summary or press cancel to abort');
var summary = mw.util.getParamValue('autosummary');
var response=prompt(txt, summary);
var summaryprompt = mw.util.getParamValue('autosummaryprompt');
if (response) { summary=response; }
var summarynotice = '';
else { return; }
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);
}
}
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() {
function autoClickToken() {
return mw.user.sessionId();
return mw.user.sessionId();
}
}


function autoEdit3() {
function autoEdit3() {
if (mw.util.getParamValue('actoken') != autoClickToken()) {
if( mw.util.getParamValue('actoken') != autoClickToken()) { return; }
return;
}


var btn = mw.util.getParamValue('autoclick');
var btn=mw.util.getParamValue('autoclick');
if (btn) {
if (btn) {
if (document.editform && document.editform[btn]) {
if (document.editform && document.editform[btn]) {
var button = 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.',
var msg = tprintf(
[ button.value ]);
'The %s button has been automatically clicked. Please wait for the next page to load.',
bannerMessage(msg);
[button.value]
document.title='('+document.title+')';
);
bannerMessage(msg);
button.click();
} else {
document.title = '(' + document.title + ')';
alert(tprintf('Could not find button %s. Please check the settings in your javascript file.',
button.click();
} else {
[ btn ]));
alert(
tprintf('Could not find button %s. Please check the settings in your javascript file.', [
btn,
])
);
}
}
}
}
}
}


function bannerMessage(s) {
function bannerMessage(s) {
var headings = document.getElementsByTagName('h1');
var headings=document.getElementsByTagName('h1');
if (headings) {
if (headings) {
var div = document.createElement('div');
var div=document.createElement('div');
div.innerHTML = '<font size=+1><b>' + pg.escapeQuotesHTML(s) + '</b></font>';
div.innerHTML='<font size=+1><b>' + s + '</b></font>';
headings[0].parentNode.insertBefore(div, headings[0]);
headings[0].parentNode.insertBefore(div, headings[0]);
}
}
}
}


function getRvSummary(template, json) {
function getRvSummary(template, json) {
try {
try {
var o = getJsObj(json);
var o=getJsObj(json);
var edit = anyChild(o.query.pages).revisions[0];
var edit = anyChild(o.query.pages).revisions[0];
var timestamp = edit.timestamp
var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, '');
return simplePrintf(template, [edit.revid, timestamp, edit.userhidden === undefined ? edit.user : '(hidden)']);
.split(/[A-Z]/g)
} catch (badness) {
.join(' ')
return false;
.replace(/^ *| *$/g, '');
return simplePrintf(template, [
edit.revid,
timestamp,
edit.userhidden ? '(hidden)' : edit.user,
]);
} catch (badness) {
return false;
}
}
}
}


//</NOLITE>
// ENDFILE: autoedit.js
// ENDFILE: autoedit.js
// STARTFILE: downloader.js
/**
@fileoverview
{@link Downloader}, a xmlhttprequest wrapper, and helper functions.
*/


/**
// STARTFILE: downloader.js
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
* @file
@type String
* {@link Downloader}, a xmlhttprequest wrapper, and helper functions.
*/
*/
this.url = url;

/**
/**
A universally unique ID number
* Creates a new Downloader
@type integer
* @constructor
*/
* @class The Downloader class. Create a new instance of this class to download stuff.
this.id=null;
* @param {string} url The url to download. This can be omitted and supplied later.
/**
*/
Modification date, to be culled from the incoming headers
function Downloader(url) {
@type Date
if (typeof XMLHttpRequest != 'undefined') {
@private
this.http = new XMLHttpRequest();
*/
}
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 http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities.
@type String
*/
this.method='GET';
/**
Async flag.
@type boolean
*/
this.async=true;
}


new Downloader();
/**
* The url to download
* @type {string}
*/
this.url = url;


/** Submits the http request. */
/**
Downloader.prototype.send = function (x) {
* A universally unique ID number
if (!this.http) { return null; }
* @type {number}
return this.http.send(x);
*/
};
this.id = null;
/** 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);
};
/** Gets the state of the download. */
Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;};


pg.misc.downloadsInProgress = { };
/**
* Modification date, to be culled from the incoming headers
* @type Date
* @private
*/
this.lastModified = null;


/** Starts the download.
/**
Note that setTarget {@link Downloader#setTarget} must be run first
* What to do when the download completes successfully
*/
* @type {Function}
Downloader.prototype.start=function () {
* @private
if (!this.http) { return; }
*/
pg.misc.downloadsInProgress[this.id] = this;
this.callbackFunction = null;
this.http.send(null);
};


/** Gets the 'Last-Modified' date from the download headers.
/**
Should be run after the download completes.
* What to do on failure
Returns <code>null</code> on failure.
* @type {Function}
@return {Date}
* @private
*/
*/
Downloader.prototype.getLastModifiedDate=function () {
this.onFailure = null;
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.
/**
* Flag set on <code>abort</code>
@param {Function} f callback function, called as <code>f(this)</code> on success
*/
* @type {boolean}
Downloader.prototype.setCallback = function (f) {
*/
this.aborted = false;
if(!this.http) { return; }
this.http.onreadystatechange = f;
};


Downloader.prototype.getStatus = function() { if (!this.http) { return null; } return this.http.status; };
/**
* 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();
// helper functions


/** Creates a new {@link Downloader} and prepares it for action.
/** Submits the http request. */
@param {String} url The url to download
Downloader.prototype.send = function (x) {
@param {integer} id The ID of the {@link Downloader} object
if (!this.http) {
@param {Function} callback The callback function invoked on success
return null;
@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 ($.isFunction(onfailure)) {
onfailure(d,url,id,callback);
}
} catch (somerr) { /* ignore it */ }
}
}
return this.http.send(x);
};
};
d.setCallback(f);
return d;
}
/** Simulates a download from cached data.
The supplied data is put into a {@link Downloader} as if it had downloaded it.
@param {String} url The url.
@param {integer} id The ID.
@param {Function} callback The callback, which is invoked immediately as <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);
}


/**
/** Aborts the download, setting the <code>aborted</code> field to true. */
Starts a download.
Downloader.prototype.abort = function () {
@param {String} url The url to download
if (!this.http) {
@param {integer} id The ID of the {@link Downloader} object
return null;
@param {Function} callback The callback function invoked on success
}
@return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
this.aborted = true;
*/
return this.http.abort();
function startDownload(url, id, callback) {
};
var d=newDownload(url, id, callback);
if (typeof d == typeof '' ) { return d; }
d.start();
return d;
}


/**
/** Returns the downloaded data. */
Aborts all downloads which have been started.
Downloader.prototype.getData = function () {
*/
if (!this.http) {
function abortAllDownloads() {
return null;
for ( var x in pg.misc.downloadsInProgress ) {
}
try {
return this.http.responseText;
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)


/**
/** Prepares the download. */
* InstaView - a Mediawiki to HTML converter in JavaScript
Downloader.prototype.setTarget = function () {
* Version 0.6.1
if (!this.http) {
* Copyright (C) Pedro Fayolle 2005-2006
return null;
* http://en.wikipedia.org/wiki/User:Pilaf
}
* Distributed under the BSD license
this.http.open(this.method, this.url, this.async);
*
this.http.setRequestHeader('Api-User-Agent', pg.api.userAgent);
* 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)
*/


/** Gets the state of the download. */
Downloader.prototype.getReadyState = function () {
if (!this.http) {
return null;
}
return this.http.readyState;
};


var Insta = {};
pg.misc.downloadsInProgress = {};


function setupLivePreview() {
/**
* 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);
};


// options
/**
Insta.conf =
* Gets the 'Last-Modified' date from the download headers.
{
* Should be run after the download completes.
baseUrl: '',
* Returns <code>null</code> on failure.

* @return {Date}
user: {},
*/
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;
};


wiki: {
/**
lang: pg.wiki.lang,
* Sets the callback function.
interwiki: pg.wiki.interwiki,
* @param {Function} f callback function, called as <code>f(this)</code> on success
default_thumb_width: 180
*/
},
Downloader.prototype.setCallback = function (f) {

if (!this.http) {
paths: {
return;
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']
}
}
this.http.onreadystatechange = f;
};
};


// options with default values or backreferences
Downloader.prototype.getStatus = function () {
Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian';
if (!this.http) {
Insta.conf.user.signature = '[['+Insta.conf.locale.user+':'+Insta.conf.user.name+'|'+Insta.conf.user.name+']]';
return null;
//Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/';
}

return this.http.status;
// define constants
};
Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|'+Insta.conf.locale.image+
'):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i');


}
//////////////////////////////////////////////////
// helper functions



/**
Insta.dump = function(from, to)
* Creates a new {@link Downloader} and prepares it for action.
{
* @param {string} url The url to download
if (typeof from == 'string') { from = document.getElementById(from); }
* @param {number} id The ID of the {@link Downloader} object
if (typeof to == 'string') { to = document.getElementById(to); }
* @param {Function} callback The callback function invoked on success
to.innerHTML = this.convert(from.value);
* @return {string|Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
};
*/

function newDownload(url, id, callback, onfailure) {
Insta.convert = function(wiki)
var d = new Downloader(url);
{
if (!d.http) {
var ll = (typeof wiki == 'string')? wiki.replace(/\r/g,'').split(/\n/): wiki, // lines of wikicode
return 'ohdear';
o = '', // output
p = 0, // para flag
$r; // result of passing a regexp to $()

// 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; }
}
}
d.id = id;
return o+f;
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;
}
}

/**
function html_entities(s) {
* Simulates a download from cached data.
return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
* 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);
}
}

// Wiki text parsing to html is a nightmare.
/**
// The below functions deliberately don't escape the ampersand since this would make it more difficult,
* Starts a download.
// and we don't absolutely need to for how we need it.
* @param {string} url The url to download
// This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML.
* @param {number} id The ID of the {@link Downloader} object
// Browsers should all be able to handle it though.
* @param {Function} callback The callback function invoked on success
// We also escape significant wikimarkup characters to prevent further matching on the processed text
* @return {string|Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
function htmlescape_text(s) {
*/
return s.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/:/g,"&#58;").replace(/\[/g,"&#91;").replace(/]/g,"&#93;");
function startDownload(url, id, callback) {
}
var d = newDownload(url, id, callback);
function htmlescape_attr(s) {
if (typeof d == typeof '') {
return htmlescape_text(s).replace(/'/g,"&#39;").replace(/"/g,"&quot;");
return d;
}
d.start();
return d;
}
}


function max(a,b) { return (a>b)?a:b; }
/**
function min(a,b) { return (a<b)?a:b; }
* Aborts all downloads which have been started.

*/
// return the first non matching character position between two strings
function abortAllDownloads() {
function str_imatch(a, b)
for (var x in pg.misc.downloadsInProgress) {
{
try {
for (var i=0, l=min(a.length, b.length); i<l; i++) {
pg.misc.downloadsInProgress[x].aborted = true;
if (a.charAt(i)!=b.charAt(i)) { break; }
pg.misc.downloadsInProgress[x].abort();
delete pg.misc.downloadsInProgress[x];
} catch (e) {}
}
}
return i;
}
}
// ENDFILE: downloader.js


// compare current line against a string or regexp
// STARTFILE: livepreview.js
// if passed a string it will compare only the first string.length characters
// TODO: location is often not correct (eg relative links in previews)
// if passed a regexp the result is stored in $r
// NOTE: removed md5 and image and math parsing. was broken, lots of bytes.
function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)); }
/**
* 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)
*/


function $$(c) { return ll[0]==c; } // compare current line against a string
var Insta = {};
function _(p) { return ll[0].charAt(p); } // return char at pos p


function setupLivePreview() {
function endl(s) { ps(s); sh(); }
// options
Insta.conf = {
baseUrl: '',


function parse_list()
user: {},
{
var prev='';


while (remain() && $(/^([*#:;]+)(.*)$/)) {
wiki: {
lang: pg.wiki.lang,
interwiki: pg.wiki.interwiki,
default_thumb_width: 180,
},


var l_match = $r;
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: {
sh();
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',
],
},
};


var ipos = str_imatch(prev, l_match[1]);
// 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 + '/';


// close uncontinued lists
// define constants
for (var prevPos=prev.length-1; prevPos >= ipos; prevPos--) {
Insta.BLOCK_IMAGE = new RegExp(
'^\\[\\[(?:File|Image|' +
Insta.conf.locale.image +
'):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)',
'i'
);
}


var pi = prev.charAt(prevPos);
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);
};


if (pi=='*') { ps('</ul>'); }
Insta.convert = function (wiki) {
else if (pi=='#') { ps('</ol>'); }
var ll = typeof wiki == 'string' ? wiki.replace(/\r/g, '').split(/\n/) : wiki, // lines of wikicode
// close a dl only if the new item is not a dl item (:, ; or empty)
o = '', // output
else if($.inArray(l_match[1].charAt(prevPos), ['','*','#'])) { ps('</dl>'); }
p = 0, // para flag
}
r; // result of passing a regexp to compareLineStringOrReg()


// some shorthands
// open new lists
for (var matchPos=ipos; matchPos<l_match[1].length; matchPos++) {
function remain() {

return ll.length;
var li = l_match[1].charAt(matchPos);
}
function sh() {
return ll.shift();
} // shift
function ps(s) {
o += s;
} // push


if (li=='*') { ps('<ul>'); }
// similar to C's printf, uses ? as placeholders, ?? to escape question marks
else if (li=='#') { ps('<ol>'); }
function f() {
// open a new dl only if the prev item is not a dl item (:, ; or empty)
var i = 1,
else if ($.inArray(prev.charAt(matchPos), ['','*','#'])) { ps('<dl>'); }
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;
}


switch (l_match[1].charAt(l_match[1].length-1)) {
function html_entities(s) {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}


case '*': case '#':
// Wiki text parsing to html is a nightmare.
ps('<li>' + parse_inline_nowiki(l_match[2]));
// The below functions deliberately don't escape the ampersand since this would make it more
break;
// 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;');
}


case ';':
// return the first non matching character position between two strings
ps('<dt>');
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)) {
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;
break;

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

return i;
prev=l_match[1];
}
}


// close remaining lists
// compare current line against a string or regexp
for (var i=prev.length-1; i>=0; i--) {
// if passed a string it will compare only the first string.length characters
ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl')));
// 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) {
function parse_table()
{
return ll[0] == c;
endl(f('<table>', $(/^\{\|( .*)$/)? $r[1]: ''));
} // compare current line against a string
function charAtPoint(p) {
return ll[0].charAt(p);
} // return char at pos p


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


function parse_list() {
function parse_table_data()
{
var prev = '';
var td_line, match_i;


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


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


if (typeof td_match[3] != 'undefined') {
var ipos = str_imatch(prev, l_match[1]);


//ps(' ' + td_match[3])
// close uncontinued lists
match_i = 4;
for (var prevPos = prev.length - 1; prevPos >= ipos; prevPos--) {
var pi = prev.charAt(prevPos);


} else match_i = 2;
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>');
}
}


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


if (li == '*') {
if (td_match[1] != '|+') {
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>');
}
}


// use || or !! as a cell separator depending on context
switch (l_match[1].charAt(l_match[1].length - 1)) {
// NOTE: when split() is passed a regexp make sure to use non-capturing brackets
case '*':
td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/);
case '#':
ps('<li>' + parse_inline_nowiki(l_match[2]));
break;


ps(parse_inline_nowiki(td_line.shift()));
case ';':
ps('<dt>');


while (td_line.length) ll.unshift(td_match[1] + td_line.pop());
var dt_match = l_match[2].match(/(.*?)(:.*?)$/);


} else ps(td_match[match_i]);
// 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;


var tc = 0, td = [];
case ':':
ps('<dd>' + parse_inline_nowiki(l_match[2]));
}

prev = l_match[1];
}


while (remain()) {
// close remaining lists
td.push(sh());
for (var i = prev.length - 1; i >= 0; i--) {
if ($('|')) {
ps(f('</?>', prev.charAt(i) == '*' ? 'ul' : prev.charAt(i) == '#' ? 'ol' : 'dl'));
if (!tc) break; // we're at the outer-most level (no nested tables), skip to td parse
else if (_(1)=='}') tc--;
}
}
else if (!tc && $('!')) break;
else if ($('{|')) tc++;
}
}


if (td.length) ps(Insta.convert(td));
function parse_table() {
}
endl(f('<table>', compareLineStringOrReg(/^\{\|( .*)$/) ? r[1] : ''));


function parse_pre()
for (; remain(); ) {
{
if (compareLineStringOrReg('|')) {
ps('<pre>');
switch (charAtPoint(1)) {
do {
case '}':
endl(parse_inline_nowiki(ll[0].substring(1)) + "\n");
endl('</table>');
} while (remain() && $(' '));
return;
ps('</pre>');
case '-':
}
endl(f('<tr>', compareLineStringOrReg(/\|-*(.*)/)[1]));

break;
function parse_block_image()
default:
{
parse_table_data();
ps(parse_image(sh()));
}
}
} else if (compareLineStringOrReg('!')) {

parse_table_data();
function parse_image(str)
} else {
{
sh();
//<NOLITE>
// 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*/);
function parse_table_data() {
attr.push(last_attr);
var td_line, match_i;
filename = attr.shift();


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


if (td_match[1] == '|+') {
for (;attr.length; attr.shift()) {
w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/);
ps('<caption');
if (w_match) width = w_match[1];
} else {
else switch(attr[0]) {
ps('<t' + (td_match[1] == '|' ? 'd' : 'h'));
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;
if (typeof td_match[3] != 'undefined') {
//ps(' ' + td_match[3])
match_i = 4;
} else {
match_i = 2;
}


ps('>');


if (td_match[1] != '|+') {
var o='';
// 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] == '|' ? '||' : /(?:\|\||!!)/);


if (frame) {
ps(parse_inline_nowiki(td_line.shift()));


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


o += f("<div class='thumb t?'>", align);
var tc = 0,
td = [];


while (remain()) {
if (thumb) {
if (!width) width = Insta.conf.wiki.default_thumb_width;
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++;
}
}


o += f("<div style='width:?px;'>?", 2+width*1, make_image(filename, caption, width)) +
if (td.length) {
f("<div class='thumbcaption'><div class='magnify' style='float:right'><a href='?' title='Enlarge'></a></div>?</div>",
ps(Insta.convert(td));
htmlescape_attr(Insta.conf.paths.articles + Insta.conf.locale.image + ':' + filename),
parse_inline_nowiki(caption)
);
} else {
o += '<div>' + make_image(filename, caption) + f("<div class='thumbcaption'>?</div>", parse_inline_nowiki(caption));
}
}
}


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


} else if (align !== '') {
function parse_block_image() {
o += f("<div class='float?'><span>?</span></div>", align, make_image(filename, caption, width));
ps(parse_image(sh()));
} else {
return make_image(filename, caption, width);
}
}


return center? f("<div class='center'>?</div>", o): o;
function parse_image(str) {
//</NOLITE>
// get what's in between "[[Image:" and "]]"
}
var tag = str.substring(str.indexOf(':') + 1, str.length - 2);

var width;
function parse_inline_nowiki(str)
var attr = [],
{
filename,
var start, lastend=0;
caption = '';
var substart=0, nestlev=0, open, close, subloop;
var thumb = 0,
frame = 0,
var html='';
center = 0;
var align = '';


while (-1 != (start = str.indexOf('<nowiki>', substart))) {
if (tag.match(/\|/)) {
html += parse_inline_wiki(str.substring(lastend, start));
// manage nested links
var nesting = 0;
start += 8;
var last_attr;
substart = start;
subloop = true;
for (var i = tag.length - 1; i > 0; i--) {
do {
if (tag.charAt(i) == '|' && !nesting) {
last_attr = tag.substr(i + 1);
open = str.indexOf('<nowiki>', substart);
tag = tag.substring(0, i);
close = str.indexOf('</nowiki>', substart);
if (close<=open || open==-1) {
break;
if (close==-1) {
return html + html_entities(str.substr(start));
}
substart = close+9;
if (nestlev) {
nestlev--;
} else {
} else {
switch (tag.substr(i - 1, 2)) {
lastend = substart;
html += html_entities(str.substring(start, lastend-9));
case ']]':
nesting++;
subloop = false;
i--;
break;
case '[[':
nesting--;
i--;
}
}
}
} else {
substart = open+8;
nestlev++;
}
}
} while (subloop);
}


return html + parse_inline_wiki(str.substr(lastend));
attr = tag.split(/\s*\|\s*/);
}
attr.push(last_attr);
filename = attr.shift();


function make_image(filename, caption, width)
var w_match;
{
//<NOLITE>
// uppercase first letter in file name
filename = filename.charAt(0).toUpperCase() + filename.substr(1);
// replace spaces with underscores
filename = filename.replace(/ /g, '_');


caption = strip_inline_wiki(caption);
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;
}


var md5 = hex_md5(filename);
return '';
}


var source = md5.charAt(0) + '/' + md5.substr(0,2) + '/' + filename;
function parse_inline_nowiki(str) {
var start,
lastend = 0;
var substart = 0,
nestlev = 0,
open,
close,
subloop;
var html = '';


if (width) width = "width='" + width + "px'";
while ((start = str.indexOf('<nowiki>', substart)) != -1) {

html += parse_inline_wiki(str.substring(lastend, start));
var img = "<img onerror=\""+
start += 8;
pg.escapeQuotesHTML("this.onerror=null;this.src='"+pg.jsescape(Insta.conf.paths.images_fallback + source)+"'")+
substart = start;
"\" src=\""+pg.escapeQuotesHTML(Insta.conf.paths.images + source)+
subloop = true;
"\" "+(caption!=='' ? "alt=\"" +pg.escapeQuotesHTML(caption) + "\"" : '')+
" "+width+">";

return f("<a class='image' ? href=\"?\">?</a>",
(caption!=='')? "title=\"" + pg.escapeQuotesHTML(caption) + "\"" : '',
pg.escapeQuotesHTML(Insta.conf.paths.articles + Insta.conf.locale.image + ':' + filename), img);
//</NOLITE>
}

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

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


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


//</NOLITE>
function parse_inline_images(str) {
return str;
var start,
}
substart = 0,
nestlev = 0;
var loop, close, open, wiki, html;


// the output of this function doesn't respect the FILO structure of HTML
while ((start = str.indexOf('[[', substart)) != -1) {
// but since most browsers can handle it I'll save myself the hassle
if (
function parse_inline_formatting(str)
str.substr(start + 2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):', 'i'))
{
) {
var em,st,i,li,o='';
loop = true;
while ((i=str.indexOf("''",li))+1) {
substart = start;
o += str.substring(li,i);
do {
substart += 2;
li=i+2;
close = str.indexOf(']]', substart);
if (str.charAt(i+2)=="'") {
li++;
open = str.indexOf('[[', substart);
st=!st;
if (close <= open || open == -1) {
o+=st?'<strong>':'</strong>';
if (close == -1) {
} else {
return str;
}
em=!em;
o+=em?'<em>':'</em>';
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;
}
}
return o+str.substr(li);
}


function parse_inline_wiki(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
var aux_match;
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);
str = parse_inline_images(str);
str = parse_inline_formatting(str);


// math
// math
str = str.replace(/<(?:)math>(.*?)<\/math>/gi, '');
str = str.replace(/<(?:)math>(.*?)<\/math>/ig, function(_,p1){
return f("<img src='?.png'>", Insta.conf.paths.math+hex_md5(p1));
});


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


// text formatting
// text formatting
str =
return str.
// signatures
str
replace(/~{5}(?!~)/g, date).
// signatures
.replace(/~{5}(?!~)/g, date)
replace(/~{4}(?!~)/g, Insta.conf.user.name+' '+date).
.replace(/~{4}(?!~)/g, Insta.conf.user.name + ' ' + date)
replace(/~{3}(?!~)/g, Insta.conf.user.name).
.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);
}


// [[:Category:...]], [[:Image:...]], etc...
// begin parsing
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));}).
for (; remain(); ) {
// remove straight category and interwiki tags
if (compareLineStringOrReg(/^(={1,6})(.*)\1(.*)$/)) {
replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),'').
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]) + ' ');
}


// [[:Category:...|Links]], [[:Image:...|Links]], etc...
sh();
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]]
return o;
replace(/\[\[(\/[^|]*?)\]\]/g, function($0,$1){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1)); }).
};


// [[/Replaced|Relative links]]
function wiki2html(txt, baseurl) {
replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2)); }).
Insta.conf.baseUrl = baseurl;
return Insta.convert(txt);
}
// ENDFILE: livepreview.js


// [[Common links]]
// STARTFILE: pageinfo.js
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)); }).
function popupFilterPageSize(data) {
return formatBytes(data.length);
}


// [[Replaced|Links]]
function popupFilterCountLinks(data) {
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)); }).
var num = countLinks(data);
return String(num) + '&nbsp;' + (num != 1 ? popupString('wikiLinks') : popupString('wikiLink'));
}


// [[Stripped:Namespace|Namespace]]
function popupFilterCountImages(data) {
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)); }).
var num = countImages(data);
return String(num) + '&nbsp;' + (num != 1 ? popupString('images') : popupString('image'));
}


// External links
function popupFilterCountCategories(data) {
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)); }).
var num = countCategories(data);
replace(/\[http:\/\/(.*?)\]/g, function($0,$1){return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1)); }).
return (
replace(/\[(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($1), htmlescape_text($2) + htmlescape_text($3)); }).
String(num) + '&nbsp;' + (num != 1 ? popupString('categories') : popupString('category'))
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__','').
function popupFilterLastModified(data, download) {
replace('__NOEDITSECTION__','');
var lastmod = download.lastModified;
var now = new Date();
var age = now - lastmod;
if (lastmod && getValueOf('popupLastModified')) {
return tprintf('%s old', [formatAge(age)]).replace(/ /g, '&nbsp;');
}
return '';
}
}
/*

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


// begin parsing
function formatAge(age) {
for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) {
// coerce into a number
var a = 0 + age,
p=0;
endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3]));
aa = a;


} else if ($(/^[*#:;]/)) {
var seclen = 1000;
p=0;
var minlen = 60 * seclen;
parse_list();
var hourlen = 60 * minlen;
var daylen = 24 * hourlen;
var weeklen = 7 * daylen;


} else if ($(' ')) {
var numweeks = (a - (a % weeklen)) / weeklen;
p=0;
a = a - numweeks * weeklen;
parse_pre();
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) {
} else if ($('{|')) {
p=0;
return sweeks;
parse_table();
}
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;
}


} else if ($(/^----+$/)) {
function addunit(num, str) {
p=0;
return String(num) + ' ' + (num != 1 ? popupString(str + 's') : popupString(str));
endl('<hr />');
}


} else if ($(Insta.BLOCK_IMAGE)) {
function runPopupFilters(list, data, download) {
var ret = [];
p=0;
parse_block_image();
for (var i = 0; i < list.length; ++i) {

if (list[i] && typeof list[i] == 'function') {
} else {
var s = list[i](data, download, download.owner.article);

if (s) {
// handle paragraphs
ret.push(s);
if ($$('')) {
}
p = (remain()>1 && ll[1]===(''));
if (p) endl('<p><br>');
} else {
if(!p) {
ps('<p>');
p=1;
}
}
ps(parse_inline_nowiki(ll[0]) + ' ');
}
}

return ret;
sh();
}
}


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


function wiki2html(txt,baseurl) {
var popupFilters = getValueOf('popupFilters') || [];
Insta.conf.baseUrl=baseurl;
var extraPopupFilters = getValueOf('extraPopupFilters') || [];
return Insta.convert(txt);
var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download);
}
// ENDFILE: livepreview.js
// STARTFILE: pageinfo.js
//<NOLITE>
function popupFilterPageSize(data) {
return formatBytes(data.length);
}


function popupFilterCountLinks(data) {
var pageInfo = pageInfoArray.join(', ');
var num=countLinks(data);
if (pageInfo !== '') {
return String(num) + '&nbsp;' + ((num!=1)?popupString('wikiLinks'):popupString('wikiLink'));
pageInfo = upcaseFirst(pageInfo);
}
}
return pageInfo;
}


function popupFilterCountImages(data) {
// this could be improved!
var num=countImages(data);
function countLinks(wikiText) {
return String(num) + '&nbsp;' + ((num!=1)?popupString('images'):popupString('image'));
return wikiText.split('[[').length - 1;
}
}


function popupFilterCountCategories(data) {
// if N = # matches, n = # brackets, then
var num=countCategories(data);
// String.parenSplit(regex) intersperses the N+1 split elements
return String(num) + '&nbsp;' + ((num!=1)?popupString('categories'):popupString('category'));
// 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) {
function popupFilterLastModified(data,download) {
var lastmod=download.lastModified;
return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1);
var now=new Date();
var age=now-lastmod;
if (lastmod && getValueOf('popupLastModified')) {
return (tprintf('%s old', [formatAge(age)])).replace(RegExp(' ','g'), '&nbsp;');
}
}
return '';
}


function popupFilterStubDetect(data, download, article) {
function formatAge(age) {
// coerce into a number
var counts = stubCount(data, article);
var a=0+age, aa=a;
if (counts.real) {

return popupString('stub');
var seclen = 1000;
}
var minlen = 60*seclen;
if (counts.sect) {
var hourlen = 60*minlen;
return popupString('section stub');
var daylen = 24*hourlen;
}
var weeklen = 7*daylen;
return '';

}
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; }
function popupFilterDisambigDetect(data, download, article) {
if (aa > weeklen) { return sweeks + ' ' + sdays; }
if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
return '';
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 '' + 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 isDisambig(data, article) ? popupString('disambig') : '';
}
}
return ret;
}


function formatBytes(num) {
function getPageInfo(data, download) {
if (!data || data.length === 0) { return popupString('Empty page'); }
return num > 949 ?
Math.round(num / 100) / 10 + popupString('kB') :
num + '&nbsp;' + popupString('bytes');
}
// ENDFILE: pageinfo.js


var popupFilters=getValueOf('popupFilters') || [];
// STARTFILE: titles.js
var extraPopupFilters = getValueOf('extraPopupFilters') || [];
/**
var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download);
* @file 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.
*/


var pageInfo=pageInfoArray.join(', ');
/**
if (pageInfo !== '' ) { pageInfo = upcaseFirst(pageInfo); }
* Creates a new Stringwrapper.
return pageInfo;
* @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;


// this could be improved!
/**
function countLinks(wikiText) { return wikiText.split('[[').length - 1; }
* 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 = '';


// if N = # matches, n = # brackets, then
this.setUtf(val);
// String.parenSplit(regex) intersperses the N+1 split elements
}
// with Nn other elements. So total length is
Title.prototype = new Stringwrapper();
// L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1).
/**
* 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;
}


function countImages(wikiText) {
// NOTE : playing with decodeURI, encodeURI, escape, unescape,
return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1);
// we seem to be able to replicate the IE borked encoding
}


function countCategories(wikiText) {
// IE doesn't do this new-fangled utf-8 thing.
return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1);
// 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


function popupFilterStubDetect(data, download, article) {
// we fix up & for all browsers, just in case.
var counts=stubCount(data, article);
var splitted = h.split('?');
if (counts.real) { return popupString('stub'); }
splitted[0] = splitted[0].split('&').join('%26');
if (counts.sect) { return popupString('section stub'); }
return '';
}


function popupFilterDisambigDetect(data, download, article) {
h = splitted.join('?');
if (getValueOf('popupOnlyArticleDabStub') && article.namespace()) { return ''; }
return (isDisambig(data, article)) ? popupString('disambig') : '';
}


function formatBytes(num) {
var contribs = pg.re.contribs.exec(h);
return (num > 949) ? (Math.round(num/100)/10+popupString('kB')) : (num +'&nbsp;' + popupString('bytes')) ;
if (contribs) {
}
if (contribs[1] == 'title=') {
//</NOLITE>
contribs[3] = contribs[3].split('+').join(' ');
// ENDFILE: pageinfo.js
}
// STARTFILE: titles.js
var u = new Title(contribs[3]);
/**
this.setUtf(
@fileoverview Defines the {@link Title} class, and associated crufty functions.
this.decodeNasties(
mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace()
)
);
return this;
}


<code>Title</code> deals with article titles and their various
var email = pg.re.email.exec(h);
forms. {@link Stringwrapper} is the parent class of
if (email) {
<code>Title</code>, which exists simply to make things a little
this.setUtf(
neater.
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.
Creates a new Stringwrapper.
var specialdiff = pg.re.specialdiff.exec(h);
@constructor
if (specialdiff) {
this.setUtf(
this.decodeNasties(
new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff')
)
);
return this;
}


@class the Stringwrapper class. This base class is not really
// no more special cases to check --
useful on its own; it just wraps various common string operations.
// hopefully it's not a disguised user-related or specially treated special page
*/
// Includes references
function Stringwrapper() {
var m = pg.re.main.exec(h);
/**
if (m === null) {
Wrapper for this.toString().indexOf()
this.value = null;
@param {String} x
} else {
@type integer
var fromBotInterface = /[?](.+[&])?title=/.test(h);
*/
if (fromBotInterface) {
this.indexOf=function(x){return this.toString().indexOf(x);};
m[2] = m[2].split('+').join('_');
/**
}
Returns this.value.
var extracted = m[2] + (m[3] ? '#' + m[3] : '');
@type String
if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) {
*/
// Fix Safari issue
this.toString=function(){return this.value;};
// Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3.
/**
this.setUtf(decodeURIComponent(unescape(extracted)));
Wrapper for {@link String#parenSplit} applied to this.toString()
} else {
@param {RegExp} x
this.setUtf(this.decodeNasties(extracted));
@type Array
}
*/
}
this.parenSplit=function(x){return this.toString().parenSplit(x);};
return this;
/**
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);
};
};
/**
Title.prototype.decodeNasties = function (txt) {
Wrapper for this.toString().split()
// myDecodeURI uses decodeExtras, which removes _,
@param {String} x
// thus ruining citations previews, which are formated as "cite_note-1"
@type Array
try {
*/
var ret = decodeURI(this.decodeEscapes(txt));
this.split=function(x){return this.toString().split(x);};
ret = ret.replace(/[_ ]*$/, '');
/**
return ret;
Wrapper for this.toString().replace()
} catch (e) {
@param {String} x
return txt; // cannot decode
@param {String} y
}
@type String
};
*/
// Decode valid %-encodings, otherwise escape them
this.replace=function(x,y){ return this.toString().replace(x,y); };
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;
Creates a new <code>Title</code>.
return null;
@constructor
};
// 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;
}


@class The Title class. Holds article titles and converts them into
var namespaceId = this.namespaceId();
various forms. Also deals with anchors, by which we mean the bits
if (namespaceId >= 0 && namespaceId % 2 == 1) {
of the article URL after a # character, representing locations
//non-special and talk namespace
within an article.
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;
}
}


@param {String} value The initial value to assign to the
this.value = null;
article. This must be the canonical title (see {@link
return null;
Title#value}. Omit this in the constructor and use another function
};
to set the title if this is unavailable.
Title.prototype.articleFromTalkPage = function () {
*/
var t = new Title(this.value);
function Title(val) {
t.toArticleFromTalkPage();
/**
if (t.value) {
The canonical article title. This must be in UTF-8 with no
return t;
entities, escaping or nasties. Also, underscores should be
}
replaced with spaces.
return null;
@type String
};
@private
Title.prototype.articleFromTalkOrArticle = function () {
*/
var t = new Title(this.value);
this.value=null;
if (t.toArticleFromTalkPage()) {
/**
return t;
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;
for (var j=1; j<len; j+=2) {
// FIXME s/decodeURI/decodeURIComponent/g ?
split[j]=decodeURIComponent(split[j].split('.').join('%')).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;
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();
};


// NOTE : playing with decodeURI, encodeURI, escape, unescape,
function parseParams(url) {
// we seem to be able to replicate the IE borked encoding
var specialDiff = pg.re.specialdiff.exec(url);

if (specialDiff) {
// IE doesn't do this new-fangled utf-8 thing.
var split = specialDiff[1].split('/');
// and it's worse than that.
if (split.length == 1) {
// IE seems to treat the query string differently to the rest of the url
return { oldid: split[0], diff: 'prev' };
// the query is treated as bona-fide utf8, but the first bit of the url is pissed around with
} else if (split.length == 2) {
return { oldid: split[0], diff: split[1] };
}
}


// we fix up & for all browsers, just in case.
var ret = {};
var splitted=h.split('?');
if (url.indexOf('?') == -1) {
splitted[0]=splitted[0].split('&').join('%26');
return ret;

}
if (pg.flag.linksLikeIE6) {
url = url.split('#')[0];
splitted[0]=encodeURI(decode_utf8(splitted[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;
}
}


h=splitted.join('?');
// (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup)
// (b) change spaces to underscores
// (c) encodeURI (just the straight one, no pg.re.urlNoPopup)


var contribs=pg.re.contribs.exec(h);
function myDecodeURI(str) {
if (contribs) {
var ret;
if (contribs[1]=='title=') { contribs[3]=contribs[3].split('+').join(' '); }
// FIXME decodeURIComponent??
var u=new Title(contribs[3]);
try {
this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace()));
ret = decodeURI(str.toString());
return this;
} 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;
}
}


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


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


//A dummy title object for a Special:Diff link.
function isDisambig(data, article) {
var specialdiff=pg.re.specialdiff.exec(h);
if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
if (specialdiff) {
return false;
this.setUtf(this.decodeNasties(new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff')));
}
return this;
return !article.isTalkPage() && pg.re.disambig.test(data);
}
}


// no more special cases to check --
function stubCount(data, article) {
// hopefully it's not a disguised user-related or specially treated special page
if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
var m=pg.re.main.exec(h);
return false;
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] : '');
var sectStub = 0;
if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) {
var realStub = 0;
// Fix Safari issue
if (pg.re.stub.test(data)) {
// Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3.
var s = data.parenSplit(pg.re.stub);
this.setUtf(decodeURIComponent(unescape(extracted)));
for (var i = 1; i < s.length; i = i + 2) {
if (s[i]) {
} else {
this.setUtf(this.decodeNasties(extracted));
++sectStub;
} else {
++realStub;
}
}
}
}
return { real: realStub, sect: sectStub };
}
}
return this;

};
function isValidImageName(str) {
Title.prototype.decodeNasties=function(txt) {
// extend as needed...
var ret= this.decodeEscapes(decodeURI(txt));
return str.indexOf('{') == -1;
ret = ret.replace(/[_ ]*$/, '');
return ret;
};
Title.prototype.decodeEscapes=function(txt) {
var split=txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/);
var len=split.length;
for (var i=1; i<len; i=i+2) {
// FIXME is decodeURIComponent better?
split[i]=unescape(split[i]);
}
}
return split.join('');

};
function isInStrippableNamespace(article) {
Title.fromAnchor=function(a) {
// Does the namespace allow subpages
return new Title().fromAnchor(a);
// Note, would be better if we had access to wgNamespacesWithSubpages
};
return article.namespaceId() !== 0;
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
if (!pg.flag.linksLikeIE6) { txt=myDecodeURI(txt); }
this.setUtf(txt);
return this;
};
Title.prototype.hintValue=function(){
if(!this.value) { return ''; }
return safeDecodeURI(this.value);
};
//<NOLITE>
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];

};
function isInMainNamespace(article) {
Title.prototype.userName=function(withNs) {
return article.namespaceId() === 0;
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 http://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;
function anchorContainsImage(a) {
return null;
// iterate over children of anchor a
};
// see if any are images
//</NOLITE>
if (a === null) {
// Return canonical, localized namespace
return false;
Title.prototype.namespace=function() {
}
return mw.config.get('wgFormattedNamespaces')[this.namespaceId()];
var kids = a.childNodes;
};
for (var i = 0; i < kids.length; ++i) {
Title.prototype.namespaceId=function() {
if (kids[i].nodeName == 'IMG') {
var n=this.value.indexOf(':');
return true;
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;
};
//<NOLITE>
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;
}
}
return false;
}
}
function isPopupLink(a) {
// NB for performance reasons, TOC links generally return true
// they should be stripped out later


this.value=null;
if (!markNopopupSpanLinks.done) {
return null;
markNopopupSpanLinks();
}
};
Title.prototype.articleFromTalkPage=function() {
if (a.inNopopupSpan) {
var t=new Title(this.value);
return false;
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());
};
//</NOLITE>
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=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();
};


// 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() {
function paramValue(param, url) {
var s=url.parenSplit(RegExp('[?&]' + literalizeRegex(param) + '=([^?&]*)'));
if (!getValueOf('popupOnlyArticleLinks')) {
if (!url) { return null; }
fixVectorMenuPopups();
return s[1] || null;
}
}


function parseParams(url) {
var s = $('.nopopups').toArray();
var specialDiff = pg.re.specialdiff.exec(url);
for (var i = 0; i < s.length; ++i) {
if (specialDiff)
var as = s[i].getElementsByTagName('a');
{
for (var j = 0; j < as.length; ++j) {
var split= specialDiff[1].split('/');
as[j].inNopopupSpan = true;
if (split.length==1) return {oldid:split[0], diff: 'prev'};
}
else if (split.length==2) return {oldid: split[0], diff: split[1]};
}

markNopopupSpanLinks.done = true;
}
}


var ret={};
function fixVectorMenuPopups() {
if (url.indexOf('?')==-1) { return ret; }
$('nav.vector-menu h3:first a:first').prop('inNopopupSpan', true);
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
// ENDFILE: titles.js
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;
}


// all sorts of stuff here
// STARTFILE: getpage.js
// FIXME almost everything needs to be rewritten
//////////////////////////////////////////////////
// Wiki-specific downloading
//


function oldidFromAnchor(a) { return paramValue('oldid', a.href); }
// Schematic for a getWiki call
//function diffFromAnchor(a) { return paramValue('diff', a.href); }
//
// getPageWithCaching
// |
// false | true
// getPage<-[findPictureInCache]->-onComplete(a fake download)
// \.
// (async)->addPageToCache(download)->-onComplete(download)


// check cache to see if page exists


function wikiMarkupToAddressFragment (str) { // for images
function getPageWithCaching(url, onComplete, owner) {
var ret = safeDecodeURI(str);
log('getPageWithCaching, url=' + url);
ret = ret.split(' ').join('_');
var i = findInPageCache(url);
ret = encodeURI(ret);
var d;
return ret;
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;
}
}
}


// (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup)
function getPage(url, onComplete, owner) {
// (b) change spaces to underscores
log('getPage');
// (c) encodeURI (just the straight one, no pg.re.urlNoPopup)
var callback = function (d) {
if (!d.aborted) {
addPageToCache(d);
onComplete(d);
}
};
return startDownload(url, owner.idNumber, callback);
}


function findInPageCache(url) {
function myDecodeURI (str) {
var ret;
for (var i = 0; i < pg.cache.pages.length; ++i) {
// FIXME decodeURIComponent??
if (url == pg.cache.pages[i].url) {
try { ret=decodeURI(str.toString()); }
return i;
catch (summat) { return str; }
}
for (var i=0; i<pg.misc.decodeExtras.length; ++i) {
}
var from=pg.misc.decodeExtras[i].from;
return -1;
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; }
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
// TESTS //
//////////////////////////////////////////////////
///////////
// parenSplit


//<NOLITE>
// String.prototype.parenSplit should do what ECMAscript says String.prototype.split does,
function isIpUser(user) {return pg.re.ipUser.test(user);}
// interspersing paren matches (regex capturing groups) between the split elements.
// i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c']


function isDisambig(data, article) {
if (String('abc'.split(/(b)/)) != 'a,b,c') {
if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; }
// broken String.split, e.g. konq, IE < 10
return ! article.isTalkPage() && pg.re.disambig.test(data);
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) {
function stubCount(data, article) {
if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; }
var s = re.toString();
var flags = '';
var sectStub=0;
var realStub=0;
for (var j = s.length; s.charAt(j) != '/'; --j) {
if (s.charAt(j) != 'g') {
if (pg.re.stub.test(data)) {
var s=data.parenSplit(pg.re.stub);
flags += s.charAt(j);
for (var i=1; i<s.length; i=i+2) {
}
if (s[i]) { ++sectStub; }
else { ++realStub; }
}
}
var t = s.substring(1, j);
return RegExp(t, flags);
}
}
return { real: realStub, sect: sectStub };
// ENDFILE: parensplit.js
}


function isValidImageName(str){ // extend as needed...
// STARTFILE: tools.js
return ( str.indexOf('{') == -1 );
// 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


function isInStrippableNamespace(article) {
// Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm
//I believe that this method means to return whether the given article is in a namspace without subpages. Meaning, it's broken.
return ( article.namespace() !== '' );
}


function isInMainNamespace(article) { return !isInStrippableNamespace(article); }
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) {
function anchorContainsImage(a) {
// iterate over children of anchor a
for (var p in obj) {
// see if any are images
return obj[p];
if (a === null) { return false; }
}
kids=a.childNodes;
return null;
for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } }
}
return false;
}
//</NOLITE>
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 upcaseFirst(str) {
function markNopopupSpanLinks() {
if( !getValueOf('popupOnlyArticleLinks'))
if (typeof str != typeof '' || str === '') {
fixVectorMenuPopups();
return '';

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;
}
}
return str.charAt(0).toUpperCase() + str.substring(1);
}
}
markNopopupSpanLinks.done=true;
}


function findInArray(arr, foo) {
function fixVectorMenuPopups() {
$('div.vectorMenu h3:first a:first').prop('inNopopupSpan', true);
if (!arr || !arr.length) {
}
return -1;
// ENDFILE: titles.js
// STARTFILE: cookies.js
//<NOLITE>
//////////////////////////////////////////////////
// Cookie handling
// from http://www.quirksmode.org/js/cookies.html

var Cookie= {
create: function(name,value,days)
{
var expires;
if (days)
{
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
expires = "; expires="+date.toGMTString();
}
}
var len = arr.length;
else { expires = ""; }
document.cookie = name+"="+value+expires+"; path=/";
for (var i = 0; i < len; ++i) {
},
if (arr[i] == foo) {
return i;
}
}
return -1;
}


read: function(name)
/* eslint-disable no-unused-vars */
{
function nextOne(array, value) {
var nameEQ = name + "=";
// NB if the array has two consecutive entries equal
var ca = document.cookie.split(';');
// then this will loop on successive calls
var i = findInArray(array, value);
for(var i=0;i < ca.length;i++)
if (i < 0) {
{
return null;
var c = ca[i];
while (c.charAt(0)==' ') { c = c.substring(1,c.length); }
if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
}
}
return array[i + 1];
return null;
}
},
/* eslint-enable no-unused-vars */


function literalizeRegex(str) {
erase: function(name)
{
return mw.util.escapeRegExp(str);
Cookie.create(name,"",-1);
}
}
};
//</NOLITE>
// ENDFILE: cookies.js
// STARTFILE: getpage.js
//////////////////////////////////////////////////
// Wiki-specific downloading
//


// Schematic for a getWiki call
String.prototype.entify = function () {
//
//var shy='&shy;';
// getWiki->-getPageWithCaching
return this.split('&')
// |
.join('&amp;')
// false | true
.split('<')
// getPage<-[findPictureInCache]->-onComplete(a fake download)
.join('&lt;')
// \.
.split('>')
// (async)->addPageToCache(download)->-onComplete(download)
.join('&gt;' /*+shy*/)
.split('"')
.join('&quot;');
};


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


/** @todo {document}
function joinPath(list) {
@param {Title} article
return list.filter(removeNulls).join('/');
@param {Function} onComplete
}
@param {integer} oldid
@param {Navapopup} owner
*/
function getWiki(article, onComplete, oldid, owner) {
// set ctype=text/css to get around opera gzip bug
var url = pg.wiki.titlebase;
if (article.namespaceId() >= 0)
url += article.removeAnchor().urlString();
if (oldid || oldid === 0 || oldid==='0')
url += '&oldid='+oldid;
url += '&action=raw';


getPageWithCaching(url, onComplete, owner);
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('');
}


// check cache to see if page exists
/* eslint-disable no-unused-vars */
function isString(x) {
return typeof x === 'string' || x instanceof String;
}


function isNumber(x) {
function getPageWithCaching(url, onComplete, owner) {
log('getPageWithCaching, url='+url);
return typeof x === 'number' || x instanceof Number;
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 isRegExp(x) {
function getPage(url, onComplete, owner) {
log('getPage');
return x instanceof RegExp;
var callback= function (d) { if (!d.aborted) {addPageToCache(d); onComplete(d);} };
}
return startDownload(url, owner.idNumber, callback);
}


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


function isObject(x) {
function addPageToCache(download) {
log('addPageToCache '+download.url);
return x instanceof Object;
var page = {url: download.url, data: download.data, lastModified: download.lastModified};
}
return pg.cache.pages.push(page);
}
// ENDFILE: getpage.js
// STARTFILE: md5-2.2alpha.js
//<NOLITE>
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2-alpha Copyright (C) Paul Johnston 1999 - 2005
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/


/*
function isFunction(x) {
* Configurable variables. You may need to tweak these to be compatible with
return !isRegExp(x) && (typeof x === 'function' || x instanceof Function);
* the server-side, but the defaults work in most cases.
}
*/
/* eslint-enable no-unused-vars */
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */


/*
function repeatString(s, mult) {
* These are the functions you'll usually want to call
var ret = '';
* They take string arguments and return either hex or base-64 encoded strings
for (var i = 0; i < mult; ++i) {
*/
ret += s;
function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
}
function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
return ret;
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
}
function hex_hmac_md5(k, d)
{ return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_md5(k, d)
{ return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_md5(k, d, e)
{ return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }


/*
function zeroFill(s, min) {
* Perform a simple self-test to see if the VM is working
min = min || 2;
*/
var t = s.toString();
function md5_vm_test()
return repeatString('0', min - t.length) + t;
{
}
return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}


/*
function map(f, o) {
* Calculate the MD5 of a raw string
if (isArray(o)) {
*/
return map_array(f, o);
function rstr_md5(s)
}
{
return map_object(f, o);
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}
}
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) {
* Calculate the HMAC-MD5, of a key and some data (raw strings)
return text
*/
.replace(/&/g, '&amp;')
function rstr_hmac_md5(key, data)
.replace(/"/g, '&quot;')
{
.replace(/</g, '&lt;')
var bkey = rstr2binl(key);
.replace(/>/g, '&gt;');
if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
};


var ipad = Array(16), opad = Array(16);
pg.unescapeQuotesHTML = function (html) {
for(var i = 0; i < 16; i++)
// From https://stackoverflow.com/a/7394787
{
// This seems to be implemented correctly on all major browsers now, so we
ipad[i] = bkey[i] ^ 0x36363636;
// don't have to make our own function.
opad[i] = bkey[i] ^ 0x5C5C5C5C;
var txt = document.createElement('textarea');
}
txt.innerHTML = html;
return txt.value;
};


var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
// ENDFILE: tools.js
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}


/*
// STARTFILE: dab.js
* Convert a raw string to a hex string
//////////////////////////////////////////////////
*/
// Dab-fixing code
function rstr2hex(input)
//
{
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
var output = "";
var x;
for(var i = 0; i < input.length; i++)
{
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0F) +
hex_tab.charAt( x & 0x0F);
}
return output;
}


/*
function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) {
* Convert a raw string to a base-64 string
log('retargetDab: newTarget=' + newTarget + ' oldTarget=' + oldTarget);
*/
return changeLinkTargetLink({
function rstr2b64(input)
newTarget: newTarget,
{
text: newTarget.split(' ').join('&nbsp;'),
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
hint: tprintf('disambigHint', [newTarget]),
var output = "";
summary: simplePrintf(getValueOf('popupFixDabsSummary'), [
var len = input.length;
friendlyCurrentArticleName,
for(var i = 0; i < len; i += 3)
newTarget,
{
]),
var triplet = (input.charCodeAt(i) << 16)
clickButton: getValueOf('popupDabsAutoClick'),
| (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
minor: true,
| (i + 2 < len ? input.charCodeAt(i+2) : 0);
oldTarget: oldTarget,
for(var j = 0; j < 4; j++)
watch: getValueOf('popupWatchDisambiggedPages'),
{
title: titleToEdit,
if(i * 8 + j * 6 > input.length * 8) output += b64pad;
});
else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
}
}
}
return output;
}


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


/* Convert to an array of 16-bit big-endian values, forming the dividend */
for (var i = 1; i < splitted.length; i = i + 3) {
var dividend = Array(input.length / 2);
if (
for(i = 0; i < dividend.length; i++)
typeof splitted[i] == typeof 'string' &&
{
splitted[i].length > 0 &&
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
!omitRegex.test(splitted[i])
}
) {
ret.push(retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit));
} /* if */
} /* for loop */


/*
ret = rmDupesFromSortedList(ret.sort());
* Repeatedly perform a long division. The binary array forms the dividend,
* the length of the encoding is the divisor. Once computed, the quotient
* forms the dividend for the next step. We stop when the dividend is zero.
* All remainders are stored for later use.
*/
while(dividend.length > 0)
{
quotient = Array();
x = 0;
for(i = 0; i < dividend.length; i++)
{
x = (x << 16) + dividend[i];
q = Math.floor(x / divisor);
x -= q * divisor;
if(quotient.length > 0 || q > 0)
quotient[quotient.length] = q;
}
remainders[remainders.length] = x;
dividend = quotient;
}


/* Convert the remainders to the output string */
if (wikPos) {
var wikTarget =
var output = "";
for(i = remainders.length - 1; i >= 0; i--)
'wiktionary:' +
output += encoding.charAt(remainders[i]);
friendlyCurrentArticleName.replace(/^(.+)\s+[(][^)]+[)]\s*$/, '$1');


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


/*
ret[meth](retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit));
* Encode a string as utf-8.
}
* For efficiency, this assumes the input is valid utf-16.
*/
function str2rstr_utf8(input)
{
var output = "";
var i = -1;
var x, y;


while(++i < input.length)
ret.push(
{
changeLinkTargetLink({
/* Decode utf-16 surrogate pairs */
newTarget: null,
x = input.charCodeAt(i);
text: popupString('remove this link').split(' ').join('&nbsp;'),
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
hint: popupString('remove all links to this disambig page from this article'),
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
clickButton: getValueOf('popupDabsAutoClick'),
{
oldTarget: oldTarget,
x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]),
i++;
watch: getValueOf('popupWatchDisambiggedPages'),
title: titleToEdit,
})
);
return ret;
}
}


/* Encode output as utf-8 */
function rmDupesFromSortedList(list) {
if(x <= 0x7F)
var ret = [];
output += String.fromCharCode(x);
for (var i = 0; i < list.length; ++i) {
else if(x <= 0x7FF)
if (ret.length === 0 || list[i] != ret[ret.length - 1]) {
output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
ret.push(list[i]);
0x80 | ( x & 0x3F));
}
else if(x <= 0xFFFF)
}
output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
return ret;
0x80 | ((x >>> 6 ) & 0x3F),
}
0x80 | ( x & 0x3F));
else if(x <= 0x1FFFFF)
output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
0x80 | ((x >>> 12) & 0x3F),
0x80 | ((x >>> 6 ) & 0x3F),
0x80 | ( x & 0x3F));
}
return output;
}


/*
function makeFixDab(data, navpop) {
* Encode a string as utf-16
// grab title from parent popup if there is one; default exists in changeLinkTargetLink
*/
var titleToEdit = navpop.parentPopup && navpop.parentPopup.article.toString();
function str2rstr_utf16le(input)
var list = listLinks(data, navpop.originalArticle, titleToEdit);
{
if (list.length === 0) {
var output = "";
log('listLinks returned empty list');
for(var i = 0; i < input.length; i++)
return null;
output += String.fromCharCode( input.charCodeAt(i) & 0xFF,
}
(input.charCodeAt(i) >>> 8) & 0xFF);
var html = '<hr />' + popupString('Click to disambiguate this link to:') + '<br>';
return output;
html += list.join(', ');
}
return html;
}


function makeFixDabs(wikiText, navpop) {
function str2rstr_utf16be(input)
{
if (
var output = "";
getValueOf('popupFixDabs') &&
for(var i = 0; i < input.length; i++)
isDisambig(wikiText, navpop.article) &&
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
Title.fromURL(location.href).namespaceId() != pg.nsSpecialId &&
input.charCodeAt(i) & 0xFF);
navpop.article.talkPage()
return output;
) {
}
setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber);
}
}


/*
function popupRedlinkHTML(article) {
* Convert a raw string to an array of little-endian words
return changeLinkTargetLink({
* Characters >255 have their high-byte silently ignored.
newTarget: null,
*/
text: popupString('remove this link').split(' ').join('&nbsp;'),
function rstr2binl(input)
hint: popupString('remove all links to this page from this article'),
{
clickButton: getValueOf('popupRedlinkAutoClick'),
var output = Array(input.length >> 2);
oldTarget: article.toString(),
for(var i = 0; i < output.length; i++)
summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()]),
output[i] = 0;
});
for(i = 0; i < input.length * 8; i += 8)
}
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
// ENDFILE: dab.js
return output;
}


/*
// STARTFILE: htmloutput.js
* Convert an array of little-endian words to a string
*/
function binl2rstr(input)
{
var output = "";
for(var i = 0; i < input.length * 32; i += 8)
output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
return output;
}


/*
// this has to use a timer loop as we don't know if the DOM element exists when we want to set the text
* Calculate the MD5 of an array of little-endian words, and a bit length.
function setPopupHTML(str, elementId, popupId, onSuccess, append) {
*/
if (typeof popupId === 'undefined') {
function binl_md5(x, len)
//console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100));
{
popupId = pg.idNumber;
/* append padding */
}
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;


var a = 1732584193;
var popupElement = document.getElementById(elementId + popupId);
var b = -271733879;
if (popupElement) {
var c = -1732584194;
if (!append) {
var d = 271733878;
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;
}


for(var i = 0; i < x.length; i += 16)
function setPopupTrailer(str, id) {
{
return setPopupHTML(str, 'popupData', id);
var olda = a;
}
var oldb = b;
var oldc = c;
var oldd = d;


a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
// args.navpopup is mandatory
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
// optional: args.redir, args.redirTarget
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
// FIXME: ye gods, this is ugly stuff
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
function fillEmptySpans(args) {
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
// if redir is present and true then redirTarget is mandatory
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
var redir = true;
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
var rcid;
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) {
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
redir = false;
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
}
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
var a = args.navpopup.parentAnchor;
b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i+15], 22, 1236535329);


a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
var article,
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
hint = null,
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
oldid = null,
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
params = {};
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
if (redir && typeof args.redirTarget == typeof {}) {
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
article = args.redirTarget;
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
//hint=article.hintValue();
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
} else {
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
article = new Title().fromAnchor(a);
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
hint = a.originalTitle || article.hintValue();
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
params = parseParams(a.href);
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
oldid = getValueOf('popupHistoricalLinks') ? params.oldid : null;
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
rcid = params.rcid;
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
}
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
var x = {
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
article: article,
hint: hint,
oldid: oldid,
rcid: rcid,
navpop: args.navpopup,
params: params,
};


a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
var structure = pg.structures[getValueOf('popupStructure')];
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
if (typeof structure != 'object') {
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
setPopupHTML(
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
'popupError',
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
'Unknown structure (this should never happen): ' + pg.option.popupStructure,
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
args.navpopup.idNumber
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
);
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
return;
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
}
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
var spans = flatten(pg.misc.layout);
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
var numspans = spans.length;
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
var redirs = pg.misc.redirSpans;
a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);


a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
for (var i = 0; i < numspans; ++i) {
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
var found = redirs && redirs.indexOf(spans[i]) !== -1;
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
//log('redir='+redir+', found='+found+', spans[i]='+spans[i]);
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
if ((found && !redir) || (!found && redir)) {
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
//log('skipping this set of the loop');
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
continue;
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
}
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
var structurefn = structure[spans[i]];
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
if (structurefn === undefined) {
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
// nothing to do for this structure part
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
continue;
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
}
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
var setfn = setPopupHTML;
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
if (
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
getValueOf('popupActiveNavlinks') &&
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
(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;
}
}
}


a = safe_add(a, olda);
// flatten an array
b = safe_add(b, oldb);
function flatten(list, start) {
c = safe_add(c, oldc);
var ret = [];
d = safe_add(d, oldd);
if (typeof start == 'undefined') {
}
start = 0;
return Array(a, b, c, d);
}
}
for (var i = start; i < list.length; ++i) {

if (typeof list[i] == typeof []) {
/*
return ret.concat(flatten(list[i])).concat(flatten(list, i + 1));
* These functions implement the four basic operations the algorithm uses.
} else {
*/
ret.push(list[i]);
function md5_cmn(q, a, b, x, s, t)
{
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
function safe_add(x, y)
{
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}

/*
* Bitwise rotate a 32-bit number to the left.
*/
function bit_rol(num, cnt)
{
return (num << cnt) | (num >>> (32 - cnt));
}
//</NOLITE>
// ENDFILE: md5-2.2alpha.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
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;
return ret;
};
} else {
String.prototype.parenSplit=function (re) { return this.split(re); };
String.prototype.parenSplit.isNative=true;
}

function nonGlobalRegex(re) {
var s=re.toString();
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
// Generate html for whole popup

function popupHTML(a) {
//<NOLITE>
getValueOf('popupStructure');
function encode_utf8(rohtext) {
var structure = pg.structures[pg.option.popupStructure];
// dient der Normalisierung des Zeilenumbruchs
if (typeof structure != 'object') {
rohtext = rohtext.replace(/\r\n/g,"\n");
//return 'Unknown structure: '+pg.option.popupStructure;
var utftext = "";
// override user choice
for(var n=0; n<rohtext.length; n++)
pg.option.popupStructure = pg.optionDefault.popupStructure;
{
return popupHTML(a);
// ermitteln des Unicodes des aktuellen Zeichens
}
var c=rohtext.charCodeAt(n);
if (typeof structure.popupLayout != 'function') {
// alle Zeichen von 0-127 => 1byte
return 'Bad layout';
if (c<128)
}
utftext += String.fromCharCode(c);
pg.misc.layout = structure.popupLayout();
// alle Zeichen von 127 bis 2047 => 2byte
if (typeof structure.popupRedirSpans === 'function') {
else if((c>127) && (c<2048)) {
pg.misc.redirSpans = structure.popupRedirSpans();
utftext += String.fromCharCode((c>>6)|192);
} else {
utftext += String.fromCharCode((c&63)|128);}
pg.misc.redirSpans = [];
// alle Zeichen von 2048 bis 66536 => 3byte
}
else {
return makeEmptySpans(pg.misc.layout, a.navpopup);
utftext += String.fromCharCode((c>>12)|224);
utftext += String.fromCharCode(((c>>6)&63)|128);
utftext += String.fromCharCode((c&63)|128);}
}
}
return utftext;
}


function makeEmptySpans(list, navpop) {
function getJsObj(json) {
try {
var ret = '';
for (var i = 0; i < list.length; ++i) {
var json_ret = JSON.parse(json);
if (typeof list[i] == typeof '') {
if( json_ret.warnings ) {
for( var w=0; w < json_ret.warnings.length; w++ ) {
ret += emptySpanHTML(list[i], navpop.idNumber, 'div');
log( json_ret.warnings[w]['*'] );
} else if (typeof list[i] == typeof [] && list[i].length > 0) {
ret = ret.parenSplit(/(<\/[^>]*?>$)/).join(makeEmptySpans(list[i], navpop));
} else if (typeof list[i] == typeof {} && list[i].nodeType) {
ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType);
}
}
} else if ( json_ret.error ) {
errlog( json_ret.error.code + ': ' + json_ret.error.info );
}
}
return ret;
return json_ret;
} catch (someError) {
errlog('Something went wrong with getJsObj, json='+json);
return 1;
}
}
}


function emptySpanHTML(name, id, tag, classname) {
function anyChild(obj) {
for (var p in obj) {
tag = tag || 'span';
return obj[p];
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]);
}
}
return null;
emptySpanHTML.classAliases = { popupSecondPreview: 'popupPreview' };
}


//</NOLITE>
// 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) {
function decode_utf8(utftext) {
var plaintext = ""; var i=0, c=0, c1=0, c2=0;
if (!when) {
// while-Schleife, weil einige Zeichen uebersprungen werden
when = 250;
while(i<utftext.length)
}
{
var popTips = function () {
c = utftext.charCodeAt(i);
setupTooltips(document.getElementById(id), false, true, popData);
if (c<128) {
};
plaintext += String.fromCharCode(c);
return function () {
i++;}
setTimeout(popTips, when, popData);
else if((c>191) && (c<224)) {
};
c2 = utftext.charCodeAt(i+1);
plaintext += String.fromCharCode(((c&31)<<6) | (c2&63));
i+=2;}
else {
c2 = utftext.charCodeAt(i+1); c3 = utftext.charCodeAt(i+2);
plaintext += String.fromCharCode(((c&15)<<12) | ((c2&63)<<6) | (c3&63));
i+=3;}
}
}
return plaintext;
}


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


function upcaseFirst(str) {
// STARTFILE: mouseout.js
if (typeof str != typeof '' || str === '') return '';
//////////////////////////////////////////////////
return str.charAt(0).toUpperCase() + str.substring(1);
// 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() {
function findInArray(arr, foo) {
if (!arr || !arr.length) { return -1; }
// stop the popup running off the right of the screen
var len=arr.length;
// FIXME avoid pg.current.link
for (var i=0; i<len; ++i) { if (arr[i]==foo) { return i; } }
if (pg.current.link && pg.current.link.navpopup) {
return -1;
pg.current.link.navpopup.limitHorizontalPosition();
}
}
}


function mouseOutWikiLink() {
function nextOne (array, value) {
// NB if the array has two consecutive entries equal
//console ('mouseOutWikiLink');
// then this will loop on successive calls
var a = this;
var i=findInArray(array, value);
if (i<0) { return null; }
return array[i+1];
}


function literalizeRegex(str){
removeModifierKeyHandler(a);
return str.replace(RegExp('([-.|()\\\\+?*^${}\\[\\]])', 'g'), '\\$1');
}


String.prototype.entify=function() {
if (a.navpopup === null || typeof a.navpopup === 'undefined') {
//var shy='&shy;';
return;
return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;'/*+shy*/).split('"').join('&quot;');
}
};
if (!a.navpopup.isVisible()) {
a.navpopup.banish();
return;
}
restoreTitle(a);
Navpopup.tracker.addHook(posCheckerHook(a.navpopup));
}


function posCheckerHook(navpop) {
function findThis(array, value) {
if (typeof array.length == 'undefined') { return null; }
return function () {
for (var i=0; i<array.length; ++i) {
if (!navpop.isVisible()) {
if (array[i]==value) { return i; }
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 = Date.now();
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;
};
}
}
return null;
}


function runStopPopupTimer(navpop) {
function removeNulls(list) {
var ret=[];
// at this point, we should have left the link but remain within the popup
for (var i=0; i<list.length; ++i) {
// so we call this function again until we leave the popup.
if (!navpop.stopPopupTimer) {
if (list[i]) {
ret.push(list[i]);
navpop.stopPopupTimer = setInterval(posCheckerHook(navpop), 500);
navpop.addHook(
function () {
clearInterval(navpop.stopPopupTimer);
},
'hide',
'before'
);
}
}
}
}
return ret;
// ENDFILE: mouseout.js
}
function joinPath(list) {
return removeNulls(list).join('/');
}


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


function simplePrintf(str, subs) {
/**
if (!str || !subs) { return str; }
* Creates a new Previewmaker
var ret=[];
* @constructor
var s=str.parenSplit(/(%s|\$[0-9]+)/);
* @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext.
var i=0;
* @param {string} wikiText The Wikitext source of the page we wish to preview.
do {
* @param {string} baseUrl The url we should prepend when creating relative urls.
ret.push(s.shift());
* @param {Navpopup} owner The navpop associated to this preview generator
if ( !s.length ) { break; }
*/
var cmd=s.shift();
function Previewmaker(wikiText, baseUrl, owner) {
if (cmd == '%s') {
/** The wikitext which is manipulated to generate the preview. */
if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); }
this.originalData = wikiText;
++i;
this.baseUrl = baseUrl;
} else {
this.owner = owner;
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('');
}


function max(a,b){return a<b ? b : a;}
this.maxCharacters = getValueOf('popupMaxPreviewCharacters');
function min(a,b){return a>b ? b : a;}
this.maxSentences = getValueOf('popupMaxPreviewSentences');


function isString(x) { return (typeof x === 'string' || x instanceof String); }
this.setData();
//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) && ($.isFunction(x) || x instanceof Function);
}


function repeatString(s,mult) {
Previewmaker.prototype.setData = function () {
var ret='';
var maxSize = Math.max(10000, 2 * this.maxCharacters);
for (var i=0; i<mult; ++i) { ret += s; }
this.data = this.originalData.substring(0, maxSize);
return ret;
};
}


function zeroFill(s, min) {
/**
min = min || 2;
* Remove HTML comments
var t=s.toString();
* @private
return repeatString('0', min - t.length) + t;
*/
}
Previewmaker.prototype.killComments = function () {
// this also kills one trailing newline, eg [[diamyo]]
this.data = this.data.replace(
/^<!--[^$]*?-->\n|\n<!--[^$]*?-->(?=\n)|<!--[^$]*?-->/g,
''
);
};


function map(f, o) {
/**
if (isArray(o)) { return map_array(f,o); }
* @private
return map_object(f,o);
*/
}
Previewmaker.prototype.killDivs = function () {
function map_array(f,o) {
// say goodbye, divs (can be nested, so use * not *?)
var ret=[];
this.data = this.data.replace(/< *div[^>]* *>[\s\S]*?< *\/ *div *>/gi, '');
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
* @private
.replace(/&/g, "&amp;")
*/
.replace(/"/g, "&quot;")
Previewmaker.prototype.killGalleries = function () {
.replace(/</g, "&lt;")
this.data = this.data.replace(/< *gallery[^>]* *>[\s\S]*?< *\/ *gallery *>/gi, '');
.replace(/>/g, "&gt;");
};
};


pg.jsescape = function(s)
/**
{
* @private
if (typeof s !== "string") throw "Invalid type in pg.jsescape";
*/
var res = "";
Previewmaker.prototype.kill = function (opening, closing, subopening, subclosing, repl) {
//this can be optimized by copying substrings instead of char by char!
var oldk = this.data;
for (var i=0; i<s.length; i++)
var k = this.killStuff(this.data, opening, closing, subopening, subclosing, repl);
{
while (k.length < oldk.length) {
oldk = k;
var c = s[i];
switch (c)
k = this.killStuff(k, opening, closing, subopening, subclosing, repl);
{
}
case '\b': res += '\\b'; continue;
this.data = k;
case '\f': res += '\\f'; continue;
};
case '\n': res += '\\n'; continue;

case '\0': res += '\\0'; continue;
/**
case '\r': res += '\\r'; continue;
* @private
case '\t': res += '\\t'; continue;
*/
case '\v': res += '\\v'; continue;
Previewmaker.prototype.killStuff = function (
case '\\': res += '\\\\'; continue;
txt,
case '\"': res += '\\\"'; continue;
opening,
case '\'': res += '\\\''; continue;
closing,
default:
subopening,
if (c < ' ' || c==='<' || c==='>' || c==="'")
subclosing,
repl
{
var unicodeChar = c.charCodeAt(0).toString(16).toUpperCase();
) {
res += "\\u" + (unicodeChar.length>1?"00":"000") + unicodeChar;
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;
}
}
else
if (!removal) {
{
removal = 1;
res += c;
}
}
}
txt = txt.substring(removal);
}
if (depth === 0) {
return res;
break;
};
}
// ENDFILE: tools.js
}
// STARTFILE: dab.js
return ret + (repl || '') + txt;
//<NOLITE>
};
//////////////////////////////////////////////////
// Dab-fixing code
//


/**
* @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');
}


function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) {
log('makeRegexp: got reStr=' + reStr + ', flags=' + flags);
log('retargetDab: newTarget='+newTarget + ' oldTarget=' + oldTarget);
return RegExp(reStr, flags);
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
* @private
// testcase: http://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633
*/
var reg=RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi');
Previewmaker.prototype.killBoxTemplates = function () {
var ret=[];
// taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general
var splitted=wikitext.parenSplit(reg);
// also, have float_begin, ... float_end
// ^[a-z]+ should match interwiki links, hopefully (case-insensitive)
this.kill(/[{][{][^{}\s|]*?(float|box)[_ ](begin|start)/i, /[}][}]\s*/, '{{');
// 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) {
// infoboxes etc
if (typeof splitted[i] == typeof 'string' && splitted[i].length>0 && !omitRegex.test(splitted[i])) {
// from [[User:Zyxw/popups.js]]: kill frames too
ret.push( retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit) );
this.kill(/[{][{][^{}\s|]*?(infobox|elementbox|frame)[_ ]/i, /[}][}]\s*/, '{{');
} /* if */
};
} /* for loop */


ret = rmDupesFromSortedList(ret.sort());
/**
* @private
*/
Previewmaker.prototype.killTemplates = function () {
this.kill('{{', '}}', '{', '}', ' ');
};


if (wikPos) {
/**
var wikTarget='wiktionary:' +
* @private
friendlyCurrentArticleName.replace( RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1' );
*/
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(/^[|].*$/mg, '');
};


var meth;
/**
if (wikPos.toLowerCase() == 'first') { meth = 'unshift'; }
* @private
else { meth = 'push'; }
*/
Previewmaker.prototype.killImages = function () {
var forbiddenNamespaceAliases = [];
$.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!
});


ret[meth]( retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) );
// images and categories are a nono
}
this.kill(
RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'),
/\]\]\s*/,
'[',
']'
);
};


ret.push(changeLinkTargetLink(
/**
{ newTarget: null,
* @private
text: popupString('remove this link').split(' ').join('&nbsp;'),
*/
hint: popupString("remove all links to this disambig page from this article"),
Previewmaker.prototype.killHTML = function () {
clickButton: "wpDiff", oldTarget: oldTarget,
// kill <ref ...>...</ref>
summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]),
this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i);
watch: getValueOf('popupWatchDisambiggedPages'),
title: titleToEdit
}));
return ret;
}


function rmDupesFromSortedList(list) {
// let's also delete entire lines starting with <. it's worth a try.
var ret=[];
this.data = this.data.replace(/(^|\n) *<.*/g, '\n');
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) {
// and those pesky html tags, but not <nowiki> or <blockquote>
// grab title from parent popup if there is one; default exists in changeLinkTargetLink
var splitted = this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/);
var titleToEdit=(navpop.parentPopup && navpop.parentPopup.article.toString());
var len = splitted.length;
var list=listLinks(data, navpop.originalArticle, titleToEdit);
for (var i = 1; i < len; i = i + 2) {
if (list.length === 0) { log('listLinks returned empty list'); return null; }
switch (splitted[i]) {
var html='<hr />' + popupString('Click to disambiguate this link to:') + '<br>';
case '<nowiki>':
html+=list.join(', ');
case '</nowiki>':
return html;
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 = /((^|\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');
};


function makeFixDabs(wikiText, navpop) {
/**
if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) &&
* @private
Title.fromURL(location.href).namespaceId() != pg.nsSpecialId &&
*/
navpop.article.talkPage() ) {
Previewmaker.prototype.mopup = function () {
setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber);
// we simply *can't* be doing with horizontal rules right now
}
this.data = this.data.replace(/^-{4,}/mg, '');
}


function popupRedlinkHTML(article) {
// no indented lines
return changeLinkTargetLink(
this.data = this.data.replace(/(^|\n) *:[^\n]*/g, '');
{ newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'),
hint: popupString("remove all links to this page from this article"),
clickButton: "wpDiff",
oldTarget: article.toString(),
summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])});
}
//</NOLITE>
// ENDFILE: dab.js
// STARTFILE: htmloutput.js


function appendPopupContent(obj, elementId, popupId, onSuccess) {
// replace __TOC__, __NOTOC__ and whatever else there is
return setPopupHTML(obj, elementId, popupId, onSuccess, true);
// this'll probably do
}
this.data = this.data.replace(/^__[A-Z_]*__ *$/gmi, '');
};


// 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) {
* @private
if (elementId=='popupPreview') {
*/
}
Previewmaker.prototype.firstBit = function () {
if (typeof popupId === 'undefined') {
// dont't be givin' me no subsequent paragraphs, you hear me?
//console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100));
/// first we "normalize" section headings, removing whitespace after, adding before
var d = this.data;
popupId = pg.idNumber;
}

if (getValueOf('popupPreviewCutHeadings')) {
this.data = this.data.replace(/\s*(==+[^=]*==+)\s*/g, '\n\n$1 ');
/// then we want to get rid of paragraph breaks whose text ends badly
this.data = this.data.replace(/([:;]) *\n{2,}/g, '$1\n');


var popupElement=document.getElementById(elementId+popupId);
this.data = this.data.replace(/^[\s\n]*/, '');
if (popupElement) {
var stuff = /^([^\n]|\n[^\n\s])*/.exec(this.data);
if (!append) { popupElement.innerHTML=''; }
if (stuff) {
if (isString(str)) {
d = stuff[0];
popupElement.innerHTML+=str;
}
} else {
if (!getValueOf('popupPreviewFirstParOnly')) {
popupElement.appendChild(str);
d = this.data;
}

/// now put \n\n after sections so that bullets and numbered lists work
d = d.replace(/(==+[^=]*==+)\s*/g, '$1\n\n');
}
}
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;
}


//<NOLITE>
// Split sentences. Superfluous sentences are RIGHT OUT.
function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);}
// note: exactly 1 set of parens here needed to make the slice work
//</NOLITE>
d = d.parenSplit(/([!?.]+["']*\s)/g);
// leading space is bad, mmkay?
d[0] = d[0].replace(/^\s*/, '');


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


function fillEmptySpans(args) { return fillEmptySpans2(args); }
this.fullLength = d.join('').length;
var n = this.maxSentences;
var dd = this.firstSentences(d, n);


// args.navpopup is mandatory
do {
// optional: args.redir, args.redirTarget
dd = this.firstSentences(d, n);
// FIXME: ye gods, this is ugly stuff
--n;
function fillEmptySpans2(args) { // if redir is present and true then redirTarget is mandatory
} while (dd.length > this.maxCharacters && n !== 0);
var redir=true;
if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) { redir=false; }
var a=args.navpopup.parentAnchor;


var article, hint=null, oldid=null, params={};
this.data = dd;
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') {
* @private
setPopupHTML('popupError', 'Unknown structure (this should never happen): '+
*/
pg.option.popupStructure, args.navpopup.idNumber);
Previewmaker.prototype.fixSentenceEnds = function (strs, reg) {
return;
// take an array of strings, strs
}
// join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg
var spans=flatten(pg.misc.layout);
var numspans = spans.length;
var redirs=pg.misc.redirSpans;


for (var i = 0; i < strs.length - 2; ++i) {
for (var i=0; i<numspans; ++i) {
if (reg.test(strs[i])) {
var f=findThis(redirs, spans[i]);
//log('redir='+redir+', f='+f+', spans[i]='+spans[i]);
var a = [];
for (var j = 0; j < strs.length; ++j) {
if ( (f && !redir) || (!f && redir) ) {
//log('skipping this set of the loop');
if (j < i) {
continue;
a[j] = strs[j];
}
}
var structurefn=structure[spans[i]];
if (j == i) {
var setfn = setPopupHTML;
a[i] = strs[i] + strs[i + 1] + strs[i + 2];
if (getValueOf('popupActiveNavlinks') &&
}
(spans[i].indexOf('popupTopLinks')===0 || spans[i].indexOf('popupRedirTopLinks')===0)
if (j > i + 2) {
) {
a[j - 2] = strs[j];
setfn = setPopupTipsAndHTML;
}
}
return this.fixSentenceEnds(a, reg);
}
}
}
switch (typeof structurefn) {
return strs;
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) {
* @private
var ret=[];
*/
if (typeof start == 'undefined') { start=0; }
Previewmaker.prototype.firstSentences = function (strs, howmany) {
for (var i=start; i<list.length; ++i) {
var t = strs.slice(0, 2 * howmany);
if (typeof list[i] == typeof []) {
return t.join('');
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) {
* @private
getValueOf('popupStructure');
*/
var structure=pg.structures[pg.option.popupStructure];
Previewmaker.prototype.killBadWhitespace = function () {
if (typeof structure != 'object') {
// also cleans up isolated '''', eg [[Suntory Sungoliath]]
//return 'Unknown structure: '+pg.option.popupStructure;
this.data = this.data.replace(/^ *'+ *$/gm, '');
// 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 ($.isFunction(structure.popupRedirSpans)) { pg.misc.redirSpans=structure.popupRedirSpans(); }
else { pg.misc.redirSpans=[]; }
return makeEmptySpans(pg.misc.layout, a.navpopup);
}


function makeEmptySpans (list, navpop) {
/**
var ret='';
* Runs the various methods to generate the preview.
for (var i=0; i<list.length; ++i) {
* The preview is stored in the <code>html</html> field.
if (typeof list[i] == typeof '') {
* @private
ret += emptySpanHTML(list[i], navpop.idNumber, 'div');
*/
} else if (typeof list[i] == typeof [] && list[i].length > 0 ) {
Previewmaker.prototype.makePreview = function () {
ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop));
if (
} else if (typeof list[i] == typeof {} && list[i].nodeType ) {
this.owner.article.namespaceId() != pg.nsTemplateId &&
ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType);
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
return ret;
this.fixHTML();
}
this.stripLongTemplates();
};


/**
* @private
*/
Previewmaker.prototype.esWiki2HtmlPart = function (data) {
var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink
reLinks.lastIndex = 0; //reset regex


function emptySpanHTML(name, id, tag, classname) {
var match;
var result = '';
tag = tag || 'span';
if (!classname) { classname = emptySpanHTML.classAliases[name]; }
var postfixIndex = 0;
classname = classname || name;
while ((match = reLinks.exec(data))) {
if (name == getValueOf('popupDragHandle')) { classname += ' popupDragHandle'; }
//match all wikilinks
return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]);
//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 +=
emptySpanHTML.classAliases={ 'popupSecondPreview': 'popupPreview' };
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));


// generate html for popup image
return result;
// <a id="popupImageLinkn"><img id="popupImagen">
};
// where n=idNumber
Previewmaker.prototype.editSummaryPreview = function () {
function imageHTML(article, idNumber) {
var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker
return simplePrintf('<a id="popupImageLink$1">' +
reAes.lastIndex = 0; //reset regex
'<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' +
'</a>', [ idNumber ]);
}


function popTipsSoonFn(id, when, popData) {
var match;
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) {
match = reAes.exec(this.data);
setPopupHTML(html, divname, idnumber,
if (match) {
getValueOf('popupSubpopups') ?
//we have a section link. Split it, process it, combine it.
popTipsSoonFn(divname + idnumber, null, popData) :
var prefix = this.data.substring(0, match.index - 1);
null);
var section = match[1];
}
var postfix = this.data.substring(reAes.lastIndex);
// ENDFILE: htmloutput.js
// STARTFILE: mouseout.js
//////////////////////////////////////////////////
// fuzzy checks


function fuzzyCursorOffMenus(x,y, fuzz, parent) {
var start = "<span class='autocomment'>";
if (!parent) { return null; }
var end = '</span>';
var uls=parent.getElementsByTagName('ul');
if (prefix.length > 0) {
for (var i=0; i<uls.length; ++i) {
start = this.esWiki2HtmlPart(prefix) + ' ' + start + '- ';
if (uls[i].className=='popup_menu') {
}
if (postfix.length > 0) {
if (uls[i].offsetWidth > 0) return false;
} // else {document.title+='.';}
end = ': ' + end + this.esWiki2HtmlPart(postfix);
}
}
return true;
}


function checkPopupPosition () { // stop the popup running off the right of the screen
var t = new Title().fromURL(this.baseUrl);
// FIXME avoid pg.current.link
t.anchorFromUtf(section);
if (pg.current.link && pg.current.link.navpopup)
var sectionLink =
pg.current.link.navpopup.limitHorizontalPosition();
Insta.conf.paths.articles +
}
pg.escapeQuotesHTML(t.toString(true)) +
'#' +
pg.escapeQuotesHTML(t.anchor);
return (
start + '<a href="' + sectionLink + '">&rarr;</a> ' + pg.escapeQuotesHTML(section) + end
);
}


function mouseOutWikiLink () {
//else there's no section link, htmlify the whole thing.
//console ('mouseOutWikiLink');
return this.esWiki2HtmlPart(this.data);
var a=this;
};
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) {
/** Test function for debugging preview problems one step at a time. */
return function() {
/*eslint-disable */
if (!navpop.isVisible()) { return true; /* remove this hook */ }
function previewSteps(txt) {
if (Navpopup.tracker.dirty) {
try {
return false;
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');
}
}
}
var x=Navpopup.tracker.x, y=Navpopup.tracker.y;
txt = txt.substring(0, 10000);
var mouseOverNavpop = navpop.isWithin(x,y,navpop.fuzz, navpop.mainDiv) ||
var base = pg.wiki.articlebase + Title.fromURL(document.location.href).urlString();
!fuzzyCursorOffMenus(x,y,navpop.fuzz, navpop.mainDiv);
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;
}


// FIXME it'd be prettier to do this internal to the Navpopup objects
if (getValueOf('popupPreviewKillTemplates')) {
var t=getValueOf('popupHideDelay');
p.killTemplates();
if (t) { t = t * 1000; }
if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) {
if (!t) {
return;
if(!mouseOverNavpop) {
}
if(navpop.parentAnchor) {
} else {
restoreTitle( navpop.parentAnchor );
p.killMultilineTemplates();
if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) {
return;
}
}
navpop.banish();
return true; /* remove this hook */
}
}
return false;

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;
}
}
}
// we have a hide delay set

var d=+(new Date());
p.html = wiki2html(p.data, base); // needs livepreview
if ( !navpop.mouseLeavingTime ) {
p.fixHTML();
navpop.mouseLeavingTime = d;
if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) {
return;
return false;
}
}
if ( mouseOverNavpop ) {
p.stripLongTemplates();
navpop.mouseLeavingTime=null;
if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) {
return;
return false;
}
if (d - navpop.mouseLeavingTime > t) {
navpop.mouseLeavingTime=null;
navpop.banish(); return true; /* remove this hook */
}
}
return false;
alert('finished preview - end result follows.\n---\n' + p.html);
};
}

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');
}
}
}
/*eslint-enable */
// ENDFILE: mouseout.js
// STARTFILE: previewmaker.js
/**
@fileoverview
Defines the {@link Previewmaker} object, which generates short previews from wiki markup.
*/


/**
/**
Creates a new Previewmaker
* Works around livepreview bugs.
@constructor
* @private
@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.
Previewmaker.prototype.fixHTML = function () {
@param {String} baseUrl The url we should prepend when creating relative urls.
if (!this.html) {
@param {Navpopup} owner The navpop associated to this preview generator
return;
*/
function Previewmaker(wikiText, baseUrl, owner) {
/** The wikitext which is manipulated to generate the preview. */
this.originalData=wikiText;
this.setData();
this.baseUrl=baseUrl;
this.owner=owner;
this.maxCharacters=getValueOf('popupMaxPreviewCharacters');
this.maxSentences=getValueOf('popupMaxPreviewSentences');
}
Previewmaker.prototype.setData=function() {
var maxSize=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);
var ret = this.html;
return RegExp(reStr, flags);
};
/**
@private
*/
Previewmaker.prototype.killBoxTemplates = function () {


// taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general
// fix question marks in wiki links
// also, have float_begin, ... float_end
// maybe this'll break some stuff :-(
this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{');
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


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


};
/**
/**
* Generates the preview and displays it in the current popup.
@private
*
*/
* Does nothing if the generated preview is invalid or consists of whitespace only.
Previewmaker.prototype.killTemplates = function () {
* Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true.
this.kill('{{', '}}', '{', '}', ' ');
*/
};
Previewmaker.prototype.showPreview = function () {
/**
this.makePreview();
@private
if (typeof this.html != typeof '') {
*/
return;
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]='';
}
}
}
if (/^\s*$/.test(this.html)) {
this.data=splitted.join('');
return;
}
};
/**
setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber);
@private
setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, {
*/
owner: this.owner,
Previewmaker.prototype.killChunks = function() { // heuristics alert
});
// chunks of italic text? you crazy, man?
var more = this.fullLength > this.data.length ? this.moreLink() : '';
var italicChunkRegex=new RegExp
setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber);
("((^|\\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'), '');
* @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;
};


// replace __TOC__, __NOTOC__ and whatever else there is
/**
// this'll probably do
* @private
this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),'');
*/
};
Previewmaker.prototype.stripLongTemplates = function () {
/**
// operates on the HTML!
@private
this.html = this.html.replace(
*/
/^.{0,1000}[{][{][^}]*?(<(p|br)( \/)?>\s*){2,}([^{}]*?[}][}])?/gi,
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
this.html = this.html.split('\n').join(' '); // workaround for <pre> templates
var d=this.data;
this.html = this.html.replace(/[{][{][^}]*<pre>[^}]*[}][}]/gi, '');
};


if (getValueOf('popupPreviewCutHeadings')) {
/**
this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 ');
* @private
/// then we want to get rid of paragraph breaks whose text ends badly
*/
this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n');
Previewmaker.prototype.killMultilineTemplates = function () {
this.kill('{{{', '}}}');
this.kill(/\s*[{][{][^{}]*\n/, '}}', '{{');
};
// ENDFILE: previewmaker.js


this.data=this.data.replace(RegExp('^[\\s\\n]*'), '');
// STARTFILE: querypreview.js
stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data);
function loadAPIPreview(queryType, article, navpop) {
if (stuff) { d = stuff[0]; }
var art = new Title(article).urlString();
if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; }
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;
};


/// now put \n\n after sections so that bullets and numbered lists work
if (navpop.visible || !getValueOf('popupLazyDownloads')) {
d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n');
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(', ');
}


// Split sentences. Superfluous sentences are RIGHT OUT.
function getTimeOffset() {
// note: exactly 1 set of parens here needed to make the slice work
var tz = mw.user.options.get('timecorrection');
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');
if (tz) {
d = this.fixSentenceEnds(d, notSentenceEnds);
if (tz.indexOf('|') > -1) {
// New format
return parseInt(tz.split('|')[1], 10);
}
}
return 0;
}


this.fullLength=d.join('').length;
function getTimeZone() {
var maxChars=getValueOf('popupMaxPreviewCharacters') + this.extraCharacters;
if (!pg.user.timeZone) {
var n=this.maxSentences;
var tz = mw.user.options.get('timecorrection');
var dd=this.firstSentences(d,n);
pg.user.timeZone = 'UTC';


do {
if (tz) {
dd=this.firstSentences(d,n); --n;
var tzComponents = tz.split('|');
if (tzComponents.length === 3 && tzComponents[0] === 'ZoneInfo') {
} while ( dd.length > this.maxCharacters && n !== 0 );

pg.user.timeZone = tzComponents[2];
this.data = dd;
} else {
};
errlog('Unexpected timezone information: ' + tz);
/**
}
@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

var abbrevRe=/\b[a-z][^a-z]*$/i;

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);
}
// BUGGY STUFF - trying to fix up [[S. C. Johnson & Son]] preview
if (false && abbrevRe.test(strs[i])) {
var prevI=i, buf='';
do {
buf=buf+strs[i]+strs[i+1];
i=i+2;
} while (i<strs.length-2 && abbrevRe.test(strs[i]));
strs[i]=buf+strs[i];
return this.fixSentenceEnds((prevI?strs.slice(0,prevI-1):[]).concat(strs.slice(i)),
reg);
}
}
return pg.user.timeZone;
}
}
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();
* Should we use an offset or can we use proper timezones
} else {
*/
this.killMultilineTemplates();
function useTimeOffset() {
if (typeof Intl.DateTimeFormat.prototype.formatToParts === 'undefined') {
// IE 11
return true;
}
}
this.killTables();
var tz = mw.user.options.get('timecorrection');
this.killImages();
if (tz && tz.indexOf('ZoneInfo|') === -1) {
this.killHTML();
// System| Default system time, default for users who didn't configure timezone
this.killChunks();
// Offset| Manual defined offset by user
this.mopup();
return true;

}
this.firstBit();
return false;
this.killBadWhitespace();
}
}
else

/**
{
this.killHTML();
* 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;
}
}
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 = "";
* Retrieve configured MW date format for this user
var postfixIndex = 0;
* These can be
while ((match = reLinks.exec(data))) //match all wikilinks
* default
{
* dmy: time, dmy
//FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases.
* mdy: time, mdy
result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) +
* ymd: time, ymd
'<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>";
* dmyt: dmy, time
postfixIndex = reLinks.lastIndex;
* dmyts: dmy, time + seconds
}
* ISO 8601: YYYY-MM-DDThh:mm:ss (local time)
//append the rest
*
result += pg.escapeQuotesHTML(data.substring(postfixIndex));
* This isn't too useful for us, as JS doesn't have formatters to match these private specifiers
*/
return result;
function getMWDateFormat() {
};
return mw.user.options.get('date');
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);
};


//<NOLITE>
/**
/** Test function for debugging preview problems one step at a time.
* 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
function previewSteps(txt) {
* @param {boolean} reallyContribs - true only if we're displaying user contributions
try {
*/
txt=txt || document.editform.wpTextbox1.value;
function editPreviewTable(article, h, reallyContribs) {
} catch (err) {
var html = ['<table>'];
if (pg.cache.pages.length > 0) {
var day = null;
txt=pg.cache.pages[pg.cache.pages.length-1].data;
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 {
} else {
alert('provide text or use an edit page');
// 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;
};
}
}
}
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')) {
for (var i = 0; i < h.length; ++i) {
p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; }
if (reallyContribs) {
} else {
page = h[i].title;
p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; }
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('');
}


p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; }
function adjustDate(d, offset) {
p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; }
// offset is in minutes
p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; }
var o = offset * 60 * 1000;
p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; }
return new Date(Number(d) + o);
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; }
* 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 }));
}
}


p.html=wiki2html(p.data, base); // needs livepreview
function formattedDateTime(date) {
p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; }
// fallback for IE11 and unknown timezones
p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; }
if (useTimeOffset()) {
alert('finished preview - end result follows.\n---\n' + p.html);
return formattedDate(date) + ' ' + formattedTime(date);
}
}
//</NOLITE>


/**
if (getMWDateFormat() === 'ISO 8601') {
Works around livepreview bugs.
var d2 = convertTimeZone(date, getTimeZone());
@private
return (
*/
map(zeroFill, [d2.getFullYear(), d2.getMonth() + 1, d2.getDate()]).join('-') +
Previewmaker.prototype.fixHTML = function() {
'T' +
if(!this.html) return;
map(zeroFill, [d2.getHours(), d2.getMinutes(), d2.getSeconds()]).join(':')
);
}


var ret = this.html;
var options = getValueOf('popupDateTimeFormatterOptions');
options.timeZone = getTimeZone();
return date.toLocaleString(getLocales(), options);
}


// fix question marks in wiki links
function formattedDate(date) {
// maybe this'll break some stuff :-(
// fallback for IE11 and unknown timezones
ret=ret.replace(RegExp('\(<a href="' + pg.wiki.articlePath + '/[^"]*\)[?]\(.*?"\)', 'g'), '$1%3F$2');
if (useTimeOffset()) {
ret=ret.replace(RegExp('\(<a href=\'' + pg.wiki.articlePath + '/[^\']*\)[?]\(.*?\'\)', 'g'), '$1%3F$2');
// we adjust the UTC time, so we print the adjusted UTC, but not really UTC values
// FIXME fix up % too
var d2 = adjustDate(date, getTimeOffset());
return map(zeroFill, [d2.getUTCFullYear(), d2.getUTCMonth() + 1, d2.getUTCDate()]).join('-');
}
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.
if (getMWDateFormat() === 'ISO 8601') {
Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true.
var d2 = convertTimeZone(date, getTimeZone());
*/
return map(zeroFill, [d2.getFullYear(), d2.getMonth() + 1, d2.getDate()]).join('-');
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&action=query&';
var htmlGenerator=function(a,d){alert('invalid html generator');};
var usernameart = '';
switch (queryType) {
case 'history':
url += 'meta=userinfo&uiprop=options&titles=' + art + '&prop=revisions&rvlimit=' +
getValueOf('popupHistoryPreviewLimit');
htmlGenerator=APIhistoryPreviewHTML;
break;
case 'category':
url += 'list=categorymembers&rawcontinue=&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&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&meta=userinfo&uiprop=options&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&rvprop=content' + trail;
htmlGenerator=APIimagepagePreviewHTML;
break;
case 'backlinks':
url += 'list=backlinks&rawcontinue=&bltitle=' + art;
htmlGenerator=APIbacklinksPreviewHTML;
break;
}
pendingNavpopTask(navpop);
if( !mw.config.get('wgEnableAPI') ) {
/* The API is not available */
htmlGenerator=function(a,d){
return 'This function of navigation popups now requires a MediaWiki ' +
'installation with the <a href="http://www.mediawiki.org/wiki/API">API</a> enabled.'; };
}
var callback=function(d){
log( "callback of API functions was hit" );
showAPIPreview(queryType, htmlGenerator(article,d,navpop), navpop.idNumber, navpop, d);
};
if (pg.flag.isIE) {
url = url + '&*'; //to circumvent https://bugzilla.wikimedia.org/show_bug.cgi?id=28840
}
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) {
var options = getValueOf('popupDateFormatterOptions');
list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); });
options.timeZone = getTimeZone();
var buf=[];
return date.toLocaleDateString(getLocales(), options);
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 formattedTime(date) {
function getTimeOffset(tz) {
if( tz ) {
// fallback for IE11 and unknown timezones
if (useTimeOffset()) {
if( tz.indexOf('|') > -1 ) {
// New format
// we adjust the UTC time, so we print the adjusted UTC, but not really UTC values
return parseInt(tz.split('|')[1],10);
var d2 = adjustDate(date, getTimeOffset());
} else if ( tz.indexOf(':') > -1 ) {
return map(zeroFill, [d2.getUTCHours(), d2.getUTCMinutes(), d2.getUTCSeconds()]).join(':');
// Old format
return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) );
}
}
}
return 0;
}


/*
if (getMWDateFormat() === 'ISO 8601') {
* Creates a HTML table that's shown in the history and user-contribs popups.
var d2 = convertTimeZone(date, getTimeZone());
* @param {Object[]} h - a list of revisions, returned from the API
return map(zeroFill, [d2.getHours(), d2.getMinutes(), d2.getSeconds()]).join(':');
* @param {boolean} reallyContribs - true only if we're displaying user contributions
}
*/
function editPreviewTable(article, h, reallyContribs, timeOffset) {
var html=['<table>'];
var day=null;
var curart=article;
var page=null;


var makeFirstColumnLinks;
var options = getValueOf('popupTimeFormatterOptions');
if(reallyContribs) {
options.timeZone = getTimeZone();

return date.toLocaleTimeString(getLocales(), options);
// 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=' + h[i].revid + '">' + popupString('cur') + '</a>';
result += '&nbsp;|&nbsp;';
result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() +
'&diff=prev&oldid=' + h[i].revid + '">' + popupString('last') + '</a>';
result += ')';
return result;
};
}
}


for (var i=0; i<h.length; ++i) {
// Get the proper groupnames for the technicalgroups
if (reallyContribs) {
function fetchUserGroupNames(userinfoResponse) {
page = h[i].title;
var queryObj = getJsObj(userinfoResponse).query;
var user = anyChild(queryObj.users);
curart = new Title(page);
}
var messages = [];
var minor = (typeof h[i].minor == 'undefined') ? '' : '<b>m </b>';
if (user.groups) {
var editDate = adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset);
user.groups.forEach(function (groupName) {
var thisDay = dayFormat(editDate);
if (['*', 'user', 'autoconfirmed', 'extendedconfirmed', 'named'].indexOf(groupName) === -1) {
var thisTime = timeFormat(editDate);
messages.push('group-' + groupName + '-member');
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( typeof h[i].userhidden == "undefined" ) {
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 : '') +
if (queryObj.globaluserinfo && queryObj.globaluserinfo.groups) {
'<a href="' + col3url + '">' + col3txt + '</a></td>');
queryObj.globaluserinfo.groups.forEach(function (groupName) {
var comment='';
messages.push('group-' + groupName + '-member');
var c=h[i].comment || h[i]['*'];
});
if (c) {
comment=new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview();
} else if (typeof h[i].commenthidden != "undefined" ) {
comment=popupString('revdel');
}
}
html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>');
return getMwApi().loadMessagesIfMissing(messages);
html.push('</tr>');
html=[html.join('')];
}
}
html.push('</table>');
return html.join('');
}


function getDateFromTimestamp(t) {
function showAPIPreview(queryType, html, id, navpop, download) {
var s=t.split(/[^0-9]/);
// DJ: done
switch(s.length) {
var target = 'popupPreview';
case 0: return null;
completedNavpopTask(navpop);
case 1: return new Date(s[0]);
case 2: return new Date(s[0], s[1]-1);
case 3: return new Date(s[0], s[1]-1, s[2]);
case 4: return new Date(s[0], s[1]-1, s[2], s[3]);
case 5: return new Date(s[0], s[1]-1, s[2], s[3], s[4]);
case 6: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]);
default: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5], s[6]);
}
}

function adjustDate(d, offset) {
// offset is in minutes
var o=offset * 60 * 1000;
return new Date( +d + o);
}


function dayFormat(editDate, utc) {
switch (queryType) {
if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); }
case 'imagelinks':
return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-');
case 'category':
}
target = 'popupPostPreview';

break;
function timeFormat(editDate, utc) {
case 'userinfo':
if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); }
target = 'popupUserData';
return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':');
break;
}
case 'revision':

insertPreview(download);
function showAPIPreview(queryType, html, id, navpop, download) {
return;
// DJ: done
}
var target='popupPreview';
setPopupTipsAndHTML(html, target, id);
switch (queryType) {
case 'imagelinks':
case 'category':
case 'userinfo':
target='popupPostPreview'; break;
}
}
setPopupTipsAndHTML(html, target, id);
completedNavpopTask(navpop);
}


function APIrevisionPreviewHTML(article, download) {
function APIbacklinksPreviewHTML(article, download, navpop) {
try {
try {
var jsObj = getJsObj(download.data);
var jsObj=getJsObj(download.data);
var page = anyChild(jsObj.query.pages);
var list=jsObj.query.backlinks;

if (page.missing) {
var html=[];
// TODO we need to fix this proper later on
if (!list) { return popupString('No backlinks found'); }
download.owner = null;
for ( var i=0; i < list.length; i++ ) {
return;
var t=new Title(list[i].title);
}
html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>');
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 :(';
}
}
html=html.join(', ');
if (jsObj['query-continue'] && jsObj['query-continue'].backlinks && jsObj['query-continue'].backlinks.blcontinue) {
html += popupString(' and more');
}
return html;
} catch (someError) {
return 'backlinksPreviewHTML went wonky';
}
}
}


pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) {
function APIbacklinksPreviewHTML(article, download /*, navpop*/) {
log( "APIsharedImagePagePreviewHTML" );
try {
var popupid = obj.requestid;
var jsObj = getJsObj(download.data);
var list = jsObj.query.backlinks;
if( obj.query && obj.query.pages )
{

var html = [];
var page=anyChild(obj.query.pages );
var content=(page && page.revisions ) ? page.revisions[0]['*'] : null;
if (!list) {
if( content )
return popupString('No backlinks found');
}
{
/* Not entirely safe, but the best we can do */
for (var i = 0; i < list.length; i++) {
var p=new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup);
var t = new Title(list[i].title);
html.push(
p.makePreview();
setPopupHTML( p.html, "popupSecondPreview", popupid );
'<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';
}
}
}
}
};


function APIimagepagePreviewHTML(article, download, navpop) {
pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) {
try {
log('APIsharedImagePagePreviewHTML');
var popupid = obj.requestid;
var jsObj=getJsObj(download.data);
if (obj.query && obj.query.pages) {
var page=anyChild(jsObj.query.pages);
var content=(page && page.revisions ) ? page.revisions[0]['*'] : null;
var page = anyChild(obj.query.pages);
var content =
var ret='';
var alt='';
page && page.revisions && page.revisions[0] &&
try{alt=navpop.parentAnchor.childNodes[0].alt;} catch(e){}
page.revisions[0].slots && page.revisions[0].slots.main &&
if (alt) {
page.revisions[0].slots.main.contentmodel === 'wikitext' ?
ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt);
page.revisions[0].slots.main.content :
}
null;
if (
if (content) {
typeof content === 'string' &&
var p=prepPreviewmaker(content, article, navpop);
p.makePreview();
pg &&
if (p.html) { ret += '<hr />' + p.html; }
pg.current &&
if (getValueOf('popupSummaryData')) {
pg.current.link &&
var info=getPageInfo(content, download);
pg.current.link.navpopup
) {
log(info);
setPopupTrailer(info, navpop.idNumber);
/* 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);
}
}
}
}
if (page && page.imagerepository == "shared" ) {
};
var art=new Title(article);

var encart = encodeURIComponent( "File:" + art.stripNamespace() );
function APIimagepagePreviewHTML(article, download, navpop) {
var shared_url = pg.wiki.apicommonsbase + '?format=json&callback=pg.fn.APIsharedImagePagePreviewHTML' +
try {
'&requestid=' + navpop.idNumber +
var jsObj = getJsObj(download.data);
'&action=query&prop=revisions&rvprop=content&titles=' + encart;
var page = anyChild(jsObj.query.pages);
var content =
if (pg.flag.isIE) {
shared_url = shared_url + '&*'; //to circumvent https://bugzilla.wikimedia.org/show_bug.cgi?id=28840
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') +
ret =
': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' +
ret +
'<hr />' +
popupString( 'Description page') + '</a>';
mw.loader.load( shared_url );
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 :(';
}
}
showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download);
return ret;
} catch (someError) {
return 'API imagepage preview failed :(';
}
}
}


function APIimagelinksPreviewHTML(article, download) {
function APIimagelinksPreviewHTML(article, download) {
try {
try {
var jsobj = getJsObj(download.data);
var jsobj=getJsObj(download.data);
var list = jsobj.query.imageusage;
var list=jsobj.query.imageusage;
if (list) {
if (list) {
var ret = [];
var ret=[];
for (var i = 0; i < list.length; i++) {
for (var i=0; i < list.length; i++) {
ret.push(list[i].title);
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');
}
}
if (ret.length === 0) { return popupString('No image links found'); }
} catch (someError) {
return 'Image links preview generation failed :(';
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) {
function APIcategoryPreviewHTML(article, download) {
try {
try{
var jsobj = getJsObj(download.data);
var jsobj=getJsObj(download.data);
var list = jsobj.query.categorymembers;
var list=jsobj.query.categorymembers;
var ret = [];
var ret=[];
for (var p = 0; p < list.length; p++) {
for (var p=0; p < list.length; p++) {
ret.push(list[p].title);
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 :(';
}
}
if (ret.length === 0) { return popupString('Empty category'); }
ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret);
if (jsobj['query-continue'] && jsobj['query-continue'].categorymembers && jsobj['query-continue'].categorymembers.cmcontinue) {
ret += popupString(' and more');
}
return ret;
} catch(someError) {
return 'Category preview failed :(';
}
}
}


function APIuserInfoPreviewHTML(article, download) {
function APIuserInfoPreviewHTML(article, download) {
var ret = [];
var ret=[];
var queryobj = {};
var queryobj = {};
try {
try{
queryobj = getJsObj(download.data).query;
queryobj=getJsObj(download.data).query;
} catch (someError) {
} catch(someError) { return 'Userinfo preview failed :('; }

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 )

ret.push('<b>' + popupString('BLOCKED') + '</b>');
var user = anyChild(queryobj.users);
if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) {
if (user) {
var lockedSulAccountIsAttachedToThis = true;
var globaluserinfo = queryobj.globaluserinfo;
for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) {
if (user.invalid === '') {
if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) {
ret.push(popupString('Invalid user'));
lockedSulAccountIsAttachedToThis=false;
} else if (user.missing === '') {
break;
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 (lockedSulAccountIsAttachedToThis) {
if (globaluserinfo && ('locked' in globaluserinfo || 'hidden' in globaluserinfo)) {
if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>');
var lockedSulAccountIsAttachedToThis = true;
for (var i = 0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) {
if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>');
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) {
if( getValueOf('popupShowGender') && user.gender ) {
switch( user.gender ) {
case 'male':
ret.push(popupString('he/him') + ' · ');
case "male": ret.push( popupString( "\u2642" ) ); break;
case "female": ret.push( popupString( "\u2640" ) ); break;
break;
}
case 'female':
}
ret.push(popupString('she/her') + ' · ');
if( user.groups ) {
break;
for( var j=0; j < user.groups.length; j++) {
var currentGroup = user.groups[j];
if( ["*", "user", "autoconfirmed", "extendedconfirmed"].indexOf( currentGroup ) === -1 ) {
ret.push( pg.escapeQuotesHTML(user.groups[j]) );
}
}
}
}
}
if (user.groups) {
if( globaluserinfo && globaluserinfo.groups ) {
user.groups.forEach(function (groupName) {
for( var k=0; k < globaluserinfo.groups.length; k++) {
if (['*', 'user', 'autoconfirmed', 'extendedconfirmed', 'named'].indexOf(groupName) === -1) {
ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[k])+'</i>' );
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( user.registration )
ret.push( pg.escapeQuotesHTML((user.editcount?user.editcount:'0') + popupString(' edits since: ') + (user.registration?dayFormat(getDateFromTimestamp(user.registration)):'')) );
}


if (queryobj.usercontribs && queryobj.usercontribs.length) {
if (queryobj.usercontribs.length) {
ret.push( popupString('last edit on ') + dayFormat(getDateFromTimestamp(queryobj.usercontribs[0].timestamp)) );
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++) {
ret.push('<b>' + popupString(queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCKED' : 'RANGEBLOCKED') + '</b>' );
}
}
}
ret = '<hr />' + ret.join( ', ' );
return ret;
}


function APIcontribsPreviewHTML(article, download, navpop) {
if (queryobj.blocks) {
return APIhistoryPreviewHTML(article, download, navpop, true);
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>');
}
}


function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) {
// if any element of ret ends with ' · ', merge it with the next element to avoid
try {
// the .join(', ') call inserting a comma after it
var jsobj=getJsObj(download.data);
for (var m = 0; m < ret.length - 1; m++) {
var tz=jsobj.query.userinfo.options.timecorrection;
if (ret[m].length > 3 && ret[m].substring(ret[m].length - 3) === ' · ') {
ret[m] = ret[m] + ret[m + 1];
var edits = [];
if( reallyContribs ) {
ret.splice(m + 1, 1); // delete element at index m+1
edits=jsobj.query.usercontribs;
m--;
}
} else {
edits=anyChild(jsobj.query.pages).revisions;
}
}
var timeOffset = getTimeOffset(tz);
Cookie.create('popTz', timeOffset, 1);


var ret=editPreviewTable(article, edits, reallyContribs, timeOffset);
ret = '<hr />' + ret.join(', ');
return ret;
return ret;
} catch (someError) {
return 'History preview failed :-(';
}
}
}


function APIcontribsPreviewHTML(article, download, navpop) {
return APIhistoryPreviewHTML(article, download, navpop, true);
}


//</NOLITE>
function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) {
// ENDFILE: querypreview.js
try {
// STARTFILE: debug.js
var jsobj = getJsObj(download.data);
////////////////////////////////////////////////////////////////////
var edits = [];
// Debugging functions
if (reallyContribs) {
////////////////////////////////////////////////////////////////////
edits = jsobj.query.usercontribs;
} else {
edits = anyChild(jsobj.query.pages).revisions;
}


function setupDebugging() {
var ret = editPreviewTable(article, edits, reallyContribs);
//<NOLITE>
return ret;
if (window.popupDebug) { // popupDebug is set from .version
} catch (someError) {
window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; };
return popupString('History preview failed');
window.console.log(x);
}
};
window.errlog=function(x) {
window.console.error(x);
};
log('Initializing logger');
} else {
//</NOLITE>
window.log = function(x) {};
window.errlog = function(x) {};
//<NOLITE>
}
}
//</NOLITE>
}
// ENDFILE: debug.js
// STARTFILE: images.js


// load image of type Title.
// ENDFILE: querypreview.js
function loadImage(image, navpop) {
if (typeof image.stripNamespace != 'function') { alert('loadImages bad'); }
// API call to retrieve image info.


if ( !getValueOf('popupImages') || !mw.config.get('wgEnableAPI') ) return;
// STARTFILE: debug.js
if ( !isValidImageName(image) ) return false;
////////////////////////////////////////////////////////////////////
// Debugging functions
var art=image.urlString();
////////////////////////////////////////////////////////////////////


var url=pg.wiki.apiwikibase + '?format=json&action=query';
function setupDebugging() {
url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge');
if (window.popupDebug) {
url += '&titles=' + art;
// popupDebug is set from .version
if (pg.flag.isIE) {
window.log = function (x) {
url = url + '&*'; //to circumvent https://bugzilla.wikimedia.org/show_bug.cgi?id=28840
//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


pendingNavpopTask(navpop);
// STARTFILE: images.js
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'); }


}
// load image of type Title.
function loadImage(image, navpop) {
if (typeof image.stripNamespace != 'function') {
alert('loadImages bad');
}
// API call to retrieve image info.


function popupsInsertImage(id, navpop, download) {
if (!getValueOf('popupImages')) {
log( "popupsInsertImage");
return;
var imageinfo;
}
try {
if (!isValidImageName(image)) {
var jsObj=getJsObj(download.data);
return false;
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);
var art = image.urlString();
if (!popupImage) {

log( "could not find insertion point for image");
var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query';
return;
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');
}
}
}


popupImage.width=getValueOf('popupImageSize');
function popupsInsertImage(id, navpop, download) {
popupImage.style.display='inline';
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;
}


// Set the source for the image.
var popupImage = document.getElementById('popupImg' + id);
if( imageinfo.thumburl )
if (!popupImage) {
popupImage.src=imageinfo.thumburl;
log('could not find insertion point for image');
else if( imageinfo.mime.indexOf("image") === 0 ){
return;
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");


popupImage.width = getValueOf('popupImageSize');
popupImage.style.display = 'inline';


var a=document.getElementById("popupImageLink"+id);
// Set the source for the image.
if (a === null) { return null; }
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");
}


// Determine the action of the surrouding imagelink.
var a = document.getElementById('popupImageLink' + id);
switch (getValueOf('popupThumbAction')) {
if (a === null) {
case 'imagepage':
return null;
if (pg.current.article.namespaceId()!=pg.nsImageId) {
}
a.href=imageinfo.descriptionurl;

// FIXME: unreliable pg.idNumber
// Determine the action of the surrouding imagelink.
popTipsSoonFn('popupImage' + id)();
switch (getValueOf('popupThumbAction')) {
break;
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;
}
}
/* 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;
}


// Toggles the image between inline small and navpop fullwidth.
if (!img.style.width || img.style.width === '') {
// It's the same image, no actual sizechange occurs, only display width.
img.style.width = '100%';
function toggleSize() {
} else {
var imgContainer=this;
img.style.width = '';
if (!imgContainer) {
}
alert('imgContainer is null :/');
return;
}
img=imgContainer.firstChild;
if (!img) {
alert('img is null :/');
return;
}
}


if (!img.style.width || img.style.width==='') {
// Returns one title of an image from wikiText.
img.style.width='100%';
function getValidImageFromWikiText(wikiText) {
} else {
// nb in pg.re.image we're interested in the second bracketed expression
img.style.width='';
// this may change if the regex changes :-(
}
//var match=pg.re.image.exec(wikiText);
}
var matched = null;

var match;
// Returns one title of an image from wikiText.
// strip html comments, used by evil bots :-(
function getValidImageFromWikiText(wikiText) {
var t = removeMatchesUnless(
// nb in pg.re.image we're interested in the second bracketed expression
wikiText,
// this may change if the regex changes :-(
/(<!--[\s\S]*?-->)/,
//var match=pg.re.image.exec(wikiText);
1,
var matched=null;
/^<!--[^[]*popup/i
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))) {
while ( ( match = pg.re.image.exec(t) ) ) {
// now find a sane image name - exclude templates by seeking {
// now find a sane image name - exclude templates by seeking {
var m = match[2] || match[6];
var m = match[2] || match[6];
if (isValidImageName(m)) {
if ( isValidImageName(m) ) {
matched = m;
matched=m;
break;
break;
}
}
pg.re.image.lastIndex = 0;
if (!matched) {
return null;
}
}
return mw.config.get('wgFormattedNamespaces')[pg.nsImageId] + ':' + upcaseFirst(matched);
}
}
pg.re.image.lastIndex=0;
if (!matched) { return null; }
return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched);
}


function removeMatchesUnless(str, re1, parencount, re2) {
function removeMatchesUnless(str, re1, parencount, re2) {
var split = str.parenSplit(re1);
var split=str.parenSplit(re1);
var c = parencount + 1;
var c=parencount + 1;
for (var i = 0; i < split.length; ++i) {
for (var i=0; i<split.length; ++i) {
if (i % c === 0 || re2.test(split[i])) {
if ( i%c === 0 || re2.test(split[i]) ) { continue; }
split[i]='';
continue;
}
split[i] = '';
}
return split.join('');
}
}
return split.join('');
}


//</NOLITE>
// ENDFILE: images.js
// ENDFILE: images.js
// STARTFILE: namespaces.js
// Set up namespaces and other non-strings.js localization
// (currently that means redirs too)


// STARTFILE: namespaces.js
// Set up namespaces and other non-strings.js localization
// (currently that means redirs too)


function setNamespaces() {
function namespaceListToRegex(list) {
return RegExp('^('+list.join('|').split(' ').join('[ _]')+'):');
pg.nsSpecialId = -1;
}
pg.nsMainspaceId = 0;
pg.nsImageId = 6;
pg.nsUserId = 2;
pg.nsUsertalkId = 3;
pg.nsCategoryId = 14;
pg.nsTemplateId = 10;
}


function setRedirs() {
function setNamespaces() {
pg.nsSpecialId = -1;
var r = 'redirect';
pg.nsMainspaceId = 0;
var R = 'REDIRECT';
pg.nsImageId = 6;
var redirLists = {
pg.nsUserId = 2;
ar: [R, 'تحويل'],
pg.nsUsertalkId = 3;
be: [r, 'перанакіраваньне'],
pg.nsCategoryId = 14;
bg: [r, 'пренасочване', 'виж'],
pg.nsTemplateId = 10;
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 = /^$/;
}
}


function setRedirs() {
// return a regexp pattern matching all variants to write the given namespace
var r='redirect';
function nsRe(namespaceId) {
var R='REDIRECT';
var imageNamespaceVariants = [];
var redirLists={
$.each(mw.config.get('wgNamespaceIds'), function (_localizedNamespaceLc, _namespaceId) {
//<NOLITE>
if (_namespaceId != namespaceId) {
'ar': [ R, 'تحويل' ],
return;
'be': [ r, 'перанакіраваньне' ],
}
'bg': [ r, 'пренасочване', 'виж' ],
_localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc);
'bs': [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ],
imageNamespaceVariants.push(
'cs': [ R, 'PŘESMĚRUJ' ],
mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]')
'cy': [ r, 'ail-cyfeirio' ],
);
'de': [ R, 'WEITERLEITUNG' ],
imageNamespaceVariants.push(mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc)));
'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' ],
'nl': [ R, 'DOORVERWIJZING' ],
'nn': [ r, 'omdiriger' ],
'pl': [ R, 'PATRZ', 'PRZEKIERUJ', 'TAM' ],
'pt': [ R, 'redir' ],
'ru': [ R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР' ],
'sk': [ r, 'presmeruj' ],
'sr': [ r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI' ],
'tt': [ R, 'yünältü', 'перенаправление', 'перенапр' ],
'uk': [ R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР' ],
'vi': [ r, 'đổi' ],
'zh': [ R, '重定向'] // no comma
//</NOLITE>
};
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() {
return '(?:' + imageNamespaceVariants.join('|') + ')';
if (pg.wiki.wikimedia) {
// From http://meta.wikimedia.org/wiki/List_of_Wikipedias
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 nsReImage() {
return nsRe(pg.nsImageId);
function nsRe(namespaceId) {
var imageNamespaceVariants = [];
jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
if (_namespaceId!=namespaceId) return;
//todo: escape regexp fragments!
_localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc);
imageNamespaceVariants.push(_localizedNamespaceLc.split(' ').join('[ _]'));
imageNamespaceVariants.push(encodeURI(_localizedNamespaceLc));
});

return '(?:' + imageNamespaceVariants.join('|') + ')';
}

function nsReImage() {
return nsRe(pg.nsImageId);
}
// ENDFILE: namespaces.js
// STARTFILE: selpop.js
//<NOLITE>
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; }
if (getValueOf('popupOnEditSelection')=='boxpreview') {
return doSeparateSelectionPopup(sel);
}
}
var article=new Title(sel.substring(open+2, (pipe < 0) ? close : pipe)).urlString();
// ENDFILE: namespaces.js
if (close > 0 && sel.substring(close+2).indexOf('[[') >= 0) {
return;
}
var a=document.createElement('a');
a.href=pg.wiki.titlebase + article;
mouseOverWikiLink2(a);
if (a.navpopup) {
a.navpopup.addHook(function(){runStopPopupTimer(a.navpopup);}, 'unhide', 'after');
}
}


function doSeparateSelectionPopup(str) {
// STARTFILE: selpop.js
var div=document.getElementById('selectionPreview');
function getEditboxSelection() {
if (!div) {
// see http://www.webgurusforum.com/8/12/0
div = document.createElement('div');
var editbox;
div.id='selectionPreview';
try {
try {
editbox = document.editform.wpTextbox1;
var box=document.editform.wpTextbox1;
box.parentNode.insertBefore(div, box);
} catch (dang) {
} catch (error) {
return;
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);
}
}
div.innerHTML=wiki2html(str);
div.ranSetupTooltipsAlready = false;
popTipsSoonFn('selectionPreview')();
}
//</NOLITE>
// 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
function doSelectionPopup() {
they look like and so on.
// popup if the selection looks like [[foo|anything afterwards at all

// or [[foo|bar]]text without ']]'
<code>Mousetracker</code> "captures" the mouse using
// or [[foo|bar]]
<code>document.onmousemove</code>.
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
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.
* @file Defines two classes: {@link Navpopup} and {@link Mousetracker}.
@type Integer
*
*/
* <code>Navpopup</code> describes popups: when they appear, where, what
this.loopDelay=400;
* they look like and so on.
*
* <code>Mousetracker</code> "captures" the mouse using
* <code>document.onmousemove</code>.
*/


/**
/**
Timer for the loop.
* Creates a new Mousetracker.
* @constructor
@type Timer
*/
* @class The Mousetracker class. This monitors mouse movements and manages associated hooks.
this.timer=null;
*/
function Mousetracker() {
/**
* Interval to regularly run the hooks anyway, in milliseconds.
* @type {number}
*/
this.loopDelay = 400;


/**
/**
Flag - are we switched on?
* Timer for the loop.
* @type Timer
@type Boolean
*/
*/
this.timer = null;
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.
* Flag - are we switched on?
@param {Function} f A function which is called as
* @type {boolean}
<code>f(x,y)</code>. It should return <code>true</code> when it
*/
wants to be removed, and <code>false</code> otherwise.
this.active = false;
*/
Mousetracker.prototype.addHook = function (f) {
this.hooks.push(f);
};


/**
/**
Runs hooks, passing them the x
* Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position?
and y coords of the mouse. Hook functions that return true are
*/
passed to {@link Mousetracker#removeHooks} for removal.
this.dirty = true;
@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
* Array of hook functions.
if (this.hooks[i](x, y)===true) {
* @private
remove=true;
* @type {Array}
removeObj[i]=true;
*/
}
this.hooks = [];
}
}
if (remove) { this.removeHooks(removeObj); }
};


/**
/**
Removes hooks.
* Adds a hook, to be called when we get events.
@private
* @param {Function} f A function which is called as
@param {Object} removeObj An object whose keys are the index
* <code>f(x,y)</code>. It should return <code>true</code> when it
numbers of functions for removal, with values that evaluate to true
* wants to be removed, and <code>false</code> otherwise.
*/
*/
Mousetracker.prototype.addHook = function (f) {
Mousetracker.prototype.removeHooks = function(removeObj) {
var newHooks=[];
this.hooks.push(f);
var len = this.hooks.length;
};
for (var i=0; i<len; ++i) {
if (! removeObj[i]) { newHooks.push(this.hooks[i]); }
}
this.hooks=newHooks;
};


/**
* 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) {
Event handler for mouse wiggles.
//~ run the hook function, and remove it if it returns true
We simply grab the event, set x and y and run the hooks.
if (this.hooks[i](x, y) === true) {
This makes the cpu all hot and bothered :-(
remove = true;
@private
removeObj[i] = true;
@param {Event} e Mousemove event
}
*/
}
Mousetracker.prototype.track=function (e) {
if (remove) {
//~ Apparently this is needed in IE.
this.removeHooks(removeObj);
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;
* 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;
};


if (docElt) { top=docElt.scrollTop; }
/**
top = top || document.body.scrollTop || document.scrollTop || 0;
* 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;


x=e.clientX + left;
if (docElt) {
y=e.clientY + top;
left = docElt.scrollLeft;
} else { return; }
}
this.setPosition(x,y);
left = left || document.body.scrollLeft || document.scrollLeft || 0;
}
};


/**
if (docElt) {
Sets the x and y coordinates stored and takes appropriate action,
top = docElt.scrollTop;
running hooks as appropriate.
}
@param {Integer} x, y Screen coordinates to set
top = top || document.body.scrollTop || document.scrollTop || 0;
*/


Mousetracker.prototype.setPosition=function(x,y) {
x = e.clientX + left;
this.x = x;
y = e.clientY + top;
this.y = y;
} else {
if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; }
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);
this.setPosition(x, 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>.
* Sets the x and y coordinates stored and takes appropriate action,
A half-hearted attempt is made to preserve the old event handler if there is one.
* running hooks as appropriate.
*/
* @param {number} x Screen coordinates to set
Mousetracker.prototype.enable = function () {
* @param {number} y Screen coordinates to set
if (this.active) { return; }
*/
this.active=true;
Mousetracker.prototype.setPosition = function (x, y) {
//~ Save the current handler for mousemove events. This isn't too
this.x = x;
//~ robust, of course.
this.y = y;
this.savedHandler=document.onmousemove;
if (this.dirty || this.hooks.length === 0) {
//~ Gotta save @tt{this} again for the closure, and use apply for
this.dirty = false;
//~ the member function.
return;
var savedThis=this;
}
document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);};
if (typeof this.lastHook_x != 'number') {
if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working');
this.lastHook_x = -100;
savedThis.runHooks();}, this.loopDelay); }
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
Disables the tracker, removing the event handler.
*/
* <code>document.onmousemove</code>. A half-hearted attempt is made to preserve the old event
Mousetracker.prototype.disable = function () {
* handler if there is one.
if (!this.active) { return; }
*/
if ($.isFunction(this.savedHandler)) {
Mousetracker.prototype.enable = function () {
document.onmousemove=this.savedHandler;
if (this.active) {
} else { delete document.onmousemove; }
return;
if (this.timer) { clearInterval(this.timer); }
}
this.active = true;
this.active=false;
};
//~ 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);
}
};


/**
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 integer
*/
this.uid=Navpopup.uid++;
/**
/**
Read-only flag for current visibility of the popup.
* Disables the tracker, removing the event handler.
@type boolean
*/
@private
Mousetracker.prototype.disable = function () {
*/
if (!this.active) {
this.visible=false;
return;
/** Flag to be set when we want to cancel a previous request to
}
show the popup in a little while.
if (typeof this.savedHandler === 'function') {
@private
document.onmousemove = this.savedHandler;
@type boolean
} else {
*/
delete document.onmousemove;
this.noshow=false;
}
/** Categorised list of hooks.
if (this.timer) {
@see #runHooks
clearInterval(this.timer);
@see #addHook
}
@private
this.active = false;
@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 integer
*/
this.pending=null;
/** Tolerance in pixels when detecting whether the mouse has left the popup.
@type integer
*/
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 integer
*/
this.width=0;
/** The popup width in pixels.
@private
@type integer
*/
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);
* 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.
A UID for each Navpopup. This constructor property is just a counter.
@type integer
* Read-only.
@private
* @type {number}
*/
*/
Navpopup.uid=0;
this.uid = Navpopup.uid++;


/**
/**
Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible.
* Read-only flag for current visibility of the popup.
* @type {boolean}
@type boolean
*/
* @private
Navpopup.prototype.isVisible=function() {
*/
this.visible = false;
return this.visible;
};


/**
/** Flag to be set when we want to cancel a previous request to
* show the popup in a little while.
Repositions popup using CSS style.
* @private
@private
@param {integer} x x-coordinate (px)
* @type {boolean}
@param {integer} y y-coordinate (px)
*/
@param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition}
this.noshow = false;
*/
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 + ')');
};


/**
/** Categorised list of hooks.
Prevents popups from being in silly locations. Hopefully.
* @see #runHooks
Should not be run if {@link #constrained} is true.
* @see #addHook
* @private
@private
*/
* @type {Object}
Navpopup.prototype.limitHorizontalPosition=function() {
*/
if (!this.constrained || this.tooWide) { return; }
this.hooks = {
this.updateDimensions();
create: [],
var x=this.left;
unhide: [],
var w=this.width;
hide: [],
var cWidth=document.body.clientWidth;
};


/**
* list of unique IDs of hook functions, to avoid duplicates
* @private
*/
this.hookIds = {};


// log('limitHorizontalPosition: x='+x+
/** List of downloads associated with the popup.
// ', this.left=' + this.left +
* @private
// ', this.width=' + this.width +
* @type {Array}
// ', cWidth=' + cWidth);
*/
this.downloads = [];


/**
* Number of uncompleted downloads.
* @type {number}
*/
this.pending = null;


if ( (x+w) >= cWidth ||
/**
( x > 0 &&
* Tolerance in pixels when detecting whether the mouse has left the popup.
this.maxWidth &&
* @type {number}
this.width < this.maxWidth &&
*/
this.fuzz = 5;
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
* Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position.
// then reset it so that it should be flush right (well, nearly)
* @type {boolean}
this.mainDiv.style.left='-10000px';
*/
this.constrained = true;
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?
* The popup width in pixels.
log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth);
* @private
this.reposition(newLeft, null, true);
* @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);
// }
}
}
};


/**
/**
Counter indicating the z-order of the "highest" popup.
* A UID for each Navpopup. This constructor property is just a counter.
We start the z-index at 1000 so that popups are above everything
* @type {number}
else on the screen.
* @private
@private
*/
@type integer
Navpopup.uid = 0;
*/
Navpopup.highest=1000;


/**
/**
Brings popup to the top of the z-order.
* Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible.
We increment the {@link #highest} property of the contructor here.
* @type {boolean}
@private
*/
*/
Navpopup.prototype.isVisible = function () {
Navpopup.prototype.raise = function () {
return this.visible;
this.mainDiv.style.zIndex=Navpopup.highest + 1;
};
++Navpopup.highest;
};


/**
/**
Shows the popup provided {@link #noshow} is not true.
* Repositions popup using CSS style.
Updates the position, brings the popup to the top of the z-order and unhides it.
* @private
*/
* @param {number} x x-coordinate (px)
Navpopup.prototype.show = function () {
* @param {number} y y-coordinate (px)
//document.title+='s';
* @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition}
if (this.noshow) { return; }
*/
//document.title+='t';
Navpopup.prototype.reposition = function (x, y, noLimitHor) {
log('reposition(' + x + ',' + y + ',' + noLimitHor + ')');
this.reposition();
this.raise();
if (typeof x != 'undefined' && x !== null) {
this.left = x;
this.unhide();
}
};
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+
Runs the {@link #show} method in a little while, unless we're
// ', this.left=' + this.left +
already visible.
// ', this.width=' + this.width +
@param {integer} time Delay in milliseconds
// ', cWidth=' + cWidth);
@see #showSoonIfStable

*/
if (
Navpopup.prototype.showSoon = function (time) {
x + w >= cWidth ||
if (this.visible) { return; }
(x > 0 &&
this.maxWidth &&
this.noshow=false;
//~ We have to save the value of @tt{this} so that the closure below
this.width < this.maxWidth &&
//~ works.
this.height > this.width &&
var savedThis=this;
x > cWidth - this.maxWidth)
//this.start_x = Navpopup.tracker.x;
) {
//this.start_y = Navpopup.tracker.y;
// This is a very nasty hack. There has to be a better way!
setTimeout(function () {
// We find the "natural" width of the div by positioning it at the far left
if (Navpopup.tracker.active) {
// then reset it so that it should be flush right (well, nearly)
savedThis.reposition.apply(savedThis, [Navpopup.tracker.x + 2, Navpopup.tracker.y + 2]);
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);
}
}
//~ Have to use apply to invoke his member function here
};
savedThis.show.apply(savedThis, []);
}, time);
};


/**
/**
Checks to see if the mouse pointer has
* Counter indicating the z-order of the "highest" popup.
stabilised (checking every <code>time</code>/2 milliseconds) and runs the
* We start the z-index at 1000 so that popups are above everything
{@link #show} method if it has. This method makes {@link #showSoon} redundant.
* else on the screen.
@param {integer} time The minimum time (ms) before the popup may be shown.
* @private
*/
* @type {number}
Navpopup.prototype.showSoonIfStable = function (time) {
*/
log ('showSoonIfStable, time='+time);
Navpopup.highest = 1000;
if (this.visible) { return; }
this.noshow = false;


//~ initialize these variables so that we never run @tt{show} after
/**
//~ just half the time
* Brings popup to the top of the z-order.
this.stable_x = -10000; this.stable_y = -10000;
* We increment the {@link #highest} property of the contructor here.
* @private
*/
Navpopup.prototype.raise = function () {
this.mainDiv.style.zIndex = Navpopup.highest + 1;
++Navpopup.highest;
};


var stableShow = function() {
/**
log('stableShow called');
* Shows the popup provided {@link #noshow} is not true.
var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y;
* Updates the position, brings the popup to the top of the z-order and unhides it.
var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y;
*/
var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz;
Navpopup.prototype.show = function () {
//document.title+='s';
//document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] ';
if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) {
if (this.noshow) {
log ('mouse is stable');
clearInterval(savedThis.showSoonStableTimer);
savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]);
savedThis.show.apply(savedThis, []);
return;
return;
}
}
savedThis.stable_x = new_x; savedThis.stable_y = new_y;
//document.title+='t';
this.reposition();
this.raise();
this.unhide();
};
};
var savedThis = this;
this.showSoonStableTimer = setInterval(stableShow, time/2);
};


/**
/**
Makes the popup unhidable until we call {@link #unstick}.
* Checks to see if the mouse pointer has
*/
* stabilised (checking every <code>time</code>/2 milliseconds) and runs the
Navpopup.prototype.stick=function() {
* {@link #show} method if it has.
this.noshow=false;
* @param {number} time The minimum time (ms) before the popup may be shown.
this.sticky=true;
*/
};
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
Allows the popup to be hidden.
//~ just half the time
*/
this.stable_x = -10000;
Navpopup.prototype.unstick=function() {
this.stable_y = -10000;
this.sticky=false;
};


/**
var stableShow = function () {
Sets the {@link #noshow} flag and hides the popup. This should be called
log('stableShow called');
when the mouse leaves the link before
var new_x = Navpopup.tracker.x,
(or after) it's actually been displayed.
new_y = Navpopup.tracker.y;
*/
var dx = savedThis.stable_x - new_x,
Navpopup.prototype.banish = function () {
dy = savedThis.stable_y - new_y;
log ('banish called');
var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz;
// hide and prevent showing with showSoon in the future
//document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] ';
this.noshow=true;
if (dx * dx <= fuzz2 && dy * dy <= fuzz2) {
if (this.showSoonStableTimer) {
log('mouse is stable');
clearInterval(savedThis.showSoonStableTimer);
log('clearing showSoonStableTimer');
clearInterval(this.showSoonStableTimer);
savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]);
}
savedThis.show.apply(savedThis, []);
this.hide();
savedThis.limitHorizontalPosition.apply(savedThis, []);
};
return;
}
savedThis.stable_x = new_x;
savedThis.stable_y = new_y;
};
var savedThis = this;
this.showSoonStableTimer = setInterval(stableShow, time / 2);
};


/**
/**
Runs hooks added with {@link #addHook}.
* Sets the {@link #noshow} flag and hides the popup. This should be called
@private
* when the mouse leaves the link before
@param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
* (or after) it's actually been displayed.
@param {String} when Controls exactly when the hook is run: either 'before' or 'after'
*/
*/
Navpopup.prototype.banish = function () {
Navpopup.prototype.runHooks = function (key, when) {
log('banish called');
if (!this.hooks[key]) { return; }
// hide and prevent showing with showSoon in the future
var keyHooks=this.hooks[key];
this.noshow = true;
var len=keyHooks.length;
if (this.showSoonStableTimer) {
for (var i=0; i< len; ++i) {
log('clearing showSoonStableTimer');
if (keyHooks[i] && keyHooks[i].when == when) {
clearInterval(this.showSoonStableTimer);
if (keyHooks[i].hook.apply(this, [])) {
}
// remove the hook
this.hide();
if (keyHooks[i].hookId) {
};
delete this.hookIds[keyHooks[i].hookId];

/**
* 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;
}
}
keyHooks[i]=null;
}
}
}
}
};
}
};


/**
/**
* Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the
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.
* Navpopup instance, and no arguments.
@param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
* @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} 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.
*/
* @param {String} uid A truthy string identifying the hook function; if it matches another hook
Navpopup.prototype.addHook = function ( hook, key, when, uid ) {
* in this position, it won't be added again.
when = when || 'after';
*/
if (!this.hooks[key]) { return; }
Navpopup.prototype.addHook = function (hook, key, when, uid) {
// if uid is specified, don't add duplicates
when = when || 'after';
var hookId=null;
if (!this.hooks[key]) {
if (uid) {
hookId=[key,when,uid].join('|');
if (this.hookIds[hookId]) {
return;
return;
}
}
this.hookIds[hookId]=true;
// if uid is specified, don't add duplicates
}
var hookId = null;
this.hooks[key].push( {hook: hook, when: when, hookId: hookId} );
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.
Creates the main DIV element, which contains all the actual popup content.
* Runs hooks with key 'create'.
Runs hooks with key 'create'.
* @private
@private
*/
*/
Navpopup.prototype.createMainDiv = function () {
Navpopup.prototype.createMainDiv = function () {
if (this.mainDiv) {
if (this.mainDiv) { return; }
this.runHooks('create', 'before');
return;
var mainDiv=document.createElement('div');
}
this.runHooks('create', 'before');
var mainDiv = document.createElement('div');


var savedThis = this;
var savedThis=this;
mainDiv.onclick = function (e) {
mainDiv.onclick=function(e) {savedThis.onclickHandler(e);};
mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv';
savedThis.onclickHandler(e);
mainDiv.id=mainDiv.className + this.uid;
};
mainDiv.className = this.className ? this.className : 'navpopup_maindiv';
mainDiv.id = mainDiv.className + this.uid;


mainDiv.style.position = 'absolute';
mainDiv.style.position='absolute';
mainDiv.style.minWidth = '350px';
mainDiv.style.display='none';
mainDiv.style.display = 'none';
mainDiv.className='navpopup';
mainDiv.className = 'navpopup';


// easy access to javascript object through DOM functions
// easy access to javascript object through DOM functions
mainDiv.navpopup = this;
mainDiv.navpopup=this;


this.mainDiv = mainDiv;
this.mainDiv=mainDiv;
document.body.appendChild(mainDiv);
document.body.appendChild(mainDiv);
this.runHooks('create', 'after');
this.runHooks('create', 'after');
};
};
/**

Calls the {@link #raise} method.
/**
@private
* Calls the {@link #raise} method.
*/
* @private
Navpopup.prototype.onclickHandler=function(e) {
*/
this.raise();
Navpopup.prototype.onclickHandler = function (/*e*/) {
};
this.raise();
/**
};
Makes the popup draggable, using a {@link Drag} object.

@private
/**
*/
* Makes the popup draggable, using a {@link Drag} object.
Navpopup.prototype.makeDraggable=function(handleName) {
* @private
if (!this.mainDiv) { this.createMainDiv(); }
*/
var drag=new Drag();
Navpopup.prototype.makeDraggable = function (handleName) {
if (!this.mainDiv) {
if (!handleName) {
drag.startCondition=function(e) {
this.createMainDiv();
try { if (!e.shiftKey) { return false; } } catch (err) { return false; }
}
return true;
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);
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.
* 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');
};


@private
/**
*/
* Shows the popup using CSS. Runs hooks with key 'unhide'.
Navpopup.prototype.hide = function () {
* Sets {@link #visible} appropriately. {@link #show} should be called externally instead of this method.
this.runHooks('hide', 'before');
* @private
this.abortDownloads();
*/
if (this.sticky) { return; }
Navpopup.prototype.unhide = function () {
if (typeof this.visible != 'undefined' && this.visible) {
this.runHooks('unhide', 'before');
this.mainDiv.style.display='none';
if (typeof this.visible != 'undefined' && !this.visible) {
this.mainDiv.style.display = 'inline';
this.visible=false;
}
this.visible = true;
this.runHooks('hide', 'after');
}
};
this.runHooks('unhide', '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.
* Sets the <code>innerHTML</code> attribute of the main div containing the popup content.
@private
* @param {string} html The HTML to set.
*/
*/
Navpopup.prototype.setInnerHTML = function (html) {
Navpopup.prototype.unhide = function () {
this.runHooks('unhide', 'before');
this.mainDiv.innerHTML = html;
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.
* Updates the {@link #width} and {@link #height} attributes with the CSS properties.
@param {String} html The HTML to set.
* @private
*/
*/
Navpopup.prototype.updateDimensions = function () {
Navpopup.prototype.setInnerHTML = function (html) {
this.mainDiv.innerHTML = html;
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
Updates the {@link #width} and {@link #height} attributes with the CSS properties.
@private
* {@link #mainDiv}.
*/
* @param {number} x x-coordinate (px)
Navpopup.prototype.updateDimensions = function () {
* @param {number} y y-coordinate (px)
this.width=parseInt(this.mainDiv.offsetWidth, 10);
* @type {boolean}
this.height=parseInt(this.mainDiv.offsetHeight, 10);
*/
};
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
);
};


/**
/**
Checks if the point (x,y) is within {@link #fuzz} of the
* Adds a download to {@link #downloads}.
{@link #mainDiv}.
* @param {Downloader} download
@param {integer} x x-coordinate (px)
*/
@param {integer} y y-coordinate (px)
Navpopup.prototype.addDownload = function (download) {
@type boolean
if (!download) {
*/
return;
Navpopup.prototype.isWithin = function(x,y) {
}
//~ If we're not even visible, no point should be considered as
this.downloads.push(download);
//~ 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);
};


/**
/**
* Aborts the downloads listed in {@link #downloads}.
Adds a download to {@link #downloads}.
* @see Downloader#abort
@param {Downloader} download
*/
*/
Navpopup.prototype.abortDownloads = function () {
Navpopup.prototype.addDownload=function(download) {
if (!download) { return; }
for (var i = 0; i < this.downloads.length; ++i) {
var d = this.downloads[i];
this.downloads.push(download);
};
if (d && d.abort) {
/**
d.abort();
Aborts the downloads listed in {@link #downloads}.
}
@see Downloader#abort
}
*/
this.downloads = [];
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
A {@link Mousetracker} instance which is a property of the constructor (pseudo-global).
/*
*/
* Javascript Diff Algorithm
Navpopup.tracker=new Mousetracker();
* By John Resig (http://ejohn.org/) and [[:en:User:Lupin]]
// ENDFILE: navpopup.js
*
// STARTFILE: diff.js
* More Info:
//<NOLITE>
* http://ejohn.org/projects/javascript-diff-algorithm/
/*
*/
* 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) {
function delFmt(x) {
if (!x.length) {
if (!x.length) { return ''; }
return "<del class='popupDiff'>" + x.join('') +"</del>";
return '';
}
}
function insFmt(x) {
return "<del class='popupDiff'>" + x.join('') + '</del>';
if (!x.length) { return ''; }
}
return "<ins class='popupDiff'>" + x.join('') +"</ins>";
}


function insFmt(x) {
function countCrossings(a, b, i, eject) {
// count the crossings on the edge starting at b[i]
if (!x.length) {
if (!b[i].row && b[i].row !== 0) { return -1; }
return '';
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 "<ins class='popupDiff'>" + x.join('') + '</ins>';
}
}
return count;
}


function countCrossings(a, b, i, eject) {
function shortenDiffString(str, context) {
var re=RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)');
// count the crossings on the edge starting at b[i]
var splitted=str.parenSplit(re);
if (!b[i].row && b[i].row !== 0) {
var ret=[''];
return -1;
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 {
var count = 0;
for (var j = 0; j < a.length; ++j) {
if (i > 0) { ret[ret.length-1] += splitted[i].substring(0,context); }
if (!a[j].row && a[j].row !== 0) {
if (i+1 < splitted.length) {
ret.push(splitted[i].substring(splitted[i].length-context) +
continue;
splitted[i+1]);
}
if ((j - b[i].row) * (i - a[j].row) > 0) {
if (eject) {
return true;
}
count++;
}
}
}
}
return count;
}
}
while (ret.length > 0 && !ret[0]) { ret = ret.slice(1); }
return ret;
}


function shortenDiffString(str, context) {
var re = /(<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) {
function diffString( o, n, simpleSplit ) {
var splitRe = /([[]{2}|[\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\s|\b)/;
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
// 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)
// individually, so that the HTML entities are never cut apart. (AxelBoldt)
var out, i, oSplitted, nSplitted;
var out, i, oSplitted, nSplitted;
if (simpleSplit) {
if (simpleSplit) {
oSplitted = o.split(/\b/);
oSplitted=o.split(/\b/);
nSplitted = n.split(/\b/);
nSplitted=n.split(/\b/);
} else {
} else {
oSplitted = o.parenSplit(splitRe);
oSplitted=o.parenSplit(splitRe);
nSplitted = n.parenSplit(splitRe);
nSplitted=n.parenSplit(splitRe);
}
}
for (i = 0; i < oSplitted.length; ++i) {
for (i=0; i<oSplitted.length; ++i) {oSplitted[i]=oSplitted[i].entify();}
oSplitted[i] = oSplitted[i].entify();
for (i=0; i<nSplitted.length; ++i) {nSplitted[i]=nSplitted[i].entify();}
}
for (i = 0; i < nSplitted.length; ++i) {
out = diff (oSplitted, nSplitted);
var str = "";
nSplitted[i] = nSplitted[i].entify();
var acc=[]; // accumulator for prettier output
}


// crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out
out = diff(oSplitted, nSplitted);
// this doesn't always do things optimally but it should be fast enough
var str = '';
var maxOutputPair=0;
var acc = []; // accumulator for prettier output
for (i=0; i<out.n.length; ++i) {

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


// output the stuff preceding the first paired old line
// see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object
for (i=0; i<out.o.length && !out.o[i].paired; ++i) { acc.push( out.o[i] ); }
// FIXME: use obj.hasOwnProperty instead of this kludge!
str += delFmt(acc); acc=[];
var jsReservedProperties = RegExp(
'^(constructor|prototype|__((define|lookup)[GS]etter)__' +
'|eval|hasOwnProperty|propertyIsEnumerable' +
'|to(Source|String|LocaleString)|(un)?watch|valueOf)$'
);


// main loop
function diffBugAlert(word) {
for ( i = 0; i < out.n.length; ++i ) {
if (!diffBugAlert.list[word]) {
// output unpaired new "lines"
diffBugAlert.list[word] = 1;
while ( i < out.n.length && !out.n[i].paired ) { acc.push( out.n[i++] ); }
alert('Bad word: ' + word + '\n\nPlease report this bug.');
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
diffBugAlert.list = {};
// 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) {
function makeDiffHashtable(src) {
var ret = {};
var ret={};
for (var i = 0; i < src.length; i++) {
for ( var i = 0; i < src.length; i++ ) {
if (jsReservedProperties.test(src[i])) {
if ( jsReservedProperties.test(src[i]) ) { src[i] += '<!-- -->'; }
src[i] += '<!-- -->';
if ( !ret[ src[i] ] ) { ret[ src[i] ] = []; }
try { ret[ src[i] ].push( i ); } catch (err) { diffBugAlert(src[i]); }
}
if (!ret[src[i]]) {
ret[src[i]] = [];
}
try {
ret[src[i]].push(i);
} catch (err) {
diffBugAlert(src[i]);
}
}
return ret;
}
}
return ret;
}


function diff(o, n) {
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
// pass 1: make hashtable ns with new rows as keys
var os = makeDiffHashtable(o);
var ns = makeDiffHashtable(n);


// pass 3: pair unique new rows and matching unique old rows
// pass 2: make hashtable os with old rows as keys
var os = makeDiffHashtable(o);
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)
// pass 3: pair unique new rows and matching unique old rows
var i;
for (i = 0; i < n.length - 1; i++) {
for ( i in ns ) {
if (
if ( ns[i].length == 1 && os[i] && os[i].length == 1 ) {
n[i].paired &&
!n[i + 1].paired &&
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 };
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


// pass 4: pair matching rows immediately following paired rows (not necessarily unique)
// STARTFILE: init.js
for ( i = 0; i < n.length - 1; i++ ) {
function setSiteInfo() {
if ( n[i].paired && ! n[i+1].paired && n[i].row + 1 < o.length && ! o[ n[i].row + 1 ].paired &&
if (window.popupLocalDebug) {
n[i+1] == o[ n[i].row + 1 ] ) {
pg.wiki.hostname = 'en.wikipedia.org';
n[i+1] = { text: n[i+1], row: n[i].row + 1, paired: true };
} else {
o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1, paired: true };
pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?)
}
}
pg.wiki.wikimedia = /(wiki([pm]edia|source|books|news|quote|versity|species|voyage|data)|metawiki|wiktionary|mediawiki)[.]org/.test(pg.wiki.hostname);
pg.wiki.wikia = /[.]wikia[.]com$/i.test(pg.wiki.hostname);
pg.wiki.isLocal = /^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;
}
}


// pass 5: pair matching rows immediately preceding paired rows (not necessarily unique)
function setUserInfo() {
for ( i = n.length - 1; i > 0; i-- ) {
var params = {
if ( n[i].paired && ! n[i-1].paired && n[i].row > 0 && ! o[ n[i].row - 1 ].paired &&
action: 'query',
n[i-1] == o[ n[i].row - 1 ] ) {
list: 'users',
n[i-1] = { text: n[i-1], row: n[i].row - 1, paired: true };
ususers: mw.config.get('wgUserName'),
o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1, paired: true };
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') ?
});
}
}
}
}


return { o: o, n: n };
function fetchSpecialPageNames() {
}
var params = {
//</NOLITE>
action: 'query',
// ENDFILE: diff.js
meta: 'siteinfo',
// STARTFILE: init.js
siprop: 'specialpagealiases',
function setSiteInfo() {
formatversion: 2,
if (window.popupLocalDebug) {
// cache for an hour
pg.wiki.hostname = 'en.wikipedia.org';
uselang: 'content',
} else {
maxage: 3600,
pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?)
};
return getMwApi()
.get(params)
.then(function (data) {
pg.wiki.specialpagealiases = data.query.specialpagealiases;
});
}
}
pg.wiki.wikimedia=RegExp('(wiki([pm]edia|source|books|news|quote|versity)|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 setTitleBase() {
function setTitleBase() {
var protocol = window.popupLocalDebug ? 'http:' : location.protocol;
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.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ""); // as in http://some.thing.com/wiki/Article
pg.wiki.botInterfacePath = mw.config.get('wgScript');
pg.wiki.botInterfacePath = mw.config.get('wgScript');
pg.wiki.APIPath = mw.config.get('wgScriptPath') + '/api.php';
pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php";
// default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo
// default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo


var titletail = pg.wiki.botInterfacePath + '?title=';
var titletail = pg.wiki.botInterfacePath + '?title=';
//var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?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
// 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.titlebase = protocol + '//' + pg.wiki.sitebase + titletail;
//pg.wiki.titlebase2 = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]);
//pg.wiki.titlebase2 = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]);
pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath;
pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath;
pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath;
pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath;
pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath;
pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath;
pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath;
pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath;
pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath;
pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath;
pg.re.basenames = RegExp(
pg.re.basenames = RegExp( '^(' +
map( literalizeRegex, [ pg.wiki.titlebase, //pg.wiki.titlebase2,
'^(' +
pg.wiki.articlebase ]).join('|') + ')' );
map(literalizeRegex, [
}
pg.wiki.titlebase, //pg.wiki.titlebase2,
pg.wiki.articlebase,
]).join('|') +
')'
);
}


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


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


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


var reEnd='(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?';
function buildSpecialPageGroup(specialPageObj) {
pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd);
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() {
function setRegexps() {
setMainRegex();
setMainRegex();
var sp = nsRe(pg.nsSpecialId);
var sp=nsRe(pg.nsSpecialId);
pg.re.urlNoPopup = RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)');
pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ;
pg.re.contribs =RegExp('(title=|/)' + sp + '(?:%3A|:)Contributions' + '(&target=|/|/' + mw.config.get('wgFormattedNamespaces')[pg.nsUserId]+':)(.*)') ;

pg.re.email =RegExp('(title=|/)' + sp + '(?:%3A|:)EmailUser' + '(&target=|/|/(?:' + mw.config.get('wgFormattedNamespaces')[pg.nsUserId]+':)?)(.*)') ;
pg.wiki.specialpagealiases.forEach(function (specialpage) {
pg.re.backlinks =RegExp('(title=|/)' + sp + '(?:%3A|:)WhatLinksHere' + '(&target=|/)([^&]*)');
if (specialpage.realname === 'Contributions') {
pg.re.specialdiff=RegExp('/' + sp + '(?:%3A|:)Diff/([^?#]*)');
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'
);
}
});


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


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


pg.re.ipUser = RegExp(
pg.re.ipUser=RegExp('^' +
'^' +
// IPv6
// 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})' +
'(?::(?::|(?::[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
// 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])\\.){3}' +
'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$'
'(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.stub= RegExp(getValueOf('popupStubRegexp'), 'im');
pg.re.disambig = RegExp(getValueOf('popupDabRegexp'), 'im');
pg.re.disambig=RegExp(getValueOf('popupDabRegexp'), 'im');


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


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


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


function setMisc() {
function setupCache() {
// page caching
pg.current.link = null;
pg.current.links = [];
pg.cache.pages = [];
}
pg.current.linksHash = {};


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


setupCache();
pg.timer.checkPopupPosition = null;
pg.counter.loop = 0;


pg.timer.checkPopupPosition=null;
// ids change with each popup: popupImage0, popupImage1 etc
pg.idNumber = 0;
pg.counter.loop=0;


// ids change with each popup: popupImage0, popupImage1 etc
// for myDecodeURI
pg.idNumber=0;
pg.misc.decodeExtras = [

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


}
function getMwApi() {

if (!pg.api.client) {
function leadingInteger(s){
pg.api.userAgent = 'Navigation popups/1.0 (' + mw.config.get('wgServerName') + ')';
var n=s.match(/^(\d*)/)[1];
pg.api.client = new mw.Api({
if (n) { return +n; }
ajax: {
return null;
headers: {
}
'Api-User-Agent': pg.api.userAgent,

},
function setBrowserHacks() {
},
var useOriginal=false;
});
var webkitUnder420 = false;
// browser-specific hacks
if (typeof window.opera != 'undefined') {
//if (leadingInteger(opera.version()) < 9)
{ useOriginal=true; } // v9 beta still seems to have buggy css
setDefault('popupNavLinkSeparator', ' &#183; ');
} else if (navigator.appName=='Konqueror') {
setDefault('popupNavLinkSeparator', ' &bull; ');
pg.flag.isKonq=true;
} else if ( navigator.vendor && navigator.vendor.toLowerCase().indexOf('apple computer')===0) {
pg.flag.isSafari=true;
var webkit=+navigator.userAgent.replace(RegExp('^.*AppleWebKit[/](\\d+).*', 'i'), '$1');
if (webkit < 420) {
useOriginal=true;
webkitUnder420 = true;
}
}
} else if (navigator.appName.indexOf("Microsoft")!=-1) {
return pg.api.client;
setDefault('popupNavLinkSeparator', ' &#183; ');
var ver=+navigator.userAgent.replace(RegExp('^.*MSIE (\\d+).*'), '$1');
pg.flag.isIE=true;
pg.flag.IEVersion=ver;
}
}
if (pg.flag.isIE && pg.flag.IEVersion < 8) {
useOriginal=true;
}
if ((pg.flag.isIE && pg.flag.IEVersion < 7) || pg.flag.isKonq || (pg.flag.isSafari && webkitUnder420)) {
pg.flag.linksLikeIE6=true;
}
if (useOriginal && pg.structures.original) {
setDefault('popupStructure','original');
}
}


// We need a callback since this might end up asynchronous because of
// We need a callback since this might end up asynchronous because of
// the mw.loader.using() call.
// the mw.loader.using() call.
function setupPopups(callback) {
function setupPopups( callback ) {
mw.loader.using( 'mediawiki.user', function() {
if (setupPopups.completed) {
// NB translatable strings should be set up first (strings.js)
if (typeof callback === 'function') {
// basics
callback();
setupDebugging();
}
setSiteInfo();
return;
setTitleBase();
}
setOptions(); // see options.js
// 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
// namespaces etc
setNamespaces();
setNamespaces();
setInterwiki();
setInterwiki();


// regexps
// regexps
setRegexps();
setRegexps();
setRedirs();
setRedirs();


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


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


setupPopups.completed = true;
setupPopups.completed = true;
if (typeof callback === 'function') {
if ( $.isFunction( callback ) ) {
callback();
callback();
}
}
});
});
}
// ENDFILE: init.js
// STARTFILE: navlinks.js
//<NOLITE>
//////////////////////////////////////////////////
// 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>>}';
}
}
// ENDFILE: init.js


// user links
// STARTFILE: navlinks.js
// contribs - log - count - email - block
//////////////////////////////////////////////////
// count only if applicable; block only if popupAdminLinks
// navlinks... let the fun begin
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
function defaultNavlinkSpec() {
// talkpage -> edit|new - history - un|watch - article|edit
var str = '';
// other page -> edit - history - un|watch - talk|edit|new
str += '<b><<mainlink|shortcut= >></b>';
var editstr='<<edit|shortcut=e>>';
if (getValueOf('popupLastEditLink')) {
var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}';
str +=
'*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}';
var historystr='<<history|shortcut=h>>if(mainspace_en){|<<editors|shortcut=E|>>}';
var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
}


str+='<br>if(talk){' +
// user links
editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' +
// contribs - log - count - email - block
'<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
// count only if applicable; block only if popupAdminLinks
'}else{' + // not a talk page
str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>';
editOldidStr + '*' + historystr + '*' + watchstr + '*' +
str += 'if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}';
'<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';
str +=
'if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}';


// editing links
// misc links
str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';
// 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>>';


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


function navLinksHTML (article, hint, params) { //oldid, rcid) {
// misc links
var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>';
str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';
// BAM
return navlinkStringToHTML(str, article, params);
}


function expandConditionalNavlinkString(s,article,z,recursionCount) {
// admin links
var oldid=z.oldid, rcid=z.rcid, diff=z.diff;
str +=
// nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out)
'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' +
if (typeof recursionCount!=typeof 0) { recursionCount=0; }
'<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}';
var conditionalSplitRegex=RegExp(
return str;
//(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];
function navLinksHTML(article, hint, params) {
var trueString=splitted[i+3-1];
//oldid, rcid) {
var falseString=splitted[i+5-1];
var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>';
if (typeof falseString=='undefined' || !falseString) { falseString=''; }
// BAM
var testResult=null;
return navlinkStringToHTML(str, article, params);
}


switch (testString) {
function expandConditionalNavlinkString(s, article, z, recursionCount) {
case 'user':
var oldid = z.oldid,
testResult=(article.userName())?true:false;
rcid = z.rcid,
diff = z.diff;
break;
case 'talk':
// nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out)
testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages
if (typeof recursionCount != typeof 0) {
break;
recursionCount = 0;
case 'admin':
testResult=getValueOf('popupAdminLinks')?true:false;
break;
case 'oldid':
testResult=(typeof oldid != 'undefined' && oldid)?true:false;
break;
case 'rcid':
testResult=(typeof rcid != 'undefined' && rcid)?true:false;
break;
case 'ipuser':
testResult=(article.isIpUser())?true:false;
break;
case 'mainspace_en':
testResult=isInMainNamespace(article) &&
pg.wiki.hostname=='en.wikipedia.org';
break;
case 'wikimedia':
testResult=(pg.wiki.wikimedia) ? true : false;
break;
case 'diff':
testResult=(typeof diff != 'undefined' && diff)?true:false;
break;
}
}
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) {
switch(testResult) {
case null: ret+=splitted[i]; break;
case 'user':
case true: ret+=trueString; break;
testResult = !!article.userName();
break;
case false: ret+=falseString; 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;
}


// append non-conditional string
switch (testResult) {
ret += splitted[i+numParens];
case null:
}
ret += splitted[i];
if (conditionalSplitRegex.test(ret) && recursionCount < 10) {
break;
return expandConditionalNavlinkString(ret,article,z,recursionCount+1);
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;
}
}
return ret;
}


function navlinkStringToArray(s, article, params) {
function navlinkStringToArray(s, article, params) {
s = expandConditionalNavlinkString(s, article, params);
s=expandConditionalNavlinkString(s,article,params);
var splitted = s.parenSplit(/<<(.*?)>>/);
var splitted=s.parenSplit(RegExp('<<(.*?)>>'));
var ret = [];
var ret=[];
for (var i = 0; i < splitted.length; ++i) {
for (var i=0; i<splitted.length; ++i) {
if (i % 2) {
if (i%2) { // i odd, so s is a tag
var t=new navlinkTag();
// i odd, so s is a tag
var t = new navlinkTag();
var ss=splitted[i].split('|');
t.id=ss[0];
var ss = splitted[i].split('|');
t.id = ss[0];
for (var j=1; j<ss.length; ++j) {
for (var j = 1; j < ss.length; ++j) {
var sss=ss[j].split('=');
var sss = ss[j].split('=');
if (sss.length>1) {
if (sss.length > 1) {
t[sss[0]]=sss[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]);
}
}
}
else { // no assignment (no "="), so treat this as a title (overwriting the last one)
t.article = article;
t.text=popupString(sss[0]);
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]);
}
}
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;
}
}
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) {
function navlinkSubstituteHTML(s) {
return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length;
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>');


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


// navlinkString: * becomes the separator
navlinkTag.prototype.html = function () {
// <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz'
this.getNewWin();
// and visible text 'fubar'
this.getPrintFunction();
// if(test){...} and if(test){...}else{...} work too (nested ok)
var html = '';

var opening, closing;
function navlinkStringToHTML(s,article,params) {
var tagType = 'span';
//limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article));
if (!tagType) {
var p=navlinkStringToArray(s,article,params);
opening = '';
closing = '';
var html='';
var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it
} else {
var menurowdepth = 0;
opening = '<' + tagType + ' class="popup_' + this.id + '">';
var wrapping = null;
closing = '</' + tagType + '>';
for (var i=0; i<p.length; ++i) {
}
if (typeof this.print != 'function') {
if (typeof p[i] == typeof '') {
html+=navlinkSubstituteHTML(p[i]);
errlog('Oh dear - invalid print function for a navlinkTag, id=' + this.id);
menudepth += navlinkDepth('menu', p[i]);
} else {
menurowdepth += navlinkDepth('menurow', p[i]);
html = this.print(this);
if (typeof html != typeof '') {
// if (menudepth === 0) {
html = '';
// tagType='span';
} else if (typeof this.shortcut != 'undefined') {
// } else if (menurowdepth === 0) {
// tagType='li';
html = addPopupShortcut(html, this.shortcut);
// } 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 opening + html + closing;
return html;
};
}


navlinkTag.prototype.getNewWin = function () {
function navlinkTag() {
this.type='navlinkTag';
getValueOf('popupLinksNewWindow');
}
if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') {
this.newWin = null;
}
this.newWin = pg.option.popupLinksNewWindow[this.id];
};


navlinkTag.prototype.getPrintFunction = function () {
navlinkTag.prototype.html=function () {
this.getNewWin();
//think about this some more
this.getPrintFunction();
// this.id and this.article should already be defined
var html='';
if (typeof this.id != typeof '' || typeof this.article != typeof {}) {
var opening, closing;
return;
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() {
this.noPopup = 1;
getValueOf('popupLinksNewWindow');
switch (this.id) {
if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') { this.newWin=null; }
case 'contribs':
this.newWin=pg.option.popupLinksNewWindow[this.id];
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();
}


navlinkTag.prototype.getPrintFunction=function() { //think about this some more
switch (this.id) {
// this.id and this.article should already be defined
case 'userTalk':
if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; }
case 'newUserTalk':
var html='';
case 'editUserTalk':
var a,t;
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;
}


this.noPopup=1;
if (this.id == 'editMonobook' || this.id == 'monobook') {
switch (this.id) {
this.article.append('/monobook.js');
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();
}


if (this.id != 'mainlink') {
switch (this.id) {
case 'userTalk': case 'newUserTalk': case 'editUserTalk':
// FIXME anchor handling should be done differently with Title object
case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog':
this.article = this.article.removeAnchor();
// if (typeof this.text=='undefined') this.text=popupString(this.id);
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'); }
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;


if (this.id != 'mainlink') {
case 'delete':
// FIXME anchor handling should be done differently with Title object
this.print = wikiLink;
this.article=this.article.removeAnchor();
this.action = 'delete';
// if (typeof this.text=='undefined') this.text=popupString(this.id);
if (this.article.namespaceId() == pg.nsImageId) {
}
var img = this.article.stripNamespace();
this.action += '&image=' + img;
}
break;


switch (this.id) {
case 'markpatrolled':
case 'undelete': this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break;
case 'edit': // editOld should keep the oldid, but edit should not.
case 'whatLinksHere': this.print=specialLink; this.specialpage='Whatlinkshere'; break;
delete this.oldid;
case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break;
/* fall through */
case 'move': this.print=specialLink; this.specialpage='Movepage'; break;
case 'view':
case 'contribs': this.print=specialLink; this.specialpage='Contributions'; break;
case 'purge':
case 'deletedContribs':this.print=specialLink; this.specialpage='Deletedcontributions'; break;
case 'render':
case 'email': this.print=specialLink; this.specialpage='EmailUser'; this.sep='/'; break;
this.print = wikiLink;
case 'block': this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break;
this.action = this.id;
case 'unblock': this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break;
break;
case 'userlog': this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break;
case 'raw':
case 'blocklog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break;
this.print = wikiLink;
case 'pagelog': this.print=specialLink; this.specialpage='Log'; this.sep='&page='; break;
this.action = 'raw';
case 'protectlog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=protect&page='; break;
break;
case 'deletelog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=delete&page='; break;
case 'new':
case 'userSpace': this.print=specialLink; this.specialpage='PrefixIndex'; this.sep='&namespace=2&prefix='; break;
this.print = wikiLink;
case 'search': this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break;
this.action = 'edit&section=new';
case 'thank': this.print=specialLink; this.specialpage='Thanks'; this.sep='/'; this.article.value = this.diff; break;
break;
case 'mainlink':
case 'unwatch': case 'watch':
this.print=magicWatchLink; this.action=this.id+'&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken='+autoClickToken(); break;
if (typeof this.text == 'undefined') {
case 'history': case 'historyfeed':
this.text = this.article.toString().entify();
case 'unprotect': case 'protect':
}
this.print=wikiLink; this.action=this.id; break;
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


case 'delete':
// STARTFILE: shortcutkeys.js
this.print=wikiLink; this.action='delete';
function popupHandleKeypress(evt) {
if (this.article.namespaceId()==pg.nsImageId) {
var keyCode = window.event ? window.event.keyCode : evt.keyCode ? evt.keyCode : evt.which;
var img=this.article.stripNamespace();
if (!keyCode || !pg.current.link || !pg.current.link.navpopup) {
this.action+='&image='+img;
return;
}
if (keyCode == 27) {
// escape
killPopup();
return false; // swallow keypress
}
}
break;


case 'markpatrolled':
var letter = String.fromCharCode(keyCode);
case 'edit': // editOld should keep the oldid, but edit should not.
var links = pg.current.link.navpopup.mainDiv.getElementsByTagName('A');
delete this.oldid;
var startLink = 0;
/* fall through */
var i, j;
case 'view': case 'purge': case 'render':

this.print=wikiLink;
if (popupHandleKeypress.lastPopupLinkSelected) {
this.action=this.id; break;
for (i = 0; i < links.length; ++i) {
case 'raw':
if (links[i] == popupHandleKeypress.lastPopupLinkSelected) {
this.print=wikiLink; this.action='raw&ctype=text/css'; break;
startLink = i;
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)) {
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;
for (j = 0; j < links.length; ++j) {
if (typeof this.title==='undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') {
i = (startLink + j + 1) % links.length;
this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article);
if (links[i].getAttribute('popupkey') == letter) {
if (evt && evt.preventDefault) {
if (typeof this.oldid !== 'undefined' && this.oldid) {
this.title=tprintf('Revision %s of %s', [this.oldid, this.title]);
evt.preventDefault();
}
links[i].focus();
popupHandleKeypress.lastPopupLinkSelected = links[i];
return false; // swallow keypress
}
}
}
}
this.action='view'; break;

case 'userPage':
// pass keypress on
case 'article':
if (document.oldPopupOnkeypress) {
case 'monobook':
return document.oldPopupOnkeypress(evt);
case 'editMonobook':
}
case 'editArticle':
return true;
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 'markpatrolled':
this.print=wikiLink; this.action='markpatrolled';
/* fall through */
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: '+this.id+'';};
}
}
};

//
function addPopupShortcuts() {
// end navlinks
if (document.onkeypress != popupHandleKeypress) {
//////////////////////////////////////////////////
document.oldPopupOnkeypress = document.onkeypress;
//</NOLITE>
}
// ENDFILE: navlinks.js
document.onkeypress = popupHandleKeypress;
// STARTFILE: shortcutkeys.js
//<NOLITE>
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);
function rmPopupShortcuts() {
var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A');
popupHandleKeypress.lastPopupLinkSelected = null;
var startLink=0;
try {
var i,j;
if (document.oldPopupOnkeypress && document.oldPopupOnkeypress == popupHandleKeypress) {
// panic
document.onkeypress = null; //function () {};
return;
}
document.onkeypress = document.oldPopupOnkeypress;
} catch (nasties) {
/* IE goes here */
}
}


if (popupHandleKeypress.lastPopupLinkSelected) {
function addLinkProperty(html, property) {
for (i=0; i<links.length; ++i) {
// take "<a href=...>...</a> and add a property
if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; }
// not sophisticated at all, easily broken
var i = html.indexOf('>');
if (i < 0) {
return html;
}
}
return html.substring(0, i) + ' ' + property + html.substring(i);
}
}
for (j=0; j<links.length; ++j) {

i=(startLink + j + 1) % links.length;
function addPopupShortcut(html, key) {
if (!getValueOf('popupShortcutKeys')) {
if (links[i].getAttribute('popupkey')==letter) {
if (evt && evt.preventDefault) evt.preventDefault();
return html;
links[i].focus();
}
popupHandleKeypress.lastPopupLinkSelected=links[i];
var ret = addLinkProperty(html, 'popupkey="' + key + '"');
return false; // swallow keypress
if (key == ' ') {
key = popupString('spacebar');
}
}
return ret.replace(/^(.*?)(title=")(.*?)(".*)$/i, '$1$2$3 [' + key + ']$4');
}
}
// ENDFILE: shortcutkeys.js


// pass keypress on
// STARTFILE: diffpreview.js
if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); }
/**
return true;
* 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();


function addPopupShortcuts() {
switch (diff) {
if (document.onkeypress!=popupHandleKeypress) {
case 'cur':
document.oldPopupOnkeypress=document.onkeypress;
switch (oldid) {
}
case null:
document.onkeypress=popupHandleKeypress;
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) {
function rmPopupShortcuts() {
popupHandleKeypress.lastPopupLinkSelected=null;
navpop.diffData.oldRev.revid = data.compare.fromrevid;
try {
navpop.diffData.newRev.revid = data.compare.torevid;
if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) {
// panic
document.onkeypress=null; //function () {};
return;
}
document.onkeypress=document.oldPopupOnkeypress;
} catch (nasties) { /* IE goes here */ }
}


addReviewLink(navpop, 'popupMiscTools');


var go = function () {
function addLinkProperty(html, property) {
// take "<a href=...>...</a> and add a property
pendingNavpopTask(navpop);
// not sophisticated at all, easily broken
var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';
var i=html.indexOf('>');
if (i<0) { return html; }
return html.substring(0,i) + ' ' + property + html.substring(i);
}


function addPopupShortcut(html, key) {
url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid;
if (!getValueOf('popupShortcutKeys')) { return html; }
url += '&prop=revisions&rvslots=main&rvprop=ids|timestamp|content';
var ret= addLinkProperty(html, 'popupkey="'+key+'"');
if (key==' ') { key=popupString('spacebar'); }
return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4');
}
//</NOLITE>
// ENDFILE: shortcutkeys.js
// STARTFILE: diffpreview.js
//<NOLITE>
function loadDiff(article, oldid, diff, navpop) {
navpop.diffData={};
var oldRev, newRev;
switch (diff) {
case 'cur':
switch ( oldid ) {
case null:
case '':
case 'prev':
// eg newmessages diff link
oldRev='0&direction=prev';
newRev=0;
break;
default:
oldRev = oldid;
newRev = ( oldid || 0 ) + '&direction=cur';
}
break;
case 'prev':
oldRev = ( oldid || 0 ) + '&direction=prev'; newRev = oldid; break;
case 'next':
oldRev = oldid; newRev = oldid + '&direction=next';
break;
default:
oldRev = oldid || 0; newRev = diff || 0; break;
}
oldRev = oldRev || 0;
newRev = newRev || 0;


var go = function() {
getPageWithCaching(url, doneDiff, navpop);
pendingNavpopTask(navpop);
getWiki(article, doneDiffNew, newRev, navpop);


pendingNavpopTask(navpop);
return true; // remove hook once run
getWiki(article, doneDiffOld, oldRev, navpop);
};
if (navpop.visible || !getValueOf('popupLazyDownloads')) {
go();
} else {
navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS');
}
});
});
}


var tz = Cookie.read('popTz');
// Put a "mark patrolled" link to an element target
if ( mw.config.get('wgEnableAPI') && getValueOf('popupAdjustDiffDates') && tz === null) {
// TODO: Allow patrol a revision, as well as a diff
pendingNavpopTask(navpop);
function addReviewLink(navpop, target) {
getPageWithCaching(pg.wiki.apiwikibase + '?format=json&action=query&meta=userinfo&uiprop=options',
if (!pg.user.canReview) {
function(d) {
return;
completedNavpopTask(navpop);
setTimecorrectionCookie(d);
if (diffDownloadsComplete(navpop)) { insertDiff(navpop); }
}, navpop);
}
}
return true; // remove hook once run
// 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) {
if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
return;
else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); }
}
}
var params = {

action: 'query',
function setTimecorrectionCookie(d) {
prop: 'info|flagged',
try {
revids: navpop.diffData.oldRev.revid,
var jsobj=getJsObj(d.data);
formatversion: 2,
var tz=jsobj.query.userinfo.options.timecorrection;
};
Cookie.create( 'popTz', getTimeOffset(tz), 1);
getMwApi()
} catch (someError) {
.get(params)
logerr( 'setTimecorretion failed' );
.then(function (data) {
return;
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) {
function doneDiff(download, isOld) {
if (!download.owner || !download.owner.diffData) {
if (!download.owner || !download.owner.diffData) { return; }
var navpop=download.owner;
return;
var label= (isOld) ? 'Old' : 'New';
}
var otherLabel=(isOld) ? 'New' : 'Old';
var navpop = download.owner;
navpop.diffData[label]=download;
completedNavpopTask(navpop);
completedNavpopTask(download.owner);
if (diffDownloadsComplete(navpop)) { insertDiff(navpop); }
}


function diffDownloadsComplete(navpop) {
var pages,
if ( Cookie.read('popTz')===null) { return false; }
revisions = [];
return navpop.diffData.Old && navpop.diffData.New;
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');
}


function doneDiffNew(download) { doneDiff(download, false); }
insertDiff(navpop);
function doneDiffOld(download) { doneDiff(download, true); }
}


function rmBoringLines(a, b, context) {
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;


if (typeof context == 'undefined') { context=2; }
// first, gather all disconnected nodes in a and all crossing nodes in a and b
// this is fairly slow... i think it's quicker than doing a word-based diff from the off, though
for (i = 0; i < a.length; ++i) {
var aa=[], aaa=[];
if (!a[i].paired) {
aa[i] = 1;
var bb=[], bbb=[];
var i, j;
} else if (countCrossings(b, a, i, true)) {
aa[i] = 1;
bb[a[i].row] = 1;
}
}


// pick up remaining disconnected nodes in b
// first, gather all disconnected nodes in a and all crossing nodes in a and b
for (i = 0; i < b.length; ++i) {
for (i=0; i<a.length; ++i ) {
if (bb[i] == 1) {
if(!a[i].paired) { aa[i]=1; }
else if (countCrossings(b,a,i, true)) {
continue;
}
aa[i]=1;
if (!b[i].paired) {
bb[ a[i].row ] = 1;
bb[i] = 1;
}
}
}
}


// pick up remaining disconnected nodes in b
// another pass to gather context: we want the neighbours of included nodes which are not
for (i=0; i<b.length; ++i ) {
// yet included we have to add in partners of these nodes, but we don't want to add context
if (bb[i]==1) { continue; }
// for *those* nodes in the next pass
if(!b[i].paired) { bb[i]=1; }
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) {
// another pass to gather context: we want the neighbours of included nodes which are not yet included
if (!bb[j]) {
// we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass
bb[j] = 1;
for (i=0; i<b.length; ++i) {
aa[b[j].row] = 0.5;
if ( bb[i] == 1 ) {
}
for (j=max(0,i-context); j < 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) {
for (i=0; i<a.length; ++i) {
if (aa[i] == 1) {
if ( aa[i] == 1 ) {
for (j = Math.max(0, i - context); j < Math.min(a.length, i + context); ++j) {
for (j=max(0,i-context); j < min(a.length, i+context); ++j) {
if (!aa[j]) {
if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; }
aa[j] = 1;
bb[a[j].row] = 0.5;
}
}
}
}
}
}
}


for (i = 0; i < bb.length; ++i) {
for (i=0; i<bb.length; ++i) {
if (bb[i] > 0) {
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
// it's a row we need
if (b[i].paired) {
else {
bbb.push(b[i].text);
bbb.push(b[i]);
} // joined; partner should be in aa
else {
bbb.push(b[i]);
}
}
}
}
}
}
for (i = 0; i < aa.length; ++i) {
if (aa[i] > 0) {
for (i=0; i<aa.length; ++i) {
// it's a row we need
if (aa[i] > 0) { // it's a row we need
if (a[i].paired) {
if (a[i].paired) { aaa.push(a[i].text); } // joined; partner should be in aa
else {
aaa.push(a[i].text);
aaa.push(a[i]);
} // joined; partner should be in aa
else {
aaa.push(a[i]);
}
}
}
}
}

return { a: aaa, b: bbb };
}
}


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;
}


function stripOuterCommonLines(a,b,context) {
return {
var i=0;
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)),
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(max(0,i - 1 - context), min(a.length+1, j + context+1)),
function insertDiff(navpop) {
b: b.slice(max(0,i - 1 - context), min(b.length+1, k + context+1)) };
// 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;
}


function insertDiff(navpop) {
var lineDiff = diff(oldlines, newlines);
// for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff
var lines2 = rmBoringLines(lineDiff.o, lineDiff.n);
// FIXME: sometimes this gives misleading diffs as distant chunks are squashed together
var oldlines2 = lines2.a;
var oldlines=navpop.diffData.Old.data.split('\n');
var newlines2 = lines2.b;
var newlines=navpop.diffData.New.data.split('\n');

var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines'));
var simpleSplit = !String.prototype.parenSplit.isNative;
oldlines=inner.a; newlines=inner.b;
var html = '<hr />';
var truncated=false;
if (getValueOf('popupDiffDates')) {
getValueOf('popupDiffMaxLines');
html += diffDatesTable(navpop);
if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) {
html += '<hr />';
// truncate
}
truncated=true;
html += shortenDiffString(
inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines),
diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),
newlines.slice(0,pg.option.popupDiffMaxLines),
getValueOf('popupDiffContextCharacters')
pg.option.popupDiffContextLines);
).join('<hr />');
oldlines=inner.a; newlines=inner.b;
setPopupTipsAndHTML(
html.split('\n').join('<br>') +
(truncated ?
'<hr /><b>' + popupString('Diff truncated for performance reasons') + '</b>' :
''),
'popupPreview',
navpop.idNumber
);
}
}


var lineDiff=diff(oldlines, newlines);
function diffDatesTable(navpop) {
var lines2=rmBoringLines(lineDiff.o, lineDiff.n);
var html = '<table class="popup_diff_dates">';
var oldlines2=lines2.a; var newlines2=lines2.b;
html += diffDatesTableRow(navpop.diffData.newRev.revision, tprintf('New revision'));

html += diffDatesTableRow(navpop.diffData.oldRev.revision, tprintf('Old revision'));
var simpleSplit = !String.prototype.parenSplit.isNative;
html += '</table>';
return html;
var html='<hr />';
if (getValueOf('popupDiffDates')) {
html += diffDatesTable(navpop.diffData.Old, navpop.diffData.New);
html += '<hr />';
}
}
html += shortenDiffString(
function diffDatesTableRow(revision, label) {
diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),
var txt = '';
getValueOf('popupDiffContextCharacters') ).join('<hr />');
var lastModifiedDate = new Date(revision.timestamp);
setPopupTipsAndHTML(html.split('\n').join('<br>') +
(truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') ,
'popupPreview', navpop.idNumber);
}


function diffDatesTable( oldDl, newDl ) {
txt = formattedDateTime(lastModifiedDate);
var html='<table class="popup_diff_dates">';

html += diffDatesTableRow( newDl, tprintf('New revision'));
var revlink = generalLink({
html += diffDatesTableRow( oldDl, tprintf('Old revision'));
url: mw.config.get('wgScript') + '?oldid=' + revision.revid,
html += '</table>';
text: label,
return html;
title: label,
}
});
function diffDatesTableRow( dl, label ) {
return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [revlink, txt]);
var txt='';
if (!dl) {
txt=popupString('Something went wrong :-(');
} else if (!dl.lastModified) {
txt= (/^\s*$/.test(dl.data)) ?
popupString('Empty revision, maybe non-existent') : popupString('Unknown date');
} else {
var datePrint=getValueOf('popupDiffDatePrinter');
if (typeof dl.lastModified[datePrint] == 'function') {
if (getValueOf('popupAdjustDiffDates')) {
var off = Cookie.read('popTz');
if (off) {
var d2 = adjustDate(dl.lastModified, off);
txt = dayFormat(d2, true) + ' ' + timeFormat(d2, true);
}
} else {
txt = dl.lastModified[datePrint]();
}
} else {
txt = tprintf('Invalid %s %s', ['popupDiffDatePrinter', datePrint]);
}
}
}
var revlink = generalLink({url: dl.url.replace(/&.*?(oldid=[0-9]+(?:&direction=[^&]*)?).*/, '&$1'),
// ENDFILE: diffpreview.js
text: label, title: label});
return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]);
}
//</NOLITE>
// ENDFILE: diffpreview.js
// STARTFILE: links.js
//<NOLITE>
/////////////////////
// LINK GENERATION //
/////////////////////


// titledDiffLink --> titledWikiLink --> generalLink
// STARTFILE: links.js
// wikiLink --> titledWikiLink --> generalLink
/////////////////////
// editCounterLink --> generalLink
// LINK GENERATION //
/////////////////////


// TODO Make these functions return Element objects, not just raw HTML strings.
// titledDiffLink --> titledWikiLink --> generalLink
// wikiLink --> titledWikiLink --> generalLink
// editCounterLink --> generalLink


function titledDiffLink(l) { // article, text, title, from, to) {
// TODO Make these functions return Element objects, not just raw HTML strings.
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 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) {
function wikiLink(l) {
//{article:article, action:action, text:text, oldid, newid}) {
//{article:article, action:action, text:text, oldid, newid}) {
if (! (typeof l.article == typeof {} &&
if (
!(typeof l.article == typeof {} && typeof l.action == typeof '' && typeof l.text == typeof '')
typeof l.action == typeof '' &&
typeof l.text==typeof '')) return null;
) {
if (typeof l.oldid == 'undefined') { l.oldid=null; }
return 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');
if (typeof l.oldid == 'undefined') {
break;
l.oldid = null;
case 'raw&ctype=text/css': hint=popupString('rawHint'); break;
case 'revert':
if ( !mw.config.get('wgEnableAPI') ) {
alert( 'This function of navigation popups now requires a MediaWiki ' +
'installation with the API enabled.');
break;
}
}
var p=parseParams(pg.current.link.href);
var savedOldid = l.oldid;
l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff);
if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) {
if (p.diff=='prev') {
l.oldid = null;
l.action += '&direction=prev';
revisionString = tprintf('the revision prior to revision %s of %s', oldidData);
}
}
if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; }
var hint = popupString(l.action + 'Hint'); // revertHint etc etc etc
if (getValueOf('popupMinorReverts')) { l.action += '&autominor=true'; }
var oldidData = [l.oldid, safeDecodeURI(l.article)];
log('revisionString is now '+revisionString);
var revisionString = tprintf('revision %s of %s', oldidData);
break;
log('revisionString=' + revisionString);
case 'nullEdit':
switch (l.action) {
l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null';
case 'edit&section=new':
break;
hint = popupString('newSectionHint');
case 'historyfeed':
break;
l.action='history&feed=rss';
case 'edit&undo=':
break;
if (l.diff && l.diff != 'prev' && savedOldid) {
case 'markpatrolled':
l.action += l.diff + '&undoafter=' + savedOldid;
l.action='markpatrolled&rcid='+l.rcid;
} 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,
});
}
}


if (hint) {
function revertSummary(oldid, diff) {
if (l.oldid) {
var ret = '';
hint = simplePrintf(hint, [revisionString]);
if (diff == 'prev') {
}
ret = getValueOf('popupQueriedRevertToPreviousSummary');
} else {
else {
hint = simplePrintf(hint, [safeDecodeURI(l.article)]);
ret = getValueOf('popupQueriedRevertSummary');
}
}
}
return ret + '&autorv=' + oldid;
else {
hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid='+l.oldid : '';
}
}


function titledWikiLink(l) {
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});
// possible properties of argument:
}
// article, action, text, title, oldid, actionName, className, noPopup
// oldid = null is fine here


function revertSummary(oldid, diff) {
// article and action are mandatory args
var ret='';
if (diff == 'prev') {
ret=getValueOf('popupQueriedRevertToPreviousSummary');
} else { ret = getValueOf('popupQueriedRevertSummary'); }
return ret + '&autorv=' + oldid;
}


function titledWikiLink(l) {
if (typeof l.article == 'undefined' || typeof l.action == 'undefined') {
// possible properties of argument:
errlog('got undefined article or action in titledWikiLink');
// article, action, text, title, oldid, actionName, className, noPopup
return null;
// oldid = null is fine here
}


// article and action are mandatory args
var base = pg.wiki.titlebase + l.article.urlString();
var url = base;


if (typeof l.actionName == 'undefined' || !l.actionName) {
if (typeof l.article == 'undefined' || typeof l.action=='undefined') {
errlog('got undefined article or action in titledWikiLink');
l.actionName = 'action';
return null;
}
}


var base = pg.wiki.titlebase + l.article.urlString();
// no need to add &action=view, and this confuses anchors
var url=base;
if (l.action != 'view') {
url = base + '&' + l.actionName + '=' + l.action;
}


if (typeof l.oldid != 'undefined' && l.oldid) {
if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; }
url += '&oldid=' + l.oldid;
}


// no need to add &action=view, and this confuses anchors
var cssClass = pg.misc.defaultNavlinkClassname;
if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; }
if (typeof l.className != 'undefined' && l.className) {
cssClass = l.className;
}


if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; }
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,
});
}


var cssClass=pg.misc.defaultNavlinkClassname;
pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) {
if (typeof l.className!='undefined' && l.className) { cssClass=l.className; }
getHistoryInfo(wikipage, function (x) {
processLastContribInfo(x, { page: wikipage, newWin: newWin });
});
};


return generalNavLink({url: url, newWin: l.newWin,
function processLastContribInfo(info, stuff) {
title: (typeof l.title != 'undefined') ? l.title : null,
if (!info.edits || !info.edits.length) {
text: (typeof l.text!='undefined')?l.text:null,
alert('Popups: an odd thing happened. Please retry.');
className: cssClass, noPopup:l.noPopup, onclick:l.onclick});
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) {
pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) {
getHistoryInfo(wikipage, function (x) {
getHistoryInfo(wikipage, function(x) {
processDiffSinceMyEdit(x, { page: wikipage, newWin: newWin });
processLastContribInfo(x, {page: wikipage, newWin: newWin});
});
});
};
};


function processDiffSinceMyEdit(info, stuff) {
function processLastContribInfo(info, stuff) {
if (!info.edits || !info.edits.length) {
if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; }
if(!info.firstNewEditor) {
alert('Popups: something fishy happened. Please try again.');
alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor,info.edits.length]));
return;
}
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);
}
}
var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+info.firstNewEditor.oldid;
displayUrl(newUrl, stuff.newWin);
}


function displayUrl(url, newWin) {
pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) {
getHistoryInfo(wikipage, function(x){
if (newWin) {
processDiffSinceMyEdit(x, {page: wikipage, newWin: newWin});
window.open(url);
});
} else {
};
document.location = url;

}
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);
}


pg.fn.purgePopups = function purgePopups() {
function displayUrl(url, newWin){
if(newWin) { window.open(url); }
processAllPopups(true);
else { document.location=url; }
setupCache(); // deletes all cached items (not browser cached, though...)
}
pg.option = {};
abortAllDownloads();
};


function processAllPopups(nullify, banish) {
pg.fn.purgePopups = function purgePopups() {
processAllPopups(true);
for (var i = 0; pg.current.links && i < pg.current.links.length; ++i) {
setupCache(); // deletes all cached items (not browser cached, though...)
if (!pg.current.links[i].navpopup) {
pg.option={};
continue;
abortAllDownloads();
}
};
if (nullify || banish) {

pg.current.links[i].navpopup.banish();
function processAllPopups(nullify, banish) {
}
pg.current.links[i].simpleNoMore = false;
for (var i=0; pg.current.links && i<pg.current.links.length; ++i) {
if (!pg.current.links[i].navpopup) { continue; }
if (nullify) {
pg.current.links[i].navpopup = null;
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() {
pg.fn.disablePopups = function disablePopups(){
processAllPopups(false, true);
processAllPopups(false, true);
setupTooltips(null, true);
setupTooltips(null, true);
};
};


pg.fn.togglePreviews = function togglePreviews() {
pg.fn.togglePreviews = function togglePreviews() {
processAllPopups(true, true);
processAllPopups(true, true);
pg.option.simplePopups = !pg.option.simplePopups;
pg.option.simplePopups=!pg.option.simplePopups;
abortAllDownloads();
abortAllDownloads();
};
};


function magicWatchLink(l) {
function magicWatchLink(l) {
//Yuck!! Would require a thorough redesign to add this as a click event though ...
//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.onclick = simplePrintf( 'pg.fn.modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id] );
return wikiLink(l);
l.article.toString(true).split('\\').join('\\\\').split("'").join("\\'"),
}
this.id,
]);
return wikiLink(l);
}


pg.fn.modifyWatchlist = function modifyWatchlist(title, action) {
pg.fn.modifyWatchlist = function modifyWatchlist(title, action) {
var reqData = {
var reqData = {
action: 'watch',
'action': 'watch',
'format': 'json',
formatversion: 2,
titles: title,
'title': title,
uselang: mw.config.get('wgUserLanguage'),
'token': mw.user.tokens.get('watchToken'),
'uselang': mw.config.get('wgUserLanguage')
};
};
if (action === 'unwatch') {
reqData.unwatch = true;
if ( action === 'unwatch' ) reqData.unwatch = '';
}


jQuery.ajax( {
url: mw.util.wikiScript( 'api' ),
dataType: 'json',
type: 'POST',
data: reqData
} ).done( function () {
// Load the Addedwatchtext or Removedwatchtext message and show it
// Load the Addedwatchtext or Removedwatchtext message and show it
var mwTitle = mw.Title.newFromText(title);
var mwTitle = mw.Title.newFromText( title );
var messageName;
var messageName;
if (mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1) {
if ( mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) {
messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk';
messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk';
} else {
} else {
messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext';
messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext';
}
}
mw.loader.using( [ 'mediawiki.api.messages', 'mediawiki.jqueryMsg' ] )
$.when(
.then( function () {
getMwApi().postWithToken('watch', reqData),
getMwApi().loadMessagesIfMissing([messageName])
return new mw.Api().loadMessagesIfMissing( [ messageName ] )
} )
).done(function () {
.done( function ( messages ) {
mw.notify(mw.message(messageName, title).parseDom());
mw.notify( mw.message( messageName, title ).parseDom() );
});
};
} );
} );
};


function magicHistoryLink(l) {
function magicHistoryLink(l) {
// FIXME use onclick change href trick to sort this out instead of window.open
// FIXME use onclick change href trick to sort this out instead of window.open


var jsUrl = '',
var jsUrl='', title='', onClick='';
switch(l.id) {
title = '',
case 'lastContrib':
onClick = '';
onClick=simplePrintf('pg.fn.getLastContrib(\'%s\',%s)',
switch (l.id) {
[l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);
case 'lastContrib':
title=popupString('lastContribHint');
onClick = simplePrintf("pg.fn.getLastContrib('%s',%s)", [
break;
l.article.toString(true).split('\\').join('\\\\').split("'").join("\\'"),
case 'sinceMe':
l.newWin,
onClick=simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)',
]);
[l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);
title = popupString('lastContribHint');
title=popupString('sinceMeHint');
break;
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,
});
}
}
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
function popupMenuLink(l) {
title: title, text: l.text, noPopup: l.noPopup, onclick: onClick });
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]);
function popupMenuLink(l) {
return generalNavLink({
var jsUrl=simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line
url: jsUrl,
var title=popupString(simplePrintf('%sHint', [l.id]));
newWin: false,
var onClick=simplePrintf('pg.fn.%s();return false;', [l.id]);
title: title,
return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup, onclick: onClick});
text: l.text,
}
noPopup: l.noPopup,
onclick: onClick,
});
}


function specialLink(l) {
function specialLink(l) {
// properties: article, specialpage, text, sep
// properties: article, specialpage, text, sep
if (typeof l.specialpage == 'undefined' || !l.specialpage) {
if (typeof l.specialpage=='undefined'||!l.specialpage) return null;
var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage;
return null;
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;
var base =
case 'PrefixIndex': article += '/'; break;
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,
});
}
}
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
* @return {string|null} null if no url is given
*/
function generalLink(link) {
if (typeof link.url == 'undefined') {
return null;
}


function generalLink(l) {
var elem = document.createElement( 'a' );
// l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick
if (typeof l.url=='undefined') return null;


// only quotation marks in the url can screw us up now... I think
elem.href = link.url;
var url=l.url.split('"').join('%22');
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 );


var ret='<a href="' + url + '"';
if ( link.noPopup ) {
if (typeof l.title!='undefined' && l.title) { ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"'; }
elem.setAttribute('noPopup', '1' );
if (typeof l.onclick!='undefined' && l.onclick) { ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"'; }
}
if (l.noPopup) { ret += ' noPopup=1'; }
var newWin;
if (typeof l.newWin=='undefined' || l.newWin === null) { newWin=getValueOf('popupNewWindows'); }
else { newWin=l.newWin; }
if (newWin) { ret += ' target="_blank"'; }
if (typeof l.className!='undefined'&&l.className) { ret+=' class="'+l.className+'"'; }
ret += '>';
if (typeof l.text==typeof '') { ret+= l.text; }
ret +='</a>';
return ret;
}


function appendParamsToLink(linkstr, params) {
var newWin;
var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i'));
if (typeof link.newWin == 'undefined' || link.newWin === null) {
if (sp.length<2) return null;
newWin = getValueOf('popupNewWindows');
var ret=sp.shift() + sp.shift();
} else {
ret += '&' + params + '"';
newWin = link.newWin;
ret += sp.join('');
}
return ret;
if (newWin) {
}
elem.target = '_blank';
}
if (link.className) {
elem.className = link.className;
}
elem.innerText = pg.unescapeQuotesHTML(link.text);


function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional) {
return elem.outerHTML;
if (x.newTarget) {
log ('changeLinkTargetLink: newTarget=' + x.newTarget);
}
}
if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) {

log ('This might be an input problem: ' + x.oldTarget );
function appendParamsToLink(linkstr, params) {
var sp = linkstr.parenSplit(/(href="[^"]+?)"/i);
if (sp.length < 2) {
return null;
}
var ret = sp.shift() + sp.shift();
ret += '&' + params + '"';
ret += sp.join('');
return ret;
}
}


// FIXME: first character of page title as well as namespace should be case insensitive
function changeLinkTargetLink(x) {
// eg category:X1 and Category:X1 are equivalent
// newTarget, text, hint, summary, clickButton, minor, title (optional), alsoChangeLabel {
// this'll break if charAt(0) is nasty
if (x.newTarget) {
var cA=literalizeRegex(x.oldTarget);
log('changeLinkTargetLink: newTarget=' + x.newTarget);
var chs=cA.charAt(0).toUpperCase();
}
chs='['+chs + chs.toLowerCase()+']';
if (x.oldTarget !== decodeURIComponent(x.oldTarget)) {
var currentArticleRegexBit=chs+cA.substring(1);
log('This might be an input problem: ' + x.oldTarget);
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
// 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(/(?:[_ ]+|%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*


var title=x.title || mw.config.get('wgPageName').split('_').join(' ');
// autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g
var lk=titledWikiLink({article: new Title(title), newWin:x.newWin,

action: 'edit',
var title = x.title || mw.config.get('wgPageName').split('_').join(' ');
text: x.text,
var lk = titledWikiLink({
article: new Title(title),
title: x.hint,
className: 'popup_change_title_link'
newWin: x.newWin,
});
action: 'edit',
var cmd='';
text: x.text,
if (x.newTarget) {
title: x.hint,
// escape '&' and other nasties
className: 'popup_change_title_link',
var t=x.newTarget;
});
var s=literalizeRegex(x.newTarget);
var cmd = '';
cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~[['+t+'|$1]]~g;';
if (x.newTarget) {
cmd += 's~\\[\\['+currentArticleRegexBit+'[|]~[['+t+'|~g;';
// escape '&' and other nasties
cmd += 's~\\[\\['+s + '\\|' + s + '\\]\\]~[[' + t + ']]~g';
var t = x.newTarget;
} else {
var s = mw.util.escapeRegExp(x.newTarget);
cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~$1~g;';
if (x.alsoChangeLabel) {
cmd += 's~\\[\\[' + currentArticleRegexBit + '\\]\\]~[[' + t + ']]~g;';
cmd += 's~\\[\\['+currentArticleRegexBit+'[|](.*?)\\]\\]~$2~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);
}
}
// 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 = '';


function redirLink(redirMatch, article) {
if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) {
// NB redirMatch is in wikiText
ret += '<hr />';
var ret='';


if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) {
if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) {
ret += popupString('Redirects to: (Fix ');
ret += '<hr />';
if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) {
log('redirLink: newTarget=' + redirMatch);
log('redirLink: newTarget=' + redirMatch);
ret += addPopupShortcut(
changeLinkTargetLink({
ret += addPopupShortcut(changeLinkTargetLink({
newTarget: redirMatch,
newTarget: redirMatch,
text: popupString('target'),
text: popupString('Redirects'),
hint: popupString('Fix this redirect, changing just the link target'),
hint: popupString('Fix this redirect'),
summary: simplePrintf(getValueOf('popupFixRedirsSummary'), [
summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]),
article.toString(),
oldTarget: article.toString(),
clickButton: getValueOf('popupRedirAutoClick'),
redirMatch,
]),
minor: true,
watch: getValueOf('popupWatchRedirredPages')
oldTarget: article.toString(),
}), 'R');
clickButton: getValueOf('popupRedirAutoClick'),
ret += popupString(' to ');
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'),
})
);
}
}
else ret += popupString('Redirects') + popupString(' to ');
return ret;
}
}


else return '<br> ' + popupString('Redirects') + popupString(' to ') +
function arinLink(l) {
titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view', /* FIXME: newWin */
if (!saneLinkCheck(l)) {
text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')});
return null;
}
}
if (!l.article.isIpUser() || !pg.wiki.wikimedia) {
return null;
}


function arinLink(l) {
var uN = l.article.userName();
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,
});
}


return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin,
function toolDbName(cookieStyle) {
title: tprintf('Look up %s in ARIN whois database', [uN]),
var ret = mw.config.get('wgDBname');
text: l.text, noPopup:1});
if (!cookieStyle) {
}
ret += '_p';
}
return ret;
}


function saneLinkCheck(l) {
function toolDbName(cookieStyle) {
var ret = mw.config.get('wgDBname');
if (typeof l.article != typeof {} || typeof l.text != typeof '') {
if (!cookieStyle) { ret+= '_p'; }
return false;
return ret;
}
}
return true;

}
function editCounterLink(l) {
function saneLinkCheck(l) {
if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; }
if (!saneLinkCheck(l)) {
return null;
return true;
}
}
function editCounterLink(l) {
if (!pg.wiki.wikimedia) {
return null;
if(!saneLinkCheck(l)) return null;
if (! pg.wiki.wikimedia) return null;
}
var uN = l.article.userName();
var uN=l.article.userName();
var tool = getValueOf('popupEditCounterTool');
var tool=getValueOf('popupEditCounterTool');
var url;
var url;
var defaultToolUrl = 'https://xtools.wmflabs.org/ec?user=$1&project=$2.$3&uselang=' + mw.config.get('wgUserLanguage');
var soxredToolUrl='//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3';


switch (tool) {
switch(tool) {
case 'custom':
case 'custom':
url = simplePrintf(getValueOf('popupEditCounterUrl'), [
url=simplePrintf(getValueOf('popupEditCounterUrl'), [ encodeURIComponent(uN), toolDbName() ]);
break;
encodeURIComponent(uN),
case 'kate':
toolDbName(),
case 'interiot':
]);
/* fall through */
break;
default:
case 'soxred': // no longer available
var theWiki=pg.wiki.hostname.split('.');
case 'kate': // no longer available
url=simplePrintf(soxredToolUrl, [ encodeURIComponent(uN), theWiki[0], theWiki[1] ]);
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,
});
}
}
return generalNavLink({url:url, title: tprintf('editCounterLinkHint', [uN]),
newWin:l.newWin, text: l.text, noPopup:1});
}


function globalSearchLink(l) {
if (!saneLinkCheck(l)) {
return null;
}


function globalSearchLink(l) {
var base = 'https://global-search.toolforge.org/?uselang=' + mw.config.get('wgUserLanguage') + '&q=';
if(!saneLinkCheck(l)) return null;
var article = l.article.urlString({ keepSpaces: true });


var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search=';
return generalNavLink({
var article=l.article.urlString({keepSpaces:true});
url: base + article,
newWin: l.newWin,
title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]),
text: l.text,
noPopup: 1,
});
}


return generalNavLink({url:base + article, newWin:l.newWin,
function googleLink(l) {
title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]),
if (!saneLinkCheck(l)) {
text: l.text, noPopup:1});
return null;
}
}


function googleLink(l) {
var base = 'https://www.google.com/search?q=';
if(!saneLinkCheck(l)) return null;
var article = l.article.urlString({ keepSpaces: true });


var base='http://www.google.com/search?q=';
return generalNavLink({
var article=l.article.urlString({keepSpaces:true});
url: base + '%22' + article + '%22',
newWin: l.newWin,
title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]),
text: l.text,
noPopup: 1,
});
}


return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin,
function editorListLink(l) {
title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]),
if (!saneLinkCheck(l)) {
text: l.text, noPopup:1});
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) {
function editorListLink(l) {
if(!saneLinkCheck(l)) return null;
l.className = l.className === null ? 'popupNavLink' : l.className;
var article= l.article.articleFromTalkPage() || l.article;
return generalLink(l);
var theWiki=pg.wiki.hostname.split('.');
}
var base='//tools.wmflabs.org/xtools/articleinfo/index.php?&uselang=' + mw.config.get('wgUserLanguage') +
'lang=' + theWiki[0] + '&wiki=' + theWiki[1] + '&begin=&end=&article=';
return generalNavLink({url:base+article.urlString(),
title: tprintf('editorListHint', [article]),
newWin:l.newWin, text: l.text, noPopup:1});
}


function generalNavLink(l) {
//////////////////////////////////////////////////
l.className = (l.className === null) ? 'popupNavLink' : l.className;
// magic history links
return generalLink(l);
//
}


//////////////////////////////////////////////////
function getHistoryInfo(wikipage, whatNext) {
// magic history links
log('getHistoryInfo');
//
getHistory(
wikipage,
whatNext ?
function (d) {
whatNext(processHistory(d));
} :
processHistory
);
}


function getHistoryInfo(wikipage, whatNext) {
// FIXME eliminate pg.idNumber ... how? :-(
log('getHistoryInfo');
getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory);
}


// FIXME eliminate pg.idNumber ... how? :-(
function getHistory(wikipage, onComplete) {

log('getHistory');
function getHistory(wikipage, onComplete) {
var url =
log('getHistory');
pg.wiki.apiwikibase +
if( !mw.config.get('wgEnableAPI') ) {
'?format=json&formatversion=2&action=query&prop=revisions&titles=' +
alert( 'This function of navigation popups now requires a MediaWiki ' +
new Title(wikipage).urlString() +
'installation with the API enabled.');
'&rvlimit=' +
return false;
getValueOf('popupHistoryLimit');
log('getHistory: url=' + url);
return startDownload(url, pg.idNumber + 'history', onComplete);
}
}
var url = pg.wiki.apiwikibase + '?format=json&action=query&prop=revisions&titles=' +
new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit');
log('getHistory: url='+url);
if (pg.flag.isIE) {
url = url + '&*'; //to circumvent https://bugzilla.wikimedia.org/show_bug.cgi?id=28840
}
return startDownload(url, pg.idNumber+'history', onComplete);
}


function processHistory(download) {
function processHistory(download) {
var jsobj = getJsObj(download.data);
var jsobj = getJsObj(download.data);
try {
try {
var revisions = anyChild(jsobj.query.pages).revisions;
window.x=jsobj;
var revisions = anyChild(jsobj.query.pages).revisions;
var edits = [];
var edits=[];
for (var i = 0; i < revisions.length; ++i) {
for (var i=0; i<revisions.length; ++i) {
edits.push({ oldid: revisions[i].revid, editor: revisions[i].user });
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([]);
}
}
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 = {};


function finishProcessHistory(edits, userName) {
histInfo.edits = edits;
histInfo.userName = userName;
var histInfo={};


histInfo.edits=edits;
for (var i = 0; i < edits.length; ++i) {
if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor == userName) {
histInfo.userName=userName;

histInfo.myLastEdit = {
for (var i=0; i<edits.length; ++i) {
index: i,
if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor==userName) {
oldid: edits[i].oldid,
previd: i === 0 ? null : edits[i - 1].oldid,
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) {
}
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)};
histInfo.firstNewEditor = {
index: i,
oldid: edits[i].oldid,
previd: i === 0 ? null : edits[i - 1].oldid,
};
}
}
}
//pg.misc.historyInfo=histInfo;
return histInfo;
}
}
//pg.misc.historyInfo=histInfo;
// ENDFILE: links.js
return histInfo;
}
//</NOLITE>
// ENDFILE: links.js
// STARTFILE: options.js
//////////////////////////////////////////////////
// options


// check for cookies and existing value, else use default
// STARTFILE: options.js
function defaultize(x) {
//////////////////////////////////////////////////
var val=null;
// options
if (x!='popupCookies') {

defaultize('popupCookies');
// check for existing value, else use default
if (pg.option.popupCookies && (val=Cookie.read(x))) {
function defaultize(x) {
if (pg.option[x] === null || typeof pg.option[x] == 'undefined') {
pg.option[x]=val;
return;
if (typeof window[x] != 'undefined') {
pg.option[x] = window[x];
} else {
pg.option[x] = pg.optionDefault[x];
}
}
}
}
}
if (pg.option[x]===null || typeof pg.option[x]=='undefined') {

if (typeof window[x] != 'undefined' ) pg.option[x]=window[x];
function newOption(x, def) {
pg.optionDefault[x] = def;
else pg.option[x]=pg.optionDefault[x];
}
}
}


function setDefault(x, def) {
function newOption(x, def) {
pg.optionDefault[x]=def;
return newOption(x, def);
}
}


function getValueOf(varName) {
function setDefault(x, def) {
return newOption(x, def);
defaultize(varName);
}
return pg.option[varName];

function getValueOf(varName) {
defaultize(varName);
return pg.option[varName];
}

function useDefaultOptions() { // for testing
for (var p in pg.optionDefault) {
pg.option[p]=pg.optionDefault[p];
if (typeof window[p]!='undefined') { delete window[p]; }
}
}
}


function setOptions() {
/*eslint-disable */
// user-settable parameters and defaults
function useDefaultOptions() {
var userIsSysop = false;
// for testing
if ( mw.config.get('wgUserGroups') ) {
for (var p in pg.optionDefault) {
for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) {
pg.option[p] = pg.optionDefault[p];
if (typeof window[p] != 'undefined') {
if ( mw.config.get('wgUserGroups')[g] == "sysop" )
delete window[p];
userIsSysop = true;
}
}
}
}
}
/*eslint-enable */


// Basic options
function setOptions() {
newOption('popupDelay', 0.5);
// user-settable parameters and defaults
newOption('popupHideDelay', 0.5);
var userIsSysop = false;
newOption('simplePopups', false);
if (mw.config.get('wgUserGroups')) {
newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true
for (var g = 0; g < mw.config.get('wgUserGroups').length; ++g) {
newOption('popupActionsMenu', true);
if (mw.config.get('wgUserGroups')[g] == 'sysop') {
newOption('popupSetupMenu', true);
userIsSysop = true;
newOption('popupAdminLinks', userIsSysop);
}
newOption('popupShortcutKeys', false);
}
newOption('popupHistoricalLinks', true);
}
newOption('popupOnlyArticleLinks', true);
newOption('removeTitles', true);
newOption('popupMaxWidth', 350);
newOption('popupInitialWidth', false); // integer or false
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('popupAdjustDiffDates', true);
newOption('popupActiveNavlinks', true);
newOption('popupModifier', false); // ctrl, shift, alt or meta
newOption('popupModifierAction', 'enable'); // or 'disable'
newOption('popupDraggable', true);


//<NOLITE>
// Basic options
// images
newOption('popupDelay', 0.5);
newOption('popupHideDelay', 0.5);
newOption('popupImages', true);
newOption('simplePopups', false);
newOption('imagePopupsForImages', true);
newOption('popupNeverGetThumbs', false);
newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true
newOption('popupActionsMenu', true);
//newOption('popupImagesToggleSize', true);
newOption('popupSetupMenu', true);
newOption('popupThumbAction', 'imagepage'); //'sizetoggle');
newOption('popupAdminLinks', userIsSysop);
newOption('popupImageSize', 60);
newOption('popupShortcutKeys', false);
newOption('popupImageSizeLarge', 200);
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',
});


// redirs, dabs, reversion
// images
newOption('popupImages', true);
newOption('popupFixRedirs', false);
newOption('imagePopupsForImages', true);
newOption('popupRedirAutoClick', 'wpDiff');
newOption('popupNeverGetThumbs', false);
newOption('popupFixDabs', false);
//newOption('popupImagesToggleSize', true);
newOption('popupDabsAutoClick', 'wpDiff');
newOption('popupThumbAction', 'imagepage'); //'sizetoggle');
newOption('popupRevertSummaryPrompt', false);
newOption('popupImageSize', 60);
newOption('popupMinorReverts', false);
newOption('popupImageSizeLarge', 200);
newOption('popupRedlinkRemoval', false);
newOption('popupWatchDisambiggedPages', null);
newOption('popupWatchRedirredPages', null);
newOption('popupDabWiktionary', 'last');


// navlinks
// redirs, dabs, reversion
newOption('popupFixRedirs', false);
newOption('popupNavLinks', true);
newOption('popupRedirAutoClick', 'wpDiff');
newOption('popupNavLinkSeparator', ' &sdot; ');
newOption('popupFixDabs', false);
newOption('popupLastEditLink', true);
newOption('popupDabsAutoClick', 'wpDiff');
newOption('popupEditCounterTool', 'soxred');
newOption('popupRevertSummaryPrompt', false);
newOption('popupEditCounterUrl', '');
newOption('popupMinorReverts', false);
newOption('popupExtraUserMenu', '');
//</NOLITE>
newOption('popupRedlinkRemoval', false);
newOption('popupRedlinkAutoClick', 'wpDiff');
newOption('popupWatchDisambiggedPages', null);
newOption('popupWatchRedirredPages', null);
newOption('popupDabWiktionary', 'last');


// previews etc
// navlinks
newOption('popupNavLinks', true);
newOption('popupPreviews', true);
newOption('popupNavLinkSeparator', ' &sdot; ');
newOption('popupSummaryData', true);
newOption('popupLastEditLink', true);
newOption('popupMaxPreviewSentences', 5);
newOption('popupEditCounterTool', 'supercount');
newOption('popupMaxPreviewCharacters', 600);
newOption('popupEditCounterUrl', '');
newOption('popupLastModified', true);
newOption('popupPreviewKillTemplates', true);
newOption('popupPreviewRawTemplates', true);
newOption('popupPreviewFirstParOnly', true);
newOption('popupPreviewCutHeadings', true);
newOption('popupPreviewButton', false);
newOption('popupPreviewButtonEvent', 'click');


//<NOLITE>
// previews etc
// diffs
newOption('popupPreviews', true);
newOption('popupSummaryData', true);
newOption('popupPreviewDiffs', true);
newOption('popupMaxPreviewSentences', 5);
newOption('popupDiffMaxLines', 100);
newOption('popupMaxPreviewCharacters', 600);
newOption('popupDiffContextLines', 2);
newOption('popupLastModified', true);
newOption('popupDiffContextCharacters', 40);
newOption('popupPreviewKillTemplates', true);
newOption('popupDiffDates', true);
newOption('popupPreviewRawTemplates', true);
newOption('popupDiffDatePrinter', 'toLocaleString');
newOption('popupPreviewFirstParOnly', true);
newOption('popupPreviewCutHeadings', true);
newOption('popupPreviewButton', false);
newOption('popupPreviewButtonEvent', 'click');


// edit summaries. God, these are ugly.
// diffs
newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary') );
newOption('popupPreviewDiffs', true);
newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary') );
newOption('popupDiffMaxLines', 100);
newOption('popupDiffContextLines', 2);
newOption('popupTimeOffset', null);
newOption('popupRevertSummary', popupString('defaultpopupRevertSummary') );
newOption('popupDiffContextCharacters', 40);
newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary') );
newOption('popupDiffDates', true);
newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary') );
newOption('popupDiffDatePrinter', 'toLocaleString'); // no longer in use
newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary') );
newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary') );
newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary') );
newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary') );
//</NOLITE>
// misc
newOption('popupCookies', false);
newOption('popupHistoryLimit', 50);
//<NOLITE>
newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect,
popupFilterPageSize, popupFilterCountLinks,
popupFilterCountImages, popupFilterCountCategories,
popupFilterLastModified]);
newOption('extraPopupFilters', []);
newOption('popupOnEditSelection', 'cursor');
newOption('popupPreviewHistory', true);
newOption('popupImageLinks', true);
newOption('popupCategoryMembers', true);
newOption('popupUserInfo', true);
newOption('popupHistoryPreviewLimit', 25);
newOption('popupContribsPreviewLimit',25);
newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion');
newOption('popupShowGender', true);
//</NOLITE>


// new windows
// edit summaries. God, these are ugly.
newOption('popupNewWindows', false);
newOption('popupReviewedSummary', popupString('defaultpopupReviewedSummary'));
newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true});
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);


// regexps
// new windows
newOption('popupDabRegexp', '(\\{\\{\\s*disambig(?!uation needed)|disambig(uation|)\\s*\\}\\}|disamb\\s*\\}\\}|dab\\s*\\}\\})|\\{\\{\\s*(((geo|hn|road?|school|number)dis)|[234][lc][acw]|(road|ship)index)(\\s*[|][^}]*)?\\s*[}][}]|is a .*disambiguation.*page');
newOption('popupNewWindows', false);
newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template
newOption('popupLinksNewWindow', { lastContrib: true, sinceMe: true });
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
//<NOLITE>
//////////////////////////////////////////////////
// Translatable strings
//////////////////////////////////////////////////
//
// See instructions at
// http://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation


pg.string = {
// regexps
/////////////////////////////////////
newOption(
// summary data, searching etc.
'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'
'article': 'article',
);
'category': 'category',
newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template
'categories': 'categories',
newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub');
'image': 'image',
newOption(
'images': 'images',
'popupImageVarsRegexp',
'stub': 'stub',
'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo'
'section stub': 'section stub',
);
'Empty page': 'Empty page',
}
'kB': 'kB',
// ENDFILE: options.js
'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',
'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',
'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',
' edits since: ': ' edits since: ',
'last edit on ': 'last edit on ',
/////////////////////////////////////
// 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'
};


// STARTFILE: strings.js
//////////////////////////////////////////////////
// Translatable strings
//////////////////////////////////////////////////
//
// See instructions at
// https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation


function popupString(str) {
pg.string = {
if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; }
/////////////////////////////////////
if (pg.string[str]) { return pg.string[str]; }
// summary data, searching etc.
return str;
/////////////////////////////////////
}
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',
'History preview failed': 'History preview failed :-(',
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) {
function tprintf(str,subs) {
if (typeof subs != typeof []) {
if (typeof subs != typeof []) { subs = [subs]; }
return simplePrintf(popupString(str), subs);
subs = [subs];
}
}
return simplePrintf(popupString(str), subs);
}


//</NOLITE>
// ENDFILE: strings.js
// ENDFILE: strings.js
// STARTFILE: run.js
////////////////////////////////////////////////////////////////////
// Run things
////////////////////////////////////////////////////////////////////


// STARTFILE: run.js
////////////////////////////////////////////////////////////////////
// Run things
////////////////////////////////////////////////////////////////////


// For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some.
// For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some.
// The old addOnloadHook did something similar to the below
// The old addOnloadHook did something similar to the below
if (document.readyState == 'complete') {
if (document.readyState=="complete")
autoEdit();
autoEdit(); //will setup popups
else
}
$( window ).on( 'load', 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;
}
}


// Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout.
function registerHooksForVisibleNavpops() {
( function () {
for (var i = 0; pg.current.links && i < pg.current.links.length; ++i) {
var once = true;
var navpop = pg.current.links[i].navpopup;
function dynamicContentHandler( $content ) {
if (!navpop || !navpop.isVisible()) {
// Try to detect the hook fired on initial page load and disregard
continue;
// 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' ) {
Navpopup.tracker.addHook(posCheckerHook(navpop));
if ( once ) {
}
once = false;
return;
}
}
}


function doIt() {
function doIt () {
$content.each( function () {
registerHooksForVisibleNavpops();
this.ranSetupTooltipsAlready = false;
$content.each(function () {
setupTooltips( this );
this.ranSetupTooltipsAlready = false;
} );
setupTooltips(this);
});
}
}


if ( !setupPopups.completed ) {
setupPopups(doIt);
setupPopups( doIt );
} else {
doIt();
}
}
}


// This hook is also fired after page load.
// This hook is also fired after page load.
mw.hook('wikipage.content').add(dynamicContentHandler);
mw.hook( 'wikipage.content' ).add( dynamicContentHandler );

mw.hook( 'ext.echo.overlay.beforeShowingOverlay' ).add( function($overlay){
dynamicContentHandler( $overlay.find(".mw-echo-state") );
});
} )();


mw.hook('ext.echo.overlay.beforeShowingOverlay').add(function ($overlay) {
dynamicContentHandler($overlay.find('.mw-echo-state'));
});
}());
});
});
// ENDFILE: run.js
// ENDFILE: run.js