MediaWiki:Gadget-popups.js/sandbox
// STARTFILE: main.js
// **********************************************************************
// ** **
// ** changes to this file affect many users. **
// ** please discuss on the talk page before editing **
// ** **
// **********************************************************************
// ** **
// ** if you do edit this file, be sure that your editor recognizes it **
// ** as utf8, or the weird and wonderful characters in the namespaces **
// ** below will be completely broken. You can check with the show **
// ** changes button before submitting the edit. **
// ** test: مدیا מיוחד Мэдыя **
// ** **
// **********************************************************************
/* eslint-env browser */
/* global $, jQuery, mw, window */
// Fix later
/* global log, errlog, popupStrings, wikEdUseWikEd, WikEdUpdateFrame */
/* eslint no-mixed-spaces-and-tabs: 0, no-empty: 0 */
$(function() {
//////////////////////////////////////////////////
// Globals
//
// Trying to shove as many of these as possible into the pg (popup globals) object
var pg = {
api: {}, // MediaWiki API requests
re: {}, // regexps
ns: {}, // namespaces
string: {}, // translatable strings
wiki: {}, // local site info
user: {}, // current user info
misc: {}, // YUCK PHOOEY
option: {}, // options, see newOption etc
optionDefault: {}, // default option values
flag: {}, // misc flags
cache: {}, // page and image cache
structures: {}, // navlink structures
timer: {}, // all sorts of timers (too damn many)
counter: {}, // .. and all sorts of counters
current: {}, // state info
fn: {}, // functions
endoflist: null
};
/* Bail if the gadget/script is being loaded twice */
/* An element with id "pg" would add a window.pg property, ignore such property */
if (window.pg && !(window.pg instanceof HTMLElement)) {
return;
}
/* Export to global context */
window.pg = pg;
/// Local Variables: ///
/// mode:c ///
/// End: ///
// ENDFILE: main.js
// STARTFILE: actions.js
function setupTooltips(container, remove, force, popData) {
log('setupTooltips, container=' + container + ', remove=' + remove);
if (!container) {
//
// the main initial call
if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) {
document.editform.wpTextbox1.onmouseup = doSelectionPopup;
}
//
// article/content is a structure-dependent thing
container = defaultPopupsContainer();
}
if (!remove && !force && container.ranSetupTooltipsAlready) {
return;
}
container.ranSetupTooltipsAlready = !remove;
var anchors;
anchors = container.getElementsByTagName('A');
setupTooltipsLoop(anchors, 0, 250, 100, remove, popData);
}
function defaultPopupsContainer() {
if (getValueOf('popupOnlyArticleLinks')) {
return document.getElementById('mw_content') ||
document.getElementById('content') ||
document.getElementById('article') || document;
}
return document;
}
function setupTooltipsLoop(anchors, begin, howmany, sleep, remove, popData) {
log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments));
var finish = begin + howmany;
var loopend = Math.min(finish, anchors.length);
var j = loopend - begin;
log('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin +
', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove);
var doTooltip = remove ? removeTooltip : addTooltip;
// try a faster (?) loop construct
if (j > 0) {
do {
var a = anchors[loopend - j];
if (typeof a === 'undefined' || !a || !a.href) {
log('got null anchor at index ' + loopend - j);
continue;
}
doTooltip(a, popData);
} while (--j);
}
if (finish < anchors.length) {
setTimeout(function() {
setupTooltipsLoop(anchors, finish, howmany, sleep, remove, popData);
},
sleep);
} else {
if (!remove && !getValueOf('popupTocLinks')) {
rmTocTooltips();
}
pg.flag.finishedLoading = true;
}
}
// eliminate popups from the TOC
// This also kills any onclick stuff that used to be going on in the toc
function rmTocTooltips() {
var toc = document.getElementById('toc');
if (toc) {
var tocLinks = toc.getElementsByTagName('A');
var tocLen = tocLinks.length;
for (var j = 0; j < tocLen; ++j) {
removeTooltip(tocLinks[j], true);
}
}
}
function addTooltip(a, popData) {
if (!isPopupLink(a)) {
return;
}
a.onmouseover = mouseOverWikiLink;
a.onmouseout = mouseOutWikiLink;
a.onmousedown = killPopup;
a.hasPopup = true;
a.popData = popData;
}
function removeTooltip(a) {
if (!a.hasPopup) {
return;
}
a.onmouseover = null;
a.onmouseout = null;
if (a.originalTitle) {
a.title = a.originalTitle;
}
a.hasPopup = false;
}
function removeTitle(a) {
if (!a.originalTitle) {
a.originalTitle = a.title;
}
a.title = '';
}
function restoreTitle(a) {
if (a.title || !a.originalTitle) {
return;
}
a.title = a.originalTitle;
}
function registerHooks(np) {
var popupMaxWidth = getValueOf('popupMaxWidth');
if (typeof popupMaxWidth === 'number') {
var setMaxWidth = function() {
np.mainDiv.style.maxWidth = popupMaxWidth + 'px';
np.maxWidth = popupMaxWidth;
};
np.addHook(setMaxWidth, 'unhide', 'before');
}
//
np.addHook(addPopupShortcuts, 'unhide', 'after');
np.addHook(rmPopupShortcuts, 'hide', 'before');
//
}
function removeModifierKeyHandler(a) {
//remove listeners for modifier key if any that were added in mouseOverWikiLink
document.removeEventListener('keydown', a.modifierKeyHandler, false);
document.removeEventListener('keyup', a.modifierKeyHandler, false);
}
function mouseOverWikiLink(evt) {
if (!evt && window.event) {
evt = window.event;
}
// if the modifier is needed, listen for it,
// we will remove the listener when we mouseout of this link or kill popup.
if (getValueOf('popupModifier')) {
// if popupModifierAction = enable, we should popup when the modifier is pressed
// if popupModifierAction = disable, we should popup unless the modifier is pressed
var action = getValueOf('popupModifierAction');
var key = action == 'disable' ? 'keyup' : 'keydown';
var a = this;
a.modifierKeyHandler = function(evt) {
mouseOverWikiLink2(a, evt);
};
document.addEventListener(key, a.modifierKeyHandler, false);
}
return mouseOverWikiLink2(this, evt);
}
/**
* Gets the references list item that the provided footnote link targets. This
* is typically a li element within the ol.references element inside the reflist.
* @param {Element} a - A footnote link.
* @returns {Element|boolean} The targeted element, or false if one can't be found.
*/
function footnoteTarget(a) {
var aTitle = Title.fromAnchor(a);
// We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly
var anch = aTitle.anchor;
if (!/^(cite_note-|_note-|endnote)/.test(anch)) {
return false;
}
var lTitle = Title.fromURL(location.href);
if (lTitle.toString(true) !== aTitle.toString(true)) {
return false;
}
var el = document.getElementById(anch);
while (el && typeof el.nodeName === 'string') {
var nt = el.nodeName.toLowerCase();
if (nt === 'li') {
return el;
} else if (nt === 'body') {
return false;
} else if (el.parentNode) {
el = el.parentNode;
} else {
return false;
}
}
return false;
}
function footnotePreview(x, navpop) {
setPopupHTML('
' + x.innerHTML, 'popupPreview', navpop.idNumber);
}
function modifierPressed(evt) {
var mod = getValueOf('popupModifier');
if (!mod) {
return false;
}
if (!evt && window.event) {
evt = window.event;
}
return (evt && mod && evt[mod.toLowerCase() + 'Key']);
}
// Checks if the correct modifier pressed/unpressed if needed
function isCorrectModifier(a, evt) {
if (!getValueOf('popupModifier')) {
return true;
}
// if popupModifierAction = enable, we should popup when the modifier is pressed
// if popupModifierAction = disable, we should popup unless the modifier is pressed
var action = getValueOf('popupModifierAction');
return (action == 'enable' && modifierPressed(evt) ||
action == 'disable' && !modifierPressed(evt));
}
function mouseOverWikiLink2(a, evt) {
if (!isCorrectModifier(a, evt)) {
return;
}
if (getValueOf('removeTitles')) {
removeTitle(a);
}
if (a == pg.current.link && a.navpopup && a.navpopup.isVisible()) {
return;
}
pg.current.link = a;
if (getValueOf('simplePopups') && !pg.option.popupStructure) {
// reset *default value* of popupStructure
setDefault('popupStructure', 'original');
}
var article = (new Title()).fromAnchor(a);
// set global variable (ugh) to hold article (wikipage)
pg.current.article = article;
if (!a.navpopup) {
a.navpopup = newNavpopup(a, article);
pg.current.linksHash[a.href] = a.navpopup;
pg.current.links.push(a);
}
if (a.navpopup.pending === null || a.navpopup.pending !== 0) {
// either fresh popups or those with unfinshed business are redone from scratch
simplePopupContent(a, article);
}
a.navpopup.showSoonIfStable(a.navpopup.delay);
clearInterval(pg.timer.checkPopupPosition);
pg.timer.checkPopupPosition = setInterval(checkPopupPosition, 600);
if (getValueOf('simplePopups')) {
if (getValueOf('popupPreviewButton') && !a.simpleNoMore) {
var d = document.createElement('div');
d.className = 'popupPreviewButtonDiv';
var s = document.createElement('span');
d.appendChild(s);
s.className = 'popupPreviewButton';
s['on' + getValueOf('popupPreviewButtonEvent')] = function() {
a.simpleNoMore = true;
d.style.display = "none";
nonsimplePopupContent(a, article);
};
s.innerHTML = popupString('show preview');
setPopupHTML(d, 'popupPreview', a.navpopup.idNumber);
}
}
if (a.navpopup.pending !== 0) {
nonsimplePopupContent(a, article);
}
}
// simplePopupContent: the content that do not require additional download
// (it is shown even when simplePopups is true)
function simplePopupContent(a, article) {
/* FIXME hack */
a.navpopup.hasPopupMenu = false;
a.navpopup.setInnerHTML(popupHTML(a));
fillEmptySpans({
navpopup: a.navpopup
});
if (getValueOf('popupDraggable')) {
var dragHandle = getValueOf('popupDragHandle') || null;
if (dragHandle && dragHandle != 'all') {
dragHandle += a.navpopup.idNumber;
}
setTimeout(function() {
a.navpopup.makeDraggable(dragHandle);
}, 150);
}
//
if (getValueOf('popupRedlinkRemoval') && a.className == 'new') {
setPopupHTML('
' + popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber);
}
//
}
function debugData(navpopup) {
if (getValueOf('popupDebugging') && navpopup.idNumber) {
setPopupHTML('idNumber=' + navpopup.idNumber + ', pending=' + navpopup.pending,
'popupError', navpopup.idNumber);
}
}
function newNavpopup(a, article) {
var navpopup = new Navpopup();
navpopup.fuzz = 5;
navpopup.delay = getValueOf('popupDelay') * 1000;
// increment global counter now
navpopup.idNumber = ++pg.idNumber;
navpopup.parentAnchor = a;
navpopup.parentPopup = (a.popData && a.popData.owner);
navpopup.article = article;
registerHooks(navpopup);
return navpopup;
}
// Should we show nonsimple context?
// If simplePopups is set to true, then we do not show nonsimple context,
// but if a bottom "show preview" was clicked we do show nonsimple context
function shouldShowNonSimple(a) {
return !getValueOf('simplePopups') || a.simpleNoMore;
}
// Should we show nonsimple context govern by the option (e.g. popupUserInfo)?
// If the user explicitly asked for nonsimple context by setting the option to true,
// then we show it even in nonsimple mode.
function shouldShow(a, option) {
if (shouldShowNonSimple(a)) {
return getValueOf(option);
} else {
return (typeof window[option] != 'undefined') && window[option];
}
}
function nonsimplePopupContent(a, article) {
var diff = null,
history = null;
var params = parseParams(a.href);
var oldid = (typeof params.oldid == 'undefined' ? null : params.oldid);
//
if (shouldShow(a, 'popupPreviewDiffs')) {
diff = params.diff;
}
if (shouldShow(a, 'popupPreviewHistory')) {
history = (params.action == 'history');
}
//
a.navpopup.pending = 0;
var referenceElement = footnoteTarget(a);
if (referenceElement) {
footnotePreview(referenceElement, a.navpopup);
//
} else if (diff || diff === 0) {
loadDiff(article, oldid, diff, a.navpopup);
} else if (history) {
loadAPIPreview('history', article, a.navpopup);
} else if (shouldShowNonSimple(a) && pg.re.contribs.test(a.href)) {
loadAPIPreview('contribs', article, a.navpopup);
} else if (shouldShowNonSimple(a) && pg.re.backlinks.test(a.href)) {
loadAPIPreview('backlinks', article, a.navpopup);
} else if ( // FIXME should be able to get all preview combinations with options
article.namespaceId() == pg.nsImageId &&
(shouldShow(a, 'imagePopupsForImages') || !anchorContainsImage(a))
) {
loadAPIPreview('imagepagepreview', article, a.navpopup);
loadImage(article, a.navpopup);
//
} else {
if (article.namespaceId() == pg.nsCategoryId &&
shouldShow(a, 'popupCategoryMembers')) {
loadAPIPreview('category', article, a.navpopup);
} else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) &&
shouldShow(a, 'popupUserInfo')) {
loadAPIPreview('userinfo', article, a.navpopup);
}
if (shouldShowNonSimple(a)) startArticlePreview(article, oldid, a.navpopup);
}
}
function pendingNavpopTask(navpop) {
if (navpop && navpop.pending === null) {
navpop.pending = 0;
}
++navpop.pending;
debugData(navpop);
}
function completedNavpopTask(navpop) {
if (navpop && navpop.pending) {
--navpop.pending;
}
debugData(navpop);
}
function startArticlePreview(article, oldid, navpop) {
navpop.redir = 0;
loadPreview(article, oldid, navpop);
}
function loadPreview(article, oldid, navpop) {
if (!navpop.redir) {
navpop.originalArticle = article;
}
article.oldid = oldid;
loadAPIPreview('revision', article, navpop);
}
function loadPreviewFromRedir(redirMatch, navpop) {
// redirMatch is a regex match
var target = new Title().fromWikiText(redirMatch[2]);
// overwrite (or add) anchor from original target
// mediawiki does overwrite; eg User:Lupin/foo3#Done
if (navpop.article.anchor) {
target.anchor = navpop.article.anchor;
}
navpop.redir++;
navpop.redirTarget = target;
//
var warnRedir = redirLink(target, navpop.article);
setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber);
//
navpop.article = target;
fillEmptySpans({
redir: true,
redirTarget: target,
navpopup: navpop
});
return loadPreview(target, null, navpop);
}
function insertPreview(download) {
if (!download.owner) {
return;
}
var redirMatch = pg.re.redirect.exec(download.data);
if (download.owner.redir === 0 && redirMatch) {
loadPreviewFromRedir(redirMatch, download.owner);
return;
}
if (download.owner.visible || !getValueOf('popupLazyPreviews')) {
insertPreviewNow(download);
} else {
var id = (download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK';
download.owner.addHook(function() {
insertPreviewNow(download);
return true;
},
'unhide', 'after', id);
}
}
function insertPreviewNow(download) {
if (!download.owner) {
return;
}
var wikiText = download.data;
var navpop = download.owner;
var art = navpop.redirTarget || navpop.originalArticle;
//
makeFixDabs(wikiText, navpop);
if (getValueOf('popupSummaryData')) {
getPageInfo(wikiText, download);
setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber);
}
var imagePage = '';
if (art.namespaceId() == pg.nsImageId) {
imagePage = art.toString();
} else {
imagePage = getValidImageFromWikiText(wikiText);
}
if (imagePage) {
loadImage(Title.fromWikiText(imagePage), navpop);
}
//
if (getValueOf('popupPreviews')) {
insertArticlePreview(download, art, navpop);
}
}
function insertArticlePreview(download, art, navpop) {
if (download && typeof download.data == typeof '') {
if (art.namespaceId() == pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) {
// FIXME compare/consolidate with diff escaping code for wikitext
var h = '
' + download.data.entify().split('\\n').join('
\\n') + '';
setPopupHTML(h, 'popupPreview', navpop.idNumber);
} else {
var p = prepPreviewmaker(download.data, art, navpop);
p.showPreview();
}
}
}
function prepPreviewmaker(data, article, navpop) {
// deal with tricksy anchors
var d = anchorize(data, article.anchorString());
var urlBase = joinPath([pg.wiki.articlebase, article.urlString()]);
var p = new Previewmaker(d, urlBase, navpop);
return p;
}
// Try to imitate the way mediawiki generates HTML anchors from section titles
function anchorize(d, anch) {
if (!anch) {
return d;
}
var anchRe = RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*' + getValueOf('popupAnchorRegexp') + '\\s*(?:\\|[^|}]*)*?\\s*' + literalizeRegex(anch) + '\\s*(?:\\|[^}]*)?}})');
var match = d.match(anchRe);
if (match && match.length > 0 && match[0]) {
return d.substring(d.indexOf(match[0]));
}
// now try to deal with == foo baz boom == -> #foo_baz_boom
var lines = d.split('\n');
for (var i = 0; i < lines.length; ++i) {
lines[i] = lines[i].replace(RegExp('\\*?[|])?(.*?)[\\]]{2}', 'g'), '$2')
.replace(/'([^'])/g, '$1').replace(RegExp("([^'])", 'g'), '$1');
if (lines[i].match(anchRe)) {
return d.split('\n').slice(i).join('\n').replace(RegExp('^[^=]*'), '');
}
}
return d;
}
function killPopup() {
removeModifierKeyHandler(this);
if (getValueOf('popupShortcutKeys')) {
rmPopupShortcuts();
}
if (!pg) {
return;
}
if (pg.current.link && pg.current.link.navpopup) {
pg.current.link.navpopup.banish();
}
pg.current.link = null;
abortAllDownloads();
if (pg.timer.checkPopupPosition) {
clearInterval(pg.timer.checkPopupPosition);
pg.timer.checkPopupPosition = null;
}
return true; // preserve default action
}
// ENDFILE: actions.js
// STARTFILE: domdrag.js
/**
@fileoverview
The {@link Drag} object, which enables objects to be dragged around.
*************************************************
dom-drag.js
09.25.2001
www.youngpup.net
**************************************************
10.28.2001 - fixed minor bug where events
sometimes fired off the handle, not the root.
*************************************************
Pared down, some hooks added by User:Lupin
Copyright Aaron Boodman.
Saying stupid things daily since March 2001.
*/
/**
Creates a new Drag object. This is used to make various DOM elements draggable.
@constructor
*/
function Drag() {
/**
Condition to determine whether or not to drag. This function should take one parameter, an Event.
To disable this, set it to null
.
@type Function
*/
this.startCondition = null;
/**
Hook to be run when the drag finishes. This is passed the final coordinates of
the dragged object (two integers, x and y). To disables this, set it to null
.
@type Function
*/
this.endHook = null;
}
/**
Gets an event in a cross-browser manner.
@param {Event} e
@private
*/
Drag.prototype.fixE = function(e) {
if (typeof e == 'undefined') {
e = window.event;
}
if (typeof e.layerX == 'undefined') {
e.layerX = e.offsetX;
}
if (typeof e.layerY == 'undefined') {
e.layerY = e.offsetY;
}
return e;
};
/**
Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by.
@param {DOMElement} o The "handle" by which oRoot
is dragged.
@param {DOMElement} oRoot The object which moves when o
is dragged, or o
if omitted.
*/
Drag.prototype.init = function(o, oRoot) {
var dragObj = this;
this.obj = o;
o.onmousedown = function(e) {
dragObj.start.apply(dragObj, [e]);
};
o.dragging = false;
o.popups_draggable = true;
o.hmode = true;
o.vmode = true;
o.root = oRoot ? oRoot : o;
if (isNaN(parseInt(o.root.style.left, 10))) {
o.root.style.left = "0px";
}
if (isNaN(parseInt(o.root.style.top, 10))) {
o.root.style.top = "0px";
}
o.root.onthisStart = function() {};
o.root.onthisEnd = function() {};
o.root.onthis = function() {};
};
/**
Starts the drag.
@private
@param {Event} e
*/
Drag.prototype.start = function(e) {
var o = this.obj; // = this;
e = this.fixE(e);
if (this.startCondition && !this.startCondition(e)) {
return;
}
var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10);
var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10);
o.root.onthisStart(x, y);
o.lastMouseX = e.clientX;
o.lastMouseY = e.clientY;
var dragObj = this;
o.onmousemoveDefault = document.onmousemove;
o.dragging = true;
document.onmousemove = function(e) {
dragObj.drag.apply(dragObj, [e]);
};
document.onmouseup = function(e) {
dragObj.end.apply(dragObj, [e]);
};
return false;
};
/**
Does the drag.
@param {Event} e
@private
*/
Drag.prototype.drag = function(e) {
e = this.fixE(e);
var o = this.obj;
var ey = e.clientY;
var ex = e.clientX;
var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10);
var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10);
var nx, ny;
nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));
this.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
this.obj.lastMouseX = ex;
this.obj.lastMouseY = ey;
this.obj.root.onthis(nx, ny);
return false;
};
/**
Ends the drag.
@private
*/
Drag.prototype.end = function() {
document.onmousemove = this.obj.onmousemoveDefault;
document.onmouseup = null;
this.obj.dragging = false;
if (this.endHook) {
this.endHook(parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10),
parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10));
}
};
// ENDFILE: domdrag.js
// STARTFILE: structures.js
//
pg.structures.original = {};
pg.structures.original.popupLayout = function() {
return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle',
'popupUserData', 'popupData', 'popupOtherLinks',
'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks',
'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'
],
'popupMiscTools', ['popupRedlink'],
'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'
];
};
pg.structures.original.popupRedirSpans = function() {
return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks',
'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'
];
};
pg.structures.original.popupTitle = function(x) {
log('defaultstructure.popupTitle');
if (!getValueOf('popupNavLinks')) {
return navlinkStringToHTML('<
}
return '';
};
pg.structures.original.popupTopLinks = function(x) {
log('defaultstructure.popupTopLinks');
if (getValueOf('popupNavLinks')) {
return navLinksHTML(x.article, x.hint, x.params);
}
return '';
};
pg.structures.original.popupImage = function(x) {
log('original.popupImage, x.article=' + x.article + ', x.navpop.idNumber=' + x.navpop.idNumber);
return imageHTML(x.article, x.navpop.idNumber);
};
pg.structures.original.popupRedirTitle = pg.structures.original.popupTitle;
pg.structures.original.popupRedirTopLinks = pg.structures.original.popupTopLinks;
function copyStructure(oldStructure, newStructure) {
pg.structures[newStructure] = {};
for (var prop in pg.structures[oldStructure]) {
pg.structures[newStructure][prop] = pg.structures[oldStructure][prop];
}
}
copyStructure('original', 'nostalgia');
pg.structures.nostalgia.popupTopLinks = function(x) {
var str = '';
str += '<
// user links
// contribs - log - count - email - block
// count only if applicable; block only if popupAdminLinks
str += 'if(user){
<
str += 'if(wikimedia){*<
str += 'if(ipuser){}else{*<
// editing links
// talkpage -> edit|new - history - un|watch - article|edit
// other page -> edit - history - un|watch - talk|edit|new
var editstr = '<
var editOldidStr = 'if(oldid){<
editstr + '}';
var historystr = '<
var watchstr = '<
str += '
if(talk){' +
editOldidStr + '|<
'<
'}else{' + // not a talk page
editOldidStr + '*' + historystr + '*' + watchstr + '*' +
'<
// misc links
str += '
<
str += 'if(admin){
}else{*}<
// admin links
str += 'if(admin){*<
'<
return navlinkStringToHTML(str, x.article, x.params);
};
pg.structures.nostalgia.popupRedirTopLinks = pg.structures.nostalgia.popupTopLinks;
/** -- fancy -- **/
copyStructure('original', 'fancy');
pg.structures.fancy.popupTitle = function(x) {
return navlinkStringToHTML('<
};
pg.structures.fancy.popupTopLinks = function(x) {
var hist = '<
var watch = '<
var move = '<
return navlinkStringToHTML('if(talk){' +
'<
'<
'}else{<
'*<
'*' + watch + '*' + move + '}
', x.article, x.params);
};
pg.structures.fancy.popupOtherLinks = function(x) {
var admin = '<
var user = '<
user += 'if(ipuser){|< popupString('email') + '>>}if(admin){*< var normal = '< return navlinkStringToHTML(' 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 return ' }; pg.structures.fancy2.popupLayout = function() { // move toplinks to after the title return ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab' ]; }; /** -- menus -- **/ copyStructure('original', 'menus'); pg.structures.menus.popupLayout = function() { return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab' ]; }; pg.structures.menus.popupTopLinks = function(x, shorter) { // FIXME maybe this stuff should be cached var s = []; var dropdiv = ' var enddiv = '
if(user){' + user + '*}if(admin){' + admin + 'if(user){
}else{*}}' + normal,
at the end and put one at the beginning
' + pg.structures.fancy.popupTopLinks(x).replace(RegExp('
$', 'i'), '');
var hist = '<
if (!shorter) {
hist = '
'|<
}
var lastedit = '<
var thank = 'if(diff){<
var jsHistory = '<
var linkshere = '<
var related = '<
var search = '
'|<
var watch = '
var protect = '
'<
var del = '
'<
var move = '<
var nullPurge = '
var viewOptions = '
var editRow = 'if(oldid){' +
'
'
var markPatrolled = 'if(rcid){<
var newTopic = 'if(talk){<
var protectDelete = 'if(admin){' + protect + del + '}';
if (getValueOf('popupActionsMenu')) {
s.push('<
} else {
s.push(dropdiv + '<
}
s.push('
' + enddiv);// user menu starts here
var email = '<
var contribs = 'if(wikimedia){
'if(admin){
s.push('if(user){*' + dropdiv + menuTitle('user'));
s.push('
' + enddiv + '}');// popups menu starts here
if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */ ) {
x.navpop.hasPopupMenu = true;
s.push('*' + dropdiv + menuTitle('popupsMenu') + '
' + enddiv);}
return navlinkStringToHTML(s.join(''), x.article, x.params);
};
function menuTitle(s) {
return '' + popupString(s) + '';
}
pg.structures.menus.popupRedirTitle = pg.structures.menus.popupTitle;
pg.structures.menus.popupRedirTopLinks = pg.structures.menus.popupTopLinks;
copyStructure('menus', 'shortmenus');
pg.structures.shortmenus.popupTopLinks = function(x) {
return pg.structures.menus.popupTopLinks(x, true);
};
pg.structures.shortmenus.popupRedirTopLinks = pg.structures.shortmenus.popupTopLinks;
//
pg.structures.lite = {};
pg.structures.lite.popupLayout = function() {
return ['popupTitle', 'popupPreview'];
};
pg.structures.lite.popupTitle = function(x) {
log(x.article + ': structures.lite.popupTitle');
//return navlinkStringToHTML('<
return '
};
// ENDFILE: structures.js
// STARTFILE: autoedit.js
//
function substitute(data, cmdBody) {
// alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags);
var fromRe = RegExp(cmdBody.from, cmdBody.flags);
return data.replace(fromRe, cmdBody.to);
}
function execCmds(data, cmdList) {
for (var i = 0; i < cmdList.length; ++i) {
data = cmdList[i].action(data, cmdList[i]);
}
return data;
}
function parseCmd(str) {
// returns a list of commands
if (!str.length) {
return [];
}
var p = false;
switch (str.charAt(0)) {
case 's':
p = parseSubstitute(str);
break;
default:
return false;
}
if (p) {
return [p].concat(parseCmd(p.remainder));
}
return false;
}
// FIXME: Only used once here, confusing with native (and more widely-used) unescape, should probably be replaced
// Then again, unescape is semi-soft-deprecated, so we should look into replacing that too
function unEscape(str, sep) {
return str.split('\\\\').join('\\').split('\\' + sep).join(sep).split('\\n').join('\n');
}
function parseSubstitute(str) {
// takes a string like s/a/b/flags;othercmds and parses it
var from, to, flags, tmp;
if (str.length < 4) {
return false;
}
var sep = str.charAt(1);
str = str.substring(2);
tmp = skipOver(str, sep);
if (tmp) {
from = tmp.segment;
str = tmp.remainder;
} else {
return false;
}
tmp = skipOver(str, sep);
if (tmp) {
to = tmp.segment;
str = tmp.remainder;
} else {
return false;
}
flags = '';
if (str.length) {
tmp = skipOver(str, ';') || skipToEnd(str, ';');
if (tmp) {
flags = tmp.segment;
str = tmp.remainder;
}
}
return {
action: substitute,
from: from,
to: to,
flags: flags,
remainder: str
};
}
function skipOver(str, sep) {
var endSegment = findNext(str, sep);
if (endSegment < 0) {
return false;
}
var segment = unEscape(str.substring(0, endSegment), sep);
return {
segment: segment,
remainder: str.substring(endSegment + 1)
};
}
/*eslint-disable*/
function skipToEnd(str, sep) {
return {
segment: str,
remainder: ''
};
}
/*eslint-enable */
function findNext(str, ch) {
for (var i = 0; i < str.length; ++i) {
if (str.charAt(i) == '\\') {
i += 2;
}
if (str.charAt(i) == ch) {
return i;
}
}
return -1;
}
function setCheckbox(param, box) {
var val = mw.util.getParamValue(param);
if (val) {
switch (val) {
case '1':
case 'yes':
case 'true':
box.checked = true;
break;
case '0':
case 'no':
case 'false':
box.checked = false;
}
}
}
function autoEdit() {
setupPopups(function() {
if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version')) {
return false;
}
if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken') === autoClickToken()) {
pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action'));
}
if (!document.editform) {
return false;
}
if (autoEdit.alreadyRan) {
return false;
}
autoEdit.alreadyRan = true;
var cmdString = mw.util.getParamValue('autoedit');
if (cmdString) {
try {
var editbox = document.editform.wpTextbox1;
var cmdList = parseCmd(cmdString);
var input = editbox.value;
var output = execCmds(input, cmdList);
editbox.value = output;
} catch (dang) {
return;
}
// wikEd user script compatibility
if (typeof(wikEdUseWikEd) != 'undefined') {
if (wikEdUseWikEd === true) {
WikEdUpdateFrame();
}
}
}
setCheckbox('autominor', document.editform.wpMinoredit);
setCheckbox('autowatch', document.editform.wpWatchthis);
var rvid = mw.util.getParamValue('autorv');
if (rvid) {
var url = pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids=' + rvid;
startDownload(url, null, autoEdit2);
} else {
autoEdit2();
}
});
}
function autoEdit2(d) {
var summary = mw.util.getParamValue('autosummary');
var summaryprompt = mw.util.getParamValue('autosummaryprompt');
var summarynotice = '';
if (d && d.data && mw.util.getParamValue('autorv')) {
var s = getRvSummary(summary, d.data);
if (s === false) {
summaryprompt = true;
summarynotice = popupString('Failed to get revision information, please edit manually.\n\n');
summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']);
} else {
summary = s;
}
}
if (summaryprompt) {
var txt = summarynotice +
popupString('Enter a non-empty edit summary or press cancel to abort');
var response = prompt(txt, summary);
if (response) {
summary = response;
} else {
return;
}
}
if (summary) {
document.editform.wpSummary.value = summary;
}
// Attempt to avoid possible premature clicking of the save button
// (maybe delays in updates to the DOM are to blame?? or a red herring)
setTimeout(autoEdit3, 100);
}
function autoClickToken() {
return mw.user.sessionId();
}
function autoEdit3() {
if (mw.util.getParamValue('actoken') != autoClickToken()) {
return;
}
var btn = mw.util.getParamValue('autoclick');
if (btn) {
if (document.editform && document.editform[btn]) {
var button = document.editform[btn];
var msg = tprintf('The %s button has been automatically clicked. Please wait for the next page to load.',
[button.value]);
bannerMessage(msg);
document.title = '(' + document.title + ')';
button.click();
} else {
alert(tprintf('Could not find button %s. Please check the settings in your javascript file.',
[btn]));
}
}
}
function bannerMessage(s) {
var headings = document.getElementsByTagName('h1');
if (headings) {
var div = document.createElement('div');
div.innerHTML = '' + pg.escapeQuotesHTML(s) + '';
headings[0].parentNode.insertBefore(div, headings[0]);
}
}
function getRvSummary(template, json) {
try {
var o = getJsObj(json);
var edit = anyChild(o.query.pages).revisions[0];
var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, '');
return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user]);
} catch (badness) {
return false;
}
}
//
// ENDFILE: autoedit.js
// STARTFILE: downloader.js
/**
@fileoverview
{@link Downloader}, a xmlhttprequest wrapper, and helper functions.
*/
/**
Creates a new Downloader
@constructor
@class The Downloader class. Create a new instance of this class to download stuff.
@param {String} url The url to download. This can be omitted and supplied later.
*/
function Downloader(url) {
if (typeof XMLHttpRequest != 'undefined') {
this.http = new XMLHttpRequest();
}
/**
The url to download
@type String
*/
this.url = url;
/**
A universally unique ID number
@type integer
*/
this.id = null;
/**
Modification date, to be culled from the incoming headers
@type Date
@private
*/
this.lastModified = null;
/**
What to do when the download completes successfully
@type Function
@private
*/
this.callbackFunction = null;
/**
What to do on failure
@type Function
@private
*/
this.onFailure = null;
/**
Flag set on abort
@type boolean
*/
this.aborted = false;
/**
HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities.
@type String
*/
this.method = 'GET';
/**
Async flag.
@type boolean
*/
this.async = true;
}
new Downloader();
/** Submits the http request. */
Downloader.prototype.send = function(x) {
if (!this.http) {
return null;
}
return this.http.send(x);
};
/** Aborts the download, setting the aborted
field to true. */
Downloader.prototype.abort = function() {
if (!this.http) {
return null;
}
this.aborted = true;
return this.http.abort();
};
/** Returns the downloaded data. */
Downloader.prototype.getData = function() {
if (!this.http) {
return null;
}
return this.http.responseText;
};
/** Prepares the download. */
Downloader.prototype.setTarget = function() {
if (!this.http) {
return null;
}
this.http.open(this.method, this.url, this.async);
this.http.setRequestHeader('Api-User-Agent', pg.api.userAgent);
};
/** Gets the state of the download. */
Downloader.prototype.getReadyState = function() {
if (!this.http) {
return null;
}
return this.http.readyState;
};
pg.misc.downloadsInProgress = {};
/** Starts the download.
Note that setTarget {@link Downloader#setTarget} must be run first
*/
Downloader.prototype.start = function() {
if (!this.http) {
return;
}
pg.misc.downloadsInProgress[this.id] = this;
this.http.send(null);
};
/** Gets the 'Last-Modified' date from the download headers.
Should be run after the download completes.
Returns null
on failure.
@return {Date}
*/
Downloader.prototype.getLastModifiedDate = function() {
if (!this.http) {
return null;
}
var lastmod = null;
try {
lastmod = this.http.getResponseHeader('Last-Modified');
} catch (err) {}
if (lastmod) {
return new Date(lastmod);
}
return null;
};
/** Sets the callback function.
@param {Function} f callback function, called as f(this)
on success
*/
Downloader.prototype.setCallback = function(f) {
if (!this.http) {
return;
}
this.http.onreadystatechange = f;
};
Downloader.prototype.getStatus = function() {
if (!this.http) {
return null;
}
return this.http.status;
};
//////////////////////////////////////////////////
// helper functions
/** Creates a new {@link Downloader} and prepares it for action.
@param {String} url The url to download
@param {integer} id The ID of the {@link Downloader} object
@param {Function} callback The callback function invoked on success
@return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
*/
function newDownload(url, id, callback, onfailure) {
var d = new Downloader(url);
if (!d.http) {
return 'ohdear';
}
d.id = id;
d.setTarget();
if (!onfailure) {
onfailure = 2;
}
var f = function() {
if (d.getReadyState() == 4) {
delete pg.misc.downloadsInProgress[this.id];
try {
if (d.getStatus() == 200) {
d.data = d.getData();
d.lastModified = d.getLastModifiedDate();
callback(d);
} else if (typeof onfailure == typeof 1) {
if (onfailure > 0) {
// retry
newDownload(url, id, callback, onfailure - 1);
}
} else if (typeof onfailure === "function") {
onfailure(d, url, id, callback);
}
} catch (somerr) {
/* ignore it */ }
}
};
d.setCallback(f);
return d;
}
/** Simulates a download from cached data.
The supplied data is put into a {@link Downloader} as if it had downloaded it.
@param {String} url The url.
@param {integer} id The ID.
@param {Function} callback The callback, which is invoked immediately as callback(d)
,
where d
is the new {@link Downloader}.
@param {String} data The (cached) data.
@param {Date} lastModified The (cached) last modified date.
*/
function fakeDownload(url, id, callback, data, lastModified, owner) {
var d = newDownload(url, callback);
d.owner = owner;
d.id = id;
d.data = data;
d.lastModified = lastModified;
return callback(d);
}
/**
Starts a download.
@param {String} url The url to download
@param {integer} id The ID of the {@link Downloader} object
@param {Function} callback The callback function invoked on success
@return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
*/
function startDownload(url, id, callback) {
var d = newDownload(url, id, callback);
if (typeof d == typeof '') {
return d;
}
d.start();
return d;
}
/**
Aborts all downloads which have been started.
*/
function abortAllDownloads() {
for (var x in pg.misc.downloadsInProgress) {
try {
pg.misc.downloadsInProgress[x].aborted = true;
pg.misc.downloadsInProgress[x].abort();
delete pg.misc.downloadsInProgress[x];
} catch (e) {}
}
}
// ENDFILE: downloader.js
// STARTFILE: livepreview.js
// TODO: location is often not correct (eg relative links in previews)
// NOTE: removed md5 and image and math parsing. was broken, lots of bytes.
/**
* InstaView - a Mediawiki to HTML converter in JavaScript
* Version 0.6.1
* Copyright (C) Pedro Fayolle 2005-2006
* https://en.wikipedia.org/wiki/User:Pilaf
* Distributed under the BSD license
*
* Changelog:
*
* 0.6.1
* - Fixed problem caused by \r characters
* - Improved inline formatting parser
*
* 0.6
* - Changed name to InstaView
* - Some major code reorganizations and factored out some common functions
* - Handled conversion of relative links (i.e. /foo)
* - Fixed misrendering of adjacent definition list items
* - Fixed bug in table headings handling
* - Changed date format in signatures to reflect Mediawiki's
* - Fixed handling of :Image:...
* - Updated MD5 function (hopefully it will work with UTF-8)
* - Fixed bug in handling of links inside images
*
* To do:
* - Better support for math tags
* - Full support for
* - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof)
* - Support for templates (through AJAX)
* - Support for coloured links (AJAX)
*/
var Insta = {};
function setupLivePreview() {
// options
Insta.conf = {
baseUrl: '',
user: {},
wiki: {
lang: pg.wiki.lang,
interwiki: pg.wiki.interwiki,
default_thumb_width: 180
},
paths: {
articles: pg.wiki.articlePath + '/',
// Only used for Insta previews with images. (not in popups)
math: '/math/',
images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname),
images_fallback: '//upload.wikimedia.org/wikipedia/commons/',
},
locale: {
user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId],
image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId],
category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId],
// shouldn't be used in popup previews, i think
months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
}
};
// options with default values or backreferences
Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian';
Insta.conf.user.signature = '' + Insta.conf.user.name + '';
//Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/';
// define constants
Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|' + Insta.conf.locale.image +
'):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i');
}
Insta.dump = function(from, to) {
if (typeof from == 'string') {
from = document.getElementById(from);
}
if (typeof to == 'string') {
to = document.getElementById(to);
}
to.innerHTML = this.convert(from.value);
};
Insta.convert = function(wiki) {
var ll = (typeof wiki == 'string') ? wiki.replace(/\r/g, '').split(/\n/) : wiki, // lines of wikicode
o = '', // output
p = 0, // para flag
r; // result of passing a regexp to compareLineStringOrReg()
// some shorthands
function remain() {
return ll.length;
}
function sh() {
return ll.shift();
} // shift
function ps(s) {
o += s;
} // push
// similar to C's printf, uses ? as placeholders, ?? to escape question marks
function f() {
var i = 1,
a = arguments,
f = a[0],
o = '',
c, p;
for (; i < a.length; i++) {
if ((p = f.indexOf('?')) + 1) {
// allow character escaping
i -= c = f.charAt(p + 1) == '?' ? 1 : 0;
o += f.substring(0, p) + (c ? '?' : a[i]);
f = f.substr(p + 1 + c);
} else {
break;
}
}
return o + f;
}
function html_entities(s) {
return s.replace(/&/g, "&").replace(//g, ">");
}
// Wiki text parsing to html is a nightmare.
// The below functions deliberately don't escape the ampersand since this would make it more difficult,
// and we don't absolutely need to for how we need it.
// This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML.
// Browsers should all be able to handle it though.
// We also escape significant wikimarkup characters to prevent further matching on the processed text
function htmlescape_text(s) {
return s.replace(//g, ">").replace(/:/g, ":").replace(/\[/g, "[").replace(/]/g, "]");
}
function htmlescape_attr(s) {
return htmlescape_text(s).replace(/'/g, "'").replace(/"/g, """);
}
// return the first non matching character position between two strings
function str_imatch(a, b) {
for (var i = 0, l = Math.min(a.length, b.length); i < l; i++) {
if (a.charAt(i) != b.charAt(i)) {
break;
}
}
return i;
}
// compare current line against a string or regexp
// if passed a string it will compare only the first string.length characters
// if passed a regexp the result is stored in r
function compareLineStringOrReg(c) {
return (typeof c == 'string') ? (ll[0] && ll[0].substr(0, c.length) == c) : (r = ll[0] && ll[0].match(c));
}
function compareLineString(c) {
return ll[0] == c;
} // compare current line against a string
function charAtPoint(p) {
return ll[0].charAt(p);
} // return char at pos p
function endl(s) {
ps(s);
sh();
}
function parse_list() {
var prev = '';
while (remain() && compareLineStringOrReg(/^([*#:;]+)(.*)$/)) {
var l_match = r;
sh();
var ipos = str_imatch(prev, l_match[1]);
// close uncontinued lists
for (var prevPos = prev.length - 1; prevPos >= ipos; prevPos--) {
var pi = prev.charAt(prevPos);
if (pi == '*') {
ps('');
} else if (pi == '#') {
ps('');
}
// close a dl only if the new item is not a dl item (:, ; or empty)
else if ($.inArray(l_match[1].charAt(prevPos), ['', '*', '#'])) {
ps('');
}
}
// open new lists
for (var matchPos = ipos; matchPos < l_match[1].length; matchPos++) {
var li = l_match[1].charAt(matchPos);
if (li == '*') {
ps('
- ');
- ' + parse_inline_nowiki(l_match[2]));
break;
case ';':
ps('
- ');
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('
- ' + 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('
', compareLineStringOrReg(/^\{\|( .*)$/) ? r[1] : ''));
');for (; remain();)
if (compareLineStringOrReg('|')) switch (charAtPoint(1)) {
case '}':
endl('
return;
case '-':
endl(f('
', compareLineStringOrReg(/\|-*(.*)/)[1])); break;
default:
parse_table_data();
}
else if (compareLineStringOrReg('!')) {
parse_table_data();
} else {
sh();
}
}
function parse_table_data() {
var td_line, match_i;
// 1: "|+", '|' or '+'
// 2: ??
// 3: attributes ??
// TODO: finish commenting this regexp
var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/);
if (td_match[1] == '|+') ps('
else ps('
if (typeof td_match[3] != 'undefined') {
//ps(' ' + td_match[3])
match_i = 4;
} else match_i = 2;
ps('>');
if (td_match[1] != '|+') {
// use || or !! as a cell separator depending on context
// NOTE: when split() is passed a regexp make sure to use non-capturing brackets
td_line = td_match[match_i].split((td_match[1] == '|') ? '||' : /(?:\|\||!!)/);
ps(parse_inline_nowiki(td_line.shift()));
while (td_line.length) ll.unshift(td_match[1] + td_line.pop());
} else {
ps(parse_inline_nowiki(td_match[match_i]));
}
var tc = 0,
td = [];
while (remain()) {
td.push(sh());
if (compareLineStringOrReg('|')) {
if (!tc) break; // we're at the outer-most level (no nested tables), skip to td parse
else if (charAtPoint(1) == '}') tc--;
} else if (!tc && compareLineStringOrReg('!')) break;
else if (compareLineStringOrReg('{|')) tc++;
}
if (td.length) ps(Insta.convert(td));
}
function parse_pre() {
ps('
');
do {
endl(parse_inline_nowiki(ll[0].substring(1)) + "\n");
} while (remain() && compareLineStringOrReg(' '));
ps('');
}
function parse_block_image() {
ps(parse_image(sh()));
}
function parse_image(str) {
//
// get what's in between "Image:" and ""
var tag = str.substring(str.indexOf(':') + 1, str.length - 2);
/* 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 '';
//
}
function parse_inline_nowiki(str) {
var start, lastend = 0;
var substart = 0,
nestlev = 0,
open, close, subloop;
var html = '';
while (-1 != (start = str.indexOf('
', substart))) { html += parse_inline_wiki(str.substring(lastend, start));
start += 8;
substart = start;
subloop = true;
do {
open = str.indexOf('
', substart); close = str.indexOf('', substart);
if (close <= open || open == -1) {
if (close == -1) {
return html + html_entities(str.substr(start));
}
substart = close + 9;
if (nestlev) {
nestlev--;
} else {
lastend = substart;
html += html_entities(str.substring(start, lastend - 9));
subloop = false;
}
} else {
substart = open + 8;
nestlev++;
}
} while (subloop);
}
return html + parse_inline_wiki(str.substr(lastend));
}
function parse_inline_images(str) {
//
var start, substart = 0,
nestlev = 0;
var loop, close, open, wiki, html;
while (-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;
}
//
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 ? '' : '';
} else {
em = !em;
o += 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("?", 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'), '').
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("?", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));
}).
replace(/\[\[(\/[^|]*?)\]\]/g, function($0, $1) {
return f("?", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1));
}).
replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function($0, $1, $2) {
return f("?", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2));
}).
// Common links
replace(/\[\[([^[|]*?)\]\](\w*)/g, function($0, $1, $2) {
return f("?", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));
}).
// Links
replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function($0, $1, $2, $3) {
return f("?", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));
}).
// Namespace
replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function($0, $1, $2, $3) {
return f("?", 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("?", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4));
}).
replace(/\[http:\/\/(.*?)\]/g, function($0, $1) {
return f("[#]", htmlescape_attr($1));
}).
replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function($0, $1, $2, $3) {
return f("?:?", 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("??:?", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4));
}).
replace('__NOTOC__', '').
replace('__NOINDEX__', '').
replace('__INDEX__', '').
replace('__NOEDITSECTION__', '');
}
// begin parsing
for (; remain();)
if (compareLineStringOrReg(/^(={1,6})(.*)\1(.*)$/)) {
p = 0;
endl(f('
? ?', r[1].length, parse_inline_nowiki(r[2]), r[1].length, r[3]));} else if (compareLineStringOrReg(/^[*#:;]/)) {
p = 0;
parse_list();
} else if (compareLineStringOrReg(' ')) {
p = 0;
parse_pre();
} else if (compareLineStringOrReg('{|')) {
p = 0;
parse_table();
} else if (compareLineStringOrReg(/^----+$/)) {
p = 0;
endl('
');} else if (compareLineStringOrReg(Insta.BLOCK_IMAGE)) {
p = 0;
parse_block_image();
} else {
// handle paragraphs
if (compareLineString('')) {
p = (remain() > 1 && ll[1] === (''));
if (p) endl('
');} else {
if (!p) {
ps('
');
p = 1;
}
ps(parse_inline_nowiki(ll[0]) + ' ');
}
sh();
}
return o;
};
function wiki2html(txt, baseurl) {
Insta.conf.baseUrl = baseurl;
return Insta.convert(txt);
}
// ENDFILE: livepreview.js
// STARTFILE: pageinfo.js
//
function popupFilterPageSize(data) {
return formatBytes(data.length);
}
function popupFilterCountLinks(data) {
var num = countLinks(data);
return String(num) + ' ' + ((num != 1) ? popupString('wikiLinks') : popupString('wikiLink'));
}
function popupFilterCountImages(data) {
var num = countImages(data);
return String(num) + ' ' + ((num != 1) ? popupString('images') : popupString('image'));
}
function popupFilterCountCategories(data) {
var num = countCategories(data);
return String(num) + ' ' + ((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'), ' ');
}
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 + ' ' + popupString('bytes'));
}
//
// ENDFILE: pageinfo.js
// STARTFILE: titles.js
/**
@fileoverview Defines the {@link Title} class, and associated crufty functions.
Title
deals with article titles and their variousforms. {@link Stringwrapper} is the parent class of
Title
, which exists simply to make things a littleneater.
*/
/**
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
Title
.@constructor
@class The Title class. Holds article titles and converts them into
various forms. Also deals with anchors, by which we mean the bits
of the article URL after a # character, representing locations
within an article.
@param {String} value The initial value to assign to the
article. This must be the canonical title (see {@link
Title#value}. Omit this in the constructor and use another function
to set the title if this is unavailable.
*/
function Title(val) {
/**
The canonical article title. This must be in UTF-8 with no
entities, escaping or nasties. Also, underscores should be
replaced with spaces.
@type String
@private
*/
this.value = null;
/**
The canonical form of the anchor. This should be exactly as
it appears in the URL, i.e. with the .C3.0A bits in.
@type String
*/
this.anchor = '';
this.setUtf(val);
}
Title.prototype = new Stringwrapper();
/**
Returns the canonical representation of the article title, optionally without anchor.
@param {boolean} omitAnchor
@fixme Decide specs for anchor
@return String The article title and the anchor.
*/
Title.prototype.toString = function(omitAnchor) {
return this.value + ((!omitAnchor && this.anchor) ? '#' + this.anchorString() : '');
};
Title.prototype.anchorString = function() {
if (!this.anchor) {
return '';
}
var split = this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/);
var len = split.length;
var value;
for (var j = 1; j < len; j += 2) {
// FIXME s/decodeURI/decodeURIComponent/g ?
value = split[j].split('.').join('%')
try {
value = decodeURIComponent(value);
} catch (e) {
// cannot decode
}
split[j] = value.split('_').join(' ');
}
return split.join('');
};
Title.prototype.urlAnchor = function() {
var split = this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/');
var len = split.length;
for (var j = 1; j < len; j += 2) {
split[j] = split[j].split('%').join('.');
}
return split.join('');
};
Title.prototype.anchorFromUtf = function(str) {
this.anchor = encodeURIComponent(str.split(' ').join('_'))
.split('%3A').join(':').split("'").join('%27').split('%').join('.');
};
Title.fromURL = function(h) {
return new Title().fromURL(h);
};
Title.prototype.fromURL = function(h) {
if (typeof h != 'string') {
this.value = null;
return this;
}
// NOTE : playing with decodeURI, encodeURI, escape, unescape,
// we seem to be able to replicate the IE borked encoding
// IE doesn't do this new-fangled utf-8 thing.
// and it's worse than that.
// IE seems to treat the query string differently to the rest of the url
// the query is treated as bona-fide utf8, but the first bit of the url is pissed around with
// we fix up & for all browsers, just in case.
var splitted = h.split('?');
splitted[0] = splitted[0].split('&').join('%26');
h = splitted.join('?');
var contribs = pg.re.contribs.exec(h);
if (contribs) {
if (contribs[1] == 'title=') {
contribs[3] = contribs[3].split('+').join(' ');
}
var u = new Title(contribs[3]);
this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace()));
return this;
}
var email = pg.re.email.exec(h);
if (email) {
this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace()));
return this;
}
var backlinks = pg.re.backlinks.exec(h);
if (backlinks) {
this.setUtf(this.decodeNasties(new Title(backlinks[3])));
return this;
}
//A dummy title object for a Special:Diff link.
var specialdiff = pg.re.specialdiff.exec(h);
if (specialdiff) {
this.setUtf(this.decodeNasties(new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff')));
return this;
}
// no more special cases to check --
// hopefully it's not a disguised user-related or specially treated special page
// Includes references
var m = pg.re.main.exec(h);
if (m === null) {
this.value = null;
} else {
var fromBotInterface = /[?](.+[&])?title=/.test(h);
if (fromBotInterface) {
m[2] = m[2].split('+').join('_');
}
var extracted = m[2] + (m[3] ? '#' + m[3] : '');
if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) {
// Fix Safari issue
// Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3.
this.setUtf(decodeURIComponent(unescape(extracted)));
} else {
this.setUtf(this.decodeNasties(extracted));
}
}
return this;
};
Title.prototype.decodeNasties = function(txt) {
// myDecodeURI uses decodeExtras, which removes _,
// thus ruining citations previews, which are formated as "cite_note-1"
try {
var ret = decodeURI(this.decodeEscapes(txt));
ret = ret.replace(/[_ ]*$/, '');
return ret;
} catch (e) {
return txt; // cannot decode
}
};
// Decode valid %-encodings, otherwise escape them
Title.prototype.decodeEscapes = function(txt) {
var split = txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/);
var len = split.length;
// No %-encoded items found, so replace the literal %
if (len === 1) {
return split[0].replace(/%(?![0-9a-fA-F][0-9a-fA-F])/g, "%25");
}
for (var i = 1; i < len; i = i + 2) {
split[i] = decodeURIComponent(split[i]);
}
return split.join('');
};
Title.fromAnchor = function(a) {
return new Title().fromAnchor(a);
};
Title.prototype.fromAnchor = function(a) {
if (!a) {
this.value = null;
return this;
}
return this.fromURL(a.href);
};
Title.fromWikiText = function(txt) {
return new Title().fromWikiText(txt);
};
Title.prototype.fromWikiText = function(txt) {
// FIXME - testing needed
txt = myDecodeURI(txt);
this.setUtf(txt);
return this;
};
Title.prototype.hintValue = function() {
if (!this.value) {
return '';
}
return safeDecodeURI(this.value);
};
//
Title.prototype.toUserName = function(withNs) {
if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) {
this.value = null;
return;
}
this.value = (withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') + this.stripNamespace().split('/')[0];
};
Title.prototype.userName = function(withNs) {
var t = (new Title(this.value));
t.toUserName(withNs);
if (t.value) {
return t;
}
return null;
};
Title.prototype.toTalkPage = function() {
// convert article to a talk page, or if we can't, return null
// In other words: return null if this ALREADY IS a talk page
// and return the corresponding talk page otherwise
//
// Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces
// * All discussion namespaces have odd-integer indices
// * The discussion namespace index for a specific namespace with index n is n + 1
if (this.value === null) {
return null;
}
var namespaceId = this.namespaceId();
if (namespaceId >= 0 && namespaceId % 2 === 0) //non-special and subject namespace
{
var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId + 1];
if (typeof localizedNamespace !== 'undefined') {
if (localizedNamespace === '') {
this.value = this.stripNamespace();
} else {
this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
}
return this.value;
}
}
this.value = null;
return null;
};
//
// Return canonical, localized namespace
Title.prototype.namespace = function() {
return mw.config.get('wgFormattedNamespaces')[this.namespaceId()];
};
Title.prototype.namespaceId = function() {
var n = this.value.indexOf(':');
if (n < 0) {
return 0;
} //mainspace
var namespaceId = mw.config.get('wgNamespaceIds')[this.value.substring(0, n).split(' ').join('_').toLowerCase()];
if (typeof namespaceId == 'undefined') return 0; //mainspace
return namespaceId;
};
//
Title.prototype.talkPage = function() {
var t = new Title(this.value);
t.toTalkPage();
if (t.value) {
return t;
}
return null;
};
Title.prototype.isTalkPage = function() {
if (this.talkPage() === null) {
return true;
}
return false;
};
Title.prototype.toArticleFromTalkPage = function() {
//largely copy/paste from toTalkPage above.
if (this.value === null) {
return null;
}
var namespaceId = this.namespaceId();
if (namespaceId >= 0 && namespaceId % 2 == 1) //non-special and talk namespace
{
var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId - 1];
if (typeof localizedNamespace !== 'undefined') {
if (localizedNamespace === '') {
this.value = this.stripNamespace();
} else {
this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
}
return this.value;
}
}
this.value = null;
return null;
};
Title.prototype.articleFromTalkPage = function() {
var t = new Title(this.value);
t.toArticleFromTalkPage();
if (t.value) {
return t;
}
return null;
};
Title.prototype.articleFromTalkOrArticle = function() {
var t = new Title(this.value);
if (t.toArticleFromTalkPage()) {
return t;
}
return this;
};
Title.prototype.isIpUser = function() {
return pg.re.ipUser.test(this.userName());
};
//
Title.prototype.stripNamespace = function() { // returns a string, not a Title
var n = this.value.indexOf(':');
if (n < 0) {
return this.value;
}
var namespaceId = this.namespaceId();
if (namespaceId === pg.nsMainspaceId) return this.value;
return this.value.substring(n + 1);
};
Title.prototype.setUtf = function(value) {
if (!value) {
this.value = '';
return;
}
var anch = value.indexOf('#');
if (anch < 0) {
this.value = value.split('_').join(' ');
this.anchor = '';
return;
}
this.value = value.substring(0, anch).split('_').join(' ');
this.anchor = value.substring(anch + 1);
this.ns = null; // wait until namespace() is called
};
Title.prototype.setUrl = function(urlfrag) {
var anch = urlfrag.indexOf('#');
this.value = safeDecodeURI(urlfrag.substring(0, anch));
this.anchor = this.value.substring(anch + 1);
};
Title.prototype.append = function(x) {
this.setUtf(this.value + x);
};
Title.prototype.urlString = function(x) {
if (!x) {
x = {};
}
var v = this.toString(true);
if (!x.omitAnchor && this.anchor) {
v += '#' + this.urlAnchor();
}
if (!x.keepSpaces) {
v = v.split(' ').join('_');
}
return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B');
};
Title.prototype.removeAnchor = function() {
return new Title(this.toString(true));
};
Title.prototype.toUrl = function() {
return pg.wiki.titlebase + this.urlString();
};
function parseParams(url) {
var specialDiff = pg.re.specialdiff.exec(url);
if (specialDiff) {
var split = specialDiff[1].split('/');
if (split.length == 1) return {
oldid: split[0],
diff: 'prev'
};
else if (split.length == 2) return {
oldid: split[0],
diff: split[1]
};
}
var ret = {};
if (url.indexOf('?') == -1) {
return ret;
}
url = url.split('#')[0];
var s = url.split('?').slice(1).join();
var t = s.split('&');
for (var i = 0; i < t.length; ++i) {
var z = t[i].split('=');
z.push(null);
ret[z[0]] = z[1];
}
//Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki
if (ret.diff && typeof(ret.oldid) === 'undefined') {
ret.oldid = "prev";
}
//Documentation seems to say something different, but oldid can also accept prev/next, and Echo is emitting such URLs. Simple fixup during parameter decoding:
if (ret.oldid && (ret.oldid === 'prev' || ret.oldid === 'next' || ret.oldid === 'cur')) {
var helper = ret.diff;
ret.diff = ret.oldid;
ret.oldid = helper;
}
return ret;
}
// (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup)
// (b) change spaces to underscores
// (c) encodeURI (just the straight one, no pg.re.urlNoPopup)
function myDecodeURI(str) {
var ret;
// FIXME decodeURIComponent??
try {
ret = decodeURI(str.toString());
} catch (summat) {
return str;
}
for (var i = 0; i < pg.misc.decodeExtras.length; ++i) {
var from = pg.misc.decodeExtras[i].from;
var to = pg.misc.decodeExtras[i].to;
ret = ret.split(from).join(to);
}
return ret;
}
function safeDecodeURI(str) {
var ret = myDecodeURI(str);
return ret || str;
}
///////////
// TESTS //
///////////
//
function isDisambig(data, article) {
if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
return false;
}
return !article.isTalkPage() && pg.re.disambig.test(data);
}
function stubCount(data, article) {
if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
return false;
}
var sectStub = 0;
var realStub = 0;
if (pg.re.stub.test(data)) {
var s = data.parenSplit(pg.re.stub);
for (var i = 1; i < s.length; i = i + 2) {
if (s[i]) {
++sectStub;
} else {
++realStub;
}
}
}
return {
real: realStub,
sect: sectStub
};
}
function isValidImageName(str) { // extend as needed...
return (str.indexOf('{') == -1);
}
function isInStrippableNamespace(article) {
// Does the namespace allow subpages
// Note, would be better if we had access to wgNamespacesWithSubpages
return (article.namespaceId() !== 0);
}
function isInMainNamespace(article) {
return article.namespaceId() === 0;
}
function anchorContainsImage(a) {
// iterate over children of anchor a
// see if any are images
if (a === null) {
return false;
}
var kids = a.childNodes;
for (var i = 0; i < kids.length; ++i) {
if (kids[i].nodeName == 'IMG') {
return true;
}
}
return false;
}
//
function isPopupLink(a) {
// NB for performance reasons, TOC links generally return true
// they should be stripped out later
if (!markNopopupSpanLinks.done) {
markNopopupSpanLinks();
}
if (a.inNopopupSpan) {
return false;
}
// FIXME is this faster inline?
if (a.onmousedown || a.getAttribute('nopopup')) {
return false;
}
var h = a.href;
if (h === document.location.href + '#') {
return false;
}
if (!pg.re.basenames.test(h)) {
return false;
}
if (!pg.re.urlNoPopup.test(h)) {
return true;
}
return (
(pg.re.email.test(h) || pg.re.contribs.test(h) || pg.re.backlinks.test(h) || pg.re.specialdiff.test(h)) &&
h.indexOf('&limit=') == -1);
}
function markNopopupSpanLinks() {
if (!getValueOf('popupOnlyArticleLinks'))
fixVectorMenuPopups();
var s = $('.nopopups').toArray();
for (var i = 0; i < s.length; ++i) {
var as = s[i].getElementsByTagName('a');
for (var j = 0; j < as.length; ++j) {
as[j].inNopopupSpan = true;
}
}
markNopopupSpanLinks.done = true;
}
function fixVectorMenuPopups() {
$('nav.vector-menu h3:first a:first').prop('inNopopupSpan', true);
}
// ENDFILE: titles.js
// STARTFILE: getpage.js
//////////////////////////////////////////////////
// Wiki-specific downloading
//
// Schematic for a getWiki call
//
// getPageWithCaching
// |
// false | true
// getPage<-[findPictureInCache]->-onComplete(a fake download)
// \.
// (async)->addPageToCache(download)->-onComplete(download)
// check cache to see if page exists
function getPageWithCaching(url, onComplete, owner) {
log('getPageWithCaching, url=' + url);
var i = findInPageCache(url);
var d;
if (i > -1) {
d = fakeDownload(url, owner.idNumber, onComplete,
pg.cache.pages[i].data, pg.cache.pages[i].lastModified,
owner);
} else {
d = getPage(url, onComplete, owner);
if (d && owner && owner.addDownload) {
owner.addDownload(d);
d.owner = owner;
}
}
}
function getPage(url, onComplete, owner) {
log('getPage');
var callback = function(d) {
if (!d.aborted) {
addPageToCache(d);
onComplete(d);
}
};
return startDownload(url, owner.idNumber, callback);
}
function findInPageCache(url) {
for (var i = 0; i < pg.cache.pages.length; ++i) {
if (url == pg.cache.pages[i].url) {
return i;
}
}
return -1;
}
function addPageToCache(download) {
log('addPageToCache ' + download.url);
var page = {
url: download.url,
data: download.data,
lastModified: download.lastModified
};
return pg.cache.pages.push(page);
}
// ENDFILE: getpage.js
// STARTFILE: parensplit.js
//////////////////////////////////////////////////
// parenSplit
// String.prototype.parenSplit should do what ECMAscript says String.prototype.split does,
// interspersing paren matches (regex capturing groups) between the split elements.
// i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c']
if (String('abc'.split(/(b)/)) != 'a,b,c') {
// broken String.split, e.g. konq, IE < 10
String.prototype.parenSplit = function(re) {
re = nonGlobalRegex(re);
var s = this;
var m = re.exec(s);
var ret = [];
while (m && s) {
// without the following loop, we have
// 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/)
for (var i = 0; i < m.length; ++i) {
if (typeof m[i] == 'undefined') m[i] = '';
}
ret.push(s.substring(0, m.index));
ret = ret.concat(m.slice(1));
s = s.substring(m.index + m[0].length);
m = re.exec(s);
}
ret.push(s);
return ret;
};
} else {
String.prototype.parenSplit = function(re) {
return this.split(re);
};
String.prototype.parenSplit.isNative = true;
}
function nonGlobalRegex(re) {
var s = re.toString();
var flags = '';
for (var j = s.length; s.charAt(j) != '/'; --j) {
if (s.charAt(j) != 'g') {
flags += s.charAt(j);
}
}
var t = s.substring(1, j);
return RegExp(t, flags);
}
// ENDFILE: parensplit.js
// STARTFILE: tools.js
// IE madness with encoding
// ========================
//
// suppose throughout that the page is in utf8, like wikipedia
//
// if a is an anchor DOM element and a.href should consist of
//
// http://host.name.here/wiki/foo?bar=baz
//
// then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie))
// but IE gives bar=baz correctly as plain utf8
//
// ---------------------------------
//
// IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here.
//
// ---------------------------------
//
// summat else
// Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm
//
function getJsObj(json) {
try {
var json_ret = JSON.parse(json);
if (json_ret.warnings) {
for (var w = 0; w < json_ret.warnings.length; w++) {
if (json_ret.warnings[w]['*']) {
log(json_ret.warnings[w]['*']);
} else {
log(json_ret.warnings[w].warnings);
}
}
} else if (json_ret.error) {
errlog(json_ret.error.code + ': ' + json_ret.error.info);
}
return json_ret;
} catch (someError) {
errlog('Something went wrong with getJsObj, json=' + json);
return 1;
}
}
function anyChild(obj) {
for (var p in obj) {
return obj[p];
}
return null;
}
//
function upcaseFirst(str) {
if (typeof str != typeof || str === ) return '';
return str.charAt(0).toUpperCase() + str.substring(1);
}
function findInArray(arr, foo) {
if (!arr || !arr.length) {
return -1;
}
var len = arr.length;
for (var i = 0; i < len; ++i) {
if (arr[i] == foo) {
return i;
}
}
return -1;
}
/* eslint-disable no-unused-vars */
function nextOne(array, value) {
// NB if the array has two consecutive entries equal
// then this will loop on successive calls
var i = findInArray(array, value);
if (i < 0) {
return null;
}
return array[i + 1];
}
/* eslint-enable no-unused-vars */
function literalizeRegex(str) {
return mw.util.escapeRegExp(str);
}
String.prototype.entify = function() {
//var shy='';
return this.split('&').join('&').split('<').join('<').split('>').join('>' /*+shy*/ ).split('"').join('"');
};
// Array filter function
function removeNulls(val) {
return val !== null;
}
function joinPath(list) {
return list.filter(removeNulls).join('/');
}
function simplePrintf(str, subs) {
if (!str || !subs) {
return str;
}
var ret = [];
var s = str.parenSplit(/(%s|\$[0-9]+)/);
var i = 0;
do {
ret.push(s.shift());
if (!s.length) {
break;
}
var cmd = s.shift();
if (cmd == '%s') {
if (i < subs.length) {
ret.push(subs[i]);
} else {
ret.push(cmd);
}
++i;
} else {
var j = parseInt(cmd.replace('$', ''), 10) - 1;
if (j > -1 && j < subs.length) {
ret.push(subs[j]);
} else {
ret.push(cmd);
}
}
} while (s.length > 0);
return ret.join('');
}
/* eslint-disable no-unused-vars */
function isString(x) {
return (typeof x === 'string' || x instanceof String);
}
function isNumber(x) {
return (typeof x === 'number' || x instanceof Number);
}
function isRegExp(x) {
return x instanceof RegExp;
}
function isArray(x) {
return x instanceof Array;
}
function isObject(x) {
return x instanceof Object;
}
function isFunction(x) {
return !isRegExp(x) && (typeof x === "function" || x instanceof Function);
}
/* eslint-enable no-unused-vars */
function repeatString(s, mult) {
var ret = '';
for (var i = 0; i < mult; ++i) {
ret += s;
}
return ret;
}
function zeroFill(s, min) {
min = min || 2;
var t = s.toString();
return repeatString('0', min - t.length) + t;
}
function map(f, o) {
if (isArray(o)) {
return map_array(f, o);
}
return map_object(f, o);
}
function map_array(f, o) {
var ret = [];
for (var i = 0; i < o.length; ++i) {
ret.push(f(o[i]));
}
return ret;
}
function map_object(f, o) {
var ret = {};
for (var i in o) {
ret[o] = f(o[i]);
}
return ret;
}
pg.escapeQuotesHTML = function(text) {
return text
.replace(/&/g, "&")
.replace(/"/g, """)
.replace(/
.replace(/>/g, ">");
};
pg.unescapeQuotesHTML = function(html) {
// From https://stackoverflow.com/a/7394787
// This seems to be implemented correctly on all major browsers now, so we
// don't have to make our own function.
var txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
};
// ENDFILE: tools.js
// STARTFILE: dab.js
//
//////////////////////////////////////////////////
// Dab-fixing code
//
function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) {
log('retargetDab: newTarget=' + newTarget + ' oldTarget=' + oldTarget);
return changeLinkTargetLink({
newTarget: newTarget,
text: newTarget.split(' ').join(' '),
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(' '),
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 = '
' + popupString('Click to disambiguate this link to:') + '
';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(' '),
hint: popupString("remove all links to this page from this article"),
clickButton: getValueOf('popupRedlinkAutoClick'),
oldTarget: article.toString(),
summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])
});
}
//
// ENDFILE: dab.js
// STARTFILE: htmloutput.js
// this has to use a timer loop as we don't know if the DOM element exists when we want to set the text
function setPopupHTML(str, elementId, popupId, onSuccess, append) {
if (typeof popupId === 'undefined') {
//console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100));
popupId = pg.idNumber;
}
var popupElement = document.getElementById(elementId + popupId);
if (popupElement) {
if (!append) {
popupElement.innerHTML = '';
}
if (isString(str)) {
popupElement.innerHTML += str;
} else {
popupElement.appendChild(str);
}
if (onSuccess) {
onSuccess();
}
setTimeout(checkPopupPosition, 100);
return true;
} else {
// call this function again in a little while...
setTimeout(function() {
setPopupHTML(str, elementId, popupId, onSuccess);
}, 600);
}
return null;
}
//
function setPopupTrailer(str, id) {
return setPopupHTML(str, 'popupData', id);
}
//
// args.navpopup is mandatory
// optional: args.redir, args.redirTarget
// FIXME: ye gods, this is ugly stuff
function fillEmptySpans(args) {
// if redir is present and true then redirTarget is mandatory
var redir = true;
var rcid;
if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) {
redir = false;
}
var a = args.navpopup.parentAnchor;
var article, hint = null,
oldid = null,
params = {};
if (redir && typeof args.redirTarget == typeof {}) {
article = args.redirTarget;
//hint=article.hintValue();
} else {
article = (new Title()).fromAnchor(a);
hint = a.originalTitle || article.hintValue();
params = parseParams(a.href);
oldid = (getValueOf('popupHistoricalLinks')) ? params.oldid : null;
rcid = params.rcid;
}
var x = {
article: article,
hint: hint,
oldid: oldid,
rcid: rcid,
navpop: args.navpopup,
params: params
};
var structure = pg.structures[getValueOf('popupStructure')];
if (typeof structure != 'object') {
setPopupHTML('popupError', 'Unknown structure (this should never happen): ' +
pg.option.popupStructure, args.navpopup.idNumber);
return;
}
var spans = flatten(pg.misc.layout);
var numspans = spans.length;
var redirs = pg.misc.redirSpans;
for (var i = 0; i < numspans; ++i) {
var found = redirs && (redirs.indexOf(spans[i]) !== -1);
//log('redir='+redir+', found='+found+', spans[i]='+spans[i]);
if ((found && !redir) || (!found && redir)) {
//log('skipping this set of the loop');
continue;
}
var structurefn = structure[spans[i]];
if (structurefn === undefined) {
// nothing to do for this structure part
continue;
}
var setfn = setPopupHTML;
if (getValueOf('popupActiveNavlinks') &&
(spans[i].indexOf('popupTopLinks') === 0 || spans[i].indexOf('popupRedirTopLinks') === 0)
) {
setfn = setPopupTipsAndHTML;
}
switch (typeof structurefn) {
case 'function':
log('running ' + spans[i] + '({article:' + x.article + ', hint:' + x.hint + ', oldid: ' + x.oldid + '})');
setfn(structurefn(x), spans[i], args.navpopup.idNumber);
break;
case 'string':
setfn(structurefn, spans[i], args.navpopup.idNumber);
break;
default:
errlog('unknown thing with label ' + spans[i] + ' (span index was ' + i + ')');
break;
}
}
}
// flatten an array
function flatten(list, start) {
var ret = [];
if (typeof start == 'undefined') {
start = 0;
}
for (var i = start; i < list.length; ++i) {
if (typeof list[i] == typeof []) {
return ret.concat(flatten(list[i])).concat(flatten(list, i + 1));
} else {
ret.push(list[i]);
}
}
return ret;
}
// Generate html for whole popup
function popupHTML(a) {
getValueOf('popupStructure');
var structure = pg.structures[pg.option.popupStructure];
if (typeof structure != 'object') {
//return 'Unknown structure: '+pg.option.popupStructure;
// override user choice
pg.option.popupStructure = pg.optionDefault.popupStructure;
return popupHTML(a);
}
if (typeof structure.popupLayout != 'function') {
return 'Bad layout';
}
pg.misc.layout = structure.popupLayout();
if (typeof structure.popupRedirSpans === "function") {
pg.misc.redirSpans = structure.popupRedirSpans();
} else {
pg.misc.redirSpans = [];
}
return makeEmptySpans(pg.misc.layout, a.navpopup);
}
function makeEmptySpans(list, navpop) {
var ret = '';
for (var i = 0; i < list.length; ++i) {
if (typeof list[i] == typeof '') {
ret += emptySpanHTML(list[i], navpop.idNumber, 'div');
} else if (typeof list[i] == typeof [] && list[i].length > 0) {
ret = ret.parenSplit(RegExp('([^>]*?>$)')).join(makeEmptySpans(list[i], navpop));
} else if (typeof list[i] == typeof {} && list[i].nodeType) {
ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType);
}
}
return ret;
}
function emptySpanHTML(name, id, tag, classname) {
tag = tag || 'span';
if (!classname) {
classname = emptySpanHTML.classAliases[name];
}
classname = classname || name;
if (name == getValueOf('popupDragHandle')) {
classname += ' popupDragHandle';
}
return simplePrintf('<%s id="%s" class="%s">%s>', [tag, name + id, classname, tag]);
}
emptySpanHTML.classAliases = {
'popupSecondPreview': 'popupPreview'
};
// generate html for popup image
// where n=idNumber
function imageHTML(article, idNumber) {
return simplePrintf('' +
'
' +'', [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(/
/i, /<\/table.*?>/i, / /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 ...
this.kill(/]*?>/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
or var splitted = this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/);
var len = splitted.length;
for (var i = 1; i < len; i = i + 2) {
switch (splitted[i]) {
case '
': case '':
case '
':
':case '
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
html
- ');
} else if (li == '#') {
ps('
- ');
}
// open a new dl only if the prev item is not a dl item (:, ; or empty)
else if ($.inArray(prev.charAt(matchPos), ['', '*', '#'])) {
ps('
- ');
}
}
switch (l_match[1].charAt(l_match[1].length - 1)) {
case '*':
case '#':
ps('