Difference between revisions of "MediaWiki:Gadget-popups.js"

Jump to navigation Jump to search
 
Line 14: Line 14:
 
// **                                                                  **
 
// **                                                                  **
 
// **********************************************************************
 
// **********************************************************************
 +
/* eslint-env browser  */
 +
/* global $, jQuery, mw, window */
  
 +
// Fix later
 +
/* global log, errlog, popupStrings, wikEdUseWikEd, WikEdUpdateFrame */
 +
/* eslint no-mixed-spaces-and-tabs: 0, no-empty: 0 */
 +
 +
$(function () {
 
//////////////////////////////////////////////////
 
//////////////////////////////////////////////////
 
// Globals
 
// Globals
Line 20: Line 27:
  
 
// 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
function pg(){} // dummy to stop errors
+
var pg = {
window.pg = {
 
 
re: {},              // regexps
 
re: {},              // regexps
 
ns: {},              // namespaces
 
ns: {},              // namespaces
 
string: {},          // translatable strings
 
string: {},          // translatable strings
 
wiki: {},            // local site info
 
wiki: {},            // local site info
 +
user: {},            // current user info
 
misc: {},            // YUCK PHOOEY
 
misc: {},            // YUCK PHOOEY
 
option: {},          // options, see newOption etc
 
option: {},          // options, see newOption etc
Line 35: Line 42:
 
counter: {},          // .. and all sorts of counters
 
counter: {},          // .. and all sorts of counters
 
current: {},          // state info
 
current: {},          // state info
 +
fn: {},              // functions
 
endoflist: null
 
endoflist: null
 
};
 
};
window.pop = {               // wrap various functions in here
+
/* Bail if the gadget/script is being loaded twice */
init: {},
+
if( window.pg ) {
util: {},
+
return;
endoflist: null
 
};
 
function popupsReady() {
 
if (!window.pg) { return false; }
 
if (!pg.flag) { return false; }
 
if (!pg.flag.finishedLoading) { return false; }
 
return true;
 
 
}
 
}
 +
/* Export to global context */
 +
window.pg = pg;
  
 
/// Local Variables: ///
 
/// Local Variables: ///
Line 59: Line 62:
 
//<NOLITE>
 
//<NOLITE>
 
// the main initial call
 
// the main initial call
if (getValueOf('popupOnEditSelection') && window.doSelectionPopup && document && document.editform && document.editform.wpTextbox1) {
+
if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) {
 
document.editform.wpTextbox1.onmouseup=doSelectionPopup;
 
document.editform.wpTextbox1.onmouseup=doSelectionPopup;
 
}
 
}
Line 87: Line 90:
 
log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments));
 
log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments));
 
var finish=begin+howmany;
 
var finish=begin+howmany;
var loopend = min(finish, anchors.length);
+
var loopend = Math.min(finish, anchors.length);
 
var j=loopend - begin;
 
var j=loopend - begin;
 
log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin +
 
log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin +
Line 120: Line 123:
 
var tocLinks=toc.getElementsByTagName('A');
 
var tocLinks=toc.getElementsByTagName('A');
 
var tocLen = tocLinks.length;
 
var tocLen = tocLinks.length;
for (j=0; j<tocLen; ++j) {
+
for (var j=0; j<tocLen; ++j) {
 
removeTooltip(tocLinks[j], true);
 
removeTooltip(tocLinks[j], true);
 
}
 
}
Line 162: Line 165:
 
np.mainDiv.style.maxWidth = popupMaxWidth + 'px';
 
np.mainDiv.style.maxWidth = popupMaxWidth + 'px';
 
np.maxWidth = popupMaxWidth;
 
np.maxWidth = popupMaxWidth;
 
try {
 
// hack for IE
 
// see http://www.svendtofte.com/code/max_width_in_ie/
 
// use setExpression as documented here on msdn: http://tinyurl dot com/dqljn
 
 
if (np.mainDiv.style.setExpression) {
 
np.mainDiv.style.setExpression(
 
'width', 'document.body.clientWidth > ' +
 
popupMaxWidth + ' ? "' +popupMaxWidth + 'px": "auto"');
 
}
 
}
 
catch (errors) {
 
errlog( "Running on IE8 are we not?: " + errors );
 
}
 
 
};
 
};
 
np.addHook(setMaxWidth, 'unhide', 'before');
 
np.addHook(setMaxWidth, 'unhide', 'before');
 
}
 
}
 
//<NOLITE>
 
//<NOLITE>
if (window.addPopupShortcuts && window.rmPopupShortcuts) {
+
np.addHook(addPopupShortcuts, 'unhide', 'after');
np.addHook(addPopupShortcuts, 'unhide', 'after');
+
np.addHook(rmPopupShortcuts, 'hide', 'before');
np.addHook(rmPopupShortcuts, 'hide', 'before');
 
}
 
 
//</NOLITE>
 
//</NOLITE>
 
}
 
}
  
 +
function removeModifierKeyHandler(a) {
 +
//remove listeners for modifier key if any that were added in mouseOverWikiLink
 +
document.removeEventListener('keydown', a.modifierKeyHandler, false);
 +
document.removeEventListener('keyup', a.modifierKeyHandler, false);
 +
}
  
 
function mouseOverWikiLink(evt) {
 
function mouseOverWikiLink(evt) {
if (!window.popupsReady || !window.popupsReady()) { return; }
 
 
if (!evt && window.event) {evt=window.event;}
 
if (!evt && window.event) {evt=window.event;}
 +
 +
// if the modifier is needed, listen for it,
 +
// we will remove the listener when we mouseout of this link or kill popup.
 +
if (getValueOf('popupModifier')) {
 +
// if popupModifierAction = enable, we should popup when the modifier is pressed
 +
// if popupModifierAction = disable, we should popup unless the modifier is pressed
 +
    var action = getValueOf('popupModifierAction');
 +
    var key = action=='disable' ? 'keyup' : 'keydown';
 +
    var a = this;
 +
    a.modifierKeyHandler = function(evt) {
 +
mouseOverWikiLink2(a, evt);
 +
};
 +
    document.addEventListener(key, a.modifierKeyHandler, false);
 +
}
 +
 
return mouseOverWikiLink2(this, evt);
 
return mouseOverWikiLink2(this, evt);
 
}
 
}
  
 +
/**
 +
* Gets the references list item that the provided footnote link targets. This
 +
* is typically a li element within the ol.references element inside the reflist.
 +
* @param {Element} a - A footnote link.
 +
* @returns {Element|boolean} The targeted element, or false if one can't be found.
 +
*/
 
function footnoteTarget(a) {
 
function footnoteTarget(a) {
 
var aTitle=Title.fromAnchor(a);
 
var aTitle=Title.fromAnchor(a);
Line 216: Line 227:
  
 
function footnotePreview(x, navpop) {
 
function footnotePreview(x, navpop) {
setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber, getValueOf('popupSubpopups')
+
setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber);
? function() { setupTooltips(document.getElementById('popupPreview' + navpop.idNumber)); }
 
: null);
 
}
 
 
 
// var modid=0;
 
// if(!window.opera) { window.opera={postError: console.log}; }
 
 
 
function modifierKeyHandler(a) {
 
return function(evt) {
 
// opera.postError('modifierKeyHandler called' + (++modid));
 
// opera.postError(''+evt + modid);
 
// 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;}
 
// opera.postError('And now....'+modid);
 
// opera.postError(''+evt+modid);
 
// opera.postError(''+evt.ctrlKey+modid);
 
 
 
var modPressed = modifierPressed(evt);
 
var action = getValueOf('popupModifierAction');
 
 
 
// FIXME: probable bug - modifierPressed should be modPressed below?
 
if ( action === 'disable' && modifierPressed ) { return true; }
 
if ( action === 'enable' && !modifierPressed ) { return true; }
 
 
 
mouseOverWikiLink2(a, evt);
 
};
 
 
}
 
}
  
Line 256: Line 235:
  
 
if (!evt && window.event) {evt=window.event;}
 
if (!evt && window.event) {evt=window.event;}
// opera.postError('And now....'+modid);
 
// opera.postError(''+evt+modid);
 
// opera.postError(''+evt.ctrlKey+modid);
 
  
 
return ( evt && mod && evt[mod.toLowerCase() + 'Key'] );
 
return ( evt && mod && evt[mod.toLowerCase() + 'Key'] );
 
 
}
 
}
  
function dealWithModifier(a,evt) {
+
// Checks if the correct modifier pressed/unpressed if needed
if (!getValueOf('popupModifier')) { return false; }
+
function isCorrectModifier(a,evt) {
 +
if (!getValueOf('popupModifier')) { return true; }
 +
// if popupModifierAction = enable, we should popup when the modifier is pressed
 +
// if popupModifierAction = disable, we should popup unless the modifier is pressed
 
var action = getValueOf('popupModifierAction');
 
var action = getValueOf('popupModifierAction');
if ( action ==  'enable' && !modifierPressed(evt) ||
+
return ( action ==  'enable' && modifierPressed(evt) ||
    action == 'disable' && modifierPressed(evt) ) {
+
        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);
 
 
 
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 false;
 
 
}
 
}
  
 
function mouseOverWikiLink2(a, evt) {
 
function mouseOverWikiLink2(a, evt) {
if (dealWithModifier(a,evt)) { return; }
+
if (!isCorrectModifier(a,evt)) { return; }
 
if ( getValueOf('removeTitles') ) { removeTitle(a); }
 
if ( getValueOf('removeTitles') ) { removeTitle(a); }
 
if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; }
 
if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; }
 
pg.current.link=a;
 
pg.current.link=a;
  
if (getValueOf('simplePopups') && pg.option.popupStructure===null) {
+
if (getValueOf('simplePopups') && !pg.option.popupStructure) {
 
// reset *default value* of popupStructure
 
// reset *default value* of popupStructure
 
setDefault('popupStructure', 'original');
 
setDefault('popupStructure', 'original');
Line 319: Line 265:
  
 
if (!a.navpopup) {
 
if (!a.navpopup) {
// FIXME: this doesn't behave well if you mouse out of a popup
+
a.navpopup=newNavpopup(a, article);
// directly into a link with the same href
+
pg.current.linksHash[a.href] = a.navpopup;
if (pg.current.linksHash[a.href] && false) {
+
pg.current.links.push(a);
a.navpopup = pg.current.linksHash[a.href];
 
}
 
else {
 
a.navpopup=newNavpopup(a, article);
 
pg.current.linksHash[a.href] = a.navpopup;
 
pg.current.links.push(a);
 
}
 
 
}
 
}
if (a.navpopup.pending===null || a.navpopup.pending!==0) {
+
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);
 
clearInterval(pg.timer.checkPopupPosition);
Line 350: Line 287:
 
s['on' + getValueOf('popupPreviewButtonEvent')] = function() {
 
s['on' + getValueOf('popupPreviewButtonEvent')] = function() {
 
a.simpleNoMore=true;
 
a.simpleNoMore=true;
 +
d.style.display = "none";
 
nonsimplePopupContent(a,article);
 
nonsimplePopupContent(a,article);
 
};
 
};
Line 355: Line 293:
 
setPopupHTML(d, 'popupPreview', a.navpopup.idNumber);
 
setPopupHTML(d, 'popupPreview', a.navpopup.idNumber);
 
}
 
}
return;
 
 
}
 
}
  
if (a.navpopup.pending!==0 ) {
+
if (a.navpopup.pending !== 0 ) {
 
nonsimplePopupContent(a, article);
 
nonsimplePopupContent(a, article);
 
}
 
}
 
}
 
}
  
// simplePopupContent: the content that is shown even when simplePopups is true
+
// simplePopupContent: the content that do not require additional download
 +
// (it is shown even when simplePopups is true)
 
function simplePopupContent(a, article) {
 
function simplePopupContent(a, article) {
 
/* FIXME hack */ a.navpopup.hasPopupMenu=false;
 
/* FIXME hack */ a.navpopup.hasPopupMenu=false;
Line 405: Line 343:
 
}
 
}
  
 +
// Should we show nonsimple context?
 +
// If simplePopups is set to true, then we do not show nonsimple context,
 +
// but if a bottom "show preview" was clicked we do show nonsimple context
 +
function shouldShowNonSimple(a) {
 +
  return !getValueOf('simplePopups') || a.simpleNoMore;
 +
}
 +
 +
// Should we show nonsimple context govern by the option (e.g. popupUserInfo)?
 +
// If the user explicitly asked for nonsimple context by setting the option to true,
 +
// then we show it even in nonsimple mode.
 +
function shouldShow(a,option) {
 +
if (shouldShowNonSimple(a)) {
 +
return getValueOf(option);
 +
} else {
 +
return (typeof window[option] != 'undefined' )  && window[option];
 +
}
 +
}
  
 
function nonsimplePopupContent(a, article) {
 
function nonsimplePopupContent(a, article) {
Line 411: Line 366:
 
var oldid=(typeof params.oldid=='undefined' ? null : params.oldid);
 
var oldid=(typeof params.oldid=='undefined' ? null : params.oldid);
 
//<NOLITE>
 
//<NOLITE>
if(getValueOf('popupPreviewDiffs') && window.loadDiff) {
+
if(shouldShow(a,'popupPreviewDiffs')) {
 
diff=params.diff;
 
diff=params.diff;
 
}
 
}
if(getValueOf('popupPreviewHistory')) {
+
if(shouldShow(a,'popupPreviewHistory')) {
 
history=(params.action=='history');
 
history=(params.action=='history');
 
}
 
}
 
//</NOLITE>
 
//</NOLITE>
 
a.navpopup.pending=0;
 
a.navpopup.pending=0;
var x;
+
var referenceElement = footnoteTarget(a);
+
if (referenceElement) {
if (x=footnoteTarget(a)) {
+
footnotePreview(referenceElement, a.navpopup);
footnotePreview(x, a.navpopup);
 
 
//<NOLITE>
 
//<NOLITE>
 
} else if ( diff || diff === 0 ) {
 
} else if ( diff || diff === 0 ) {
Line 428: Line 382:
 
} else if ( history ) {
 
} else if ( history ) {
 
loadAPIPreview('history', article, a.navpopup);
 
loadAPIPreview('history', article, a.navpopup);
} else if ( pg.re.contribs.test(a.href) ) {
+
} else if ( shouldShowNonSimple(a) && pg.re.contribs.test(a.href) ) {
 
loadAPIPreview('contribs', article, a.navpopup);
 
loadAPIPreview('contribs', article, a.navpopup);
} else if ( pg.re.backlinks.test(a.href) ) {
+
} else if ( shouldShowNonSimple(a) && pg.re.backlinks.test(a.href) ) {
 
loadAPIPreview('backlinks', article, a.navpopup);
 
loadAPIPreview('backlinks', article, a.navpopup);
 
} else if ( // FIXME should be able to get all preview combinations with options
 
} else if ( // FIXME should be able to get all preview combinations with options
 
article.namespaceId()==pg.nsImageId &&
 
article.namespaceId()==pg.nsImageId &&
( getValueOf('imagePopupsForImages') || ! anchorContainsImage(a) )
+
( shouldShow(a,'imagePopupsForImages') || ! anchorContainsImage(a) )
 
) {
 
) {
 
loadAPIPreview('imagepagepreview', article, a.navpopup);
 
loadAPIPreview('imagepagepreview', article, a.navpopup);
Line 441: Line 395:
 
} else {
 
} else {
 
if (article.namespaceId() == pg.nsCategoryId &&
 
if (article.namespaceId() == pg.nsCategoryId &&
getValueOf('popupCategoryMembers')) {
+
shouldShow(a,'popupCategoryMembers')) {
 
loadAPIPreview('category', article, a.navpopup);
 
loadAPIPreview('category', article, a.navpopup);
 
} else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) &&
 
} else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) &&
getValueOf('popupUserInfo')) {
+
shouldShow(a,'popupUserInfo')) {
 
loadAPIPreview('userinfo', article, a.navpopup);
 
loadAPIPreview('userinfo', article, a.navpopup);
 
}
 
}
startArticlePreview(article, oldid, a.navpopup);
+
if (shouldShowNonSimple(a)) startArticlePreview(article, oldid, a.navpopup);
 
}
 
}
 
}
 
}
  
 
function pendingNavpopTask(navpop) {
 
function pendingNavpopTask(navpop) {
if (navpop && navpop.pending===null) { navpop.pending=0; }
+
if (navpop && navpop.pending === null) { navpop.pending=0; }
 
++navpop.pending;
 
++navpop.pending;
 
debugData(navpop);
 
debugData(navpop);
Line 468: Line 422:
  
 
function loadPreview(article, oldid, navpop) {
 
function loadPreview(article, oldid, navpop) {
pendingNavpopTask(navpop);
 
 
if (!navpop.redir) { navpop.originalArticle=article; }
 
if (!navpop.redir) { navpop.originalArticle=article; }
if (!navpop.visible && getValueOf('popupLazyDownloads')) {
+
article.oldid = oldid;
var id=(navpop.redir) ? 'DOWNLOAD_PREVIEW_REDIR_HOOK' : 'DOWNLOAD_PREVIEW_HOOK';
+
loadAPIPreview('revision', article, navpop);
navpop.addHook(function() {
 
getWiki(article, insertPreview, oldid, navpop);
 
return true; }, 'unhide', 'before', id);
 
} else {
 
getWiki(article, insertPreview, oldid, navpop);
 
}
 
 
}
 
}
  
Line 486: Line 433:
 
// mediawiki does overwrite; eg [[User:Lupin/foo3#Done]]
 
// mediawiki does overwrite; eg [[User:Lupin/foo3#Done]]
 
if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; }
 
if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; }
var trailingRubbish=redirMatch[4];
 
 
navpop.redir++;
 
navpop.redir++;
 
navpop.redirTarget=target;
 
navpop.redirTarget=target;
 
//<NOLITE>
 
//<NOLITE>
if (window.redirLink) {
+
var warnRedir = redirLink(target, navpop.article);
var warnRedir = redirLink(target, navpop.article);
+
setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber);
setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber);
 
}
 
 
//</NOLITE>
 
//</NOLITE>
 
navpop.article=target;
 
navpop.article=target;
Line 504: Line 448:
  
 
var redirMatch = pg.re.redirect.exec(download.data);
 
var redirMatch = pg.re.redirect.exec(download.data);
if (download.owner.redir===0 && redirMatch) {
+
if (download.owner.redir === 0 && redirMatch) {
completedNavpopTask(download.owner);
 
 
loadPreviewFromRedir(redirMatch, download.owner);
 
loadPreviewFromRedir(redirMatch, download.owner);
 
return;
 
return;
Line 523: Line 466:
 
var wikiText=download.data;
 
var wikiText=download.data;
 
var navpop=download.owner;
 
var navpop=download.owner;
completedNavpopTask(navpop);
 
 
var art=navpop.redirTarget || navpop.originalArticle;
 
var art=navpop.redirTarget || navpop.originalArticle;
  
 
//<NOLITE>
 
//<NOLITE>
makeFixDabs(wikiText, navpop);
+
makeFixDabs(wikiText, navpop, download.pageprops);
if (getValueOf('popupSummaryData') && window.getPageInfo) {
+
if (getValueOf('popupSummaryData')) {
var info=getPageInfo(wikiText, download);
+
getPageInfo(wikiText, download);
 
setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber);
 
setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber);
 
}
 
}
Line 569: Line 511:
 
function anchorize(d, anch) {
 
function anchorize(d, anch) {
 
if (!anch) { return d; }
 
if (!anch) { return d; }
var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?\}\})');
+
var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?}})');
 
var match=d.match(anchRe);
 
var match=d.match(anchRe);
 
if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); }
 
if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); }
Line 586: Line 528:
  
 
function killPopup() {
 
function killPopup() {
if (getValueOf('popupShortcutKeys') && window.rmPopupShortcuts) { rmPopupShortcuts(); }
+
removeModifierKeyHandler(this);
 +
if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); }
 
if (!pg) { return; }
 
if (!pg) { return; }
pg.current.link && pg.current.link.navpopup && pg.current.link.navpopup.banish();
+
if (pg.current.link && pg.current.link.navpopup) { pg.current.link.navpopup.banish(); }
 
pg.current.link=null;
 
pg.current.link=null;
 
abortAllDownloads();
 
abortAllDownloads();
if (pg.timer.checkPopupPosition !== null) {
+
if (pg.timer.checkPopupPosition) {
 
clearInterval(pg.timer.checkPopupPosition);
 
clearInterval(pg.timer.checkPopupPosition);
 
pg.timer.checkPopupPosition=null;
 
pg.timer.checkPopupPosition=null;
Line 663: Line 606:
 
o.vmode   = true;
 
o.vmode   = true;
  
o.root = oRoot && oRoot !== null ? oRoot : o ;
+
o.root = oRoot ? oRoot : o ;
  
 
if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left  = "0px"; }
 
if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left  = "0px"; }
Line 742: Line 685:
 
pg.structures.original.popupLayout=function () {
 
pg.structures.original.popupLayout=function () {
 
return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle',
 
return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle',
'popupData', 'popupOtherLinks',
+
'popupUserData', 'popupData', 'popupOtherLinks',
 
'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks',
 
'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks',
 
  'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
 
  'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
Line 795: Line 738:
 
// 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='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' +
+ editstr + '}';
+
editstr + '}';
 
var historystr='<<history|shortcut=h>>';
 
var historystr='<<history|shortcut=h>>';
 
var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
 
var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
  
str+='<br>if(talk){' +
+
str += '<br>if(talk){' +
 
editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' +
 
editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' +
 
'<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
 
'<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
 
'}else{' + // not a talk page
 
'}else{' + // not a talk page
 
editOldidStr + '*' + historystr + '*' + watchstr + '*' +
 
editOldidStr + '*' + historystr + '*' + watchstr + '*' +
'<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>'
+
'<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';
+ '}';
 
  
 
// misc links
 
// misc links
Line 825: Line 767:
 
};
 
};
 
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>>';
 
var move='<<move|shortcut=m|move>>';
Line 857: Line 799:
 
};
 
};
 
pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title
 
pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title
return ['popupError', 'popupImage', 'popupTitle', 'popupData', 'popupTopLinks', 'popupOtherLinks',
+
return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks',
 
'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
 
'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
 
'popupMiscTools', ['popupRedlink'],
 
'popupMiscTools', ['popupRedlink'],
Line 868: Line 810:
 
return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks',
 
return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks',
 
'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
 
'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
'popupData', 'popupMiscTools', ['popupRedlink'],
+
'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'],
 
'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
 
'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
 
};
 
};
function toggleSticky(uid) {
+
 
var popDiv=document.getElementById('navpopup_maindiv'+uid);
 
if (!popDiv) { return; }
 
if (!popDiv.navpopup.sticky) { popDiv.navpopup.stick(); }
 
else {
 
popDiv.navpopup.unstick();
 
popDiv.navpopup.hide();
 
}
 
}
 
 
pg.structures.menus.popupTopLinks = function (x, shorter) {
 
pg.structures.menus.popupTopLinks = function (x, shorter) {
 
// FIXME maybe this stuff should be cached
 
// FIXME maybe this stuff should be cached
Line 885: Line 819:
 
var dropdiv='<div class="popup_drop">';
 
var dropdiv='<div class="popup_drop">';
 
var enddiv='</div>';
 
var enddiv='</div>';
var endspan='</span>';
 
 
var hist='<<history|shortcut=h>>';
 
var hist='<<history|shortcut=h>>';
 
if (!shorter) { hist = '<menurow>' + hist +
 
if (!shorter) { hist = '<menurow>' + hist +
'|<<historyfeed|rss>>if(mainspace_en){|<<editors|shortcut=E>>}</menurow>'; }
+
'|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>'; }
 
var lastedit='<<lastEdit|shortcut=/|show last edit>>';
 
var lastedit='<<lastEdit|shortcut=/|show last edit>>';
 +
var thank='if(diff){<<thank|send thanks>>}';
 
var jsHistory='<<lastContrib|last set of edits>><<sinceMe|changes since mine>>';
 
var jsHistory='<<lastContrib|last set of edits>><<sinceMe|changes since mine>>';
 
var linkshere='<<whatLinksHere|shortcut=l|what links here>>';
 
var linkshere='<<whatLinksHere|shortcut=l|what links here>>';
Line 916: Line 850:
 
}
 
}
 
s.push( '<menu>');
 
s.push( '<menu>');
s.push( editRow + markPatrolled + newTopic + hist + lastedit );
+
s.push( editRow + markPatrolled + newTopic + hist + lastedit + thank );
 
if (!shorter) { s.push(jsHistory); }
 
if (!shorter) { s.push(jsHistory); }
 
s.push( move + linkshere + related);
 
s.push( move + linkshere + related);
Line 934: Line 868:
  
 
s.push('if(user){*' + dropdiv + menuTitle('user'));
 
s.push('if(user){*' + dropdiv + menuTitle('user'));
s.push('<menu>'); +
+
s.push('<menu>');
 
s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>');
 
s.push('<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>');
 
s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' +
 
s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' +
Line 943: Line 877:
 
s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}');
 
s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}');
 
s.push('if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}');
 
s.push('if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}');
s.push('<<blocklog|shortcut=B|block log>>' + getValueOf('popupExtraUserMenu'));
+
s.push('<<blocklog|shortcut=B|block log>>');
 
s.push('</menu>'  + enddiv + '}');
 
s.push('</menu>'  + enddiv + '}');
  
Line 970: Line 904:
 
};
 
};
 
pg.structures.shortmenus.popupRedirTopLinks=pg.structures.shortmenus.popupTopLinks;
 
pg.structures.shortmenus.popupRedirTopLinks=pg.structures.shortmenus.popupTopLinks;
 
copyStructure('shortmenus', 'dabshortmenus');
 
pg.structures.dabshortmenus.popupLayout=function () {
 
return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks',
 
'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
 
'popupData', 'popupMiscTools', ['popupRedlink'], 'popupFixDab',
 
'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview'];
 
};
 
 
copyStructure('menus', 'dabmenus');
 
pg.structures.dabmenus.popupLayout=pg.structures.dabshortmenus.popupLayout;
 
 
  
 
//</NOLITE>
 
//</NOLITE>
Line 1,063: Line 985:
 
}
 
}
  
 +
/*eslint-disable*/
 
function skipToEnd(str,sep) {
 
function skipToEnd(str,sep) {
 
return {segment: str, remainder: ''};
 
return {segment: str, remainder: ''};
 
}
 
}
 +
/*eslint-enable */
  
 
function findNext(str, ch) {
 
function findNext(str, ch) {
Line 1,077: Line 1,001:
 
function setCheckbox(param, box) {
 
function setCheckbox(param, box) {
 
var val=mw.util.getParamValue(param);
 
var val=mw.util.getParamValue(param);
if (val!==null) {
+
if (val) {
 
switch (val) {
 
switch (val) {
 
case '1': case 'yes': case 'true':
 
case '1': case 'yes': case 'true':
Line 1,089: Line 1,013:
  
 
function autoEdit() {
 
function autoEdit() {
if (!setupPopups.completed) { setupPopups(); }
+
setupPopups( function () {
if (!mw.config.get('wgEnableAPI') || mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; }
+
if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; }
if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) {
+
if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) {
modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action'));
+
pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action'));
}
+
}
if (!document.editform) { return false; }
+
if (!document.editform) { return false; }
if (window.autoEdit.alreadyRan) { return false; }
+
if (autoEdit.alreadyRan) { return false; }
window.autoEdit.alreadyRan=true;
+
autoEdit.alreadyRan=true;
var cmdString=mw.util.getParamValue('autoedit');
+
var cmdString=mw.util.getParamValue('autoedit');
if (cmdString) {
+
if (cmdString) {
try {
+
try {
var editbox=document.editform.wpTextbox1;
+
var editbox=document.editform.wpTextbox1;
} catch (dang) { return; }
+
var cmdList=parseCmd(cmdString);
var cmdList=parseCmd(cmdString);
+
var input=editbox.value;
var input=editbox.value;
+
var output=execCmds(input, cmdList);
var output=execCmds(input, cmdList);
+
editbox.value=output;
editbox.value=output;
+
} catch (dang) { return; }
// wikEd user script compatibility
+
// wikEd user script compatibility
if (typeof(wikEdUseWikEd) != 'undefined') {
+
if (typeof(wikEdUseWikEd) != 'undefined') {
if (wikEdUseWikEd === true) {
+
if (wikEdUseWikEd === true) {
WikEdUpdateFrame();
+
WikEdUpdateFrame();
 +
}
 
}
 
}
 
}
 
}
}
+
setCheckbox('autominor', document.editform.wpMinoredit);
setCheckbox('autominor', document.editform.wpMinoredit);
+
setCheckbox('autowatch', document.editform.wpWatchthis);
setCheckbox('autowatch', document.editform.wpWatchthis);
+
 
+
var rvid = mw.util.getParamValue('autorv');
var rvid = mw.util.getParamValue('autorv');
+
if (rvid) {
if (rvid) {
+
var url=pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids='+rvid;
var url=pg.wiki.apiwikibase + '?action=query&format=json&prop=revisions&revids='+rvid;
+
startDownload(url, null, autoEdit2);
startDownload(url, null, autoEdit2);
+
} else { autoEdit2(); }
} else { autoEdit2(); }
+
} );
 
}
 
}
  
Line 1,129: Line 1,054:
 
if (d && d.data && mw.util.getParamValue('autorv')) {
 
if (d && d.data && mw.util.getParamValue('autorv')) {
 
var s = getRvSummary(summary, d.data);
 
var s = getRvSummary(summary, d.data);
if (s===false) {
+
if (s === false) {
 
summaryprompt=true;
 
summaryprompt=true;
 
summarynotice=popupString('Failed to get revision information, please edit manually.\n\n');
 
summarynotice=popupString('Failed to get revision information, please edit manually.\n\n');
Line 1,184: Line 1,109:
 
var o=getJsObj(json);
 
var o=getJsObj(json);
 
var edit = anyChild(o.query.pages).revisions[0];
 
var edit = anyChild(o.query.pages).revisions[0];
} catch (badness) {return false;}
+
var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, '');
var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, '');
+
return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user ]);
return simplePrintf(template, [edit.revid, timestamp, edit.user]);
+
} catch (badness) {
 +
return false;
 +
}
 
}
 
}
  
Line 1,204: Line 1,131:
 
*/
 
*/
 
function Downloader(url) {
 
function Downloader(url) {
// Source: http://jibbering.com/2002/4/httprequest.html
+
if (typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); }
/** xmlhttprequest object which we're wrapping */
 
this.http = false;
 
 
 
/*@cc_on @*/
 
/*@if (@_jscript_version >= 5)
 
// JScript gives us Conditional compilation,
 
// we can cope with old IE versions.
 
// and security blocked creation of the objects.
 
try {
 
this.http = new ActiveXObject("Msxml2.XMLHTTP");
 
} catch (e) {
 
try {
 
this.http = new ActiveXObject("Microsoft.XMLHTTP");
 
} catch (E) {
 
// this.http = false;
 
}
 
}
 
@end @*/
 
 
 
if (! this.http && typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); }
 
 
/**
 
/**
 
The url to download
 
The url to download
Line 1,259: Line 1,166:
 
this.aborted = false;
 
this.aborted = false;
 
/**
 
/**
  HTTP method. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities.
+
  HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities.
 
  @type String
 
  @type String
 
*/
 
*/
Line 1,289: Line 1,196:
 
if (!this.http) { return null; }
 
if (!this.http) { return null; }
 
this.http.open(this.method, this.url, this.async);
 
this.http.open(this.method, this.url, this.async);
 +
this.http.setRequestHeader( 'Api-User-Agent', pg.misc.userAgent );
 
};
 
};
 
/** Gets the state of the download. */
 
/** Gets the state of the download. */
Line 1,408: Line 1,316:
 
pg.misc.downloadsInProgress[x].abort();
 
pg.misc.downloadsInProgress[x].abort();
 
delete pg.misc.downloadsInProgress[x];
 
delete pg.misc.downloadsInProgress[x];
} catch (e) { }
+
} catch (e) {}
 
}
 
}
 
}
 
}
Line 1,414: Line 1,322:
 
// STARTFILE: livepreview.js
 
// STARTFILE: livepreview.js
 
// TODO: location is often not correct (eg relative links in previews)
 
// TODO: location is often not correct (eg relative links in previews)
 
+
// NOTE: removed md5 and image and math parsing. was broken, lots of bytes.
 
/**
 
/**
 
  * InstaView - a Mediawiki to HTML converter in JavaScript
 
  * InstaView - a Mediawiki to HTML converter in JavaScript
 
  * Version 0.6.1
 
  * Version 0.6.1
 
  * Copyright (C) Pedro Fayolle 2005-2006
 
  * Copyright (C) Pedro Fayolle 2005-2006
  * http://en.wikipedia.org/wiki/User:Pilaf
+
  * https://en.wikipedia.org/wiki/User:Pilaf
 
  * Distributed under the BSD license
 
  * Distributed under the BSD license
 
  *
 
  *
Line 1,469: Line 1,377:
 
// Only used for Insta previews with images. (not in popups)
 
// Only used for Insta previews with images. (not in popups)
 
math: '/math/',
 
math: '/math/',
images: '//upload.wikimedia.org/wikipedia/en/', // FIXME ( window.getImageUrlStart ? getImageUrlStart(pg.wiki.hostname) : ''),
+
images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname),
 
images_fallback: '//upload.wikimedia.org/wikipedia/commons/',
 
images_fallback: '//upload.wikimedia.org/wikipedia/commons/',
 
},
 
},
Line 1,483: Line 1,391:
  
 
// options with default values or backreferences
 
// options with default values or backreferences
with (Insta.conf) {
+
Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian';
user.name = user.name || 'Wikipedian';
+
Insta.conf.user.signature = '[['+Insta.conf.locale.user+':'+Insta.conf.user.name+'|'+Insta.conf.user.name+']]';
user.signature = '[['+locale.user+':'+user.name+'|'+user.name+']]';
+
//Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/';
//paths.images = '//upload.wikimedia.org/wikipedia/' + wiki.lang + '/';
 
}
 
  
 
// define constants
 
// define constants
Line 1,546: Line 1,452:
 
return htmlescape_text(s).replace(/'/g,"&#39;").replace(/"/g,"&quot;");
 
return htmlescape_text(s).replace(/'/g,"&#39;").replace(/"/g,"&quot;");
 
}
 
}
 
function max(a,b) { return (a>b)?a:b; }
 
function min(a,b) { return (a<b)?a:b; }
 
  
 
// return the first non matching character position between two strings
 
// return the first non matching character position between two strings
 
function str_imatch(a, b)
 
function str_imatch(a, b)
 
{
 
{
for (var i=0, l=min(a.length, b.length); i<l; i++) {
+
for (var i=0, l=Math.min(a.length, b.length); i<l; i++) {
 
if (a.charAt(i)!=b.charAt(i)) { break; }
 
if (a.charAt(i)!=b.charAt(i)) { break; }
 
}
 
}
Line 1,582: Line 1,485:
  
 
// close uncontinued lists
 
// close uncontinued lists
for (var i=prev.length-1; i >= ipos; i--) {
+
for (var prevPos=prev.length-1; prevPos >= ipos; prevPos--) {
  
var pi = prev.charAt(i);
+
var pi = prev.charAt(prevPos);
  
 
if (pi=='*') { ps('</ul>'); }
 
if (pi=='*') { ps('</ul>'); }
 
else if (pi=='#') { ps('</ol>'); }
 
else if (pi=='#') { ps('</ol>'); }
 
// close a dl only if the new item is not a dl item (:, ; or empty)
 
// close a dl only if the new item is not a dl item (:, ; or empty)
else switch (l_match[1].charAt(i)) { case'':case'*':case'#': ps('</dl>') }
+
else if($.inArray(l_match[1].charAt(prevPos), ['','*','#'])) { ps('</dl>'); }
 
}
 
}
  
 
// open new lists
 
// open new lists
for (var i=ipos; i<l_match[1].length; i++) {
+
for (var matchPos=ipos; matchPos<l_match[1].length; matchPos++) {
  
var li = l_match[1].charAt(i);
+
var li = l_match[1].charAt(matchPos);
  
 
if (li=='*') { ps('<ul>'); }
 
if (li=='*') { ps('<ul>'); }
 
else if (li=='#') { ps('<ol>'); }
 
else if (li=='#') { ps('<ol>'); }
 
// open a new dl only if the prev item is not a dl item (:, ; or empty)
 
// open a new dl only if the prev item is not a dl item (:, ; or empty)
else { switch(prev.charAt(i)) { case'':case'*':case'#': ps('<dl>') } }
+
else if ($.inArray(prev.charAt(matchPos), ['','*','#'])) { ps('<dl>'); }
 
}
 
}
  
Line 1,606: Line 1,509:
  
 
case '*': case '#':
 
case '*': case '#':
ps('<li>' + parse_inline_nowiki(l_match[2])); break
+
ps('<li>' + parse_inline_nowiki(l_match[2]));
 +
break;
  
 
case ';':
 
case ';':
 
ps('<dt>');
 
ps('<dt>');
  
var dt_match;
+
var dt_match = l_match[2].match(/(.*?)(:.*?)$/);
  
 
// handle ;dt :dd format
 
// handle ;dt :dd format
if (dt_match = l_match[2].match(/(.*?)(:.*?)$/)) {
+
if (dt_match) {
 
+
ps(parse_inline_nowiki(dt_match[1]));
ps(parse_inline_nowiki(dt_match[1]))
+
ll.unshift(dt_match[2]);
ll.unshift(dt_match[2])
 
 
 
} else ps(parse_inline_nowiki(l_match[2]))
 
  
 +
} else ps(parse_inline_nowiki(l_match[2]));
 
break;
 
break;
  
 
case ':':
 
case ':':
ps('<dd>' + parse_inline_nowiki(l_match[2]))
+
ps('<dd>' + parse_inline_nowiki(l_match[2]));
 
}
 
}
  
prev=l_match[1]
+
prev=l_match[1];
 
}
 
}
  
 
// close remaining lists
 
// close remaining lists
for (var i=prev.length-1; i>=0; i--)
+
for (var i=prev.length-1; i>=0; i--) {
ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl')))
+
ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl')));
 +
}
 
}
 
}
  
 
function parse_table()
 
function parse_table()
 
{
 
{
endl(f('<table>', $(/^\{\|( .*)$/)? $r[1]: ''))
+
endl(f('<table>', $(/^\{\|( .*)$/)? $r[1]: ''));
  
 
for (;remain();) if ($('|')) switch (_(1)) {
 
for (;remain();) if ($('|')) switch (_(1)) {
case '}': endl('</table>'); return
+
case '}':
case '-': endl(f('<tr>', $(/\|-*(.*)/)[1])); break
+
endl('</table>');
default: parse_table_data()
+
return;
 +
case '-':
 +
endl(f('<tr>', $(/\|-*(.*)/)[1]));
 +
break;
 +
default:
 +
parse_table_data();
 
}
 
}
else if ($('!')) parse_table_data()
+
else if ($('!')) { parse_table_data(); }
else sh()
+
else { sh(); }
 
}
 
}
  
 
function parse_table_data()
 
function parse_table_data()
 
{
 
{
var td_line, match_i
+
var td_line, match_i;
  
 
// 1: "|+", '|' or '+'
 
// 1: "|+", '|' or '+'
Line 1,656: Line 1,564:
 
// 3: attributes ??
 
// 3: attributes ??
 
// TODO: finish commenting this regexp
 
// TODO: finish commenting this regexp
var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/)
+
var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/);
  
 
if (td_match[1] == '|+') ps('<caption');
 
if (td_match[1] == '|+') ps('<caption');
else ps('<t' + ((td_match[1]=='|')?'d':'h'))
+
else ps('<t' + ((td_match[1]=='|')?'d':'h'));
  
 
if (typeof td_match[3] != 'undefined') {
 
if (typeof td_match[3] != 'undefined') {
  
 
//ps(' ' + td_match[3])
 
//ps(' ' + td_match[3])
match_i = 4
+
match_i = 4;
  
} else match_i = 2
+
} else match_i = 2;
  
ps('>')
+
ps('>');
  
 
if (td_match[1] != '|+') {
 
if (td_match[1] != '|+') {
Line 1,674: Line 1,582:
 
// use || or !! as a cell separator depending on context
 
// use || or !! as a cell separator depending on context
 
// NOTE: when split() is passed a regexp make sure to use non-capturing brackets
 
// NOTE: when split() is passed a regexp make sure to use non-capturing brackets
td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/)
+
td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/);
  
ps(parse_inline_nowiki(td_line.shift()))
+
ps(parse_inline_nowiki(td_line.shift()));
  
while (td_line.length) ll.unshift(td_match[1] + td_line.pop())
+
while (td_line.length) ll.unshift(td_match[1] + td_line.pop());
  
} else ps(td_match[match_i])
+
} else ps(td_match[match_i]);
  
var tc = 0, td = []
+
var tc = 0, td = [];
  
for (;remain(); td.push(sh()))
+
while (remain()) {
if ($('|')) {
+
td.push(sh());
if (!tc) break // we're at the outer-most level (no nested tables), skip to td parse
+
if ($('|')) {
else if (_(1)=='}') tc--
+
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++;
 
}
 
}
else if (!tc && $('!')) break
 
else if ($('{|')) tc++
 
  
if (td.length) ps(Insta.convert(td))
+
if (td.length) ps(Insta.convert(td));
 
}
 
}
  
 
function parse_pre()
 
function parse_pre()
 
{
 
{
ps('<pre>')
+
ps('<pre>');
do endl(parse_inline_nowiki(ll[0].substring(1)) + "\n"); while (remain() && $(' '))
+
do {
ps('</pre>')
+
endl(parse_inline_nowiki(ll[0].substring(1)) + "\n");
 +
} while (remain() && $(' '));
 +
ps('</pre>');
 
}
 
}
  
 
function parse_block_image()
 
function parse_block_image()
 
{
 
{
ps(parse_image(sh()))
+
ps(parse_image(sh()));
 
}
 
}
  
Line 1,712: Line 1,624:
 
// get what's in between "[[Image:" and "]]"
 
// get what's in between "[[Image:" and "]]"
 
var tag = str.substring(str.indexOf(':') + 1, str.length - 2);
 
var tag = str.substring(str.indexOf(':') + 1, str.length - 2);
 
+
/* eslint-disable no-unused-vars */
 
var width;
 
var width;
 
var attr = [], filename, caption = '';
 
var attr = [], filename, caption = '';
 
var thumb=0, frame=0, center=0;
 
var thumb=0, frame=0, center=0;
 
var align='';
 
var align='';
 +
/* eslint-enable no-unused-vars */
  
 
if (tag.match(/\|/)) {
 
if (tag.match(/\|/)) {
Line 1,744: Line 1,657:
 
var w_match;
 
var w_match;
  
for (;attr.length; attr.shift())
+
for (;attr.length; attr.shift()) {
if (w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/)) width = w_match[1]
+
w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/);
else switch(attr[0]) {
+
if (w_match) width = w_match[1];
case 'thumb':
+
else switch(attr[0]) {
case 'thumbnail':
+
case 'thumb':
thumb=true;
+
case 'thumbnail':
case 'frame':
+
thumb=true;
frame=true;
+
frame=true;
break;
+
break;
case 'none':
+
case 'frame':
case 'right':
+
frame=true;
case 'left':
+
break;
center=false;
+
case 'none':
align=attr[0];
+
case 'right':
break;
+
case 'left':
case 'center':
+
center=false;
center=true;
+
align=attr[0];
align='none';
+
break;
break;
+
case 'center':
default:
+
center=true;
if (attr.length == 1) caption = attr[0];
+
align='none';
 +
break;
 +
default:
 +
if (attr.length == 1) caption = attr[0];
 
}
 
}
 +
}
  
 
} else filename = tag;
 
} else filename = tag;
  
 
+
return '';
var o='';
 
 
 
if (frame) {
 
 
 
if (align=='') align = 'right';
 
 
 
o += f("<div class='thumb t?'>", align);
 
 
 
if (thumb) {
 
if (!width) width = Insta.conf.wiki.default_thumb_width;
 
 
 
o += f("<div style='width:?px;'>?", 2+width*1, make_image(filename, caption, width)) +
 
f("<div class='thumbcaption'><div class='magnify' style='float:right'><a href='?' title='Enlarge'></a></div>?</div>",
 
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>';
 
 
 
} else if (align != '') {
 
o += f("<div class='float?'><span>?</span></div>", align, make_image(filename, caption, width));
 
} else {
 
return make_image(filename, caption, width);
 
}
 
 
 
return center? f("<div class='center'>?</div>", o): o;
 
 
//</NOLITE>
 
//</NOLITE>
 
}
 
}
Line 1,804: Line 1,692:
 
function parse_inline_nowiki(str)
 
function parse_inline_nowiki(str)
 
{
 
{
var start, lastend=0
+
var start, lastend=0;
 
var substart=0, nestlev=0, open, close, subloop;
 
var substart=0, nestlev=0, open, close, subloop;
 
var html='';
 
var html='';
Line 1,832: Line 1,720:
 
nestlev++;
 
nestlev++;
 
}
 
}
} while (subloop)
+
} while (subloop);
 
}
 
}
  
 
return html + parse_inline_wiki(str.substr(lastend));
 
return html + parse_inline_wiki(str.substr(lastend));
}
 
 
function make_image(filename, caption, width)
 
{
 
//<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);
 
 
var md5 = hex_md5(filename);
 
 
var source = md5.charAt(0) + '/' + md5.substr(0,2) + '/' + filename;
 
 
if (width) width = "width='" + width + "px'";
 
 
var img = "<img onerror=\""+pg.escapeQuotesHTML("this.onerror=null;this.src='"+pg.jsescape(Insta.conf.paths.images_fallback + source)+"'")+"\" src=\""+pg.escapeQuotesHTML(Insta.conf.paths.images + source)+"\" "+(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>
 
 
}
 
}
  
Line 1,890: Line 1,756:
 
nestlev++;
 
nestlev++;
 
}
 
}
} while (loop)
+
} while (loop);
  
 
} else break;
 
} else break;
Line 1,921: Line 1,787:
 
function parse_inline_wiki(str)
 
function parse_inline_wiki(str)
 
{
 
{
var aux_match;
 
 
 
str = parse_inline_images(str);
 
str = parse_inline_images(str);
 
str = parse_inline_formatting(str);
 
str = parse_inline_formatting(str);
  
 
// math
 
// math
while (aux_match = str.match(/<(?:)math>(.*?)<\/math>/i)) {
+
str = str.replace(/<(?:)math>(.*?)<\/math>/ig, '');
var math_md5 = hex_md5(aux_match[1]);
 
str = str.replace(aux_match[0], f("<img src='?.png'>", Insta.conf.paths.math+math_md5));
 
}
 
  
 
// 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) minutes = '0' + minutes;
 
if (minutes < 10) minutes = '0' + minutes;
var date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear());
+
date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear());
  
 
// text formatting
 
// text formatting
Line 1,946: Line 1,807:
  
 
// [[:Category:...]], [[:Image:...]], etc...
 
// [[: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));}).
+
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
 
// remove straight category and interwiki tags
 
replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),'').
 
replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),'').
Line 1,960: Line 1,821:
  
 
// [[Common links]]
 
// [[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)); }).
+
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]]
 
// [[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)); }).
+
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]]
 
// [[Stripped:Namespace|Namespace]]
Line 1,971: Line 1,832:
 
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(/\[(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(/\[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,$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)); }).
+
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(/(^| )(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('__NOTOC__','').
 
replace('__NOEDITSECTION__','');
 
replace('__NOEDITSECTION__','');
}
 
/*
 
*/
 
function strip_inline_wiki(str)
 
{
 
return str
 
.replace(/\[\[[^\]]*\|(.*?)\]\]/g,'$1')
 
.replace(/\[\[(.*?)\]\]/g,'$1')
 
.replace(/''(.*?)''/g,'$1');
 
 
}
 
}
  
 
// begin parsing
 
// begin parsing
 
for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) {
 
for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) {
p=0
+
p=0;
endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3]))
+
endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3]));
  
 
} else if ($(/^[*#:;]/)) {
 
} else if ($(/^[*#:;]/)) {
p=0
+
p=0;
parse_list()
+
parse_list();
  
 
} else if ($(' ')) {
 
} else if ($(' ')) {
p=0
+
p=0;
parse_pre()
+
parse_pre();
  
 
} else if ($('{|')) {
 
} else if ($('{|')) {
p=0
+
p=0;
parse_table()
+
parse_table();
  
 
} else if ($(/^----+$/)) {
 
} else if ($(/^----+$/)) {
p=0
+
p=0;
endl('<hr />')
+
endl('<hr />');
  
 
} else if ($(Insta.BLOCK_IMAGE)) {
 
} else if ($(Insta.BLOCK_IMAGE)) {
p=0
+
p=0;
parse_block_image()
+
parse_block_image();
  
 
} else {
 
} else {
Line 2,016: Line 1,868:
 
// handle paragraphs
 
// handle paragraphs
 
if ($$('')) {
 
if ($$('')) {
if (p = (remain()>1 && ll[1]==(''))) endl('<p><br>')
+
p = (remain()>1 && ll[1]===(''));
 +
if (p) endl('<p><br>');
 
} else {
 
} else {
 
if(!p) {
 
if(!p) {
ps('<p>')
+
ps('<p>');
p=1
+
p=1;
 
}
 
}
ps(parse_inline_nowiki(ll[0]) + ' ')
+
ps(parse_inline_nowiki(ll[0]) + ' ');
 
}
 
}
  
Line 2,028: Line 1,881:
 
}
 
}
  
return o
+
return o;
 
};
 
};
  
window.wiki2html=function(txt,baseurl) {
+
function wiki2html(txt,baseurl) {
 
Insta.conf.baseUrl=baseurl;
 
Insta.conf.baseUrl=baseurl;
 
return Insta.convert(txt);
 
return Insta.convert(txt);
};
+
}
 
// ENDFILE: livepreview.js
 
// ENDFILE: livepreview.js
 
// STARTFILE: pageinfo.js
 
// STARTFILE: pageinfo.js
Line 2,144: Line 1,997:
  
 
function popupFilterDisambigDetect(data, download, article) {
 
function popupFilterDisambigDetect(data, download, article) {
if (getValueOf('popupOnlyArticleDabStub') && article.namespace()) { return ''; }
+
if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return ''; }
return (isDisambig(data, article)) ? popupString('disambig') : '';
+
return (isDisambig(data, article, download.pageprops)) ? popupString('disambig') : '';
 
}
 
}
  
Line 2,299: Line 2,152:
 
var splitted=h.split('?');
 
var splitted=h.split('?');
 
splitted[0]=splitted[0].split('&').join('%26');
 
splitted[0]=splitted[0].split('&').join('%26');
 
if (pg.flag.linksLikeIE6) {
 
splitted[0]=encodeURI(decode_utf8(splitted[0]));
 
}
 
  
 
h=splitted.join('?');
 
h=splitted.join('?');
  
 
var contribs=pg.re.contribs.exec(h);
 
var contribs=pg.re.contribs.exec(h);
if (contribs !== null) {
+
if (contribs) {
 
if (contribs[1]=='title=') { contribs[3]=contribs[3].split('+').join(' '); }
 
if (contribs[1]=='title=') { contribs[3]=contribs[3].split('+').join(' '); }
 
var u=new Title(contribs[3]);
 
var u=new Title(contribs[3]);
Line 2,315: Line 2,164:
  
 
var email=pg.re.email.exec(h);
 
var email=pg.re.email.exec(h);
if (email !== null) {
+
if (email) {
 
this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace()));
 
this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace()));
 
return this;
 
return this;
Line 2,336: Line 2,185:
 
// hopefully it's not a disguised user-related or specially treated special page
 
// hopefully it's not a disguised user-related or specially treated special page
 
var m=pg.re.main.exec(h);
 
var m=pg.re.main.exec(h);
if(m===null) { this.value=null; }
+
if(m === null) { this.value=null; }
 
else {
 
else {
 
var fromBotInterface = /[?](.+[&])?title=/.test(h);
 
var fromBotInterface = /[?](.+[&])?title=/.test(h);
Line 2,379: Line 2,228:
 
Title.prototype.fromWikiText=function(txt) {
 
Title.prototype.fromWikiText=function(txt) {
 
// FIXME - testing needed
 
// FIXME - testing needed
if (!pg.flag.linksLikeIE6) { txt=myDecodeURI(txt); }
+
txt=myDecodeURI(txt);
 
this.setUtf(txt);
 
this.setUtf(txt);
 
return this;
 
return this;
Line 2,406: Line 2,255:
 
// and return the corresponding talk page otherwise
 
// and return the corresponding talk page otherwise
 
//
 
//
// Per http://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces
+
// Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces
 
// * All discussion namespaces have odd-integer indices
 
// * All discussion namespaces have odd-integer indices
 
// * The discussion namespace index for a specific namespace with index n is n + 1
 
// * The discussion namespace index for a specific namespace with index n is n + 1
if (this.value===null) { return null; }
+
if (this.value === null) { return null; }
 
 
 
var namespaceId = this.namespaceId();
 
var namespaceId = this.namespaceId();
if (namespaceId>=0 && namespaceId % 2 == 0) //non-special and subject namespace
+
if (namespaceId>=0 && namespaceId % 2 === 0) //non-special and subject namespace
 
{
 
{
 
var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId+1];
 
var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId+1];
 
if (typeof localizedNamespace!=='undefined')
 
if (typeof localizedNamespace!=='undefined')
 
{
 
{
if (localizedNamespace==='') return this.value = this.stripNamespace();
+
if (localizedNamespace === '') {
this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
+
this.value = this.stripNamespace();
 +
} else {
 +
this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
 +
}
 
return this.value;
 
return this.value;
 
}
 
}
Line 2,451: Line 2,303:
 
Title.prototype.toArticleFromTalkPage=function() {
 
Title.prototype.toArticleFromTalkPage=function() {
 
//largely copy/paste from toTalkPage above.
 
//largely copy/paste from toTalkPage above.
if (this.value===null) { return null; }
+
if (this.value === null) { return null; }
 
 
 
var namespaceId = this.namespaceId();
 
var namespaceId = this.namespaceId();
if (namespaceId>=0 && namespaceId % 2 == 1) //non-special and talk namespace
+
if (namespaceId >= 0 && namespaceId % 2 == 1) //non-special and talk namespace
 
{
 
{
 
var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId-1];
 
var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId-1];
 
if (typeof localizedNamespace!=='undefined')
 
if (typeof localizedNamespace!=='undefined')
 
{
 
{
if (localizedNamespace==='') return this.value = this.stripNamespace();
+
if (localizedNamespace === '') {
this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
+
this.value = this.stripNamespace();
 +
} else {
 +
this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
 +
}
 
return this.value;
 
return this.value;
 
}
 
}
Line 2,487: Line 2,342:
 
if (n<0) { return this.value; }
 
if (n<0) { return this.value; }
 
var namespaceId = this.namespaceId();
 
var namespaceId = this.namespaceId();
if (namespaceId===pg.nsMainspaceId) return this.value;
+
if (namespaceId === pg.nsMainspaceId) return this.value;
 
return this.value.substring(n+1);
 
return this.value.substring(n+1);
 
};
 
};
Line 2,501: Line 2,356:
 
var anch=urlfrag.indexOf('#');
 
var anch=urlfrag.indexOf('#');
 
this.value=safeDecodeURI(urlfrag.substring(0,anch));
 
this.value=safeDecodeURI(urlfrag.substring(0,anch));
this.anchor=value.substring(anch+1);
+
this.anchor=this.value.substring(anch+1);
 
};
 
};
 
Title.prototype.append=function(x){
 
Title.prototype.append=function(x){
Line 2,507: Line 2,362:
 
};
 
};
 
Title.prototype.urlString=function(x) {
 
Title.prototype.urlString=function(x) {
x || ( x={} );
+
if(!x) { x={}; }
 
var v=this.toString(true);
 
var v=this.toString(true);
 
if (!x.omitAnchor && this.anchor) { v+= '#' + this.urlAnchor(); }
 
if (!x.omitAnchor && this.anchor) { v+= '#' + this.urlAnchor(); }
Line 2,519: Line 2,374:
 
return pg.wiki.titlebase + this.urlString();
 
return pg.wiki.titlebase + this.urlString();
 
};
 
};
 
 
function paramValue(param, url) {
 
var s=url.parenSplit(RegExp('[?&]' + literalizeRegex(param) + '=([^?&]*)'));
 
if (!url) { return null; }
 
return s[1] || null;
 
}
 
  
 
function parseParams(url) {
 
function parseParams(url) {
 
var specialDiff = pg.re.specialdiff.exec(url);
 
var specialDiff = pg.re.specialdiff.exec(url);
if (specialDiff!==null)
+
if (specialDiff)
 
{
 
{
 
var split= specialDiff[1].split('/');
 
var split= specialDiff[1].split('/');
Line 2,558: Line 2,406:
 
ret.oldid = helper;
 
ret.oldid = helper;
 
}
 
}
return ret;
 
}
 
 
// all sorts of stuff here
 
// FIXME almost everything needs to be rewritten
 
 
function oldidFromAnchor(a) { return paramValue('oldid', a.href); }
 
//function diffFromAnchor(a) { return paramValue('diff', a.href); }
 
 
 
function wikiMarkupToAddressFragment (str) { // for images
 
var ret = safeDecodeURI(str);
 
ret = ret.split(' ').join('_');
 
ret = encodeURI(ret);
 
 
return ret;
 
return ret;
 
}
 
}
Line 2,599: Line 2,433:
  
 
//<NOLITE>
 
//<NOLITE>
function isIpUser(user) {return pg.re.ipUser.test(user);}
+
function isDisambig(data, article, props) {
 
 
function isDisambig(data, article) {
 
 
if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; }
 
if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; }
return ! article.isTalkPage() && pg.re.disambig.test(data);
+
return ! article.isTalkPage() && props.hasOwnProperty('disambiguation');
 
}
 
}
  
Line 2,625: Line 2,457:
  
 
function isInStrippableNamespace(article) {
 
function isInStrippableNamespace(article) {
//I believe that this method means to return whether the given article is in a namspace without subpages. Meaning, it's broken.
+
// Does the namespace allow subpages
return ( article.namespace() !== '' );
+
// Note, would be better if we had access to wgNamespacesWithSubpages
 +
return ( article.namespaceId() !== 0 );
 
}
 
}
  
function isInMainNamespace(article) { return !isInStrippableNamespace(article); }
+
function isInMainNamespace(article) { return article.namespaceId() === 0; }
  
 
function anchorContainsImage(a) {
 
function anchorContainsImage(a) {
 
// iterate over children of anchor a
 
// iterate over children of anchor a
 
// see if any are images
 
// see if any are images
if (a===null) { return false; }
+
if (a === null) { return false; }
kids=a.childNodes;
+
var kids=a.childNodes;
 
for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } }
 
for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } }
 
return false;
 
return false;
Line 2,650: Line 2,483:
 
if (a.onmousedown || a.getAttribute('nopopup')) { return false; }
 
if (a.onmousedown || a.getAttribute('nopopup')) { return false; }
 
var h=a.href;
 
var h=a.href;
if (h===document.location.href+'#') { return false; }
+
if (h === document.location.href+'#') { return false; }
 
if (!pg.re.basenames.test(h)) { return false; }
 
if (!pg.re.basenames.test(h)) { return false; }
 
if (!pg.re.urlNoPopup.test(h)) { return true; }
 
if (!pg.re.urlNoPopup.test(h)) { return true; }
Line 2,677: Line 2,510:
 
}
 
}
 
// ENDFILE: titles.js
 
// ENDFILE: titles.js
// STARTFILE: cookies.js
+
// STARTFILE: getpage.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();
 
}
 
else { expires = ""; }
 
document.cookie = name+"="+value+expires+"; path=/";
 
},
 
 
 
read: function(name)
 
{
 
var nameEQ = name + "=";
 
var ca = document.cookie.split(';');
 
for(var i=0;i < ca.length;i++)
 
{
 
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 null;
 
},
 
 
 
erase: function(name)
 
{
 
Cookie.create(name,"",-1);
 
}
 
};
 
//</NOLITE>
 
// ENDFILE: cookies.js
 
// STARTFILE: getpage.js
 
 
//////////////////////////////////////////////////
 
//////////////////////////////////////////////////
 
// Wiki-specific downloading
 
// Wiki-specific downloading
Line 2,724: Line 2,517:
 
// Schematic for a getWiki call
 
// Schematic for a getWiki call
 
//
 
//
//   getWiki->-getPageWithCaching
+
//             getPageWithCaching
 
// |
 
// |
 
//   false |   true
 
//   false |   true
Line 2,730: Line 2,523:
 
//  \.
 
//  \.
 
// (async)->addPageToCache(download)->-onComplete(download)
 
// (async)->addPageToCache(download)->-onComplete(download)
 
 
/** @todo {document}
 
@param {Title} article
 
@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&ctype=text/css&maxage=0&smaxage=0';
 
 
getPageWithCaching(url, onComplete, owner);
 
}
 
  
 
// check cache to see if page exists
 
// check cache to see if page exists
Line 2,755: Line 2,529:
 
log('getPageWithCaching, url='+url);
 
log('getPageWithCaching, url='+url);
 
var i=findInPageCache(url);
 
var i=findInPageCache(url);
 +
var d;
 
if (i > -1) {
 
if (i > -1) {
var d=fakeDownload(url, owner.idNumber, onComplete,
+
d=fakeDownload(url, owner.idNumber, onComplete,
  pg.cache.pages[i].data, pg.cache.pages[i].lastModified,
+
pg.cache.pages[i].data, pg.cache.pages[i].lastModified,
  owner);
+
owner);
 
} else {
 
} else {
var d=getPage(url, onComplete, owner);
+
d=getPage(url, onComplete, owner);
 
if (d && owner && owner.addDownload) {
 
if (d && owner && owner.addDownload) {
 
owner.addDownload(d);
 
owner.addDownload(d);
Line 2,787: Line 2,562:
 
}
 
}
 
// ENDFILE: getpage.js
 
// ENDFILE: getpage.js
// STARTFILE: md5-2.2alpha.js
+
// STARTFILE: parensplit.js
//<NOLITE>
+
//////////////////////////////////////////////////
/*
+
// parenSplit
* 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.
 
*/
 
  
/*
+
// String.prototype.parenSplit should do what ECMAscript says String.prototype.split does,
* Configurable variables. You may need to tweak these to be compatible with
+
// interspersing paren matches (regex capturing groups) between the split elements.
* the server-side, but the defaults work in most cases.
+
// i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c']
*/
 
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase */
 
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance  */
 
  
/*
+
if (String('abc'.split(/(b)/))!='a,b,c') {
* These are the functions you'll usually want to call
+
// broken String.split, e.g. konq, IE < 10
* They take string arguments and return either hex or base-64 encoded strings
+
String.prototype.parenSplit=function (re) {
*/
+
re=nonGlobalRegex(re);
function hex_md5(s) { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
+
var s=this;
function b64_md5(s) { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
+
var m=re.exec(s);
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
+
var ret=[];
function hex_hmac_md5(k, d)
+
while (m && s) {
  { return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
+
// without the following loop, we have
function b64_hmac_md5(k, d)
+
// 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/)
  { return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
+
for(var i=0; i<m.length; ++i) {
function any_hmac_md5(k, d, e)
+
if (typeof m[i]=='undefined') m[i]='';
  { return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
+
}
 
+
ret.push(s.substring(0,m.index));
/*
+
ret = ret.concat(m.slice(1));
* Perform a simple self-test to see if the VM is working
+
s=s.substring(m.index + m[0].length);
*/
+
m=re.exec(s);
function md5_vm_test()
+
}
{
+
ret.push(s);
  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
+
return ret;
}
+
};
 
+
} else {
/*
+
String.prototype.parenSplit=function (re) { return this.split(re); };
* Calculate the MD5 of a raw string
+
String.prototype.parenSplit.isNative=true;
*/
 
function rstr_md5(s)
 
{
 
  return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
 
 
}
 
}
  
/*
+
function nonGlobalRegex(re) {
* Calculate the HMAC-MD5, of a key and some data (raw strings)
+
var s=re.toString();
*/
+
var flags='';
function rstr_hmac_md5(key, data)
+
for (var j=s.length; s.charAt(j) != '/'; --j) {
{
+
if (s.charAt(j) != 'g') { flags += s.charAt(j); }
  var bkey = rstr2binl(key);
 
  if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
 
 
 
  var ipad = Array(16), opad = Array(16);
 
  for(var i = 0; i < 16; i++)
 
  {
 
ipad[i] = bkey[i] ^ 0x36363636;
 
opad[i] = bkey[i] ^ 0x5C5C5C5C;
 
  }
 
 
 
  var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
 
  return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
 
}
 
 
 
/*
 
* Convert a raw string to a hex string
 
*/
 
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;
 
}
 
 
 
/*
 
* Convert a raw string to a base-64 string
 
*/
 
function rstr2b64(input)
 
{
 
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
  var output = "";
 
  var len = input.length;
 
  for(var i = 0; i < len; i += 3)
 
  {
 
var triplet = (input.charCodeAt(i) << 16)
 
| (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
 
| (i + 2 < len ? input.charCodeAt(i+2) : 0);
 
for(var j = 0; j < 4; j++)
 
{
 
  if(i * 8 + j * 6 > input.length * 8) output += b64pad;
 
  else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
 
 
}
 
}
  }
+
var t=s.substring(1,j);
  return output;
+
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
* Convert a raw string to an arbitrary string encoding
 
*/
 
function rstr2any(input, encoding)
 
{
 
  var divisor = encoding.length;
 
  var remainders = Array();
 
  var i, q, x, quotient;
 
 
 
  /* Convert to an array of 16-bit big-endian values, forming the dividend */
 
  var dividend = Array(input.length / 2);
 
  for(i = 0; i < dividend.length; i++)
 
  {
 
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
 
  }
 
  
  /*
+
//<NOLITE>
  * 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 */
 
  var output = "";
 
  for(i = remainders.length - 1; i >= 0; i--)
 
output += encoding.charAt(remainders[i]);
 
  
  return output;
+
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) {
* Encode a string as utf-8.
+
for (var p in obj) {
* For efficiency, this assumes the input is valid utf-16.
+
return obj[p];
*/
 
function str2rstr_utf8(input)
 
{
 
  var output = "";
 
  var i = -1;
 
  var x, y;
 
 
 
  while(++i < input.length)
 
  {
 
/* Decode utf-16 surrogate pairs */
 
x = input.charCodeAt(i);
 
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
 
if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
 
{
 
  x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
 
  i++;
 
 
}
 
}
 
+
return null;
/* Encode output as utf-8 */
 
if(x <= 0x7F)
 
  output += String.fromCharCode(x);
 
else if(x <= 0x7FF)
 
  output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
 
0x80 | ( x   & 0x3F));
 
else if(x <= 0xFFFF)
 
  output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
 
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;
 
 
}
 
}
  
/*
+
//</NOLITE>
* Encode a string as utf-16
+
 
*/
+
function upcaseFirst(str) {
function str2rstr_utf16le(input)
+
if (typeof str != typeof '' || str === '') return '';
{
+
return str.charAt(0).toUpperCase() + str.substring(1);
  var output = "";
 
  for(var i = 0; i < input.length; i++)
 
output += String.fromCharCode( input.charCodeAt(i)   & 0xFF,
 
  (input.charCodeAt(i) >>> 8) & 0xFF);
 
  return output;
 
 
}
 
}
  
function str2rstr_utf16be(input)
+
 
{
+
function findInArray(arr, foo) {
  var output = "";
+
if (!arr || !arr.length) { return -1; }
  for(var i = 0; i < input.length; i++)
+
var len=arr.length;
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
+
for (var i=0; i<len; ++i) { if (arr[i]==foo) { return i; } }
  input.charCodeAt(i)   & 0xFF);
+
return -1;
  return output;
 
 
}
 
}
  
/*
+
/* eslint-disable no-unused-vars */
* Convert a raw string to an array of little-endian words
+
function nextOne (array, value) {
* Characters >255 have their high-byte silently ignored.
+
// NB if the array has two consecutive entries equal
*/
+
// then this will loop on successive calls
function rstr2binl(input)
+
var i=findInArray(array, value);
{
+
if (i<0) { return null; }
  var output = Array(input.length >> 2);
+
return array[i+1];
  for(var i = 0; i < output.length; i++)
 
output[i] = 0;
 
  for(var i = 0; i < input.length * 8; i += 8)
 
output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
 
  return output;
 
 
}
 
}
 +
/* eslint-enable no-unused-vars */
  
/*
+
function literalizeRegex(str){
* Convert an array of little-endian words to a string
+
return mw.RegExp.escape(str);
*/
 
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;
 
 
}
 
}
  
/*
+
String.prototype.entify=function() {
* Calculate the MD5 of an array of little-endian words, and a bit length.
+
//var shy='&shy;';
*/
+
return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;'/*+shy*/).split('"').join('&quot;');
function binl_md5(x, len)
+
};
{
 
  /* append padding */
 
  x[len >> 5] |= 0x80 << ((len) % 32);
 
  x[(((len + 64) >>> 9) << 4) + 14] = len;
 
  
  var a =  1732584193;
+
// Array filter function
  var b = -271733879;
+
function removeNulls(val) { return val !== null; }
  var c = -1732584194;
 
  var d = 271733878;
 
  
  for(var i = 0; i < x.length; i += 16)
+
function joinPath(list) {
  {
+
return list.filter(removeNulls).join('/');
var olda = a;
+
}
var oldb = b;
 
var oldc = c;
 
var oldd = d;
 
  
a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
 
d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
 
c = md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
 
b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
 
a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
 
d = md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
 
c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
 
b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
 
a = md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
 
d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
 
c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
 
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);
+
function simplePrintf(str, subs) {
d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+
if (!str || !subs) { return str; }
c = md5_gg(c, d, a, b, x[i+11], 14, 643717713);
+
var ret=[];
b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+
var s=str.parenSplit(/(%s|\$[0-9]+)/);
a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+
var i=0;
d = md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
+
do {
c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+
ret.push(s.shift());
b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+
if ( !s.length ) { break; }
a = md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
+
var cmd=s.shift();
d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+
if (cmd == '%s') {
c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+
if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); }
b = md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
+
++i;
a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+
} else {
d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+
var j=parseInt( cmd.replace('$', ''), 10 ) - 1;
c = md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
+
if ( j > -1 && j < subs.length ) { ret.push(subs[j]); } else { ret.push(cmd); }
b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
}
 
+
} while (s.length > 0);
a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+
return ret.join('');
d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
 
c = md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
 
b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
 
a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
 
d = md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
 
c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
 
b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
 
a = md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
 
d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
 
c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
 
b = md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
 
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);
 
d = md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
 
c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
 
b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
 
a = md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
 
d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
 
c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
 
b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
 
a = md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
 
d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
 
c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
 
b = md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
 
a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
 
d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
 
c = md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
 
b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
 
 
 
a = safe_add(a, olda);
 
b = safe_add(b, oldb);
 
c = safe_add(c, oldc);
 
d = safe_add(d, oldd);
 
  }
 
  return Array(a, b, c, d);
 
}
 
 
 
/*
 
* These functions implement the four basic operations the algorithm uses.
 
*/
 
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)
+
/* eslint-disable no-unused-vars */
{
+
function isString(x) { return (typeof x === 'string' || x instanceof String); }
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+
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 md5_gg(a, b, c, d, x, s, t)
+
/* eslint-enable no-unused-vars */
{
+
 
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+
function repeatString(s,mult) {
}
+
var ret='';
function md5_hh(a, b, c, d, x, s, t)
+
for (var i=0; i<mult; ++i) { ret += s; }
{
+
return ret;
  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);
 
 
}
 
}
  
/*
+
function zeroFill(s, min) {
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
+
min = min || 2;
* to work around bugs in some JS interpreters.
+
var t=s.toString();
*/
+
return repeatString('0', min - t.length) + t;
function safe_add(x, y)
 
{
 
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
 
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
 
  return (msw << 16) | (lsw & 0xFFFF);
 
 
}
 
}
  
/*
+
function map(f, o) {
* Bitwise rotate a 32-bit number to the left.
+
if (isArray(o)) { return map_array(f,o); }
*/
+
return map_object(f,o);
function bit_rol(num, cnt)
+
}
{
+
function map_array(f,o) {
  return (num << cnt) | (num >>> (32 - cnt));
+
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;
 
}
 
}
//</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') {
+
pg.escapeQuotesHTML = function ( text ) {
// broken String.split, e.g. konq, IE
+
return text
String.prototype.parenSplit=function (re) {
+
.replace(/&/g, "&amp;")
re=nonGlobalRegex(re);
+
.replace(/"/g, "&quot;")
var s=this;
+
.replace(/</g, "&lt;")
var m=re.exec(s);
+
.replace(/>/g, "&gt;");
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) {
+
// ENDFILE: tools.js
var s=re.toString();
+
// STARTFILE: dab.js
flags='';
+
//<NOLITE>
for (var j=s.length; s.charAt(j) != '/'; --j) {
+
//////////////////////////////////////////////////
if (s.charAt(j) != 'g') { flags += s.charAt(j); }
+
// Dab-fixing code
}
 
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
 
  
//<NOLITE>
+
function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) {
function encode_utf8(rohtext) {
+
log('retargetDab: newTarget='+newTarget + ' oldTarget=' + oldTarget);
// dient der Normalisierung des Zeilenumbruchs
+
return changeLinkTargetLink(
rohtext = rohtext.replace(/\r\n/g,"\n");
+
{newTarget: newTarget,
var utftext = "";
+
text: newTarget.split(' ').join('&nbsp;'),
for(var n=0; n<rohtext.length; n++)
+
hint: tprintf('disambigHint', [newTarget]),
{
+
summary: simplePrintf(
// ermitteln des Unicodes des  aktuellen Zeichens
+
getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget ]),
var c=rohtext.charCodeAt(n);
+
clickButton: getValueOf('popupDabsAutoClick'), minor: true, oldTarget: oldTarget,
// alle Zeichen von 0-127 => 1byte
+
watch: getValueOf('popupWatchDisambiggedPages'),
if (c<128)
+
title: titleToEdit});
utftext += String.fromCharCode(c);
 
// alle Zeichen von 127 bis 2047 => 2byte
 
else if((c>127) && (c<2048)) {
 
utftext += String.fromCharCode((c>>6)|192);
 
utftext += String.fromCharCode((c&63)|128);}
 
// alle Zeichen von 2048 bis 66536 => 3byte
 
else {
 
utftext += String.fromCharCode((c>>12)|224);
 
utftext += String.fromCharCode(((c>>6)&63)|128);
 
utftext += String.fromCharCode((c&63)|128);}
 
}
 
return utftext;
 
 
}
 
}
  
function getJsObj(json) {
+
function listLinks(wikitext, oldTarget, titleToEdit) {
try {
+
// mediawiki strips trailing spaces, so we do the same
var json_ret = JSON.parse(json);
+
// testcase: https://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633
} catch (someError) {
+
var reg=RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi');
errlog('Something went wrong with getJsobj, json='+json);
+
var ret=[];
return 1;
+
var splitted=wikitext.parenSplit(reg);
}
+
// ^[a-z]+ should match interwiki links, hopefully (case-insensitive)
if( json_ret['warnings'] ) {
+
// and ^[a-z]* should match those and [[:Category...]] style links too
for( var w=0; w < json_ret['warnings'].length; w++ ) {
+
var omitRegex=RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory');
log( json_ret['warnings'][w]['*'] );
+
var friendlyCurrentArticleName= oldTarget.toString();
}
+
var wikPos = getValueOf('popupDabWiktionary');
} else if ( json_ret['error'] ) {
+
 
errlog( json_ret['error'].code + ': ' + json_ret['error'].info );
+
for (var i=1; i<splitted.length; i=i+3) {
}
+
if (typeof splitted[i] == typeof 'string' && splitted[i].length>0 && !omitRegex.test(splitted[i])) {
return json_ret;
+
ret.push( retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit) );
}
+
} /* if */
 +
} /* for loop */
 +
 
 +
ret = rmDupesFromSortedList(ret.sort());
 +
 
 +
if (wikPos) {
 +
var wikTarget='wiktionary:' +
 +
friendlyCurrentArticleName.replace( RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1' );
 +
 
 +
var meth;
 +
if (wikPos.toLowerCase() == 'first') { meth = 'unshift'; }
 +
else { meth = 'push'; }
  
function anyChild(obj) {
+
ret[meth]( retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) );
for (var p in obj) {
 
return obj[p];
 
 
}
 
}
return null;
+
 
 +
ret.push(changeLinkTargetLink(
 +
{ newTarget: null,
 +
text: popupString('remove this link').split(' ').join('&nbsp;'),
 +
hint: popupString("remove all links to this disambig page from this article"),
 +
clickButton: getValueOf('popupDabsAutoClick'), oldTarget: oldTarget,
 +
summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]),
 +
watch: getValueOf('popupWatchDisambiggedPages'),
 +
title: titleToEdit
 +
}));
 +
return ret;
 
}
 
}
  
//</NOLITE>
+
function rmDupesFromSortedList(list) {
 
+
var ret=[];
function decode_utf8(utftext) {
+
for (var i=0; i<list.length; ++i) {
var plaintext = ""; var i=0, c=0, c1=0, c2=0;
+
if (ret.length === 0 || list[i]!=ret[ret.length-1]) { ret.push(list[i]); }
// while-Schleife, weil einige Zeichen uebersprungen werden
 
while(i<utftext.length)
 
{
 
c = utftext.charCodeAt(i);
 
if (c<128) {
 
plaintext += String.fromCharCode(c);
 
i++;}
 
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;
+
return ret;
 
}
 
}
  
 
+
function makeFixDab(data, navpop) {
function upcaseFirst(str) {
+
// grab title from parent popup if there is one; default exists in changeLinkTargetLink
if (typeof str != typeof '' || str=='') return '';
+
var titleToEdit=(navpop.parentPopup && navpop.parentPopup.article.toString());
return str.charAt(0).toUpperCase() + str.substring(1);
+
var list=listLinks(data, navpop.originalArticle, titleToEdit);
 +
if (list.length === 0) { log('listLinks returned empty list'); return null; }
 +
var html='<hr />' + popupString('Click to disambiguate this link to:') + '<br>';
 +
html+=list.join(', ');
 +
return html;
 
}
 
}
  
  
function findInArray(arr, foo) {
+
function makeFixDabs(wikiText, navpop, props) {
if (!arr || !arr.length) { return -1; }
+
if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article, props) &&
var len=arr.length;
+
Title.fromURL(location.href).namespaceId() != pg.nsSpecialId &&
for (var i=0; i<len; ++i) { if (arr[i]==foo) { return i; } }
+
navpop.article.talkPage() ) {
return -1;
+
setPopupHTML(makeFixDab(wikiText, navpop, props), 'popupFixDab', navpop.idNumber);
 +
}
 
}
 
}
  
function nextOne (array, value) {
+
function popupRedlinkHTML(article) {
// NB if the array has two consecutive entries equal
+
return changeLinkTargetLink(
// then this will loop on successive calls
+
{ newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'),
var i=findInArray(array, value);
+
hint: popupString("remove all links to this page from this article"),
if (i<0) { return null; }
+
clickButton: getValueOf('popupRedlinkAutoClick'),
return array[i+1];
+
oldTarget: article.toString(),
 +
summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])});
 
}
 
}
 +
//</NOLITE>
 +
// ENDFILE: dab.js
 +
// STARTFILE: htmloutput.js
  
function literalizeRegex(str){
+
// this has to use a timer loop as we don't know if the DOM element exists when we want to set the text
return str.replace(RegExp('([-.|()\\\\+?*^${}\\[\\]])', 'g'), '\\$1');
+
function setPopupHTML (str, elementId, popupId, onSuccess, append) {
}
+
if (typeof popupId === 'undefined') {
 
+
//console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100));
String.prototype.entify=function() {
+
popupId = pg.idNumber;
//var shy='&shy;';
 
return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;'/*+shy*/).split('"').join('&quot;');
 
};
 
 
 
function findThis(array, value) {
 
if (typeof array.length == 'undefined') { return null; }
 
for (var i=0; i<array.length; ++i) {
 
if (array[i]==value) { return i; }
 
 
}
 
}
return null;
 
}
 
  
function removeNulls(list) {
+
var popupElement=document.getElementById(elementId+popupId);
var ret=[];
+
if (popupElement) {
for (var i=0; i<list.length; ++i) {
+
if (!append) { popupElement.innerHTML=''; }
if (list[i]) {
+
if (isString(str)) {
ret.push(list[i]);
+
popupElement.innerHTML+=str;
 +
} else {
 +
popupElement.appendChild(str);
 
}
 
}
}
+
if (onSuccess) { onSuccess(); }
return ret;
+
setTimeout(checkPopupPosition, 100);
}
+
return true;
function joinPath(list) {
+
} else {
return removeNulls(list).join('/');
+
// call this function again in a little while...
 +
setTimeout(function(){
 +
setPopupHTML(str,elementId,popupId,onSuccess);
 +
}, 600);
 +
}
 +
return null;
 
}
 
}
  
 +
//<NOLITE>
 +
function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);}
 +
//</NOLITE>
  
function simplePrintf(str, subs) {
+
// args.navpopup is mandatory
if (!str || !subs) { return str; }
+
// optional: args.redir, args.redirTarget
var ret=[];
+
// FIXME: ye gods, this is ugly stuff
var s=str.parenSplit(/(%s|\$[0-9]+)/);
+
function fillEmptySpans(args) {  
var i=0;
+
// if redir is present and true then redirTarget is mandatory
do {
+
var redir=true;
ret.push(s.shift());
+
var rcid;
if ( !s.length ) { break; }
+
if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) { redir=false; }
var cmd=s.shift();
+
var a=args.navpopup.parentAnchor;
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('');
 
}
 
  
function max(a,b){return a<b ? b : a;}
+
var article, hint=null, oldid=null, params={};
function min(a,b){return a>b ? b : a;}
+
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 };
  
function isString(x) { return (typeof x === 'string' || x instanceof String); }
+
var structure=pg.structures[getValueOf('popupStructure')];
//function isNumber(x) { return (typeof x === 'number' || x instanceof Number); }
+
if (typeof structure != 'object') {
function isRegExp(x) { return x instanceof RegExp; }
+
setPopupHTML('popupError', 'Unknown structure (this should never happen): '+
function isArray (x) { return x instanceof Array; }
+
pg.option.popupStructure, args.navpopup.idNumber);
function isObject(x) { return x instanceof Object; }
+
return;
function isFunction(x) {
+
}
return !isRegExp(x) && ($.isFunction(x) || x instanceof Function);
+
var spans=flatten(pg.misc.layout);
 +
var numspans = spans.length;
 +
var redirs=pg.misc.redirSpans;
 +
 
 +
for (var i=0; i<numspans; ++i) {
 +
var found = redirs && (redirs.indexOf( spans[i] ) !== -1);
 +
//log('redir='+redir+', found='+found+', spans[i]='+spans[i]);
 +
if ( (found && !redir) || (!found && redir) ) {
 +
//log('skipping this set of the loop');
 +
continue;
 +
}
 +
var structurefn=structure[spans[i]];
 +
var setfn = setPopupHTML;
 +
if (getValueOf('popupActiveNavlinks') &&
 +
(spans[i].indexOf('popupTopLinks')===0 || spans[i].indexOf('popupRedirTopLinks')===0)
 +
) {
 +
setfn = setPopupTipsAndHTML;
 +
}
 +
switch (typeof structurefn) {
 +
case 'function':
 +
log('running '+spans[i]+'({article:'+x.article+', hint:'+x.hint+', oldid: '+x.oldid+'})');
 +
setfn(structurefn(x), spans[i], args.navpopup.idNumber);
 +
break;
 +
case 'string':
 +
setfn(structurefn, spans[i], args.navpopup.idNumber);
 +
break;
 +
default:
 +
errlog('unknown thing with label '+spans[i] + ' (span index was ' + i + ')');
 +
break;
 +
}
 +
}
 
}
 
}
  
function repeatString(s,mult) {
+
// flatten an array
var ret='';
+
function flatten(list, start) {
for (var i=0; i<mult; ++i) { ret += s; }
+
var ret=[];
 +
if (typeof start == 'undefined') { start=0; }
 +
for (var i=start; i<list.length; ++i) {
 +
if (typeof list[i] == typeof []) {
 +
return ret.concat(flatten(list[i])).concat(flatten(list, i+1));
 +
}
 +
else { ret.push(list[i]); }
 +
}
 
return ret;
 
return ret;
 
}
 
}
  
function zeroFill(s, min) {
+
// Generate html for whole popup
min = min || 2;
+
function popupHTML (a) {
var t=s.toString();
+
getValueOf('popupStructure');
return repeatString('0', min - t.length) + t;
+
var structure=pg.structures[pg.option.popupStructure];
 +
if (typeof structure != 'object') {
 +
//return 'Unknown structure: '+pg.option.popupStructure;
 +
// override user choice
 +
pg.option.popupStructure=pg.optionDefault.popupStructure;
 +
return popupHTML(a);
 +
}
 +
if (typeof structure.popupLayout != 'function') { return 'Bad layout'; }
 +
pg.misc.layout=structure.popupLayout();
 +
if ($.isFunction(structure.popupRedirSpans)) { pg.misc.redirSpans=structure.popupRedirSpans(); }
 +
else { pg.misc.redirSpans=[]; }
 +
return makeEmptySpans(pg.misc.layout, a.navpopup);
 
}
 
}
  
function map(f, o) {
+
function makeEmptySpans (list, navpop) {
if (isArray(o)) { return map_array(f,o); }
+
var ret='';
return map_object(f,o);
+
for (var i=0; i<list.length; ++i) {
}
+
if (typeof list[i] == typeof '') {
function map_array(f,o) {
+
ret += emptySpanHTML(list[i], navpop.idNumber, 'div');
var ret=[];
+
} else if (typeof list[i] == typeof [] && list[i].length > 0 ) {
for (var i=0; i<o.length; ++i) {
+
ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop));
ret.push(f(o[i]));
+
} else if (typeof list[i] == typeof {} && list[i].nodeType ) {
 +
ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType);
 +
}
 
}
 
}
return ret;
 
}
 
function map_object(f,o) {
 
var ret={};
 
for (var i in o) { ret[o]=f(o[i]); }
 
 
return ret;
 
return ret;
 
}
 
}
  
pg.escapeQuotesHTML = function ( text ) { var re = new RegExp( '&', "g" ); text = text.replace( re, "&amp;" ); re = new RegExp( '"', "g" ); text = text.replace( re, "&quot;" ); re = new RegExp( '<', "g" ); text = text.replace( re, "&lt;" ); re = new RegExp( '>', "g" ); text = text.replace( re, "&gt;" ); return text;}
 
  
pg.jsescape = function(s)
+
function emptySpanHTML(name, id, tag, classname) {
{
+
tag = tag || 'span';
if (typeof s !== "string") throw "Invalid type in pg.jsescape";
+
if (!classname) { classname = emptySpanHTML.classAliases[name]; }
var res = "";
+
classname = classname || name;
//this can be optimized by copying substrings instead of char by char!
+
if (name == getValueOf('popupDragHandle')) { classname += ' popupDragHandle'; }
for (var i=0; i<s.length; i++)
+
return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]);
{
 
var c = s[i];
 
switch (c)
 
{
 
  case '\b': res += '\\b'; continue;
 
  case '\f': res += '\\f'; continue;
 
  case '\n': res += '\\n'; continue;
 
  case '\0': res += '\\0'; continue;
 
  case '\r': res += '\\r'; continue;
 
  case '\t': res += '\\t'; continue;
 
  case '\v': res += '\\v'; continue;
 
  case '\\': res += '\\\\'; continue;
 
  case '\"':  res += '\\\"'; continue;
 
  case '\'':  res += '\\\''; continue;
 
  continue;
 
  default:
 
if (c < ' ' || c==='<' || c==='>' || c==="'")
 
{
 
var unicodeChar = c.charCodeAt(0).toString(16).toUpperCase();
 
res += "\\u" + (unicodeChar.length>1?"00":"000") + unicodeChar;
 
}
 
else
 
{
 
res += c;
 
}
 
}
 
  }
 
  return res;
 
 
}
 
}
 +
emptySpanHTML.classAliases={ 'popupSecondPreview': 'popupPreview' };
  
// ENDFILE: tools.js
+
// generate html for popup image
// STARTFILE: dab.js
+
// <a id="popupImageLinkn"><img id="popupImagen">
//<NOLITE>
+
// where n=idNumber
//////////////////////////////////////////////////
+
function imageHTML(article, idNumber) {
// Dab-fixing code
+
return simplePrintf('<a id="popupImageLink$1">' +
//
+
'<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' +
 +
'</a>', [ idNumber ]);
 +
}
  
 +
function popTipsSoonFn(id, when, popData) {
 +
if (!when) { when=250; }
 +
var popTips=function(){ setupTooltips(document.getElementById(id), false, true, popData); };
 +
return function() { setTimeout( popTips, when, popData ); };
 +
}
  
function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) {
+
function setPopupTipsAndHTML(html, divname, idnumber, popData) {
log('retargetDab: newTarget='+newTarget + ' oldTarget=' + oldTarget);
+
setPopupHTML(html, divname, idnumber,
return changeLinkTargetLink(
+
getValueOf('popupSubpopups') ?
{newTarget: newTarget,
+
popTipsSoonFn(divname + idnumber, null, popData) :  
text: newTarget.split(' ').join('&nbsp;'),
+
null);
hint: tprintf('disambigHint', [newTarget]),
 
summary: simplePrintf(
 
getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget ]),
 
clickButton: getValueOf('popupDabsAutoClick'), minor: true, oldTarget: oldTarget,
 
watch: getValueOf('popupWatchDisambiggedPages'),
 
title: titleToEdit});
 
 
}
 
}
 +
// ENDFILE: htmloutput.js
 +
// STARTFILE: mouseout.js
 +
//////////////////////////////////////////////////
 +
// fuzzy checks
  
function listLinks(wikitext, oldTarget, titleToEdit) {
+
function fuzzyCursorOffMenus(x,y, fuzz, parent) {
// mediawiki strips trailing spaces, so we do the same
+
if (!parent) { return null; }
// testcase: http://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633
+
var uls=parent.getElementsByTagName('ul');
var reg=RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi');
+
for (var i=0; i<uls.length; ++i) {
var ret=[];
+
if (uls[i].className=='popup_menu') {
var splitted=wikitext.parenSplit(reg);
+
if (uls[i].offsetWidth > 0) return false;
// ^[a-z]+ should match interwiki links, hopefully (case-insensitive)
+
} // else {document.title+='.';}
// and ^[a-z]* should match those and [[:Category...]] style links too
 
var omitRegex=RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory');
 
var friendlyCurrentArticleName= oldTarget.toString();
 
var wikPos = getValueOf('popupDabWiktionary');
 
 
 
for (var i=1; i<splitted.length; i=i+3) {
 
if (typeof splitted[i] == typeof 'string' && splitted[i].length>0 && !omitRegex.test(splitted[i])) {
 
ret.push( retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit) );
 
} /* if */
 
} /* for loop */
 
 
 
ret = rmDupesFromSortedList(ret.sort());
 
 
 
if (wikPos) {
 
var wikTarget='wiktionary:' +
 
friendlyCurrentArticleName.replace( RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1' );
 
 
 
var meth;
 
if (wikPos.toLowerCase() == 'first') { meth = 'unshift'; }
 
else { meth = 'push'; }
 
 
 
ret[meth]( retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) );
 
 
}
 
}
 
+
return true;
ret.push(changeLinkTargetLink(
 
{ newTarget: null,
 
text: popupString('remove this link').split(' ').join('&nbsp;'),
 
hint: popupString("remove all links to this disambig page from this article"),
 
clickButton: "wpDiff", oldTarget: oldTarget,
 
summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]),
 
watch: getValueOf('popupWatchDisambiggedPages'),
 
title: titleToEdit
 
}));
 
return ret;
 
 
}
 
}
  
function rmDupesFromSortedList(list) {
+
function checkPopupPosition () { // stop the popup running off the right of the screen
var ret=[];
+
// FIXME avoid pg.current.link
for (var i=0; i<list.length; ++i) {
+
if (pg.current.link && pg.current.link.navpopup)
if (ret.length===0 || list[i]!=ret[ret.length-1]) { ret.push(list[i]); }
+
pg.current.link.navpopup.limitHorizontalPosition();
}
 
return ret;
 
 
}
 
}
  
function makeFixDab(data, navpop) {
+
function mouseOutWikiLink () {
// grab title from parent popup if there is one; default exists in changeLinkTargetLink
+
//console ('mouseOutWikiLink');
var titleToEdit=(navpop.parentPopup && navpop.parentPopup.article.toString());
+
var a=this;
var list=listLinks(data, navpop.originalArticle, titleToEdit);
+
if (list.length===0) { log('listLinks returned empty list'); return null; }
+
removeModifierKeyHandler(a);
var html='<hr />' + popupString('Click to disambiguate this link to:') + '<br>';
+
html+=list.join(', ');
+
if (a.navpopup === null || typeof a.navpopup === 'undefined') return;
return html;
+
if ( ! a.navpopup.isVisible() ) {
 +
a.navpopup.banish();
 +
return;
 +
}
 +
restoreTitle(a);
 +
Navpopup.tracker.addHook(posCheckerHook(a.navpopup));
 
}
 
}
  
 +
function posCheckerHook(navpop) {
 +
return function() {
 +
if (!navpop.isVisible()) { return true; /* remove this hook */ }
 +
if (Navpopup.tracker.dirty) {
 +
return false;
 +
}
 +
var x=Navpopup.tracker.x, y=Navpopup.tracker.y;
 +
var mouseOverNavpop = navpop.isWithin(x,y,navpop.fuzz, navpop.mainDiv) ||
 +
!fuzzyCursorOffMenus(x,y,navpop.fuzz, navpop.mainDiv);
  
function makeFixDabs(wikiText, navpop) {
+
// FIXME it'd be prettier to do this internal to the Navpopup objects
if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) &&
+
var t=getValueOf('popupHideDelay');
Title.fromURL(location.href).namespaceId() != pg.nsSpecialId &&
+
if (t) { t = t * 1000; }
navpop.article.talkPage() ) {
+
if (!t) {
setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber);
+
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=+(new Date());
 +
if ( !navpop.mouseLeavingTime ) {
 +
navpop.mouseLeavingTime = d;
 +
return false;
 +
}
 +
if ( mouseOverNavpop ) {
 +
navpop.mouseLeavingTime=null;
 +
return false;
 +
}
 +
if (d - navpop.mouseLeavingTime > t) {
 +
navpop.mouseLeavingTime=null;
 +
navpop.banish(); return true; /* remove this hook */
 +
}
 +
return false;
 +
};
 
}
 
}
  
function popupRedlinkHTML(article) {
+
function runStopPopupTimer(navpop) {
return changeLinkTargetLink(
+
// at this point, we should have left the link but remain within the popup
{ newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'),
+
// so we call this function again until we leave the popup.
hint: popupString("remove all links to this page from this article"),
+
if (!navpop.stopPopupTimer) {
clickButton: "wpDiff",
+
navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500);
oldTarget: article.toString(),
+
navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);},
summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])});
+
  'hide', 'before');
 +
}
 
}
 
}
//</NOLITE>
+
// ENDFILE: mouseout.js
// ENDFILE: dab.js
+
// STARTFILE: previewmaker.js
// STARTFILE: htmloutput.js
+
/**
 +
  @fileoverview
 +
  Defines the {@link Previewmaker} object, which generates short previews from wiki markup.
 +
*/
  
function appendPopupContent(obj, elementId, popupId, onSuccess) {
+
/**
return setPopupHTML(obj, elementId, popupId, onSuccess, true);
+
  Creates a new Previewmaker
}
+
  @constructor
 +
  @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext.
 +
  @param {String} wikiText The Wikitext source of the page we wish to preview.
 +
  @param {String} baseUrl The url we should prepend when creating relative urls.
 +
  @param {Navpopup} owner The navpop associated to this preview generator
 +
*/
 +
function Previewmaker(wikiText, baseUrl, owner) {
 +
/** The wikitext which is manipulated to generate the preview. */
 +
this.originalData=wikiText;
 +
this.baseUrl=baseUrl;
 +
this.owner=owner;
  
// this has to use a timer loop as we don't know if the DOM element exists when we want to set the text
+
this.maxCharacters=getValueOf('popupMaxPreviewCharacters');
function setPopupHTML (str, elementId, popupId, onSuccess, append) {
+
this.maxSentences=getValueOf('popupMaxPreviewSentences');
if (elementId=='popupPreview') {
 
}
 
if (typeof popupId === 'undefined') {
 
//console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100));
 
popupId = pg.idNumber;
 
}
 
  
var popupElement=document.getElementById(elementId+popupId);
+
this.setData();
if (popupElement) {
+
}
if (!append) { popupElement.innerHTML=''; }
+
Previewmaker.prototype.setData=function() {
if (isString(str)) {
+
var maxSize=Math.max(10000, 2*this.maxCharacters);
popupElement.innerHTML+=str;
+
this.data=this.originalData.substring(0,maxSize);
} else {
+
};
popupElement.appendChild(str);
+
/** Remove HTML comments
}
+
@private
if (onSuccess) { onSuccess(); }
+
*/
setTimeout(checkPopupPosition, 100);
+
Previewmaker.prototype.killComments = function () {
return true;
+
// this also kills one trailing newline, eg [[diamyo]]
} else {
+
this.data=this.data.replace(RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), '');
// call this function again in a little while...
+
};
setTimeout(function(){
+
/**
setPopupHTML(str,elementId,popupId,onSuccess);
+
  @private
}, 600);
+
*/
 +
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);
 
}
 
}
return null;
+
this.data=k;
}
+
};
 
+
/**
//<NOLITE>
+
  @private
function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);}
+
*/
//</NOLITE>
+
Previewmaker.prototype.killStuff = function (txt, opening, closing, subopening, subclosing, repl) {
 
+
var op=this.makeRegexp(opening);
 
+
var cl=this.makeRegexp(closing, '^');
function fillEmptySpans(args) { return fillEmptySpans2(args); }
+
var sb=subopening ? this.makeRegexp(subopening, '^') : null;
 
+
var sc=subclosing ? this.makeRegexp(subclosing, '^') : cl;
// args.navpopup is mandatory
+
if (!op || !cl) {
// optional: args.redir, args.redirTarget
+
alert('Navigation Popups error: op or cl is null! something is wrong.');
// FIXME: ye gods, this is ugly stuff
 
function fillEmptySpans2(args) { // if redir is present and true then redirTarget is mandatory
 
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={};
 
if (redir && typeof args.redirTarget == typeof {}) {
 
article=args.redirTarget;
 
//hint=article.hintValue();
 
} else {
 
article=(new Title()).fromAnchor(a);
 
hint=a.originalTitle || article.hintValue();
 
params=parseParams(a.href);
 
oldid=(getValueOf('popupHistoricalLinks')) ? params.oldid : null;
 
rcid=params.rcid;
 
}
 
var x={ article:article, hint: hint, oldid: oldid, rcid: rcid, navpop:args.navpopup, params:params };
 
 
 
var structure=pg.structures[getValueOf('popupStructure')];
 
if (typeof structure != 'object') {
 
setPopupHTML('popupError', 'Unknown structure (this should never happen): '+
 
pg.option.popupStructure, args.navpopup.idNumber);
 
 
return;
 
return;
 
}
 
}
var spans=flatten(pg.misc.layout);
+
if (!op.test(txt)) { return txt; }
var numspans = spans.length;
+
var ret='';
var redirs=pg.misc.redirSpans;
+
var opResult = op.exec(txt);
 
+
ret = txt.substring(0,opResult.index);
for (var i=0; i<numspans; ++i) {
+
txt=txt.substring(opResult.index+opResult[0].length);
var f=findThis(redirs, spans[i]);
+
var depth = 1;
//log('redir='+redir+', f='+f+', spans[i]='+spans[i]);
+
while (txt.length > 0) {
if ( (f!==null && !redir) || (f===null && redir) ) {
+
var removal=0;
//log('skipping this set of the loop');
+
if (depth==1 && cl.test(txt)) {
continue;
+
depth--;
}
+
removal=cl.exec(txt)[0].length;
var structurefn=structure[spans[i]];
+
} else if (depth > 1 && sc.test(txt)) {
var setfn = setPopupHTML;
+
depth--;
if (getValueOf('popupActiveNavlinks') &&  
+
removal=sc.exec(txt)[0].length;
(spans[i].indexOf('popupTopLinks')==0 || spans[i].indexOf('popupRedirTopLinks')==0)
+
}else if (sb && sb.test(txt)) {
) {
+
depth++;
setfn = setPopupTipsAndHTML;
+
removal=sb.exec(txt)[0].length;
}
 
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]);
 
break;
 
 
}
 
}
 +
if ( !removal ) { removal = 1; }
 +
txt=txt.substring(removal);
 +
if (depth === 0) { break; }
 
}
 
}
}
+
return ret + (repl || '') + txt;
 
+
};
// flatten an array
+
/**
function flatten(list, start) {
+
  @private
var ret=[];
+
*/
if (typeof start == 'undefined') { start=0; }
+
Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) {
for (var i=start; i<list.length; ++i) {
+
prefix = prefix || '';
if (typeof list[i] == typeof []) {
+
suffix = suffix || '';
return ret.concat(flatten(list[i])).concat(flatten(list, i+1));
+
var reStr='';
}
+
var flags='';
else { ret.push(list[i]); }
+
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');
 
}
 
}
return ret;
 
}
 
  
// Generate html for whole popup
+
log ('makeRegexp: got reStr=' + reStr + ', flags=' + flags);
function popupHTML (a) {
+
return RegExp(reStr, flags);
getValueOf('popupStructure');
+
};
var structure=pg.structures[pg.option.popupStructure];
+
/**
if (typeof structure != 'object') {
+
  @private
//return 'Unknown structure: '+pg.option.popupStructure;
+
*/
// override user choice
+
Previewmaker.prototype.killBoxTemplates = function () {
pg.option.popupStructure=pg.optionDefault.popupStructure;
+
 
return popupHTML(a);
+
// taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general
}
+
// also, have float_begin, ... float_end
if (typeof structure.popupLayout != 'function') { return 'Bad layout'; }
+
this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{');
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) {
+
// infoboxes etc
var ret='';
+
// from [[User:Zyxw/popups.js]]: kill frames too
for (var i=0; i<list.length; ++i) {
+
this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{');
if (typeof list[i] == typeof '') {
+
 
ret += emptySpanHTML(list[i], navpop.idNumber, 'div');
+
};
} else if (typeof list[i] == typeof [] && list[i].length > 0 ) {
+
/**
ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop));
+
  @private
} else if (typeof list[i] == typeof {} && list[i].nodeType ) {
+
  <nowiki>
ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType);
+
*/
}
+
Previewmaker.prototype.killTemplates = function () {
}
+
this.kill('{{', '}}', '{', '}', ' ');
return ret;
+
};
}
+
/**
 +
  </nowiki>
 +
  @private
 +
*/
 +
Previewmaker.prototype.killTables = function () {
 +
// tables are bad, too
 +
// this can be slow, but it's an inprovement over a browser hang
 +
// torture test: [[Comparison_of_Intel_Central_Processing_Units]]
 +
this.kill('{|', /[|]}\s*/, '{|');
 +
this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i);
 +
// remove lines starting with a pipe for the hell of it (?)
 +
this.data=this.data.replace(RegExp('^[|].*$', 'mg'), '');
 +
};
 +
/**
 +
  @private
 +
*/
 +
Previewmaker.prototype.killImages = function () {
 +
var forbiddenNamespaceAliases = [];
 +
jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
 +
if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return;
 +
forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments!
 +
});
 +
 +
// images and categories are a nono
 +
this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'),
 +
  /\]\]\s*/, '[', ']');
 +
};
 +
/**
 +
  @private
 +
*/
 +
Previewmaker.prototype.killHTML = function () {
 +
// kill <ref ...>...</ref>
 +
this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i);
  
 +
// let's also delete entire lines starting with <. it's worth a try.
 +
this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n');
  
function emptySpanHTML(name, id, tag, classname) {
+
// and those pesky html tags, but not <nowiki> or <blockquote>
tag = tag || 'span';
+
var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/);
if (!classname) { classname = emptySpanHTML.classAliases[name]; }
+
var len=splitted.length;
classname = classname || name;
+
for (var i=1; i<len; i=i+2) {
if (name == getValueOf('popupDragHandle')) { classname += ' popupDragHandle'; }
+
switch (splitted[i]) {
return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]);
+
case '<nowiki>':
}
+
case '</nowiki>':
emptySpanHTML.classAliases={ 'popupSecondPreview': 'popupPreview' };
+
case '<blockquote>':
 +
case '</blockquote>':
 +
break;
 +
default:
 +
splitted[i]='';
 +
}
 +
}
 +
this.data=splitted.join('');
 +
};
 +
/**
 +
  @private
 +
*/
 +
Previewmaker.prototype.killChunks = function() { // heuristics alert
 +
// chunks of italic text? you crazy, man?
 +
var italicChunkRegex=new RegExp
 +
("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g');
 +
// keep stuff separated, though, so stick in \n (fixes [[Union Jack]]?
 +
this.data=this.data.replace(italicChunkRegex, '\n');
 +
};
 +
/**
 +
  @private
 +
*/
 +
Previewmaker.prototype.mopup = function () {
 +
// we simply *can't* be doing with horizontal rules right now
 +
this.data=this.data.replace(RegExp('^-{4,}','mg'),'');
  
// generate html for popup image
+
// no indented lines
// <a id="popupImageLinkn"><img id="popupImagen">
+
this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), '');
// 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) {
+
// replace __TOC__, __NOTOC__ and whatever else there is
when || ( when=250 );
+
// this'll probably do
var popTips=function(){ setupTooltips(document.getElementById(id), false, true, popData); };
+
this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),'');
return function() { setTimeout( popTips, when, popData ); };
+
};
}
+
/**
 +
  @private
 +
*/
 +
Previewmaker.prototype.firstBit = function () {
 +
// dont't be givin' me no subsequent paragraphs, you hear me?
 +
/// first we "normalize" section headings, removing whitespace after, adding before
 +
var d=this.data;
 +
 
 +
if (getValueOf('popupPreviewCutHeadings')) {
 +
this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 ');
 +
/// then we want to get rid of paragraph breaks whose text ends badly
 +
this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n');
  
function setPopupTipsAndHTML(html, divname, idnumber, popData) {
+
this.data=this.data.replace(RegExp('^[\\s\\n]*'), '');
setPopupHTML(html, divname, idnumber,
+
var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data);
getValueOf('popupSubpopups') ?
+
if (stuff) { d = stuff[0]; }
popTipsSoonFn(divname + idnumber, null, popData) :
+
if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; }
null);
 
}
 
// ENDFILE: htmloutput.js
 
// STARTFILE: mouseout.js
 
//////////////////////////////////////////////////
 
// fuzzy checks
 
  
function fuzzyCursorOffMenus(x,y, fuzz, parent) {
+
/// now put \n\n after sections so that bullets and numbered lists work
if (!parent) { return null; }
+
d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n');
var uls=parent.getElementsByTagName('ul');
 
for (var i=0; i<uls.length; ++i) {
 
if (uls[i].className=='popup_menu') {
 
if (uls[i].offsetWidth > 0) return false;
 
} // else {document.title+='.';}
 
 
}
 
}
return true;
 
}
 
  
function checkPopupPosition () { // stop the popup running off the right of the screen
 
// FIXME avoid pg.current.link
 
pg.current.link && pg.current.link.navpopup &&
 
pg.current.link.navpopup.limitHorizontalPosition();
 
}
 
  
function mouseOutWikiLink () {
+
// Split sentences. Superfluous sentences are RIGHT OUT.
if (!window.popupsReady || !window.popupsReady()) { return; }
+
// note: exactly 1 set of parens here needed to make the slice work
//console ('mouseOutWikiLink');
+
d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g'));
var a=this;
+
// leading space is bad, mmkay?
if (a.navpopup==null) return;
+
d[0]=d[0].replace(RegExp('^\\s*'), '');
if ( ! a.navpopup.isVisible() ) {
+
 
a.navpopup.banish();
+
var notSentenceEnds=RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i');
return;
+
d = this.fixSentenceEnds(d, notSentenceEnds);
}
+
 
restoreTitle(a);
+
this.fullLength=d.join('').length;
Navpopup.tracker.addHook(posCheckerHook(a.navpopup));
+
var n=this.maxSentences;
}
+
var dd=this.firstSentences(d,n);  
 +
 
 +
do {
 +
dd=this.firstSentences(d,n); --n;
 +
} while ( dd.length > this.maxCharacters && n !== 0 );
  
function posCheckerHook(navpop) {
+
this.data = dd;
return function() {
+
};
if (!navpop.isVisible()) { return true; /* remove this hook */ }
+
/**
if (Navpopup.tracker.dirty) {
+
  @private
return false;
+
*/
}
+
Previewmaker.prototype.fixSentenceEnds = function(strs, reg) {
var x=Navpopup.tracker.x, y=Navpopup.tracker.y;
+
// take an array of strings, strs
var mouseOverNavpop = navpop.isWithin(x,y,navpop.fuzz, navpop.mainDiv) ||
+
// join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg
!fuzzyCursorOffMenus(x,y,navpop.fuzz, navpop.mainDiv);
 
  
// FIXME it'd be prettier to do this internal to the Navpopup objects
+
for (var i=0; i<strs.length-2; ++i) {
var t=getValueOf('popupHideDelay');
+
if (reg.test(strs[i])) {
if (t) { t = t * 1000; }
+
var a=[];
if (!t) {
+
for (var j=0; j<strs.length; ++j) {
if(!mouseOverNavpop) {
+
if (j<i)   a[j]=strs[j];
if(navpop.parentAnchor) {
+
if (j==i) a[i]=strs[i]+strs[i+1]+strs[i+2];
restoreTitle( navpop.parentAnchor );
+
if (j>i+2) a[j-2]=strs[j];
}
 
navpop.banish();
 
return true; /* remove this hook */
 
 
}
 
}
return false;
+
return this.fixSentenceEnds(a,reg);
}
 
// we have a hide delay set
 
var d=+(new Date());
 
if ( !navpop.mouseLeavingTime ) {
 
navpop.mouseLeavingTime = d;
 
return false;
 
}
 
if ( mouseOverNavpop ) {
 
navpop.mouseLeavingTime=null;
 
return false;
 
}
 
if (d - navpop.mouseLeavingTime > t) {
 
navpop.mouseLeavingTime=null;
 
navpop.banish(); return true; /* remove this hook */
 
 
}
 
}
return false;
 
};
 
}
 
 
function runStopPopupTimer(navpop) {
 
// at this point, we should have left the link but remain within the popup
 
// so we call this function again until we leave the popup.
 
if (!navpop.stopPopupTimer) {
 
navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500);
 
navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);},
 
  'hide', 'before');
 
 
}
 
}
}
+
return strs;
// ENDFILE: mouseout.js
+
};
// STARTFILE: previewmaker.js
 
 
/**
 
/**
   @fileoverview
+
   @private
  Defines the {@link Previewmaker} object, which generates short previews from wiki markup.
 
 
*/
 
*/
 
+
Previewmaker.prototype.firstSentences = function(strs, howmany) {
 +
var t=strs.slice(0, 2*howmany);
 +
return t.join('');
 +
};
 
/**
 
/**
  Creates a new Previewmaker
+
   @private
   @constructor
 
  @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext.
 
  @param {String} wikiText The Wikitext source of the page we wish to preview.
 
  @param {String} baseUrl The url we should prepend when creating relative urls.
 
  @param {Navpopup} owner The navpop associated to this preview generator
 
 
*/
 
*/
function Previewmaker(wikiText, baseUrl, owner) {
+
Previewmaker.prototype.killBadWhitespace = function() {
/** The wikitext which is manipulated to generate the preview. */
+
// also cleans up isolated '''', eg [[Suntory Sungoliath]]
this.originalData=wikiText;
+
this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), '');
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('<!--[\\s\\S]*?-->\\n?', 'g'), '');  
 
 
};
 
};
 
/**
 
/**
 +
  Runs the various methods to generate the preview.
 +
  The preview is stored in the <code>html</html> field.
 
   @private
 
   @private
 
*/
 
*/
Previewmaker.prototype.killDivs = function () {
+
Previewmaker.prototype.makePreview = function() {
// say goodbye, divs (can be nested, so use * not *?)
+
if (this.owner.article.namespaceId()!=pg.nsTemplateId &&
this.data=this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>',
+
this.owner.article.namespaceId()!=pg.nsImageId ) {
  'gi'), '');
+
this.killComments();
 +
this.killDivs();
 +
this.killGalleries();
 +
this.killBoxTemplates();
 +
 
 +
if (getValueOf('popupPreviewKillTemplates')) {
 +
this.killTemplates();
 +
} else {
 +
this.killMultilineTemplates();
 +
}
 +
this.killTables();
 +
this.killImages();
 +
this.killHTML();
 +
this.killChunks();
 +
this.mopup();
 +
 
 +
this.firstBit();
 +
this.killBadWhitespace();
 +
}
 +
else
 +
{
 +
this.killHTML();
 +
}
 +
this.html=wiki2html(this.data, this.baseUrl); // needs livepreview
 +
this.fixHTML();
 +
this.stripLongTemplates();
 
};
 
};
 
/**
 
/**
 
   @private
 
   @private
 
*/
 
*/
Previewmaker.prototype.killGalleries = function () {
+
Previewmaker.prototype.esWiki2HtmlPart = function(data) {
this.data=this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>',
+
  var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink
  'gi'), '');
+
  reLinks.lastIndex = 0; //reset regex
 +
 
 +
  var match;
 +
  var result = "";
 +
  var postfixIndex = 0;
 +
  while ((match = reLinks.exec(data))) //match all wikilinks
 +
  {
 +
//FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases.
 +
result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) +
 +
  '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>";
 +
postfixIndex = reLinks.lastIndex;
 +
  }
 +
  //append the rest
 +
  result += pg.escapeQuotesHTML(data.substring(postfixIndex));
 +
 
 +
  return result;
 
};
 
};
/**
+
Previewmaker.prototype.editSummaryPreview=function() {
  @private
+
var reAes  = /\/\* *(.*?) *\*\//g; //match the first section marker
*/
+
reAes.lastIndex = 0; //reset regex
Previewmaker.prototype.kill = function(opening, closing, subopening, subclosing, repl) {
+
var oldk=this.data;
+
var match;
var k=this.killStuff(this.data, opening, closing, subopening, subclosing, repl);
+
while (k.length < oldk.length) {
+
match = reAes.exec(this.data);
oldk=k;
+
if (match)
k=this.killStuff(k, opening, closing, subopening, subclosing, repl);
+
{
}
+
//we have a section link. Split it, process it, combine it.
this.data=k;
+
var prefix = this.data.substring(0,match.index-1);
};
+
var section = match[1];
/**
+
var postfix = this.data.substring(reAes.lastIndex);
  @private
+
*/
+
var start = "<span class='autocomment'>";
Previewmaker.prototype.killStuff = function (txt, opening, closing, subopening, subclosing, repl) {
+
var end = "</span>";
var op=this.makeRegexp(opening);
+
if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- ";
var cl=this.makeRegexp(closing, '^');
+
if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix);
var sb=subopening ? this.makeRegexp(subopening, '^') : null;
+
var sc=subclosing ? this.makeRegexp(subclosing, '^') : cl;
+
 
if (!op || !cl) {
+
var t=new Title().fromURL(this.baseUrl);
alert('Navigation Popups error: op or cl is null! something is wrong.');
+
t.anchorFromUtf(section);
return;
+
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;
 
}
 
}
if (!op.test(txt)) { return txt; }
+
var ret='';
+
//else there's no section link, htmlify the whole thing.
var opResult = op.exec(txt);
+
return this.esWiki2HtmlPart(this.data);
ret = txt.substring(0,opResult.index);
+
};
txt=txt.substring(opResult.index+opResult[0].length);
+
 
var depth = 1;
+
//<NOLITE>
while (txt.length > 0) {
+
/** Test function for debugging preview problems one step at a time.
var removal=0;
+
*/
if (depth==1 && cl.test(txt)) {
+
/*eslint-disable */
depth--;
+
function previewSteps(txt) {
removal=cl.exec(txt)[0].length;
+
try {
} else if (depth > 1 && sc.test(txt)) {
+
txt=txt || document.editform.wpTextbox1.value;
depth--;
+
} catch (err) {
removal=sc.exec(txt)[0].length;
+
if (pg.cache.pages.length > 0) {
}else if (sb && sb.test(txt)) {
+
txt=pg.cache.pages[pg.cache.pages.length-1].data;
depth++;
+
} else {
removal=sb.exec(txt)[0].length;
+
alert('provide text or use an edit page');
 
}
 
}
if ( !removal ) { removal = 1; }
 
txt=txt.substring(removal);
 
if (depth==0) { break; }
 
 
}
 
}
return ret + (repl || '') + txt;
+
txt=txt.substring(0,10000);
};
+
var base=pg.wiki.articlebase + Title.fromURL(document.location.href).urlString();
 +
var p=new Previewmaker(txt, base, pg.current.link.navpopup);
 +
if (this.owner.article.namespaceId() != pg.nsTemplateId) {
 +
p.killComments(); if (!confirm('done killComments(). Continue?\n---\n' + p.data)) { return; }
 +
p.killDivs(); if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) { return; }
 +
p.killGalleries(); if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) { return; }
 +
p.killBoxTemplates(); if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) { return; }
 +
 
 +
if (getValueOf('popupPreviewKillTemplates')) {
 +
p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; }
 +
} else {
 +
p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; }
 +
}
 +
 
 +
p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; }
 +
p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; }
 +
p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; }
 +
p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; }
 +
p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; }
 +
 
 +
p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; }
 +
p.killBadWhitespace(); if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) { return; }
 +
}
 +
 
 +
p.html=wiki2html(p.data, base); // needs livepreview
 +
p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; }
 +
p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; }
 +
alert('finished preview - end result follows.\n---\n' + p.html);
 +
}
 +
/*eslint-enable */
 +
//</NOLITE>
 +
 
 
/**
 
/**
 +
  Works around livepreview bugs.
 
   @private
 
   @private
 
*/
 
*/
Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) {
+
Previewmaker.prototype.fixHTML = function() {
prefix = prefix || '';
+
if(!this.html) return;
suffix = suffix || '';
+
 
var reStr='';
+
  var ret = this.html;
var flags='';
+
 
if (isString(x)) {
+
// fix question marks in wiki links
reStr=prefix + literalizeRegex(x) + suffix;
+
// maybe this'll break some stuff :-(
} else if (isRegExp(x)) {
+
ret=ret.replace(RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'), '$1%3F$2');
var s=x.toString().substring(1);
+
ret=ret.replace(RegExp('(<a href=\'' + pg.wiki.articlePath + '/[^\']*)[?](.*?\')', 'g'), '$1%3F$2');
var sp=s.split('/');
+
// FIXME fix up % too
flags=sp[sp.length-1];
+
sp[sp.length-1]='';
+
s=sp.join('/');
+
this.html=ret;
s=s.substring(0,s.length-1);
 
reStr= prefix + s + suffix;
 
} else {
 
log ('makeRegexp failed');
 
}
 
 
 
log ('makeRegexp: got reStr=' + reStr + ', flags=' + flags);
 
return RegExp(reStr, flags);
 
 
};
 
};
 
/**
 
/**
   @private
+
   Generates the preview and displays it in the current popup.
*/
 
Previewmaker.prototype.killBoxTemplates = function () {
 
  
// taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general
+
  Does nothing if the generated preview is invalid or consists of whitespace only.
// also, have float_begin, ... float_end
+
  Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true.
this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{');
 
 
 
// infoboxes etc
 
// from [[User:Zyxw/popups.js]]: kill frames too
 
this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{');
 
 
 
};
 
/**
 
  @private
 
 
*/
 
*/
Previewmaker.prototype.killTemplates = function () {
+
Previewmaker.prototype.showPreview = function () {
this.kill('{{', '}}', '{', '}', ' ');
+
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
 
   @private
 
*/
 
*/
Previewmaker.prototype.killTables = function () {
+
Previewmaker.prototype.moreLink=function() {
// tables are bad, too
+
var a=document.createElement('a');
// this can be slow, but it's an inprovement over a browser hang
+
a.className='popupMoreLink';
// torture test: [[Comparison_of_Intel_Central_Processing_Units]]
+
a.innerHTML=popupString('more...');
this.kill('{|', /[|]}\s*/, '{|');
+
var savedThis=this;
this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i);
+
a.onclick=function() {
// remove lines starting with a pipe for the hell of it (?)
+
savedThis.maxCharacters+=2000;
this.data=this.data.replace(RegExp('^[|].*$', 'mg'), '');
+
savedThis.maxSentences+=20;
 +
savedThis.setData();
 +
savedThis.showPreview();
 +
};
 +
return a;
 
};
 
};
 +
 
/**
 
/**
 
   @private
 
   @private
 
*/
 
*/
Previewmaker.prototype.killImages = function () {
+
Previewmaker.prototype.stripLongTemplates = function() {
var forbiddenNamespaceAliases = [];
+
// operates on the HTML!
jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
+
this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), '');
if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return;
+
this.html=this.html.split('\n').join(' '); // workaround for <pre> templates
forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments!
+
this.html=this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]','gi'), '');
});
 
 
// images and categories are a nono
 
this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'),
 
  /\]\]\s*/, '[', ']');
 
 
};
 
};
 
/**
 
/**
 
   @private
 
   @private
 
*/
 
*/
Previewmaker.prototype.killHTML = function () {
+
Previewmaker.prototype.killMultilineTemplates = function() {
// kill <ref ...>...</ref>
+
this.kill('{{{', '}}}');
this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i);
+
this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{');
 
+
};
// let's also delete entire lines starting with <. it's worth a try.
+
// ENDFILE: previewmaker.js
this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n');
+
// STARTFILE: querypreview.js
 
+
function loadAPIPreview(queryType, article, navpop) {
// and those pesky html tags, but not <nowiki> or <blockquote>
+
var art=new Title(article).urlString();
var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/);
+
var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';
var len=splitted.length;
+
var htmlGenerator=function(/*a, d*/){alert('invalid html generator');};
for (var i=1; i<len; i=i+2) {
+
var usernameart = '';
switch (splitted[i]) {
+
switch (queryType) {
case '<nowiki>':
+
case 'history':
case '</nowiki>':
+
url += 'titles=' + art + '&prop=revisions&rvlimit=' +
case '<blockquote>':
+
getValueOf('popupHistoryPreviewLimit');
case '</blockquote>':
+
htmlGenerator=APIhistoryPreviewHTML;
break;
+
break;
default:
+
case 'category':
splitted[i]='';
+
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&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&rvprop=content&rvslots=main' + 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&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max&rvslots=main';
 +
htmlGenerator=APIrevisionPreviewHTML;
 +
break;
 
}
 
}
this.data=splitted.join('');
+
pendingNavpopTask(navpop);
};
+
var callback=function(d){
/**
+
log( "callback of API functions was hit" );
  @private
+
showAPIPreview(queryType, htmlGenerator(article,d,navpop), navpop.idNumber, navpop, d);
*/
+
};
Previewmaker.prototype.killChunks = function() { // heuristics alert
+
var go = function(){
// chunks of italic text? you crazy, man?
+
getPageWithCaching(url, callback, navpop);
var italicChunkRegex=new RegExp
+
return true;
("((^|\\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
+
if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), '');
+
else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_'+queryType+'_QUERY_DATA'); }
 +
}
  
// replace __TOC__, __NOTOC__ and whatever else there is
+
function linkList(list) {
// this'll probably do
+
list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); });
this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),'');
+
var buf=[];
};
+
for (var i=0; i<list.length; ++i) {
/**
+
buf.push(wikiLink({article: new Title(list[i]),
  @private
+
  text: list[i].split(' ').join('&nbsp;'),
*/
+
  action:  'view'}));
Previewmaker.prototype.firstBit = function () {
 
// dont't be givin' me no subsequent paragraphs, you hear me?
 
/// first we "normalize" section headings, removing whitespace after, adding before
 
var d=this.data;
 
 
 
if (getValueOf('popupPreviewCutHeadings')) {
 
this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 ');
 
/// then we want to get rid of paragraph breaks whose text ends badly
 
this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n');
 
 
 
this.data=this.data.replace(RegExp('^[\\s\\n]*'), '');
 
stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data);
 
if (stuff) { d = stuff[0]; }
 
if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; }
 
 
 
/// now put \n\n after sections so that bullets and numbered lists work
 
d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n');
 
 
}
 
}
 +
return buf.join(', ');
 +
}
  
 +
function getTimeOffset() {
 +
var tz = mw.user.options.get('timecorrection');
  
// Split sentences. Superfluous sentences are RIGHT OUT.
+
if(tz) {
// note: exactly 1 set of parens here needed to make the slice work
+
if( tz.indexOf('|') > -1 ) {
d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g'));
+
// New format
// leading space is bad, mmkay?
+
return parseInt(tz.split('|')[1],10);
d[0]=d[0].replace(RegExp('^\\s*'), '');
+
} else if ( tz.indexOf(':') > -1 ) {
 +
// Old format
 +
return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) );
 +
}
 +
}
 +
return 0;
 +
}
  
var notSentenceEnds=RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i');
+
/*
d = this.fixSentenceEnds(d, notSentenceEnds);
+
* Creates a HTML table that's shown in the history and user-contribs popups.
 +
* @param {Object[]} h - a list of revisions, returned from the API
 +
* @param {boolean} reallyContribs - true only if we're displaying user contributions
 +
*/
 +
function editPreviewTable(article, h, reallyContribs, timeOffset) {
 +
var html=['<table>'];
 +
var day=null;
 +
var curart=article;
 +
var page=null;
  
this.fullLength=d.join('').length;
+
var makeFirstColumnLinks;
var maxChars=getValueOf('popupMaxPreviewCharacters') + this.extraCharacters;
+
if(reallyContribs) {
var n=this.maxSentences;
 
var dd=this.firstSentences(d,n);
 
  
do {
+
// We're showing user contributions, so make (diff | hist) links
dd=this.firstSentences(d,n); --n;
+
makeFirstColumnLinks = function(currentRevision) {
} while ( dd.length > this.maxCharacters && n != 0 );
+
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 {
  
this.data = dd;
+
// It's a regular history page, so make (cur | last) links
};
+
var firstRevid = h[0].revid;
/**
+
makeFirstColumnLinks = function(currentRevision) {
  @private
+
var result = '(';
*/
+
result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() +
Previewmaker.prototype.fixSentenceEnds = function(strs, reg) {
+
'&diff=' + firstRevid + '&oldid=' + currentRevision.revid + '">' + popupString('cur') + '</a>';
// take an array of strings, strs
+
result += '&nbsp;|&nbsp;';
// join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg
+
result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() +
 +
'&diff=prev&oldid=' + currentRevision.revid + '">' + popupString('last') + '</a>';
 +
result += ')';
 +
return result;
 +
};
 +
}
  
var abbrevRe=/\b[a-z][^a-z]*$/i;
+
for (var i=0; i<h.length; ++i) {
 
+
if (reallyContribs) {  
for (var i=0; i<strs.length-2; ++i) {
+
page = h[i].title;
if (reg.test(strs[i])) {
+
curart = new Title(page);
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
+
var minor = h[i].minor ? '<b>m </b>' : '';
if (false && abbrevRe.test(strs[i])) {
+
var editDate = adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset);
var j=i, buf='';
+
var thisDay = dayFormat(editDate);
do {
+
var thisTime = timeFormat(editDate);
buf=buf+strs[i]+strs[i+1];
+
if (thisDay == day) {
i=i+2;
+
thisDay = '';
} while (i<strs.length-2 && abbrevRe.test(strs[i]));
+
} else {
strs[i]=buf+strs[i];
+
day = thisDay;
var a=(j?strs.slice(0,j-1):[]).concat(strs.slice(i));
 
return this.fixSentenceEnds(a,reg);
 
 
}
 
}
 +
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 || h[i].content;
 +
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('')];
 
}
 
}
return strs;
+
html.push('</table>');
};
+
return html.join('');
/**
+
}
  @private
+
 
*/
+
function getDateFromTimestamp(t) {
Previewmaker.prototype.firstSentences = function(strs, howmany) {
+
var s=t.split(/[^0-9]/);
var t=strs.slice(0, 2*howmany);
+
switch(s.length) {
return t.join('');
+
case 0: return null;
};
+
case 1: return new Date(s[0]);
/**
+
case 2: return new Date(s[0], s[1]-1);
  @private
+
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]);
Previewmaker.prototype.killBadWhitespace = function() {
+
case 5: return new Date(s[0], s[1]-1, s[2], s[3], s[4]);
// also cleans up isolated '''', eg [[Suntory Sungoliath]]
+
case 6: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]);
this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), '');
+
default: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5], s[6]);
};
+
}
/**
+
}
  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')) {
+
function adjustDate(d, offset) {
this.killTemplates();
+
// offset is in minutes
} else {
+
var o=offset * 60 * 1000;
this.killMultilineTemplates();
+
return new Date( +d + o);
}
+
}
this.killTables();
 
this.killImages();
 
this.killHTML();
 
this.killChunks();
 
this.mopup();
 
  
this.firstBit();
+
function dayFormat(editDate, utc) {
this.killBadWhitespace();
+
if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); }
}
+
return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-');
else
+
}
{
 
this.killHTML();
 
}
 
this.html=wiki2html(this.data, this.baseUrl); // needs livepreview
 
this.fixHTML();
 
this.stripLongTemplates();
 
};
 
/**
 
  @private
 
*/
 
Previewmaker.prototype.esWiki2HtmlPart = function(data) {
 
  var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink
 
  reLinks.lastIndex = 0; //reset regex
 
  
  var match;
+
function timeFormat(editDate, utc) {
  var result = "";
+
if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); }
  var postfixIndex = 0;
+
return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':');
  while ((match = reLinks.exec(data)) != null) //match all wikilinks
+
}
  {
 
//FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases.
 
result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) +
 
  '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>";
 
postfixIndex = reLinks.lastIndex;
 
  }
 
  //append the rest
 
  result += pg.escapeQuotesHTML(data.substring(postfixIndex));
 
 
 
  return result;
 
};
 
Previewmaker.prototype.editSummaryPreview=function() {
 
var reAes  = /\/\* *(.*?) *\*\//g; //match the first section marker
 
reAes.lastIndex = 0; //reset regex
 
 
var match;
 
 
match = reAes.exec(this.data);
 
if (match)
 
{
 
//we have a section link. Split it, process it, combine it.
 
var prefix = this.data.substring(0,match.index-1);
 
var section = match[1];
 
var postfix = this.data.substring(reAes.lastIndex);
 
 
var start = "<span class='autocomment'>";
 
var end = "</span>";
 
if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- ";
 
if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix);
 
 
  
var t=new Title().fromURL(this.baseUrl);
+
function showAPIPreview(queryType, html, id, navpop, download) {
t.anchorFromUtf(section);
+
// DJ: done
var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor);
+
var target='popupPreview';
return start + '<a href="'+sectionLink+'">&rarr;</a> '+pg.escapeQuotesHTML(section) + end;
+
completedNavpopTask(navpop);
 +
 
 +
switch (queryType) {
 +
case 'imagelinks':
 +
case 'category':
 +
target='popupPostPreview'; break;
 +
case 'userinfo':
 +
target='popupUserData'; break;
 +
case 'revision':
 +
insertPreview(download);
 +
return;
 +
}
 +
setPopupTipsAndHTML(html, target, id);
 +
}
 +
 
 +
function ContentHandler(slot)
 +
{
 +
var preview_content = null;
 +
switch(slot.contentmodel)
 +
{
 +
case 'wikitext':
 +
preview_content = slot.content;
 +
break;
 +
case 'flow-board':
 +
preview_content = 'Flow workflow: '+getJsObj(slot.content)["flow-workflow"]; //placeholder
 +
break;
 +
case 'javascript':
 +
case 'css':
 +
preview_content = '['+slot.contentmodel+' code]';
 +
break;
 +
case 'NewsletterContent':
 +
preview_content = "<b>Newsletter</b> \n"+getJsObj(slot.content)["description"];
 +
break;
 +
}
 +
return preview_content;
 +
}
 +
 
 +
function APIrevisionPreviewHTML(article, download) {
 +
try{
 +
var jsObj=getJsObj(download.data);
 +
var page=anyChild(jsObj.query.pages);
 +
if( page.missing ) {
 +
// TODO we need to fix this proper later on
 +
download.owner = null;
 +
return;
 +
}
 +
var content = (
 +
page &&
 +
page.revisions
 +
) ? ContentHandler(page.revisions[0].slots.main) : null;
 +
 +
if( typeof content === 'string' )
 +
{
 +
download.data = content;
 +
download.lastModified = new Date(page.revisions[0].timestamp);
 +
download.pageprops = (page.hasOwnProperty('pageprops')) ? page.pageprops : {};
 +
}
 +
} catch(someError) {
 +
return 'Revision preview failed :(';
 
}
 
}
+
}
//else there's no section link, htmlify the whole thing.
 
return this.esWiki2HtmlPart(this.data);
 
};
 
  
//<NOLITE>
+
function APIbacklinksPreviewHTML(article, download/*, navpop*/ ) {
/** Test function for debugging preview problems one step at a time.
 
*/
 
function previewSteps(txt) {
 
 
try {
 
try {
txt=txt || document.editform.wpTextbox1.value;
+
var jsObj=getJsObj(download.data);
} catch (err) {
+
var list=jsObj.query.backlinks;
if (pg.cache.pages.length > 0) {
+
 
txt=pg.cache.pages[pg.cache.pages.length-1].data;
+
var html=[];
} else {
+
if (!list) { return popupString('No backlinks found'); }
alert('provide text or use an edit page');
+
for ( var i=0; i < list.length; i++ ) {
 +
var t=new Title(list[i].title);
 +
html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>');
 +
}
 +
html=html.join(', ');
 +
if (jsObj['continue'] && jsObj['continue'].blcontinue) {
 +
html += popupString(' and more');
 
}
 
}
 +
return html;
 +
} catch (someError) {
 +
return 'backlinksPreviewHTML went wonky';
 
}
 
}
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')) {
+
pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) {
p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; }
+
log( "APIsharedImagePagePreviewHTML" );
} else {
+
var popupid = obj.requestid;
p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; }
+
if( obj.query && obj.query.pages )
 +
{
 +
var page=anyChild(obj.query.pages );
 +
var content = (
 +
page &&
 +
page.revisions &&
 +
page.revisions[0].slots.main.contentmodel === 'wikitext'
 +
) ? page.revisions[0].slots.main.content : null;
 +
if( typeof content === 'string' )
 +
{
 +
/* 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 );
 
}
 
}
 +
}
 +
};
  
p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; }
+
function APIimagepagePreviewHTML(article, download, navpop) {
p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; }
+
try {
p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; }
+
var jsObj=getJsObj(download.data);
p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; }
+
var page=anyChild(jsObj.query.pages);
p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; }
+
var content= (
 +
page &&
 +
page.revisions &&
 +
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&rvprop=content&rvslots=main&titles=' + encart;
  
p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; }
+
ret = ret +'<hr />' + popupString( 'Image from Commons') +
p.killBadWhitespace(); if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) { return; }
+
': <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 :(';
 
}
 
}
 
p.html=wiki2html(p.data, base); // needs livepreview
 
p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; }
 
p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; }
 
alert('finished preview - end result follows.\n---\n' + p.html);
 
 
}
 
}
//</NOLITE>
 
  
/**
+
function APIimagelinksPreviewHTML(article, download) {
  Works around livepreview bugs.
+
try {
  @private
+
var jsobj=getJsObj(download.data);
*/
+
var list=jsobj.query.imageusage;
Previewmaker.prototype.fixHTML = function() {
+
if (list) {
if(!this.html) return;
+
var ret=[];
 +
for (var i=0; i < list.length; i++) {
 +
ret.push(list[i].title);
 +
}
 +
if (ret.length === 0) { return popupString('No image links found'); }
 +
return '<h2>' + popupString('File links') + '</h2>' + linkList(ret);
 +
} else {
 +
return popupString('No image links found');
 +
}
 +
} catch(someError) {
 +
return 'Image links preview generation failed :(';
 +
}
 +
}
  
  var ret = this.html;
+
function APIcategoryPreviewHTML(article, download) {
 +
try{
 +
var jsobj=getJsObj(download.data);
 +
var list=jsobj.query.categorymembers;
 +
var ret=[];
 +
for (var p=0; p < list.length; p++) {
 +
  ret.push(list[p].title);
 +
}
 +
if (ret.length === 0) { return popupString('Empty category'); }
 +
ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret);
 +
if (jsobj['continue'] && jsobj['continue'].cmcontinue) {
 +
ret += popupString(' and more');
 +
}
 +
return ret;
 +
} catch(someError) {
 +
return 'Category preview failed :(';
 +
}
 +
}
  
// fix question marks in wiki links
+
function APIuserInfoPreviewHTML(article, download) {
// maybe this'll break some stuff :-(
+
var ret=[];
ret=ret.replace(RegExp('\(<a href="' + pg.wiki.articlePath + '/[^"]*\)[?]\(.*?"\)', 'g'), '$1%3F$2');
+
var queryobj = {};
ret=ret.replace(RegExp('\(<a href=\'' + pg.wiki.articlePath + '/[^\']*\)[?]\(.*?\'\)', 'g'), '$1%3F$2');
+
try{
// FIXME fix up % too
+
queryobj=getJsObj(download.data).query;
+
} catch(someError) { return 'Userinfo preview failed :('; }
 
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.
+
var user=anyChild(queryobj.users);
  Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true.
+
if (user) {
*/
+
var globaluserinfo=queryobj.globaluserinfo;
Previewmaker.prototype.showPreview = function () {
+
if (user.invalid === '') {
this.makePreview();
+
ret.push( popupString( 'Invalid user') );
if (typeof this.html != typeof '') return;
+
} else if (user.missing === '') {
if (RegExp('^\\s*$').test(this.html)) return;
+
ret.push( popupString( 'Not a registered username') );
setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber);
+
}
setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, { owner: this.owner });
+
if( user.blockedby )
var more = (this.fullLength > this.data.length) ? this.moreLink() : '';
+
ret.push('<b>' + popupString('BLOCKED') + '</b>');
setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber);
+
if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) {
};
+
var lockedSulAccountIsAttachedToThis = true;
/**
+
for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) {
  @private
+
if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) {
*/
+
lockedSulAccountIsAttachedToThis=false;
Previewmaker.prototype.moreLink=function() {
+
break;
var a=document.createElement('a');
+
}
a.className='popupMoreLink';
+
}
a.innerHTML=popupString('more...');
+
if (lockedSulAccountIsAttachedToThis) {
var savedThis=this;
+
if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>');
a.onclick=function() {
+
if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>');
savedThis.maxCharacters+=2000;
+
}
savedThis.maxSentences+=20;
+
}
savedThis.setData();
+
if( getValueOf('popupShowGender') && user.gender ) {
savedThis.showPreview();
+
switch( user.gender ) {
 +
case "male": ret.push( popupString( "\u2642" ) ); break;
 +
case "female": ret.push( popupString( "\u2640" ) ); break;
 +
}
 +
}
 +
if( user.groups ) {
 +
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( globaluserinfo && globaluserinfo.groups ) {
 +
for( var k=0; k < globaluserinfo.groups.length; k++) {
 +
ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[k])+'</i>' );
 +
}
 +
}
 +
 +
regdate = user.registration ? user.registration : globaluserinfo.registration;
 +
ret.push( pg.escapeQuotesHTML((user.editcount ? user.editcount + popupString(' edits since: '): '0') + (regdate ? dayFormat(getDateFromTimestamp(regdate)) : '?')) );
 +
}
 +
 
 +
if (queryobj.usercontribs && queryobj.usercontribs.length) {
 +
ret.push( popupString('last edit on ') + dayFormat(getDateFromTimestamp(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>' );
 +
}
 
}
 
}
return a;
+
 +
ret = '<hr />' + ret.join( ', ' );
 +
return ret;
 +
}
 +
 
 +
function APIcontribsPreviewHTML(article, download, navpop) {
 +
return APIhistoryPreviewHTML(article, download, navpop, true);
 
}
 
}
  
/**
+
function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) {
  @private
+
try {
*/
+
var jsobj=getJsObj(download.data);
Previewmaker.prototype.stripLongTemplates = function() {
+
var edits = [];
// operates on the HTML!
+
if( reallyContribs ) {
this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), '');
+
edits=jsobj.query.usercontribs;
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');};
 
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();
 
var usernameart = encodeURIComponent( username );
 
if (pg.re.ipUser.test(username)) {
 
url += 'list=blocks&bkprop=range&bkip=' + usernameart;
 
 
} else {
 
} else {
url += 'list=users&usprop=blockinfo|groups|editcount|registration&ususers=' + usernameart + "&meta=globaluserinfo&guiprop=groups|unattached&guiuser="+ usernameart;
+
edits=anyChild(jsobj.query.pages).revisions;
 
}
 
}
htmlGenerator=APIuserInfoPreviewHTML;
+
 
break;
+
var ret=editPreviewTable(article, edits, reallyContribs, getTimeOffset());
case 'contribs':
+
return ret;
var usernameart = encodeURIComponent( new Title( article ).userName() );
+
} catch (someError) {
url += 'list=usercontribs&meta=userinfo&uiprop=options&ucuser=' + usernameart +
+
return 'History preview failed :-(';
'&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.'; };
 
 
}
 
}
 +
}
 +
 +
 +
//</NOLITE>
 +
// ENDFILE: querypreview.js
 +
// STARTFILE: debug.js
 +
////////////////////////////////////////////////////////////////////
 +
// Debugging functions
 +
////////////////////////////////////////////////////////////////////
 +
 +
function setupDebugging() {
 +
//<NOLITE>
 +
if (window.popupDebug) { // popupDebug is set from .version
 +
window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; };
 +
window.console.log(x);
 +
};
 +
window.errlog=function(x) {
 +
window.console.error(x);
 +
};
 +
log('Initializing logger');
 +
} else {
 +
//</NOLITE>
 +
window.log = function() {};
 +
window.errlog = function() {};
 +
//<NOLITE>
 +
}
 +
//</NOLITE>
 +
}
 +
// ENDFILE: debug.js
 +
// STARTFILE: images.js
 +
 +
// load image of type Title.
 +
function loadImage(image, navpop) {
 +
if (typeof image.stripNamespace != 'function') { alert('loadImages bad'); }
 +
// API call to retrieve image info.
 +
 +
if ( !getValueOf('popupImages') ) return;
 +
if ( !isValidImageName(image) ) return false;
 +
 +
var art=image.urlString();
 +
 +
var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query';
 +
url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge');
 +
url += '&titles=' + art;
 +
 +
pendingNavpopTask(navpop);
 
var callback=function(d){
 
var callback=function(d){
log( "callback of API functions was hit" );
+
popupsInsertImage(navpop.idNumber, navpop, d);
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(){
 
var go = function(){
 
getPageWithCaching(url, callback, navpop);
 
getPageWithCaching(url, callback, navpop);
 
return true;
 
return true;
}
+
};
 
if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
 
if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_'+queryType+'_QUERY_DATA'); }
+
else { navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA'); }
 +
 
 
}
 
}
  
function linkList(list) {
+
function popupsInsertImage(id, navpop, download) {
list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); });
+
log( "popupsInsertImage");
var buf=[];
+
var imageinfo;
for (var i=0; i<list.length; ++i) {
+
try {
buf.push(wikiLink({article: new Title(list[i]),
+
var jsObj=getJsObj(download.data);
  text: list[i].split(' ').join('&nbsp;'),
+
var imagepage=anyChild(jsObj.query.pages);
  action:  'view'}));
+
if (typeof imagepage.imageinfo === 'undefined') return;
 +
imageinfo = imagepage.imageinfo[0];
 +
} catch (someError) {
 +
log( "popupsInsertImage failed :(" );
 +
return;
 
}
 
}
return buf.join(', ');
 
}
 
  
function getTimeOffset(tz) {
+
var popupImage = document.getElementById("popupImg"+id);
if( tz ) {
+
if (!popupImage) {
if( tz.indexOf('|') > -1 ) {
+
log( "could not find insertion point for image");
// New format
+
return;
return parseInt(tz.split('|')[1],10);
 
} else if ( tz.indexOf(':') > -1 ) {
 
// Old format
 
return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) );
 
}
 
 
}
 
}
return 0;
 
}
 
  
function editPreviewTable(article, h, reallyContribs, timeOffset) {
+
popupImage.width=getValueOf('popupImageSize');
var html=['<table>'];
+
popupImage.style.display='inline';
var day=null;
+
 
var curart=article;
+
// Set the source for the image.
for (var i=0; i<h.length; ++i) {
+
if( imageinfo.thumburl )
if (reallyContribs) {
+
popupImage.src=imageinfo.thumburl;
var page=h[i]['title']; curart = new Title(page);
+
else if( imageinfo.mime.indexOf("image") === 0 ){
}
+
popupImage.src=imageinfo.url;
var minor=typeof h[i]['minor']=='undefined' ? '' : '<b>m </b>';
+
log( "a thumb could not be found, using original image" );
var editDate=adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset);
+
} else log( "fullsize imagethumb, but not sure if it's an image");
var thisDay = dayFormat(editDate);
+
 
var thisTime = timeFormat(editDate);
+
 
if (thisDay==day) { thisDay=''; }
+
var a=document.getElementById("popupImageLink"+id);
else { day=thisDay; }
+
if (a === null) { return null; }
if (thisDay) {
+
 
html.push( '<tr><td colspan=3><span class="popup_history_date">' +
+
// Determine the action of the surrouding imagelink.
  thisDay+'</span></td></tr>' );
+
switch (getValueOf('popupThumbAction')) {
 +
case 'imagepage':
 +
if (pg.current.article.namespaceId()!=pg.nsImageId) {
 +
a.href=imageinfo.descriptionurl;
 +
// FIXME: unreliable pg.idNumber
 +
popTipsSoonFn('popupImage' + id)();
 +
break;
 
}
 
}
html.push('<tr class="popup_history_row_' + ( (i%2) ? 'odd' : 'even') + '">');
+
/* falls through */
html.push('<td>(<a href="' + pg.wiki.titlebase + new Title(curart).urlString() +
+
case 'sizetoggle':
'&diff=prev&oldid=' + h[i]['revid'] + '">' + popupString('last') + '</a>)</td>');
+
a.onclick=toggleSize;
html.push('<td>' +
+
a.title=popupString('Toggle image size');
'<a href="' + pg.wiki.titlebase + new Title(curart).urlString() +
+
return;
'&oldid=' + h[i]['revid'] + '">' + thisTime + '</a></td>');
+
case 'linkfull':
var col3url='', col3txt='';
+
a.href = imageinfo.url;
if (!reallyContribs) {
+
a.title=popupString('Open full-size image');
var user=h[i]['user'];
+
return;
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 : '') +
 
'<a href="' + col3url + '">' + col3txt + '</a></td>');
 
var comment='';
 
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>');
 
html.push('</tr>');
 
html=[html.join('')];
 
 
}
 
}
html.push('</table>');
+
 
return html.join('');
 
 
}
 
}
  
function getDateFromTimestamp(t) {
+
// Toggles the image between inline small and navpop fullwidth.
var s=t.split(/[^0-9]/);
+
// It's the same image, no actual sizechange occurs, only display width.
switch(s.length) {
+
function toggleSize() {
case 0: return null;
+
var imgContainer=this;
case 1: return new Date(s[0]);
+
if (!imgContainer) {
case 2: return new Date(s[0], s[1]-1);
+
alert('imgContainer is null :/');
case 3: return new Date(s[0], s[1]-1, s[2]);
+
return;
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]);
+
var img=imgContainer.firstChild;
case 6: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]);
+
if (!img) {
default: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5], s[6]);
+
alert('img is null :/');
 +
return;
 +
}
 +
 
 +
if (!img.style.width || img.style.width==='') {
 +
img.style.width='100%';
 +
} else {
 +
img.style.width='';
 
}
 
}
 
}
 
}
  
function adjustDate(d, offset) {
+
// Returns one title of an image from wikiText.
// offset is in minutes
+
function getValidImageFromWikiText(wikiText) {
var o=offset * 60 * 1000;
+
// nb in pg.re.image we're interested in the second bracketed expression
return new Date( +d + o);
+
// this may change if the regex changes :-(
}
+
//var match=pg.re.image.exec(wikiText);
 +
var matched=null;
 +
var match;
 +
// strip html comments, used by evil bots :-(
 +
var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1,
 +
RegExp('^<!--[^[]*popup', 'i'));
  
function dayFormat(editDate, utc) {
+
while ( ( match = pg.re.image.exec(t) ) ) {
if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); }
+
// now find a sane image name - exclude templates by seeking {
return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-');
+
var m = match[2] || match[6];
 +
if ( isValidImageName(m) ) {
 +
matched=m;
 +
break;
 +
}
 +
}
 +
pg.re.image.lastIndex=0;
 +
if (!matched) { return null; }
 +
return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched);
 
}
 
}
  
function timeFormat(editDate, utc) {
+
function removeMatchesUnless(str, re1, parencount, re2) {
if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); }
+
var split=str.parenSplit(re1);
return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':');
+
var c=parencount + 1;
 +
for (var i=0; i<split.length; ++i) {
 +
if ( i%c === 0 || re2.test(split[i]) ) { continue; }
 +
split[i]='';
 +
}
 +
return split.join('');
 
}
 
}
  
function showAPIPreview(queryType, html, id, navpop, download) {
+
//</NOLITE>
// DJ: done
+
// ENDFILE: images.js
var target='popupPreview';
+
// STARTFILE: namespaces.js
switch (queryType) {
+
// Set up namespaces and other non-strings.js localization
case 'imagelinks':
+
// (currently that means redirs too)
case 'category':
 
case 'userinfo':
 
target='popupPostPreview'; break;
 
}
 
setPopupTipsAndHTML(html, target, id);
 
completedNavpopTask(navpop);
 
}
 
  
function APIbacklinksPreviewHTML(article, download, navpop) {
+
function setNamespaces() {
try {
+
pg.nsSpecialId  = -1;
var jsObj=getJsObj(download.data);
+
pg.nsMainspaceId = 0;
  var list=jsObj.query.backlinks;
+
pg.nsImageId    = 6;
} catch (someError) { return 'backlinksPreviewHTML went wonky'; }
+
pg.nsUserId      = 2;
var html=[];
+
pg.nsUsertalkId  = 3;
if (!list) { return popupString('No backlinks found'); }
+
pg.nsCategoryId  = 14;
for ( var i=0; i < list.length; i++ ) {
+
pg.nsTemplateId  = 10;
var t=new Title(list[i]['title']);
 
html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>');
 
}
 
html=html.join(', ');
 
if (jsObj['query-continue'] && jsObj['query-continue'].backlinks && jsObj['query-continue'].backlinks.blcontinue) {
 
html += popupString(' and more');
 
}
 
return html;
 
 
}
 
}
  
function APIsharedImagePagePreviewHTML(obj) {
 
log( "APIsharedImagePagePreviewHTML" );
 
var popupid = obj['requestid'];
 
if( obj['query'] && obj['query']['pages'] )
 
{
 
var page=anyChild(obj['query']['pages']);
 
var content=(page && page.revisions ) ? page.revisions[0]['*'] : null;
 
if( content )
 
{
 
/* Not entirely safe, but the best we can do */
 
var p=new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup);
 
p.makePreview();
 
setPopupHTML( p.html, "popupSecondPreview", popupid );
 
}
 
}
 
}
 
  
function APIimagepagePreviewHTML(article, download, navpop) {
+
function setRedirs() {
try {
+
var r='redirect';
var jsObj=getJsObj(download.data);
+
var R='REDIRECT';
var page=anyChild(jsObj.query.pages);
+
var redirLists={
var content=(page && page.revisions ) ? page.revisions[0]['*'] : null;
+
//<NOLITE>
} catch (someError) {
+
'ar':  [ R, 'تحويل' ],
return 'API imagepage preview failed :(';
+
'be':  [ r, 'перанакіраваньне' ],
}
+
'bg': [ r, 'пренасочване', 'виж' ],
var ret='';
+
'bs':  [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ],
var alt='';
+
'cs': [ R, 'PŘESMĚRUJ' ],
try{alt=navpop.parentAnchor.childNodes[0].alt;} catch(e){}
+
'cy':  [ r, 'ail-cyfeirio' ],
if (alt) {
+
'de':  [ R, 'WEITERLEITUNG' ],
ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt);
+
'el'[ R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'],
}
+
'eo':  [ R, 'ALIDIREKTU', 'ALIDIREKTI' ],
if (content) {
+
'es':  [ R, 'REDIRECCIÓN' ],
var p=prepPreviewmaker(content, article, navpop);
+
'et':  [ r, 'suuna' ],
p.makePreview();
+
'ga':  [ r, 'athsheoladh' ],
if (p.html) { ret += '<hr />' + p.html; }
+
'gl':  [ r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'],
}
+
'he':  [ R, 'הפניה' ],
if (content!==null && getValueOf('popupSummaryData')) {
+
'hu':  [ R, 'ÁTIRÁNYÍTÁS' ],
var info=getPageInfo(content, download);
+
'is':  [ r, 'tilvísun', 'TILVÍSUN' ],
log(info);
+
'it':  [ R, 'RINVIA', 'Rinvia'],
setPopupTrailer(info, navpop.idNumber);
+
'ja':  [ R, '転送' ],
}
+
'mk':  [ r, 'пренасочување', 'види' ],
if (page && page.imagerepository == "shared" ) {
+
'nds': [ r, 'wiederleiden' ],
var art=new Title(article);
+
'nl':  [ R, 'DOORVERWIJZING' ],
var encart = encodeURIComponent( "File:" + art.stripNamespace() );
+
'nn':  [ r, 'omdiriger' ],
var shared_url = pg.wiki.apicommonsbase + '?format=json&callback=APIsharedImagePagePreviewHTML' +
+
'pl': [ R, 'PATRZ', 'PRZEKIERUJ', 'TAM' ],
'&requestid=' + navpop.idNumber +
+
'pt': [ R, 'redir' ],
'&action=query&prop=revisions&rvprop=content&titles=' + encart;
+
'ru':  [ R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР' ],
if (pg.flag.isIE) {
+
'sk': [ r, 'presmeruj' ],
shared_url = shared_url + '&*'; //to circumvent https://bugzilla.wikimedia.org/show_bug.cgi?id=28840
+
'sr':  [ r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI' ],
}
+
'tt':  [ R, 'yünältü', 'перенаправление', 'перенапр' ],
 
+
'uk': [ R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР' ],
ret = ret +'<hr />' + popupString( 'Image from Commons') +
+
'vi':  [ r, 'đổi' ],
': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' +
+
'zh':  [ R, '重定向'] // no comma
popupString( 'Description page') + '</a>';
+
//</NOLITE>
mw.loader.load( shared_url );
+
};
}
+
var redirList=redirLists[ pg.wiki.lang ] || [r, R];
showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download);
+
// Mediawiki is very tolerant about what comes after the #redirect at the start
return ret;
+
pg.re.redirect=RegExp('^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)', 'i');
 
}
 
}
  
function APIimagelinksPreviewHTML(article, download) {
+
function setInterwiki() {
try {
+
if (pg.wiki.wikimedia) {
var jsobj=getJsObj(download.data);
+
// From https://meta.wikimedia.org/wiki/List_of_Wikipedias
var list=jsobj.query.imageusage;
+
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';
if (!list) { return popupString('No image links found'); }
+
pg.re.interwiki=RegExp('^'+pg.wiki.interwiki+':');
} catch(someError) { return 'Image links preview generation failed :('; }
+
} else {
var ret=[];
+
pg.wiki.interwiki=null;
for (var i=0; i < list.length; i++) {
+
pg.re.interwiki=RegExp('^$');
ret.push(list[i]['title']);
 
 
}
 
}
if (ret.length === 0) { return popupString('No image links found'); }
 
return '<h2>' + popupString('File links') + '</h2>' + linkList(ret);
 
 
}
 
}
  
function APIcategoryPreviewHTML(article, download) {
+
// return a regexp pattern matching all variants to write the given namespace
try{
+
function nsRe(namespaceId) {
var jsobj=getJsObj(download.data);
+
var imageNamespaceVariants = [];
var list=jsobj.query.categorymembers;
+
jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
} catch(someError) { return 'Category preview failed :('; }
+
if (_namespaceId!=namespaceId) return;
var ret=[];
+
_localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc);
for (var p=0; p < list.length; p++) {
+
imageNamespaceVariants.push(mw.RegExp.escape(_localizedNamespaceLc).split(' ').join('[ _]'));
  ret.push(list[p]['title']);
+
imageNamespaceVariants.push(mw.RegExp.escape(encodeURI(_localizedNamespaceLc)));
}
+
});
if (ret.length === 0) { return popupString('Empty category'); }
+
 
ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret);
+
return '(?:' + imageNamespaceVariants.join('|') + ')';
if (jsobj['query-continue'] && jsobj['query-continue'].categorymembers && jsobj['query-continue'].categorymembers.cmcontinue) {
 
ret += popupString(' and more');
 
}
 
return ret;
 
 
}
 
}
  
function APIuserInfoPreviewHTML(article, download) {
+
function nsReImage() {
var ret=[];
+
return nsRe(pg.nsImageId);
try{
+
}
var queryobj=getJsObj(download.data).query;
+
// ENDFILE: namespaces.js
} catch(someError) { return 'Userinfo preview failed :('; }
+
// STARTFILE: selpop.js
+
//<NOLITE>
var user=anyChild(queryobj.users);
+
function getEditboxSelection() {
if (user) {
+
// see http://www.webgurusforum.com/8/12/0
var globaluserinfo=queryobj.globaluserinfo;
+
var editbox;
if (user.invalid == '') {
+
try {
ret.push( popupString( 'Invalid user') );
+
editbox=document.editform.wpTextbox1;
} else if (user.missing == '') {
+
} catch (dang) { return; }
ret.push( popupString( 'Not a registered username') );
+
// IE, Opera
}
+
if (document.selection) { return document.selection.createRange().text; }
if( user.blockedby )
+
// Mozilla
ret.push('<b>' + popupString('BLOCKED') + '</b>');
+
var selStart = editbox.selectionStart;
if( globaluserinfo && (globaluserinfo.locked != null || globaluserinfo.hidden != null) ) {
+
var selEnd = editbox.selectionEnd;
var lockedSulAccountIsAttachedToThis = true;
+
return (editbox.value).substring(selStart, selEnd);
for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) {
+
}
if ( globaluserinfo.unattached[i].wiki===mw.config.get('wgDBname') ) { lockedSulAccountIsAttachedToThis=false; break; }
+
 
}
+
function doSelectionPopup() {
if (lockedSulAccountIsAttachedToThis) {
+
// popup if the selection looks like [[foo|anything afterwards at all
if (globaluserinfo.locked != null) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>');
+
// or [[foo|bar]]text without ']]'
if (globaluserinfo.hidden != null) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>');
+
// or [[foo|bar]]
}
+
var sel=getEditboxSelection();
}
+
var open=sel.indexOf('[[');
for( var i=0; (user.groups && i < user.groups.length); i++) {
+
var pipe=sel.indexOf('|');
switch (user.groups[i]) {
+
var close=sel.indexOf(']]');
case '*':
+
if (open == -1 || ( pipe == -1 && close == -1) ) { return; }
case 'user':
+
if (pipe != -1 && open > pipe || close != -1 && open > close) { return; }
case 'autoconfirmed':
+
if (getValueOf('popupOnEditSelection')=='boxpreview') {
break;
+
return doSeparateSelectionPopup(sel);
default:
+
}
ret.push( pg.escapeQuotesHTML(user.groups[i]) );
+
var article=new Title(sel.substring(open+2, (pipe < 0) ? close : pipe)).urlString();
}
+
if (close > 0 && sel.substring(close+2).indexOf('[[') >= 0) {
}
+
return;  
for( var i=0; (globaluserinfo && globaluserinfo.groups && i < globaluserinfo.groups.length); i++) {
 
ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[i])+'</i>' );
 
}
 
if( user.editcount || user.registration )
 
ret.push( pg.escapeQuotesHTML((user.editcount?user.editcount:'') + popupString(' edits since: ') + (user.registration?dayFormat(getDateFromTimestamp(user.registration)):'')) );
 
 
}
 
}
+
var a=document.createElement('a');
if (queryobj.blocks) {
+
a.href=pg.wiki.titlebase + article;
ret.push( popupString( 'IP user') ); //we only request list=blocks for IPs
+
mouseOverWikiLink2(a);
for (var i=0; i<queryobj.blocks.length; i++) {
+
if (a.navpopup) {
ret.push('<b>' + popupString(queryobj.blocks[i].rangestart===queryobj.blocks[i].rangeend ? 'BLOCKED' : 'RANGEBLOCKED') + '</b>' );
+
a.navpopup.addHook(function(){runStopPopupTimer(a.navpopup);}, 'unhide', 'after');
}
 
 
}
 
}
 
ret = '<hr />' + ret.join( ', ' );
 
return ret;
 
 
}
 
}
  
function APIcontribsPreviewHTML(article, download, navpop) {
+
function doSeparateSelectionPopup(str) {
return APIhistoryPreviewHTML(article, download, navpop, true);
+
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;
 +
}
 +
}
 +
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}.
  
function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) {
+
  <code>Navpopup</code> describes popups: when they appear, where, what
try {
+
  they look like and so on.
var jsobj=getJsObj(download.data);
 
var tz=jsobj.query.userinfo.options.timecorrection;
 
if( reallyContribs )
 
var edits=jsobj.query.usercontribs;
 
else
 
var edits=anyChild(jsobj.query.pages)['revisions'];
 
} catch (someError) {
 
return 'History preview failed :-(';
 
}
 
var timeOffset = getTimeOffset(tz);
 
Cookie.create('popTz', timeOffset, 1);
 
  
var ret=editPreviewTable(article, edits, reallyContribs, timeOffset);
+
  <code>Mousetracker</code> "captures" the mouse using
return ret;
+
  <code>document.onmousemove</code>.
}
+
*/
  
  
//</NOLITE>
+
/**
// ENDFILE: querypreview.js
+
  Creates a new Mousetracker.
// STARTFILE: debug.js
+
  @constructor
////////////////////////////////////////////////////////////////////
+
  @class The Mousetracker class. This monitors mouse movements and manages associated hooks.
// Debugging functions
+
*/
////////////////////////////////////////////////////////////////////
+
function Mousetracker() {
 +
/**
 +
  Interval to regularly run the hooks anyway, in milliseconds.
 +
  @type Integer
 +
*/
 +
this.loopDelay=400;
  
function log(){}; // dummy to stop errors
+
/**
function setupDebugging() {
+
  Timer for the loop.
//<NOLITE>
+
  @type Timer
if (window.popupDebug) { // popupDebug is set from .version
+
*/
window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; };
+
this.timer=null;
window.console.log(x);
+
 
}
+
/**
window.errlog=function(x) {
+
  Flag - are we switched on?
window.console.error(x);
+
  @type Boolean
}
+
*/
log('Initializing logger');
+
this.active=false;
} else {
+
/**
//</NOLITE>
+
  Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position?
window.log = function(x) {};
+
*/
window.errlog = function(x) {};
+
this.dirty=true;
//<NOLITE>
+
/**
}
+
  Array of hook functions.
//</NOLITE>
+
  @private
 +
  @type Array
 +
*/
 +
this.hooks=[];
 
}
 
}
// ENDFILE: debug.js
 
// STARTFILE: images.js
 
  
// load image of type Title.
+
/**
function loadImage(image, navpop) {
+
  Adds a hook, to be called when we get events.
if (typeof image.stripNamespace != 'function') { alert('loadImages bad'); }
+
  @param {Function} f A function which is called as
// API call to retrieve image info.
+
  <code>f(x,y)</code>. It should return <code>true</code> when it
 +
  wants to be removed, and <code>false</code> otherwise.
 +
*/
 +
Mousetracker.prototype.addHook = function (f) {
 +
this.hooks.push(f);
 +
};
  
if ( !getValueOf('popupImages') || !mw.config.get('wgEnableAPI') ) return;
+
/**
if ( !isValidImageName(image) ) return false;
+
  Runs hooks, passing them the x
+
  and y coords of the mouse.  Hook functions that return true are
var art=image.urlString();
+
  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;
  
var url=pg.wiki.apiwikibase + '?format=json&action=query';
+
for (var i=0; i<len; ++i) {
url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge');;
+
//~ run the hook function, and remove it if it returns true
url += '&titles=' + art;
+
if (this.hooks[i](x, y)===true) {
if (pg.flag.isIE) {
+
remove=true;
url = url + '&*'; //to circumvent https://bugzilla.wikimedia.org/show_bug.cgi?id=28840
+
removeObj[i]=true;
 +
}
 
}
 
}
 +
if (remove) { this.removeHooks(removeObj); }
 +
};
  
pendingNavpopTask(navpop);
+
/**
var callback=function(d){
+
  Removes hooks.
popupsInsertImage(navpop.idNumber, navpop, d);
+
  @private
};
+
  @param {Object} removeObj An object whose keys are the index
var go = function(){
+
  numbers of functions for removal, with values that evaluate to true
getPageWithCaching(url, callback, navpop);
+
*/
return 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]); }
 
}
 
}
if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
+
this.hooks=newHooks;
else { navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA'); }
+
};
  
}
 
  
function popupsInsertImage(id, navpop, download) {
+
/**
log( "popupsInsertImage");
+
  Event handler for mouse wiggles.
try {
+
  We simply grab the event, set x and y and run the hooks.
var jsObj=getJsObj(download.data);
+
  This makes the cpu all hot and bothered :-(
var imagepage=anyChild(jsObj.query.pages);
+
  @private
if (typeof imagepage.imageinfo === 'undefined') return;
+
  @param {Event} e Mousemove event
var imageinfo = imagepage.imageinfo[0];
+
*/
} catch (someError) {
+
Mousetracker.prototype.track=function (e) {
log( "popupsInsertImage failed :(" );
+
//~ Apparently this is needed in IE.
return;
+
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;
  
var popupImage = document.getElementById("popupImg"+id);
+
if (docElt) { left=docElt.scrollLeft; }
if (!popupImage) {
+
left = left || document.body.scrollLeft || document.scrollLeft || 0;
log( "could not find insertion point for image");
 
return;
 
}
 
  
popupImage.width=getValueOf('popupImageSize');
+
if (docElt) { top=docElt.scrollTop; }
popupImage.style.display='inline';
+
top = top || document.body.scrollTop || document.scrollTop || 0;
  
// Set the source for the image.
+
x=e.clientX + left;
if( imageinfo.thumburl )
+
y=e.clientY + top;
popupImage.src=imageinfo.thumburl;
+
} else { return; }
else if( imageinfo.mime.indexOf("image") == 0 ){
+
this.setPosition(x,y);
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");
 
  
 +
/**
 +
  Sets the x and y coordinates stored and takes appropriate action,
 +
  running hooks as appropriate.
 +
  @param {Integer} x, y Screen coordinates to set
 +
*/
  
var a=document.getElementById("popupImageLink"+id);
+
Mousetracker.prototype.setPosition=function(x,y) {
if (a === null) { return null; }
+
this.x = x;
 
+
this.y = y;
// Determine the action of the surrouding imagelink.
+
if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; }
switch (getValueOf('popupThumbAction')) {
+
if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; }
case 'imagepage':
+
var diff = (this.lastHook_x - x)*(this.lastHook_y - y);
if (pg.current.article.namespaceId()!=pg.nsImageId) {
+
diff = (diff >= 0) ? diff : -diff;
a.href=imageinfo.descriptionurl;
+
if ( diff > 1 ) {
// FIXME: unreliable pg.idNumber
+
this.lastHook_x=x;
popTipsSoonFn('popupImage' + id)();
+
this.lastHook_y=y;
break;
+
if (this.dirty) { this.dirty = false; }
} // else fall through
+
else { this.runHooks(); }
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;
 
 
}
 
}
 +
};
  
}
+
/**
 +
  Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>.
 +
  A half-hearted attempt is made to preserve the old event handler if there is one.
 +
*/
 +
Mousetracker.prototype.enable = function () {
 +
if (this.active) { return; }
 +
this.active=true;
 +
//~ Save the current handler for mousemove events. This isn't too
 +
//~ robust, of course.
 +
this.savedHandler=document.onmousemove;
 +
//~ Gotta save @tt{this} again for the closure, and use apply for
 +
//~ the member function.
 +
var savedThis=this;
 +
document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);};
 +
if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working');
 +
savedThis.runHooks();}, this.loopDelay); }
 +
};
  
// Toggles the image between inline small and navpop fullwidth.
+
/**
// It's the same image, no actual sizechange occurs, only display width.
+
  Disables the tracker, removing the event handler.
function toggleSize() {
+
*/
var imgContainer=this;
+
Mousetracker.prototype.disable = function () {
if (!imgContainer) { alert('imgContainer is null :/'); return;}
+
if (!this.active) { return; }
img=imgContainer.firstChild;
+
if ($.isFunction(this.savedHandler)) {
if (!img) { alert('img is null :/'); return;}
+
document.onmousemove=this.savedHandler;
if (!img.style.width || img.style.width=='') { img.style.width='100%'; }
+
} else { delete document.onmousemove; }
else { img.style.width=''; }
+
if (this.timer) { clearInterval(this.timer); }
}
+
this.active=false;
 
+
};
// Returns one title of an image from wikiText.
 
function getValidImageFromWikiText(wikiText) {
 
// nb in pg.re.image we're interested in the second bracketed expression
 
// this may change if the regex changes :-(
 
//var match=pg.re.image.exec(wikiText);
 
var matched=null;
 
var match;
 
// strip html comments, used by evil bots :-(
 
var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1,
 
RegExp('^<!--[^[]*popup', 'i'));
 
 
 
while ( match = pg.re.image.exec(t) ) {
 
// now find a sane image name - exclude templates by seeking {
 
var m = match[2] || match[6];
 
if ( isValidImageName(m) ) {
 
matched=m;
 
break;
 
}
 
}
 
pg.re.image.lastIndex=0;
 
if (!matched) { return null; }
 
return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched);
 
}
 
 
 
function removeMatchesUnless(str, re1, parencount, re2) {
 
var split=str.parenSplit(re1);
 
var c=parencount + 1;
 
for (var i=0; i<split.length; ++i) {
 
if ( i%c === 0 || re2.test(split[i]) ) { continue; }
 
split[i]='';
 
}
 
return split.join('');
 
}
 
 
 
//</NOLITE>
 
// ENDFILE: images.js
 
// STARTFILE: namespaces.js
 
// Set up namespaces and other non-strings.js localization
 
// (currently that means redirs too)
 
 
 
 
 
function namespaceListToRegex(list) {return RegExp('^('+list.join('|').split(' ').join('[ _]')+'):');};
 
 
 
function setNamespaces() {
 
pg.nsSpecialId  = -1;
 
pg.nsMainspaceId  = 0;
 
pg.nsImageId = 6;
 
pg.nsUserId   = 2;
 
pg.nsUsertalkId  = 3;
 
pg.nsCategoryId  = 14;
 
pg.nsTemplateId  = 10;
 
}
 
  
 +
/**
 +
  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.
 +
  @type boolean
 +
  @private
 +
*/
 +
this.visible=false;
 +
/** Flag to be set when we want to cancel a previous request to
 +
show the popup in a little while.
 +
@private
 +
@type boolean
 +
*/
 +
this.noshow=false;
 +
/** Categorised list of hooks.
 +
@see #runHooks
 +
@see #addHook
 +
@private
 +
@type Object
 +
*/
 +
this.hooks={
 +
'create': [],
 +
'unhide': [],
 +
'hide': []
 +
};
 +
/** list of unique IDs of hook functions, to avoid duplicates
 +
@private
 +
*/
 +
this.hookIds={};
 +
/** List of downloads associated with the popup.
 +
@private
 +
@type Array
 +
*/
 +
this.downloads=[];
 +
/** Number of uncompleted downloads.
 +
@type 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();
  
function setRedirs() {
+
// if (!init || typeof init.popups_draggable=='undefined' || init.popups_draggable) {
var r='redirect';
+
// this.makeDraggable(true);
var R='REDIRECT';
+
// }
var redirLists={
+
}
//<NOLITE>
+
 
'ar':  [ R, 'تحويل' ],
+
/**
'be':  [ r, 'перанакіраваньне' ],
+
  A UID for each Navpopup. This constructor property is just a counter.
'bg':  [ r, 'пренасочване', 'виж' ],
+
  @type integer
'bs':  [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ],
+
  @private
'cs':  [ R, 'PŘESMĚRUJ' ],
+
*/
'cy':  [ r, 'ail-cyfeirio' ],
+
Navpopup.uid=0;
'de':  [ R, 'WEITERLEITUNG' ],
+
 
'el':  [ R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'],
+
/**
'eo':  [ R, 'ALIDIREKTU', 'ALIDIREKTI' ],
+
  Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible.
'es':  [ R, 'REDIRECCIÓN' ],
+
  @type boolean
'et':  [ r, 'suuna' ],
+
*/
'ga':  [ r, 'athsheoladh' ],
+
Navpopup.prototype.isVisible=function() {
'gl':  [ r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'],
+
return this.visible;
'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() {
+
/**
if (pg.wiki.wikimedia) {
+
  Repositions popup using CSS style.
// From http://meta.wikimedia.org/wiki/List_of_Wikipedias
+
  @private
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';
+
  @param {integer} x x-coordinate (px)
pg.re.interwiki=RegExp('^'+pg.wiki.interwiki+':');
+
  @param {integer} y y-coordinate (px)
} else {
+
  @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition}
pg.wiki.interwiki=null;
+
*/
pg.re.interwiki=RegExp('^$');
+
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 + ')');
 +
};
  
// return a regexp pattern matching all variants to write the given namespace
+
/**
function nsRe(namespaceId) {
+
  Prevents popups from being in silly locations. Hopefully.
var imageNamespaceVariants = [];
+
  Should not be run if {@link #constrained} is true.
jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
+
  @private
if (_namespaceId!=namespaceId) return;
+
*/
//todo: escape regexp fragments!
+
Navpopup.prototype.limitHorizontalPosition=function() {
_localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc);
+
if (!this.constrained || this.tooWide) { return; }
imageNamespaceVariants.push(_localizedNamespaceLc.split(' ').join('[ _]'));
+
this.updateDimensions();
imageNamespaceVariants.push(encodeURI(_localizedNamespaceLc));
+
var x=this.left;
});
+
var w=this.width;
 +
var cWidth=document.body.clientWidth;
  
return '(?:' + imageNamespaceVariants.join('|') + ')';
 
}
 
  
function nsReImage() {
+
// log('limitHorizontalPosition: x='+x+
return nsRe(pg.nsImageId);
+
// ', this.left=' + this.left +
}
+
// ', this.width=' + this.width +
 +
// ', cWidth=' + cWidth);
  
// ENDFILE: namespaces.js
 
// STARTFILE: selpop.js
 
//<NOLITE>
 
function getEditboxSelection() {
 
// see http://www.webgurusforum.com/8/12/0
 
try {
 
var 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() {
+
if ( (x+w) >= cWidth ||
// popup if the selection looks like [[foo|anything afterwards at all
+
( x > 0 &&
// or [[foo|bar]]text without ']]'
+
this.maxWidth &&
// or [[foo|bar]]
+
this.width < this.maxWidth &&
var sel=getEditboxSelection();
+
this.height > this.width &&
var open=sel.indexOf('[[');
+
x > cWidth - this.maxWidth ) ) {
var pipe=sel.indexOf('|');
+
// This is a very nasty hack. There has to be a better way!
var close=sel.indexOf(']]');
+
// We find the "natural" width of the div by positioning it at the far left
if (open == -1 || ( pipe == -1 && close == -1) ) { return; }
+
// then reset it so that it should be flush right (well, nearly)
if (pipe != -1 && open > pipe || close != -1 && open > close) { return; }
+
this.mainDiv.style.left='-10000px';
if (getValueOf('popupOnEditSelection')=='boxpreview') {
+
this.mainDiv.style.width = this.maxWidth + 'px';
return doSeparateSelectionPopup(sel);
+
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);
 
}
 
}
var article=new Title(sel.substring(open+2, (pipe < 0) ? close : pipe)).urlString();
+
};
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) {
 
var div=document.getElementById('selectionPreview');
 
if (!div) {
 
div = document.createElement('div');
 
div.id='selectionPreview';
 
try { var box=document.editform.wpTextbox1; }
 
catch (oopsie) { return; }
 
box.parentNode.insertBefore(div, box);
 
}
 
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}.
+
   Counter indicating the z-order of the "highest" popup.
 
+
   We start the z-index at 1000 so that popups are above everything
   <code>Navpopup</code> describes popups: when they appear, where, what
+
   else on the screen.
   they look like and so on.
+
  @private
 +
  @type integer
 +
*/
 +
Navpopup.highest=1000;
  
   <code>Mousetracker</code> "captures" the mouse using
+
/**
   <code>document.onmousemove</code>.
+
   Brings popup to the top of the z-order.
 +
   We increment the {@link #highest} property of the contructor here.
 +
  @private
 
*/
 
*/
 +
Navpopup.prototype.raise = function () {
 +
this.mainDiv.style.zIndex=Navpopup.highest + 1;
 +
++Navpopup.highest;
 +
};
  
 +
/**
 +
  Shows the popup provided {@link #noshow} is not true.
 +
  Updates the position, brings the popup to the top of the z-order and unhides it.
 +
*/
 +
Navpopup.prototype.show = function () {
 +
//document.title+='s';
 +
if (this.noshow) { return; }
 +
//document.title+='t';
 +
this.reposition();
 +
this.raise();
 +
this.unhide();
 +
};
  
 
/**
 
/**
   Creates a new Mousetracker.
+
   Checks to see if the mouse pointer has
   @constructor
+
  stabilised (checking every <code>time</code>/2 milliseconds) and runs the
   @class The Mousetracker class. This monitors mouse movements and manages associated hooks.
+
   {@link #show} method if it has.
 +
   @param {integer} time The minimum time (ms) before the popup may be shown.
 
*/
 
*/
function Mousetracker() {
+
Navpopup.prototype.showSoonIfStable = function (time) {
/**
+
log ('showSoonIfStable, time='+time);
  Interval to regularly run the hooks anyway, in milliseconds.
+
if (this.visible) { return; }
  @type Integer
+
this.noshow = false;
*/
+
 
this.loopDelay=400;
+
//~ initialize these variables so that we never run @tt{show} after
 +
//~ just half the time
 +
this.stable_x = -10000; this.stable_y = -10000;
  
/**
+
var stableShow = function() {
  Timer for the loop.
+
log('stableShow called');
  @type Timer
+
var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y;
*/
+
var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y;
this.timer=null;
+
var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz;
 
+
//document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] ';
/**
+
if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) {
  Flag - are we switched on?
+
log ('mouse is stable');
  @type Boolean
+
clearInterval(savedThis.showSoonStableTimer);
*/
+
savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]);
this.active=false;
+
savedThis.show.apply(savedThis, []);
/**
+
savedThis.limitHorizontalPosition.apply(savedThis, []);
  Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position?
+
return;
*/
+
}
this.dirty=true;
+
savedThis.stable_x = new_x; savedThis.stable_y = new_y;
/**
+
};
  Array of hook functions.
+
var savedThis = this;
  @private
+
this.showSoonStableTimer = setInterval(stableShow, time/2);
  @type Array
+
};
*/
 
this.hooks=[];
 
}
 
  
 
/**
 
/**
   Adds a hook, to be called when we get events.
+
   Sets the {@link #noshow} flag and hides the popup. This should be called
   @param {Function} f A function which is called as
+
   when the mouse leaves the link before
   <code>f(x,y)</code>. It should return <code>true</code> when it
+
   (or after) it's actually been displayed.
  wants to be removed, and <code>false</code> otherwise.
 
 
*/
 
*/
Mousetracker.prototype.addHook = function (f) {
+
Navpopup.prototype.banish = function () {
this.hooks.push(f);
+
log ('banish called');
};
+
// hide and prevent showing with showSoon in the future
 
+
this.noshow=true;
/**
+
if (this.showSoonStableTimer) {
   Runs hooks, passing them the x
+
log('clearing showSoonStableTimer');
  and y coords of the mouse.  Hook functions that return true are
+
clearInterval(this.showSoonStableTimer);
  passed to {@link Mousetracker#removeHooks} for removal.
+
}
 +
this.hide();
 +
};
 +
 
 +
/**
 +
   Runs hooks added with {@link #addHook}.
 
   @private
 
   @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'
 
*/
 
*/
Mousetracker.prototype.runHooks = function () {
+
Navpopup.prototype.runHooks = function (key, when) {
if (!this.hooks || !this.hooks.length) { return; }
+
if (!this.hooks[key]) { return; }
//log('Mousetracker.runHooks; we got some hooks to run');
+
var keyHooks=this.hooks[key];
var remove=false;
+
var len=keyHooks.length;
var removeObj={};
+
for (var i=0; i< len; ++i) {
// this method gets called a LOT -
+
if (keyHooks[i] && keyHooks[i].when == when) {
// pre-cache some variables
+
if (keyHooks[i].hook.apply(this, [])) {
var x=this.x, y=this.y, len = this.hooks.length;
+
// remove the hook
 
+
if (keyHooks[i].hookId) {
for (var i=0; i<len; ++i) {
+
delete this.hookIds[keyHooks[i].hookId];
//~ run the hook function, and remove it if it returns true
+
}
if (this.hooks[i](x, y)===true) {
+
keyHooks[i]=null;
remove=true;
+
}
removeObj[i]=true;
 
 
}
 
}
 
}
 
}
if (remove) { this.removeHooks(removeObj); }
 
 
};
 
};
  
 
/**
 
/**
   Removes hooks.
+
   Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments.
   @private
+
   @param {Function} hook The hook function. Functions that return true are deleted.
   @param {Object} removeObj An object whose keys are the index
+
   @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
   numbers of functions for removal, with values that evaluate to true
+
  @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.
 
*/
 
*/
Mousetracker.prototype.removeHooks = function(removeObj) {
+
Navpopup.prototype.addHook = function ( hook, key, when, uid ) {
var newHooks=[];
+
when = when || 'after';
var len = this.hooks.length;
+
if (!this.hooks[key]) { return; }
for (var i=0; i<len; ++i) {
+
// if uid is specified, don't add duplicates
if (! removeObj[i]) { newHooks.push(this.hooks[i]); }
+
var hookId=null;
 +
if (uid) {
 +
hookId=[key,when,uid].join('|');
 +
if (this.hookIds[hookId]) {
 +
return;
 +
}
 +
this.hookIds[hookId]=true;
 
}
 
}
this.hooks=newHooks;
+
this.hooks[key].push( {hook: hook, when: when, hookId: hookId} );
 
};
 
};
 
  
 
/**
 
/**
   Event handler for mouse wiggles.
+
   Creates the main DIV element, which contains all the actual popup content.
  We simply grab the event, set x and y and run the hooks.
+
   Runs hooks with key 'create'.
   This makes the cpu all hot and bothered :-(
 
 
   @private
 
   @private
  @param {Event} e Mousemove event
 
 
*/
 
*/
Mousetracker.prototype.track=function (e) {
+
Navpopup.prototype.createMainDiv = function () {
//~ Apparently this is needed in IE.
+
if (this.mainDiv) { return; }
e = e || window.event;
+
this.runHooks('create', 'before');
var x, y;
+
var mainDiv=document.createElement('div');
if (e) {
 
if (e.pageX) { x=e.pageX; y=e.pageY; }
 
else if (typeof e.clientX!='undefined') {
 
var left, top, docElt = window.document.documentElement;
 
  
if (docElt) { left=docElt.scrollLeft; }
+
var savedThis=this;
left = left || window.document.body.scrollLeft || window.document.scrollLeft || 0;
+
mainDiv.onclick=function(e) {savedThis.onclickHandler(e);};
 +
mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv';
 +
mainDiv.id=mainDiv.className + this.uid;
  
if (docElt) { top=docElt.scrollTop; }
+
mainDiv.style.position='absolute';
top = top || window.document.body.scrollTop || window.document.scrollTop || 0;
+
mainDiv.style.minWidth = '350px';
 +
mainDiv.style.display='none';
 +
mainDiv.className='navpopup';
  
x=e.clientX + left;
+
// easy access to javascript object through DOM functions
y=e.clientY + top;
+
mainDiv.navpopup=this;
} else { return; }
+
 
this.setPosition(x,y);
+
this.mainDiv=mainDiv;
}
+
document.body.appendChild(mainDiv);
 +
this.runHooks('create', 'after');
 
};
 
};
 
 
/**
 
/**
   Sets the x and y coordinates stored and takes appropriate action,
+
   Calls the {@link #raise} method.
  running hooks as appropriate.
+
   @private
   @param {Integer} x, y Screen coordinates to set
 
 
*/
 
*/
 
+
Navpopup.prototype.onclickHandler=function(/*e*/) {
Mousetracker.prototype.setPosition=function(x,y) {
+
this.raise();
this.x = x;
 
this.y = y;
 
if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; }
 
if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; }
 
var diff = (this.lastHook_x - x)*(this.lastHook_y - y);
 
diff = (diff >= 0) ? diff : -diff;
 
if ( diff > 1 ) {
 
this.lastHook_x=x;
 
this.lastHook_y=y;
 
if (this.dirty) { this.dirty = false; }
 
else { this.runHooks(); }
 
}
 
}
 
 
 
/**
 
  Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>.
 
  A half-hearted attempt is made to preserve the old event handler if there is one.
 
*/
 
Mousetracker.prototype.enable = function () {
 
if (this.active) { return; }
 
this.active=true;
 
//~ Save the current handler for mousemove events. This isn't too
 
//~ robust, of course.
 
this.savedHandler=document.onmousemove;
 
//~ Gotta save @tt{this} again for the closure, and use apply for
 
//~ the member function.
 
var savedThis=this;
 
document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);};
 
if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working');
 
savedThis.runHooks();}, this.loopDelay); }
 
 
};
 
};
 
 
/**
 
/**
   Disables the tracker, removing the event handler.
+
   Makes the popup draggable, using a {@link Drag} object.
 +
  @private
 
*/
 
*/
Mousetracker.prototype.disable = function () {
+
Navpopup.prototype.makeDraggable=function(handleName) {
if (!this.active) { return; }
+
if (!this.mainDiv) { this.createMainDiv(); }
if ($.isFunction(this.savedHandler)) {
+
var drag=new Drag();
document.onmousemove=this.savedHandler;
+
if (!handleName) {
} else { delete document.onmousemove; }
+
drag.startCondition=function(e) {
if (this.timer) { clearInterval(this.timer); }
+
try { if (!e.shiftKey) { return false; } } catch (err) { return false; }
this.active=false;
+
return true;
 +
};
 +
}
 +
var dragHandle;
 +
if (handleName) dragHandle = document.getElementById(handleName);
 +
if (!dragHandle) dragHandle = this.mainDiv;
 +
var np=this;
 +
drag.endHook=function(x,y) {
 +
Navpopup.tracker.dirty=true;
 +
np.reposition(x,y);
 +
};
 +
drag.init(dragHandle,this.mainDiv);
 
};
 
};
  
/**
+
/** Hides the popup using CSS. Runs hooks with key 'hide'.
  Creates a new Navpopup.
+
Sets {@link #visible} appropriately. {@link #banish} should be called externally instead of this method.
  Gets a UID for the popup and
+
 
  @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable.
+
@private
  @constructor
 
  @class The Navpopup class. This generates popup hints, and does some management of them.
 
 
*/
 
*/
function Navpopup(init) {
+
Navpopup.prototype.hide = function () {
//alert('new Navpopup(init)');
+
this.runHooks('hide', 'before');
/** UID for each Navpopup instance.
+
this.abortDownloads();
Read-only.
+
if (typeof this.visible != 'undefined' && this.visible) {
@type integer
+
this.mainDiv.style.display='none';
*/
+
this.visible=false;
this.uid=Navpopup.uid++;
+
}
/**
+
this.runHooks('hide', 'after');
  Read-only flag for current visibility of the popup.
+
};
  @type boolean
+
 
  @private
+
/** Shows the popup using CSS. Runs hooks with key 'unhide'.
*/
+
Sets {@link #visible} appropriately.  {@link #show} should be called externally instead of this method.
this.visible=false;
+
@private
/** Flag to be set when we want to cancel a previous request to
+
*/
show the popup in a little while.
+
Navpopup.prototype.unhide = function () {
@private
+
this.runHooks('unhide', 'before');
@type boolean
+
if (typeof this.visible != 'undefined' && !this.visible) {
*/
+
this.mainDiv.style.display='inline';
this.noshow=false;
+
this.visible=true;
/** Categorised list of hooks.
+
}
@see #runHooks
+
this.runHooks('unhide', 'after');
@see #addHook
+
};
@private
+
 
@type Object
+
/**
*/
+
  Sets the <code>innerHTML</code> attribute of the main div containing the popup content.
this.hooks={
+
  @param {String} html The HTML to set.
'create': [],
+
*/
'unhide': [],
+
Navpopup.prototype.setInnerHTML = function (html) {
'hide': []
+
this.mainDiv.innerHTML = html;
};
+
};
/** list of unique IDs of hook functions, to avoid duplicates
+
 
@private
+
/**
*/
+
  Updates the {@link #width} and {@link #height} attributes with the CSS properties.
this.hookIds={};
+
  @private
/** List of downloads associated with the popup.
+
*/
@private
+
Navpopup.prototype.updateDimensions = function () {
@type Array
+
this.width=parseInt(this.mainDiv.offsetWidth, 10);
*/
+
this.height=parseInt(this.mainDiv.offsetHeight, 10);
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);
 
// }
 
}
 
  
 
/**
 
/**
   A UID for each Navpopup. This constructor property is just a counter.
+
   Checks if the point (x,y) is within {@link #fuzz} of the
   @type integer
+
  {@link #mainDiv}.
   @private
+
   @param {integer} x x-coordinate (px)
 +
  @param {integer} y y-coordinate (px)
 +
   @type boolean
 
*/
 
*/
Navpopup.uid=0;
+
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);
 +
};
  
 
/**
 
/**
   Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible.
+
   Adds a download to {@link #downloads}.
   @type boolean
+
   @param {Downloader} download
 
*/
 
*/
Navpopup.prototype.isVisible=function() {
+
Navpopup.prototype.addDownload=function(download) {
return this.visible;
+
if (!download) { return; }
 +
this.downloads.push(download);
 
};
 
};
 
 
/**
 
/**
   Repositions popup using CSS style.
+
   Aborts the downloads listed in {@link #downloads}.
  @private
+
   @see Downloader#abort
  @param {integer} x x-coordinate (px)
 
  @param {integer} y y-coordinate (px)
 
   @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition}
 
 
*/
 
*/
Navpopup.prototype.reposition= function (x,y, noLimitHor) {
+
Navpopup.prototype.abortDownloads=function() {
log ('reposition('+x+','+y+','+noLimitHor+')');
+
for(var i=0; i<this.downloads.length; ++i) {
if (typeof x != 'undefined' && x!==null) { this.left=x; }
+
var d=this.downloads[i];
if (typeof y != 'undefined' && y!==null) { this.top=y; }
+
if (d && d.abort) { d.abort(); }
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(); }
+
this.downloads=[];
//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.
+
   A {@link Mousetracker} instance which is a property of the constructor (pseudo-global).
  Should not be run if {@link #constrained} is true.
 
  @private
 
 
*/
 
*/
Navpopup.prototype.limitHorizontalPosition=function() {
+
Navpopup.tracker=new Mousetracker();
if (!this.constrained || this.tooWide) { return; }
+
// ENDFILE: navpopup.js
this.updateDimensions();
+
// STARTFILE: diff.js
var x=this.left;
+
//<NOLITE>
var w=this.width;
+
/*
var cWidth=document.body.clientWidth;
+
* Javascript Diff Algorithm
 +
*  By John Resig (http://ejohn.org/) and [[:en:User:Lupin]]
 +
*
 +
* More Info:
 +
*  http://ejohn.org/projects/javascript-diff-algorithm/
 +
*/
  
 +
function delFmt(x) {
 +
if (!x.length) { return ''; }
 +
return "<del class='popupDiff'>" + x.join('') +"</del>";
 +
}
 +
function insFmt(x) {
 +
if (!x.length) { return ''; }
 +
return "<ins class='popupDiff'>" + x.join('') +"</ins>";
 +
}
  
// log('limitHorizontalPosition: x='+x+
+
function countCrossings(a, b, i, eject) {
// ', this.left=' + this.left +
+
// count the crossings on the edge starting at b[i]
// ', this.width=' + this.width +
+
if (!b[i].row && b[i].row !== 0) { return -1; }
// ', cWidth=' + cWidth);
+
var count=0;
 
+
for (var j=0; j<a.length; ++j) {
 
+
if (!a[j].row && a[j].row !== 0) { continue; }
if ( (x+w) >= cWidth ||
+
if ( (j-b[i].row)*(i-a[j].row) > 0) {
( x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width
+
if(eject) { return true; }
  && x > cWidth - this.maxWidth ) ) {
+
count++;
// This is a very nasty hack. There has to be a better way!
+
}
// We find the "natural" width of the div by positioning it at the far left
 
// then reset it so that it should be flush right (well, nearly)
 
this.mainDiv.style.left='-10000px';
 
this.mainDiv.style.width = this.maxWidth + 'px';
 
var naturalWidth=parseInt(this.mainDiv.offsetWidth, 10);
 
var newLeft=cWidth - naturalWidth - 1;
 
if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups?
 
log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth);
 
this.reposition(newLeft, null, true);
 
 
}
 
}
};
+
return count;
 +
}
  
/**
+
function shortenDiffString(str, context) {
  Counter indicating the z-order of the "highest" popup.
+
var re=RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)');
  We start the z-index at 1000 so that popups are above everything
+
var splitted=str.parenSplit(re);
  else on the screen.
+
var ret=[''];
  @private
+
for (var i=0; i<splitted.length; i+=2) {
  @type integer
+
if (splitted[i].length < 2*context) {
*/
+
ret[ret.length-1] += splitted[i];
Navpopup.highest=1000;
+
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;
 +
}
  
/**
 
  Brings popup to the top of the z-order.
 
  We increment the {@link #highest} property of the contructor here.
 
  @private
 
*/
 
Navpopup.prototype.raise = function () {
 
this.mainDiv.style.zIndex=Navpopup.highest + 1;
 
++Navpopup.highest;
 
};
 
  
/**
+
function diffString( o, n, simpleSplit ) {
  Shows the popup provided {@link #noshow} is not true.
+
var splitRe=RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)');
  Updates the position, brings the popup to the top of the z-order and unhides it.
 
*/
 
Navpopup.prototype.show = function () {
 
//document.title+='s';
 
if (this.noshow) { return; }
 
//document.title+='t';
 
this.reposition();
 
this.raise();
 
this.unhide();
 
};
 
  
 
+
// 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)
  Runs the {@link #show} method in a little while, unless we're
+
var out, i, oSplitted, nSplitted;
  already visible.
+
if (simpleSplit) {  
  @param {integer} time Delay in milliseconds
+
oSplitted=o.split(/\b/);
  @see #showSoonIfStable
+
nSplitted=n.split(/\b/);
*/
+
} else {
Navpopup.prototype.showSoon = function (time) {
+
oSplitted=o.parenSplit(splitRe);
if (this.visible) { return; }
+
nSplitted=n.parenSplit(splitRe);
this.noshow=false;
+
}
//~ We have to save the value of @tt{this} so that the closure below
+
for (i=0; i<oSplitted.length; ++i) {oSplitted[i]=oSplitted[i].entify();}
//~ works.
+
for (i=0; i<nSplitted.length; ++i) {nSplitted[i]=nSplitted[i].entify();}
var savedThis=this;
+
//this.start_x = Navpopup.tracker.x;
+
out = diff (oSplitted, nSplitted);
//this.start_y = Navpopup.tracker.y;
+
var str = "";
setTimeout(function () {
+
var acc=[]; // accumulator for prettier output
if (Navpopup.tracker.active) {
+
 
savedThis.reposition.apply(savedThis, [Navpopup.tracker.x + 2, Navpopup.tracker.y + 2]);
+
// crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out
 +
// this doesn't always do things optimally but it should be fast enough
 +
var maxOutputPair=0;
 +
for (i=0; i<out.n.length; ++i) {
 +
if ( out.n[i].paired ) {
 +
if( maxOutputPair > out.n[i].row ) {
 +
// tangle - delete pairing
 +
out.o[ out.n[i].row ]=out.o[ out.n[i].row ].text;
 +
out.n[i]=out.n[i].text;
 +
}
 +
if (maxOutputPair < out.n[i].row) { maxOutputPair = out.n[i].row; }
 
}
 
}
//~ Have to use apply to invoke his member function here
+
}
savedThis.show.apply(savedThis, []);
 
}, time);
 
};
 
  
/**
+
// output the stuff preceding the first paired old line
  Checks to see if the mouse pointer has
+
for (i=0; i<out.o.length && !out.o[i].paired; ++i) { acc.push( out.o[i] ); }
  stabilised (checking every <code>time</code>/2 milliseconds) and runs the
+
str += delFmt(acc); acc=[];
  {@link #show} method if it has. This method makes {@link #showSoon} redundant.
 
  @param {integer} time The minimum time (ms) before the popup may be shown.
 
*/
 
Navpopup.prototype.showSoonIfStable = function (time) {
 
log ('showSoonIfStable, time='+time);
 
if (this.visible) { return; }
 
this.noshow = false;
 
  
//~ initialize these variables so that we never run @tt{show} after
+
// main loop
//~ just half the time
+
for ( i = 0; i < out.n.length; ++i ) {
this.stable_x = -10000; this.stable_y = -10000;
+
// output unpaired new "lines"
 
+
while ( i < out.n.length && !out.n[i].paired ) { acc.push( out.n[i++] ); }
var stableShow = function() {
+
str += insFmt(acc); acc=[];
log('stableShow called');
+
if ( i < out.n.length ) { // this new "line" is paired with the (out.n[i].row)th old "line"
var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y;
+
str += out.n[i].text;
var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y;
+
// output unpaired old rows starting after this new line's partner
var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz;
+
var m = out.n[i].row + 1;
//document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] ';
+
while ( m < out.o.length && !out.o[m].paired ) { acc.push ( out.o[m++] ); }
if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) {
+
str += delFmt(acc); acc=[];
log ('mouse is stable');
 
clearInterval(savedThis.showSoonStableTimer);
 
savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]);
 
savedThis.show.apply(savedThis, []);
 
return;
 
 
}
 
}
savedThis.stable_x = new_x; savedThis.stable_y = new_y;
+
}
};
+
return str;
var savedThis = this;
+
}
this.showSoonStableTimer = setInterval(stableShow, time/2);
 
};
 
  
/**
+
// see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object
  Makes the popup unhidable until we call {@link #unstick}.
+
// FIXME: use obj.hasOwnProperty instead of this kludge!
*/
+
var jsReservedProperties=RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' +
Navpopup.prototype.stick=function() {
+
  '|eval|hasOwnProperty|propertyIsEnumerable' +
this.noshow=false;
+
  '|to(Source|String|LocaleString)|(un)?watch|valueOf)$');
this.sticky=true;
+
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) {
  Allows the popup to be hidden.
+
var ret={};
*/
+
for ( var i = 0; i < src.length; i++ ) {
Navpopup.prototype.unstick=function() {
+
if ( jsReservedProperties.test(src[i]) ) { src[i] += '<!-- -->'; }
this.sticky=false;
+
if ( !ret[ src[i] ] ) { ret[ src[i] ] = []; }
};
+
try { ret[ src[i] ].push( i ); } catch (err) { diffBugAlert(src[i]); }
 +
}
 +
return ret;
 +
}
 +
 
 +
function diff( o, n ) {
 +
 
 +
// pass 1: make hashtable ns with new rows as keys
 +
var ns = makeDiffHashtable(n);
 +
 
 +
// pass 2: make hashtable os with old rows as keys
 +
var os = makeDiffHashtable(o);
  
/**
+
// pass 3: pair unique new rows and matching unique old rows
  Sets the {@link #noshow} flag and hides the popup. This should be called
+
var i;
  when the mouse leaves the link before
+
for ( i in ns ) {
  (or after) it's actually been displayed.
+
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 };
Navpopup.prototype.banish = function () {
+
o[ os[i][0] ] = { text: o[ os[i][0] ], row: ns[i][0], paired: true };
log ('banish called');
+
}
// hide and prevent showing with showSoon in the future
 
this.noshow=true;
 
if (this.showSoonStableTimer) {
 
log('clearing showSoonStableTimer');
 
clearInterval(this.showSoonStableTimer);
 
 
}
 
}
this.hide();
 
};
 
  
/**
+
// pass 4: pair matching rows immediately following paired rows (not necessarily unique)
  Runs hooks added with {@link #addHook}.
+
for ( i = 0; i < n.length - 1; i++ ) {
  @private
+
if ( n[i].paired && ! n[i+1].paired && n[i].row + 1 < o.length && ! o[ n[i].row + 1 ].paired &&
  @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
+
n[i+1] == o[ n[i].row + 1 ] ) {
  @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
+
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 };
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;
 
}
 
 
}
 
}
 
}
 
}
};
 
  
/**
+
// pass 5: pair matching rows immediately preceding paired rows (not necessarily unique)
  Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments.
+
for ( i = n.length - 1; i > 0; i-- ) {
  @param {Function} hook The hook function. Functions that return true are deleted.
+
if ( n[i].paired && ! n[i-1].paired && n[i].row > 0 && ! o[ n[i].row - 1 ].paired &&
  @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
+
n[i-1] == o[ n[i].row - 1 ] ) {
  @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
+
n[i-1] = { text: n[i-1], row: n[i].row - 1, paired: true };
  @param {String} uid A truthy string identifying the hook function; if it matches another hook in this position, it won't be added again.
+
o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1, paired: true };
*/
 
Navpopup.prototype.addHook = function ( hook, key, when, uid ) {
 
when = when || 'after';
 
if (!this.hooks[key]) { return; }
 
// if uid is specified, don't add duplicates
 
var hookId=null;
 
if (uid) {
 
hookId=[key,when,uid].join('|');
 
if (this.hookIds[hookId]) {
 
return;
 
 
}
 
}
this.hookIds[hookId]=true;
 
 
}
 
}
this.hooks[key].push( {hook: hook, when: when, hookId: hookId} );
 
};
 
  
/**
+
return { o: o, n: n };
  Creates the main DIV element, which contains all the actual popup content.
+
}
  Runs hooks with key 'create'.
+
//</NOLITE>
  @private
+
// ENDFILE: diff.js
*/
+
// STARTFILE: init.js
Navpopup.prototype.createMainDiv = function () {
+
function setSiteInfo() {
if (this.mainDiv) { return; }
+
if (window.popupLocalDebug) {
this.runHooks('create', 'before');
+
pg.wiki.hostname = 'en.wikipedia.org';
var mainDiv=document.createElement('div');
+
} else {
 +
pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?)
 +
}
 +
pg.wiki.wikimedia=RegExp('(wiki([pm]edia|source|books|news|quote|versity)|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname);
 +
pg.wiki.wikia=RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname);
 +
pg.wiki.isLocal=RegExp('^localhost').test(pg.wiki.hostname);
 +
pg.wiki.commons=( pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org') ? 'commons.wikimedia.org' : null;
 +
pg.wiki.lang = mw.config.get('wgContentLanguage');
 +
var port = location.port ? ':' + location.port : '';
 +
pg.wiki.sitebase = pg.wiki.hostname + port;
 +
}
 +
 
 +
function setUserInfo() {
 +
var api = new mw.Api( {
 +
    ajax: {
 +
        headers: { 'Api-User-Agent': pg.misc.userAgent }
 +
    }
 +
} );
 +
var params = {
 +
action: 'query',
 +
list: 'users',
 +
ususers: mw.config.get('wgUserName'),
 +
usprop: 'rights'
 +
};
 +
   
 +
pg.user.canReview = false;
 +
    if (getValueOf('popupReview')) {
 +
api.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') ?
 +
});
 +
    }
 +
}
  
var savedThis=this;
+
function setTitleBase() {
mainDiv.onclick=function(e) {savedThis.onclickHandler(e);};
+
var protocol = ( window.popupLocalDebug ? 'http:' : location.protocol );
mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv';
+
pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, "");   // as in http://some.thing.com/wiki/Article
mainDiv.id=mainDiv.className + this.uid;
+
pg.wiki.botInterfacePath = mw.config.get('wgScript');
 +
pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php";
 +
// default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo
  
mainDiv.style.position='absolute';
+
var titletail = pg.wiki.botInterfacePath + '?title=';
mainDiv.style.display='none';
+
//var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']);
mainDiv.className='navpopup';
 
  
// easy access to javascript object through DOM functions
+
// other sites may need to add code here to set titletail depending on how their urls work
mainDiv.navpopup=this;
+
 
 +
pg.wiki.titlebase  = protocol + '//' + pg.wiki.sitebase + titletail;
 +
//pg.wiki.titlebase2  = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]);
 +
pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath;
 +
pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath;
 +
pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath;
 +
pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons  + pg.wiki.botInterfacePath;
 +
pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons  + pg.wiki.APIPath;
 +
pg.re.basenames = RegExp( '^(' +
 +
  map( literalizeRegex, [ pg.wiki.titlebase, //pg.wiki.titlebase2,
 +
  pg.wiki.articlebase ]).join('|') + ')' );
 +
}
  
this.mainDiv=mainDiv;
 
document.body.appendChild(mainDiv);
 
this.runHooks('create', 'after');
 
};
 
/**
 
  Calls the {@link #raise} method.
 
  @private
 
*/
 
Navpopup.prototype.onclickHandler=function(e) {
 
this.raise();
 
};
 
/**
 
  Makes the popup draggable, using a {@link Drag} object.
 
  @private
 
*/
 
Navpopup.prototype.makeDraggable=function(handleName) {
 
if (!this.mainDiv) { this.createMainDiv(); }
 
var drag=new Drag();
 
if (!handleName) {
 
drag.startCondition=function(e) {
 
try { if (!e.shiftKey) { return false; } } catch (err) { return false; }
 
return true;
 
};
 
}
 
var dragHandle;
 
if (handleName) dragHandle = document.getElementById(handleName);
 
if (!dragHandle) dragHandle = this.mainDiv;
 
var np=this;
 
drag.endHook=function(x,y) {
 
Navpopup.tracker.dirty=true;
 
np.reposition(x,y);
 
};
 
drag.init(dragHandle,this.mainDiv);
 
};
 
  
/** Hides the popup using CSS. Runs hooks with key 'hide'.
+
//////////////////////////////////////////////////
Sets {@link #visible} appropriately. {@link #banish} should be called externally instead of this method.
+
// Global regexps
  
@private
+
function setMainRegex() {
*/
+
var reStart='[^:]*://';
Navpopup.prototype.hide = function () {
+
var preTitles = literalizeRegex( mw.config.get('wgScriptPath') ) + '/(?:index[.]php|wiki[.]phtml)[?]title=';
this.runHooks('hide', 'before');
+
preTitles += '|' + literalizeRegex( pg.wiki.articlePath + '/' );
this.abortDownloads();
 
if (this.sticky) { return; }
 
if (typeof this.visible != 'undefined' && this.visible) {
 
this.mainDiv.style.display='none';
 
this.visible=false;
 
}
 
this.runHooks('hide', 'after');
 
};
 
  
/** Shows the popup using CSS. Runs hooks with key 'unhide'.
+
var reEnd='(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?';
Sets {@link #visible} appropriately.  {@link #show} should be called externally instead of this method.
+
pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd);
@private
+
}
*/
+
 
Navpopup.prototype.unhide = function () {
+
function setRegexps() {
this.runHooks('unhide', 'before');
+
// TODO: We shoud use an api call to get the aliases for special pages, now it does not work for non-English wikipedias:
if (typeof this.visible != 'undefined' && !this.visible) {
+
// E.g., https://ru.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=specialpagealiases&formatversion=2
this.mainDiv.style.display='inline';
+
setMainRegex();
this.visible=true;
+
var sp=nsRe(pg.nsSpecialId);
}
+
pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ;
this.runHooks('unhide', 'after');
+
pg.re.contribs  =RegExp('(title=|/)' + sp + '(?:%3A|:)(?:Contributions' + getValueOf('popupSPAliasesContribs') + ')(&target=|/|/' + nsRe(pg.nsUserId)+':)(.*)') ;
};
+
pg.re.email     =RegExp('(title=|/)'  + sp + '(?:%3A|:)(?:EmailUser' + getValueOf('popupSPAliasesEmail') + ')(&target=|/|/(?:' + nsRe(pg.nsUserId)+':)?)(.*)') ;
 +
pg.re.backlinks =RegExp('(title=|/)'  + sp + '(?:%3A|:)(?:WhatLinksHere' + getValueOf('popupSPAliasesBacklinks') + ')(&target=|/)([^&]*)');
 +
pg.re.specialdiff=RegExp('/'         + sp + '(?:%3A|:)Diff/([^?#]*)');
  
/**
+
//<NOLITE>
  Sets the <code>innerHTML</code> attribute of the main div containing the popup content.
+
var im=nsReImage();
  @param {String} html The HTML to set.
+
// note: tries to get images in infobox templates too, e.g. movie pages, album pages etc
*/
+
//   (^|\[\[)image: *([^|\]]*[^|\] ]) *
Navpopup.prototype.setInnerHTML = function (html) {
+
//   (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)?
this.mainDiv.innerHTML = html;
+
// $4 = 120 as in 120px
};
+
pg.re.image = RegExp('(^|\\[\\[)' + im + ': *([^|\\]]*[^|\\] ])' +
 +
'([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' +
 +
'(' + getValueOf('popupImageVarsRegexp') + ')' +
 +
' *= *(?:\\[\\[ *)?(?:' + im + ':)?' +
 +
'([^|]*?)(?:\\]\\])? *[|]? *\\n', 'img') ;
 +
pg.re.imageBracketCount = 6;
  
/**
+
pg.re.category = RegExp('\\[\\[' +nsRe(pg.nsCategoryId) +
  Updates the {@link #width} and {@link #height} attributes with the CSS properties.
+
': *([^|\\]]*[^|\\] ]) *', 'i');
  @private
+
pg.re.categoryBracketCount = 1;
*/
 
Navpopup.prototype.updateDimensions = function () {
 
this.width=parseInt(this.mainDiv.offsetWidth, 10);
 
this.height=parseInt(this.mainDiv.offsetHeight, 10);
 
};
 
  
/**
+
pg.re.ipUser=RegExp('^' +
  Checks if the point (x,y) is within {@link #fuzz} of the
+
// IPv6
  {@link #mainDiv}.
+
'(?::(?::|(?::[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})' +
  @param {integer} x x-coordinate (px)
+
// IPv4
  @param {integer} y y-coordinate (px)
+
'|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' +
  @type boolean
+
'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$');
*/
+
 
Navpopup.prototype.isWithin = function(x,y) {
+
pg.re.stub= RegExp(getValueOf('popupStubRegexp'), 'im');
//~ If we're not even visible, no point should be considered as
+
pg.re.disambig=RegExp(getValueOf('popupDabRegexp'), 'im');
//~ 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);
 
};
 
  
/**
+
//</NOLITE>
  Adds a download to {@link #downloads}.
+
// FIXME replace with general parameter parsing function, this is daft
  @param {Downloader} download
+
pg.re.oldid=RegExp('[?&]oldid=([^&]*)');
*/
+
pg.re.diff=RegExp('[?&]diff=([^&]*)');
Navpopup.prototype.addDownload=function(download) {
+
}
if (!download) { return; }
 
this.downloads.push(download);
 
};
 
/**
 
  Aborts the downloads listed in {@link #downloads}.
 
  @see Downloader#abort
 
*/
 
Navpopup.prototype.abortDownloads=function() {
 
for(var i=0; i<this.downloads.length; ++i) {
 
var d=this.downloads[i];
 
if (d && d.abort) { d.abort(); }
 
}
 
this.downloads=[];
 
};
 
  
  
/**
+
//////////////////////////////////////////////////
  A {@link Mousetracker} instance which is a property of the constructor (pseudo-global).
+
// miscellany
*/
 
Navpopup.tracker=new Mousetracker();
 
// ENDFILE: navpopup.js
 
// STARTFILE: diff.js
 
//<NOLITE>
 
/*
 
* 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 setupCache() {
if (!x.length) { return ''; }
+
// page caching
return "<del class='popupDiff'>" + x.join('') +"</del>";
+
pg.cache.pages = [];
}
 
function insFmt(x) {
 
if (!x.length) { return ''; }
 
return "<ins class='popupDiff'>" + x.join('') +"</ins>";
 
 
}
 
}
  
function countCrossings(a, b, i, eject) {
+
function setMisc() {
// count the crossings on the edge starting at b[i]
+
pg.current.link=null;
if (!b[i].row && b[i].row !== 0) { return -1; }
+
pg.current.links=[];
var count=0;
+
pg.current.linksHash={};
for (var j=0; j<a.length; ++j) {
+
 
if (!a[j].row && a[j].row !== 0) { continue; }
+
setupCache();
if ( (j-b[i].row)*(i-a[j].row) > 0) {
+
 
if(eject) { return true; }
+
pg.timer.checkPopupPosition=null;
count++;
+
pg.counter.loop=0;
}
+
 
}
+
// ids change with each popup: popupImage0, popupImage1 etc
return count;
+
pg.idNumber=0;
}
 
  
function shortenDiffString(str, context) {
+
// for myDecodeURI
var re=RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)');
+
pg.misc.decodeExtras = [
var splitted=str.parenSplit(re);
+
{from: '%2C', to: ',' },
var ret=[''];
+
{from: '_',  to: ' ' },
for (var i=0; i<splitted.length; i+=2) {
+
{from: '%24', to: '$'},
if (splitted[i].length < 2*context) {
+
{from: '%26',  to: '&' } // no ,
ret[ret.length-1] += splitted[i];
+
];
if (i+1<splitted.length) { ret[ret.length-1] += splitted[i+1]; }
+
 
continue;
+
pg.misc.userAgent = 'Navigation popups/1.0 (' + mw.config.get( 'wgServerName' ) +')';
}
+
}
else {
+
 
if (i > 0) { ret[ret.length-1] += splitted[i].substring(0,context); }
+
// We need a callback since this might end up asynchronous because of
if (i+1 < splitted.length) {
+
// the mw.loader.using() call.
ret.push(splitted[i].substring(splitted[i].length-context) +
+
function setupPopups( callback ) {
splitted[i+1]);
+
if ( setupPopups.completed ) {
}
+
if ( $.isFunction( callback ) ) {
 +
callback();
 
}
 
}
 +
return;
 
}
 
}
while (ret.length > 0 && !ret[0]) { ret = ret.slice(1); }
+
// These dependencies are also enforced from the gadget,
return ret;
+
// but not everyone loads this as a gadget, so double check
}
+
mw.loader.using( ['mediawiki.util', 'mediawiki.user', 'user.options'] ).then( function() {
 +
// NB translatable strings should be set up first (strings.js)
 +
// basics
 +
setupDebugging();
 +
setSiteInfo();
 +
setTitleBase();
 +
setOptions(); // see options.js
 +
setUserInfo();
 +
 
 +
// namespaces etc
 +
setNamespaces();
 +
setInterwiki();
  
 +
// regexps
 +
setRegexps();
 +
setRedirs();
  
function diffString( o, n, simpleSplit ) {
+
// other stuff
var splitRe=RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)');
+
setMisc();
 +
setupLivePreview();
  
//  We need to split the strings o and n first, and entify() the parts
+
// main deal here
//  individually, so that the HTML entities are never cut apart. (AxelBoldt)
+
setupTooltips();
var out, i, oSplitted, nSplitted;
+
log('In setupPopups(), just called setupTooltips()');
if (simpleSplit) {
+
Navpopup.tracker.enable();
oSplitted=o.split(/\b/);
 
nSplitted=n.split(/\b/);  
 
} else {
 
oSplitted=o.parenSplit(splitRe);
 
nSplitted=n.parenSplit(splitRe);
 
}
 
for (i=0; i<oSplitted.length; ++i) {oSplitted[i]=oSplitted[i].entify();}
 
for (i=0; i<nSplitted.length; ++i) {nSplitted[i]=nSplitted[i].entify();}
 
 
out = diff (oSplitted, nSplitted);
 
var str = "";
 
var acc=[]; // accumulator for prettier output
 
  
// crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out
+
setupPopups.completed = true;
// this doesn't always do things optimally but it should be fast enough
+
if ( $.isFunction( callback ) ) {
var maxOutputPair=0;
+
callback();
for (i=0; i<out.n.length; ++i) {
 
if ( out.n[i].paired ) {
 
if( maxOutputPair > out.n[i].row ) {
 
// tangle - delete pairing
 
out.o[ out.n[i].row ]=out.o[ out.n[i].row ].text;
 
out.n[i]=out.n[i].text;
 
}
 
if (maxOutputPair < out.n[i].row) { maxOutputPair = out.n[i].row; }
 
 
}
 
}
 +
});
 +
}
 +
// 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>>}';
 
}
 
}
  
// output the stuff preceding the first paired old line
+
// user links
for (i=0; i<out.o.length && !out.o[i].paired; ++i) { acc.push( out.o[i] ); }
+
// contribs - log - count - email - block
str += delFmt(acc); acc=[];
+
// count only if applicable; block only if popupAdminLinks
 +
str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>';
 +
str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}';
 +
str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}';
 +
 
 +
// editing links
 +
// talkpage  -> edit|new - history - un|watch - article|edit
 +
// other page -> edit - history - un|watch - talk|edit|new
 +
var editstr='<<edit|shortcut=e>>';
 +
var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}';
 +
var historystr='<<history|shortcut=h>>|<<editors|shortcut=E|>>';
 +
var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
 +
 
 +
str+='<br>if(talk){' +
 +
editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' +
 +
'<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
 +
'}else{' + // not a talk page
 +
editOldidStr + '*' + historystr + '*' + watchstr + '*' +
 +
'<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';
 +
 
 +
// misc links
 +
str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';
  
// main loop
+
// admin links
for ( i = 0; i < out.n.length; ++i ) {
+
str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' +
// output unpaired new "lines"
+
'<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}';
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;
 
return str;
 
}
 
}
  
// see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object
+
function navLinksHTML (article, hint, params) { //oldid, rcid) {
// FIXME: use obj.hasOwnProperty instead of this kludge!
+
var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>';
window.jsReservedProperties=RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' +
+
// BAM
  '|eval|hasOwnProperty|propertyIsEnumerable' +
+
return navlinkStringToHTML(str, article, params);
  '|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 expandConditionalNavlinkString(s,article,z,recursionCount) {
var ret={};
+
var oldid=z.oldid, rcid=z.rcid, diff=z.diff;
for ( var i = 0; i < src.length; i++ ) {
+
// nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out)
if ( jsReservedProperties.test(src[i]) ) { src[i] += '<!-- -->'; }
+
if (typeof recursionCount!=typeof 0) { recursionCount=0; }
if ( !ret[ src[i] ] ) { ret[ src[i] ] = []; }
+
var conditionalSplitRegex=RegExp(
try { ret[ src[i] ].push( i ); } catch (err) { diffBugAlert(src[i]); }
+
//(1 if \\( (2 2) \\)   {(3 3)}  (4  else   {(5 5)} 4)1)
}
+
'(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i');
return ret;
+
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) {
  
function diff( o, n ) {
+
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;
  
// pass 1: make hashtable ns with new rows as keys
+
switch (testString) {
var ns = makeDiffHashtable(n);
+
case 'user':
 
+
testResult=(article.userName())?true:false;
// pass 2: make hashtable os with old rows as keys
+
break;
var os = makeDiffHashtable(o);
+
case 'talk':
 
+
testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages
// pass 3: pair unique new rows and matching unique old rows
+
break;
var i;
+
case 'admin':
for ( i in ns ) {
+
testResult=getValueOf('popupAdminLinks')?true:false;
if ( ns[i].length == 1 && os[i] && os[i].length == 1 ) {
+
break;
n[ ns[i][0] ] = { text: n[ ns[i][0] ], row: os[i][0], paired: true };
+
case 'oldid':
o[ os[i][0] ] = { text: o[ os[i][0] ], row: ns[i][0], paired: true };
+
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;
 
}
 
}
}
 
  
// pass 4: pair matching rows immediately following paired rows (not necessarily unique)
+
switch(testResult) {
for ( i = 0; i < n.length - 1; i++ ) {
+
case null: ret+=splitted[i];  break;
if ( n[i].paired && ! n[i+1].paired && n[i].row + 1 < o.length && ! o[ n[i].row + 1 ].paired &&
+
case true: ret+=trueString;  break;
n[i+1] == o[ n[i].row + 1 ] ) {
+
case false: ret+=falseString; break;
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 };
 
 
}
 
}
 +
 +
// append non-conditional string
 +
ret += splitted[i+numParens];
 
}
 
}
 
+
if (conditionalSplitRegex.test(ret) && recursionCount < 10) {
// pass 5: pair matching rows immediately preceding paired rows (not necessarily unique)
+
return expandConditionalNavlinkString(ret,article,z,recursionCount+1);
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 ret;
 +
}
  
return { o: o, n: n };
+
function navlinkStringToArray(s, article, params) {
}
+
s=expandConditionalNavlinkString(s,article,params);
//</NOLITE>
+
var splitted=s.parenSplit(RegExp('<<(.*?)>>'));
// ENDFILE: diff.js
+
var ret=[];
// STARTFILE: init.js
+
for (var i=0; i<splitted.length; ++i) {
function setSiteInfo() {
+
if (i%2) { // i odd, so s is a tag
if (window.popupLocalDebug) {
+
var t=new navlinkTag();
pg.wiki.hostname = 'en.wikipedia.org';
+
var ss=splitted[i].split('|');
} else {
+
t.id=ss[0];
pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?)
+
for (var j=1; j<ss.length; ++j) {
 +
var sss=ss[j].split('=');
 +
if (sss.length>1) {
 +
t[sss[0]]=sss[1];
 +
}
 +
else { // no assignment (no "="), so treat this as a title (overwriting the last one)
 +
t.text=popupString(sss[0]);
 +
}
 +
}
 +
t.article=article;
 +
var oldid=params.oldid, rcid=params.rcid, diff=params.diff;
 +
if (typeof oldid !== 'undefined' && oldid !== null) { t.oldid=oldid; }
 +
if (typeof rcid !== 'undefined' && rcid !== null) { t.rcid=rcid; }
 +
if (typeof diff !== 'undefined' && diff !== null) { t.diff=diff; }
 +
if (!t.text && t.id !== 'mainlink') { t.text=popupString(t.id); }
 +
ret.push(t);
 +
}
 +
else { // plain HTML
 +
ret.push(splitted[i]);
 +
}
 
}
 
}
pg.wiki.wikimedia=RegExp('(wiki([pm]edia|source|books|news|quote|versity)|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname);
+
return ret;
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() {
 
var protocol = ( window.popupLocalDebug ? 'http:' : location.protocol );
 
pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, "");  // as in http://some.thing.com/wiki/Article
 
pg.wiki.botInterfacePath = mw.config.get('wgScript');
 
pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php";
 
// default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo
 
  
var titletail = pg.wiki.botInterfacePath + '?title=';
+
function navlinkSubstituteHTML(s) {
//var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']);
+
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>');
  
// 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;
+
function navlinkDepth(magic,s) {
//pg.wiki.titlebase2  = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]);
+
return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length;
pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath;
 
pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath;
 
pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath;
 
pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons  + pg.wiki.botInterfacePath;
 
pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons  + pg.wiki.APIPath;
 
pg.re.basenames = RegExp( '^(' +
 
  map( literalizeRegex, [ pg.wiki.titlebase, //pg.wiki.titlebase2,
 
  pg.wiki.articlebase ]).join('|') + ')' );
 
 
}
 
}
  
  
//////////////////////////////////////////////////
+
// navlinkString: * becomes the separator
// Global regexps
+
// <<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 setMainRegex() {
+
function navlinkStringToHTML(s,article,params) {
var reStart='[^:]*://';
+
//limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article));
var preTitles = literalizeRegex( mw.config.get('wgScriptPath') ) + '/(?:index[.]php|wiki[.]phtml)[?]title=';
+
var p=navlinkStringToArray(s,article,params);
preTitles += '|' + literalizeRegex( pg.wiki.articlePath + '/' );
+
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;
 +
}
  
var reEnd='(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?';
+
function navlinkTag() {
pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd);
+
this.type='navlinkTag';
 
}
 
}
  
function setRegexps() {
+
navlinkTag.prototype.html=function () {
setMainRegex();
+
this.getNewWin();
var sp=nsRe(pg.nsSpecialId);
+
this.getPrintFunction();
pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ;
+
var html='';
pg.re.contribs  =RegExp('(title=|/)' + sp + '(?:%3A|:)Contributions' + '(&target=|/|/' + mw.config.get('wgFormattedNamespaces')[pg.nsUserId]+':)(.*)') ;
+
var opening, closing;
pg.re.email     =RegExp('(title=|/)' + sp + '(?:%3A|:)EmailUser' + '(&target=|/|/(?:' + mw.config.get('wgFormattedNamespaces')[pg.nsUserId]+':)?)(.*)') ;
+
var tagType='span';
pg.re.backlinks =RegExp('(title=|/)' + sp + '(?:%3A|:)WhatLinksHere' + '(&target=|/)([^&]*)');
+
if (!tagType) {
pg.re.specialdiff=RegExp('/'          + sp + '(?:%3A|:)Diff/([^?#]*)');
+
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;
 +
};
  
//<NOLITE>
+
navlinkTag.prototype.getNewWin=function() {
var im=nsReImage();
+
getValueOf('popupLinksNewWindow');
// note: tries to get images in infobox templates too, e.g. movie pages, album pages etc
+
if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') { this.newWin=null; }
//   (^|\[\[)image: *([^|\]]*[^|\] ]) *
+
this.newWin=pg.option.popupLinksNewWindow[this.id];
//   (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)?
+
};
// $4 = 120 as in 120px
 
pg.re.image = RegExp('(^|\\[\\[)' + im + ': *([^|\\]]*[^|\\] ])' +
 
'([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' +
 
'(' + getValueOf('popupImageVarsRegexp') + ')' +
 
' *= *(?:\\[\\[ *)?(?:' + im + ':)?' +
 
'([^|]*?)(?:\\]\\])? *[|]? *\\n', 'img') ;
 
pg.re.imageBracketCount = 6;
 
  
pg.re.category = RegExp('\\[\\[' +nsRe(pg.nsCategoryId) +
+
navlinkTag.prototype.getPrintFunction=function() { //think about this some more
': *([^|\\]]*[^|\\] ]) *', 'i');
+
// this.id and this.article should already be defined
pg.re.categoryBracketCount = 1;
+
if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; }
  
pg.re.ipUser=RegExp('^' +
+
this.noPopup=1;
// IPv6
+
switch (this.id) {
'(?::(?::|(?::[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})' +
+
case 'contribs': case 'history': case 'whatLinksHere':
// IPv4
+
case 'userPage': case 'monobook': case 'userTalk':
'|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' +
+
case 'talk': case 'article': case 'lastEdit':
'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$');
+
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();
 +
}
  
pg.re.stub= RegExp(getValueOf('popupStubRegexp'), 'im');
+
switch (this.id) {
pg.re.disambig=RegExp(getValueOf('popupDabRegexp'), 'im');
+
case 'userTalk': case 'newUserTalk': case 'editUserTalk':
 +
case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog':
 +
this.article=this.article.userName(true);
 +
/* fall through */
 +
case 'pagelog': case 'deletelog': case 'protectlog':
 +
delete this.oldid;
 +
}
  
//</NOLITE>
+
if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); }
// FIXME replace with general parameter parsing function, this is daft
 
pg.re.oldid=RegExp('[?&]oldid=([^&]*)');
 
pg.re.diff=RegExp('[?&]diff=([^&]*)');
 
}
 
  
 +
if (this.id != 'mainlink') {
 +
// FIXME anchor handling should be done differently with Title object
 +
this.article=this.article.removeAnchor();
 +
// if (typeof this.text=='undefined') this.text=popupString(this.id);
 +
}
  
//////////////////////////////////////////////////
+
switch (this.id) {
// miscellany
+
case 'undelete':      this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break;
 
+
case 'whatLinksHere':  this.print=specialLink; this.specialpage='Whatlinkshere'; break;
function setupCache() {
+
case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break;
// page caching
+
case 'move':          this.print=specialLink; this.specialpage='Movepage'; break;
pg.cache.pages = [];
+
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; 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;
  
function setMisc() {
+
case 'delete':
pg.current.link=null;
+
this.print=wikiLink; this.action='delete';
pg.current.links=[];
+
if (this.article.namespaceId()==pg.nsImageId) {
pg.current.linksHash={};
+
var img=this.article.stripNamespace();
 +
this.action+='&image='+img;
 +
}
 +
break;
  
setupCache();
+
case 'markpatrolled':
 
+
case 'edit': // editOld should keep the oldid, but edit should not.
pg.timer.checkPopupPosition=null;
+
delete this.oldid;
pg.counter.loop=0;
+
/* fall through */
 
+
case 'view': case 'purge': case 'render':
// ids change with each popup: popupImage0, popupImage1 etc
+
this.print=wikiLink;
pg.idNumber=0;
+
this.action=this.id; break;
 
+
case 'raw':
// for myDecodeURI
+
this.print=wikiLink; this.action='raw'; break;
pg.misc.decodeExtras = [
+
case 'new':
{from: '%2C', to: ',' },
+
this.print=wikiLink; this.action='edit&section=new'; break;
{from: '_',  to: ' ' },
+
case 'mainlink':
{from: '%24', to: '$'},
+
if (typeof this.text=='undefined') { this.text=this.article.toString().entify(); }
{from: '%26',  to: '&' } // no ,
+
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]; }
 
+
}
function leadingInteger(s){
+
this.print=titledWikiLink;
var n=s.match(/^(\d*)/)[1];
+
if (typeof this.title==='undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') {
if (n) { return +n; }
+
this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article);
return null;
+
if (typeof this.oldid !== 'undefined' && this.oldid) {
}
+
this.title=tprintf('Revision %s of %s', [this.oldid, this.title]);
 
+
}
function setBrowserHacks() {
+
}
var useOriginal=false;
+
this.action='view'; break;
// browser-specific hacks
+
case 'userPage':
if (typeof window.opera != 'undefined') {
+
case 'article':
//if (leadingInteger(opera.version()) < 9)
+
case 'monobook':
{ useOriginal=true; } // v9 beta still seems to have buggy css
+
case 'editMonobook':
setDefault('popupNavLinkSeparator', ' &#183; ');
+
case 'editArticle':
} else if (navigator.appName=='Konqueror') {
+
delete this.oldid;
setDefault('popupNavLinkSeparator', ' &bull; ');
+
//alert(this.id+'\n'+this.article + '\n'+ typeof this.article);
pg.flag.isKonq=true;
+
this.article=this.article.articleFromTalkOrArticle();
} else if ( navigator.vendor && navigator.vendor.toLowerCase().indexOf('apple computer')===0) {
+
//alert(this.id+'\n'+this.article + '\n'+ typeof this.article);
pg.flag.isSafari=true;
+
this.print=wikiLink;
var webkit=+navigator.userAgent.replace(RegExp('^.*AppleWebKit[/](\\d+).*', 'i'), '$1');
+
if (this.id.indexOf('edit')===0) {
if (webkit < 420) { useOriginal=true; }
+
this.action='edit';
} else if (navigator.appName.indexOf("Microsoft")!=-1) {
+
} else { this.action='view';}
setDefault('popupNavLinkSeparator', ' &#183; ');
+
break;
var ver=+navigator.userAgent.replace(RegExp('^.*MSIE (\\d+).*'), '$1');
+
case 'userTalk':
pg.flag.isIE=true;
+
case 'talk':
pg.flag.IEVersion=ver;
+
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: '+this.id+'';};
 
}
 
}
if (pg.flag.isIE && pg.flag.IEVersion < 8) {
+
};
useOriginal=true;
+
//
}
+
//  end navlinks
if ((pg.flag.isIE && pg.flag.IEVersion < 7) || pg.flag.isKonq || (pg.flag.isSafari && webkit < 420)) {
+
//////////////////////////////////////////////////
pg.flag.linksLikeIE6=true;
+
//</NOLITE>
}
+
// ENDFILE: navlinks.js
if (useOriginal && pg.structures.original) {
+
// STARTFILE: shortcutkeys.js
setDefault('popupStructure','original');
+
//<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
 
}
 
}
}
 
  
// We need a callback since this might end up asynchronous because of
+
var letter=String.fromCharCode(keyCode);
// the mw.loader.using() call.
+
var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A');
function setupPopups( callback ) {
+
var startLink=0;
mw.loader.using( 'mediawiki.user', function() {
+
var i,j;
// NB translatable strings should be set up first (strings.js)
 
// basics
 
setupDebugging();
 
setSiteInfo();
 
setTitleBase();
 
setOptions(); // see options.js
 
  
// namespaces etc
+
if (popupHandleKeypress.lastPopupLinkSelected) {
setNamespaces();
+
for (i=0; i<links.length; ++i) {
setInterwiki();
+
if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; }
 +
}
 +
}
 +
for (j=0; j<links.length; ++j) {
 +
i=(startLink + j + 1) % links.length;
 +
if (links[i].getAttribute('popupkey')==letter) {
 +
if (evt && evt.preventDefault) evt.preventDefault();
 +
links[i].focus();
 +
popupHandleKeypress.lastPopupLinkSelected=links[i];
 +
return false; // swallow keypress
 +
}
 +
}
  
// regexps
+
// pass keypress on
setRegexps();
+
if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); }
setRedirs();
+
return true;
 +
}
  
// other stuff
+
function addPopupShortcuts() {
setBrowserHacks();
+
if (document.onkeypress!=popupHandleKeypress) {
setMisc();
+
document.oldPopupOnkeypress=document.onkeypress;
setupLivePreview();
+
}
 +
document.onkeypress=popupHandleKeypress;
 +
}
  
// main deal here
+
function rmPopupShortcuts() {
setupTooltips();
+
popupHandleKeypress.lastPopupLinkSelected=null;
Navpopup.tracker.enable();
+
try {
 
+
if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) {
setupPopups.completed = true;
+
// panic
if ( $.isFunction( callback ) ) {
+
document.onkeypress=null; //function () {};
callback();
+
return;
 
}
 
}
});
+
document.onkeypress=document.oldPopupOnkeypress;
 +
} catch (nasties) { /* IE goes here */ }
 
}
 
}
// 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>>}';
 
}
 
  
// user links
+
function addLinkProperty(html, property) {
// contribs - log - count - email - block
+
// take "<a href=...>...</a> and add a property
// count only if applicable; block only if popupAdminLinks
+
// not sophisticated at all, easily broken
str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>';
+
var i=html.indexOf('>');
str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}';
+
if (i<0) { return html; }
str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}';
+
return html.substring(0,i) + ' ' + property + html.substring(i);
 +
}
  
// editing links
+
function addPopupShortcut(html, key) {
// talkpage  -> edit|new - history - un|watch - article|edit
+
if (!getValueOf('popupShortcutKeys')) { return html; }
// other page -> edit - history - un|watch - talk|edit|new
+
var ret= addLinkProperty(html, 'popupkey="'+key+'"');
var editstr='<<edit|shortcut=e>>';
+
if (key==' ') { key=popupString('spacebar'); }
var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'
+
return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4');
var historystr='<<history|shortcut=h>>if(mainspace_en){|<<editors|shortcut=E|>>}';
+
}
var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
+
//</NOLITE>
 
+
// ENDFILE: shortcutkeys.js
str+='<br>if(talk){' +
+
// STARTFILE: diffpreview.js
editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' +
+
//<NOLITE>
'<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
+
//lets jump through hoops to find the rev ids we need to retrieve
'}else{' + // not a talk page
+
function loadDiff(article, oldid, diff, navpop) {
editOldidStr + '*' + historystr + '*' + watchstr + '*' +
+
navpop.diffData={ oldRev: {}, newRev: {} };
'<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>'
+
mw.loader.using( 'mediawiki.api' ).then( function() {
+ '}';
+
var api = new mw.Api( {
 +
    ajax: {
 +
        headers: { 'Api-User-Agent': pg.misc.userAgent }
 +
    }
 +
} );
 +
var params = {
 +
action: 'compare',
 +
prop: 'ids|title'
 +
};
 +
if(article.title){
 +
params.fromtitle = article.title;
 +
}
 +
 +
switch (diff) {
 +
case 'cur':
 +
switch ( oldid ) {
 +
case null:
 +
case '':
 +
case 'prev':
 +
// this can only work if we have the title
 +
// cur -> prev
 +
params.torelative = 'prev';
 +
break;
 +
default:
 +
params.fromrev = oldid;
 +
params.torelative = 'cur';
 +
break;
 +
}
 +
break;
 +
case 'prev':
 +
if( oldid ) {
 +
params.fromrev = oldid;
 +
} else {
 +
params.fromtitle;
 +
}
 +
params.torelative = 'prev';
 +
break;
 +
case 'next':
 +
params.fromrev = oldid || 0;
 +
params.torelative = 'next';
 +
break;
 +
default:
 +
params.fromrev = oldid || 0;
 +
params.torev = diff || 0;
 +
break;
 +
}
 +
 +
api.get( params ).then( function( data ) {
 +
navpop.diffData.oldRev.revid = data.compare.fromrevid;
 +
navpop.diffData.newRev.revid = data.compare.torevid;
 +
 +
addReviewLink(navpop, 'popupMiscTools');
  
// misc links
+
var go = function() {
str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';
+
pendingNavpopTask(navpop);
 +
var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';
 +
 +
url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid;
 +
url += '&prop=revisions&rvprop=ids|timestamp|content';
 +
 +
getPageWithCaching(url, doneDiff, navpop);
 +
 +
return true; // remove hook once run
 +
};
 +
if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
 +
else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); }
 +
} );
 +
} );
 +
}
  
// admin links
+
// Put a "mark patrolled" link to an element target
str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' +
+
// TODO: Allow patrol a revision, as well as a diff
'<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}';
+
function addReviewLink (navpop, target) {
return str;
+
if (! pg.user.canReview) return;
 +
// If 'newRev' is older than 'oldRev' than it could be confusing, so we do not show the review link.
 +
if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) return;
 +
var api = new mw.Api( {
 +
    ajax: {
 +
        headers: { 'Api-User-Agent': pg.misc.userAgent }
 +
    }
 +
} );
 +
var params = {
 +
action: 'query',
 +
prop: 'info|flagged',
 +
revids: navpop.diffData.oldRev.revid,
 +
formatversion : 2
 +
};
 +
api.get (params).then(function(data){
 +
var stable_revid = data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid || 0;
 +
// The diff can be reviewed if the old version is the last reviewed version
 +
// TODO: Other possible conditions that we may want to implement instead of this one:
 +
//  * old version is patrolled and the new version is not patrolled
 +
//  * old version is patrolled and the new version is more recent than the last reviewed version
 +
if (stable_revid == navpop.diffData.oldRev.revid) {
 +
var a = document.createElement('a');
 +
a.innerHTML = popupString('mark patrolled');
 +
a.title=popupString('markpatrolledHint');
 +
a.onclick = function() {
 +
var params = {
 +
action: 'review',
 +
revid: navpop.diffData.newRev.revid,
 +
comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid])
 +
};
 +
api.postWithToken('csrf',params).done(function(){
 +
a.style.display = "none";
 +
// TODO: Update current page and other already constructed popups
 +
} ).fail(function(){
 +
alert(popupString('Could not marked this edit as patrolled'));
 +
});
 +
};
 +
setPopupHTML(a, target, navpop.idNumber,null,true);
 +
}
 +
});
 
}
 
}
  
function navLinksHTML (article, hint, params) { //oldid, rcid) {
+
function doneDiff(download) {
var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>';
+
if (!download.owner || !download.owner.diffData) { return; }
// BAM
+
var navpop=download.owner;
return navlinkStringToHTML(str, article, params);
+
completedNavpopTask(navpop);
 +
 +
var pages, revisions=[];
 +
try{
 +
// Process the downloads
 +
pages = getJsObj(download.data).query.pages;
 +
for(var i=0; i < pages.length; i++ ) {
 +
revisions = revisions.concat(pages[i].revisions);
 +
}
 +
for(i=0; i< revisions.length; i++){
 +
if(revisions[i].revid == navpop.diffData.oldRev.revid) {
 +
navpop.diffData.oldRev.revision = revisions[i];
 +
} else if (revisions[i].revid == navpop.diffData.newRev.revid) {
 +
navpop.diffData.newRev.revision = revisions[i];
 +
}
 +
}
 +
} catch(someError) {
 +
errlog( 'Could not get diff' );
 +
}
 +
 +
insertDiff(navpop);
 
}
 
}
  
function expandConditionalNavlinkString(s,article,z,recursionCount) {
+
function rmBoringLines(a,b,context) {
var oldid=z.oldid, rcid=z.rcid, diff=z.diff;
+
 
// nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out)
+
if (typeof context == 'undefined') { context=2; }
if (typeof recursionCount!=typeof 0) { recursionCount=0; }
+
// this is fairly slow... i think it's quicker than doing a word-based diff from the off, though
var conditionalSplitRegex=RegExp(
+
var aa=[], aaa=[];
//(1 if \\( (2 2) \\)   {(3 3)}  (4  else   {(5 5)}  4)1)
+
var bb=[], bbb=[];
'(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i');
+
var i, j;
var splitted=s.parenSplit(conditionalSplitRegex);
+
 
// $1: whole conditional
+
// first, gather all disconnected nodes in a and all crossing nodes in a and b
// $2: test condition
+
for (i=0; i<a.length; ++i ) {
// $3: true expansion
+
if(!a[i].paired) { aa[i]=1; }
// $4: else clause (possibly empty)
+
else if (countCrossings(b,a,i, true)) {
// $5: false expansion (possibly null)
+
aa[i]=1;
var numParens=5;
+
bb[ a[i].row ] = 1;
var ret = splitted[0];
+
}
for (var i=1; i<splitted.length; i=i+numParens+1) {
+
}
  
var testString=splitted[i+2-1];
+
// pick up remaining disconnected nodes in b
var trueString=splitted[i+3-1];
+
for (i=0; i<b.length; ++i ) {
var falseString=splitted[i+5-1];
+
if (bb[i]==1) { continue; }
if (typeof falseString=='undefined' || !falseString) { falseString=''; }
+
if(!b[i].paired) { bb[i]=1; }
var testResult=null;
+
}
  
switch (testString) {
+
// another pass to gather context: we want the neighbours of included nodes which are not yet included
case 'user':
+
// we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass
testResult=(article.userName())?true:false;
+
for (i=0; i<b.length; ++i) {
break;
+
if ( bb[i] == 1 ) {
case 'talk':
+
for (j=Math.max(0,i-context); j < Math.min(b.length, i+context); ++j) {
testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages
+
if ( !bb[j] ) { bb[j] = 1; aa[ b[j].row ] = 0.5; }
break;
+
}
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;
 
 
}
 
}
 +
}
  
switch(testResult) {
+
for (i=0; i<a.length; ++i) {
case null: ret+=splitted[i];  break;
+
if ( aa[i] == 1 ) {
case true: ret+=trueString;   break;
+
for (j=Math.max(0,i-context); j < Math.min(a.length, i+context); ++j) {
case false: ret+=falseString; break;
+
if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; }
 +
}
 
}
 
}
 +
}
  
// append non-conditional string
+
for (i=0; i<bb.length; ++i) {
ret += splitted[i+numParens];
+
if (bb[i] > 0) { // it's a row we need
 +
if (b[i].paired) { bbb.push(b[i].text); } // joined; partner should be in aa
 +
else {
 +
bbb.push(b[i]);
 +
}
 +
}
 
}
 
}
if (conditionalSplitRegex.test(ret) && recursionCount < 10) {
+
for (i=0; i<aa.length; ++i) {
return expandConditionalNavlinkString(ret,article,z,recursionCount+1);
+
if (aa[i] > 0) { // it's a row we need
 +
if (a[i].paired) { aaa.push(a[i].text); } // joined; partner should be in aa
 +
else {
 +
aaa.push(a[i]);
 +
}
 +
}
 
}
 
}
return ret;
 
}
 
  
function navlinkStringToArray(s, article, params) {
+
return { a: aaa, b: bbb};
s=expandConditionalNavlinkString(s,article,params);
 
var splitted=s.parenSplit(RegExp('<<(.*?)>>'));
 
var ret=[];
 
for (var i=0; i<splitted.length; ++i) {
 
if (i%2) { // i odd, so s is a tag
 
var t=new navlinkTag();
 
var ss=splitted[i].split('|');
 
t.id=ss[0];
 
for (var j=1; j<ss.length; ++j) {
 
var sss=ss[j].split('=');
 
if (sss.length>1) {
 
t[sss[0]]=sss[1];
 
}
 
else { // no assignment (no "="), so treat this as a title (overwriting the last one)
 
t.text=popupString(sss[0]);
 
}
 
}
 
t.article=article;
 
var oldid=params.oldid, rcid=params.rcid, diff=params.diff;
 
if (typeof oldid != 'undefined' && oldid != null) { t.oldid=oldid; }
 
if (typeof rcid != 'undefined' && rcid != null) { t.rcid=rcid; }
 
if (typeof diff != 'undefined' && diff != null) { t.diff=diff; }
 
if (!t.text && t.id != 'mainlink') { t.text=popupString(t.id); }
 
ret.push(t);
 
}
 
else { // plain HTML
 
ret.push(splitted[i]);
 
}
 
}
 
return ret;
 
 
}
 
}
  
 +
function 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 navlinkSubstituteHTML(s) {
+
return { a: a.slice(Math.max(0,i - 1 - context), Math.min(a.length+1, j + context+1)),
return s.split('*').join(getValueOf('popupNavLinkSeparator'))
+
b: b.slice(Math.max(0,i - 1 - context), Math.min(b.length+1, k + context+1)) };
.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 insertDiff(navpop) {
return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length;
+
// for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff
}
+
// FIXME: sometimes this gives misleading diffs as distant chunks are squashed together
 
+
var oldlines = navpop.diffData.oldRev.revision.content.split('\n');
 
+
var newlines = navpop.diffData.newRev.revision.content.split('\n');
// navlinkString: * becomes the separator
+
var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines'));
// <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz'
+
oldlines=inner.a; newlines=inner.b;
//   and visible text 'fubar'
+
var truncated=false;
// if(test){...} and if(test){...}else{...} work too (nested ok)
+
getValueOf('popupDiffMaxLines');
 
+
if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) {
function navlinkStringToHTML(s,article,params) {
+
// truncate
//limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article));
+
truncated=true;
var p=navlinkStringToArray(s,article,params);
+
inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines),
var html='';
+
newlines.slice(0,pg.option.popupDiffMaxLines),
var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it
+
pg.option.popupDiffContextLines);
var menurowdepth = 0;
+
oldlines=inner.a; newlines=inner.b;
var wrapping = null;
 
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() {
+
var lineDiff=diff(oldlines, newlines);
this.type='navlinkTag';
+
var lines2=rmBoringLines(lineDiff.o, lineDiff.n);
}
+
var oldlines2=lines2.a; var newlines2=lines2.b;
  
navlinkTag.prototype.html=function () {
+
var simpleSplit = !String.prototype.parenSplit.isNative;
this.getNewWin();
+
var html='<hr />';
this.getPrintFunction();
+
if (getValueOf('popupDiffDates')) {
var html='';
+
html += diffDatesTable(navpop);
var opening, closing;
+
html += '<hr />';
var tagType='span';
 
if (!tagType) {
 
opening = ''; closing = '';
 
} else {
 
opening = '<' + tagType + ' class="popup_' + this.id + '">';
 
closing = '</' + tagType + '>';
 
 
}
 
}
if (typeof this.print!='function') {
+
html += shortenDiffString(
errlog ('Oh dear - invalid print function for a navlinkTag, id='+this.id);
+
diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),
 +
getValueOf('popupDiffContextCharacters') ).join('<hr />');
 +
setPopupTipsAndHTML(html.split('\n').join('<br>') +
 +
(truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') ,
 +
'popupPreview', navpop.idNumber);
 +
}
 +
 
 +
function diffDatesTable( navpop ) {
 +
var html='<table class="popup_diff_dates">';
 +
html += diffDatesTableRow( navpop.diffData.newRev.revision, tprintf('New revision'));
 +
html += diffDatesTableRow( navpop.diffData.oldRev.revision, tprintf('Old revision'));
 +
html += '</table>';
 +
return html;
 +
}
 +
function diffDatesTableRow( revision, label ) {
 +
var txt='';
 +
var lastModifiedDate = new Date(revision.timestamp);
 +
var datePrint=getValueOf('popupDiffDatePrinter');
 +
if (typeof lastModifiedDate[datePrint] == 'function') {
 +
var d2 = adjustDate(lastModifiedDate, getTimeOffset());
 +
txt = dayFormat(d2, true) + ' ' + timeFormat(d2, true);
 
} else {
 
} else {
html=this.print(this);
+
txt = tprintf('Invalid %s %s', ['popupDiffDatePrinter', datePrint]);
if (typeof html != typeof '') {html='';}
 
else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut);
 
 
}
 
}
return opening + html + closing;
+
var revlink = generalLink({url: mw.config.get('wgScript') + '?oldid='+revision.revid,
};
+
  text: label, title: label});
 
+
return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]);
navlinkTag.prototype.getNewWin=function() {
 
getValueOf('popupLinksNewWindow');
 
if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') { this.newWin=null; }
 
this.newWin=pg.option.popupLinksNewWindow[this.id];
 
 
}
 
}
 +
//</NOLITE>
 +
// ENDFILE: diffpreview.js
 +
// STARTFILE: links.js
 +
//<NOLITE>
 +
/////////////////////
 +
// LINK GENERATION //
 +
/////////////////////
  
navlinkTag.prototype.getPrintFunction=function() { //think about this some more
+
// titledDiffLink --> titledWikiLink --> generalLink
// this.id and this.article should already be defined
+
// wikiLink   --> titledWikiLink --> generalLink
if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; }
+
// editCounterLink --> generalLink
var html='';
 
var a,t;
 
  
this.noPopup=1;
+
// TODO Make these functions return Element objects, not just raw HTML strings.
switch (this.id) {
 
case 'contribs': case 'history': case 'whatLinksHere':
 
case 'userPage': case 'monobook': case 'userTalk':
 
case 'talk': case 'article': case 'lastEdit':
 
this.noPopup=null;
 
}
 
switch (this.id) {
 
case 'email': case 'contribs':  case 'block': case 'unblock':
 
case 'userlog':  case 'userSpace': case 'deletedContribs':
 
this.article=this.article.userName();
 
}
 
  
switch (this.id) {
+
function titledDiffLink(l) { // article, text, title, from, to) {
case 'userTalk': case 'newUserTalk': case 'editUserTalk':
+
return titledWikiLink({article: l.article, action: l.to + '&oldid=' + l.from,
case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog':
+
newWin: l.newWin,
this.article=this.article.userName(true);
+
noPopup: l.noPopup,
// fall through; no break
+
text: l.text, title: l.title,
case 'pagelog': case 'deletelog': case 'protectlog':
+
/* hack: no oldid here */
delete this.oldid;
+
actionName: 'diff'});
}
+
}
  
if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); }
 
  
if (this.id != 'mainlink') {
+
function wikiLink(l) {
// FIXME anchor handling should be done differently with Title object
+
//{article:article, action:action, text:text, oldid, newid}) {
this.article=this.article.removeAnchor();
+
if (! (typeof l.article == typeof {} &&
// if (typeof this.text=='undefined') this.text=popupString(this.id);
+
typeof l.action == typeof '' &&
}
+
typeof l.text==typeof '')) return null;
 
+
if (typeof l.oldid == 'undefined') { l.oldid=null; }
switch (this.id) {
+
var savedOldid = l.oldid;
case 'undelete':      this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break;
+
if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) { l.oldid=null; }
case 'whatLinksHere':  this.print=specialLink; this.specialpage='Whatlinkshere'; break;
+
var hint=popupString(l.action + 'Hint'); // revertHint etc etc etc
case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break;
+
var oldidData=[l.oldid, safeDecodeURI(l.article)];
case 'move':          this.print=specialLink; this.specialpage='Movepage'; break;
+
var revisionString = tprintf('revision %s of %s', oldidData);
case 'contribs':      this.print=specialLink; this.specialpage='Contributions'; break;
+
log('revisionString='+revisionString);
case 'deletedContribs':this.print=specialLink; this.specialpage='Deletedcontributions'; break;
+
switch (l.action) {
case 'email':         this.print=specialLink; this.specialpage='EmailUser'; this.sep='/'; break;
+
case 'edit&section=new': hint = popupString('newSectionHint'); break;
case 'block':          this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break;
+
case 'edit&undo=':
case 'unblock':        this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break;
+
if (l.diff && l.diff != 'prev' && savedOldid ) {
case 'userlog':       this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break;
+
  l.action += l.diff + '&undoafter=' + savedOldid;
case 'blocklog':       this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break;
+
} else if (savedOldid) {
case 'pagelog':        this.print=specialLink; this.specialpage='Log'; this.sep='&page='; break;
+
  l.action += savedOldid;
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;
+
hint = popupString('undoHint');
case 'userSpace':      this.print=specialLink; this.specialpage='PrefixIndex'; this.sep='&namespace=2&prefix='; break;
+
break;
case 'search':         this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break;
+
case 'raw&ctype=text/css': hint=popupString('rawHint'); break;
case 'unwatch': case 'watch':
+
case 'revert':
this.print=magicWatchLink; this.action=this.id+'&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken='+autoClickToken(); break;
+
var p=parseParams(pg.current.link.href);
case 'history': case 'historyfeed':  
+
l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff);
case 'unprotect': case 'protect':
+
if (p.diff=='prev') {
this.print=wikiLink; this.action=this.id; break;
+
l.action += '&direction=prev';
 
+
revisionString = tprintf('the revision prior to revision %s of %s', oldidData);
case 'delete':
+
}
this.print=wikiLink; this.action='delete';
+
if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; }
if (this.article.namespaceId()==pg.nsImageId) {
+
if (getValueOf('popupMinorReverts')) { l.action += '&autominor=true'; }
var img=this.article.stripNamespace();
+
log('revisionString is now '+revisionString);
this.action+='&image='+img;
+
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;
 
}
 
}
break;
 
  
case 'markpatrolled':
+
if (hint) {
case 'edit': // editOld should keep the oldid, but edit should not.
+
if (l.oldid) {
delete this.oldid; // fall through
+
hint = simplePrintf(hint, [revisionString]);
case 'view': case 'purge': case 'render':
+
}
this.print=wikiLink;
+
else {
this.action=this.id; break;
+
hint = simplePrintf(hint, [safeDecodeURI(l.article)]);
case 'raw':
 
this.print=wikiLink; this.action='raw&ctype=text/css'; break;
 
case 'new':
 
this.print=wikiLink; this.action='edit&section=new'; break;
 
case 'mainlink':
 
if (typeof this.text=='undefined') { this.text=this.article.toString().entify(); }
 
if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) {
 
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;
+
else {
case 'userPage':
+
hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid='+l.oldid : '';
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 'markpatrolled':
 
this.print=wikiLink; this.action='markpatrolled';
 
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');
 
case 'disablePopups': case 'purgePopups':
 
this.print=popupMenuLink;
 
break;
 
default:
 
this.print=function () {return 'Unknown navlink type: '+this.id+''};
 
}
 
};
 
//
 
//  end navlinks
 
//////////////////////////////////////////////////
 
//</NOLITE>
 
// ENDFILE: navlinks.js
 
// 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);
+
return titledWikiLink({article: l.article, action: l.action, text: l.text, newWin:l.newWin,
var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A');
+
title: hint, oldid: l.oldid, noPopup: l.noPopup, onclick: l.onclick});
var startLink=0;
+
}
var i,j;
+
 
 +
function revertSummary(oldid, diff) {
 +
var ret='';
 +
if (diff == 'prev') {
 +
ret=getValueOf('popupQueriedRevertToPreviousSummary');
 +
} else { ret = getValueOf('popupQueriedRevertSummary'); }
 +
return ret + '&autorv=' + oldid;
 +
}
 +
 
 +
function titledWikiLink(l) {
 +
// possible properties of argument:
 +
// article, action, text, title, oldid, actionName, className, noPopup
 +
// oldid = null is fine here
 +
 
 +
// article and action are mandatory args
  
if (popupHandleKeypress.lastPopupLinkSelected) {
+
if (typeof l.article == 'undefined' || typeof l.action=='undefined') {
for (i=0; i<links.length; ++i) {
+
errlog('got undefined article or action in titledWikiLink');
if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; }
+
return null;
}
 
 
}
 
}
for (j=0; j<links.length; ++j) {
 
i=(startLink + j + 1) % links.length;
 
if (links[i].getAttribute('popupkey')==letter) {
 
if (evt && evt.preventDefault) evt.preventDefault();
 
links[i].focus();
 
popupHandleKeypress.lastPopupLinkSelected=links[i];
 
return false; // swallow keypress
 
}
 
}
 
 
// pass keypress on
 
if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); }
 
return true;
 
}
 
  
function addPopupShortcuts() {
+
var base = pg.wiki.titlebase +  l.article.urlString();
if (document.onkeypress!=popupHandleKeypress) {
+
var url=base;
document.oldPopupOnkeypress=document.onkeypress;
+
 
}
+
if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; }
document.onkeypress=popupHandleKeypress;
+
 
}
+
// no need to add &action=view, and this confuses anchors
 +
if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; }
 +
 
 +
if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; }
 +
 
 +
var cssClass=pg.misc.defaultNavlinkClassname;
 +
if (typeof l.className!='undefined' && l.className) { cssClass=l.className; }
  
function rmPopupShortcuts() {
+
return generalNavLink({url: url, newWin: l.newWin,
popupHandleKeypress.lastPopupLinkSelected=null;
+
title: (typeof l.title != 'undefined') ? l.title : null,
try {
+
text: (typeof l.text!='undefined')?l.text:null,
if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) {
+
className: cssClass, noPopup:l.noPopup, onclick:l.onclick});
// panic
 
document.onkeypress=null; //function () {};
 
return;
 
}
 
document.onkeypress=document.oldPopupOnkeypress;
 
} catch (nasties) { /* IE goes here */ }
 
 
}
 
}
  
 +
pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) {
 +
getHistoryInfo(wikipage, function(x) {
 +
processLastContribInfo(x, {page: wikipage, newWin: newWin});
 +
});
 +
};
  
function addLinkProperty(html, property) {
+
function processLastContribInfo(info, stuff) {
// take "<a href=...>...</a> and add a property
+
if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; }
// not sophisticated at all, easily broken
+
if(!info.firstNewEditor) {
var i=html.indexOf('>');
+
alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor,info.edits.length]));
if (i<0) { return html; }
+
return;
return html.substring(0,i) + ' ' + property + html.substring(i);
+
}
 +
var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+info.firstNewEditor.oldid;
 +
displayUrl(newUrl, stuff.newWin);
 
}
 
}
  
function addPopupShortcut(html, key) {
+
pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) {
if (!getValueOf('popupShortcutKeys')) { return html; }
+
getHistoryInfo(wikipage, function(x){
var ret= addLinkProperty(html, 'popupkey="'+key+'"');
+
processDiffSinceMyEdit(x, {page: wikipage, newWin: newWin});
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() {
+
function processDiffSinceMyEdit(info, stuff) {
pendingNavpopTask(navpop);
+
if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; }
getWiki(article, doneDiffNew, newRev, navpop);
+
var friendlyName=stuff.page.split('_').join(' ');
 
+
if(!info.myLastEdit) {
pendingNavpopTask(navpop);
+
alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s',
getWiki(article, doneDiffOld, oldRev, navpop);
+
  [info.userName, getValueOf('popupHistoryLimit'), friendlyName]));
 
+
return;
var tz = Cookie.read('popTz');
 
if ( mw.config.get('wgEnableAPI') && getValueOf('popupAdjustDiffDates') && tz===null) {
 
pendingNavpopTask(navpop);
 
getPageWithCaching(pg.wiki.apiwikibase + '?format=json&action=query&meta=userinfo&uiprop=options',
 
  function(d) {
 
  completedNavpopTask(navpop);
 
  setTimecorrectionCookie(d);
 
  if (diffDownloadsComplete(navpop)) { insertDiff(navpop); }
 
  },  navpop);
 
}
 
return true; // remove hook once run
 
 
}
 
}
if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
+
if(info.myLastEdit.index === 0) {
else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); }
+
alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName]));
}
 
 
 
function setTimecorrectionCookie(d) {
 
try {
 
var jsobj=getJsObj(d.data);
 
var tz=jsobj.query.userinfo.options.timecorrection;
 
} catch (someError) {
 
logerr( 'setTimecorretion failed' );
 
 
return;
 
return;
 
}
 
}
Cookie.create( 'popTz', getTimeOffset(tz), 1);
+
var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+ info.myLastEdit.oldid;
 +
displayUrl(newUrl, stuff.newWin);
 
}
 
}
  
function doneDiff(download, isOld) {
+
function displayUrl(url, newWin){
if (!download.owner || !download.owner.diffData) { return; }
+
if(newWin) { window.open(url); }
var navpop=download.owner;
+
else { document.location=url; }
var label= (isOld) ? 'Old' : 'New';
 
var otherLabel=(isOld) ? 'New' : 'Old';
 
navpop.diffData[label]=download;
 
completedNavpopTask(download.owner);
 
if (diffDownloadsComplete(navpop)) { insertDiff(navpop); }
 
 
}
 
}
  
function diffDownloadsComplete(navpop) {
+
pg.fn.purgePopups = function purgePopups() {
if ( Cookie.read('popTz')===null) { return false; }
+
processAllPopups(true);
return navpop.diffData.Old && navpop.diffData.New;
+
setupCache(); // deletes all cached items (not browser cached, though...)
 +
pg.option={};
 +
abortAllDownloads();
 +
};
 +
 
 +
function processAllPopups(nullify, banish) {
 +
for (var i=0; pg.current.links && i<pg.current.links.length; ++i) {
 +
if (!pg.current.links[i].navpopup) { continue; }
 +
if (nullify || banish) pg.current.links[i].navpopup.banish();
 +
pg.current.links[i].simpleNoMore=false;
 +
if (nullify) pg.current.links[i].navpopup=null;
 +
}
 
}
 
}
  
function doneDiffNew(download) { doneDiff(download, false); }
+
pg.fn.disablePopups = function disablePopups(){
function doneDiffOld(download) { doneDiff(download, true); }
+
processAllPopups(false, true);
 +
setupTooltips(null, true);
 +
};
  
function rmBoringLines(a,b,context) {
+
pg.fn.togglePreviews = function togglePreviews() {
 +
processAllPopups(true, true);
 +
pg.option.simplePopups=!pg.option.simplePopups;
 +
abortAllDownloads();
 +
};
 +
 
 +
function magicWatchLink(l) {
 +
//Yuck!! Would require a thorough redesign to add this as a click event though ...
 +
l.onclick = simplePrintf( 'pg.fn.modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id] );
 +
return wikiLink(l);
 +
}
  
if (typeof context == 'undefined') { context=2; }
+
pg.fn.modifyWatchlist = function modifyWatchlist(title, action) {
// this is fairly slow... i think it's quicker than doing a word-based diff from the off, though
+
var reqData = {
var aa=[], aaa=[];
+
'action': 'watch',
var bb=[], bbb=[];
+
'formatversion': 2,
var i, j;
+
'titles': title,
 +
'uselang': mw.config.get('wgUserLanguage')
 +
};
 +
if ( action === 'unwatch' ) reqData.unwatch = true;
  
// first, gather all disconnected nodes in a and all crossing nodes in a and b
+
var api = new mw.Api( {
for (i=0; i<a.length; ++i ) {
+
    ajax: {
if(!a[i].paired) { aa[i]=1; }
+
        headers: { 'Api-User-Agent': pg.misc.userAgent }
else if (countCrossings(b,a,i, true)) {
+
    }
aa[i]=1;
+
} );
bb[ a[i].row ] = 1;
+
// Load the Addedwatchtext or Removedwatchtext message and show it
}
+
var mwTitle = mw.Title.newFromText( title );
 +
var messageName;
 +
if ( mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) {
 +
messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk';
 +
} else {
 +
messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext';
 
}
 
}
 +
$.when(
 +
api.postWithToken( 'watch', reqData ),
 +
mw.loader.using( [ 'mediawiki.api.messages', 'mediawiki.jqueryMsg' ] ).then( function () {
 +
return api.loadMessagesIfMissing( [ messageName ] );
 +
} )
 +
).done( function () {
 +
mw.notify( mw.message( messageName, title ).parseDom() );
 +
} );
 +
};
  
// pick up remaining disconnected nodes in b
+
function magicHistoryLink(l) {
for (i=0; i<b.length; ++i ) {
+
// FIXME use onclick change href trick to sort this out instead of window.open
if (bb[i]==1) { continue; }
 
if(!b[i].paired) { bb[i]=1; }
 
}
 
  
// another pass to gather context: we want the neighbours of included nodes which are not yet included
+
var jsUrl='', title='', onClick='';
// we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass
+
switch(l.id) {
for (i=0; i<b.length; ++i) {
+
case 'lastContrib':
if ( bb[i] == 1 ) {
+
onClick=simplePrintf('pg.fn.getLastContrib(\'%s\',%s)',
for (j=max(0,i-context); j < min(b.length, i+context); ++j) {
+
[l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);
if ( !bb[j] ) { bb[j] = 1; aa[ b[j].row ] = 0.5; }
+
title=popupString('lastContribHint');
}
+
break;
}
+
case 'sinceMe':
 +
onClick=simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)',
 +
[l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);
 +
title=popupString('sinceMeHint');
 +
break;
 
}
 
}
 +
jsUrl = 'javascript:' + onClick; // jshint ignore:line
 +
onClick += ';return false;';
  
for (i=0; i<a.length; ++i) {
+
return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think
if ( aa[i] == 1 ) {
+
title: title, text: l.text, noPopup: l.noPopup, onclick: onClick });
for (j=max(0,i-context); j < min(a.length, i+context); ++j) {
+
}
if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; }
+
 
}
+
function popupMenuLink(l) {
}
+
var jsUrl=simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line
}
+
var title=popupString(simplePrintf('%sHint', [l.id]));
 +
var onClick=simplePrintf('pg.fn.%s();return false;', [l.id]);
 +
return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup, onclick: onClick});
 +
}
  
for (i=0; i<bb.length; ++i) {
+
function specialLink(l) {
if (bb[i] > 0) { // it's a row we need
+
// properties: article, specialpage, text, sep
if (b[i].paired) { bbb.push(b[i].text); } // joined; partner should be in aa
+
if (typeof l.specialpage=='undefined'||!l.specialpage) return null;
else {
+
var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage;
bbb.push(b[i]);
+
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) {
for (i=0; i<aa.length; ++i) {
+
case 'Log':
if (aa[i] > 0) { // it's a row we need
+
switch (l.sep) {
if (a[i].paired) { aaa.push(a[i].text); } // joined; partner should be in aa
+
case '&user=': hint=popupString('userLogHint'); break;
else {
+
case '&type=block&page=': hint=popupString('blockLogHint'); break;
aaa.push(a[i]);
+
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) ;
  
return { a: aaa, b: bbb};
+
var url = base + l.sep + article;
 +
return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin, noPopup:l.noPopup});
 
}
 
}
  
function stripOuterCommonLines(a,b,context) {
+
function generalLink(l) {
var i=0;
+
// l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick
while (i<a.length && i < b.length && a[i]==b[i]) { ++i; }
+
if (typeof l.url=='undefined') return null;
var j=a.length-1; var k=b.length-1;
+
 
while ( j>=0 && k>=0 && a[j]==b[k] ) { --j; --k; }
+
// only quotation marks in the url can screw us up now... I think
 +
var url=l.url.split('"').join('%22');
  
return { a: a.slice(max(0,i - 1 - context), min(a.length+1, j + context+1)),
+
var ret='<a href="' + url + '"';
b: b.slice(max(0,i - 1 - context), min(b.length+1, k + context+1)) };
+
if (typeof l.title!='undefined' && l.title) { ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"'; }
 +
if (typeof l.onclick!='undefined' && l.onclick) { ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"'; }
 +
if (l.noPopup) { ret += ' noPopup=1'; }
 +
var newWin;
 +
if (typeof l.newWin=='undefined' || l.newWin === null) { newWin=getValueOf('popupNewWindows'); }
 +
else { newWin=l.newWin; }
 +
if (newWin) { ret += ' target="_blank"'; }
 +
if (typeof l.className!='undefined'&&l.className) { ret+=' class="'+l.className+'"'; }
 +
ret += '>';
 +
if (typeof l.text==typeof '') { ret+= l.text; }
 +
ret +='</a>';
 +
return ret;
 
}
 
}
  
function insertDiff(navpop) {
+
function appendParamsToLink(linkstr, params) {
// for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff
+
var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i'));
// FIXME: sometimes this gives misleading diffs as distant chunks are squashed together
+
if (sp.length<2) return null;
var oldlines=navpop.diffData.Old.data.split('\n');
+
var ret=sp.shift() + sp.shift();
var newlines=navpop.diffData.New.data.split('\n');
+
ret += '&' + params + '"';
var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines'));
+
ret += sp.join('');
oldlines=inner.a; newlines=inner.b;
+
return ret;
var truncated=false;
+
}
getValueOf('popupDiffMaxLines');
+
 
if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) {
+
function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional) {
// truncate
+
if (x.newTarget) {
truncated=true;
+
log ('changeLinkTargetLink: newTarget=' + x.newTarget);
inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines),
+
}
newlines.slice(0,pg.option.popupDiffMaxLines),
+
if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) {
pg.option.popupDiffContextLines);
+
log ('This might be an input problem: ' + x.oldTarget );
oldlines=inner.a; newlines=inner.b;
 
 
}
 
}
  
var lineDiff=diff(oldlines, newlines);
+
// FIXME: first character of page title as well as namespace should be case insensitive
var lines2=rmBoringLines(lineDiff.o, lineDiff.n);
+
// eg [[category:X1]] and [[Category:X1]] are equivalent
var oldlines2=lines2.a; var newlines2=lines2.b;
+
// this'll break if charAt(0) is nasty
 +
var cA=literalizeRegex(x.oldTarget);
 +
var chs=cA.charAt(0).toUpperCase();
 +
chs='['+chs + chs.toLowerCase()+']';
 +
var currentArticleRegexBit=chs+cA.substring(1);
 +
currentArticleRegexBit=currentArticleRegexBit
 +
.split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)')
 +
.split('\\(').join('(?:%28|\\()')
 +
.split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ?
 +
// leading and trailing space should be ignored, and anchor bits optional:
 +
currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*';
 +
// e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s*
 +
 
 +
// autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g
  
var simpleSplit = !String.prototype.parenSplit.isNative;
+
var title=x.title || mw.config.get('wgPageName').split('_').join(' ');
var html='<hr />';
+
var lk=titledWikiLink({article: new Title(title), newWin:x.newWin,
if (getValueOf('popupDiffDates')) {
+
action:  'edit',
html += diffDatesTable(navpop.diffData.Old, navpop.diffData.New);
+
text: x.text,
html += '<hr />';
+
title:  x.hint,
 +
className: 'popup_change_title_link'
 +
});
 +
var cmd='';
 +
if (x.newTarget) {
 +
// escape '&' and other nasties
 +
var t=x.newTarget;
 +
var s=literalizeRegex(x.newTarget);
 +
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';
 
}
 
}
html += shortenDiffString(
+
// Build query
diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),
+
cmd = 'autoedit=' + encodeURIComponent ( cmd );
getValueOf('popupDiffContextCharacters') ).join('<hr />');
+
cmd += '&autoclick='+ encodeURIComponent( x.clickButton ) + '&actoken=' + encodeURIComponent( autoClickToken() );
setPopupTipsAndHTML(html.split('\n').join('<br>') +
+
cmd += ( x.minor === null ) ? '' : '&autominor='+ encodeURIComponent( x.minor );
(truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') ,
+
cmd += ( x.watch === null ) ? '' : '&autowatch='+ encodeURIComponent( x.watch );
'popupPreview', navpop.idNumber);
+
cmd += '&autosummary='+encodeURIComponent(x.summary);
 +
cmd += '&autoimpl='+encodeURIComponent( popupString('autoedit_version') );
 +
return appendParamsToLink(lk, cmd);
 
}
 
}
  
function diffDatesTable( oldDl, newDl ) {
+
 
var html='<table class="popup_diff_dates">';
+
function redirLink(redirMatch, article) {
html += diffDatesTableRow( newDl, tprintf('New revision'));
+
// NB redirMatch is in wikiText
html += diffDatesTableRow( oldDl, tprintf('Old revision'));
+
var ret='';
html += '</table>';
+
 
return html;
+
if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) {
}
+
ret += '<hr />';
function diffDatesTableRow( dl, label ) {
+
if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) {
var txt='';
+
log('redirLink: newTarget=' + redirMatch);
if (!dl) {
+
ret += addPopupShortcut(changeLinkTargetLink({
txt=popupString('Something went wrong :-(');
+
newTarget: redirMatch,
} else if (!dl.lastModified) {
+
text: popupString('Redirects'),
txt= (/^\s*$/.test(dl.data)) ?
+
hint: popupString('Fix this redirect'),
popupString('Empty revision, maybe non-existent') : popupString('Unknown date');
+
summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]),
} else {
+
oldTarget: article.toString(),
var datePrint=getValueOf('popupDiffDatePrinter');
+
clickButton: getValueOf('popupRedirAutoClick'),
if (typeof dl.lastModified[datePrint] == 'function') {
+
minor: true,
if (getValueOf('popupAdjustDiffDates')) {
+
watch: getValueOf('popupWatchRedirredPages')
var off;
+
}), 'R');
if (off=Cookie.read('popTz')) {
+
ret += popupString(' to ');
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]);
 
 
}
 
}
 +
else ret += popupString('Redirects') + popupString(' to ');
 +
return ret;
 
}
 
}
var revlink = generalLink({url: dl.url.replace(/&.*?(oldid=[0-9]+(?:&direction=[^&]*)?).*/, '&$1'),
+
 
  text: label, title: label});
+
else return '<br> ' + popupString('Redirects') + popupString(' to ') +
return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]);
+
titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view', /* FIXME: newWin */
 +
  text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')});
 
}
 
}
//</NOLITE>
 
// ENDFILE: diffpreview.js
 
// STARTFILE: links.js
 
//<NOLITE>
 
/////////////////////
 
// LINK GENERATION //
 
/////////////////////
 
  
// titledDiffLink --> titledWikiLink --> generalLink
+
function arinLink(l) {
// wikiLink   --> titledWikiLink --> generalLink
+
if (!saneLinkCheck(l)) { return null; }
// editCounterLink --> generalLink
+
if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null;
  
function titledDiffLink(l) { // article, text, title, from, to) {
+
var uN=l.article.userName();
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'});
 
}
 
  
 
+
return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin,
function wikiLink(l) {
+
title: tprintf('Look up %s in ARIN whois database', [uN]),
//{article:article, action:action, text:text, oldid, newid}) {
+
text: l.text, noPopup:1});
if (! (typeof l.article == typeof {}
+
}
  && typeof l.action == typeof '' && typeof l.text==typeof '')) return null;
+
 
if (typeof l.oldid == 'undefined') { l.oldid=null; }
+
function toolDbName(cookieStyle) {
var savedOldid = l.oldid;
+
var ret = mw.config.get('wgDBname');
if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) { l.oldid=null; }
+
if (!cookieStyle) { ret+= '_p'; }
var hint=popupString(l.action + 'Hint'); // revertHint etc etc etc
+
return ret;
var oldidData=[l.oldid, safeDecodeURI(l.article)];
+
}
var revisionString = tprintf('revision %s of %s', oldidData);
+
 
log('revisionString='+revisionString);
+
function saneLinkCheck(l) {
switch (l.action) {
+
if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; }
case 'edit&section=new': hint = popupString('newSectionHint');  break;
+
return true;
case 'edit&undo=':
+
}
if (l.diff && l.diff != 'prev' && savedOldid ) {
+
function editCounterLink(l) {
  l.action += l.diff + '&undoafter=' + savedOldid;
+
if(!saneLinkCheck(l)) return null;
} else if (savedOldid) {
+
if (! pg.wiki.wikimedia) return null;
  l.action += savedOldid;
+
var uN=l.article.userName();
}
+
var tool=getValueOf('popupEditCounterTool');
hint = popupString('undoHint');
+
var url;
 +
var defaultToolUrl='//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3';
 +
 
 +
switch(tool) {
 +
case 'custom':
 +
url=simplePrintf(getValueOf('popupEditCounterUrl'), [ encodeURIComponent(uN), toolDbName() ]);
 
break;
 
break;
case 'raw&ctype=text/css': hint=popupString('rawHint'); break;
+
case 'soxred': // no longer available
case 'revert':
+
case 'kate':    // no longer available
if ( !mw.config.get('wgEnableAPI') ) {
+
case 'interiot':// no longer available
alert( 'This function of navigation popups now requires a MediaWiki ' +
+
/* fall through */
'installation with the API enabled.');
+
case 'supercount':
break;
+
default:
}
+
var theWiki=pg.wiki.hostname.split('.');
var p=parseParams(pg.current.link.href);
+
url=simplePrintf(defaultToolUrl, [ encodeURIComponent(uN), theWiki[0], theWiki[1] ]);
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;
 
 
}
 
}
 +
return generalNavLink({url:url, title: tprintf('editCounterLinkHint', [uN]),
 +
newWin:l.newWin, text: l.text, noPopup:1});
 +
}
  
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,
+
function globalSearchLink(l) {
title: hint, oldid: l.oldid, noPopup: l.noPopup, onclick: l.onclick});
+
if(!saneLinkCheck(l)) return null;
}
+
 
 +
var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search=';
 +
var article=l.article.urlString({keepSpaces:true});
  
function revertSummary(oldid, diff) {
+
return generalNavLink({url:base + article, newWin:l.newWin,
var ret='';
+
title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]),
if (diff == 'prev') {
+
text: l.text, noPopup:1});
ret=getValueOf('popupQueriedRevertToPreviousSummary');
 
} else { ret = getValueOf('popupQueriedRevertSummary'); }
 
return ret + '&autorv=' + oldid;
 
 
}
 
}
  
function titledWikiLink(l) {
+
function googleLink(l) {
// possible properties of argument:
+
if(!saneLinkCheck(l)) return null;
// article, action, text, title, oldid, actionName, className, noPopup
 
// oldid = null is fine here
 
  
// article and action are mandatory args
+
var base='https://www.google.com/search?q=';
 +
var article=l.article.urlString({keepSpaces:true});
  
if (typeof l.article == 'undefined' || typeof l.action=='undefined') {
+
return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin,
errlog('got undefined article or action in titledWikiLink');
+
title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]),
return null;
+
text: l.text, noPopup:1});
}
+
}
  
var base = pg.wiki.titlebase + l.article.urlString();
+
function editorListLink(l) {
var url=base;
+
if(!saneLinkCheck(l)) return null;
 +
var article= l.article.articleFromTalkPage() || l.article;
 +
var url='https://xtools.wmflabs.org/articleinfo/' +
 +
encodeURI( pg.wiki.hostname ) + '/' +
 +
article.urlString() +
 +
'?uselang=' + mw.config.get('wgUserLanguage');
 +
return generalNavLink({url:url,
 +
title: tprintf('editorListHint', [article]),
 +
newWin:l.newWin, text: l.text, noPopup:1});
 +
}
  
if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; }
+
function generalNavLink(l) {
 +
l.className = (l.className === null) ? 'popupNavLink' : l.className;
 +
return generalLink(l);
 +
}
  
// no need to add &action=view, and this confuses anchors
+
//////////////////////////////////////////////////
if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; }
+
// magic history links
 +
//
  
if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; }
+
function getHistoryInfo(wikipage, whatNext) {
 +
log('getHistoryInfo');
 +
getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory);
 +
}
  
var cssClass=pg.misc.defaultNavlinkClassname;
+
// FIXME eliminate pg.idNumber ... how? :-(
if (typeof l.className!='undefined' && l.className) { cssClass=l.className; }
 
  
return generalNavLink({url: url, newWin: l.newWin,
+
function getHistory(wikipage, onComplete) {
title: (typeof l.title != 'undefined') ? l.title : null,
+
log('getHistory');
text: (typeof l.text!='undefined')?l.text:null,
+
var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' +
className: cssClass, noPopup:l.noPopup, onclick:l.onclick});
+
new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit');
 +
log('getHistory: url='+url);
 +
return startDownload(url, pg.idNumber+'history', onComplete);
 
}
 
}
  
function getLastContrib(wikipage, newWin) {
+
function processHistory(download) {
getHistoryInfo(wikipage, function(x){processLastContribInfo(x,{page: wikipage, newWin: newWin})});
+
var jsobj = getJsObj(download.data);
}
+
try {
function processLastContribInfo(info, stuff) {
+
var revisions = anyChild(jsobj.query.pages).revisions;
if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; }
+
var edits=[];
if(!info.firstNewEditor) {
+
for (var i=0; i<revisions.length; ++i) {
alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor,info.edits.length]));
+
edits.push({ oldid: revisions[i].revid, editor: revisions[i].user });
return;
+
}
 +
log('processed ' + edits.length + ' edits');
 +
return finishProcessHistory( edits, mw.config.get('wgUserName') );
 +
} catch (someError) {
 +
log('Something went wrong with JSON business');
 +
return finishProcessHistory([]);
 
}
 
}
var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+info.firstNewEditor.oldid;
 
displayUrl(newUrl, stuff.newWin);
 
 
}
 
}
function getDiffSinceMyEdit(wikipage, newWin) {
+
 
getHistoryInfo(wikipage, function(x){processDiffSinceMyEdit(x,{page: wikipage, newWin: newWin})});
+
 
 +
function finishProcessHistory(edits, userName) {
 +
var histInfo={};
 +
 
 +
histInfo.edits=edits;
 +
histInfo.userName=userName;
 +
 
 +
for (var i=0; i<edits.length; ++i) {
 +
if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor==userName) {
 +
histInfo.myLastEdit={index: i, oldid: edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)};
 +
}
 +
if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor != edits[0].editor) {
 +
histInfo.firstNewEditor={index:i, oldid:edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)};
 +
}
 +
}
 +
//pg.misc.historyInfo=histInfo;
 +
return histInfo;
 
}
 
}
function processDiffSinceMyEdit(info, stuff) {
+
//</NOLITE>
if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; }
+
// ENDFILE: links.js
var friendlyName=stuff.page.split('_').join(' ');
+
// STARTFILE: options.js
if(!info.myLastEdit) {
+
//////////////////////////////////////////////////
alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s',
+
// options
  [info.userName, getValueOf('popupHistoryLimit'), friendlyName]));
+
 
return;
+
// check for existing value, else use default
}
+
function defaultize(x) {
if(info.myLastEdit.index==0) {
+
if (pg.option[x]===null || typeof pg.option[x]=='undefined') {
alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName]));
+
if (typeof window[x] != 'undefined' ) pg.option[x]=window[x];
return;
+
else pg.option[x]=pg.optionDefault[x];
 
}
 
}
var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+ info.myLastEdit.oldid;
 
displayUrl(newUrl, stuff.newWin);
 
 
}
 
}
function displayUrl(url, newWin){
+
 
if(newWin) { window.open(url); }
+
function newOption(x, def) {
else { document.location=url; }
+
pg.optionDefault[x]=def;
 
}
 
}
  
function purgePopups() {
+
function setDefault(x, def) {
processAllPopups(true);
+
return newOption(x, def);
setupCache(); // deletes all cached items (not browser cached, though...)
 
pg.option={};
 
abortAllDownloads();
 
 
}
 
}
  
function processAllPopups(nullify, banish) {
+
function getValueOf(varName) {
for (var i=0; pg.current.links && i<pg.current.links.length; ++i) {
+
defaultize(varName);
if (!pg.current.links[i].navpopup) { continue; }
+
return pg.option[varName];
(nullify || banish) && pg.current.links[i].navpopup.banish();
 
pg.current.links[i].simpleNoMore=false;
 
nullify && (pg.current.links[i].navpopup=null);
 
}
 
 
}
 
}
  
function disablePopups(){
+
/*eslint-disable */
processAllPopups(false, true);
+
function useDefaultOptions() { // for testing
setupTooltips(null, true);
+
for (var p in pg.optionDefault) {
 +
pg.option[p]=pg.optionDefault[p];
 +
if (typeof window[p]!='undefined') { delete window[p]; }
 +
}
 
}
 
}
 +
/*eslint-enable */
  
function togglePreviews() {
+
function setOptions() {
processAllPopups(true, true);
+
// user-settable parameters and defaults
pg.option.simplePopups=!pg.option.simplePopups;
+
var userIsSysop = false;
abortAllDownloads();
+
if ( mw.config.get('wgUserGroups') ) {
}
+
for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) {
 +
if ( mw.config.get('wgUserGroups')[g] == "sysop" )
 +
userIsSysop = true;
 +
}
 +
}
  
function magicWatchLink(l) {
+
// Basic options
//Yuck!! Would require a thorough redesign to add this as a click event though ...
+
newOption('popupDelay',              0.5);
l.onclick = simplePrintf( 'modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id] );
+
newOption('popupHideDelay',          0.5);
return wikiLink(l);
+
newOption('simplePopups',            false);
}
+
newOption('popupStructure',           'shortmenus');   // see later - default for popupStructure is 'original' if simplePopups is true
 
+
newOption('popupActionsMenu',         true);
function modifyWatchlist(title, action) {
+
newOption('popupSetupMenu',          true);
var reqData = {
+
newOption('popupAdminLinks',          userIsSysop);
'action': 'watch',
+
newOption('popupShortcutKeys',        false);
'format': 'json',
+
newOption('popupHistoricalLinks',     true);
'title': title,
+
newOption('popupOnlyArticleLinks',    true);
'token': mw.user.tokens.get('watchToken'),
+
newOption('removeTitles',             true);
'uselang': mw.config.get('wgUserLanguage')
+
newOption('popupMaxWidth',            350);
};
+
newOption('popupSimplifyMainLink',   true);
if (action==='unwatch') reqData.unwatch = '';
+
newOption('popupAppendRedirNavLinks', true);
 
+
newOption('popupTocLinks',           false);
jQuery.ajax({
+
newOption('popupSubpopups',           true);
url: mw.util.wikiScript('api'),
+
newOption('popupDragHandle',          false /* 'popupTopLinks'*/);
dataType: 'json',
+
newOption('popupLazyPreviews',        true);
type: 'POST',
+
newOption('popupLazyDownloads',      true);
data: reqData,
+
newOption('popupAllDabsStubs',        false);
success: function( data, textStatus, xhr ) {
+
newOption('popupDebugging',          false);
mw.util.jsMessage( data.watch.message, 'watch' );
+
newOption('popupActiveNavlinks',     true);
}
+
newOption('popupModifier',           false); // ctrl, shift, alt or meta
});
+
newOption('popupModifierAction',     'enable'); // or 'disable'
}
+
newOption('popupDraggable',          true);
 +
newOption('popupReview',              false);
  
function magicHistoryLink(l) {
+
//<NOLITE>
// FIXME use onclick change href trick to sort this out instead of window.open
+
// images
 +
newOption('popupImages',                true);
 +
newOption('imagePopupsForImages',        true);
 +
newOption('popupNeverGetThumbs',        false);
 +
//newOption('popupImagesToggleSize',      true);
 +
newOption('popupThumbAction',            'imagepage'); //'sizetoggle');
 +
newOption('popupImageSize',              60);
 +
newOption('popupImageSizeLarge',        200);
  
var jsUrl='', title='';
+
// redirs, dabs, reversion
switch(l.id) {
+
newOption('popupFixRedirs',             false);
case 'lastContrib':
+
newOption('popupRedirAutoClick',        'wpDiff');
jsUrl=simplePrintf('javascript:getLastContrib(\'%s\',%s)',
+
newOption('popupFixDabs',              false);
[l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);
+
newOption('popupDabsAutoClick',        'wpDiff');
title=popupString('lastContribHint');
+
newOption('popupRevertSummaryPrompt',   false);
break;
+
newOption('popupMinorReverts',         false);
case 'sinceMe':
+
newOption('popupRedlinkRemoval',        false);
jsUrl=simplePrintf('javascript:getDiffSinceMyEdit(\'%s\',%s)',
+
newOption('popupRedlinkAutoClick',      'wpDiff');
[l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);
+
newOption('popupWatchDisambiggedPages', null);
title=popupString('sinceMeHint');
+
newOption('popupWatchRedirredPages',   null);
break;
+
newOption('popupDabWiktionary',        'last');
}
 
  
return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think
+
// navlinks
title: title, text: l.text, noPopup: l.noPopup});
+
newOption('popupNavLinks',         true);
}
+
newOption('popupNavLinkSeparator', ' &sdot; ');
 +
newOption('popupLastEditLink',     true);
 +
newOption('popupEditCounterTool',   'supercount');
 +
newOption('popupEditCounterUrl',   '');
 +
//</NOLITE>
  
function popupMenuLink(l) {
+
// previews etc
var jsUrl=simplePrintf('javascript:%s()', [l.id]);
+
newOption('popupPreviews',            true);
var title=popupString(simplePrintf('%sHint', [l.id]));
+
newOption('popupSummaryData',          true);
return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup});
+
newOption('popupMaxPreviewSentences',  5);
}
+
newOption('popupMaxPreviewCharacters', 600);
 +
newOption('popupLastModified',        true);
 +
newOption('popupPreviewKillTemplates', true);
 +
newOption('popupPreviewRawTemplates',  true);
 +
newOption('popupPreviewFirstParOnly', true);
 +
newOption('popupPreviewCutHeadings',   true);
 +
newOption('popupPreviewButton',       false);
 +
newOption('popupPreviewButtonEvent',   'click');
  
function specialLink(l) {
+
//<NOLITE>
// properties: article, specialpage, text, sep
+
// diffs
if (typeof l.specialpage=='undefined'||!l.specialpage) return null;
+
newOption('popupPreviewDiffs',          true);
var base = pg.wiki.titlebase +  mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage;
+
newOption('popupDiffMaxLines',          100);
if (typeof l.sep == 'undefined' || l.sep===null) l.sep='&target=';
+
newOption('popupDiffContextLines',      2);
var article=l.article.urlString({keepSpaces: l.specialpage=='Search'});
+
newOption('popupDiffContextCharacters', 40);
var hint=popupString(l.specialpage+'Hint');
+
newOption('popupDiffDates',            true);
switch (l.specialpage) {
+
newOption('popupDiffDatePrinter',       'toLocaleString');
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;
+
// edit summaries. God, these are ugly.
return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin, noPopup:l.noPopup});
+
newOption('popupReviewedSummary',          popupString('defaultpopupReviewedSummary') );
}
+
newOption('popupFixDabsSummary',          popupString('defaultpopupFixDabsSummary') );
 +
newOption('popupExtendedRevertSummary',    popupString('defaultpopupExtendedRevertSummary') );
 +
newOption('popupRevertSummary',            popupString('defaultpopupRevertSummary') );
 +
newOption('popupRevertToPreviousSummary',  popupString('defaultpopupRevertToPreviousSummary') );
 +
newOption('popupQueriedRevertSummary',            popupString('defaultpopupQueriedRevertSummary') );
 +
newOption('popupQueriedRevertToPreviousSummary',  popupString('defaultpopupQueriedRevertToPreviousSummary') );
 +
newOption('popupFixRedirsSummary',        popupString('defaultpopupFixRedirsSummary') );
 +
newOption('popupRedlinkSummary',          popupString('defaultpopupRedlinkSummary') );
 +
newOption('popupRmDabLinkSummary',        popupString('defaultpopupRmDabLinkSummary') );
 +
//</NOLITE>
 +
// misc
 +
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>
  
function generalLink(l) {
+
// new windows
// l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick
+
newOption('popupNewWindows',     false);
if (typeof l.url=='undefined') return null;
+
newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true});
  
// only quotation marks in the url can screw us up now... I think
+
// regexps
var url=l.url.split('"').join('%22');
+
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('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template
var ret='<a href="' + url + '"';
+
newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub');
if (typeof l.title!='undefined' && l.title) { ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"'; }
+
newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo');
if (typeof l.onclick!='undefined' && l.onclick) { ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"'; }
+
}
if (l.noPopup) { ret += ' noPopup=1'; }
+
// ENDFILE: options.js
var newWin;
+
// STARTFILE: strings.js
if (typeof l.newWin=='undefined' || l.newWin===null) { newWin=getValueOf('popupNewWindows'); }
+
//<NOLITE>
else { newWin=l.newWin; }
+
//////////////////////////////////////////////////
if (newWin) { ret += ' target="_blank"'; }
+
// Translatable strings
if (typeof l.className!='undefined'&&l.className) { ret+=' class="'+l.className+'"'; }
+
//////////////////////////////////////////////////
ret += '>';
+
//
if (typeof l.text==typeof '') { ret+= l.text; }
+
// See instructions at
ret +='</a>';
+
// https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation
return ret;
 
}
 
  
function appendParamsToLink(linkstr, params) {
+
pg.string = {
var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i'));
+
/////////////////////////////////////
if (sp.length<2) return null;
+
// summary data, searching etc.
var ret=sp.shift() + sp.shift();
+
/////////////////////////////////////
ret += '&' + params + '"';
+
'article': 'article',
ret += sp.join('');
+
'category': 'category',
return ret;
+
'categories': 'categories',
}
+
'image': 'image',
 
+
'images': 'images',
function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional) {
+
'stub': 'stub',
if (x.newTarget) {
+
'section stub': 'section stub',
log ('changeLinkTargetLink: newTarget=' + x.newTarget);
+
'Empty page': 'Empty page',
}
+
'kB': 'kB',
if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) {
+
'bytes': 'bytes',
log ('This might be an input problem: ' + x.oldTarget );
+
'day': 'day',
}
+
'days': 'days',
 
+
'hour': 'hour',
// FIXME: first character of page title as well as namespace should be case insensitive
+
'hours': 'hours',
// eg [[category:foo]] and [[Category:Foo]] are equivalent
+
'minute': 'minute',
// this'll break if charAt(0) is nasty
+
'minutes': 'minutes',
var cA=literalizeRegex(x.oldTarget);
+
'second': 'second',
var chs=cA.charAt(0).toUpperCase();
+
'seconds': 'seconds',
chs='['+chs + chs.toLowerCase()+']';
+
'week': 'week',
var currentArticleRegexBit=chs+cA.substring(1);
+
'weeks': 'weeks',
currentArticleRegexBit=currentArticleRegexBit
+
'search': 'search',
.split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)')
+
'SearchHint': 'Find English Wikipedia articles containing %s',
.split('\\(').join('(?:%28|\\()')
+
'web': 'web',
.split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ?
+
'global': 'global',
// leading and trailing space should be ignored, and anchor bits optional:
+
'globalSearchHint': 'Search across Wikipedias in different languages for %s',
currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*';
+
'googleSearchHint': 'Google for %s',
// e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s*
+
/////////////////////////////////////
 
+
// article-related actions and info
// autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g
+
// (some actions also apply to user pages)
 
+
/////////////////////////////////////
var title=x.title || mw.config.get('wgPageName').split('_').join(' ');
+
'actions': 'actions', ///// view articles and view talk
var lk=titledWikiLink({article: new Title(title), newWin:x.newWin,
+
'popupsMenu': 'popups',
action: 'edit',
+
'togglePreviewsHint': 'Toggle preview generation in popups on this page',
text: x.text,
+
'enable previews': 'enable previews',
title:   x.hint,
+
'disable previews': 'disable previews',
className: 'popup_change_title_link'
+
'toggle previews': 'toggle previews',
});
+
'show preview': 'show preview',
var cmd='';
+
'reset': 'reset',
if (x.newTarget) {
+
'more...': 'more...',
// escape '&' and other nasties
+
'disable': 'disable popups',
var t=x.newTarget;
+
'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.',
var s=literalizeRegex(x.newTarget);
+
'historyfeedHint': 'RSS feed of recent changes to this page',
cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~[['+t+'|$1]]~g;';
+
'purgePopupsHint': 'Reset popups, clearing all cached popup data.',
cmd += 's~\\[\\['+currentArticleRegexBit+'[|]~[['+t+'|~g;';
+
'PopupsHint': 'Reset popups, clearing all cached popup data.',
cmd += 's~\\[\\['+s + '\\|' + s + '\\]\\]~[[' + t + ']]~g';
+
'spacebar': 'space',
} else {
+
'view': 'view',
cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~$1~g;';
+
'view article': 'view article',
cmd += 's~\\[\\['+currentArticleRegexBit+'[|](.*?)\\]\\]~$2~g';
+
'viewHint': 'Go to %s',
}
+
'talk': 'talk',
// Build query
+
'talk page': 'talk page',
cmd = 'autoedit=' + encodeURIComponent ( cmd );
+
'this&nbsp;revision': 'this&nbsp;revision',
cmd += '&autoclick='+ encodeURIComponent( x.clickButton ) + '&actoken=' + encodeURIComponent( autoClickToken() );
+
'revision %s of %s': 'revision %s of %s',
cmd += ( x.minor == null ) ? '' : '&autominor='+ encodeURIComponent( x.minor );
+
'Revision %s of %s': 'Revision %s of %s',
cmd += ( x.watch == null ) ? '' : '&autowatch='+ encodeURIComponent( x.watch );
+
'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s',
cmd += '&autosummary='+encodeURIComponent(x.summary);
+
'Toggle image size': 'Click to toggle image size',
cmd += '&autoimpl='+encodeURIComponent( popupString('autoedit_version') );
+
'del': 'del', ///// delete, protect, move
return appendParamsToLink(lk, cmd);
+
'delete': 'delete',
}
+
'deleteHint': 'Delete %s',
 
+
'undeleteShort': 'un',
 
+
'UndeleteHint': 'Show the deletion history for %s',
function redirLink(redirMatch, article) {
+
'protect': 'protect',
// NB redirMatch is in wikiText
+
'protectHint': 'Restrict editing rights to %s',
var ret='';
+
'unprotectShort': 'un',
 
+
'unprotectHint': 'Allow %s to be edited by anyone again',
if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) {
+
'send thanks': 'send thanks',
ret += '<hr />';
+
'ThanksHint': 'Send a thank you notification to this user',
if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) {
+
'move': 'move',
log('redirLink: newTarget=' + redirMatch);
+
'move page': 'move page',
ret += addPopupShortcut(
+
'MovepageHint': 'Change the title of %s',
changeLinkTargetLink(
+
'edit': 'edit',   ///// edit articles and talk
{newTarget: redirMatch, text: popupString('Redirects'),
+
'edit article': 'edit article',
hint: popupString('Fix this redirect'),
+
'editHint': 'Change the content of %s',
summary: simplePrintf(getValueOf('popupFixRedirsSummary'),
+
'edit talk': 'edit talk',
  [article.toString(), redirMatch ]),
+
'new': 'new',
oldTarget: article.toString(),
+
'new topic': 'new topic',
clickButton: getValueOf('popupRedirAutoClick'), minor: true,
+
'newSectionHint': 'Start a new section on %s',
watch: getValueOf('popupWatchRedirredPages')})
+
'null edit': 'null edit',
, 'R');
+
'nullEditHint': 'Submit an edit to %s, making no changes ',
ret += popupString(' to ');
+
'hist': 'hist',   ///// history, diffs, editors, related
}
+
'history': 'history',
else ret += popupString('Redirects') + popupString(' to ');
+
'historyHint': 'List the changes made to %s',
return ret;
+
'last': 'prev', // For labelling the previous revision in history pages; the key is "last" for backwards compatibility
}
+
'lastEdit': 'lastEdit',
 
+
'mark patrolled': 'mark patrolled',
else return '<br> ' + popupString('Redirects') + popupString(' to ') +
+
'markpatrolledHint': 'Mark this edit as patrolled',
titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view', /* FIXME: newWin */
+
'Could not marked this edit as patrolled': 'Could not marked this edit as patrolled',
  text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')});
+
'show last edit': 'most recent edit',
}
+
'Show the last edit': 'Show the effects of the most recent change',
 
+
'lastContrib': 'lastContrib',
function arinLink(l) {
+
'last set of edits': 'latest edits',
if (!saneLinkCheck(l)) { return null; }
+
'lastContribHint': 'Show the net effect of changes made by the last editor',
if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null;
+
'cur': 'cur',
 
+
'diffCur': 'diffCur',
var uN=l.article.userName();
+
'Show changes since revision %s': 'Show changes since revision %s',
 
+
'%s old': '%s old', // as in 4 weeks old
return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin,
+
'oldEdit': 'oldEdit',
title: tprintf('Look up %s in ARIN whois database', [uN]),
+
'purge': 'purge',
text: l.text, noPopup:1});
+
'purgeHint': 'Demand a fresh copy of %s',
}
+
'raw': 'source',
 
+
'rawHint': 'Download the source of %s',
function toolDbName(cookieStyle) {
+
'render': 'simple',
var ret = mw.config.get('wgDBname');
+
'renderHint': 'Show a plain HTML version of %s',
if (!cookieStyle) { ret+= '_p'; }
+
'Show the edit made to get revision': 'Show the edit made to get revision',
return ret;
+
'sinceMe': 'sinceMe',
}
+
'changes since mine': 'diff my edit',
 
+
'sinceMeHint': 'Show changes since my last edit',
function saneLinkCheck(l) {
+
'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',
if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; }
+
'eds': 'eds',
return true;
+
'editors': 'editors',
}
+
'editorListHint': 'List the users who have edited %s',
function editCounterLink(l) {
+
'related': 'related',
if(!saneLinkCheck(l)) return null;
+
'relatedChanges': 'relatedChanges',
if (! pg.wiki.wikimedia) return null;
+
'related changes': 'related changes',
var uN=l.article.userName();
+
'RecentchangeslinkedHint': 'Show changes in articles related to %s',
var tool=getValueOf('popupEditCounterTool');
+
'editOld': 'editOld',   ///// edit old version, or revert
var url;
+
'rv': 'rv',
var soxredToolUrl='//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3';
+
'revert': 'revert',
 
+
'revertHint': 'Revert to %s',
switch(tool) {
+
'defaultpopupReviewedSummary': 'Accepted by reviewing the [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
case 'custom':
+
'defaultpopupRedlinkSummary': 'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
url=simplePrintf(getValueOf('popupEditCounterUrl'), [ encodeURIComponent(uN), toolDbName() ]);
+
'defaultpopupFixDabsSummary': 'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
break;
+
'defaultpopupFixRedirsSummary': 'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
case 'kate':
+
'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
case 'interiot':
+
'defaultpopupRevertToPreviousSummary': 'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
default:
+
'defaultpopupRevertSummary': 'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
var theWiki=pg.wiki.hostname.split('.');
+
'defaultpopupQueriedRevertToPreviousSummary': 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
url=simplePrintf(soxredToolUrl, [ encodeURIComponent(uN), theWiki[0], theWiki[1] ]);
+
'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]]',
return generalNavLink({url:url, title: tprintf('editCounterLinkHint', [uN]),
+
'Redirects': 'Redirects', // as in Redirects to ...
newWin:l.newWin, text: l.text, noPopup:1});
+
' to ': ' to ',   // as in Redirects to ...
}
+
'Bypass redirect': 'Bypass redirect',
 
+
'Fix this redirect': 'Fix this redirect',
 
+
'disambig': 'disambig',   ///// add or remove dab etc.
function globalSearchLink(l) {
+
'disambigHint': 'Disambiguate this link to [[%s]]',
if(!saneLinkCheck(l)) return null;
+
'Click to disambiguate this link to:': 'Click to disambiguate this link to:',
 
+
'remove this link': 'remove this link',
var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search=';
+
'remove all links to this page from this article': 'remove all links to this page from this article',
var article=l.article.urlString({keepSpaces:true});
+
'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
return generalNavLink({url:base + article, newWin:l.newWin,
+
'wikiLink': 'wikiLink',
title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]),
+
'wikiLinks': 'wikiLinks',
text: l.text, noPopup:1});
+
'links here': 'links here',
}
+
'whatLinksHere': 'whatLinksHere',
 
+
'what links here': 'what links here',
function googleLink(l) {
+
'WhatlinkshereHint': 'List the pages that are hyperlinked to %s',
if(!saneLinkCheck(l)) return null;
+
'unwatchShort': 'un',
 
+
'watchThingy': 'watch',  // called watchThingy because {}.watch is a function
var base='http://www.google.com/search?q=';
+
'watchHint': 'Add %s to my watchlist',
var article=l.article.urlString({keepSpaces:true});
+
'unwatchHint': 'Remove %s from my watchlist',
 
+
'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits',
return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin,
+
'%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s',
title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]),
+
'rss': 'rss',
text: l.text, noPopup:1});
+
/////////////////////////////////////
}
+
// diff previews
 
+
/////////////////////////////////////
function editorListLink(l) {
+
'Diff truncated for performance reasons': 'Diff truncated for performance reasons',
if(!saneLinkCheck(l)) return null;
+
'Old revision': 'Old revision',
var article= l.article.articleFromTalkPage() || l.article;
+
'New revision': 'New revision',
var theWiki=pg.wiki.hostname.split('.');
+
'Something went wrong :-(': 'Something went wrong :-(',
var base='//tools.wmflabs.org/xtools/articleinfo/index.php?&uselang=' + mw.config.get('wgUserLanguage') +
+
'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent',
'lang=' + theWiki[0] + '&wiki=' + theWiki[1] + '&begin=&end=&article='
+
'Unknown date': 'Unknown date',
return generalNavLink({url:base+article.urlString(),
+
/////////////////////////////////////
title: tprintf('editorListHint', [article]),
+
// other special previews
newWin:l.newWin, text: l.text, noPopup:1});
+
/////////////////////////////////////
}
+
'Empty category': 'Empty category',
 
+
'Category members (%s shown)': 'Category members (%s shown)',
function generalNavLink(l) {
+
'No image links found': 'No image links found',
l.className = (l.className==null) ? 'popupNavLink' : l.className;
+
'File links': 'File links',
return generalLink(l);
+
'No image found': 'No image found',
}
+
'Image from Commons': 'Image from Commons',
 
+
'Description page': 'Description page',
//////////////////////////////////////////////////
+
'Alt text:': 'Alt text:',
// magic history links
+
'revdel':'Hidden revision',
//
+
/////////////////////////////////////
 
+
// user-related actions and info
function getHistoryInfo(wikipage, whatNext) {
+
/////////////////////////////////////
log('getHistoryInfo');
+
'user': 'user',   ///// user page, talk, email, space
getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory);
+
'user&nbsp;page': 'user&nbsp;page',
}
+
'user talk': 'user talk',
 
+
'edit user talk': 'edit user talk',
// FIXME eliminate pg.idNumber ... how? :-(
+
'leave comment': 'leave comment',
 
+
'email': 'email',
function getHistory(wikipage, onComplete) {
+
'email user': 'email user',
log('getHistory');
+
'EmailuserHint': 'Send an email to %s',
if( !mw.config.get('wgEnableAPI') ) {
+
'space': 'space', // short form for userSpace link
alert( 'This function of navigation popups now requires a MediaWiki ' +
+
'PrefixIndexHint': 'Show pages in the userspace of %s',
'installation with the API enabled.');
+
'count': 'count', ///// contributions, log
return false;
+
'edit counter': 'edit counter',
}
+
'editCounterLinkHint': 'Count the contributions made by %s',
var url = pg.wiki.apiwikibase + '?format=json&action=query&prop=revisions&titles=' +
+
'contribs': 'contribs',
new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit');
+
'contributions': 'contributions',
log('getHistory: url='+url);
+
'deletedContribs': 'deleted contributions',
if (pg.flag.isIE) {
+
'DeletedcontributionsHint': 'List deleted edits made by %s',
url = url + '&*'; //to circumvent https://bugzilla.wikimedia.org/show_bug.cgi?id=28840
+
'ContributionsHint': 'List the contributions made by %s',
}
+
'log': 'log',
return startDownload(url, pg.idNumber+'history', onComplete);
+
'user log': 'user log',
}
+
'userLogHint': 'Show %s\'s user log',
 
+
'arin': 'ARIN lookup', ///// ARIN lookup, block user or IP
function processHistory(download) {
+
'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database',
var jsobj = getJsObj(download.data);
+
'unblockShort': 'un',
try {
+
'block': 'block',
window.x=jsobj;
+
'block user': 'block user',
var p=jsobj['query']['pages']
+
'IpblocklistHint': 'Unblock %s',
for (var pageid in p) {
+
'BlockipHint': 'Prevent %s from editing',
var revisions=p[pageid]['revisions'];
+
'block log': 'block log',
// we only get the first one
+
'blockLogHint': 'Show the block log for %s',
break;
+
'protectLogHint': 'Show the protection log for %s',
}
+
'pageLogHint': 'Show the page log for %s',
} catch (someError) {
+
'deleteLogHint': 'Show the deletion log for %s',
log('Something went wrong with JSON business');
+
'Invalid %s %s': 'The option %s is invalid: %s',
return finishProcessHistory([]);
+
'No backlinks found': 'No backlinks found',
}
+
' and more': ' and more',
var edits=[];
+
'undo': 'undo',
for (var i=0; i<revisions.length; ++i) {
+
'undoHint': 'undo this edit',
edits.push({ oldid: revisions[i]['revid'], editor: revisions[i]['user'] });
+
'Download preview data': 'Download preview data',
}
+
'Invalid or IP user': 'Invalid or IP user',
log('processed ' + edits.length + ' edits');
+
'Not a registered username': 'Not a registered username',
return finishProcessHistory( edits, mw.config.get('wgUserName') );
+
'BLOCKED': 'BLOCKED',
}
+
' edits since: ': ' edits since: ',
 
+
'last edit on ': 'last edit on ',
 
+
/////////////////////////////////////
function finishProcessHistory(edits, userName) {
+
// Autoediting
var histInfo={};
+
/////////////////////////////////////
 
+
'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort',
histInfo.edits=edits;
+
'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n',
histInfo.userName=userName;
+
'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'
 +
};
  
for (var i=0; i<edits.length; ++i) {
+
 
if (typeof histInfo.myLastEdit == 'undefined' && userName && edits[i].editor==userName) {
+
function popupString(str) {
histInfo.myLastEdit={index: i, oldid: edits[i].oldid, previd: (i==0 ? null : edits[i-1].oldid)};
+
if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; }
}
+
if (pg.string[str]) { return pg.string[str]; }
if (typeof histInfo.firstNewEditor == 'undefined' && edits[i].editor != edits[0].editor) {
+
return str;
histInfo.firstNewEditor={index:i, oldid:edits[i].oldid, previd: (i==0 ? null : edits[i-1].oldid)};
 
}
 
}
 
//pg.misc.historyInfo=histInfo;
 
return histInfo;
 
 
}
 
}
//</NOLITE>
 
// ENDFILE: links.js
 
// STARTFILE: options.js
 
//////////////////////////////////////////////////
 
// options
 
  
// check for cookies and existing value, else use default
+
 
function defaultize(x) {
+
function tprintf(str,subs) {
var val=null;
+
if (typeof subs != typeof []) { subs = [subs]; }
if (x!='popupCookies') {
+
return simplePrintf(popupString(str), subs);
defaultize('popupCookies');
 
if (pg.option.popupCookies && (val=Cookie.read(x))) {
 
pg.option[x]=val;
 
return;
 
}
 
}
 
if (pg.option[x]===null || typeof pg.option[x]=='undefined') {
 
if (typeof window[x] != 'undefined' ) pg.option[x]=window[x];
 
else pg.option[x]=pg.optionDefault[x];
 
}
 
 
}
 
}
  
function newOption(x, def) {
+
//</NOLITE>
pg.optionDefault[x]=def;
+
// ENDFILE: strings.js
}
+
// STARTFILE: run.js
 +
////////////////////////////////////////////////////////////////////
 +
// Run things
 +
////////////////////////////////////////////////////////////////////
  
function setDefault(x, def) {
 
return newOption(x, def);
 
}
 
  
function getValueOf(varName) {
+
// For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some.
defaultize(varName);
+
// The old addOnloadHook did something similar to the below
return pg.option[varName];
+
if (document.readyState=="complete")
}
+
autoEdit(); //will setup popups
 +
else
 +
$( window ).on( 'load', autoEdit );
  
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() {
+
// Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout.
// user-settable parameters and defaults
+
( function () {
var userIsSysop = false;
+
var once = true;
if ( mw.config.get('wgUserGroups') ) {
+
function dynamicContentHandler( $content ) {
for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) {
+
// Try to detect the hook fired on initial page load and disregard
if ( mw.config.get('wgUserGroups')[g] == "sysop" )
+
// it, we already hook to onload (possibly to different parts of
userIsSysop = true
+
// page - it's configurable) and running twice might be bad. Ugly…
}
+
if ( $content.attr( 'id' ) == 'mw-content-text' ) {
 +
if ( once ) {
 +
once = false;
 +
return;
 +
}
 +
}
 +
 +
function registerHooksForVisibleNavpops () {
 +
for (var i=0; pg.current.links && i<pg.current.links.length; ++i) {
 +
var navpop = pg.current.links[i].navpopup;
 +
if (!navpop || !navpop.isVisible()) { continue; }
 +
 +
Navpopup.tracker.addHook(posCheckerHook(navpop));
 +
}
 +
}
 +
 +
function doIt () {
 +
registerHooksForVisibleNavpops();
 +
$content.each( function () {
 +
this.ranSetupTooltipsAlready = false;
 +
setupTooltips( this );
 +
} );
 +
}
 +
 
 +
setupPopups( doIt );
 
}
 
}
  
// Basic options
+
// This hook is also fired after page load.
newOption('popupDelay',              0.5);
+
mw.hook( 'wikipage.content' ).add( dynamicContentHandler );
newOption('popupHideDelay',          0.5);
 
newOption('simplePopups',            false);
 
newOption('popupStructure',          'shortmenus');  // see later - default for popupStructure is 'original' if simplePopups is true
 
newOption('popupActionsMenu',        true);
 
newOption('popupSetupMenu',          true);
 
newOption('popupAdminLinks',          userIsSysop);
 
newOption('popupShortcutKeys',        false);
 
newOption('popupHistoricalLinks',    true);
 
newOption('popupOnlyArticleLinks',    true);
 
newOption('removeTitles',            true);
 
newOption('popupMaxWidth',            350);
 
newOption('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>
+
mw.hook( 'ext.echo.overlay.beforeShowingOverlay' ).add( function($overlay){
// images
+
dynamicContentHandler( $overlay.find(".mw-echo-state") );
newOption('popupImages',                true);
+
});
newOption('imagePopupsForImages',        true);
+
} )();
newOption('popupNeverGetThumbs',        false);
 
//newOption('popupImagesToggleSize',      true);
 
newOption('popupThumbAction',            'imagepage'); //'sizetoggle');
 
newOption('popupImageSize',              60);
 
newOption('popupImageSizeLarge',        200);
 
 
 
// redirs, dabs, reversion
 
newOption('popupFixRedirs',            false);
 
newOption('popupRedirAutoClick',        'wpDiff');
 
newOption('popupFixDabs',              false);
 
newOption('popupDabsAutoClick',        'wpDiff');
 
newOption('popupRevertSummaryPrompt',  false);
 
newOption('popupMinorReverts',          false);
 
newOption('popupRedlinkRemoval',        false);
 
newOption('popupWatchDisambiggedPages', null);
 
newOption('popupWatchRedirredPages',    null);
 
newOption('popupDabWiktionary',        'last');
 
 
 
// navlinks
 
newOption('popupNavLinks',          true);
 
newOption('popupNavLinkSeparator',  ' &sdot; ');
 
newOption('popupLastEditLink',      true);
 
newOption('popupEditCounterTool',  'soxred');
 
newOption('popupEditCounterUrl',    '');
 
newOption('popupExtraUserMenu',    '');
 
//</NOLITE>
 
 
 
// previews etc
 
newOption('popupPreviews',            true);
 
newOption('popupSummaryData',          true);
 
newOption('popupMaxPreviewSentences',  5);
 
newOption('popupMaxPreviewCharacters', 600);
 
newOption('popupLastModified',        true);
 
newOption('popupPreviewKillTemplates', true);
 
newOption('popupPreviewRawTemplates',  true);
 
newOption('popupPreviewFirstParOnly',  true);
 
newOption('popupPreviewCutHeadings',  true);
 
newOption('popupPreviewButton',        false);
 
newOption('popupPreviewButtonEvent',  'click');
 
 
 
//<NOLITE>
 
// diffs
 
newOption('popupPreviewDiffs',          true);
 
newOption('popupDiffMaxLines',          100);
 
newOption('popupDiffContextLines',      2);
 
newOption('popupDiffContextCharacters', 40);
 
newOption('popupDiffDates',            true);
 
newOption('popupDiffDatePrinter',      'toLocaleString');
 
 
 
// edit summaries. God, these are ugly.
 
newOption('popupFixDabsSummary',          popupString('defaultpopupFixDabsSummary') );
 
newOption('popupExtendedRevertSummary',    popupString('defaultpopupExtendedRevertSummary') );
 
newOption('popupTimeOffset',              null);
 
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') );
 
//</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');
 
//</NOLITE>
 
 
 
// new windows
 
newOption('popupNewWindows',    false);
 
newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true});
 
 
 
// regexps
 
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('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template
 
newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub');
 
newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo');
 
}
 
// ENDFILE: options.js
 
// STARTFILE: strings.js
 
//<NOLITE>
 
//////////////////////////////////////////////////
 
// Translatable strings
 
//////////////////////////////////////////////////
 
//
 
// See instructions at
 
// http://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation
 
 
 
pg.string = {
 
/////////////////////////////////////
 
// summary data, searching etc.
 
/////////////////////////////////////
 
'article': 'article',
 
'category': 'category',
 
'categories': 'categories',
 
'image': 'image',
 
'images': 'images',
 
'stub': 'stub',
 
'section stub': 'section stub',
 
'Empty page': 'Empty page',
 
'kB': 'kB',
 
'bytes': 'bytes',
 
'day': 'day',
 
'days': 'days',
 
'hour': 'hour',
 
'hours': 'hours',
 
'minute': 'minute',
 
'minutes': 'minutes',
 
'second': 'second',
 
'seconds': 'seconds',
 
'week': 'week',
 
'weeks': 'weeks',
 
'search': 'search',
 
'SearchHint': 'Find English Wikipedia articles containing %s',
 
'web': 'web',
 
'global': 'global',
 
'globalSearchHint': 'Search across Wikipedias in different languages for %s',
 
'googleSearchHint': 'Google for %s',
 
/////////////////////////////////////
 
// article-related actions and info
 
// (some actions also apply to user pages)
 
/////////////////////////////////////
 
'actions': 'actions', ///// view articles and view talk
 
'popupsMenu': 'popups',
 
'togglePreviewsHint': 'Toggle preview generation in popups on this page',
 
'enable previews': 'enable previews',
 
'disable previews': 'disable previews',
 
'toggle previews': 'toggle previews',
 
'show preview': 'show preview',
 
'reset': 'reset',
 
'more...': 'more...',
 
'disable': 'disable popups',
 
'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.',
 
'historyfeedHint': 'RSS feed of recent changes to this page',
 
'purgePopupsHint': 'Reset popups, clearing all cached popup data.',
 
'PopupsHint': 'Reset popups, clearing all cached popup data.',
 
'spacebar': 'space',
 
'view': 'view',
 
'view article': 'view article',
 
'viewHint': 'Go to %s',
 
'talk': 'talk',
 
'talk page': 'talk page',
 
'this&nbsp;revision': 'this&nbsp;revision',
 
'revision %s of %s': 'revision %s of %s',
 
'Revision %s of %s': 'Revision %s of %s',
 
'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s',
 
'Toggle image size': 'Click to toggle image size',
 
'del': 'del', ///// delete, protect, move
 
'delete': 'delete',
 
'deleteHint': 'Delete %s',
 
'undeleteShort': 'un',
 
'UndeleteHint': 'Show the deletion history for %s',
 
'protect': 'protect',
 
'protectHint': 'Restrict editing rights to %s',
 
'unprotectShort': 'un',
 
'unprotectHint': 'Allow %s to be edited by anyone again',
 
'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': 'last',
 
'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: ',
 
/////////////////////////////////////
 
// Autoediting
 
/////////////////////////////////////
 
'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort',
 
'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n',
 
'The %s button has been automatically clicked. Please wait for the next page to load.': 'The %s button has been automatically clicked. Please wait for the next page to load.',
 
'Could not find button %s. Please check the settings in your javascript file.': 'Could not find button %s. Please check the settings in your javascript file.',
 
/////////////////////////////////////
 
// Popups setup
 
/////////////////////////////////////
 
'Open full-size image': 'Open full-size image',
 
'zxy': 'zxy',
 
'autoedit_version': 'np20140416'
 
};
 
 
 
 
 
function popupString(str) {
 
if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; }
 
if (pg.string[str]) { return pg.string[str]; }
 
return str;
 
}
 
 
 
 
 
function tprintf(str,subs) {
 
if (typeof subs != typeof []) { subs = [subs]; }
 
return simplePrintf(popupString(str), subs);
 
}
 
 
 
//</NOLITE>
 
// ENDFILE: strings.js
 
 
 
 
 
////////////////////////////////////////////////////////////////////
 
// Run things
 
////////////////////////////////////////////////////////////////////
 
 
 
 
 
// For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some.
 
// The old addOnloadHook did something similar to the below
 
if (document.readyState=="complete")
 
autoEdit(); //will setup popups
 
else
 
$( window ).on( 'load', autoEdit );
 
  
 
+
});
// Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout.
+
// ENDFILE: run.js
( function () {
 
var once = true;
 
function dynamicContentHandler( $content ) {
 
// Try to detect the hook fired on initial page load and disregard
 
// it, we already hook to onload (possibly to different parts of
 
// page - it's configurable) and running twice might be bad. Ugly…
 
if ( $content.attr( 'id' ) == 'mw-content-text' ) {
 
if ( once ) {
 
once = false;
 
return;
 
}
 
}
 
 
 
function doIt () {
 
$content.each( function () {
 
this.ranSetupTooltipsAlready = false;
 
setupTooltips( this );
 
} );
 
};
 
 
 
if ( !setupPopups.completed ) {
 
setupPopups( doIt );
 
} else {
 
doIt();
 
}
 
}
 
 
 
// This hook is also fired after page load.
 
mw.hook( 'wikipage.content' ).add( dynamicContentHandler );
 
 
 
mw.hook( 'ext.echo.overlay.beforeShowingOverlay' ).add( function($overlay){
 
dynamicContentHandler( $overlay.find(".mw-echo-state") );
 
});
 
} )();
 

Latest revision as of 18:14, 5 October 2019

// STARTFILE: main.js
// **********************************************************************
// **                                                                  **
// **             changes to this file affect many users.              **
// **           please discuss on the talk page before editing         **
// **                                                                  **
// **********************************************************************
// **                                                                  **
// ** if you do edit this file, be sure that your editor recognizes it **
// ** as utf8, or the weird and wonderful characters in the namespaces **
// **   below will be completely broken. You can check with the show   **
// **            changes button before submitting the edit.            **
// **                      test: مدیا מיוחד Мэдыя                      **
// **                                                                  **
// **********************************************************************
/* eslint-env browser  */
/* global $, jQuery, mw, window */

// Fix later
/* global log, errlog, popupStrings, wikEdUseWikEd, WikEdUpdateFrame */
/* eslint no-mixed-spaces-and-tabs: 0, no-empty: 0 */

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

// Trying to shove as many of these as possible into the pg (popup globals) object
var pg = {
	re: {},               // regexps
	ns: {},               // namespaces
	string: {},           // translatable strings
	wiki: {},             // local site info
	user: {},             // current user info
	misc: {},             // YUCK PHOOEY
	option: {},           // options, see newOption etc
	optionDefault: {},    // default option values
	flag: {},             // misc flags
	cache: {},            // page and image cache
	structures: {},       // navlink structures
	timer: {},            // all sorts of timers (too damn many)
	counter: {},          // .. and all sorts of counters
	current: {},          // state info
	fn: {},               // functions
	endoflist: null
};
/* Bail if the gadget/script is being loaded twice */
if( window.pg ) {
	return;
}
/* Export to global context */
window.pg = pg;

/// Local Variables: ///
/// mode:c ///
/// End: ///
// ENDFILE: main.js
// STARTFILE: actions.js
function setupTooltips(container, remove, force, popData) {
	log('setupTooltips, container='+container+', remove='+remove);
	if (!container) {
//<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; }
	container.ranSetupTooltipsAlready = !remove;

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

function defaultPopupsContainer() {
	if (getValueOf('popupOnlyArticleLinks')) {
		return document.getElementById('mw_content') || 
			document.getElementById('content') ||
			document.getElementById('article') || document;
	}
	return  document;
}

function setupTooltipsLoop(anchors,begin,howmany,sleep, remove, popData) {
	log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments));
	var finish=begin+howmany;
	var loopend = Math.min(finish, anchors.length);
	var j=loopend - begin;
	log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin +
		 ', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove);
	var doTooltip= remove ? removeTooltip : addTooltip;
	// try a faster (?) loop construct
	if (j > 0) {
		do {
			var a=anchors[loopend - j];
			if (typeof a==='undefined' || !a || !a.href) {
				log('got null anchor at index ' + loopend - j);
				continue;
			}
			doTooltip(a, popData);
		} while (--j);
	}
	if (finish < anchors.length) {
		setTimeout(function() {
				setupTooltipsLoop(anchors,finish,howmany,sleep,remove,popData);},
			sleep);
	} else {
		if ( !remove && ! getValueOf('popupTocLinks')) { rmTocTooltips(); }
		pg.flag.finishedLoading=true;
	}
}

// eliminate popups from the TOC
// This also kills any onclick stuff that used to be going on in the toc
function rmTocTooltips() {
	var toc=document.getElementById('toc');
	if (toc) {
		var tocLinks=toc.getElementsByTagName('A');
		var tocLen = tocLinks.length;
		for (var j=0; j<tocLen; ++j) {
			removeTooltip(tocLinks[j], true);
		}
	}
}

function addTooltip(a, popData) {
	if ( !isPopupLink(a) ) { return; }
	a.onmouseover=mouseOverWikiLink;
	a.onmouseout= mouseOutWikiLink;
	a.onmousedown = killPopup;
	a.hasPopup = true;
	a.popData = popData;
}

function removeTooltip(a) {
	if ( !a.hasPopup ) { return; }
	a.onmouseover = null;
	a.onmouseout = null;
	if (a.originalTitle) { a.title = a.originalTitle; }
	a.hasPopup=false;
}

function removeTitle(a) {
	if (!a.originalTitle) {
		a.originalTitle=a.title;
	}
		a.title='';
}

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

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

	if (typeof popupMaxWidth === 'number') {
		var setMaxWidth = function () {
			np.mainDiv.style.maxWidth = popupMaxWidth + 'px';
			np.maxWidth = popupMaxWidth;
		};
		np.addHook(setMaxWidth, 'unhide', 'before');
	}
//<NOLITE>
	np.addHook(addPopupShortcuts, 'unhide', 'after');
	np.addHook(rmPopupShortcuts, 'hide', 'before');
//</NOLITE>
}

function removeModifierKeyHandler(a) {
	//remove listeners for modifier key if any that were added in mouseOverWikiLink
	document.removeEventListener('keydown', a.modifierKeyHandler, false);
	document.removeEventListener('keyup', a.modifierKeyHandler, false);
}

function mouseOverWikiLink(evt) {
	if (!evt && window.event) {evt=window.event;}
	
	// if the modifier is needed, listen for it, 
	// we will remove the listener when we mouseout of this link or kill popup.
	if (getValueOf('popupModifier')) {
		// if popupModifierAction = enable, we should popup when the modifier is pressed
		// if popupModifierAction = disable, we should popup unless the modifier is pressed 
    	var action = getValueOf('popupModifierAction');
    	var key = action=='disable' ? 'keyup' : 'keydown';
    	var a = this;
    	a.modifierKeyHandler = function(evt) {
			mouseOverWikiLink2(a, evt);
		};
    	document.addEventListener(key, a.modifierKeyHandler, false);
	}

	return mouseOverWikiLink2(this, evt);
}

/**
 * Gets the references list item that the provided footnote link targets. This
 * is typically a li element within the ol.references element inside the reflist.
 * @param {Element} a - A footnote link.
 * @returns {Element|boolean} The targeted element, or false if one can't be found. 
 */
function footnoteTarget(a) {
	var aTitle=Title.fromAnchor(a);
	// We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly
	var anch = aTitle.anchor;
	if ( ! /^(cite_note-|_note-|endnote)/.test(anch) ) { return false; }

	var lTitle=Title.fromURL(location.href);
	if ( lTitle.toString(true) !== aTitle.toString(true) ) { return false; }

	var el=document.getElementById(anch);
	while ( el && typeof el.nodeName === 'string') {
		var nt = el.nodeName.toLowerCase();
		if ( nt === 'li' ) { return el; }
		else if ( nt === 'body' ) { return false; }
		else if ( el.parentNode ) { el=el.parentNode; }
		else { return false; }
	}
	return false;
}

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

function modifierPressed(evt) {
		var mod=getValueOf('popupModifier');
		if (!mod) { return false; }

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

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

// Checks if the correct modifier pressed/unpressed if needed
function isCorrectModifier(a,evt) {
	if (!getValueOf('popupModifier')) { return true; }
	// if popupModifierAction = enable, we should popup when the modifier is pressed
	// if popupModifierAction = disable, we should popup unless the modifier is pressed 
	var action = getValueOf('popupModifierAction');
	return ( action ==  'enable' &&  modifierPressed(evt) ||
	         action == 'disable' && !modifierPressed(evt) );
}

function mouseOverWikiLink2(a, evt) {
	if (!isCorrectModifier(a,evt)) { return; }
	if ( getValueOf('removeTitles') ) { removeTitle(a); }
	if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; }
	pg.current.link=a;

	if (getValueOf('simplePopups') && !pg.option.popupStructure) {
		// reset *default value* of popupStructure
		setDefault('popupStructure', 'original');
	}

	var article=(new Title()).fromAnchor(a);
	// set global variable (ugh) to hold article (wikipage)
	pg.current.article = article;

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

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

	if(getValueOf('simplePopups')) {
		if (getValueOf('popupPreviewButton') && !a.simpleNoMore) {
			var d=document.createElement('div');
			d.className='popupPreviewButtonDiv';
			var s=document.createElement('span');
			d.appendChild(s);
			s.className='popupPreviewButton';
			s['on' + getValueOf('popupPreviewButtonEvent')] = function() {
				a.simpleNoMore=true;
				d.style.display = "none";
				nonsimplePopupContent(a,article);
			};
			s.innerHTML=popupString('show preview');
			setPopupHTML(d, 'popupPreview', a.navpopup.idNumber);
		}
	}

	if (a.navpopup.pending !== 0 ) {
		nonsimplePopupContent(a, article);
	}
}

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

	if (getValueOf('popupDraggable'))
	{
		var dragHandle = getValueOf('popupDragHandle') || null;
		if (dragHandle && dragHandle != 'all') {
			dragHandle += a.navpopup.idNumber;
		}
		setTimeout(function(){a.navpopup.makeDraggable(dragHandle);}, 150);
	}

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

function debugData(navpopup) {
	if(getValueOf('popupDebugging') && navpopup.idNumber) {
		setPopupHTML('idNumber='+navpopup.idNumber + ', pending=' + navpopup.pending,
				 'popupError', navpopup.idNumber);
	}
}

function newNavpopup(a, article) {
	var navpopup = new Navpopup();
	navpopup.fuzz=5;
	navpopup.delay=getValueOf('popupDelay')*1000;
	// increment global counter now
	navpopup.idNumber = ++pg.idNumber;
	navpopup.parentAnchor = a;
	navpopup.parentPopup = (a.popData && a.popData.owner);
	navpopup.article = article;
	registerHooks(navpopup);
	return navpopup;
}

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

// Should we show nonsimple context govern by the option (e.g. popupUserInfo)?
// If the user explicitly asked for nonsimple context by setting the option to true,
// then we show it even in nonsimple mode.
function shouldShow(a,option) {
	if (shouldShowNonSimple(a)) {
		return getValueOf(option);
	} else {
		return (typeof window[option] != 'undefined' )  && window[option];
	}
}

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

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

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

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

function loadPreview(article, oldid, navpop) {
	if (!navpop.redir) { navpop.originalArticle=article; }
	article.oldid = oldid;
	loadAPIPreview('revision', article, navpop);
}

function loadPreviewFromRedir(redirMatch, navpop) {
	// redirMatch is a regex match
	var target = new Title().fromWikiText(redirMatch[2]);
	// overwrite (or add) anchor from original target
	// mediawiki does overwrite; eg [[User:Lupin/foo3#Done]]
	if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; }
	navpop.redir++;
	navpop.redirTarget=target;
//<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) {
		loadPreviewFromRedir(redirMatch, download.owner);
		return;
	}

	if (download.owner.visible || !getValueOf('popupLazyPreviews')) {
		insertPreviewNow(download);
	} else {
		var id=(download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK';
		download.owner.addHook( function(){insertPreviewNow(download); return true;},
					'unhide', 'after', id );
	}
}

function insertPreviewNow(download) {
	if (!download.owner) { return; }
	var wikiText=download.data;
	var navpop=download.owner;
	var art=navpop.redirTarget || navpop.originalArticle;

//<NOLITE>
	makeFixDabs(wikiText, navpop, download.pageprops);
	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); }
//</NOLITE>

	if (getValueOf('popupPreviews')) { insertArticlePreview(download, art, navpop); }

}

function insertArticlePreview(download, art, navpop) {
	if (download && typeof download.data == typeof ''){
		if (art.namespaceId()==pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) {
			// FIXME compare/consolidate with diff escaping code for wikitext
			var h='<hr /><span style="font-family: monospace;">' + download.data.entify().split('\\n').join('<br />\\n') + '</span>';
			setPopupHTML(h, 'popupPreview', navpop.idNumber);
		}
		else {
			var p=prepPreviewmaker(download.data, art, navpop);
			p.showPreview();
		}
	}
}

function prepPreviewmaker(data, article, navpop) {
	// deal with tricksy anchors
	var d=anchorize(data, article.anchorString());
	var urlBase=joinPath([pg.wiki.articlebase, article.urlString()]);
	var p=new Previewmaker(d, urlBase, navpop);
	return p;
}


// Try to imitate the way mediawiki generates HTML anchors from section titles
function anchorize(d, anch) {
	if (!anch) { return d; }
	var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?}})');
	var match=d.match(anchRe);
	if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); }

	// now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom
	var lines=d.split('\n');
	for (var i=0; i<lines.length; ++i) {
		lines[i]=lines[i].replace(RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{2}', 'g'), '$2')
			.replace(/'''([^'])/g, '$1').replace(RegExp("''([^'])", 'g'), '$1');
		if (lines[i].match(anchRe)) {
			return d.split('\n').slice(i).join('\n').replace(RegExp('^[^=]*'), '');
		}
	}
	return d;
}

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

   <pre>
   *************************************************
   dom-drag.js
   09.25.2001
   www.youngpup.net
   **************************************************
   10.28.2001 - fixed minor bug where events
   sometimes fired off the handle, not the root.
   *************************************************
   Pared down, some hooks added by [[User:Lupin]]

   Copyright Aaron Boodman.
   Saying stupid things daily since March 2001.
   </pre>
*/

/**
   Creates a new Drag object. This is used to make various DOM elements draggable.
   @constructor
*/
function Drag () {
	/**
	   Condition to determine whether or not to drag. This function should take one parameter, an Event.
	   To disable this, set it to <code>null</code>.
	   @type Function
	*/
	this.startCondition = null;
	/**
	   Hook to be run when the drag finishes. This is passed the final coordinates of
	   the dragged object (two integers, x and y). To disables this, set it to <code>null</code>.
	   @type Function
	*/
	this.endHook = null;
}

/**
   Gets an event in a cross-browser manner.
   @param {Event} e
   @private
*/
Drag.prototype.fixE = function(e) {
	if (typeof e == 'undefined') { e = window.event; }
	if (typeof e.layerX == 'undefined') { e.layerX = e.offsetX; }
	if (typeof e.layerY == 'undefined') { e.layerY = e.offsetY; }
	return e;
};
/**
   Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by.
   @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged.
   @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted.
*/
Drag.prototype.init = function(o, oRoot) {
	var dragObj	  = this;
	this.obj = o;
	o.onmousedown	= function(e) { dragObj.start.apply( dragObj, [e]); };
	o.dragging	   = false;
	o.popups_draggable	  = true;
	o.hmode		  = true;
	o.vmode		  = true;

	o.root = oRoot ? oRoot : o ;

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

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

/**
   Starts the drag.
   @private
   @param {Event} e
*/
Drag.prototype.start = function(e) {
	var o = this.obj; // = this;
	e = this.fixE(e);
	if (this.startCondition && !this.startCondition(e)) { return; }
	var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom, 10);
	var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right,  10);
	o.root.onthisStart(x, y);

	o.lastMouseX	= e.clientX;
	o.lastMouseY	= e.clientY;

	var dragObj	  = this;
	o.onmousemoveDefault	= document.onmousemove;
	o.dragging			  = true;
	document.onmousemove	= function(e) { dragObj.drag.apply( dragObj, [e] ); };
	document.onmouseup	  = function(e) { dragObj.end.apply( dragObj, [e] ); };
	return false;
};
/**
   Does the drag.
   @param {Event} e
   @private
*/
Drag.prototype.drag = function(e) {
	e = this.fixE(e);
	var o = this.obj;

	var ey	= e.clientY;
	var ex	= e.clientX;
	var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom, 10);
	var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right,  10 );
	var nx, ny;

	nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
	ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));

	this.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
	this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
	this.obj.lastMouseX	= ex;
	this.obj.lastMouseY	= ey;

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

/**
   Ends the drag.
   @private
*/
Drag.prototype.end = function()  {
	document.onmousemove=this.obj.onmousemoveDefault;
	document.onmouseup   = null;
	this.obj.dragging	= false;
	if (this.endHook) {
		this.endHook( parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10),
				  parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10));
	}
};
// ENDFILE: domdrag.js
// STARTFILE: structures.js
//<NOLITE>
pg.structures.original={};
pg.structures.original.popupLayout=function () {
	return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle',
		'popupUserData', 'popupData', 'popupOtherLinks',
		'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks',
				   'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
		'popupMiscTools', ['popupRedlink'],
		'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
};
pg.structures.original.popupRedirSpans=function () {
	return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks',
		'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'];
};
pg.structures.original.popupTitle=function (x) {
	log ('defaultstructure.popupTitle');
	if (!getValueOf('popupNavLinks')) {
		return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params);
	}
	return '';
};
pg.structures.original.popupTopLinks=function (x) {
	log ('defaultstructure.popupTopLinks');
	if (getValueOf('popupNavLinks')) { return navLinksHTML(x.article, x.hint, x.params); }
	return '';
};
pg.structures.original.popupImage=function(x) {
	log ('original.popupImage, x.article='+x.article+', x.navpop.idNumber='+x.navpop.idNumber);
	return imageHTML(x.article, x.navpop.idNumber);
};
pg.structures.original.popupRedirTitle=pg.structures.original.popupTitle;
pg.structures.original.popupRedirTopLinks=pg.structures.original.popupTopLinks;


function copyStructure(oldStructure, newStructure) {
	pg.structures[newStructure]={};
	for (var prop in pg.structures[oldStructure]) {
		pg.structures[newStructure][prop]=pg.structures[oldStructure][prop];
	}
}

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

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

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

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

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

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

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

	var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>';
	return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal,
				   x.article, x.params);
};
pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle;
pg.structures.fancy.popupRedirTopLinks=pg.structures.fancy.popupTopLinks;
pg.structures.fancy.popupRedirOtherLinks=pg.structures.fancy.popupOtherLinks;


/** -- fancy2 -- **/
// hack for [[User:MacGyverMagic]]
copyStructure('fancy', 'fancy2');
pg.structures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning
	return '<br>'+pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$','i'),'');
};
pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title
	return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks',
		'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
		'popupMiscTools', ['popupRedlink'],
		'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
};

/** -- menus -- **/
copyStructure('original', 'menus');
pg.structures.menus.popupLayout=function () {
	return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks',
		'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
		'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'],
		'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
};

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

	if (getValueOf('popupActionsMenu')) {
		s.push( '<<mainlink>>*' + dropdiv + menuTitle('actions'));
	} else {
		s.push( dropdiv + '<<mainlink>>');
	}
	s.push( '<menu>');
	s.push( editRow + markPatrolled + newTopic + hist + lastedit + thank );
	if (!shorter) { s.push(jsHistory); }
	s.push( move + linkshere + related);
	if (!shorter) { s.push(nullPurge + search); }
	if (!shorter) { s.push(viewOptions); }
	s.push('<hr />' + watch + protectDelete);
	s.push('<hr />' +
		   'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' +
		   'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' +
		   '<<newTalk|shortcut=+|new topic>>}</menu>' + enddiv);

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


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

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

function menuTitle(s) {
	return '<a href="#" noPopup=1>' + popupString(s) + '</a>';
}

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

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

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

function unEscape(str, sep) {
	return str.split('\\\\').join('\\').split('\\'+sep).join(sep).split('\\n').join('\n');
}


function parseSubstitute(str) {
	// takes a string like s/a/b/flags;othercmds and parses it

	var from,to,flags,tmp;

	if (str.length<4) { return false; }
	var sep=str.charAt(1);
	str=str.substring(2);

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

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

	flags='';
	if (str.length) {
		tmp=skipOver(str,';') || skipToEnd(str, ';');
		if (tmp) {flags=tmp.segment; str=tmp.remainder; }
	}

	return {action: substitute, from: from, to: to, flags: flags, remainder: str};

}

function skipOver(str,sep) {
	var endSegment=findNext(str,sep);
	if (endSegment<0) { return false; }
	var segment=unEscape(str.substring(0,endSegment), sep);
	return {segment: segment, remainder: str.substring(endSegment+1)};
}

/*eslint-disable*/
function skipToEnd(str,sep) {
	return {segment: str, remainder: ''};
}
/*eslint-enable */

function findNext(str, ch) {
	for (var i=0; i<str.length; ++i) {
		if (str.charAt(i)=='\\') { i+=2; }
		if (str.charAt(i)==ch) { return i; }
	}
	return -1;
}

function setCheckbox(param, box) {
	var val=mw.util.getParamValue(param);
	if (val) {
		switch (val) {
		case '1': case 'yes': case 'true':
			box.checked=true;
			break;
		case '0': case 'no':  case 'false':
			box.checked=false;
		}
	}
}

function autoEdit() {
	setupPopups( function () {
		if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; }
		if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) {
			pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action'));
		}
		if (!document.editform) { return false; }
		if (autoEdit.alreadyRan) { return false; }
		autoEdit.alreadyRan=true;
		var cmdString=mw.util.getParamValue('autoedit');
		if (cmdString) {
			try {
				var editbox=document.editform.wpTextbox1;
				var cmdList=parseCmd(cmdString);
				var input=editbox.value;
				var output=execCmds(input, cmdList);
				editbox.value=output;
			} catch (dang) { return; }
			// wikEd user script compatibility
			if (typeof(wikEdUseWikEd) != 'undefined') {
				if (wikEdUseWikEd === true) {
					WikEdUpdateFrame();
				}
			}
		}
		setCheckbox('autominor', document.editform.wpMinoredit);
		setCheckbox('autowatch', document.editform.wpWatchthis);
	
		var rvid = mw.util.getParamValue('autorv');
		if (rvid) {
			var url=pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids='+rvid;
			startDownload(url, null, autoEdit2);
		} else { autoEdit2(); }
	} );
}

function autoEdit2(d) {
	var summary=mw.util.getParamValue('autosummary');
	var summaryprompt=mw.util.getParamValue('autosummaryprompt');
	var summarynotice='';
	if (d && d.data && mw.util.getParamValue('autorv')) {
		var s = getRvSummary(summary, d.data);
		if (s === false) {
			summaryprompt=true;
			summarynotice=popupString('Failed to get revision information, please edit manually.\n\n');
			summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']);
		} else { summary = s; }
	}
	if (summaryprompt) {
		var txt= summarynotice +
			popupString('Enter a non-empty edit summary or press cancel to abort');
		var response=prompt(txt, summary);
		if (response) { summary=response; }
		else { return; }
	}
	if (summary) { document.editform.wpSummary.value=summary; }
	// Attempt to avoid possible premature clicking of the save button
	// (maybe delays in updates to the DOM are to blame?? or a red herring)
	setTimeout(autoEdit3, 100);
}

function autoClickToken() {
	return mw.user.sessionId();
}

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

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

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

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

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

/**
   Creates a new Downloader
   @constructor
   @class The Downloader class. Create a new instance of this class to download stuff.
   @param {String} url The url to download. This can be omitted and supplied later.
*/
function Downloader(url) {
	if (typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); }
	/**
		The url to download
		@type String
	*/
	this.url = url;
	/**
		A universally unique ID number
		@type integer
	*/
	this.id=null;
	/**
		Modification date, to be culled from the incoming headers
		@type Date
		@private
	*/
	this.lastModified = null;
	/**
		What to do when the download completes successfully
		@type Function
		@private
	*/
	this.callbackFunction = null;
	/**
		What to do on failure
		@type Function
		@private
	*/
	this.onFailure = null;
	/**
		Flag set on <code>abort</code>
		@type boolean
	*/
	this.aborted = false;
	/**
	   HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities.
	   @type String
	*/
	this.method='GET';
	/**
		Async flag.
		@type boolean
	*/
	this.async=true;
}

new Downloader();

/** Submits the http request. */
Downloader.prototype.send = function (x) {
	if (!this.http) { return null; }
	return this.http.send(x);
};
/** Aborts the download, setting the <code>aborted</code> field to true.  */
Downloader.prototype.abort = function () {
	if (!this.http) { return null; }
	this.aborted=true;
	return this.http.abort();
};
/** Returns the downloaded data. */
Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;};
/** Prepares the download. */
Downloader.prototype.setTarget = function () {
	if (!this.http) { return null; }
	this.http.open(this.method, this.url, this.async);
	this.http.setRequestHeader( 'Api-User-Agent', pg.misc.userAgent );
};
/** Gets the state of the download. */
Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;};

pg.misc.downloadsInProgress = { };

/** Starts the download.
	Note that setTarget {@link Downloader#setTarget} must be run first
*/
Downloader.prototype.start=function () {
	if (!this.http) { return; }
	pg.misc.downloadsInProgress[this.id] = this;
	this.http.send(null);
};

/** Gets the 'Last-Modified' date from the download headers.
	Should be run after the download completes.
	Returns <code>null</code> on failure.
	@return {Date}
*/
Downloader.prototype.getLastModifiedDate=function () {
	if(!this.http) { return null; }
	var lastmod=null;
	try {
		lastmod=this.http.getResponseHeader('Last-Modified');
	} catch (err) {}
	if (lastmod) { return new Date(lastmod); }
	return null;
};

/** Sets the callback function.
	@param {Function} f callback function, called as <code>f(this)</code> on success
*/
Downloader.prototype.setCallback = function (f) {
	if(!this.http) { return; }
	this.http.onreadystatechange = f;
};

Downloader.prototype.getStatus = function() { if (!this.http) { return null; } return this.http.status; };

//////////////////////////////////////////////////
// helper functions

/** Creates a new {@link Downloader} and prepares it for action.
	@param {String} url The url to download
	@param {integer} id The ID of the {@link Downloader} object
	@param {Function} callback The callback function invoked on success
	@return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
*/
function newDownload(url, id, callback, onfailure) {
	var d=new Downloader(url);
	if (!d.http) { return 'ohdear'; }
	d.id=id;
	d.setTarget();
	if (!onfailure) {
		onfailure=2;
	}
	var f = function () {
		if (d.getReadyState() == 4) {
			delete pg.misc.downloadsInProgress[this.id];
			try {
				if ( d.getStatus() == 200 ) {
					d.data=d.getData();
					d.lastModified=d.getLastModifiedDate();
					callback(d);
				} else if (typeof onfailure == typeof 1) {
					if (onfailure > 0) {
						// retry
						newDownload(url, id, callback, onfailure - 1);
					}
				} else if ($.isFunction(onfailure)) {
					onfailure(d,url,id,callback);
				}
			} catch (somerr) { /* ignore it */ }
		}
	};
	d.setCallback(f);
	return d;
}
/** Simulates a download from cached data.
	The supplied data is put into a {@link Downloader} as if it had downloaded it.
	@param {String} url The url.
	@param {integer} id The ID.
	@param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>,
	where <code>d</code> is the new {@link Downloader}.
	@param {String} data The (cached) data.
	@param {Date} lastModified The (cached) last modified date.
*/
function fakeDownload(url, id, callback, data, lastModified, owner) {
	var d=newDownload(url,callback);
	d.owner=owner;
	d.id=id; d.data=data;
	d.lastModified=lastModified;
	return callback(d);
}

/**
   Starts a download.
   @param {String} url The url to download
   @param {integer} id The ID of the {@link Downloader} object
   @param {Function} callback The callback function invoked on success
   @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
*/
function startDownload(url, id, callback) {
	var d=newDownload(url, id, callback);
	if (typeof d == typeof '' ) { return d; }
	d.start();
	return d;
}

/**
   Aborts all downloads which have been started.
*/
function abortAllDownloads() {
	for ( var x in pg.misc.downloadsInProgress ) {
		try {
			pg.misc.downloadsInProgress[x].aborted=true;
			pg.misc.downloadsInProgress[x].abort();
			delete pg.misc.downloadsInProgress[x];
		} catch (e) {}
	}
}
// ENDFILE: downloader.js
// STARTFILE: livepreview.js
// TODO: location is often not correct (eg relative links in previews)
// NOTE: removed md5 and image and math parsing. was broken, lots of bytes.
/**
 * InstaView - a Mediawiki to HTML converter in JavaScript
 * Version 0.6.1
 * Copyright (C) Pedro Fayolle 2005-2006
 * https://en.wikipedia.org/wiki/User:Pilaf
 * Distributed under the BSD license
 *
 * Changelog:
 *
 * 0.6.1
 * - Fixed problem caused by \r characters
 * - Improved inline formatting parser
 *
 * 0.6
 * - Changed name to InstaView
 * - Some major code reorganizations and factored out some common functions
 * - Handled conversion of relative links (i.e. [[/foo]])
 * - Fixed misrendering of adjacent definition list items
 * - Fixed bug in table headings handling
 * - Changed date format in signatures to reflect Mediawiki's
 * - Fixed handling of [[:Image:...]]
 * - Updated MD5 function (hopefully it will work with UTF-8)
 * - Fixed bug in handling of links inside images
 *
 * To do:
 * - Better support for math tags
 * - Full support for <nowiki>
 * - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof)
 * - Support for templates (through AJAX)
 * - Support for coloured links (AJAX)
 */


var Insta = {};

function setupLivePreview() {

	// options
	Insta.conf =
	{
		baseUrl: '',

		user: {},

		wiki: {
		lang: pg.wiki.lang,
		interwiki: pg.wiki.interwiki,
		default_thumb_width: 180
		},

		paths: {
		articles: pg.wiki.articlePath + '/',
		// Only used for Insta previews with images. (not in popups)
		math: '/math/',
		images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname),
		images_fallback: '//upload.wikimedia.org/wikipedia/commons/',
		},

		locale: {
		user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId],
		image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId],
		category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId],
		// shouldn't be used in popup previews, i think
		months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
		}
	};

	// options with default values or backreferences
	Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian';
	Insta.conf.user.signature = '[['+Insta.conf.locale.user+':'+Insta.conf.user.name+'|'+Insta.conf.user.name+']]';
	//Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/';

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

}


Insta.dump = function(from, to)
{
	if (typeof from == 'string') { from = document.getElementById(from); }
	if (typeof to == 'string') { to = document.getElementById(to); }
	to.innerHTML = this.convert(from.value);
};

Insta.convert = function(wiki)
{
	var ll = (typeof wiki == 'string')? wiki.replace(/\r/g,'').split(/\n/): wiki, // lines of wikicode
		o  = '', // output
		p  = 0,	 // para flag
		$r;	 // result of passing a regexp to $()

	// some shorthands
	function remain() { return ll.length; }
	function sh() { return ll.shift(); } // shift
	function ps(s) { o += s; } // push

	// similar to C's printf, uses ? as placeholders, ?? to escape question marks
	function f()
	{
		var i=1, a=arguments,  f=a[0], o='', c, p;
		for (; i<a.length; i++) {
			if ((p=f.indexOf('?'))+1) {
				// allow character escaping
				i -= c = f.charAt(p+1)=='?' ? 1 : 0;
				o += f.substring(0,p) + (c ? '?' : a[i]);
				f = f.substr(p+1+c);
			} else { break; }
		}
		return o+f;
	}

	function html_entities(s) {
		return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
	}
	
	// Wiki text parsing to html is a nightmare.
	// The below functions deliberately don't escape the ampersand since this would make it more difficult,
	// and we don't absolutely need to for how we need it.
	// This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML.
	// Browsers should all be able to handle it though.
	// We also escape significant wikimarkup characters to prevent further matching on the processed text
	function htmlescape_text(s) {
		return s.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/:/g,"&#58;").replace(/\[/g,"&#91;").replace(/]/g,"&#93;");
	}
	function htmlescape_attr(s) {
		return htmlescape_text(s).replace(/'/g,"&#39;").replace(/"/g,"&quot;");
	}

	// return the first non matching character position between two strings
	function str_imatch(a, b)
	{
		for (var i=0, l=Math.min(a.length, b.length); i<l; i++) {
			if (a.charAt(i)!=b.charAt(i)) { break; }
		}
		return i;
	}

	// compare current line against a string or regexp
	// if passed a string it will compare only the first string.length characters
	// if passed a regexp the result is stored in $r
	function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)); }

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

	function endl(s) { ps(s); sh(); }

	function parse_list()
	{
		var prev='';

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

			var l_match = $r;

			sh();

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

			// close uncontinued lists
			for (var prevPos=prev.length-1; prevPos >= ipos; prevPos--) {

				var pi = prev.charAt(prevPos);

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

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

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

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

			switch (l_match[1].charAt(l_match[1].length-1)) {

				case '*': case '#':
					ps('<li>' + parse_inline_nowiki(l_match[2]));
					break;

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

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

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

					} else ps(parse_inline_nowiki(l_match[2]));
					break;

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

			prev=l_match[1];
		}

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

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

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

	function parse_table_data()
	{
		var td_line, match_i;

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

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

		if (typeof td_match[3] != 'undefined') {

			//ps(' ' + td_match[3])
			match_i = 4;

		} else match_i = 2;

		ps('>');

		if (td_match[1] != '|+') {

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

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

			while (td_line.length) ll.unshift(td_match[1] + td_line.pop());

		} else ps(td_match[match_i]);

		var tc = 0, td = [];

		while (remain()) {
			td.push(sh());
			if ($('|')) {
				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_pre()
	{
		ps('<pre>');
		do {
			endl(parse_inline_nowiki(ll[0].substring(1)) + "\n");
		} while (remain() && $(' '));
		ps('</pre>');
	}

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

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

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

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

			var w_match;

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

		} else filename = tag;

		return '';
//</NOLITE>
	}

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

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

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

	function parse_inline_images(str)
	{
//<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 {
					substart+=2;
					close=str.indexOf(']]',substart);
					open=str.indexOf('[[',substart);
					if (close<=open||open==-1) {
						if (close==-1) return str;
						substart=close;
						if (nestlev) {
							nestlev--;
						} else {
							wiki=str.substring(start,close+2);
							html=parse_image(wiki);
							str=str.replace(wiki,html);
							substart=start+html.length;
							loop=false;
						}
					} else {
						substart=open;
						nestlev++;
					}
				} while (loop);

			} else break;
		}

//</NOLITE>
		return str;
	}

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

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

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

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

		// text formatting
		return str.
			// signatures
			replace(/~{5}(?!~)/g, date).
			replace(/~{4}(?!~)/g, Insta.conf.user.name+' '+date).
			replace(/~{3}(?!~)/g, Insta.conf.user.name).

			// [[:Category:...]], [[:Image:...]], etc...
			replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):[^|]*?)\\]\\](\\w*)','gi'), function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));}).
			// remove straight category and interwiki tags
			replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),'').

			// [[:Category:...|Links]], [[:Image:...|Links]], etc...
			replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):.*?)\\|([^\\]]+?)\\]\\](\\w*)','gi'), function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));}).

			// [[/Relative links]]
			replace(/\[\[(\/[^|]*?)\]\]/g, function($0,$1){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1)); }).

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

			// [[Common links]]
			replace(/\[\[([^[|]*?)\]\](\w*)/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2)); }).

			// [[Replaced|Links]]
			replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3)); }).

			// [[Stripped:Namespace|Namespace]]
			replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1) + htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($2)); }).

			// External links
			replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, function($0,$1,$2,$3,$4){return f("<a class='external' href='?:?'>?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4)); }).
			replace(/\[http:\/\/(.*?)\]/g, function($0,$1){return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1)); }).
			replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function($0,$1,$2,$3){return f("<a class='external' href='?:?'>?:?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($1), htmlescape_text($2) + htmlescape_text($3)); }).
			replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g, function($0,$1,$2,$3,$4){return f("?<a class='external' href='?:?'>?:?</a>", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4)); }).

			replace('__NOTOC__','').
			replace('__NOEDITSECTION__','');
	}

	// begin parsing
	for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) {
		p=0;
		endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3]));

	} else if ($(/^[*#:;]/)) {
		p=0;
		parse_list();

	} else if ($(' ')) {
		p=0;
		parse_pre();

	} else if ($('{|')) {
		p=0;
		parse_table();

	} else if ($(/^----+$/)) {
		p=0;
		endl('<hr />');

	} else if ($(Insta.BLOCK_IMAGE)) {
		p=0;
		parse_block_image();

	} else {

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

		sh();
	}

	return o;
};

function wiki2html(txt,baseurl) {
	Insta.conf.baseUrl=baseurl;
	return Insta.convert(txt);
}
// ENDFILE: livepreview.js
// STARTFILE: pageinfo.js
//<NOLITE>
function popupFilterPageSize(data) {
	return formatBytes(data.length);
}

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

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

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


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

function formatBytes(num) {
	return (num > 949) ? (Math.round(num/100)/10+popupString('kB')) : (num +'&nbsp;' + popupString('bytes')) ;
}
//</NOLITE>
// ENDFILE: pageinfo.js
// STARTFILE: titles.js
/**
   @fileoverview Defines the {@link Title} class, and associated crufty functions.

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

*/

/**
   Creates a new Stringwrapper.
   @constructor

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


/**
   Creates a new <code>Title</code>.
   @constructor

   @class The Title class. Holds article titles and converts them into
   various forms. Also deals with anchors, by which we mean the bits
   of the article URL after a # character, representing locations
   within an article.

   @param {String} value The initial value to assign to the
   article. This must be the canonical title (see {@link
   Title#value}. Omit this in the constructor and use another function
   to set the title if this is unavailable.
*/
function Title(val) {
	/**
	   The canonical article title. This must be in UTF-8 with no
	   entities, escaping or nasties. Also, underscores should be
	   replaced with spaces.
	   @type String
	   @private
	*/
	this.value=null;
	/**
	   The canonical form of the anchor. This should be exactly as
	   it appears in the URL, i.e. with the .C3.0A bits in.
	   @type String
	*/
	this.anchor='';

	this.setUtf(val);
}
Title.prototype=new Stringwrapper();
/**
   Returns the canonical representation of the article title, optionally without anchor.
   @param {boolean} omitAnchor
   @fixme Decide specs for anchor
   @return String The article title and the anchor.
*/
Title.prototype.toString=function(omitAnchor) {
	return this.value + ( (!omitAnchor && this.anchor) ? '#' + this.anchorString() : '' );
};
Title.prototype.anchorString=function() {
	if (!this.anchor) { return ''; }
	var split=this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/);
	var len=split.length;
	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;
	}

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

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

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

	h=splitted.join('?');

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

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

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

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

	// no more special cases to check --
	// hopefully it's not a disguised user-related or specially treated special page
	var m=pg.re.main.exec(h);
	if(m === null) { this.value=null; }
	else {
		var fromBotInterface = /[?](.+[&])?title=/.test(h);
		if (fromBotInterface) {
			m[2]=m[2].split('+').join('_');
		}
		var extracted = m[2] + (m[3] ? '#' + m[3] : '');
		if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) {
			// Fix Safari issue
			// Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3.
			this.setUtf(decodeURIComponent(unescape(extracted)));
		} else {
			this.setUtf(this.decodeNasties(extracted));
		}
	}
	return this;
};
Title.prototype.decodeNasties=function(txt) {
	var ret= this.decodeEscapes(decodeURI(txt));
	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('');
};
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);
};
//<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];
};
Title.prototype.userName=function(withNs) {
	var t=(new Title(this.value));
	t.toUserName(withNs);
	if (t.value) { return t; }
	return null;
};
Title.prototype.toTalkPage=function() {
	// convert article to a talk page, or if we can't, return null
	// In other words: return null if this ALREADY IS a talk page
	// and return the corresponding talk page otherwise
	//
	// Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces
	// * All discussion namespaces have odd-integer indices
	// * The discussion namespace index for a specific namespace with index n is n + 1
	if (this.value === null) { return null; }
	
	var namespaceId = this.namespaceId();
	if (namespaceId>=0 && namespaceId % 2 === 0) //non-special and subject namespace
	{
		var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId+1];
		if (typeof localizedNamespace!=='undefined')
		{
			if (localizedNamespace === '') {
				this.value = this.stripNamespace();
			} else {
				this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
			}
			return this.value;
		}
	}

	this.value=null;
	return null;
};
//</NOLITE>
// 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;
};
//<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;
		}
	}

	this.value=null;
	return null;
};
Title.prototype.articleFromTalkPage=function() {
	var t=new Title(this.value);
	t.toArticleFromTalkPage();
	if (t.value) { return t; }
	return null;
};
Title.prototype.articleFromTalkOrArticle=function() {
	var t=new Title(this.value);
	if ( t.toArticleFromTalkPage() ) { return t; }
	return this;
};
Title.prototype.isIpUser=function() {
	return pg.re.ipUser.test(this.userName());
};
//</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=this.value.substring(anch+1);
};
Title.prototype.append=function(x){
	this.setUtf(this.value + x);
};
Title.prototype.urlString=function(x) {
	if(!x) { x={}; }
	var v=this.toString(true);
	if (!x.omitAnchor && this.anchor) { v+= '#' + this.urlAnchor(); }
	if (!x.keepSpaces) { v=v.split(' ').join('_'); }
	return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B');
};
Title.prototype.removeAnchor=function() {
	return new Title(this.toString(true));
};
Title.prototype.toUrl=function() {
	return pg.wiki.titlebase + this.urlString();
};

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

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

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

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

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

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

//<NOLITE>
function isDisambig(data, article, props) {
	if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; }
	return ! article.isTalkPage() && props.hasOwnProperty('disambiguation');
}

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

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

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

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

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

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

function fixVectorMenuPopups() {
	$('div.vectorMenu h3:first a:first').prop('inNopopupSpan', true);
}
// ENDFILE: titles.js
// STARTFILE: getpage.js
//////////////////////////////////////////////////
// Wiki-specific downloading
//

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

// check cache to see if page exists

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

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

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

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

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

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

function nonGlobalRegex(re) {
	var s=re.toString();
	var flags='';
	for (var j=s.length; s.charAt(j) != '/'; --j) {
		if (s.charAt(j) != 'g') { flags += s.charAt(j); }
	}
	var t=s.substring(1,j);
	return RegExp(t,flags);
}
// ENDFILE: parensplit.js
// STARTFILE: tools.js
// IE madness with encoding
// ========================
//
// suppose throughout that the page is in utf8, like wikipedia
//
// if a is an anchor DOM element and a.href should consist of
//
// http://host.name.here/wiki/foo?bar=baz
//
// then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie))
// but IE gives bar=baz correctly as plain utf8
//
// ---------------------------------
//
// IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here.
//
// ---------------------------------
//
// summat else

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

//<NOLITE>


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

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

//</NOLITE>

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


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

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

function literalizeRegex(str){
	return mw.RegExp.escape(str);
}

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

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

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


function simplePrintf(str, subs) {
	if (!str || !subs) { return str; }
	var ret=[];
	var s=str.parenSplit(/(%s|\$[0-9]+)/);
	var i=0;
	do {
		ret.push(s.shift());
		if ( !s.length ) { break; }
		var cmd=s.shift();
		if (cmd == '%s') {
			if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); }
			++i;
		} else {
			var j=parseInt( cmd.replace('$', ''), 10 ) - 1;
			if ( j > -1 && j < subs.length ) { ret.push(subs[j]); } else { ret.push(cmd); }
		}
	} while (s.length > 0);
	return ret.join('');
}
/* eslint-disable no-unused-vars */
function isString(x) { return (typeof x === 'string' || x instanceof String); }
function isNumber(x) { return (typeof x === 'number' || x instanceof Number); }
function isRegExp(x) { return x instanceof RegExp; }
function isArray (x) { return x instanceof Array; }
function isObject(x) { return x instanceof Object; }
function isFunction(x) {
	return !isRegExp(x) && ($.isFunction(x) || x instanceof Function);
}
/* eslint-enable no-unused-vars */

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

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

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

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

// ENDFILE: tools.js
// STARTFILE: dab.js
//<NOLITE>
//////////////////////////////////////////////////
// Dab-fixing code
//


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

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

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

	ret = rmDupesFromSortedList(ret.sort());

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

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

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

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

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

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


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

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

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

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

//<NOLITE>
function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);}
//</NOLITE>

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

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

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

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

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

// Generate html for whole popup
function popupHTML (a) {
	getValueOf('popupStructure');
	var structure=pg.structures[pg.option.popupStructure];
	if (typeof structure != 'object') {
		//return 'Unknown structure: '+pg.option.popupStructure;
		// override user choice
		pg.option.popupStructure=pg.optionDefault.popupStructure;
		return popupHTML(a);
	}
	if (typeof structure.popupLayout != 'function') { return 'Bad layout'; }
	pg.misc.layout=structure.popupLayout();
	if ($.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='';
	for (var i=0; i<list.length; ++i) {
		if (typeof list[i] == typeof '') {
			ret += emptySpanHTML(list[i], navpop.idNumber, 'div');
		} else if (typeof list[i] == typeof [] && list[i].length > 0 ) {
			ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop));
		} else if (typeof list[i] == typeof {} && list[i].nodeType ) {
			ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType);
		}
	}
	return ret;
}


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

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

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

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

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

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

function mouseOutWikiLink () {
	//console ('mouseOutWikiLink');
	var a=this;
	
	removeModifierKeyHandler(a);
	
	if (a.navpopup === null || typeof a.navpopup === 'undefined') return;
	if ( ! a.navpopup.isVisible() ) {
		a.navpopup.banish();
		return;
	}
	restoreTitle(a);
	Navpopup.tracker.addHook(posCheckerHook(a.navpopup));
}

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

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

function runStopPopupTimer(navpop) {
	// at this point, we should have left the link but remain within the popup
	// so we call this function again until we leave the popup.
	if (!navpop.stopPopupTimer) {
		navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500);
		navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);},
				   'hide', 'before');
	}
}
// ENDFILE: mouseout.js
// STARTFILE: previewmaker.js
/**
   @fileoverview
   Defines the {@link Previewmaker} object, which generates short previews from wiki markup.
*/

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

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

	this.setData();
}
Previewmaker.prototype.setData=function() {
	var maxSize=Math.max(10000, 2*this.maxCharacters);
	this.data=this.originalData.substring(0,maxSize);
};
/** Remove HTML comments
	@private
*/
Previewmaker.prototype.killComments = function () {
	// this also kills one trailing newline, eg [[diamyo]]
	this.data=this.data.replace(RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), '');
};
/**
   @private
*/
Previewmaker.prototype.killDivs = function () {
	// say goodbye, divs (can be nested, so use * not *?)
	this.data=this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>',
					   'gi'), '');
};
/**
   @private
*/
Previewmaker.prototype.killGalleries = function () {
	this.data=this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>',
					   'gi'), '');
};
/**
   @private
*/
Previewmaker.prototype.kill = function(opening, closing, subopening, subclosing, repl) {
	var oldk=this.data;
	var k=this.killStuff(this.data, opening, closing, subopening, subclosing, repl);
	while (k.length < oldk.length) {
		oldk=k;
		k=this.killStuff(k, opening, closing, subopening, subclosing, repl);
	}
	this.data=k;
};
/**
   @private
*/
Previewmaker.prototype.killStuff = function (txt, opening, closing, subopening, subclosing, repl) {
	var op=this.makeRegexp(opening);
	var cl=this.makeRegexp(closing, '^');
	var sb=subopening ? this.makeRegexp(subopening, '^') : null;
	var sc=subclosing ? this.makeRegexp(subclosing, '^') : cl;
	if (!op || !cl) {
		alert('Navigation Popups error: op or cl is null! something is wrong.');
		return;
	}
	if (!op.test(txt)) { return txt; }
	var ret='';
	var opResult = op.exec(txt);
	ret = txt.substring(0,opResult.index);
	txt=txt.substring(opResult.index+opResult[0].length);
	var depth = 1;
	while (txt.length > 0) {
		var removal=0;
		if (depth==1 && cl.test(txt)) {
			depth--;
			removal=cl.exec(txt)[0].length;
		} else if (depth > 1 && sc.test(txt)) {
			depth--;
			removal=sc.exec(txt)[0].length;
		}else if (sb && sb.test(txt)) {
			depth++;
			removal=sb.exec(txt)[0].length;
		}
		if ( !removal ) { removal = 1; }
		txt=txt.substring(removal);
		if (depth === 0) { break; }
	}
	return ret + (repl || '') + txt;
};
/**
   @private
*/
Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) {
	prefix = prefix || '';
	suffix = suffix || '';
	var reStr='';
	var flags='';
	if (isString(x)) {
		reStr=prefix + literalizeRegex(x) + suffix;
	} else if (isRegExp(x)) {
		var s=x.toString().substring(1);
		var sp=s.split('/');
		flags=sp[sp.length-1];
		sp[sp.length-1]='';
		s=sp.join('/');
		s=s.substring(0,s.length-1);
		reStr= prefix + s + suffix;
	} else {
		log ('makeRegexp failed');
	}

	log ('makeRegexp: got reStr=' + reStr + ', flags=' + flags);
	return RegExp(reStr, flags);
};
/**
   @private
*/
Previewmaker.prototype.killBoxTemplates = function () {

	// taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general
	// also, have float_begin, ... float_end
	this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'),	/[}][}]\s*/, '{{');

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

};
/**
   @private
   <nowiki>
*/
Previewmaker.prototype.killTemplates = function () {
	this.kill('{{', '}}', '{', '}', ' ');
};
/**
   </nowiki>
   @private
*/
Previewmaker.prototype.killTables = function () {
	// tables are bad, too
	// this can be slow, but it's an inprovement over a browser hang
	// torture test: [[Comparison_of_Intel_Central_Processing_Units]]
	this.kill('{|', /[|]}\s*/, '{|');
	this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i);
	// remove lines starting with a pipe for the hell of it (?)
	this.data=this.data.replace(RegExp('^[|].*$', 'mg'), '');
};
/**
   @private
*/
Previewmaker.prototype.killImages = function () {
	var forbiddenNamespaceAliases = [];
	jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
		if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return;
		forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments!
	});
	
	// images and categories are a nono
	this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'),
		  /\]\]\s*/, '[', ']');
};
/**
   @private
*/
Previewmaker.prototype.killHTML = function () {
	// kill <ref ...>...</ref>
	this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i);

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

	// and those pesky html tags, but not <nowiki> or <blockquote>
	var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/);
	var len=splitted.length;
	for (var i=1; i<len; i=i+2) {
		switch (splitted[i]) {
		case '<nowiki>':
		case '</nowiki>':
		case '<blockquote>':
		case '</blockquote>':
			break;
		default:
			splitted[i]='';
		}
	}
	this.data=splitted.join('');
};
/**
   @private
*/
Previewmaker.prototype.killChunks = function() { // heuristics alert
	// chunks of italic text? you crazy, man?
	var italicChunkRegex=new RegExp
	("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g');
	// keep stuff separated, though, so stick in \n (fixes [[Union Jack]]?
	this.data=this.data.replace(italicChunkRegex, '\n');
};
/**
   @private
*/
Previewmaker.prototype.mopup = function () {
	// we simply *can't* be doing with horizontal rules right now
	this.data=this.data.replace(RegExp('^-{4,}','mg'),'');

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

	// replace __TOC__, __NOTOC__ and whatever else there is
	// this'll probably do
	this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),'');
};
/**
   @private
*/
Previewmaker.prototype.firstBit = function () {
	// dont't be givin' me no subsequent paragraphs, you hear me?
	/// first we "normalize" section headings, removing whitespace after, adding before
	var d=this.data;

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

		this.data=this.data.replace(RegExp('^[\\s\\n]*'), '');
		var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data);
		if (stuff) { d = stuff[0]; }
		if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; }

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


	// Split sentences. Superfluous sentences are RIGHT OUT.
	// note: exactly 1 set of parens here needed to make the slice work
	d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g'));
	// leading space is bad, mmkay?
	d[0]=d[0].replace(RegExp('^\\s*'), '');

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

	this.fullLength=d.join('').length;
	var n=this.maxSentences;
	var dd=this.firstSentences(d,n); 

	do {
		dd=this.firstSentences(d,n); --n;
	} while ( dd.length > this.maxCharacters && n !== 0 );

	this.data = dd;
};
/**
   @private
*/
Previewmaker.prototype.fixSentenceEnds = function(strs, reg) {
	// take an array of strings, strs
	// join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg

	for (var i=0; i<strs.length-2; ++i) {
		if (reg.test(strs[i])) {
			var a=[];
			for (var j=0; j<strs.length; ++j) {
				if (j<i)   a[j]=strs[j];
				if (j==i)  a[i]=strs[i]+strs[i+1]+strs[i+2];
				if (j>i+2) a[j-2]=strs[j];
			}
			return this.fixSentenceEnds(a,reg);
		}
	}
	return strs;
};
/**
   @private
*/
Previewmaker.prototype.firstSentences = function(strs, howmany) {
	var t=strs.slice(0, 2*howmany);
	return t.join('');
};
/**
   @private
*/
Previewmaker.prototype.killBadWhitespace = function() {
	// also cleans up isolated '''', eg [[Suntory Sungoliath]]
	this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), '');
};
/**
   Runs the various methods to generate the preview.
   The preview is stored in the <code>html</html> field.
   @private
*/
Previewmaker.prototype.makePreview = function() {
	if (this.owner.article.namespaceId()!=pg.nsTemplateId &&
				this.owner.article.namespaceId()!=pg.nsImageId ) {
		this.killComments();
		this.killDivs();
		this.killGalleries();
		this.killBoxTemplates();

		if (getValueOf('popupPreviewKillTemplates')) {
			this.killTemplates();
		} else {
			this.killMultilineTemplates();
		}
		this.killTables();
		this.killImages();
		this.killHTML();
		this.killChunks();
		this.mopup();

		this.firstBit();
		this.killBadWhitespace();
	}
	else
	{
		this.killHTML();
	}
	this.html=wiki2html(this.data, this.baseUrl); // needs livepreview
	this.fixHTML();
	this.stripLongTemplates();
};
/**
   @private
*/
Previewmaker.prototype.esWiki2HtmlPart = function(data) {
  var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink
  reLinks.lastIndex = 0; //reset regex

  var match;
  var result = "";
  var postfixIndex = 0;
  while ((match = reLinks.exec(data))) //match all wikilinks
  {
	//FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases.
	result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) + 
			  '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>";
	postfixIndex = reLinks.lastIndex;
  }
  //append the rest
  result += pg.escapeQuotesHTML(data.substring(postfixIndex));
  
  return result;
};
Previewmaker.prototype.editSummaryPreview=function() {
	var reAes   = /\/\* *(.*?) *\*\//g; //match the first section marker
	reAes.lastIndex = 0; //reset regex
	
	var match;
	
	match = reAes.exec(this.data);
	if (match)
	{
		//we have a section link. Split it, process it, combine it.
		var prefix = this.data.substring(0,match.index-1);
		var section = match[1];
		var postfix = this.data.substring(reAes.lastIndex);
		
		var start = "<span class='autocomment'>";
		var end = "</span>";
		if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- ";
		if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix);
		

		var t=new Title().fromURL(this.baseUrl);
		t.anchorFromUtf(section);
		var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor);
		return start + '<a href="'+sectionLink+'">&rarr;</a> '+pg.escapeQuotesHTML(section) + end;
	}
	
	//else there's no section link, htmlify the whole thing.
	return this.esWiki2HtmlPart(this.data);
};

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

		if (getValueOf('popupPreviewKillTemplates')) {
			p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; }
		} else {
			p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; }
		}

		p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; }
		p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; }
		p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; }
		p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; }
		p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; }

		p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; }
		p.killBadWhitespace(); if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) { return; }
	}

	p.html=wiki2html(p.data, base); // needs livepreview
	p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; }
	p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; }
	alert('finished preview - end result follows.\n---\n' + p.html);
}
/*eslint-enable */
//</NOLITE>

/**
   Works around livepreview bugs.
   @private
*/
Previewmaker.prototype.fixHTML = function() {
	if(!this.html) return;

  var ret = this.html;

	// fix question marks in wiki links
	// maybe this'll break some stuff :-(
	ret=ret.replace(RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'), '$1%3F$2');
	ret=ret.replace(RegExp('(<a href=\'' + pg.wiki.articlePath + '/[^\']*)[?](.*?\')', 'g'), '$1%3F$2');
	// FIXME fix up % too
	
	
	this.html=ret;
};
/**
   Generates the preview and displays it in the current popup.

   Does nothing if the generated preview is invalid or consists of whitespace only.
   Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true.
*/
Previewmaker.prototype.showPreview = function () {
	this.makePreview();
	if (typeof this.html != typeof '') return;
	if (RegExp('^\\s*$').test(this.html)) return;
	setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber);
	setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, { owner: this.owner });
	var more = (this.fullLength > this.data.length) ? this.moreLink() : '';
	setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber);
};
/**
   @private
*/
Previewmaker.prototype.moreLink=function() {
	var a=document.createElement('a');
	a.className='popupMoreLink';
	a.innerHTML=popupString('more...');
	var savedThis=this;
	a.onclick=function() {
		savedThis.maxCharacters+=2000;
		savedThis.maxSentences+=20;
		savedThis.setData();
		savedThis.showPreview();
	};
	return a;
};

/**
   @private
*/
Previewmaker.prototype.stripLongTemplates = function() {
	// operates on the HTML!
	this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), '');
	this.html=this.html.split('\n').join(' '); // workaround for <pre> templates
	this.html=this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]','gi'), '');
};
/**
   @private
*/
Previewmaker.prototype.killMultilineTemplates = function() {
	this.kill('{{{', '}}}');
	this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{');
};
// ENDFILE: previewmaker.js
// STARTFILE: querypreview.js
function loadAPIPreview(queryType, article, navpop) {
	var art=new Title(article).urlString();
	var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';
	var htmlGenerator=function(/*a, d*/){alert('invalid html generator');};
	var usernameart = '';
	switch (queryType) {
	case 'history':
		url += 'titles=' + art + '&prop=revisions&rvlimit=' +
			getValueOf('popupHistoryPreviewLimit');
		htmlGenerator=APIhistoryPreviewHTML;
		break;
	case 'category':
		url += 'list=categorymembers&cmtitle=' + art;
		htmlGenerator=APIcategoryPreviewHTML;
		break;
	case 'userinfo':
		var username = new Title( article ).userName();
		usernameart = encodeURIComponent( username );
		if (pg.re.ipUser.test(username)) {
			url += 'list=blocks&bkprop=range&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&rvprop=content&rvslots=main' + 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&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max&rvslots=main';
		htmlGenerator=APIrevisionPreviewHTML;
		break;
	}
	pendingNavpopTask(navpop);
	var callback=function(d){
		log( "callback of API functions was hit" );
		showAPIPreview(queryType, htmlGenerator(article,d,navpop), navpop.idNumber, navpop, d);
	};
	var go = function(){
		getPageWithCaching(url, callback, navpop);
		return true;
	};

	if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
	else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_'+queryType+'_QUERY_DATA'); }
}

function linkList(list) {
	list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); });
	var buf=[];
	for (var i=0; i<list.length; ++i) {
		buf.push(wikiLink({article: new Title(list[i]),
				   text:	list[i].split(' ').join('&nbsp;'),
				   action:  'view'}));
	}
	return buf.join(', ');
}

function getTimeOffset() {
	var tz = mw.user.options.get('timecorrection');

	if(tz) {
		if( tz.indexOf('|') > -1 ) {
			// New format
			return parseInt(tz.split('|')[1],10);
		} else if ( tz.indexOf(':') > -1 ) {
			// Old format
			return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) );
		}
	}
	return 0;
}

/*
 * Creates a HTML table that's shown in the history and user-contribs popups.
 * @param {Object[]} h - a list of revisions, returned from the API
 * @param {boolean} reallyContribs - true only if we're displaying user contributions
 */
function editPreviewTable(article, h, reallyContribs, timeOffset) {
	var html=['<table>'];
	var day=null;
	var curart=article;
	var page=null;

	var makeFirstColumnLinks;
	if(reallyContribs) {

		// We're showing user contributions, so make (diff | hist) links
		makeFirstColumnLinks = function(currentRevision) {
			var result = '(';
			result += '<a href="' + pg.wiki.titlebase +
				new Title(currentRevision.title).urlString() + '&diff=prev' +
				'&oldid=' + currentRevision.revid + '">' + popupString('diff') + '</a>';
			result += '&nbsp;|&nbsp;';
			result += '<a href="' + pg.wiki.titlebase + 
				new Title(currentRevision.title).urlString() + '&action=history">' +
				popupString('hist') + '</a>';
			result += ')';
			return result;
		};
	} else {

		// It's a regular history page, so make (cur | last) links
		var firstRevid = h[0].revid;
		makeFirstColumnLinks = function(currentRevision) {
			var result = '(';
			result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() +
				'&diff=' + firstRevid + '&oldid=' + currentRevision.revid + '">' + popupString('cur') + '</a>';
			result += '&nbsp;|&nbsp;';
			result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() +
				'&diff=prev&oldid=' + currentRevision.revid + '">' + popupString('last') + '</a>';
			result += ')';
			return result;
		};
	}

	for (var i=0; i<h.length; ++i) {
		if (reallyContribs) { 
			page = h[i].title;
			curart = new Title(page);
		}
		var minor = h[i].minor ? '<b>m </b>' : '';
		var editDate = adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset);
		var thisDay = dayFormat(editDate);
		var thisTime = timeFormat(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 || h[i].content;
		if (c) {
			comment=new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview();
		} else if ( h[i].commenthidden ) {
			comment=popupString('revdel');
		}
		html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>');
		html.push('</tr>');
		html=[html.join('')];
	}
	html.push('</table>');
	return html.join('');
}

function getDateFromTimestamp(t) {
	var s=t.split(/[^0-9]/);
	switch(s.length) {
	case 0: return null;
	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) {
	if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); }
	return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-');
}

function timeFormat(editDate, utc) {
	if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); }
	return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':');
}

function showAPIPreview(queryType, html, id, navpop, download) {
	// DJ: done
	var target='popupPreview';
	completedNavpopTask(navpop);

	switch (queryType) {
		case 'imagelinks':
		case 'category':
			target='popupPostPreview'; break;
		case 'userinfo':
			target='popupUserData'; break;
		case 'revision':
			insertPreview(download);
			return;
	}
	setPopupTipsAndHTML(html, target, id);
}

function ContentHandler(slot)
{
var preview_content = null;
	switch(slot.contentmodel)
	{
		case 'wikitext':
			preview_content = slot.content;
		break;
		case 'flow-board':
			preview_content = 'Flow workflow: '+getJsObj(slot.content)["flow-workflow"]; //placeholder
		break;
		case 'javascript':
		case 'css':
			preview_content = '['+slot.contentmodel+' code]';
		break;
		case 'NewsletterContent':
			preview_content = "<b>Newsletter</b> \n"+getJsObj(slot.content)["description"];
		break;
	}
return	preview_content;
}

function APIrevisionPreviewHTML(article, download) {
	try{
		var jsObj=getJsObj(download.data);
		var page=anyChild(jsObj.query.pages);
		if( page.missing ) {
			// TODO we need to fix this proper later on
			download.owner = null;
			return;
		}
		var content = (
			page &&
			page.revisions
		) ? ContentHandler(page.revisions[0].slots.main) : null;
		
		if( typeof content === 'string' ) 
		{
			download.data = content;
			download.lastModified = new Date(page.revisions[0].timestamp);
			download.pageprops = (page.hasOwnProperty('pageprops')) ? page.pageprops : {};
		}
	} catch(someError) {
		return 'Revision preview failed :(';
	}
}

function APIbacklinksPreviewHTML(article, download/*, navpop*/ ) {
	try {
		var jsObj=getJsObj(download.data);
		var list=jsObj.query.backlinks;

		var html=[];
		if (!list) { return popupString('No backlinks found'); }
		for ( var i=0; i < list.length; i++ ) {
			var t=new Title(list[i].title);
			html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>');
		}
		html=html.join(', ');
		if (jsObj['continue'] && jsObj['continue'].blcontinue) {
			html += popupString(' and more');
		}
		return html;
	} catch (someError) {
		return 'backlinksPreviewHTML went wonky';
	}
}

pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) {
	log( "APIsharedImagePagePreviewHTML" );
	var popupid = obj.requestid;
	if( obj.query && obj.query.pages )
	{
		var page=anyChild(obj.query.pages );
		var content = (
			page &&
			page.revisions &&
			page.revisions[0].slots.main.contentmodel === 'wikitext'
		) ? page.revisions[0].slots.main.content : null;
		if( typeof content === 'string' ) 
		{
			/* Not entirely safe, but the best we can do */
			var p=new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup);
			p.makePreview();
			setPopupHTML( p.html, "popupSecondPreview", popupid );
		}
	}
};

function APIimagepagePreviewHTML(article, download, navpop) {
	try {
		var jsObj=getJsObj(download.data);
		var page=anyChild(jsObj.query.pages);
		var content= (
			page &&
			page.revisions &&
			page.revisions[0].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&rvprop=content&rvslots=main&titles=' + encart;

			ret = ret +'<hr />' + popupString( 'Image from Commons') +
					': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' +
					popupString( 'Description page') + '</a>';
			mw.loader.load( shared_url );
		}
		showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download);
		return ret;
	} catch (someError) {
		return 'API imagepage preview failed :(';
	}
}

function APIimagelinksPreviewHTML(article, download) {
	try {
		var jsobj=getJsObj(download.data);
		var list=jsobj.query.imageusage;
		if (list) {
			var ret=[];
			for (var i=0; i < list.length; i++) {
				ret.push(list[i].title);
			}
			if (ret.length === 0) { return popupString('No image links found'); }
			return '<h2>' + popupString('File links') + '</h2>' + linkList(ret);
		} else {
			return popupString('No image links found');
		}
	} catch(someError) {
		return 'Image links preview generation failed :(';
	}
}

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

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

	var user=anyChild(queryobj.users);
	if (user) {
		var globaluserinfo=queryobj.globaluserinfo;
		if (user.invalid === '') {
			ret.push( popupString( 'Invalid user') );
		} else if (user.missing === '') {
			ret.push( popupString( 'Not a registered username') );
		}
		if( user.blockedby )
			ret.push('<b>' + popupString('BLOCKED') + '</b>');
		if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) {
			var lockedSulAccountIsAttachedToThis = true;
			for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) {
				if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) {
					lockedSulAccountIsAttachedToThis=false;
					break;
				}
			}
			if (lockedSulAccountIsAttachedToThis) {
				if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>');
				if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>');
			}
		}
		if( getValueOf('popupShowGender') && user.gender ) {
			switch( user.gender ) {
				case "male": ret.push( popupString( "\u2642" ) ); break;
				case "female": ret.push( popupString( "\u2640" ) ); break;
			}
		}
		if( user.groups ) {
			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( globaluserinfo && globaluserinfo.groups ) {
			for( var k=0; k < globaluserinfo.groups.length; k++) {
				ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[k])+'</i>' );
			}
		}
		
		regdate = user.registration ? user.registration : globaluserinfo.registration;
		ret.push( pg.escapeQuotesHTML((user.editcount ? user.editcount + popupString(' edits since: '): '0') + (regdate ? dayFormat(getDateFromTimestamp(regdate)) : '?')) );
	}

	if (queryobj.usercontribs && queryobj.usercontribs.length) {
		ret.push( popupString('last edit on ') + dayFormat(getDateFromTimestamp(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) {
	return APIhistoryPreviewHTML(article, download, navpop, true);
}

function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) {
	try {
		var jsobj=getJsObj(download.data);
		var edits = [];
		if( reallyContribs ) {
			edits=jsobj.query.usercontribs;
		} else {
			edits=anyChild(jsobj.query.pages).revisions;
		}

		var ret=editPreviewTable(article, edits, reallyContribs, getTimeOffset());
		return ret;
	} catch (someError) {
		return 'History preview failed :-(';
	}
}


//</NOLITE>
// ENDFILE: querypreview.js
// STARTFILE: debug.js
////////////////////////////////////////////////////////////////////
// Debugging functions
////////////////////////////////////////////////////////////////////

function setupDebugging() {
//<NOLITE>
	if (window.popupDebug) { // popupDebug is set from .version
		window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; };
			window.console.log(x);
		};
		window.errlog=function(x) {
			window.console.error(x);
		};
		log('Initializing logger');
	} else {
//</NOLITE>
		window.log = function() {};
		window.errlog = function() {};
//<NOLITE>
	}
//</NOLITE>
}
// ENDFILE: debug.js
// STARTFILE: images.js

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

	if ( !getValueOf('popupImages') ) return;
	if ( !isValidImageName(image) ) return false;
	
	var art=image.urlString();

	var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query';
	url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge');
	url += '&titles=' + art;

	pendingNavpopTask(navpop);
	var callback=function(d){
		popupsInsertImage(navpop.idNumber, navpop, d);
	};
	var go = function(){
		getPageWithCaching(url, callback, navpop);
		return true;
	};
	if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
	else { navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA'); }

}

function popupsInsertImage(id, navpop, download) {
	log( "popupsInsertImage");
	var imageinfo;
	try {
		var jsObj=getJsObj(download.data);
		var imagepage=anyChild(jsObj.query.pages);
		if (typeof imagepage.imageinfo === 'undefined') return;
		imageinfo = imagepage.imageinfo[0];
	} catch (someError) {
		log( "popupsInsertImage failed :(" );
		return;
	}

	var popupImage = document.getElementById("popupImg"+id);
	if (!popupImage) {
		log( "could not find insertion point for image");
		return;
	}

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

	// Set the source for the image.
	if( imageinfo.thumburl )
		popupImage.src=imageinfo.thumburl;
	else if( imageinfo.mime.indexOf("image") === 0 ){
		popupImage.src=imageinfo.url;
		log( "a thumb could not be found, using original image" );
	} else log( "fullsize imagethumb, but not sure if it's an image");


	var a=document.getElementById("popupImageLink"+id);
	if (a === null) { return null; }

	// Determine the action of the surrouding imagelink.
	switch (getValueOf('popupThumbAction')) {
	case 'imagepage':
		if (pg.current.article.namespaceId()!=pg.nsImageId) {
			a.href=imageinfo.descriptionurl;
			// FIXME: unreliable pg.idNumber
			popTipsSoonFn('popupImage' + id)();
			break;
		}
		/* falls through */
	case 'sizetoggle':
		a.onclick=toggleSize;
		a.title=popupString('Toggle image size');
		return;
	case 'linkfull':
		a.href = imageinfo.url;
		a.title=popupString('Open full-size image');
		return;
	}

}

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

	if (!img.style.width || img.style.width==='') {
		img.style.width='100%';
	} else {
		img.style.width='';
	}
}

// Returns one title of an image from wikiText.
function getValidImageFromWikiText(wikiText) {
	// nb in pg.re.image we're interested in the second bracketed expression
	// this may change if the regex changes :-(
	//var match=pg.re.image.exec(wikiText);
	var matched=null;
	var match;
	// strip html comments, used by evil bots :-(
	var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1,
					RegExp('^<!--[^[]*popup', 'i'));

	while ( ( match = pg.re.image.exec(t) ) ) {
		// now find a sane image name - exclude templates by seeking {
		var m = match[2] || match[6];
		if ( isValidImageName(m) ) {
			matched=m;
			break;
		}
	}
	pg.re.image.lastIndex=0;
	if (!matched) { return null; }
	return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched);
}

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

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

function setNamespaces() {
	pg.nsSpecialId   = -1;
	pg.nsMainspaceId = 0;
	pg.nsImageId     = 6;
	pg.nsUserId      = 2;
	pg.nsUsertalkId  = 3;
	pg.nsCategoryId  = 14;
	pg.nsTemplateId  = 10;
}


function setRedirs() {
	var r='redirect';
	var R='REDIRECT';
	var redirLists={
//<NOLITE>
		'ar':  [ R, 'تحويل' ],
		'be':  [ r, 'перанакіраваньне' ],
		'bg':  [ r, 'пренасочване', 'виж' ],
		'bs':  [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ],
		'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' ],
		'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() {
	if (pg.wiki.wikimedia) {
		// From https://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 nsRe(namespaceId) {
	var imageNamespaceVariants = [];
	jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
		if (_namespaceId!=namespaceId) return;
		_localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc);
		imageNamespaceVariants.push(mw.RegExp.escape(_localizedNamespaceLc).split(' ').join('[ _]'));
		imageNamespaceVariants.push(mw.RegExp.escape(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();
	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) {
	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;
		}
	}
	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
   they look like and so on.

   <code>Mousetracker</code> "captures" the mouse using
   <code>document.onmousemove</code>.
*/


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

	/**
	   Timer for the loop.
	   @type Timer
	*/
	this.timer=null;

	/**
	   Flag - are we switched on?
	   @type Boolean
	*/
	this.active=false;
	/**
	   Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position?
	*/
	this.dirty=true;
	/**
	   Array of hook functions.
	   @private
	   @type Array
	*/
	this.hooks=[];
}

/**
   Adds a hook, to be called when we get events.
   @param {Function} f A function which is called as
   <code>f(x,y)</code>. It should return <code>true</code> when it
   wants to be removed, and <code>false</code> otherwise.
*/
Mousetracker.prototype.addHook = function (f) {
	this.hooks.push(f);
};

/**
   Runs hooks, passing them the x
   and y coords of the mouse.  Hook functions that return true are
   passed to {@link Mousetracker#removeHooks} for removal.
   @private
*/
Mousetracker.prototype.runHooks = function () {
	if (!this.hooks || !this.hooks.length) { return; }
	//log('Mousetracker.runHooks; we got some hooks to run');
	var remove=false;
	var removeObj={};
	// this method gets called a LOT -
	// pre-cache some variables
	var x=this.x, y=this.y, len = this.hooks.length;

	for (var i=0; i<len; ++i) {
		//~ run the hook function, and remove it if it returns true
		if (this.hooks[i](x, y)===true) {
			remove=true;
			removeObj[i]=true;
		}
	}
	if (remove) { this.removeHooks(removeObj); }
};

/**
   Removes hooks.
   @private
   @param {Object} removeObj An object whose keys are the index
   numbers of functions for removal, with values that evaluate to true
*/
Mousetracker.prototype.removeHooks = function(removeObj) {
	var newHooks=[];
	var len = this.hooks.length;
	for (var i=0; i<len; ++i) {
		if (! removeObj[i]) { newHooks.push(this.hooks[i]); }
	}
	this.hooks=newHooks;
};


/**
   Event handler for mouse wiggles.
   We simply grab the event, set x and y and run the hooks.
   This makes the cpu all hot and bothered :-(
   @private
   @param {Event} e Mousemove event
*/
Mousetracker.prototype.track=function (e) {
	//~ Apparently this is needed in IE.
	e = e || window.event;
	var x, y;
	if (e) {
		if (e.pageX) { x=e.pageX; y=e.pageY; }
		else if (typeof e.clientX!='undefined') {
			var left, top, docElt = document.documentElement;

			if (docElt) { left=docElt.scrollLeft; }
			left = left || document.body.scrollLeft || document.scrollLeft || 0;

			if (docElt) { top=docElt.scrollTop; }
			top = top || document.body.scrollTop || document.scrollTop || 0;

			x=e.clientX + left;
			y=e.clientY + top;
		} else { return; }
		this.setPosition(x,y);
	}
};

/**
   Sets the x and y coordinates stored and takes appropriate action,
   running hooks as appropriate.
   @param {Integer} x, y Screen coordinates to set
*/

Mousetracker.prototype.setPosition=function(x,y) {
	this.x = x;
	this.y = y;
	if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; }
	if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; }
	var diff = (this.lastHook_x - x)*(this.lastHook_y - y);
	diff = (diff >= 0) ? diff : -diff;
	if ( diff > 1 ) {
		this.lastHook_x=x;
		this.lastHook_y=y;
		if (this.dirty) { this.dirty = false; }
		else { this.runHooks(); }
	}
};

/**
   Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>.
   A half-hearted attempt is made to preserve the old event handler if there is one.
*/
Mousetracker.prototype.enable = function () {
	if (this.active) { return; }
	this.active=true;
	//~ Save the current handler for mousemove events. This isn't too
	//~ robust, of course.
	this.savedHandler=document.onmousemove;
	//~ Gotta save @tt{this} again for the closure, and use apply for
	//~ the member function.
	var savedThis=this;
	document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);};
	if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working');
									savedThis.runHooks();}, this.loopDelay); }
};

/**
   Disables the tracker, removing the event handler.
*/
Mousetracker.prototype.disable = function () {
	if (!this.active) { return; }
	if ($.isFunction(this.savedHandler)) {
		document.onmousemove=this.savedHandler;
	} else { delete document.onmousemove; }
	if (this.timer) { clearInterval(this.timer); }
	this.active=false;
};

/**
   Creates a new Navpopup.
   Gets a UID for the popup and
   @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable.
   @constructor
   @class The Navpopup class. This generates popup hints, and does some management of them.
*/
function Navpopup(/*init*/) {
	//alert('new Navpopup(init)');
	/** UID for each Navpopup instance.
		Read-only.
		@type integer
	*/
	this.uid=Navpopup.uid++;
	/**
	   Read-only flag for current visibility of the popup.
	   @type boolean
	   @private
	*/
	this.visible=false;
	/** Flag to be set when we want to cancel a previous request to
		show the popup in a little while.
		@private
		@type boolean
	*/
	this.noshow=false;
	/** Categorised list of hooks.
		@see #runHooks
		@see #addHook
		@private
		@type Object
	*/
	this.hooks={
		'create': [],
		'unhide': [],
		'hide': []
	};
	/** list of unique IDs of hook functions, to avoid duplicates
		@private
	*/
	this.hookIds={};
	/** List of downloads associated with the popup.
		@private
		@type Array
	*/
	this.downloads=[];
	/** Number of uncompleted downloads.
		@type 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);
//	}
}

/**
   A UID for each Navpopup. This constructor property is just a counter.
   @type integer
   @private
*/
Navpopup.uid=0;

/**
   Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible.
   @type boolean
*/
Navpopup.prototype.isVisible=function() {
	return this.visible;
};

/**
   Repositions popup using CSS style.
   @private
   @param {integer} x x-coordinate (px)
   @param {integer} y y-coordinate (px)
   @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition}
*/
Navpopup.prototype.reposition= function (x,y, noLimitHor) {
	log ('reposition('+x+','+y+','+noLimitHor+')');
	if (typeof x != 'undefined' && x !== null) { this.left=x; }
	if (typeof y != 'undefined' && y !== null) { this.top=y; }
	if (typeof this.left != 'undefined' && typeof this.top != 'undefined') {
		this.mainDiv.style.left=this.left + 'px';
		this.mainDiv.style.top=this.top + 'px';
	}
	if (!noLimitHor) { this.limitHorizontalPosition(); }
	//console.log('navpop'+this.uid+' - (left,top)=(' + this.left + ',' + this.top + '), css=('
	//+ this.mainDiv.style.left + ',' + this.mainDiv.style.top + ')');
};

/**
   Prevents popups from being in silly locations. Hopefully.
   Should not be run if {@link #constrained} is true.
   @private
*/
Navpopup.prototype.limitHorizontalPosition=function() {
	if (!this.constrained || this.tooWide) { return; }
	this.updateDimensions();
	var x=this.left;
	var w=this.width;
	var cWidth=document.body.clientWidth;


//	log('limitHorizontalPosition: x='+x+
//			', this.left=' + this.left +
//			', this.width=' + this.width +
//			', cWidth=' + cWidth);


	if ( (x+w) >= cWidth ||
		 ( x > 0 &&
		 	this.maxWidth &&
		 	this.width < this.maxWidth &&
		 	this.height > this.width &&
		 	x > cWidth - this.maxWidth ) ) {
		// This is a very nasty hack. There has to be a better way!
		// We find the "natural" width of the div by positioning it at the far left
		// then reset it so that it should be flush right (well, nearly)
		this.mainDiv.style.left='-10000px';
		this.mainDiv.style.width = this.maxWidth + 'px';
		var naturalWidth=parseInt(this.mainDiv.offsetWidth, 10);
		var newLeft=cWidth - naturalWidth - 1;
		if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups?
		log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth);
		this.reposition(newLeft, null, true);
	}
};

/**
   Counter indicating the z-order of the "highest" popup.
   We start the z-index at 1000 so that popups are above everything
   else on the screen.
   @private
   @type integer
*/
Navpopup.highest=1000;

/**
   Brings popup to the top of the z-order.
   We increment the {@link #highest} property of the contructor here.
   @private
*/
Navpopup.prototype.raise = function () {
	this.mainDiv.style.zIndex=Navpopup.highest + 1;
	++Navpopup.highest;
};

/**
   Shows the popup provided {@link #noshow} is not true.
   Updates the position, brings the popup to the top of the z-order and unhides it.
*/
Navpopup.prototype.show = function () {
	//document.title+='s';
	if (this.noshow) { return; }
	//document.title+='t';
	this.reposition();
	this.raise();
	this.unhide();
};

/**
   Checks to see if the mouse pointer has
   stabilised (checking every <code>time</code>/2 milliseconds) and runs the
   {@link #show} method if it has.
   @param {integer} time The minimum time (ms) before the popup may be shown.
*/
Navpopup.prototype.showSoonIfStable = function (time) {
	log ('showSoonIfStable, time='+time);
	if (this.visible) { return; }
	this.noshow = false;

	//~ initialize these variables so that we never run @tt{show} after
	//~ just half the time
	this.stable_x = -10000; this.stable_y = -10000;

	var stableShow = function() {
		log('stableShow called');
		var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y;
		var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y;
		var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz;
		//document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] ';
		if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) {
			log ('mouse is stable');
			clearInterval(savedThis.showSoonStableTimer);
			savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]);
			savedThis.show.apply(savedThis, []);
			savedThis.limitHorizontalPosition.apply(savedThis, []);
			return;
		}
		savedThis.stable_x = new_x; savedThis.stable_y = new_y;
	};
	var savedThis = this;
	this.showSoonStableTimer = setInterval(stableShow, time/2);
};

/**
   Sets the {@link #noshow} flag and hides the popup. This should be called
   when the mouse leaves the link before
   (or after) it's actually been displayed.
*/
Navpopup.prototype.banish = function () {
	log ('banish called');
	// hide and prevent showing with showSoon in the future
	this.noshow=true;
	if (this.showSoonStableTimer) {
		log('clearing showSoonStableTimer');
		clearInterval(this.showSoonStableTimer);
	}
	this.hide();
};

/**
   Runs hooks added with {@link #addHook}.
   @private
   @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
   @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
*/
Navpopup.prototype.runHooks = function (key, when) {
	if (!this.hooks[key]) { return; }
	var keyHooks=this.hooks[key];
	var len=keyHooks.length;
	for (var i=0; i< len; ++i) {
		if (keyHooks[i] && keyHooks[i].when == when) {
			if (keyHooks[i].hook.apply(this, [])) {
				// remove the hook
				if (keyHooks[i].hookId) {
					delete this.hookIds[keyHooks[i].hookId];
				}
				keyHooks[i]=null;
			}
		}
	}
};

/**
   Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments.
   @param {Function} hook The hook function. Functions that return true are deleted.
   @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
   @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
   @param {String} uid A truthy string identifying the hook function; if it matches another hook in this position, it won't be added again.
*/
Navpopup.prototype.addHook = function ( hook, key, when, uid ) {
	when = when || 'after';
	if (!this.hooks[key]) { return; }
	// if uid is specified, don't add duplicates
	var hookId=null;
	if (uid) {
		hookId=[key,when,uid].join('|');
		if (this.hookIds[hookId]) {
			return;
		}
		this.hookIds[hookId]=true;
	}
	this.hooks[key].push( {hook: hook, when: when, hookId: hookId} );
};

/**
   Creates the main DIV element, which contains all the actual popup content.
   Runs hooks with key 'create'.
   @private
*/
Navpopup.prototype.createMainDiv = function () {
	if (this.mainDiv) { return; }
	this.runHooks('create', 'before');
	var mainDiv=document.createElement('div');

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

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

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

	this.mainDiv=mainDiv;
	document.body.appendChild(mainDiv);
	this.runHooks('create', 'after');
};
/**
   Calls the {@link #raise} method.
   @private
*/
Navpopup.prototype.onclickHandler=function(/*e*/) {
	this.raise();
};
/**
   Makes the popup draggable, using a {@link Drag} object.
   @private
*/
Navpopup.prototype.makeDraggable=function(handleName) {
	if (!this.mainDiv) { this.createMainDiv(); }
	var drag=new Drag();
	if (!handleName) {
		drag.startCondition=function(e) {
		try { if (!e.shiftKey) { return false; } } catch (err) { return false; }
		return true;
		};
	}
	var dragHandle;
	if (handleName) dragHandle = document.getElementById(handleName);
	if (!dragHandle) dragHandle = this.mainDiv;
	var np=this;
	drag.endHook=function(x,y) {
		Navpopup.tracker.dirty=true;
		np.reposition(x,y);
	};
	drag.init(dragHandle,this.mainDiv);
};

/** Hides the popup using CSS. Runs hooks with key 'hide'.
	Sets {@link #visible} appropriately.	 {@link #banish} should be called externally instead of this method.

	@private
*/
Navpopup.prototype.hide = function () {
	this.runHooks('hide', 'before');
	this.abortDownloads();
	if (typeof this.visible != 'undefined' && this.visible) {
		this.mainDiv.style.display='none';
		this.visible=false;
	}
	this.runHooks('hide', 'after');
};

/** Shows the popup using CSS. Runs hooks with key 'unhide'.
	Sets {@link #visible} appropriately.   {@link #show} should be called externally instead of this method.
	@private
*/
Navpopup.prototype.unhide = function () {
	this.runHooks('unhide', 'before');
	if (typeof this.visible != 'undefined' && !this.visible) {
		this.mainDiv.style.display='inline';
		this.visible=true;
	}
	this.runHooks('unhide', 'after');
};

/**
   Sets the <code>innerHTML</code> attribute of the main div containing the popup content.
   @param {String} html The HTML to set.
*/
Navpopup.prototype.setInnerHTML = function (html) {
	this.mainDiv.innerHTML = html;
};

/**
   Updates the {@link #width} and {@link #height} attributes with the CSS properties.
   @private
*/
Navpopup.prototype.updateDimensions = function () {
	this.width=parseInt(this.mainDiv.offsetWidth, 10);
	this.height=parseInt(this.mainDiv.offsetHeight, 10);
};

/**
   Checks if the point (x,y) is within {@link #fuzz} of the
   {@link #mainDiv}.
   @param {integer} x x-coordinate (px)
   @param {integer} y y-coordinate (px)
   @type boolean
*/
Navpopup.prototype.isWithin = function(x,y) {
	//~ If we're not even visible, no point should be considered as
	//~ being within the popup.
	if (!this.visible) { return false; }
	this.updateDimensions();
	var fuzz=this.fuzz || 0;
	//~ Use a simple box metric here.
	return (x+fuzz >= this.left && x-fuzz <= this.left + this.width &&
		y+fuzz >= this.top  && y-fuzz <= this.top  + this.height);
};

/**
   Adds a download to {@link #downloads}.
   @param {Downloader} download
*/
Navpopup.prototype.addDownload=function(download) {
	if (!download) { return; }
	this.downloads.push(download);
};
/**
   Aborts the downloads listed in {@link #downloads}.
   @see Downloader#abort
*/
Navpopup.prototype.abortDownloads=function() {
	for(var i=0; i<this.downloads.length; ++i) {
		var d=this.downloads[i];
		if (d && d.abort) { d.abort(); }
	}
	this.downloads=[];
};


/**
   A {@link Mousetracker} instance which is a property of the constructor (pseudo-global).
*/
Navpopup.tracker=new Mousetracker();
// ENDFILE: navpopup.js
// STARTFILE: diff.js
//<NOLITE>
/*
 * Javascript Diff Algorithm
 *  By John Resig (http://ejohn.org/) and [[:en:User:Lupin]]
 *
 * More Info:
 *  http://ejohn.org/projects/javascript-diff-algorithm/
 */

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

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

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


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

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

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

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

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

// see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object
// FIXME: use obj.hasOwnProperty instead of this kludge!
var jsReservedProperties=RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' +
				   '|eval|hasOwnProperty|propertyIsEnumerable' +
				   '|to(Source|String|LocaleString)|(un)?watch|valueOf)$');
function diffBugAlert(word) {
	if (!diffBugAlert.list[word]) {
		diffBugAlert.list[word]=1;
		alert('Bad word: '+word+'\n\nPlease report this bug.');
	}
}
diffBugAlert.list={};

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

function diff( o, n ) {

	// pass 1: make hashtable ns with new rows as keys
	var ns = makeDiffHashtable(n);

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

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

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

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

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

function setUserInfo() {
	var api = new mw.Api( {
		    ajax: {
		        headers: { 'Api-User-Agent': pg.misc.userAgent }
		    }
		} );
	var params = {
			action: 'query',
			list: 'users',
			ususers: mw.config.get('wgUserName'),
			usprop: 'rights'
		};
    
 	pg.user.canReview = false;
    if (getValueOf('popupReview')) {
		api.get(params).done(function(data){
			var rights = data.query.users[0].rights;
			pg.user.canReview = rights.indexOf('review') !== -1; // TODO: Should it be a getValueOf('ReviewRight') ?
		});
    }
}

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

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

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

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


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

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

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

function setRegexps() {
	// TODO: We shoud use an api call to get the aliases for special pages, now it does not work for non-English wikipedias: 
	// E.g., https://ru.wikipedia.org/w/api.php?action=query&meta=siteinfo&siprop=specialpagealiases&formatversion=2
	setMainRegex();
	var sp=nsRe(pg.nsSpecialId);
	pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ;
	pg.re.contribs  =RegExp('(title=|/)'  + sp + '(?:%3A|:)(?:Contributions' + getValueOf('popupSPAliasesContribs') + ')(&target=|/|/' + nsRe(pg.nsUserId)+':)(.*)') ;
	pg.re.email	    =RegExp('(title=|/)'  + sp + '(?:%3A|:)(?:EmailUser' + getValueOf('popupSPAliasesEmail') + ')(&target=|/|/(?:' + nsRe(pg.nsUserId)+':)?)(.*)') ;
	pg.re.backlinks =RegExp('(title=|/)'  + sp + '(?:%3A|:)(?:WhatLinksHere' + getValueOf('popupSPAliasesBacklinks') + ')(&target=|/)([^&]*)');
	pg.re.specialdiff=RegExp('/'          + sp + '(?:%3A|:)Diff/([^?#]*)');

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

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

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

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

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


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

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

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

	setupCache();

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

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

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

	pg.misc.userAgent = 'Navigation popups/1.0 (' + mw.config.get( 'wgServerName' ) +')';
}

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

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

		// regexps
		setRegexps();
		setRedirs();

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

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

		setupPopups.completed = true;
		if ( $.isFunction( 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>>}';
	}

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

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

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

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

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

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

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

		var testString=splitted[i+2-1];
		var trueString=splitted[i+3-1];
		var falseString=splitted[i+5-1];
		if (typeof falseString=='undefined' || !falseString) { falseString=''; }
		var testResult=null;

		switch (testString) {
		case 'user':
			testResult=(article.userName())?true:false;
			break;
		case 'talk':
			testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages
			break;
		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;
		}

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

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

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


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

}

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


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

function navlinkStringToHTML(s,article,params) {
	//limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article));
	var p=navlinkStringToArray(s,article,params);
	var html='';
	var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it
	var menurowdepth = 0;
	for (var i=0; i<p.length; ++i) {
		if (typeof p[i] == typeof '') {
			html+=navlinkSubstituteHTML(p[i]);
			menudepth += navlinkDepth('menu', p[i]);
			menurowdepth += navlinkDepth('menurow', p[i]);
//			if (menudepth === 0) {
//				tagType='span';
//			} else if (menurowdepth === 0) {
//				tagType='li';
//			} else {
//				tagType = null;
//			}
		} else if (typeof p[i].type != 'undefined' && p[i].type=='navlinkTag') {
			if (menudepth > 0 && menurowdepth === 0) {
				html += '<li class="popup_menu_item">' + p[i].html() + '</li>';
			} else {
				html+=p[i].html();
			}
		}
	}
	return html;
}

function navlinkTag() {
	this.type='navlinkTag';
}

navlinkTag.prototype.html=function () {
	this.getNewWin();
	this.getPrintFunction();
	var html='';
	var opening, closing;
	var tagType='span';
	if (!tagType) {
		opening = ''; closing = '';
	} else {
		opening = '<' + tagType + ' class="popup_' + this.id + '">';
		closing = '</' + tagType + '>';
	}
	if (typeof this.print!='function') {
		errlog ('Oh dear - invalid print function for a navlinkTag, id='+this.id);
	} else {
		html=this.print(this);
		if (typeof html != typeof '') {html='';}
		else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut);
	}
	return opening + html + closing;
};

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

navlinkTag.prototype.getPrintFunction=function() { //think about this some more
	// this.id and this.article should already be defined
	if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; }

	this.noPopup=1;
	switch (this.id) {
	case 'contribs': case 'history': case 'whatLinksHere':
	case 'userPage': case 'monobook': case 'userTalk':
	case 'talk': case 'article': case 'lastEdit':
		this.noPopup=null;
	}
	switch (this.id) {
	case 'email':	 case 'contribs':  case 'block':	 case 'unblock':
	case 'userlog':   case 'userSpace': case 'deletedContribs':
		this.article=this.article.userName();
	}

	switch (this.id) {
	case 'userTalk': case 'newUserTalk': case 'editUserTalk':
	case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog':
		this.article=this.article.userName(true);
		/* fall through */
	case 'pagelog': case 'deletelog': case 'protectlog':
	delete this.oldid;
	}

	if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); }

	if (this.id != 'mainlink') {
		// FIXME anchor handling should be done differently with Title object
		this.article=this.article.removeAnchor();
		// if (typeof this.text=='undefined') this.text=popupString(this.id);
	}

	switch (this.id) {
	case 'undelete':       this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break;
	case 'whatLinksHere':  this.print=specialLink; this.specialpage='Whatlinkshere'; break;
	case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break;
	case 'move':           this.print=specialLink; this.specialpage='Movepage'; break;
	case 'contribs':       this.print=specialLink; this.specialpage='Contributions'; break;
	case 'deletedContribs':this.print=specialLink; this.specialpage='Deletedcontributions'; break;
	case 'email':          this.print=specialLink; this.specialpage='EmailUser'; this.sep='/'; break;
	case 'block':          this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break;
	case 'unblock':        this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break;
	case 'userlog':        this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break;
	case 'blocklog':       this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break;
	case 'pagelog':        this.print=specialLink; this.specialpage='Log'; this.sep='&page='; break;
	case 'protectlog':     this.print=specialLink; this.specialpage='Log'; this.sep='&type=protect&page='; break;
	case 'deletelog':      this.print=specialLink; this.specialpage='Log'; this.sep='&type=delete&page='; break;
	case 'userSpace':      this.print=specialLink; this.specialpage='PrefixIndex'; this.sep='&namespace=2&prefix='; break;
	case 'search':         this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break;
	case 'thank':          this.print=specialLink; this.specialpage='Thanks'; this.sep='/'; this.article.value = this.diff; break;
	case 'unwatch': case 'watch':
		this.print=magicWatchLink; this.action=this.id+'&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken='+autoClickToken(); break;
	case 'history': case 'historyfeed': 
	case 'unprotect': case 'protect':
		this.print=wikiLink; this.action=this.id; break;

	case 'delete':
		this.print=wikiLink; this.action='delete';
		if (this.article.namespaceId()==pg.nsImageId) {
			var img=this.article.stripNamespace();
			this.action+='&image='+img;
		}
	break;

	case 'markpatrolled':
	case 'edit': // editOld should keep the oldid, but edit should not.
		delete this.oldid;
		/* fall through */
	case 'view': case 'purge': case 'render':
		this.print=wikiLink;
		this.action=this.id; break;
	case 'raw':
		this.print=wikiLink; this.action='raw'; break;
	case 'new':
		this.print=wikiLink; this.action='edit&section=new'; break;
	case 'mainlink':
		if (typeof this.text=='undefined') { this.text=this.article.toString().entify(); }
		if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) {
			// only show the /subpage part of the title text
			var s=this.text.split('/'); this.text=s[s.length-1];
			if (this.text==='' && s.length > 1) { this.text=s[s.length-2]; }
		}
		this.print=titledWikiLink;
		if (typeof this.title==='undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') {
			this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article);
			if (typeof this.oldid !== 'undefined' && this.oldid) {
			this.title=tprintf('Revision %s of %s', [this.oldid, this.title]);
			}
		}
		this.action='view'; break;
	case 'userPage':
	case 'article':
	case 'monobook':
	case 'editMonobook':
	case 'editArticle':
		delete this.oldid;
		//alert(this.id+'\n'+this.article + '\n'+ typeof this.article);
			this.article=this.article.articleFromTalkOrArticle();
		//alert(this.id+'\n'+this.article + '\n'+ typeof this.article);
		this.print=wikiLink;
		if (this.id.indexOf('edit')===0) {
			this.action='edit';
		} else { this.action='view';}
		break;
	case 'userTalk':
	case 'talk':
		this.article=this.article.talkPage();
		delete this.oldid;
		this.print=wikiLink;
		this.action='view'; break;
	case 'arin':
		this.print=arinLink; break;
	case 'count':
		this.print=editCounterLink; break;
	case 'google':
		this.print=googleLink; break;
	case 'editors':
		this.print=editorListLink; break;
	case 'globalsearch':
		this.print=globalSearchLink; break;
	case 'lastEdit':
		this.print=titledDiffLink;
		this.title=popupString('Show the last edit');
		this.from='prev'; this.to='cur'; break;
	case 'oldEdit':
		this.print=titledDiffLink;
		this.title=popupString('Show the edit made to get revision') + ' ' + this.oldid;
		this.from='prev'; this.to=this.oldid; break;
	case 'editOld':
		this.print=wikiLink; this.action='edit'; break;
	case 'undo':
		this.print=wikiLink; this.action='edit&undo='; break;
	case 'revert':
		this.print=wikiLink; this.action='revert'; break;
	case 'nullEdit':
		this.print=wikiLink; this.action='nullEdit'; break;
	case 'diffCur':
		this.print=titledDiffLink;
		this.title=tprintf('Show changes since revision %s', [this.oldid]);
		this.from=this.oldid; this.to='cur'; break;
	case 'editUserTalk':
	case 'editTalk':
	delete this.oldid;
		this.article=this.article.talkPage();
		this.action='edit'; this.print=wikiLink; break;
	case 'newUserTalk':
	case 'newTalk':
		this.article=this.article.talkPage();
		this.action='edit&section=new'; this.print=wikiLink; break;
	case 'lastContrib':
	case 'sinceMe':
		this.print=magicHistoryLink;
	break;
	case 'togglePreviews':
		this.text=popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews');
	/* fall through */
	case 'disablePopups': case 'purgePopups':
		this.print=popupMenuLink;
	break;
	default:
		this.print=function () {return 'Unknown navlink type: '+this.id+'';};
	}
};
//
//  end navlinks
//////////////////////////////////////////////////
//</NOLITE>
// ENDFILE: navlinks.js
// 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);
	var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A');
	var startLink=0;
	var i,j;

	if (popupHandleKeypress.lastPopupLinkSelected) {
		for (i=0; i<links.length; ++i) {
			if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; }
		}
	}
	for (j=0; j<links.length; ++j) {
		i=(startLink + j + 1) % links.length;
		if (links[i].getAttribute('popupkey')==letter) {
			if (evt && evt.preventDefault) evt.preventDefault();
			links[i].focus();
			popupHandleKeypress.lastPopupLinkSelected=links[i];
			return false; // swallow keypress
		}
	}

	// pass keypress on
	if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); }
	return true;
}

function addPopupShortcuts() {
	if (document.onkeypress!=popupHandleKeypress) {
		document.oldPopupOnkeypress=document.onkeypress;
	}
	document.onkeypress=popupHandleKeypress;
}

function rmPopupShortcuts() {
	popupHandleKeypress.lastPopupLinkSelected=null;
	try {
		if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) {
			// panic
			document.onkeypress=null; //function () {};
			return;
		}
		document.onkeypress=document.oldPopupOnkeypress;
	} catch (nasties) { /* IE goes here */ }
}


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

function addPopupShortcut(html, key) {
	if (!getValueOf('popupShortcutKeys')) { return html; }
	var ret= addLinkProperty(html, 'popupkey="'+key+'"');
	if (key==' ') { key=popupString('spacebar'); }
	return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4');
}
//</NOLITE>
// ENDFILE: shortcutkeys.js
// STARTFILE: diffpreview.js
//<NOLITE>
//lets jump through hoops to find the rev ids we need to retrieve
function loadDiff(article, oldid, diff, navpop) {
	navpop.diffData={ oldRev: {}, newRev: {} };
	mw.loader.using( 'mediawiki.api' ).then( function() {
		var api = new mw.Api( {
		    ajax: {
		        headers: { 'Api-User-Agent': pg.misc.userAgent }
		    }
		} );
		var params = {
			action: 'compare',
			prop: 'ids|title'
		};
		if(article.title){
			params.fromtitle = article.title;
		}
		
		switch (diff) {
			case 'cur':
				switch ( oldid ) {
				case null:
				case '':
				case 'prev':
					// this can only work if we have the title
					// cur -> prev
					params.torelative = 'prev';
					break;
				default:
					params.fromrev = oldid;
					params.torelative = 'cur';
					break;
				}
				break;
			case 'prev':
				if( oldid ) {
					params.fromrev = oldid;
				} else {
					params.fromtitle;
				}
				params.torelative = 'prev';
				break;
			case 'next':
				params.fromrev = oldid || 0;
				params.torelative = 'next';
				break;
			default:
				params.fromrev = oldid || 0;
				params.torev = diff || 0;
				break;
			}
		
		api.get( params ).then( function( data ) {
			navpop.diffData.oldRev.revid = data.compare.fromrevid;
			navpop.diffData.newRev.revid = data.compare.torevid;
			
			addReviewLink(navpop, 'popupMiscTools');

			var go = function() {
				pendingNavpopTask(navpop);
				var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';
	
				url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid;
				url += '&prop=revisions&rvprop=ids|timestamp|content';
			
				getPageWithCaching(url, doneDiff, navpop);
		
				return true; // remove hook once run
			};
			if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
			else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); }
		} );
	} );
}

// Put a "mark patrolled" link to an element target 
// TODO: Allow patrol a revision, as well as a diff
function addReviewLink (navpop, target) {
	if (! pg.user.canReview) return;
	// If 'newRev' is older than 'oldRev' than it could be confusing, so we do not show the review link.
	if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) return;
	var api = new mw.Api( {
		    ajax: {
		        headers: { 'Api-User-Agent': pg.misc.userAgent }
		    }
		} );
	var params = {
		action: 'query',
		prop: 'info|flagged',
		revids: navpop.diffData.oldRev.revid,
		formatversion : 2
	};
	api.get (params).then(function(data){
		var stable_revid = data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid || 0;
		// The diff can be reviewed if the old version is the last reviewed version
		// TODO: Other possible conditions that we may want to implement instead of this one:
		//  * old version is patrolled and the new version is not patrolled
		//  * old version is patrolled and the new version is more recent than the last reviewed version
		if (stable_revid == navpop.diffData.oldRev.revid) {
			var a = document.createElement('a');
			a.innerHTML = popupString('mark patrolled');
			a.title=popupString('markpatrolledHint');
			a.onclick = function() {
				var params = {
					action: 'review',
					revid: navpop.diffData.newRev.revid,
					comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid])
				};
				api.postWithToken('csrf',params).done(function(){
					a.style.display = "none";
					// TODO: Update current page and other already constructed popups 
				} ).fail(function(){
					alert(popupString('Could not marked this edit as patrolled'));
				});
			};
			setPopupHTML(a, target, navpop.idNumber,null,true);
		}
	});
}

function doneDiff(download) {
	if (!download.owner || !download.owner.diffData) { return; }
	var navpop=download.owner;
	completedNavpopTask(navpop);
	
	var pages, revisions=[];
	try{
		// Process the downloads
		pages = getJsObj(download.data).query.pages;
		for(var i=0; i < pages.length; i++ ) {
			revisions = revisions.concat(pages[i].revisions);
		}
		for(i=0; i< revisions.length; i++){
			if(revisions[i].revid == navpop.diffData.oldRev.revid) {
				navpop.diffData.oldRev.revision = revisions[i];
			} else if (revisions[i].revid == navpop.diffData.newRev.revid) {
				navpop.diffData.newRev.revision = revisions[i];
			}
		}
	} catch(someError) {
		errlog( 'Could not get diff' );
	}
	
	insertDiff(navpop);
}

function rmBoringLines(a,b,context) {

	if (typeof context == 'undefined') { context=2; }
	// this is fairly slow... i think it's quicker than doing a word-based diff from the off, though
	var aa=[], aaa=[];
	var bb=[], bbb=[];
	var i, j;

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

	// pick up remaining disconnected nodes in b
	for (i=0; i<b.length; ++i ) {
		if (bb[i]==1) { continue; }
		if(!b[i].paired) { bb[i]=1; }
	}

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

	for (i=0; i<a.length; ++i) {
		if ( aa[i] == 1 ) {
			for (j=Math.max(0,i-context); j < Math.min(a.length, i+context); ++j) {
				if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; }
			}
		}
	}

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

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

function stripOuterCommonLines(a,b,context) {
	var i=0;
	while (i<a.length && i < b.length && a[i]==b[i]) { ++i; }
	var j=a.length-1; var k=b.length-1;
	while ( j>=0 && k>=0 && a[j]==b[k] ) { --j; --k; }

	return { a: a.slice(Math.max(0,i - 1 - context), Math.min(a.length+1, j + context+1)),
				b: b.slice(Math.max(0,i - 1 - context), Math.min(b.length+1, k + context+1)) };
}

function insertDiff(navpop) {
	// for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff
	// FIXME: sometimes this gives misleading diffs as distant chunks are squashed together
	var oldlines = navpop.diffData.oldRev.revision.content.split('\n');
	var newlines = navpop.diffData.newRev.revision.content.split('\n');
	var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines'));
	oldlines=inner.a; newlines=inner.b;
	var truncated=false;
	getValueOf('popupDiffMaxLines');
	if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) {
		// truncate
		truncated=true;
		inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines),
						newlines.slice(0,pg.option.popupDiffMaxLines),
						pg.option.popupDiffContextLines);
		oldlines=inner.a; newlines=inner.b;
	}

	var lineDiff=diff(oldlines, newlines);
	var lines2=rmBoringLines(lineDiff.o, lineDiff.n);
	var oldlines2=lines2.a; var newlines2=lines2.b;

	var simpleSplit = !String.prototype.parenSplit.isNative;
	var html='<hr />';
	if (getValueOf('popupDiffDates')) {
		html += diffDatesTable(navpop);
		html += '<hr />';
	}
	html += shortenDiffString(
		diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),
		getValueOf('popupDiffContextCharacters') ).join('<hr />');
	setPopupTipsAndHTML(html.split('\n').join('<br>') +
			 (truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') ,
				'popupPreview', navpop.idNumber);
}

function diffDatesTable( navpop ) {
	var html='<table class="popup_diff_dates">';
	html += diffDatesTableRow( navpop.diffData.newRev.revision, tprintf('New revision'));
	html += diffDatesTableRow( navpop.diffData.oldRev.revision, tprintf('Old revision'));
	html += '</table>';
	return html;
}
function diffDatesTableRow( revision, label ) {
	var txt='';
	var lastModifiedDate = new Date(revision.timestamp);
	var datePrint=getValueOf('popupDiffDatePrinter');
	if (typeof lastModifiedDate[datePrint] == 'function') {
		var d2 = adjustDate(lastModifiedDate, getTimeOffset());
		txt = dayFormat(d2, true) + ' ' + timeFormat(d2, true);
	} else {
		txt = tprintf('Invalid %s %s', ['popupDiffDatePrinter', datePrint]);
	}
	var revlink = generalLink({url: mw.config.get('wgScript') + '?oldid='+revision.revid,
				   text: label, title: label});
	return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]);
}
//</NOLITE>
// ENDFILE: diffpreview.js
// STARTFILE: links.js
//<NOLITE>
/////////////////////
// LINK GENERATION //
/////////////////////

// titledDiffLink --> titledWikiLink --> generalLink
// wikiLink	   --> titledWikiLink --> generalLink
// editCounterLink --> generalLink

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

function titledDiffLink(l) { // article, text, title, from, to) {
	return titledWikiLink({article: l.article, action: l.to + '&oldid=' + l.from,
				newWin: l.newWin,
				noPopup: l.noPopup,
				text: l.text, title: l.title,
				/* hack: no oldid here */
				actionName: 'diff'});
}


function wikiLink(l) {
	//{article:article, action:action, text:text, oldid, newid}) {
	if (! (typeof l.article == typeof {} &&
		typeof l.action == typeof '' &&
		typeof l.text==typeof '')) return null;
	if (typeof l.oldid == 'undefined') { l.oldid=null; }
	var savedOldid = l.oldid;
	if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) { l.oldid=null; }
	var hint=popupString(l.action + 'Hint'); // revertHint etc etc etc
	var oldidData=[l.oldid, safeDecodeURI(l.article)];
	var revisionString = tprintf('revision %s of %s', oldidData);
	log('revisionString='+revisionString);
	switch (l.action) {
	case 'edit&section=new': hint = popupString('newSectionHint');  break;
	case 'edit&undo=':
		if (l.diff && l.diff != 'prev' && savedOldid ) {
		  l.action += l.diff + '&undoafter=' + savedOldid;
		} else if (savedOldid) {
		  l.action += savedOldid;
		}
		hint = popupString('undoHint');
		break;
	case 'raw&ctype=text/css': hint=popupString('rawHint'); break;
	case 'revert':
		var p=parseParams(pg.current.link.href);
		l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff);
		if (p.diff=='prev') {
			l.action += '&direction=prev';
			revisionString = tprintf('the revision prior to revision %s of %s', oldidData);
		}
		if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; }
		if (getValueOf('popupMinorReverts')) { l.action += '&autominor=true'; }
		log('revisionString is now '+revisionString);
		break;
	case 'nullEdit':
		l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null';
		break;
	case 'historyfeed':
		l.action='history&feed=rss';
		break;
	case 'markpatrolled':
		l.action='markpatrolled&rcid='+l.rcid;
	}

	if (hint) {
		if (l.oldid) {
			hint = simplePrintf(hint, [revisionString]);
		}
		else {
			hint = simplePrintf(hint, [safeDecodeURI(l.article)]);
		}
	}
	else {
		hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid='+l.oldid : '';
	}

	return titledWikiLink({article: l.article, action: l.action, text: l.text, newWin:l.newWin,
				title: hint, oldid: l.oldid, noPopup: l.noPopup, onclick: l.onclick});
}

function revertSummary(oldid, diff) {
	var ret='';
	if (diff == 'prev') {
		ret=getValueOf('popupQueriedRevertToPreviousSummary');
	} else { ret = getValueOf('popupQueriedRevertSummary'); }
	return ret + '&autorv=' + oldid;
}

function titledWikiLink(l) {
	// possible properties of argument:
	// article, action, text, title, oldid, actionName, className, noPopup
	// oldid = null is fine here

	// article and action are mandatory args

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

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

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

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

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

	var cssClass=pg.misc.defaultNavlinkClassname;
	if (typeof l.className!='undefined' && l.className) { cssClass=l.className; }

	return generalNavLink({url: url, newWin: l.newWin,
				title: (typeof l.title != 'undefined') ? l.title : null,
				text: (typeof l.text!='undefined')?l.text:null,
				className: cssClass, noPopup:l.noPopup, onclick:l.onclick});
}

pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) {
	getHistoryInfo(wikipage, function(x) {
		processLastContribInfo(x, {page: wikipage, newWin: newWin});
	});
};

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

pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) {
	getHistoryInfo(wikipage, function(x){
		processDiffSinceMyEdit(x, {page: wikipage, newWin: newWin});
	});
};

function processDiffSinceMyEdit(info, stuff) {
	if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; }
	var friendlyName=stuff.page.split('_').join(' ');
	if(!info.myLastEdit) {
		alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s',
				  [info.userName, getValueOf('popupHistoryLimit'), friendlyName]));
		return;
	}
	if(info.myLastEdit.index === 0) {
		alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName]));
		return;
	}
	var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+ info.myLastEdit.oldid;
	displayUrl(newUrl, stuff.newWin);
}

function displayUrl(url, newWin){
	if(newWin) { window.open(url); }
	else { document.location=url; }
}

pg.fn.purgePopups = function purgePopups() {
	processAllPopups(true);
	setupCache(); // deletes all cached items (not browser cached, though...)
	pg.option={};
	abortAllDownloads();
};

function processAllPopups(nullify, banish) {
	for (var i=0; pg.current.links && i<pg.current.links.length; ++i) {
		if (!pg.current.links[i].navpopup) { continue; }
		if (nullify || banish) pg.current.links[i].navpopup.banish();
		pg.current.links[i].simpleNoMore=false;
		if (nullify) pg.current.links[i].navpopup=null;
	}
}

pg.fn.disablePopups = function disablePopups(){
	processAllPopups(false, true);
	setupTooltips(null, true);
};

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

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

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

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

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

	var jsUrl='', title='', onClick='';
	switch(l.id) {
	case 'lastContrib':
		onClick=simplePrintf('pg.fn.getLastContrib(\'%s\',%s)',
			[l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);
		title=popupString('lastContribHint');
		break;
	case 'sinceMe':
		onClick=simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)',
			[l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);
		title=popupString('sinceMeHint');
		break;
	}
	jsUrl = 'javascript:' + onClick; // jshint ignore:line
	onClick += ';return false;';

	return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think
				title: title, text: l.text, noPopup: l.noPopup, onclick: onClick });
}

function popupMenuLink(l) {
	var jsUrl=simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line
	var title=popupString(simplePrintf('%sHint', [l.id]));
	var onClick=simplePrintf('pg.fn.%s();return false;', [l.id]);
	return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup, onclick: onClick});
}

function specialLink(l) {
	// properties: article, specialpage, text, sep
	if (typeof l.specialpage=='undefined'||!l.specialpage) return null;
	var base = pg.wiki.titlebase +  mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage;
	if (typeof l.sep == 'undefined' || l.sep === null) l.sep='&target=';
	var article=l.article.urlString({keepSpaces: l.specialpage=='Search'});
	var hint=popupString(l.specialpage+'Hint');
	switch (l.specialpage) {
	case 'Log':
		switch (l.sep) {
		case '&user=': hint=popupString('userLogHint'); break;
		case '&type=block&page=': hint=popupString('blockLogHint'); break;
		case '&page=': hint=popupString('pageLogHint'); break;
		case '&type=protect&page=': hint=popupString('protectLogHint'); break;
		case '&type=delete&page=': hint=popupString('deleteLogHint'); break;
		default: log('Unknown log type, sep=' + l.sep); hint='Missing hint (FIXME)';
		}
		break;
	case 'PrefixIndex': article += '/'; break;
	}
	if (hint) hint = simplePrintf(hint, [safeDecodeURI(l.article)]);
	else hint = safeDecodeURI(l.specialpage+':'+l.article) ;

	var url = base + l.sep + article;
	return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin, noPopup:l.noPopup});
}

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

	// only quotation marks in the url can screw us up now... I think
	var url=l.url.split('"').join('%22');

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

function appendParamsToLink(linkstr, params) {
	var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i'));
	if (sp.length<2) return null;
	var ret=sp.shift() + sp.shift();
	ret += '&' + params + '"';
	ret += sp.join('');
	return ret;
}

function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional) {
	if (x.newTarget) {
		log ('changeLinkTargetLink: newTarget=' + x.newTarget);
	}
	if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) {
		log ('This might be an input problem: ' + x.oldTarget );
	}

	// FIXME: first character of page title as well as namespace should be case insensitive
	// eg [[category:X1]] and [[Category:X1]] are equivalent
	// this'll break if charAt(0) is nasty
	var cA=literalizeRegex(x.oldTarget);
	var chs=cA.charAt(0).toUpperCase();
	chs='['+chs + chs.toLowerCase()+']';
	var currentArticleRegexBit=chs+cA.substring(1);
	currentArticleRegexBit=currentArticleRegexBit
		.split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)')
		.split('\\(').join('(?:%28|\\()')
		.split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ?
	// leading and trailing space should be ignored, and anchor bits optional:
	currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*';
	// e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s*

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

	var title=x.title || mw.config.get('wgPageName').split('_').join(' ');
	var lk=titledWikiLink({article: new Title(title), newWin:x.newWin,
						action:  'edit',
						text:	x.text,
						title:   x.hint,
						className: 'popup_change_title_link'
						});
	var cmd='';
	if (x.newTarget) {
		// escape '&' and other nasties
		var t=x.newTarget;
		var s=literalizeRegex(x.newTarget);
		cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~[['+t+'|$1]]~g;';
		cmd += 's~\\[\\['+currentArticleRegexBit+'[|]~[['+t+'|~g;';
		cmd += 's~\\[\\['+s + '\\|' + s + '\\]\\]~[[' + t + ']]~g';
	} else {
		cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~$1~g;';
		cmd += 's~\\[\\['+currentArticleRegexBit+'[|](.*?)\\]\\]~$2~g';
	}
	// Build query
	cmd = 'autoedit=' + encodeURIComponent ( cmd );
	cmd += '&autoclick='+ encodeURIComponent( x.clickButton ) + '&actoken=' + encodeURIComponent( autoClickToken() );
	cmd += ( x.minor === null ) ? '' : '&autominor='+ encodeURIComponent( x.minor );
	cmd += ( x.watch === null ) ? '' : '&autowatch='+ encodeURIComponent( x.watch );
	cmd += '&autosummary='+encodeURIComponent(x.summary);
	cmd += '&autoimpl='+encodeURIComponent( popupString('autoedit_version') );
	return appendParamsToLink(lk, cmd);
}


function redirLink(redirMatch, article) {
	// NB redirMatch is in wikiText
	var ret='';

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

	else return '<br> ' + popupString('Redirects') + popupString(' to ') +
			 titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view',  /* FIXME: newWin */
							  text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')});
}

function arinLink(l) {
	if (!saneLinkCheck(l)) { return null; }
	if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null;

	var uN=l.article.userName();

	return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin,
				title: tprintf('Look up %s in ARIN whois database', [uN]),
				text: l.text, noPopup:1});
}

function toolDbName(cookieStyle) {
	var ret = mw.config.get('wgDBname');
	if (!cookieStyle) { ret+= '_p'; }
	return ret;
}

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

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


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

	var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search=';
	var article=l.article.urlString({keepSpaces:true});

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

function googleLink(l) {
	if(!saneLinkCheck(l)) return null;

	var base='https://www.google.com/search?q=';
	var article=l.article.urlString({keepSpaces:true});

	return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin,
				title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]),
				text: l.text, noPopup:1});
}

function editorListLink(l) {
	if(!saneLinkCheck(l)) return null;
	var article= l.article.articleFromTalkPage() || l.article;
	var url='https://xtools.wmflabs.org/articleinfo/' + 
		encodeURI( pg.wiki.hostname ) + '/' +
		article.urlString() +
		'?uselang=' + mw.config.get('wgUserLanguage');
	return generalNavLink({url:url,
				title: tprintf('editorListHint', [article]),
				newWin:l.newWin, text: l.text, noPopup:1});
}

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

//////////////////////////////////////////////////
// magic history links
//

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

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

function getHistory(wikipage, onComplete) {
	log('getHistory');
	var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' +
			new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit');
	log('getHistory: url='+url);
	return startDownload(url, pg.idNumber+'history', onComplete);
}

function processHistory(download) {
	var jsobj = getJsObj(download.data);
	try {
		var revisions = anyChild(jsobj.query.pages).revisions;
		var edits=[];
		for (var i=0; i<revisions.length; ++i) {
			edits.push({ oldid: revisions[i].revid, editor: revisions[i].user });
		}
		log('processed ' + edits.length + ' edits');
		return finishProcessHistory( edits, mw.config.get('wgUserName') );
	} catch (someError) {
		log('Something went wrong with JSON business');
		return finishProcessHistory([]);
	}
}


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

	histInfo.edits=edits;
	histInfo.userName=userName;

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

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

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

function setDefault(x, def) {
	return newOption(x, def);
}

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

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

function setOptions() {
	// user-settable parameters and defaults
	var userIsSysop = false;
	if ( mw.config.get('wgUserGroups') ) {
		for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) {
			if ( mw.config.get('wgUserGroups')[g] == "sysop" )
				userIsSysop = true;
		}
	}

	// Basic options
	newOption('popupDelay',               0.5);
	newOption('popupHideDelay',           0.5);
	newOption('simplePopups',             false);
	newOption('popupStructure',           'shortmenus');   // see later - default for popupStructure is 'original' if simplePopups is true
	newOption('popupActionsMenu',         true);
	newOption('popupSetupMenu',           true);
	newOption('popupAdminLinks',          userIsSysop);
	newOption('popupShortcutKeys',        false);
	newOption('popupHistoricalLinks',     true);
	newOption('popupOnlyArticleLinks',    true);
	newOption('removeTitles',             true);
	newOption('popupMaxWidth',            350);
	newOption('popupSimplifyMainLink',    true);
	newOption('popupAppendRedirNavLinks', true);
	newOption('popupTocLinks',            false);
	newOption('popupSubpopups',           true);
	newOption('popupDragHandle',          false /* 'popupTopLinks'*/);
	newOption('popupLazyPreviews',        true);
	newOption('popupLazyDownloads',       true);
	newOption('popupAllDabsStubs',        false);
	newOption('popupDebugging',           false);
	newOption('popupActiveNavlinks',      true);
	newOption('popupModifier',            false); // ctrl, shift, alt or meta
	newOption('popupModifierAction',      'enable'); // or 'disable'
	newOption('popupDraggable',           true);
	newOption('popupReview',              false);

//<NOLITE>
	// images
	newOption('popupImages',                 true);
	newOption('imagePopupsForImages',        true);
	newOption('popupNeverGetThumbs',         false);
	//newOption('popupImagesToggleSize',       true);
	newOption('popupThumbAction',            'imagepage'); //'sizetoggle');
	newOption('popupImageSize',              60);
	newOption('popupImageSizeLarge',         200);

	// redirs, dabs, reversion
	newOption('popupFixRedirs',             false);
	newOption('popupRedirAutoClick',        'wpDiff');
	newOption('popupFixDabs',               false);
	newOption('popupDabsAutoClick',         'wpDiff');
	newOption('popupRevertSummaryPrompt',   false);
	newOption('popupMinorReverts',          false);
	newOption('popupRedlinkRemoval',        false);
	newOption('popupRedlinkAutoClick',      'wpDiff');
	newOption('popupWatchDisambiggedPages', null);
	newOption('popupWatchRedirredPages',    null);
	newOption('popupDabWiktionary',         'last');

	// navlinks
	newOption('popupNavLinks',          true);
	newOption('popupNavLinkSeparator',  ' &sdot; ');
	newOption('popupLastEditLink',      true);
	newOption('popupEditCounterTool',   'supercount');
	newOption('popupEditCounterUrl',    '');
//</NOLITE>

	// previews etc
	newOption('popupPreviews',             true);
	newOption('popupSummaryData',          true);
	newOption('popupMaxPreviewSentences',  5);
	newOption('popupMaxPreviewCharacters', 600);
	newOption('popupLastModified',         true);
	newOption('popupPreviewKillTemplates', true);
	newOption('popupPreviewRawTemplates',  true);
	newOption('popupPreviewFirstParOnly',  true);
	newOption('popupPreviewCutHeadings',   true);
	newOption('popupPreviewButton',        false);
	newOption('popupPreviewButtonEvent',   'click');

//<NOLITE>
	// diffs
	newOption('popupPreviewDiffs',          true);
	newOption('popupDiffMaxLines',          100);
	newOption('popupDiffContextLines',      2);
	newOption('popupDiffContextCharacters', 40);
	newOption('popupDiffDates',             true);
	newOption('popupDiffDatePrinter',       'toLocaleString');

	// edit summaries. God, these are ugly.
	newOption('popupReviewedSummary',          popupString('defaultpopupReviewedSummary') );
	newOption('popupFixDabsSummary',           popupString('defaultpopupFixDabsSummary') );
	newOption('popupExtendedRevertSummary',    popupString('defaultpopupExtendedRevertSummary') );
	newOption('popupRevertSummary',            popupString('defaultpopupRevertSummary') );
	newOption('popupRevertToPreviousSummary',  popupString('defaultpopupRevertToPreviousSummary') );
	newOption('popupQueriedRevertSummary',            popupString('defaultpopupQueriedRevertSummary') );
	newOption('popupQueriedRevertToPreviousSummary',  popupString('defaultpopupQueriedRevertToPreviousSummary') );
	newOption('popupFixRedirsSummary',         popupString('defaultpopupFixRedirsSummary') );
	newOption('popupRedlinkSummary',           popupString('defaultpopupRedlinkSummary') );
	newOption('popupRmDabLinkSummary',         popupString('defaultpopupRmDabLinkSummary') );
//</NOLITE>
	// misc
	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
	newOption('popupNewWindows',     false);
	newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true});

	// regexps
	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('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template
	newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub');
	newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo');
}
// ENDFILE: options.js
// STARTFILE: strings.js
//<NOLITE>
//////////////////////////////////////////////////
// Translatable strings
//////////////////////////////////////////////////
//
// See instructions at
// https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation

pg.string = {
	/////////////////////////////////////
	// summary data, searching etc.
	/////////////////////////////////////
	'article': 'article',
	'category': 'category',
	'categories': 'categories',
	'image': 'image',
	'images': 'images',
	'stub': 'stub',
	'section stub': 'section stub',
	'Empty page': 'Empty page',
	'kB': 'kB',
	'bytes': 'bytes',
	'day': 'day',
	'days': 'days',
	'hour': 'hour',
	'hours': 'hours',
	'minute': 'minute',
	'minutes': 'minutes',
	'second': 'second',
	'seconds': 'seconds',
	'week': 'week',
	'weeks': 'weeks',
	'search': 'search',
	'SearchHint': 'Find English Wikipedia articles containing %s',
	'web': 'web',
	'global': 'global',
	'globalSearchHint': 'Search across Wikipedias in different languages for %s',
	'googleSearchHint': 'Google for %s',
	/////////////////////////////////////
	// article-related actions and info
	// (some actions also apply to user pages)
	/////////////////////////////////////
	'actions': 'actions',		 ///// view articles and view talk
	'popupsMenu': 'popups',
	'togglePreviewsHint': 'Toggle preview generation in popups on this page',
	'enable previews': 'enable previews',
	'disable previews': 'disable previews',
	'toggle previews': 'toggle previews',
	'show preview': 'show preview',
	'reset': 'reset',
	'more...': 'more...',
	'disable': 'disable popups',
	'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.',
	'historyfeedHint': 'RSS feed of recent changes to this page',
	'purgePopupsHint': 'Reset popups, clearing all cached popup data.',
	'PopupsHint': 'Reset popups, clearing all cached popup data.',
	'spacebar': 'space',
	'view': 'view',
	'view article': 'view article',
	'viewHint': 'Go to %s',
	'talk': 'talk',
	'talk page': 'talk page',
	'this&nbsp;revision': 'this&nbsp;revision',
	'revision %s of %s': 'revision %s of %s',
	'Revision %s of %s': 'Revision %s of %s',
	'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s',
	'Toggle image size': 'Click to toggle image size',
	'del': 'del',				 ///// delete, protect, move
	'delete': 'delete',
	'deleteHint': 'Delete %s',
	'undeleteShort': 'un',
	'UndeleteHint': 'Show the deletion history for %s',
	'protect': 'protect',
	'protectHint': 'Restrict editing rights to %s',
	'unprotectShort': 'un',
	'unprotectHint': 'Allow %s to be edited by anyone again',
	'send thanks': 'send thanks',
	'ThanksHint': 'Send a thank you notification to this user',
	'move': 'move',
	'move page': 'move page',
	'MovepageHint': 'Change the title of %s',
	'edit': 'edit',			   ///// edit articles and talk
	'edit article': 'edit article',
	'editHint': 'Change the content of %s',
	'edit talk': 'edit talk',
	'new': 'new',
	'new topic': 'new topic',
	'newSectionHint': 'Start a new section on %s',
	'null edit': 'null edit',
	'nullEditHint': 'Submit an edit to %s, making no changes ',
	'hist': 'hist',			   ///// history, diffs, editors, related
	'history': 'history',
	'historyHint': 'List the changes made to %s',
	'last': 'prev', // For labelling the previous revision in history pages; the key is "last" for backwards compatibility
	'lastEdit': 'lastEdit',
	'mark patrolled': 'mark patrolled',
	'markpatrolledHint': 'Mark this edit as patrolled',
	'Could not marked this edit as patrolled': 'Could not marked this edit as patrolled',
	'show last edit': 'most recent edit',
	'Show the last edit': 'Show the effects of the most recent change',
	'lastContrib': 'lastContrib',
	'last set of edits': 'latest edits',
	'lastContribHint': 'Show the net effect of changes made by the last editor',
	'cur': 'cur',
	'diffCur': 'diffCur',
	'Show changes since revision %s': 'Show changes since revision %s',
	'%s old': '%s old', // as in 4 weeks old
	'oldEdit': 'oldEdit',
	'purge': 'purge',
	'purgeHint': 'Demand a fresh copy of %s',
	'raw': 'source',
	'rawHint': 'Download the source of %s',
	'render': 'simple',
	'renderHint': 'Show a plain HTML version of %s',
	'Show the edit made to get revision': 'Show the edit made to get revision',
	'sinceMe': 'sinceMe',
	'changes since mine': 'diff my edit',
	'sinceMeHint': 'Show changes since my last edit',
	'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s',
	'eds': 'eds',
	'editors': 'editors',
	'editorListHint': 'List the users who have edited %s',
	'related': 'related',
	'relatedChanges': 'relatedChanges',
	'related changes': 'related changes',
	'RecentchangeslinkedHint': 'Show changes in articles related to %s',
	'editOld': 'editOld',		  ///// edit old version, or revert
	'rv': 'rv',
	'revert': 'revert',
	'revertHint': 'Revert to %s',
	'defaultpopupReviewedSummary': 'Accepted by reviewing the [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
	'defaultpopupRedlinkSummary': 'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
	'defaultpopupFixDabsSummary': 'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
	'defaultpopupFixRedirsSummary': 'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
	'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
	'defaultpopupRevertToPreviousSummary': 'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
	'defaultpopupRevertSummary': 'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
	'defaultpopupQueriedRevertToPreviousSummary': 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
	'defaultpopupQueriedRevertSummary': 'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
	'defaultpopupRmDabLinkSummary': 'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
	'Redirects': 'Redirects', // as in Redirects to ...
	' to ': ' to ',		   // as in Redirects to ...
	'Bypass redirect': 'Bypass redirect',
	'Fix this redirect': 'Fix this redirect',
	'disambig': 'disambig',		  ///// add or remove dab etc.
	'disambigHint': 'Disambiguate this link to [[%s]]',
	'Click to disambiguate this link to:': 'Click to disambiguate this link to:',
	'remove this link': 'remove this link',
	'remove all links to this page from this article': 'remove all links to this page from this article',
	'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article',
	'mainlink': 'mainlink',		  ///// links, watch, unwatch
	'wikiLink': 'wikiLink',
	'wikiLinks': 'wikiLinks',
	'links here': 'links here',
	'whatLinksHere': 'whatLinksHere',
	'what links here': 'what links here',
	'WhatlinkshereHint': 'List the pages that are hyperlinked to %s',
	'unwatchShort': 'un',
	'watchThingy': 'watch',  // called watchThingy because {}.watch is a function
	'watchHint': 'Add %s to my watchlist',
	'unwatchHint': 'Remove %s from my watchlist',
	'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits',
	'%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s',
	'rss': 'rss',
	/////////////////////////////////////
	// diff previews
	/////////////////////////////////////
	'Diff truncated for performance reasons': 'Diff truncated for performance reasons',
	'Old revision': 'Old revision',
	'New revision': 'New revision',
	'Something went wrong :-(': 'Something went wrong :-(',
	'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent',
	'Unknown date': 'Unknown date',
	/////////////////////////////////////
	// other special previews
	/////////////////////////////////////
	'Empty category': 'Empty category',
	'Category members (%s shown)': 'Category members (%s shown)',
	'No image links found': 'No image links found',
	'File links': 'File links',
	'No image found': 'No image found',
	'Image from Commons': 'Image from Commons',
	'Description page': 'Description page',
	'Alt text:': 'Alt text:',
	'revdel':'Hidden revision',
	/////////////////////////////////////
	// user-related actions and info
	/////////////////////////////////////
	'user': 'user',			   ///// user page, talk, email, space
	'user&nbsp;page': 'user&nbsp;page',
	'user talk': 'user talk',
	'edit user talk': 'edit user talk',
	'leave comment': 'leave comment',
	'email': 'email',
	'email user': 'email user',
	'EmailuserHint': 'Send an email to %s',
	'space': 'space', // short form for userSpace link
	'PrefixIndexHint': 'Show pages in the userspace of %s',
	'count': 'count',			 ///// contributions, log
	'edit counter': 'edit counter',
	'editCounterLinkHint': 'Count the contributions made by %s',
	'contribs': 'contribs',
	'contributions': 'contributions',
	'deletedContribs': 'deleted contributions',
	'DeletedcontributionsHint': 'List deleted edits made by %s',
	'ContributionsHint': 'List the contributions made by %s',
	'log': 'log',
	'user log': 'user log',
	'userLogHint': 'Show %s\'s user log',
	'arin': 'ARIN lookup',			 ///// ARIN lookup, block user or IP
	'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database',
	'unblockShort': 'un',
	'block': 'block',
	'block user': 'block user',
	'IpblocklistHint': 'Unblock %s',
	'BlockipHint': 'Prevent %s from editing',
	'block log': 'block log',
	'blockLogHint': 'Show the block log for %s',
	'protectLogHint': 'Show the protection log for %s',
	'pageLogHint': 'Show the page log for %s',
	'deleteLogHint': 'Show the deletion log for %s',
	'Invalid %s %s': 'The option %s is invalid: %s',
	'No backlinks found': 'No backlinks found',
	' and more': ' and more',
	'undo': 'undo',
	'undoHint': 'undo this edit',
	'Download preview data': 'Download preview data',
	'Invalid or IP user': 'Invalid or IP user',
	'Not a registered username': 'Not a registered username',
	'BLOCKED': 'BLOCKED',
	' 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'
};


function popupString(str) {
	if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; }
	if (pg.string[str]) { return pg.string[str]; }
	return str;
}


function tprintf(str,subs) {
	if (typeof subs != typeof []) { subs = [subs]; }
	return simplePrintf(popupString(str), subs);
}

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


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


// Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout.
( function () {
	var once = true;
	function dynamicContentHandler( $content ) {
		// Try to detect the hook fired on initial page load and disregard
		// it, we already hook to onload (possibly to different parts of
		// page - it's configurable) and running twice might be bad. Ugly…
		if ( $content.attr( 'id' ) == 'mw-content-text' ) {
			if ( once ) {
				once = false;
				return;
			}
		}
				
		function registerHooksForVisibleNavpops () {
			for (var i=0; pg.current.links && i<pg.current.links.length; ++i) {
				var navpop = pg.current.links[i].navpopup;
				if (!navpop || !navpop.isVisible()) { continue; }
				
				Navpopup.tracker.addHook(posCheckerHook(navpop));
			}
		}
		
		function doIt () {
			registerHooksForVisibleNavpops();
			$content.each( function () {
				this.ranSetupTooltipsAlready = false;
				setupTooltips( this );
			} );
		}

		setupPopups( doIt );
	}

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

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

});
// ENDFILE: run.js