MediaWiki:Gadget-popups.js:修订间差异

来自SS唯基
跳到导航 跳到搜索
(撤销873756069讨论)的版本8630)
无编辑摘要
 
(未显示同一用户的3个中间版本)
第1行: 第1行:
// STARTFILE: main.js
(function($, mw) {
// **********************************************************************
    if (!mw.util.escapeRegExp) {
// **                                                                  **
        mw.util.escapeRegExp = mw.RegExp.escape;
// **            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
};
 
function escapeRegExp ( str ){
 
    return str.replace( /([\\{}()|.?*+\-^$\[\]])/g, '\\$1' );
 
}
 
/* 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);
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)) ? 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) {
if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; }
return ! article.isTalkPage() && pg.re.disambig.test(data);
}
 
function stubCount(data, article) {
if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; }
var sectStub=0;
var realStub=0;
if (pg.re.stub.test(data)) {
var s=data.parenSplit(pg.re.stub);
for (var i=1; i<s.length; i=i+2) {
if (s[i]) { ++sectStub; }
else { ++realStub; }
}
}
return { real: realStub, sect: sectStub };
}
 
function isValidImageName(str){ // extend as needed...
return ( str.indexOf('{') == -1 );
}
 
function isInStrippableNamespace(article) {
// Does the namespace allow subpages
// Note, would be better if we had access to wgNamespacesWithSubpages
return ( article.namespaceId() !== 0 );
}
 
function isInMainNamespace(article) { return article.namespaceId() === 0; }
 
function anchorContainsImage(a) {
// iterate over children of anchor a
// see if any are images
if (a === null) { return false; }
var kids=a.childNodes;
for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } }
return false;
}
//</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.vector-menu h3:first a:first').prop('inNopopupSpan', true);
}
// ENDFILE: titles.js
// STARTFILE: getpage.js
//////////////////////////////////////////////////
// Wiki-specific downloading
//
 
// Schematic for a getWiki call
//
//            getPageWithCaching
// |
//   false |   true
// getPage<-[findPictureInCache]->-onComplete(a fake download)
//  \.
// (async)->addPageToCache(download)->-onComplete(download)
 
// check cache to see if page exists
 
function getPageWithCaching(url, onComplete, owner) {
log('getPageWithCaching, url='+url);
var i=findInPageCache(url);
var d;
if (i > -1) {
d=fakeDownload(url, owner.idNumber, onComplete,
pg.cache.pages[i].data, pg.cache.pages[i].lastModified,
owner);
} else {
d=getPage(url, onComplete, owner);
if (d && owner && owner.addDownload) {
owner.addDownload(d);
d.owner=owner;
}
}
}
 
function getPage(url, onComplete, owner) {
log('getPage');
var callback= function (d) { if (!d.aborted) {addPageToCache(d); onComplete(d);} };
return startDownload(url, owner.idNumber, callback);
}
 
function findInPageCache(url) {
for (var i=0; i<pg.cache.pages.length; ++i) {
if (url==pg.cache.pages[i].url) { return i; }
}
return -1;
}
 
function addPageToCache(download) {
log('addPageToCache '+download.url);
var page = {url: download.url, data: download.data, lastModified: download.lastModified};
return pg.cache.pages.push(page);
}
// ENDFILE: getpage.js
// STARTFILE: parensplit.js
//////////////////////////////////////////////////
// parenSplit
 
// String.prototype.parenSplit should do what ECMAscript says String.prototype.split does,
// interspersing paren matches (regex capturing groups) between the split elements.
// i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c']
 
if (String('abc'.split(/(b)/))!='a,b,c') {
// broken String.split, e.g. konq, IE < 10
String.prototype.parenSplit=function (re) {
re=nonGlobalRegex(re);
var s=this;
var m=re.exec(s);
var ret=[];
while (m && s) {
// without the following loop, we have
// 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/)
for(var i=0; i<m.length; ++i) {
if (typeof m[i]=='undefined') m[i]='';
}
ret.push(s.substring(0,m.index));
ret = ret.concat(m.slice(1));
s=s.substring(m.index + m[0].length);
m=re.exec(s);
}
ret.push(s);
return ret;
};
} else {
String.prototype.parenSplit=function (re) { return this.split(re); };
String.prototype.parenSplit.isNative=true;
}
 
function nonGlobalRegex(re) {
var s=re.toString();
var flags='';
for (var j=s.length; s.charAt(j) != '/'; --j) {
if (s.charAt(j) != 'g') { flags += s.charAt(j); }
}
var t=s.substring(1,j);
return RegExp(t,flags);
}
// ENDFILE: parensplit.js
// STARTFILE: tools.js
// IE madness with encoding
// ========================
//
// suppose throughout that the page is in utf8, like wikipedia
//
// if a is an anchor DOM element and a.href should consist of
//
// http://host.name.here/wiki/foo?bar=baz
//
// then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie))
// but IE gives bar=baz correctly as plain utf8
//
// ---------------------------------
//
// IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here.
//
// ---------------------------------
//
// summat else
 
// Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm
 
//<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 escapeRegExp(str);
}
 
String.prototype.entify=function() {
//var shy='&shy;';
return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;'/*+shy*/).split('"').join('&quot;');
};
 
// Array filter function
function removeNulls(val) { return val !== null; }
 
function joinPath(list) {
return list.filter(removeNulls).join('/');
}
 
 
function simplePrintf(str, subs) {
if (!str || !subs) { return str; }
var ret=[];
var s=str.parenSplit(/(%s|\$[0-9]+)/);
var i=0;
do {
ret.push(s.shift());
if ( !s.length ) { break; }
var cmd=s.shift();
if (cmd == '%s') {
if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); }
++i;
} else {
var j=parseInt( cmd.replace('$', ''), 10 ) - 1;
if ( j > -1 && j < subs.length ) { ret.push(subs[j]); } else { ret.push(cmd); }
}
} while (s.length > 0);
return ret.join('');
}
/* eslint-disable no-unused-vars */
function isString(x) { return (typeof x === 'string' || x instanceof String); }
function isNumber(x) { return (typeof x === 'number' || x instanceof Number); }
function isRegExp(x) { return x instanceof RegExp; }
function isArray (x) { return x instanceof Array; }
function isObject(x) { return x instanceof Object; }
function isFunction(x) {
return !isRegExp(x) && ($.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) {
if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) &&
Title.fromURL(location.href).namespaceId() != pg.nsSpecialId &&
navpop.article.talkPage() ) {
setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber);
}
}
 
function popupRedlinkHTML(article) {
return changeLinkTargetLink(
{ newTarget: null, text: popupString('remove this link').split(' ').join('&nbsp;'),
hint: popupString("remove all links to this page from this article"),
clickButton: getValueOf('popupRedlinkAutoClick'),
oldTarget: article.toString(),
summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])});
}
//</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
*/
Previewmaker.prototype.killTemplates = function () {
this.kill('{{', '}}', '{', '}', ' ');
};
/**
  @private
*/
Previewmaker.prototype.killTables = function () {
// tables are bad, too
// this can be slow, but it's an inprovement over a browser hang
// torture test: [[Comparison_of_Intel_Central_Processing_Units]]
this.kill('{|', /[|]}\s*/, '{|');
this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i);
// remove lines starting with a pipe for the hell of it (?)
this.data=this.data.replace(RegExp('^[|].*$', 'mg'), '');
};
/**
  @private
*/
Previewmaker.prototype.killImages = function () {
var forbiddenNamespaceAliases = [];
jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return;
forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments!
});
// images and categories are a nono
this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'),
  /\]\]\s*/, '[', ']');
};
/**
  @private
*/
Previewmaker.prototype.killHTML = function () {
// kill <ref ...>...</ref>
this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i);
 
// let's also delete entire lines starting with <. it's worth a try.
this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n');
 
// and those pesky html tags, but not <nowiki> or <blockquote>
var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/);
var len=splitted.length;
for (var i=1; i<len; i=i+2) {
switch (splitted[i]) {
case '<nowiki>':
case '</nowiki>':
case '<blockquote>':
case '</blockquote>':
break;
default:
splitted[i]='';
}
}
this.data=splitted.join('');
};
/**
  @private
*/
Previewmaker.prototype.killChunks = function() { // heuristics alert
// chunks of italic text? you crazy, man?
var italicChunkRegex=new RegExp
("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g');
// keep stuff separated, though, so stick in \n (fixes [[Union Jack]]?
this.data=this.data.replace(italicChunkRegex, '\n');
};
/**
  @private
*/
Previewmaker.prototype.mopup = function () {
// we simply *can't* be doing with horizontal rules right now
this.data=this.data.replace(RegExp('^-{4,}','mg'),'');
 
// no indented lines
this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), '');
 
// replace __TOC__, __NOTOC__ and whatever else there is
// this'll probably do
this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),'');
};
/**
  @private
*/
Previewmaker.prototype.firstBit = function () {
// dont't be givin' me no subsequent paragraphs, you hear me?
/// first we "normalize" section headings, removing whitespace after, adding before
var d=this.data;
 
if (getValueOf('popupPreviewCutHeadings')) {
this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 ');
/// then we want to get rid of paragraph breaks whose text ends badly
this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n');
 
this.data=this.data.replace(RegExp('^[\\s\\n]*'), '');
var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data);
if (stuff) { d = stuff[0]; }
if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; }
 
/// now put \n\n after sections so that bullets and numbered lists work
d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n');
}
 
 
// Split sentences. Superfluous sentences are RIGHT OUT.
// note: exactly 1 set of parens here needed to make the slice work
d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g'));
// leading space is bad, mmkay?
d[0]=d[0].replace(RegExp('^\\s*'), '');
 
var notSentenceEnds=RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i');
d = this.fixSentenceEnds(d, notSentenceEnds);
 
this.fullLength=d.join('').length;
var n=this.maxSentences;
var dd=this.firstSentences(d,n);
 
do {
dd=this.firstSentences(d,n); --n;
} while ( dd.length > this.maxCharacters && n !== 0 );
 
this.data = dd;
};
/**
  @private
*/
Previewmaker.prototype.fixSentenceEnds = function(strs, reg) {
// take an array of strings, strs
// join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg
 
for (var i=0; i<strs.length-2; ++i) {
if (reg.test(strs[i])) {
var a=[];
for (var j=0; j<strs.length; ++j) {
if (j<i)  a[j]=strs[j];
if (j==i)  a[i]=strs[i]+strs[i+1]+strs[i+2];
if (j>i+2) a[j-2]=strs[j];
}
return this.fixSentenceEnds(a,reg);
}
}
return strs;
};
/**
  @private
*/
Previewmaker.prototype.firstSentences = function(strs, howmany) {
var t=strs.slice(0, 2*howmany);
return t.join('');
};
/**
  @private
*/
Previewmaker.prototype.killBadWhitespace = function() {
// also cleans up isolated '''', eg [[Suntory Sungoliath]]
this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), '');
};
/**
  Runs the various methods to generate the preview.
  The preview is stored in the <code>html</html> field.
  @private
*/
Previewmaker.prototype.makePreview = function() {
if (this.owner.article.namespaceId()!=pg.nsTemplateId &&
this.owner.article.namespaceId()!=pg.nsImageId ) {
this.killComments();
this.killDivs();
this.killGalleries();
this.killBoxTemplates();
 
if (getValueOf('popupPreviewKillTemplates')) {
this.killTemplates();
} else {
this.killMultilineTemplates();
}
this.killTables();
this.killImages();
this.killHTML();
this.killChunks();
this.mopup();
 
this.firstBit();
this.killBadWhitespace();
}
else
{
this.killHTML();
}
this.html=wiki2html(this.data, this.baseUrl); // needs livepreview
this.fixHTML();
this.stripLongTemplates();
};
/**
  @private
*/
Previewmaker.prototype.esWiki2HtmlPart = function(data) {
  var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink
  reLinks.lastIndex = 0; //reset regex
 
  var match;
  var result = "";
  var postfixIndex = 0;
  while ((match = reLinks.exec(data))) //match all wikilinks
  {
//FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases.
result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) +
  '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>";
postfixIndex = reLinks.lastIndex;
  }
  //append the rest
  result += pg.escapeQuotesHTML(data.substring(postfixIndex));
 
  return result;
};
Previewmaker.prototype.editSummaryPreview=function() {
var reAes  = /\/\* *(.*?) *\*\//g; //match the first section marker
reAes.lastIndex = 0; //reset regex
var match;
match = reAes.exec(this.data);
if (match)
{
//we have a section link. Split it, process it, combine it.
var prefix = this.data.substring(0,match.index-1);
var section = match[1];
var postfix = this.data.substring(reAes.lastIndex);
var start = "<span class='autocomment'>";
var end = "</span>";
if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- ";
if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix);
 
var t=new Title().fromURL(this.baseUrl);
t.anchorFromUtf(section);
var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor);
return start + '<a href="'+sectionLink+'">&rarr;</a> '+pg.escapeQuotesHTML(section) + end;
}
//else there's no section link, htmlify the whole thing.
return this.esWiki2HtmlPart(this.data);
};
 
//<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|restrictions&bkip=' + usernameart;
} else {
url += 'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + usernameart + "&meta=globaluserinfo&guiprop=groups|unattached&guiuser="+ usernameart + "&uclimit=1&ucprop=timestamp&ucuser=" + usernameart;
}
htmlGenerator=APIuserInfoPreviewHTML;
break;
case 'contribs':
usernameart = encodeURIComponent( new Title( article ).userName() );
url += 'list=usercontribs&ucuser=' + usernameart +
'&uclimit=' + getValueOf('popupContribsPreviewLimit');
htmlGenerator=APIcontribsPreviewHTML;
break;
case 'imagepagepreview':
var trail='';
if (getValueOf('popupImageLinks')) { trail = '&list=imageusage&iutitle=' + art; }
url += 'titles=' + art + '&prop=revisions|imageinfo&rvprop=content' + trail;
htmlGenerator=APIimagepagePreviewHTML;
break;
case 'backlinks':
url += 'list=backlinks&bltitle=' + art;
htmlGenerator=APIbacklinksPreviewHTML;
break;
case 'revision':
if (article.oldid) {
url += 'revids=' + article.oldid;
} else {
url += 'titles=' + article.removeAnchor().urlString();
}
url += '&prop=revisions|pageprops|info|images|categories&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max';
htmlGenerator=APIrevisionPreviewHTML;
break;
}
pendingNavpopTask(navpop);
var callback=function(d){
log( "callback of API functions was hit" );
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 APIrevisionPreviewHTML(article, download) {
try{
var jsObj=getJsObj(download.data);
var page=anyChild(jsObj.query.pages);
if( page.missing ) {
// TODO we need to fix this proper later on
download.owner = null;
return;
}
var content = (
page &&
page.revisions &&
page.revisions[0].contentmodel === 'wikitext'
) ? page.revisions[0].content : null;
if( typeof content === 'string' )
{
download.data = content;
download.lastModified = new Date(page.revisions[0].timestamp);
}
} 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].contentmodel === 'wikitext'
) ? page.revisions[0].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].contentmodel === 'wikitext'
) ? page.revisions[0].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&titles=' + encart;
 
ret = ret +'<hr />' + popupString( 'Image from Commons') +
': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' +
popupString( 'Description page') + '</a>';
mw.loader.load( shared_url );
}
showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download);
return ret;
} catch (someError) {
return 'API imagepage preview failed :(';
}
}
 
function APIimagelinksPreviewHTML(article, download) {
try {
var jsobj=getJsObj(download.data);
var list=jsobj.query.imageusage;
if (list) {
var ret=[];
for (var i=0; i < list.length; i++) {
ret.push(list[i].title);
}
if (ret.length === 0) { return popupString('No image links found'); }
return '<h2>' + popupString('File links') + '</h2>' + linkList(ret);
} else {
return popupString('No image links found');
}
} catch(someError) {
return 'Image links preview generation failed :(';
}
}
 
function APIcategoryPreviewHTML(article, download) {
try{
var jsobj=getJsObj(download.data);
var list=jsobj.query.categorymembers;
var ret=[];
for (var p=0; p < list.length; p++) {
  ret.push(list[p].title);
}
if (ret.length === 0) { return popupString('Empty category'); }
ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret);
if (jsobj['continue'] && jsobj['continue'].cmcontinue) {
ret += popupString(' and more');
}
return ret;
} catch(someError) {
return 'Category preview failed :(';
}
}
 
function APIuserInfoPreviewHTML(article, download) {
var ret=[];
var queryobj = {};
try{
queryobj=getJsObj(download.data).query;
} catch(someError) { return 'Userinfo preview failed :('; }
 
var user=anyChild(queryobj.users);
if (user) {
var globaluserinfo=queryobj.globaluserinfo;
if (user.invalid === '') {
ret.push( popupString( 'Invalid user') );
} else if (user.missing === '') {
ret.push( popupString( 'Not a registered username') );
}
if( user.blockedby ) {
if( user.blockpartial ) {
ret.push('<b>' + popupString('Has blocks') + '</b>');
} else {
ret.push('<b>' + popupString('BLOCKED') + '</b>');
}
}
if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) {
var lockedSulAccountIsAttachedToThis = true;
for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) {
if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) {
lockedSulAccountIsAttachedToThis=false;
break;
}
}
if (lockedSulAccountIsAttachedToThis) {
if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>');
if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>');
}
}
if( getValueOf('popupShowGender') && user.gender ) {
switch( user.gender ) {
case "male": ret.push( popupString( "\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>' );
}
}
if( user.registration )
ret.push( pg.escapeQuotesHTML((user.editcount?user.editcount:'0') + popupString(' edits since: ') + (user.registration?dayFormat(getDateFromTimestamp(user.registration)):'')) );
}
 
if (queryobj.usercontribs && queryobj.usercontribs.length) {
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++) {
console.log(queryobj);
var rbstr = queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK';
rbstr = (!Array.isArray(queryobj.blocks[l].restrictions) ? 'Has ' + rbstr.toLowerCase() + 's' : rbstr + 'ED')
ret.push('<b>' + popupString(rbstr) + '</b>' );
}
}
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' ],
'bn':  [ R, 'পুনর্নির্দেশ'],
'cs':  [ R, 'PŘESMĚRUJ' ],
'cy':  [ r, 'ail-cyfeirio' ],
'de':  [ R, 'WEITERLEITUNG' ],
'el':  [ R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'],
'eo':  [ R, 'ALIDIREKTU', 'ALIDIREKTI' ],
'es':  [ R, 'REDIRECCIÓN' ],
'et':  [ r, 'suuna' ],
'ga':  [ r, 'athsheoladh' ],
'gl':  [ r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'],
'he':  [ R, 'הפניה' ],
'hu':  [ R, 'ÁTIRÁNYÍTÁS' ],
'is':  [ r, 'tilvísun', 'TILVÍSUN' ],
'it':  [ R, 'RINVIA', 'Rinvia'],
'ja':  [ R, '転送' ],
'mk':  [ r, 'пренасочување', 'види' ],
'nds': [ r, 'wiederleiden' ],
'nds-nl': [ R, 'DEURVERWIEZING', 'DUURVERWIEZING' ],
'nl':  [ R, 'DOORVERWIJZING' ],
'nn':  [ r, 'omdiriger' ],
'pl':  [ R, 'PATRZ', 'PRZEKIERUJ', 'TAM' ],
'pt':  [ R, 'redir' ],
'ru':  [ R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР' ],
'sk':  [ r, 'presmeruj' ],
'sr':  [ r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI' ],
'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(escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]'));
imageNamespaceVariants.push(escapeRegExp(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') ?
});
     }
     }
}
    //////////////////////////////////////////////////
 
    // Translatable strings
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
    // See instructions at
pg.wiki.botInterfacePath = mw.config.get('wgScript');
    // https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation
pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php";
    // 下有萌百的自订翻译和额外补正
// default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo
    window.popupStrings = new Proxy({
 
        /////////////////////////////////////
var titletail = pg.wiki.botInterfacePath + '?title=';
        // summary data, searching etc.
//var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']);
        /////////////////////////////////////
 
        article: wgULS("条目", "條目"),
// other sites may need to add code here to set titletail depending on how their urls work
        category: wgULS("个分类", "个分類"),
 
        categories: wgULS("个分类", "个分類"),
pg.wiki.titlebase  = protocol + '//' + pg.wiki.sitebase + titletail;
        image: wgULS("个文件", "个檔案"),
//pg.wiki.titlebase2  = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]);
        images
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' + '(&target=|/|/' + nsRe(pg.nsUserId)+':)(.*)') ;
pg.re.email     =RegExp('(title=|/)'  + sp + '(?:%3A|:)EmailUser' + '(&target=|/|/(?:' + nsRe(pg.nsUserId)+':)?)(.*)') ;
pg.re.backlinks =RegExp('(title=|/)'  + sp + '(?:%3A|:)WhatLinksHere' + '(&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();
 

2020年8月11日 (二) 06:46的最新版本

(function($, mw) {
    if (!mw.util.escapeRegExp) {
        mw.util.escapeRegExp = mw.RegExp.escape;
    }
    //////////////////////////////////////////////////
    // Translatable strings
    //////////////////////////////////////////////////
    //
    // See instructions at
    // https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation
    // 下有萌百的自订翻译和额外补正
    window.popupStrings = new Proxy({
        /////////////////////////////////////
        // summary data, searching etc.
        /////////////////////////////////////
        article: wgULS("条目", "條目"),
        category: wgULS("个分类", "个分類"),
        categories: wgULS("个分类", "个分類"),
        image: wgULS("个文件", "个檔案"),
        images: wgULS("个文件", "个檔案"),
        stub: wgULS("小作品", "小作品"),
        "section stub": wgULS("小章节", "小章節"),
        "Empty page": wgULS("空页面", "空頁面"),
        kB: wgULS("千字节<sub>(以1000为一进)</sub>", "千位元組<sub>(以1000為一進)</sub>"),
        bytes: wgULS("字节", "位元組"),
        day: wgULS("天", "天"),
        days: wgULS("天", "天"),
        hour: wgULS("小时", "小時"),
        hours: wgULS("小时", "小時"),
        minute: wgULS("分", "分"),
        minutes: wgULS("分", "分"),
        second: wgULS("秒", "秒"),
        seconds: wgULS("秒", "秒"),
        week: wgULS("周", "周"),
        weeks: wgULS("周", "周"),
        month: wgULS("月", "月"),
        months: wgULS("月", "月"),
        year: wgULS("年", "年"),
        years: wgULS("年", "年"),
        search: wgULS("搜索", "搜尋"),
        SearchHint: wgULS("搜索包含 %s 的页面", "搜尋包含 %s 的頁面"),
        web: "Google",
        global: wgULS("全域", "全域"),
        "more...": wgULS("更多……", "更多……"),
        /////////////////////////////////////
        // article-related actions and info
        // (some actions also apply to user pages)
        /////////////////////////////////////
        actions: wgULS("操作", "動作"),
        ///// view articles and view talk
        popupsMenu: wgULS("Popups", "Popups"),
        "disable previews": wgULS("禁用预览", "禁用預覽"),
        togglePreviewsHint: wgULS("切换本页 Popups 的预览开关", "切換本頁 Popups 的預覽開關"),
        "toggle previews": wgULS("切换预览开关", "切換預覽開關"),
        reset: wgULS("复位", "複位"),
        disable: wgULS("禁用 Popups", "禁用 Popups"),
        disablePopupsHint: wgULS("在本页禁用 Popups,刷新页面以重新启用。", "在本頁禁用 Popups,重新整理頁面以重新啟用。"),
        purgePopupsHint: wgULS("复位 Popups,清除所有缓存数据。", "複位 Popups,清除所有快取資料。"),
        PopupsHint: wgULS("复位 Popups,清除所有缓存数据。", "複位 Popups,清除所有快取資料。"),
        spacebar: wgULS("空格", "空格"),
        view: wgULS("查看", "檢視"),
        "view article": wgULS("查看条目", "檢視條目"),
        viewHint: wgULS("前往 %s", "前往 %s"),
        talk: wgULS("讨论", "討論"),
        "talk page": wgULS("讨论页", "討論頁"),
        "this&nbsp;revision": wgULS("此修订版本", "此修訂版本"),
        "revision %s of %s": wgULS("页面 $2 的修订版本 $1", "頁面 $2 的修訂版本 $1"),
        "Revision %s of %s": wgULS("页面 $2 的修订版本 $1", "頁面 $2 的修訂版本 $1"),
        "the revision prior to revision %s of %s": wgULS("页面 $2 的修订版本 $1 之前的修订版本", "頁面 $2 的修訂版本 $1 之前的修訂版本"),
        "Toggle image size": wgULS("点击切换图片大小", "點擊切換圖片大小"),
        del: wgULS("删除", "删除"),
        ///// delete, protect, move
        "delete": wgULS("删除", "删除"),
        deleteHint: wgULS("删除 %s", "删除 %s"),
        undeleteShort: wgULS("恢复", "恢復"),
        UndeleteHint: wgULS("恢复 %s", "恢復 %s"),
        protect: wgULS("保护", "保護"),
        protectHint: wgULS("保护 %s", "保護 %s"),
        unprotectShort: wgULS("解除", "解除"),
        unprotectHint: wgULS("解除对 %s 的保护", "解除對 %s 的保護"),
        move: wgULS("移动", "移動"),
        "move page": wgULS("移动页面", "移動頁面"),
        MovepageHint: wgULS("修改 %s 的标题", "修改 %s 的標題"),
        edit: wgULS("编辑", "編輯"),
        ///// edit articles and talk
        "edit article": wgULS("编辑条目", "編輯條目"),
        editHint: wgULS("修改 %s 的内容", "修改 %s 的內容"),
        "edit talk": wgULS("编辑讨论页", "編輯討論頁"),
        "new": wgULS("新", "新"),
        "new topic": wgULS("新主题", "新主题"),
        newSectionHint: wgULS("在 %s 增加新的评论主题", "在 %s 增加新的評論主題"),
        "null edit": wgULS("零编辑", "零編輯"),
        nullEditHint: wgULS("进行一次对 %s 的零编辑", "製造一次對 %s 的零編輯"),
        hist: wgULS("历史", "歷史"),
        ///// history, diffs, editors, related
        history: wgULS("历史", "歷史"),
        historyHint: wgULS("%s 的修订历史", "%s 的修訂歷史"),
        last: wgULS("最近", "最近"),
        lastEdit: wgULS("最近更改", "最近更改"),
        "show last edit": wgULS("最近一次更改", "最近一次更改"),
        "Show the last edit": wgULS("显示最近一次更改的差异", "顯示最近一次更改的差異"),
        lastContrib: wgULS("最近编辑", "最近編輯"),
        "last set of edits": wgULS("最近编辑", "最近編輯"),
        lastContribHint: wgULS("显示由最后一位编辑者造成的差异", "顯示由最後一位編輯者製造的差異"),
        cur: wgULS("当前", "當前"),
        diffCur: wgULS("与当前版本的差异", "與目前版本的差異"),
        "Show changes since revision %s": wgULS("显示自修订版本 %s 的差异", "顯示自修訂版本 %s 的差異"),
        "%s old": wgULS("%s 前的最后版本", "%s 前的最后版本"),
        // as in 4 weeks old
        oldEdit: wgULS("旧编辑", "舊編輯"),
        purge: wgULS("清除缓存", "清除快取"),
        purgeHint: wgULS("清除服务器中 %s 的缓存", "清除伺服器中 %s 的快取"),
        raw: wgULS("源码", "原始碼"),
        rawHint: wgULS("查看 %s 的源代码", "檢視 %s 的原始碼"),
        render: wgULS("仅正文", "僅正文"),
        renderHint: wgULS("显示 %s 的纯HTML解析(仅正文内容)", "顯示 %s 的純HTML解析(僅正文內容)"),
        "Show the edit made to get revision": wgULS("显示编辑以得到修订版本", "顯示編輯以得到修訂版本"),
        sinceMe: wgULS("自我", "自我"),
        "changes since mine": wgULS("自我修订的差异", "自我修訂的差異"),
        sinceMeHint: wgULS("显示自我上次修改以来的差异", "顯示自我上次修改以來的差異"),
        "Couldn't find an edit by %s\nin the last %s edits to\n%s": wgULS("在 $3 最近 $2 次编辑中找不到 $1 做出的修改", "在 $3 最近 $2 次編輯中找不到 $1 做出的修改"),
        eds: wgULS("编辑", "編輯"),
        editors: wgULS("编辑者", "編輯者"),
        editorListHint: wgULS("列出编辑过 %s 的用户", "列出編輯過 %s 的使用者"),
        related: wgULS("相关", "相關"),
        relatedChanges: wgULS("相关更改", "相關更改"),
        "related changes": wgULS("相关更改", "相關更改"),
        RecentchangeslinkedHint: wgULS("显示相关 %s 的修改", "顯示相關 %s 的修改"),
        editOld: wgULS("编辑旧版", "編輯舊版"),
        ///// edit old version, or revert
        rv: wgULS("回退", "回退"),
        revert: wgULS("回退", "回退"),
        revertHint: wgULS("回退到 %s", "回退到 %s"),
        undo: wgULS("撤销", "撤銷"),
        undoHint: wgULS("撤销这次编辑", "撤銷這次編輯"),
        defaultpopupRedlinkSummary: wgULS("取消到空页面[[%s]]的链接", "取消到空頁面[[%s]]的連結"),
        defaultpopupFixDabsSummary: wgULS("消歧义[[%s]]到[[%s]]", "消歧義[[%s]]到[[%s]]"),
        defaultpopupFixRedirsSummary: wgULS("忽略从[[%s]]到[[%s]]的重定向", "忽略從[[%s]]到[[%s]]的重定向"),
        defaultpopupExtendedRevertSummary: wgULS("回退到$2在$1时制作的修订版本$3", "回退到$2在$1時製作的修訂版本$3"),
        defaultpopupRevertToPreviousSummary: wgULS("回退到修订版本%s的上一个版本", "回退到修訂版本%s的上一個版本"),
        defaultpopupRevertSummary: wgULS("回退到修订版本%s", "回退到修訂版本%s"),
        defaultpopupQueriedRevertToPreviousSummary: wgULS("回退到修订版本$1的上一个版本,由$3在$2时制作", "回退到修訂版本$1的上一個版本,由$3在$2時製作"),
        defaultpopupQueriedRevertSummary: wgULS("回退到$3在$2时制作的修订版本$1", "回退到$3在$2時製作的修訂版本$1"),
        defaultpopupRmDabLinkSummary: wgULS("移除到消歧义页[[%s]]的链接", "移除到消歧義頁[[%s]]的連結"),
        Redirects: wgULS("重定向", "重定向"),
        // as in Redirects to ...
        // " to ": wgULS("到", "到"),
        // as in Redirects to ...
        "Bypass redirect": wgULS("忽略重定向", "忽略重定向"),
        "Fix this redirect": wgULS("修复重定向", "修復重定向"),
        disambig: wgULS("消歧义", "消歧義"),
        ///// add or remove dab etc.
        disambigHint: wgULS("消歧义这个链接到 [[%s]]", "消歧義這個連結到 [[%s]]"),
        "Click to disambiguate this link to:": wgULS("点击以消歧义这个链接到:", "點擊以消歧義這個連結到:"),
        "remove this link": wgULS("移除链接", "移除連結"),
        "remove all links to this page from this article": wgULS("移除此条目到这页的所有链接", "移除此條目到這頁的所有連結"),
        "remove all links to this disambig page from this article": wgULS("移除此条目到这消歧义的所有链接", "移除此條目到這消歧義的所有連結"),
        mainlink: wgULS("主链接", "主連結"),
        ///// links, watch, unwatch
        wikiLink: wgULS("个内部链接", "个內部連結"),
        wikiLinks: wgULS("个内部链接", "个內部連結"),
        "links here": wgULS("链入", "鏈入"),
        whatLinksHere: wgULS("链入页面", "鏈入頁面"),
        "what links here": wgULS("链入页面", "鏈入頁面"),
        WhatlinkshereHint: wgULS("显示链接到 %s 的页面", "顯示連結到 %s 的頁面"),
        unwatchShort: wgULS("取消", "取消"),
        watchThingy: wgULS("监视", "監視"),
        // called watchThingy because {}.watch is a function
        watchHint: wgULS("加入 %s 到我的监视列表", "加入 %s 到我的監視列表"),
        unwatchHint: wgULS("从我的监视列表移除 %s", "從我的監視列表移除 %s"),
        "Only found one editor: %s made %s edits": wgULS("仅找到一位编者:%s 制造了 %s 次编辑", "僅找到一位編者:%s 製造了 %s 次編輯"),
        "%s seems to be the last editor to the page %s": wgULS("%s 看上去是 %s 这页的最后一位编者", "%s 看上去是 %s 這頁的最後一位編者"),
        rss: wgULS("RSS", "RSS"),
        /////////////////////////////////////
        // diff previews
        /////////////////////////////////////
        "Diff truncated for performance reasons": wgULS("出于性能考虑,差异已被截断", "出於效能考慮,差異已被截斷"),
        "Old revision": wgULS("旧版本", "舊版本"),
        "New revision": wgULS("新版本", "新版本"),
        "Something went wrong :-(": wgULS("出问题了 :-(", "出問題了 :-("),
        "Empty revision, maybe non-existent": wgULS("空的修订,可能并不存在", "空的修訂,可能並不存在"),
        "Unknown date": wgULS("未知日期", "未知日期"),
        /////////////////////////////////////
        // other special previews
        /////////////////////////////////////
        "Empty category": wgULS("空的分类", "空的分類"),
        "Category members (%s shown)": wgULS("分类成员(%s 显示)", "分類成員(%s 顯示)"),
        "No image links found": wgULS("未找到文件链接", "未找到檔案連結"),
        "File links": wgULS("文件链接", "檔案連結"),
        "not commons": wgULS("维基共享中无此名称的文件。", "維基共享中無此名稱的檔案。"),
        "commons only": wgULS("此文件来自维基共享。", "此檔案來自維基共享。"),
        "No image found": wgULS("找不到文件", "找不到檔案"),
        "commons dupe": wgULS("维基共享中存在此文件的副本。", "維基共享中存在此檔案的副本。"),
        "commons conflict": wgULS("维基共享中存在此文件名称不同的副本。", "維基共享中存在此檔名稱不同的副本。"),
        /////////////////////////////////////
        // user-related actions and info
        /////////////////////////////////////
        user: wgULS("用户", "使用者"),
        ///// user page, talk, email, space
        "user&nbsp;page": wgULS("用户页", "使用者頁"),
        "user talk": wgULS("用户讨论", "使用者討論"),
        "edit user talk": wgULS("编辑用户讨论", "編輯使用者討論"),
        "leave comment": wgULS("留言", "留言"),
        email: wgULS("电邮", "電郵"),
        "email user": wgULS("电邮用户", "電郵使用者"),
        EmailuserHint: wgULS("给 %s 发送电子邮件", "給 %s 發送電子郵件"),
        space: wgULS("子页面", "子頁面"),
        // short form for userSpace link
        PrefixindexHint: wgULS("显示 %s 的用户页子页面", "顯示 %s 的用戶頁子頁面"),
        count: wgULS("统计", "統計"),
        ///// contributions, tree, log
        "edit counter": wgULS("编辑次数", "編輯次數"),
        katelinkHint: wgULS("%s 的编辑次数", "%s 的編輯次數"),
        contribs: wgULS("贡献", "貢獻"),
        contributions: wgULS("贡献", "貢獻"),
        deletedContribs: wgULS("已删除的贡献", "已刪除的貢獻"),
        ContributionsHint: wgULS("%s 的用户贡献", "%s 的使用者貢獻"),
        tree: wgULS("树", "樹"),
        contribsTreeHint: wgULS("根据名字空间查看 %s 的贡献", "根據名字空間檢視 %s 的貢獻"),
        log: wgULS("日志", "日誌"),
        "user log": wgULS("用户日志", "使用者日誌"),
        userLogHint: wgULS("显示 %s 的用户日志", "顯示 %s 的使用者日誌"),
        arin: wgULS("ARIN 查询", "ARIN 查詢"),
        ///// ARIN lookup, block user or IP
        "Look up %s in ARIN whois database": wgULS("在 ARIN Whois 数据库中查询 %s", "在 ARIN Whois 數據庫中查詢 %s"),
        unblockShort: wgULS("解除", "解除"),
        block: wgULS("封禁", "封禁"),
        "block user": wgULS("封禁用户", "封禁使用者"),
        IpblocklistHint: wgULS("解封 %s", "解封 %s"),
        BlockipHint: wgULS("封禁 %s", "封禁 %s"),
        "block log": wgULS("封禁日志", "封禁日誌"),
        blockLogHint: wgULS("显示 %s 的封禁日志", "顯示 %s 的封禁日誌"),
        protectLogHint: wgULS("显示 %s 的保护日志", "顯示 %s 的保護日誌"),
        pageLogHint: wgULS("显示 %s 的日志", "顯示 %s 的日誌"),
        deleteLogHint: wgULS("显示 %s 的删除日志", "顯示 %s 的刪除日誌"),
        "Invalid %s %s": wgULS("选项 %s 不可用:%s", "選項 %s 不可用:%s"),
        m: wgULS("小", "小"),
        /////////////////////////////////////
        // Autoediting
        /////////////////////////////////////
        "Enter a non-empty edit summary or press cancel to abort": wgULS("输入编辑摘要,或按取消中止操作", "輸入編輯摘要,或按取消中止操作"),
        "Failed to get revision information, please edit manually.\n\n": wgULS("获取修订版本信息失败,请手动修改。\n\n", "獲取修訂版本資訊失敗,請手動修改。\n\n"),
        "The %s button has been automatically clicked. Please wait for the next page to load.": wgULS("按钮 %s 已被自动点击,请等待下一个页面加载。", "按鈕 %s 已被自動點擊,請等待下一個頁面載入。"),
        "Could not find button %s. Please check the settings in your javascript file.": wgULS("找不到按钮 %s,请检查您 JavaScript 文件中的设置。", "找不到按鈕 %s,請檢查您 JavaScript 檔案中的設定。"),
        /////////////////////////////////////
        // Popups setup
        /////////////////////////////////////
        "Open full-size image": wgULS("查看全尺寸图像", "檢視全尺寸影像"),
        zxy: wgULS("zxy", "zxy"),
        /////////////////////////////////////
        // 以下内容由 [[User:AnnAngela]] 补正
        /////////////////////////////////////
        globalSearchHint: wgULS("在维基百科其他语言搜索“%s”", "在維基百科其他語言搜尋「%s」"),
        googleSearchHint: wgULS("在 Google 上搜索“%s”", "在 Google 上搜尋「%s」"),
        "enable previews": wgULS("启用预览", "啟用預覽"),
        "show preview": wgULS("禁用预览", "禁用預覽"),
        historyfeedHint: wgULS("该页面的近期更改 RSS feed", "該頁面的近期更改 RSS feed"),
        "send thanks": wgULS("发送感谢", "傳送感謝"),
        ThanksHint: wgULS("向该用户发送一封感谢消息", "向該使用者傳送一封感謝訊息"),
        "mark patrolled": wgULS("标记为已巡查", "標記為已巡查"),
        markpatrolledHint: wgULS("标记该编辑为已巡查", "標記該編輯為已巡查"),
        "Could not marked this edit as patrolled": wgULS("无法标记该编辑为已巡查", "無法標記該編輯為已巡查"),
        defaultpopupReviewedSummary: wgULS("标记从版本%s到%s间的编辑为已巡查", "標記從版本%s到%s間的編輯為已巡查"),
        "Image from Commons": wgULS("来自维基共享的图片", "來自維基共用的圖片"),
        "Description page": wgULS("图片描述页", "圖片描述頁"),
        "Alt text:": wgULS("替换文本(Alt):", "替換文字(Alt):"),
        revdel: wgULS("历史版本被隐藏", "歷史版本被隱藏"),
        editCounterLinkHint: wgULS("用户%s的编辑次数", "使用者%s的編輯次數"),
        DeletedcontributionsHint: wgULS("用户%s的被删除编辑次数", "使用者%s的被刪除編輯次數"),
        "No backlinks found": wgULS("找不到链入页面", "找不到鏈入頁面"),
        " and more": wgULS("以及其他页面", "以及其他頁面"),
        "Download preview data": wgULS("下载预览数据", "下載預覽資料"),
        "Invalid or IP user": wgULS("错误的用户名或IP用户", "錯誤的使用者名稱或IP使用者"),
        "Not a registered username": wgULS("非已注册的用户", "非已註冊的使用者"),
        BLOCKED: wgULS("被封禁", "被封鎖"),
        " edits since: ": wgULS("次编辑,注册日期为", "次編輯,註冊日期為"),
        "last edit on ": wgULS("最后一次编辑于", "最後一次編輯於"),
        EmailUserHint: wgULS("给 %s 发送电子邮件", "給 %s 發送電子郵件"),
        RANGEBLOCKED: wgULS("IP段被封禁", "IP段被封鎖"),
        "IP user": wgULS("IP用户", "IP使用者"),
        "♀": "♀",
        "♂": "♂",
        HIDDEN: wgULS("全域隐藏", "全域隱藏"),
        LOCKED: wgULS("全域锁定", "全域鎖定"),
        "Invalid user": wgULS("非法用户名", "非法使用者名稱"),
        diff: wgULS("差异", "差異"),
        " to ": wgULS("至", "至"),
        autoedit_version: "np20140416",
        PrefixIndexHint: wgULS("显示用户%s的子页面", "顯示使用者%s的子頁面"),
        nullEditSummary: wgULS("进行一次零编辑", "進行一次零編輯"),
        "group-bot": wgULS("机器人", "機器人"),
        "group-sysop": wgULS("管理员", "管理員"),
        "group-bureaucrat": wgULS("行政员", "行政員"),
        "group-suppress": wgULS("Flow监督员", "監督員"),
        "group-patroller": wgULS("巡查姬", "巡查姬"),
        "group-goodeditor": wgULS("优质编辑者", "优质编辑者"),
        "group-developer": "developer",
        "group-widgeteditor": wgULS("小部件编辑者", "Widget 編輯者"),
        "group-VIP": "VIP",
        "group-删除执行员": wgULS("删除执行员", "删除执行员"),
        "group-checkuser": wgULS("用户查核员", "使用者查核員"),
        "group-steward": "steward",
        "group-user": wgULS("用户", "使用者"),
        "group-no-autoconfirmed": wgULS("非自动确认用户", "非自動確認的使用者"),
        separator: "、",
        comma: ","
    }, {
        get: function(target, name) {
            return name in target ? target[name] : name.startsWith("group-") ? name.substring(6) : undefined;
        }
    });
    mw.loader.load('//en.wikipedia.org/w/index.php?title=MediaWiki:Gadget-popups.js&action=raw&ctype=text/javascript');
})(jQuery, mediaWiki);