· 7 years ago · Mar 27, 2018, 08:02 AM
1// STARTFILE: main.js
2// **********************************************************************
3// ** **
4// ** changes to this file affect many users. **
5// ** please discuss on the talk page before editing **
6// ** **
7// **********************************************************************
8// ** **
9// ** if you do edit this file, be sure that your editor recognizes it **
10// ** as utf8, or the weird and wonderful characters in the namespaces **
11// ** below will be completely broken. You can check with the show **
12// ** changes button before submitting the edit. **
13// ** test: مدیا מיוחד МÑÐ´Ñ‹Ñ **
14// ** **
15// **********************************************************************
16/* eslint-env browser */
17/* global $, jQuery, mw, window */
18
19// Fix later
20/* global log, errlog, popupStrings, wikEdUseWikEd, WikEdUpdateFrame */
21/* eslint no-mixed-spaces-and-tabs: 0, no-empty: 0 */
22
23$(function () {
24//////////////////////////////////////////////////
25// Globals
26//
27
28// Trying to shove as many of these as possible into the pg (popup globals) object
29var pg = {
30 re: {}, // regexps
31 ns: {}, // namespaces
32 string: {}, // translatable strings
33 wiki: {}, // local site info
34 misc: {}, // YUCK PHOOEY
35 option: {}, // options, see newOption etc
36 optionDefault: {}, // default option values
37 flag: {}, // misc flags
38 cache: {}, // page and image cache
39 structures: {}, // navlink structures
40 timer: {}, // all sorts of timers (too damn many)
41 counter: {}, // .. and all sorts of counters
42 current: {}, // state info
43 fn: {}, // functions
44 endoflist: null
45};
46/* Bail if the gadget/script is being loaded twice */
47if( window.pg ) {
48 return;
49}
50/* Export to global context */
51window.pg = pg;
52
53/// Local Variables: ///
54/// mode:c ///
55/// End: ///
56// ENDFILE: main.js
57// STARTFILE: actions.js
58function setupTooltips(container, remove, force, popData) {
59 log('setupTooltips, container='+container+', remove='+remove);
60 if (!container) {
61//<NOLITE>
62 // the main initial call
63 if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) {
64 document.editform.wpTextbox1.onmouseup=doSelectionPopup;
65 }
66//</NOLITE>
67 // article/content is a structure-dependent thing
68 container = defaultPopupsContainer();
69 }
70
71 if (!remove && !force && container.ranSetupTooltipsAlready) { return; }
72 container.ranSetupTooltipsAlready = !remove;
73
74 var anchors;
75 anchors=container.getElementsByTagName('A');
76 setupTooltipsLoop(anchors, 0, 250, 100, remove, popData);
77}
78
79function defaultPopupsContainer() {
80 if (getValueOf('popupOnlyArticleLinks')) {
81 return document.getElementById('mw_content') ||
82 document.getElementById('content') ||
83 document.getElementById('article') || document;
84 }
85 return document;
86}
87
88function setupTooltipsLoop(anchors,begin,howmany,sleep, remove, popData) {
89 log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments));
90 var finish=begin+howmany;
91 var loopend = Math.min(finish, anchors.length);
92 var j=loopend - begin;
93 log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin +
94 ', howmany=' + howmany + ', loopend=' + loopend + ', remove=' + remove);
95 var doTooltip= remove ? removeTooltip : addTooltip;
96 // try a faster (?) loop construct
97 if (j > 0) {
98 do {
99 var a=anchors[loopend - j];
100 if (typeof a==='undefined' || !a || !a.href) {
101 log('got null anchor at index ' + loopend - j);
102 continue;
103 }
104 doTooltip(a, popData);
105 } while (--j);
106 }
107 if (finish < anchors.length) {
108 setTimeout(function() {
109 setupTooltipsLoop(anchors,finish,howmany,sleep,remove,popData);},
110 sleep);
111 } else {
112 if ( !remove && ! getValueOf('popupTocLinks')) { rmTocTooltips(); }
113 pg.flag.finishedLoading=true;
114 }
115}
116
117// eliminate popups from the TOC
118// This also kills any onclick stuff that used to be going on in the toc
119function rmTocTooltips() {
120 var toc=document.getElementById('toc');
121 if (toc) {
122 var tocLinks=toc.getElementsByTagName('A');
123 var tocLen = tocLinks.length;
124 for (var j=0; j<tocLen; ++j) {
125 removeTooltip(tocLinks[j], true);
126 }
127 }
128}
129
130function addTooltip(a, popData) {
131 if ( !isPopupLink(a) ) { return; }
132 a.onmouseover=mouseOverWikiLink;
133 a.onmouseout= mouseOutWikiLink;
134 a.onmousedown = killPopup;
135 a.hasPopup = true;
136 a.popData = popData;
137}
138
139function removeTooltip(a) {
140 if ( !a.hasPopup ) { return; }
141 a.onmouseover = null;
142 a.onmouseout = null;
143 if (a.originalTitle) { a.title = a.originalTitle; }
144 a.hasPopup=false;
145}
146
147function removeTitle(a) {
148 if (!a.originalTitle) {
149 a.originalTitle=a.title;
150 }
151 a.title='';
152}
153
154function restoreTitle(a) {
155 if ( a.title || !a.originalTitle ) { return; }
156 a.title = a.originalTitle;
157}
158
159function registerHooks(np) {
160 var popupMaxWidth=getValueOf('popupMaxWidth');
161
162 if (typeof popupMaxWidth === 'number') {
163 var setMaxWidth = function () {
164 np.mainDiv.style.maxWidth = popupMaxWidth + 'px';
165 np.maxWidth = popupMaxWidth;
166 };
167 np.addHook(setMaxWidth, 'unhide', 'before');
168 }
169//<NOLITE>
170 np.addHook(addPopupShortcuts, 'unhide', 'after');
171 np.addHook(rmPopupShortcuts, 'hide', 'before');
172//</NOLITE>
173}
174
175
176function mouseOverWikiLink(evt) {
177 if (!evt && window.event) {evt=window.event;}
178 return mouseOverWikiLink2(this, evt);
179}
180
181/**
182 * Gets the references list item that the provided footnote link targets. This
183 * is typically a li element within the ol.references element inside the reflist.
184 * @param {Element} a - A footnote link.
185 * @returns {Element|boolean} The targeted element, or false if one can't be found.
186 */
187function footnoteTarget(a) {
188 var aTitle=Title.fromAnchor(a);
189 // We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly
190 var anch = aTitle.anchor;
191 if ( ! /^(cite_note-|_note-|endnote)/.test(anch) ) { return false; }
192
193 var lTitle=Title.fromURL(location.href);
194 if ( lTitle.toString(true) !== aTitle.toString(true) ) { return false; }
195
196 var el=document.getElementById(anch);
197 while ( el && typeof el.nodeName === 'string') {
198 var nt = el.nodeName.toLowerCase();
199 if ( nt === 'li' ) { return el; }
200 else if ( nt === 'body' ) { return false; }
201 else if ( el.parentNode ) { el=el.parentNode; }
202 else { return false; }
203 }
204 return false;
205}
206
207function footnotePreview(x, navpop) {
208 setPopupHTML('<hr />' + x.innerHTML, 'popupPreview', navpop.idNumber);
209}
210
211function modifierKeyHandler(a) {
212 return function(evt) {
213 var mod=getValueOf('popupModifier');
214 if (!mod) { return true; }
215
216 if (!evt && window.event) {evt=window.event;}
217
218 // FIXME
219// var modPressed = modifierPressed(evt);
220 var action = getValueOf('popupModifierAction');
221
222 // FIXME: probable bug - modifierPressed should be modPressed below?
223 if ( action === 'disable' && modifierPressed ) { return true; }
224 if ( action === 'enable' && !modifierPressed ) { return true; }
225
226 mouseOverWikiLink2(a, evt);
227 };
228}
229
230function modifierPressed(evt) {
231 var mod=getValueOf('popupModifier');
232 if (!mod) { return false; }
233
234 if (!evt && window.event) {evt=window.event;}
235
236 return ( evt && mod && evt[mod.toLowerCase() + 'Key'] );
237
238}
239
240function dealWithModifier(a,evt) {
241 if (!getValueOf('popupModifier')) { return false; }
242 var action = getValueOf('popupModifierAction');
243 if ( action == 'enable' && !modifierPressed(evt) ||
244 action == 'disable' && modifierPressed(evt) ) {
245 // if the modifier is needed and not pressed, listen for it until
246 // we mouseout of this link.
247 restoreTitle(a);
248 a.modifierKeyHandler=modifierKeyHandler(a);
249
250 switch (action) {
251 case 'enable':
252 document.addEventListener('keydown', a.modifierKeyHandler, false);
253 a.addEventListener('mouseout', function() {
254 document.removeEventListener('keydown',
255 a.modifierKeyHandler, false);
256 }, true);
257 break;
258 case 'disable':
259 document.addEventListener('keyup', a.modifierKeyHandler, false);
260 }
261
262 return true;
263 }
264 return false;
265}
266
267function mouseOverWikiLink2(a, evt) {
268 if (dealWithModifier(a,evt)) { return; }
269 if ( getValueOf('removeTitles') ) { removeTitle(a); }
270 if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; }
271 pg.current.link=a;
272
273 if (getValueOf('simplePopups') && pg.option.popupStructure === null) {
274 // reset *default value* of popupStructure
275 setDefault('popupStructure', 'original');
276 }
277
278 var article=(new Title()).fromAnchor(a);
279 // set global variable (ugh) to hold article (wikipage)
280 pg.current.article = article;
281
282 if (!a.navpopup) {
283 a.navpopup=newNavpopup(a, article);
284 pg.current.linksHash[a.href] = a.navpopup;
285 pg.current.links.push(a);
286 }
287 if (a.navpopup.pending === null || a.navpopup.pending !== 0) {
288 // either fresh popups or those with unfinshed business are redone from scratch
289 simplePopupContent(a, article);
290 }
291 a.navpopup.showSoonIfStable(a.navpopup.delay);
292
293 clearInterval(pg.timer.checkPopupPosition);
294 pg.timer.checkPopupPosition=setInterval(checkPopupPosition, 600);
295
296 if(getValueOf('simplePopups')) {
297 if (getValueOf('popupPreviewButton') && !a.simpleNoMore) {
298 var d=document.createElement('div');
299 d.className='popupPreviewButtonDiv';
300 var s=document.createElement('span');
301 d.appendChild(s);
302 s.className='popupPreviewButton';
303 s['on' + getValueOf('popupPreviewButtonEvent')] = function() {
304 a.simpleNoMore=true;
305 nonsimplePopupContent(a,article);
306 };
307 s.innerHTML=popupString('show preview');
308 setPopupHTML(d, 'popupPreview', a.navpopup.idNumber);
309 }
310 return;
311 }
312
313 if (a.navpopup.pending !== 0 ) {
314 nonsimplePopupContent(a, article);
315 }
316}
317
318// simplePopupContent: the content that is shown even when simplePopups is true
319function simplePopupContent(a, article) {
320 /* FIXME hack */ a.navpopup.hasPopupMenu=false;
321 a.navpopup.setInnerHTML(popupHTML(a));
322 fillEmptySpans({navpopup:a.navpopup});
323
324 if (getValueOf('popupDraggable'))
325 {
326 var dragHandle = getValueOf('popupDragHandle') || null;
327 if (dragHandle && dragHandle != 'all') {
328 dragHandle += a.navpopup.idNumber;
329 }
330 setTimeout(function(){a.navpopup.makeDraggable(dragHandle);}, 150);
331 }
332
333//<NOLITE>
334 if (getValueOf('popupRedlinkRemoval') && a.className=='new') {
335 setPopupHTML('<br>'+popupRedlinkHTML(article), 'popupRedlink', a.navpopup.idNumber);
336 }
337//</NOLITE>
338}
339
340function debugData(navpopup) {
341 if(getValueOf('popupDebugging') && navpopup.idNumber) {
342 setPopupHTML('idNumber='+navpopup.idNumber + ', pending=' + navpopup.pending,
343 'popupError', navpopup.idNumber);
344 }
345}
346
347function newNavpopup(a, article) {
348 var navpopup = new Navpopup();
349 navpopup.fuzz=5;
350 navpopup.delay=getValueOf('popupDelay')*1000;
351 // increment global counter now
352 navpopup.idNumber = ++pg.idNumber;
353 navpopup.parentAnchor = a;
354 navpopup.parentPopup = (a.popData && a.popData.owner);
355 navpopup.article = article;
356 registerHooks(navpopup);
357 return navpopup;
358}
359
360function nonsimplePopupContent(a, article) {
361 var diff=null, history=null;
362 var params=parseParams(a.href);
363 var oldid=(typeof params.oldid=='undefined' ? null : params.oldid);
364//<NOLITE>
365 if(getValueOf('popupPreviewDiffs')) {
366 diff=params.diff;
367 }
368 if(getValueOf('popupPreviewHistory')) {
369 history=(params.action=='history');
370 }
371//</NOLITE>
372 a.navpopup.pending=0;
373 var referenceElement = footnoteTarget(a);
374 if (referenceElement) {
375 footnotePreview(referenceElement, a.navpopup);
376//<NOLITE>
377 } else if ( diff || diff === 0 ) {
378 loadDiff(article, oldid, diff, a.navpopup);
379 } else if ( history ) {
380 loadAPIPreview('history', article, a.navpopup);
381 } else if ( pg.re.contribs.test(a.href) ) {
382 loadAPIPreview('contribs', article, a.navpopup);
383 } else if ( pg.re.backlinks.test(a.href) ) {
384 loadAPIPreview('backlinks', article, a.navpopup);
385 } else if ( // FIXME should be able to get all preview combinations with options
386 article.namespaceId()==pg.nsImageId &&
387 ( getValueOf('imagePopupsForImages') || ! anchorContainsImage(a) )
388 ) {
389 loadAPIPreview('imagepagepreview', article, a.navpopup);
390 loadImage(article, a.navpopup);
391//</NOLITE>
392 } else {
393 if (article.namespaceId() == pg.nsCategoryId &&
394 getValueOf('popupCategoryMembers')) {
395 loadAPIPreview('category', article, a.navpopup);
396 } else if ((article.namespaceId() == pg.nsUserId || article.namespaceId() == pg.nsUsertalkId) &&
397 getValueOf('popupUserInfo')) {
398 loadAPIPreview('userinfo', article, a.navpopup);
399 }
400 startArticlePreview(article, oldid, a.navpopup);
401 }
402}
403
404function pendingNavpopTask(navpop) {
405 if (navpop && navpop.pending === null) { navpop.pending=0; }
406 ++navpop.pending;
407 debugData(navpop);
408}
409
410function completedNavpopTask(navpop) {
411 if (navpop && navpop.pending) { --navpop.pending; }
412 debugData(navpop);
413}
414
415function startArticlePreview(article, oldid, navpop) {
416 navpop.redir=0;
417 loadPreview(article, oldid, navpop);
418}
419
420function loadPreview(article, oldid, navpop) {
421 if (!navpop.redir) { navpop.originalArticle=article; }
422 article.oldid = oldid;
423 loadAPIPreview('revision', article, navpop);
424}
425
426function loadPreviewFromRedir(redirMatch, navpop) {
427 // redirMatch is a regex match
428 var target = new Title().fromWikiText(redirMatch[2]);
429 // overwrite (or add) anchor from original target
430 // mediawiki does overwrite; eg [[User:Lupin/foo3#Done]]
431 if ( navpop.article.anchor ) { target.anchor = navpop.article.anchor; }
432 navpop.redir++;
433 navpop.redirTarget=target;
434//<NOLITE>
435 var warnRedir = redirLink(target, navpop.article);
436 setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber);
437//</NOLITE>
438 navpop.article=target;
439 fillEmptySpans({redir: true, redirTarget: target, navpopup:navpop});
440 return loadPreview(target, null, navpop);
441}
442
443function insertPreview(download) {
444 if (!download.owner) { return; }
445
446 var redirMatch = pg.re.redirect.exec(download.data);
447 if (download.owner.redir === 0 && redirMatch) {
448 loadPreviewFromRedir(redirMatch, download.owner);
449 return;
450 }
451
452 if (download.owner.visible || !getValueOf('popupLazyPreviews')) {
453 insertPreviewNow(download);
454 } else {
455 var id=(download.owner.redir) ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK';
456 download.owner.addHook( function(){insertPreviewNow(download); return true;},
457 'unhide', 'after', id );
458 }
459}
460
461function insertPreviewNow(download) {
462 if (!download.owner) { return; }
463 var wikiText=download.data;
464 var navpop=download.owner;
465 var art=navpop.redirTarget || navpop.originalArticle;
466
467//<NOLITE>
468 makeFixDabs(wikiText, navpop);
469 if (getValueOf('popupSummaryData')) {
470 getPageInfo(wikiText, download);
471 setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber);
472 }
473
474 var imagePage='';
475 if (art.namespaceId()==pg.nsImageId) { imagePage=art.toString(); }
476 else { imagePage=getValidImageFromWikiText(wikiText); }
477 if(imagePage) { loadImage(Title.fromWikiText(imagePage), navpop); }
478//</NOLITE>
479
480 if (getValueOf('popupPreviews')) { insertArticlePreview(download, art, navpop); }
481
482}
483
484function insertArticlePreview(download, art, navpop) {
485 if (download && typeof download.data == typeof ''){
486 if (art.namespaceId()==pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) {
487 // FIXME compare/consolidate with diff escaping code for wikitext
488 var h='<hr /><span style="font-family: monospace;">' + download.data.entify().split('\\n').join('<br />\\n') + '</span>';
489 setPopupHTML(h, 'popupPreview', navpop.idNumber);
490 }
491 else {
492 var p=prepPreviewmaker(download.data, art, navpop);
493 p.showPreview();
494 }
495 }
496}
497
498function prepPreviewmaker(data, article, navpop) {
499 // deal with tricksy anchors
500 var d=anchorize(data, article.anchorString());
501 var urlBase=joinPath([pg.wiki.articlebase, article.urlString()]);
502 var p=new Previewmaker(d, urlBase, navpop);
503 return p;
504}
505
506
507// Try to imitate the way mediawiki generates HTML anchors from section titles
508function anchorize(d, anch) {
509 if (!anch) { return d; }
510 var anchRe=RegExp('(?:=+\\s*' + literalizeRegex(anch).replace(/[_ ]/g, '[_ ]') + '\\s*=+|\\{\\{\\s*'+getValueOf('popupAnchorRegexp')+'\\s*(?:\\|[^|}]*)*?\\s*'+literalizeRegex(anch)+'\\s*(?:\\|[^}]*)?}})');
511 var match=d.match(anchRe);
512 if(match && match.length > 0 && match[0]) { return d.substring(d.indexOf(match[0])); }
513
514 // now try to deal with == foo [[bar|baz]] boom == -> #foo_baz_boom
515 var lines=d.split('the');
516 for (var i=0; i<lines.length; ++i) {
517 lines[i]=lines[i].replace(RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{2}', 'g'), '$2')
518 .replace(/'''([^'])/g, '$1').replace(RegExp("''([^'])", 'g'), '$1');
519 if (lines[i].match(anchRe)) {
520 return d.split('the').slice(i).join('the').replace(RegExp('^[^=]*'), '');
521 }
522 }
523 return d;
524}
525
526function killPopup() {
527 if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); }
528 if (!pg) { return; }
529 if (pg.current.link && pg.current.link.navpopup) { pg.current.link.navpopup.banish(); }
530 pg.current.link=null;
531 abortAllDownloads();
532 if (pg.timer.checkPopupPosition) {
533 clearInterval(pg.timer.checkPopupPosition);
534 pg.timer.checkPopupPosition=null;
535 }
536 return true; // preserve default action
537}
538// ENDFILE: actions.js
539// STARTFILE: domdrag.js
540/**
541 @fileoverview
542 The {@link Drag} object, which enables objects to be dragged around.
543
544 <pre>
545 *************************************************
546 dom-drag.js
547 09.25.2001
548 www.youngpup.net
549 **************************************************
550 10.28.2001 - fixed minor bug where events
551 sometimes fired off the handle, not the root.
552 *************************************************
553 Pared down, some hooks added by [[User:Lupin]]
554
555 Copyright Aaron Boodman.
556 Saying stupid things daily since March 2001.
557 </pre>
558*/
559
560/**
561 Creates a new Drag object. This is used to make various DOM elements draggable.
562 @constructor
563*/
564function Drag () {
565 /**
566 Condition to determine whether or not to drag. This function should take one parameter, an Event.
567 To disable this, set it to <code>null</code>.
568 @type Function
569 */
570 this.startCondition = null;
571 /**
572 Hook to be run when the drag finishes. This is passed the final coordinates of
573 the dragged object (two integers, x and y). To disables this, set it to <code>null</code>.
574 @type Function
575 */
576 this.endHook = null;
577}
578
579/**
580 Gets an event in a cross-browser manner.
581 @param {Event} e
582 @private
583*/
584Drag.prototype.fixE = function(e) {
585 if (typeof e == 'undefined') { e = window.event; }
586 if (typeof e.layerX == 'undefined') { e.layerX = e.offsetX; }
587 if (typeof e.layerY == 'undefined') { e.layerY = e.offsetY; }
588 return e;
589};
590/**
591 Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by.
592 @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged.
593 @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted.
594*/
595Drag.prototype.init = function(o, oRoot) {
596 var dragObj = this;
597 this.obj = o;
598 o.onmousedown = function(e) { dragObj.start.apply( dragObj, [e]); };
599 o.dragging = false;
600 o.popups_draggable = true;
601 o.hmode = true;
602 o.vmode = true;
603
604 o.root = oRoot ? oRoot : o ;
605
606 if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left = "0px"; }
607 if (isNaN(parseInt(o.root.style.top, 10))) { o.root.style.top = "0px"; }
608
609 o.root.onthisStart = function(){};
610 o.root.onthisEnd = function(){};
611 o.root.onthis = function(){};
612};
613
614/**
615 Starts the drag.
616 @private
617 @param {Event} e
618*/
619Drag.prototype.start = function(e) {
620 var o = this.obj; // = this;
621 e = this.fixE(e);
622 if (this.startCondition && !this.startCondition(e)) { return; }
623 var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10);
624 var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10);
625 o.root.onthisStart(x, y);
626
627 o.lastMouseX = e.clientX;
628 o.lastMouseY = e.clientY;
629
630 var dragObj = this;
631 o.onmousemoveDefault = document.onmousemove;
632 o.dragging = true;
633 document.onmousemove = function(e) { dragObj.drag.apply( dragObj, [e] ); };
634 document.onmouseup = function(e) { dragObj.end.apply( dragObj, [e] ); };
635 return false;
636};
637/**
638 Does the drag.
639 @param {Event} e
640 @private
641*/
642Drag.prototype.drag = function(e) {
643 e = this.fixE(e);
644 var o = this.obj;
645
646 var ey = e.clientY;
647 var ex = e.clientX;
648 var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10);
649 var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10 );
650 var nx, ny;
651
652 nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
653 ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));
654
655 this.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
656 this.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
657 this.obj.lastMouseX = ex;
658 this.obj.lastMouseY = ey;
659
660 this.obj.root.onthis(nx, ny);
661 return false;
662};
663
664/**
665 Ends the drag.
666 @private
667*/
668Drag.prototype.end = function() {
669 document.onmousemove=this.obj.onmousemoveDefault;
670 document.onmouseup = null;
671 this.obj.dragging = false;
672 if (this.endHook) {
673 this.endHook( parseInt(this.obj.root.style[this.obj.hmode ? "left" : "right"], 10),
674 parseInt(this.obj.root.style[this.obj.vmode ? "top" : "bottom"], 10));
675 }
676};
677// ENDFILE: domdrag.js
678// STARTFILE: structures.js
679//<NOLITE>
680pg.structures.original={};
681pg.structures.original.popupLayout=function () {
682 return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle',
683 'popupData', 'popupOtherLinks',
684 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks',
685 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
686 'popupMiscTools', ['popupRedlink'],
687 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
688};
689pg.structures.original.popupRedirSpans=function () {
690 return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks',
691 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'];
692};
693pg.structures.original.popupTitle=function (x) {
694 log ('defaultstructure.popupTitle');
695 if (!getValueOf('popupNavLinks')) {
696 return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params);
697 }
698 return '';
699};
700pg.structures.original.popupTopLinks=function (x) {
701 log ('defaultstructure.popupTopLinks');
702 if (getValueOf('popupNavLinks')) { return navLinksHTML(x.article, x.hint, x.params); }
703 return '';
704};
705pg.structures.original.popupImage=function(x) {
706 log ('original.popupImage, x.article='+x.article+', x.navpop.idNumber='+x.navpop.idNumber);
707 return imageHTML(x.article, x.navpop.idNumber);
708};
709pg.structures.original.popupRedirTitle=pg.structures.original.popupTitle;
710pg.structures.original.popupRedirTopLinks=pg.structures.original.popupTopLinks;
711
712
713function copyStructure(oldStructure, newStructure) {
714 pg.structures[newStructure]={};
715 for (var prop in pg.structures[oldStructure]) {
716 pg.structures[newStructure][prop]=pg.structures[oldStructure][prop];
717 }
718}
719
720copyStructure('original', 'nostalgia');
721pg.structures.nostalgia.popupTopLinks=function(x) {
722 var str='';
723 str += '<b><<mainlink|shortcut= >></b>';
724
725 // user links
726 // contribs - log - count - email - block
727 // count only if applicable; block only if popupAdminLinks
728 str += 'if(user){<br><<contribs|shortcut=c>>';
729 str+='if(wikimedia){*<<count|shortcut=#>>}';
730 str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}';
731
732 // editing links
733 // talkpage -> edit|new - history - un|watch - article|edit
734 // other page -> edit - history - un|watch - talk|edit|new
735 var editstr='<<edit|shortcut=e>>';
736 var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' +
737 editstr + '}';
738 var historystr='<<history|shortcut=h>>';
739 var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
740
741 str += '<br>if(talk){' +
742 editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' +
743 '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
744 '}else{' + // not a talk page
745 editOldidStr + '*' + historystr + '*' + watchstr + '*' +
746 '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';
747
748 // misc links
749 str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>';
750 str += 'if(admin){<br>}else{*}<<move|shortcut=m>>';
751
752 // admin links
753 str += 'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' +
754 '<<undelete|undeleteShort>>|<<delete|shortcut=d>>}';
755 return navlinkStringToHTML(str, x.article, x.params);
756};
757pg.structures.nostalgia.popupRedirTopLinks=pg.structures.nostalgia.popupTopLinks;
758
759/** -- fancy -- **/
760copyStructure('original', 'fancy');
761pg.structures.fancy.popupTitle=function (x) {
762 return navlinkStringToHTML('<font size=+0><<mainlink>></font>',x.article,x.params);
763};
764pg.structures.fancy.popupTopLinks=function(x) {
765 var hist='<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>';
766 var watch='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
767 var move='<<move|shortcut=m|move>>';
768 return navlinkStringToHTML('if(talk){' +
769 '<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' +
770 '<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move +
771 '}else{<<edit|shortcut=e>>*' + hist +
772 '*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' +
773 '*' + watch + '*' + move+'}<br>', x.article, x.params);
774};
775pg.structures.fancy.popupOtherLinks=function(x) {
776 var admin='<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>';
777 var user='<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}';
778 user+='if(ipuser){|<<arin>>}else{*<<email|shortcut=E|'+
779 popupString('email')+'>>}if(admin){*<<block|shortcut=b>>}';
780
781 var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>';
782 return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal,
783 x.article, x.params);
784};
785pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle;
786pg.structures.fancy.popupRedirTopLinks=pg.structures.fancy.popupTopLinks;
787pg.structures.fancy.popupRedirOtherLinks=pg.structures.fancy.popupOtherLinks;
788
789
790/** -- fancy2 -- **/
791// hack for [[User:MacGyverMagic]]
792copyStructure('fancy', 'fancy2');
793pg.structures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning
794 return '<br>'+pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$','i'),'');
795};
796pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title
797 return ['popupError', 'popupImage', 'popupTitle', 'popupData', 'popupTopLinks', 'popupOtherLinks',
798 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
799 'popupMiscTools', ['popupRedlink'],
800 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
801};
802
803/** -- menus -- **/
804copyStructure('original', 'menus');
805pg.structures.menus.popupLayout=function () {
806 return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks',
807 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'],
808 'popupData', 'popupMiscTools', ['popupRedlink'],
809 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
810};
811
812pg.structures.menus.popupTopLinks = function (x, shorter) {
813 // FIXME maybe this stuff should be cached
814 var s=[];
815 var dropdiv='<div class="popup_drop">';
816 var enddiv='</div>';
817 var hist='<<history|shortcut=h>>';
818 if (!shorter) { hist = '<menurow>' + hist +
819 '|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>'; }
820 var lastedit='<<lastEdit|shortcut=/|show last edit>>';
821 var thank='if(diff){<<thank|send thanks>>}';
822 var jsHistory='<<lastContrib|last set of edits>><<sinceMe|changes since mine>>';
823 var linkshere='<<whatLinksHere|shortcut=l|what links here>>';
824 var related='<<relatedChanges|shortcut=r|related changes>>';
825 var search='<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' +
826 '|<<google|shortcut=G|web>></menurow>';
827 var watch='<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>';
828 var protect='<menurow><<unprotect|unprotectShort>>|' +
829 '<<protect|shortcut=p>>|<<protectlog|log>></menurow>';
830 var del='<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' +
831 '<<deletelog|log>></menurow>';
832 var move='<<move|shortcut=m|move page>>';
833 var nullPurge='<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>';
834 var viewOptions='<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>';
835 var editRow='if(oldid){' +
836 '<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this revision>></menurow>' +
837 '<menurow><<revert|shortcut=v>>|<<undo>></menurow>' + '}else{<<edit|shortcut=e>>}';
838 var markPatrolled='if(rcid){<<markpatrolled|mark patrolled>>}';
839 var newTopic='if(talk){<<new|shortcut=+|new topic>>}';
840 var protectDelete='if(admin){' + protect + del + '}';
841
842 if (getValueOf('popupActionsMenu')) {
843 s.push( '<<mainlink>>*' + dropdiv + menuTitle('actions'));
844 } else {
845 s.push( dropdiv + '<<mainlink>>');
846 }
847 s.push( '<menu>');
848 s.push( editRow + markPatrolled + newTopic + hist + lastedit + thank );
849 if (!shorter) { s.push(jsHistory); }
850 s.push( move + linkshere + related);
851 if (!shorter) { s.push(nullPurge + search); }
852 if (!shorter) { s.push(viewOptions); }
853 s.push('<hr />' + watch + protectDelete);
854 s.push('<hr />' +
855 'if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}' +
856 'else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>' +
857 '<<newTalk|shortcut=+|new topic>>}</menu>' + enddiv);
858
859 // user menu starts here
860 var email='<<email|shortcut=E|email user>>';
861 var contribs= 'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' +
862 'if(admin){<menurow><<deletedContribs>></menurow>}';
863
864
865 s.push('if(user){*' + dropdiv + menuTitle('user'));
866 s.push('<menu>');
867 s.push('<menurow><<userPage|shortcut=u|user page>>|<<userSpace|space>></menurow>');
868 s.push('<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' +
869 '<<newUserTalk|shortcut=+|leave comment>>');
870 if(!shorter) { s.push( 'if(ipuser){<<arin>>}else{' + email + '}' ); }
871 else { s.push( 'if(ipuser){}else{' + email + '}' ); }
872 s.push('<hr />' + contribs + '<<userlog|shortcut=L|user log>>');
873 s.push('if(wikimedia){<<count|shortcut=#|edit counter>>}');
874 s.push('if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}');
875 s.push('<<blocklog|shortcut=B|block log>>');
876 s.push('</menu>' + enddiv + '}');
877
878 // popups menu starts here
879 if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) {
880 x.navpop.hasPopupMenu=true;
881 s.push('*' + dropdiv + menuTitle('popupsMenu') + '<menu>');
882 s.push('<<togglePreviews|toggle previews>>');
883 s.push('<<purgePopups|reset>>');
884 s.push('<<disablePopups|disable>>');
885 s.push('</menu>'+enddiv);
886 }
887 return navlinkStringToHTML(s.join(''), x.article, x.params);
888};
889
890function menuTitle(s) {
891 return '<a href="#" noPopup=1>' + popupString(s) + '</a>';
892}
893
894pg.structures.menus.popupRedirTitle=pg.structures.menus.popupTitle;
895pg.structures.menus.popupRedirTopLinks=pg.structures.menus.popupTopLinks;
896
897copyStructure('menus', 'shortmenus');
898pg.structures.shortmenus.popupTopLinks=function(x) {
899 return pg.structures.menus.popupTopLinks(x,true);
900};
901pg.structures.shortmenus.popupRedirTopLinks=pg.structures.shortmenus.popupTopLinks;
902
903//</NOLITE>
904pg.structures.lite={};
905pg.structures.lite.popupLayout=function () {
906 return ['popupTitle', 'popupPreview' ];
907};
908pg.structures.lite.popupTitle=function (x) {
909 log (x.article + ': structures.lite.popupTitle');
910 //return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params);
911 return '<div><span class="popup_mainlink"><b>' + x.article.toString() + '</b></span></div>';
912};
913// ENDFILE: structures.js
914// STARTFILE: autoedit.js
915//<NOLITE>
916function substitute(data,cmdBody) {
917 // alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags);
918 var fromRe=RegExp(cmdBody.from, cmdBody.flags);
919 return data.replace(fromRe, cmdBody.to);
920}
921
922function execCmds(data, cmdList) {
923 for (var i=0; i<cmdList.length; ++i) {
924 data=cmdList[i].action(data, cmdList[i]);
925 }
926 return data;
927}
928
929function parseCmd(str) {
930 // returns a list of commands
931 if (!str.length) { return []; }
932 var p=false;
933 switch (str.charAt(0)) {
934 case 's':
935 p=parseSubstitute(str);
936 break;
937 default:
938 return false;
939 }
940 if (p) { return [p].concat(parseCmd(p.remainder)); }
941 return false;
942}
943
944function unEscape(str, sep) {
945 return str.split('\\\\').join('\\').split('\\'+sep).join(sep).split('\\n').join('\n');
946}
947
948
949function parseSubstitute(str) {
950 // takes a string like s/a/b/flags;othercmds and parses it
951
952 var from,to,flags,tmp;
953
954 if (str.length<4) { return false; }
955 var sep=str.charAt(1);
956 str=str.substring(2);
957
958 tmp=skipOver(str,sep);
959 if (tmp) { from=tmp.segment; str=tmp.remainder; }
960 else { return false; }
961
962 tmp=skipOver(str,sep);
963 if (tmp) { to=tmp.segment; str=tmp.remainder; }
964 else { return false; }
965
966 flags='';
967 if (str.length) {
968 tmp=skipOver(str,';') || skipToEnd(str, ';');
969 if (tmp) {flags=tmp.segment; str=tmp.remainder; }
970 }
971
972 return {action: substitute, from: from, to: to, flags: flags, remainder: str};
973
974}
975
976function skipOver(str,sep) {
977 var endSegment=findNext(str,sep);
978 if (endSegment<0) { return false; }
979 var segment=unEscape(str.substring(0,endSegment), sep);
980 return {segment: segment, remainder: str.substring(endSegment+1)};
981}
982
983/*eslint-disable*/
984function skipToEnd(str,sep) {
985 return {segment: str, remainder: ''};
986}
987/*eslint-enable */
988
989function findNext(str, ch) {
990 for (var i=0; i<str.length; ++i) {
991 if (str.charAt(i)=='\\') { i+=2; }
992 if (str.charAt(i)==ch) { return i; }
993 }
994 return -1;
995}
996
997function setCheckbox(param, box) {
998 var val=mw.util.getParamValue(param);
999 if (val) {
1000 switch (val) {
1001 case '1': case 'yes': case 'true':
1002 box.checked=true;
1003 break;
1004 case '0': case 'no': case 'false':
1005 box.checked=false;
1006 }
1007 }
1008}
1009
1010function autoEdit() {
1011 setupPopups( function () {
1012 if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version') ) { return false; }
1013 if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken')===autoClickToken()) {
1014 pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action'));
1015 }
1016 if (!document.editform) { return false; }
1017 if (autoEdit.alreadyRan) { return false; }
1018 autoEdit.alreadyRan=true;
1019 var cmdString=mw.util.getParamValue('autoedit');
1020 if (cmdString) {
1021 try {
1022 var editbox=document.editform.wpTextbox1;
1023 var cmdList=parseCmd(cmdString);
1024 var input=editbox.value;
1025 var output=execCmds(input, cmdList);
1026 editbox.value=output;
1027 } catch (dang) { return; }
1028 // wikEd user script compatibility
1029 if (typeof(wikEdUseWikEd) != 'undefined') {
1030 if (wikEdUseWikEd === true) {
1031 WikEdUpdateFrame();
1032 }
1033 }
1034 }
1035 setCheckbox('autominor', document.editform.wpMinoredit);
1036 setCheckbox('autowatch', document.editform.wpWatchthis);
1037
1038 var rvid = mw.util.getParamValue('autorv');
1039 if (rvid) {
1040 var url=pg.wiki.apiwikibase + '?action=query&format=json&formatversion=2&prop=revisions&revids='+rvid;
1041 startDownload(url, null, autoEdit2);
1042 } else { autoEdit2(); }
1043 } );
1044}
1045
1046function autoEdit2(d) {
1047 var summary=mw.util.getParamValue('autosummary');
1048 var summaryprompt=mw.util.getParamValue('autosummaryprompt');
1049 var summarynotice='';
1050 if (d && d.data && mw.util.getParamValue('autorv')) {
1051 var s = getRvSummary(summary, d.data);
1052 if (s === false) {
1053 summaryprompt=true;
1054 summarynotice=popupString('Failed to get revision information, please edit manually.\n\n');
1055 summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']);
1056 } else { summary = s; }
1057 }
1058 if (summaryprompt) {
1059 var txt= summarynotice +
1060 popupString('Enter a non-empty edit summary or press cancel to abort');
1061 var response=prompt(txt, summary);
1062 if (response) { summary=response; }
1063 else { return; }
1064 }
1065 if (summary) { document.editform.wpSummary.value=summary; }
1066 // Attempt to avoid possible premature clicking of the save button
1067 // (maybe delays in updates to the DOM are to blame?? or a red herring)
1068 setTimeout(autoEdit3, 100);
1069}
1070
1071function autoClickToken() {
1072 return mw.user.sessionId();
1073}
1074
1075function autoEdit3() {
1076 if( mw.util.getParamValue('actoken') != autoClickToken()) { return; }
1077
1078 var btn=mw.util.getParamValue('autoclick');
1079 if (btn) {
1080 if (document.editform && document.editform[btn]) {
1081 var button=document.editform[btn];
1082 var msg=tprintf('The %s button has been automatically clicked. Please wait for the next page to load.',
1083 [ button.value ]);
1084 bannerMessage(msg);
1085 document.title='('+document.title+')';
1086 button.click();
1087 } else {
1088 alert(tprintf('Could not find button %s. Please check the settings in your javascript file.',
1089 [ btn ]));
1090 }
1091 }
1092}
1093
1094function bannerMessage(s) {
1095 var headings=document.getElementsByTagName('h1');
1096 if (headings) {
1097 var div=document.createElement('div');
1098 div.innerHTML='<font size=+1><b>' + s + '</b></font>';
1099 headings[0].parentNode.insertBefore(div, headings[0]);
1100 }
1101}
1102
1103function getRvSummary(template, json) {
1104 try {
1105 var o=getJsObj(json);
1106 var edit = anyChild(o.query.pages).revisions[0];
1107 var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, '');
1108 return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user ]);
1109 } catch (badness) {
1110 return false;
1111 }
1112}
1113
1114//</NOLITE>
1115// ENDFILE: autoedit.js
1116// STARTFILE: downloader.js
1117/**
1118 @fileoverview
1119 {@link Downloader}, a xmlhttprequest wrapper, and helper functions.
1120*/
1121
1122/**
1123 Creates a new Downloader
1124 @constructor
1125 @class The Downloader class. Create a new instance of this class to download stuff.
1126 @param {String} url The url to download. This can be omitted and supplied later.
1127*/
1128function Downloader(url) {
1129 if (typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); }
1130 /**
1131 The url to download
1132 @type String
1133 */
1134 this.url = url;
1135 /**
1136 A universally unique ID number
1137 @type integer
1138 */
1139 this.id=null;
1140 /**
1141 Modification date, to be culled from the incoming headers
1142 @type Date
1143 @private
1144 */
1145 this.lastModified = null;
1146 /**
1147 What to do when the download completes successfully
1148 @type Function
1149 @private
1150 */
1151 this.callbackFunction = null;
1152 /**
1153 What to do on failure
1154 @type Function
1155 @private
1156 */
1157 this.onFailure = null;
1158 /**
1159 Flag set on <code>abort</code>
1160 @type boolean
1161 */
1162 this.aborted = false;
1163 /**
1164 HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for possibilities.
1165 @type String
1166 */
1167 this.method='GET';
1168 /**
1169 Async flag.
1170 @type boolean
1171 */
1172 this.async=true;
1173}
1174
1175new Downloader();
1176
1177/** Submits the http request. */
1178Downloader.prototype.send = function (x) {
1179 if (!this.http) { return null; }
1180 return this.http.send(x);
1181};
1182/** Aborts the download, setting the <code>aborted</code> field to true. */
1183Downloader.prototype.abort = function () {
1184 if (!this.http) { return null; }
1185 this.aborted=true;
1186 return this.http.abort();
1187};
1188/** Returns the downloaded data. */
1189Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;};
1190/** Prepares the download. */
1191Downloader.prototype.setTarget = function () {
1192 if (!this.http) { return null; }
1193 this.http.open(this.method, this.url, this.async);
1194 this.http.setRequestHeader( 'Api-User-Agent', pg.misc.userAgent );
1195};
1196/** Gets the state of the download. */
1197Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;};
1198
1199pg.misc.downloadsInProgress = { };
1200
1201/** Starts the download.
1202 Note that setTarget {@link Downloader#setTarget} must be run first
1203*/
1204Downloader.prototype.start=function () {
1205 if (!this.http) { return; }
1206 pg.misc.downloadsInProgress[this.id] = this;
1207 this.http.send(null);
1208};
1209
1210/** Gets the 'Last-Modified' date from the download headers.
1211 Should be run after the download completes.
1212 Returns <code>null</code> on failure.
1213 @return {Date}
1214*/
1215Downloader.prototype.getLastModifiedDate=function () {
1216 if(!this.http) { return null; }
1217 var lastmod=null;
1218 try {
1219 lastmod=this.http.getResponseHeader('Last-Modified');
1220 } catch (err) {}
1221 if (lastmod) { return new Date(lastmod); }
1222 return null;
1223};
1224
1225/** Sets the callback function.
1226 @param {Function} f callback function, called as <code>f(this)</code> on success
1227*/
1228Downloader.prototype.setCallback = function (f) {
1229 if(!this.http) { return; }
1230 this.http.onreadystatechange = f;
1231};
1232
1233Downloader.prototype.getStatus = function() { if (!this.http) { return null; } return this.http.status; };
1234
1235//////////////////////////////////////////////////
1236// helper functions
1237
1238/** Creates a new {@link Downloader} and prepares it for action.
1239 @param {String} url The url to download
1240 @param {integer} id The ID of the {@link Downloader} object
1241 @param {Function} callback The callback function invoked on success
1242 @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
1243*/
1244function newDownload(url, id, callback, onfailure) {
1245 var d=new Downloader(url);
1246 if (!d.http) { return 'ohdear'; }
1247 d.id=id;
1248 d.setTarget();
1249 if (!onfailure) {
1250 onfailure=2;
1251 }
1252 var f = function () {
1253 if (d.getReadyState() == 4) {
1254 delete pg.misc.downloadsInProgress[this.id];
1255 try {
1256 if ( d.getStatus() == 200 ) {
1257 d.data=d.getData();
1258 d.lastModified=d.getLastModifiedDate();
1259 callback(d);
1260 } else if (typeof onfailure == typeof 1) {
1261 if (onfailure > 0) {
1262 // retry
1263 newDownload(url, id, callback, onfailure - 1);
1264 }
1265 } else if ($.isFunction(onfailure)) {
1266 onfailure(d,url,id,callback);
1267 }
1268 } catch (somerr) { /* ignore it */ }
1269 }
1270 };
1271 d.setCallback(f);
1272 return d;
1273}
1274/** Simulates a download from cached data.
1275 The supplied data is put into a {@link Downloader} as if it had downloaded it.
1276 @param {String} url The url.
1277 @param {integer} id The ID.
1278 @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>,
1279 where <code>d</code> is the new {@link Downloader}.
1280 @param {String} data The (cached) data.
1281 @param {Date} lastModified The (cached) last modified date.
1282*/
1283function fakeDownload(url, id, callback, data, lastModified, owner) {
1284 var d=newDownload(url,callback);
1285 d.owner=owner;
1286 d.id=id; d.data=data;
1287 d.lastModified=lastModified;
1288 return callback(d);
1289}
1290
1291/**
1292 Starts a download.
1293 @param {String} url The url to download
1294 @param {integer} id The ID of the {@link Downloader} object
1295 @param {Function} callback The callback function invoked on success
1296 @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
1297*/
1298function startDownload(url, id, callback) {
1299 var d=newDownload(url, id, callback);
1300 if (typeof d == typeof '' ) { return d; }
1301 d.start();
1302 return d;
1303}
1304
1305/**
1306 Aborts all downloads which have been started.
1307*/
1308function abortAllDownloads() {
1309 for ( var x in pg.misc.downloadsInProgress ) {
1310 try {
1311 pg.misc.downloadsInProgress[x].aborted=true;
1312 pg.misc.downloadsInProgress[x].abort();
1313 delete pg.misc.downloadsInProgress[x];
1314 } catch (e) {}
1315 }
1316}
1317// ENDFILE: downloader.js
1318// STARTFILE: livepreview.js
1319// TODO: location is often not correct (eg relative links in previews)
1320// NOTE: removed md5 and image and math parsing. was broken, lots of bytes.
1321/**
1322 * InstaView - a Mediawiki to HTML converter in JavaScript
1323 * Version 0.6.1
1324 * Copyright (C) Pedro Fayolle 2005-2006
1325 * https://en.wikipedia.org/wiki/User:Pilaf
1326 * Distributed under the BSD license
1327 *
1328 * Changelog:
1329 *
1330 * 0.6.1
1331 * - Fixed problem caused by \r characters
1332 * - Improved inline formatting parser
1333 *
1334 * 0.6
1335 * - Changed name to InstaView
1336 * - Some major code reorganizations and factored out some common functions
1337 * - Handled conversion of relative links (i.e. [[/foo]])
1338 * - Fixed misrendering of adjacent definition list items
1339 * - Fixed bug in table headings handling
1340 * - Changed date format in signatures to reflect Mediawiki's
1341 * - Fixed handling of [[:Image:...]]
1342 * - Updated MD5 function (hopefully it will work with UTF-8)
1343 * - Fixed bug in handling of links inside images
1344 *
1345 * To do:
1346 * - Better support for math tags
1347 * - Full support for <nowiki>
1348 * - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and bullet-proof)
1349 * - Support for templates (through AJAX)
1350 * - Support for coloured links (AJAX)
1351 */
1352
1353
1354var Insta = {};
1355
1356function setupLivePreview() {
1357
1358 // options
1359 Insta.conf =
1360 {
1361 baseUrl: '',
1362
1363 user: {},
1364
1365 wiki: {
1366 lang: pg.wiki.lang,
1367 interwiki: pg.wiki.interwiki,
1368 default_thumb_width: 180
1369 },
1370
1371 paths: {
1372 articles: pg.wiki.articlePath + '/',
1373 // Only used for Insta previews with images. (not in popups)
1374 math: '/math/',
1375 images: '//upload.wikimedia.org/wikipedia/en/', // FIXME getImageUrlStart(pg.wiki.hostname),
1376 images_fallback: '//upload.wikimedia.org/wikipedia/commons/',
1377 },
1378
1379 locale: {
1380 user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId],
1381 image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId],
1382 category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId],
1383 // shouldn't be used in popup previews, i think
1384 months: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
1385 }
1386 };
1387
1388 // options with default values or backreferences
1389 Insta.conf.user.name = Insta.conf.user.name || 'Wikipedian';
1390 Insta.conf.user.signature = '[['+Insta.conf.locale.user+':'+Insta.conf.user.name+'|'+Insta.conf.user.name+']]';
1391 //Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/';
1392
1393 // define constants
1394 Insta.BLOCK_IMAGE = new RegExp('^\\[\\[(?:File|Image|'+Insta.conf.locale.image+
1395 '):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)', 'i');
1396
1397}
1398
1399
1400Insta.dump = function(from, to)
1401{
1402 if (typeof from == 'string') { from = document.getElementById(from); }
1403 if (typeof to == 'string') { to = document.getElementById(to); }
1404 to.innerHTML = this.convert(from.value);
1405};
1406
1407Insta.convert = function(wiki)
1408{
1409 var ll = (typeof wiki == 'string')? wiki.replace(/\r/g,'').split(/\n/): wiki, // lines of wikicode
1410 o = '', // output
1411 p = 0, // para flag
1412 $r; // result of passing a regexp to $()
1413
1414 // some shorthands
1415 function remain() { return ll.length; }
1416 function sh() { return ll.shift(); } // shift
1417 function ps(s) { o += s; } // push
1418
1419 // similar to C's printf, uses ? as placeholders, ?? to escape question marks
1420 function f()
1421 {
1422 var i=1, a=arguments, f=a[0], o='', c, p;
1423 for (; i<a.length; i++) {
1424 if ((p=f.indexOf('?'))+1) {
1425 // allow character escaping
1426 i -= c = f.charAt(p+1)=='?' ? 1 : 0;
1427 o += f.substring(0,p) + (c ? '?' : a[i]);
1428 f = f.substr(p+1+c);
1429 } else { break; }
1430 }
1431 return o+f;
1432 }
1433
1434 function html_entities(s) {
1435 return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");
1436 }
1437
1438 // Wiki text parsing to html is a nightmare.
1439 // The below functions deliberately don't escape the ampersand since this would make it more difficult,
1440 // and we don't absolutely need to for how we need it.
1441 // This means that any unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML.
1442 // Browsers should all be able to handle it though.
1443 // We also escape significant wikimarkup characters to prevent further matching on the processed text
1444 function htmlescape_text(s) {
1445 return s.replace(/</g,"<").replace(/>/g,">").replace(/:/g,":").replace(/\[/g,"[").replace(/]/g,"]");
1446 }
1447 function htmlescape_attr(s) {
1448 return htmlescape_text(s).replace(/'/g,"'").replace(/"/g,""");
1449 }
1450
1451 // return the first non matching character position between two strings
1452 function str_imatch(a, b)
1453 {
1454 for (var i=0, l=Math.min(a.length, b.length); i<l; i++) {
1455 if (a.charAt(i)!=b.charAt(i)) { break; }
1456 }
1457 return i;
1458 }
1459
1460 // compare current line against a string or regexp
1461 // if passed a string it will compare only the first string.length characters
1462 // if passed a regexp the result is stored in $r
1463 function $(c) { return (typeof c == 'string') ? (ll[0].substr(0,c.length)==c) : ($r = ll[0].match(c)); }
1464
1465 function $$(c) { return ll[0]==c; } // compare current line against a string
1466 function _(p) { return ll[0].charAt(p); } // return char at pos p
1467
1468 function endl(s) { ps(s); sh(); }
1469
1470 function parse_list()
1471 {
1472 var prev='';
1473
1474 while (remain() && $(/^([*#:;]+)(.*)$/)) {
1475
1476 var l_match = $r;
1477
1478 sh();
1479
1480 var ipos = str_imatch(prev, l_match[1]);
1481
1482 // close uncontinued lists
1483 for (var prevPos=prev.length-1; prevPos >= ipos; prevPos--) {
1484
1485 var pi = prev.charAt(prevPos);
1486
1487 if (pi=='*') { ps('</ul>'); }
1488 else if (pi=='#') { ps('</ol>'); }
1489 // close a dl only if the new item is not a dl item (:, ; or empty)
1490 else if($.inArray(l_match[1].charAt(prevPos), ['','*','#'])) { ps('</dl>'); }
1491 }
1492
1493 // open new lists
1494 for (var matchPos=ipos; matchPos<l_match[1].length; matchPos++) {
1495
1496 var li = l_match[1].charAt(matchPos);
1497
1498 if (li=='*') { ps('<ul>'); }
1499 else if (li=='#') { ps('<ol>'); }
1500 // open a new dl only if the prev item is not a dl item (:, ; or empty)
1501 else if ($.inArray(prev.charAt(matchPos), ['','*','#'])) { ps('<dl>'); }
1502 }
1503
1504 switch (l_match[1].charAt(l_match[1].length-1)) {
1505
1506 case '*': case '#':
1507 ps('<li>' + parse_inline_nowiki(l_match[2]));
1508 break;
1509
1510 case ';':
1511 ps('<dt>');
1512
1513 var dt_match = l_match[2].match(/(.*?)(:.*?)$/);
1514
1515 // handle ;dt :dd format
1516 if (dt_match) {
1517 ps(parse_inline_nowiki(dt_match[1]));
1518 ll.unshift(dt_match[2]);
1519
1520 } else ps(parse_inline_nowiki(l_match[2]));
1521 break;
1522
1523 case ':':
1524 ps('<dd>' + parse_inline_nowiki(l_match[2]));
1525 }
1526
1527 prev=l_match[1];
1528 }
1529
1530 // close remaining lists
1531 for (var i=prev.length-1; i>=0; i--) {
1532 ps(f('</?>', (prev.charAt(i)=='*')? 'ul': ((prev.charAt(i)=='#')? 'ol': 'dl')));
1533 }
1534 }
1535
1536 function parse_table()
1537 {
1538 endl(f('<table>', $(/^\{\|( .*)$/)? $r[1]: ''));
1539
1540 for (;remain();) if ($('|')) switch (_(1)) {
1541 case '}':
1542 endl('</table>');
1543 return;
1544 case '-':
1545 endl(f('<tr>', $(/\|-*(.*)/)[1]));
1546 break;
1547 default:
1548 parse_table_data();
1549 }
1550 else if ($('!')) { parse_table_data(); }
1551 else { sh(); }
1552 }
1553
1554 function parse_table_data()
1555 {
1556 var td_line, match_i;
1557
1558 // 1: "|+", '|' or '+'
1559 // 2: ??
1560 // 3: attributes ??
1561 // TODO: finish commenting this regexp
1562 var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/);
1563
1564 if (td_match[1] == '|+') ps('<caption');
1565 else ps('<t' + ((td_match[1]=='|')?'d':'h'));
1566
1567 if (typeof td_match[3] != 'undefined') {
1568
1569 //ps(' ' + td_match[3])
1570 match_i = 4;
1571
1572 } else match_i = 2;
1573
1574 ps('>');
1575
1576 if (td_match[1] != '|+') {
1577
1578 // use || or !! as a cell separator depending on context
1579 // NOTE: when split() is passed a regexp make sure to use non-capturing brackets
1580 td_line = td_match[match_i].split((td_match[1] == '|')? '||': /(?:\|\||!!)/);
1581
1582 ps(parse_inline_nowiki(td_line.shift()));
1583
1584 while (td_line.length) ll.unshift(td_match[1] + td_line.pop());
1585
1586 } else ps(td_match[match_i]);
1587
1588 var tc = 0, td = [];
1589
1590 while (remain()) {
1591 td.push(sh());
1592 if ($('|')) {
1593 if (!tc) break; // we're at the outer-most level (no nested tables), skip to td parse
1594 else if (_(1)=='}') tc--;
1595 }
1596 else if (!tc && $('!')) break;
1597 else if ($('{|')) tc++;
1598 }
1599
1600 if (td.length) ps(Insta.convert(td));
1601 }
1602
1603 function parse_pre()
1604 {
1605 ps('<pre>');
1606 do {
1607 endl(parse_inline_nowiki(ll[0].substring(1)) + "\n");
1608 } while (remain() && $(' '));
1609 ps('</pre>');
1610 }
1611
1612 function parse_block_image()
1613 {
1614 ps(parse_image(sh()));
1615 }
1616
1617 function parse_image(str)
1618 {
1619//<NOLITE>
1620 // get what's in between "[[Image:" and "]]"
1621 var tag = str.substring(str.indexOf(':') + 1, str.length - 2);
1622 /* eslint-disable no-unused-vars */
1623 var width;
1624 var attr = [], filename, caption = '';
1625 var thumb=0, frame=0, center=0;
1626 var align='';
1627 /* eslint-enable no-unused-vars */
1628
1629 if (tag.match(/\|/)) {
1630 // manage nested links
1631 var nesting = 0;
1632 var last_attr;
1633 for (var i = tag.length-1; i > 0; i--) {
1634 if (tag.charAt(i) == '|' && !nesting) {
1635 last_attr = tag.substr(i+1);
1636 tag = tag.substring(0, i);
1637 break;
1638 } else switch (tag.substr(i-1, 2)) {
1639 case ']]':
1640 nesting++;
1641 i--;
1642 break;
1643 case '[[':
1644 nesting--;
1645 i--;
1646 }
1647 }
1648
1649 attr = tag.split(/\s*\|\s*/);
1650 attr.push(last_attr);
1651 filename = attr.shift();
1652
1653 var w_match;
1654
1655 for (;attr.length; attr.shift()) {
1656 w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/);
1657 if (w_match) width = w_match[1];
1658 else switch(attr[0]) {
1659 case 'thumb':
1660 case 'thumbnail':
1661 thumb=true;
1662 frame=true;
1663 break;
1664 case 'frame':
1665 frame=true;
1666 break;
1667 case 'none':
1668 case 'right':
1669 case 'left':
1670 center=false;
1671 align=attr[0];
1672 break;
1673 case 'center':
1674 center=true;
1675 align='none';
1676 break;
1677 default:
1678 if (attr.length == 1) caption = attr[0];
1679 }
1680 }
1681
1682 } else filename = tag;
1683
1684 return '';
1685//</NOLITE>
1686 }
1687
1688 function parse_inline_nowiki(str)
1689 {
1690 var start, lastend=0;
1691 var substart=0, nestlev=0, open, close, subloop;
1692 var html='';
1693
1694 while (-1 != (start = str.indexOf('<nowiki>', substart))) {
1695 html += parse_inline_wiki(str.substring(lastend, start));
1696 start += 8;
1697 substart = start;
1698 subloop = true;
1699 do {
1700 open = str.indexOf('<nowiki>', substart);
1701 close = str.indexOf('</nowiki>', substart);
1702 if (close<=open || open==-1) {
1703 if (close==-1) {
1704 return html + html_entities(str.substr(start));
1705 }
1706 substart = close+9;
1707 if (nestlev) {
1708 nestlev--;
1709 } else {
1710 lastend = substart;
1711 html += html_entities(str.substring(start, lastend-9));
1712 subloop = false;
1713 }
1714 } else {
1715 substart = open+8;
1716 nestlev++;
1717 }
1718 } while (subloop);
1719 }
1720
1721 return html + parse_inline_wiki(str.substr(lastend));
1722 }
1723
1724 function parse_inline_images(str)
1725 {
1726//<NOLITE>
1727 var start, substart=0, nestlev=0;
1728 var loop, close, open, wiki, html;
1729
1730 while (-1 != (start=str.indexOf('[[', substart))) {
1731 if(str.substr(start+2).match(RegExp('^(Image|File|' + Insta.conf.locale.image + '):','i'))) {
1732 loop=true;
1733 substart=start;
1734 do {
1735 substart+=2;
1736 close=str.indexOf(']]',substart);
1737 open=str.indexOf('[[',substart);
1738 if (close<=open||open==-1) {
1739 if (close==-1) return str;
1740 substart=close;
1741 if (nestlev) {
1742 nestlev--;
1743 } else {
1744 wiki=str.substring(start,close+2);
1745 html=parse_image(wiki);
1746 str=str.replace(wiki,html);
1747 substart=start+html.length;
1748 loop=false;
1749 }
1750 } else {
1751 substart=open;
1752 nestlev++;
1753 }
1754 } while (loop);
1755
1756 } else break;
1757 }
1758
1759//</NOLITE>
1760 return str;
1761 }
1762
1763 // the output of this function doesn't respect the FILO structure of HTML
1764 // but since most browsers can handle it I'll save myself the hassle
1765 function parse_inline_formatting(str)
1766 {
1767 var em,st,i,li,o='';
1768 while ((i=str.indexOf("''",li))+1) {
1769 o += str.substring(li,i);
1770 li=i+2;
1771 if (str.charAt(i+2)=="'") {
1772 li++;
1773 st=!st;
1774 o+=st?'<strong>':'</strong>';
1775 } else {
1776 em=!em;
1777 o+=em?'<em>':'</em>';
1778 }
1779 }
1780 return o+str.substr(li);
1781 }
1782
1783 function parse_inline_wiki(str)
1784 {
1785 str = parse_inline_images(str);
1786 str = parse_inline_formatting(str);
1787
1788 // math
1789 str = str.replace(/<(?:)math>(.*?)<\/math>/ig, '');
1790
1791 // Build a Mediawiki-formatted date string
1792 var date = new Date();
1793 var minutes = date.getUTCMinutes();
1794 if (minutes < 10) minutes = '0' + minutes;
1795 date = f("?:?, ? ? ? (UTC)", date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear());
1796
1797 // text formatting
1798 return str.
1799 // signatures
1800 replace(/~{5}(?!~)/g, date).
1801 replace(/~{4}(?!~)/g, Insta.conf.user.name+' '+date).
1802 replace(/~{3}(?!~)/g, Insta.conf.user.name).
1803
1804 // [[:Category:...]], [[:Image:...]], etc...
1805 replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):[^|]*?)\\]\\](\\w*)','gi'), function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));}).
1806 // remove straight category and interwiki tags
1807 replace(RegExp('\\[\\[(?:'+Insta.conf.locale.category+'|'+Insta.conf.wiki.interwiki+'):.*?\\]\\]','gi'),'').
1808
1809 // [[:Category:...|Links]], [[:Image:...|Links]], etc...
1810 replace(RegExp('\\[\\[:((?:'+Insta.conf.locale.category+'|Image|File|'+Insta.conf.locale.image+'|'+Insta.conf.wiki.interwiki+'):.*?)\\|([^\\]]+?)\\]\\](\\w*)','gi'), function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));}).
1811
1812 // [[/Relative links]]
1813 replace(/\[\[(\/[^|]*?)\]\]/g, function($0,$1){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1)); }).
1814
1815 // [[/Replaced|Relative links]]
1816 replace(/\[\[(\/.*?)\|(.+?)\]\]/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2)); }).
1817
1818 // [[Common links]]
1819 replace(/\[\[([^[|]*?)\]\](\w*)/g, function($0,$1,$2){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2)); }).
1820
1821 // [[Replaced|Links]]
1822 replace(/\[\[([^[]*?)\|([^\]]+?)\]\](\w*)/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3)); }).
1823
1824 // [[Stripped:Namespace|Namespace]]
1825 replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|\]\]/g, function($0,$1,$2,$3){return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1) + htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($2)); }).
1826
1827 // External links
1828 replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)\]/g, function($0,$1,$2,$3,$4){return f("<a class='external' href='?:?'>?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4)); }).
1829 replace(/\[http:\/\/(.*?)\]/g, function($0,$1){return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1)); }).
1830 replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)\]/g, function($0,$1,$2,$3){return f("<a class='external' href='?:?'>?:?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($1), htmlescape_text($2) + htmlescape_text($3)); }).
1831 replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^.,!?;: $])/g, function($0,$1,$2,$3,$4){return f("?<a class='external' href='?:?'>?:?</a>", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4)); }).
1832
1833 replace('__NOTOC__','').
1834 replace('__NOEDITSECTION__','');
1835 }
1836
1837 // begin parsing
1838 for (;remain();) if ($(/^(={1,6})(.*)\1(.*)$/)) {
1839 p=0;
1840 endl(f('<h?>?</h?>?', $r[1].length, parse_inline_nowiki($r[2]), $r[1].length, $r[3]));
1841
1842 } else if ($(/^[*#:;]/)) {
1843 p=0;
1844 parse_list();
1845
1846 } else if ($(' ')) {
1847 p=0;
1848 parse_pre();
1849
1850 } else if ($('{|')) {
1851 p=0;
1852 parse_table();
1853
1854 } else if ($(/^----+$/)) {
1855 p=0;
1856 endl('<hr />');
1857
1858 } else if ($(Insta.BLOCK_IMAGE)) {
1859 p=0;
1860 parse_block_image();
1861
1862 } else {
1863
1864 // handle paragraphs
1865 if ($$('')) {
1866 p = (remain()>1 && ll[1]===(''));
1867 if (p) endl('<p><br>');
1868 } else {
1869 if(!p) {
1870 ps('<p>');
1871 p=1;
1872 }
1873 ps(parse_inline_nowiki(ll[0]) + ' ');
1874 }
1875
1876 sh();
1877 }
1878
1879 return o;
1880};
1881
1882function wiki2html(txt,baseurl) {
1883 Insta.conf.baseUrl=baseurl;
1884 return Insta.convert(txt);
1885}
1886// ENDFILE: livepreview.js
1887// STARTFILE: pageinfo.js
1888//<NOLITE>
1889function popupFilterPageSize(data) {
1890 return formatBytes(data.length);
1891}
1892
1893function popupFilterCountLinks(data) {
1894 var num=countLinks(data);
1895 return String(num) + ' ' + ((num!=1)?popupString('wikiLinks'):popupString('wikiLink'));
1896}
1897
1898function popupFilterCountImages(data) {
1899 var num=countImages(data);
1900 return String(num) + ' ' + ((num!=1)?popupString('images'):popupString('image'));
1901}
1902
1903function popupFilterCountCategories(data) {
1904 var num=countCategories(data);
1905 return String(num) + ' ' + ((num!=1)?popupString('categories'):popupString('category'));
1906}
1907
1908
1909function popupFilterLastModified(data,download) {
1910 var lastmod=download.lastModified;
1911 var now=new Date();
1912 var age=now-lastmod;
1913 if (lastmod && getValueOf('popupLastModified')) {
1914 return (tprintf('%s old', [formatAge(age)])).replace(RegExp(' ','g'), ' ');
1915 }
1916 return '';
1917}
1918
1919function formatAge(age) {
1920 // coerce into a number
1921 var a=0+age, aa=a;
1922
1923 var seclen = 1000;
1924 var minlen = 60*seclen;
1925 var hourlen = 60*minlen;
1926 var daylen = 24*hourlen;
1927 var weeklen = 7*daylen;
1928
1929 var numweeks = (a-a%weeklen)/weeklen; a = a-numweeks*weeklen; var sweeks = addunit(numweeks, 'week');
1930 var numdays = (a-a%daylen)/daylen; a = a-numdays*daylen; var sdays = addunit(numdays, 'day');
1931 var numhours = (a-a%hourlen)/hourlen; a = a-numhours*hourlen; var shours = addunit(numhours,'hour');
1932 var nummins = (a-a%minlen)/minlen; a = a-nummins*minlen; var smins = addunit(nummins, 'minute');
1933 var numsecs = (a-a%seclen)/seclen; a = a-numsecs*seclen; var ssecs = addunit(numsecs, 'second');
1934
1935 if (aa > 4*weeklen) { return sweeks; }
1936 if (aa > weeklen) { return sweeks + ' ' + sdays; }
1937 if (aa > daylen) { return sdays + ' ' + shours; }
1938 if (aa > 6*hourlen) { return shours; }
1939 if (aa > hourlen) { return shours + ' ' + smins; }
1940 if (aa > 10*minlen) { return smins; }
1941 if (aa > minlen) { return smins + ' ' + ssecs; }
1942 return ssecs;
1943}
1944
1945function addunit(num,str) { return '' + num + ' ' + ((num!=1) ? popupString(str+'s') : popupString(str)) ;}
1946
1947function runPopupFilters(list, data, download) {
1948 var ret=[];
1949 for (var i=0; i<list.length; ++i) {
1950 if (list[i] && typeof list[i] == 'function') {
1951 var s=list[i](data, download, download.owner.article);
1952 if (s) { ret.push(s); }
1953 }
1954 }
1955 return ret;
1956}
1957
1958function getPageInfo(data, download) {
1959 if (!data || data.length === 0) { return popupString('Empty page'); }
1960
1961 var popupFilters=getValueOf('popupFilters') || [];
1962 var extraPopupFilters = getValueOf('extraPopupFilters') || [];
1963 var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download);
1964
1965 var pageInfo=pageInfoArray.join(', ');
1966 if (pageInfo !== '' ) { pageInfo = upcaseFirst(pageInfo); }
1967 return pageInfo;
1968}
1969
1970
1971// this could be improved!
1972function countLinks(wikiText) { return wikiText.split('[[').length - 1; }
1973
1974// if N = # matches, n = # brackets, then
1975// String.parenSplit(regex) intersperses the N+1 split elements
1976// with Nn other elements. So total length is
1977// L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1).
1978
1979function countImages(wikiText) {
1980 return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1);
1981}
1982
1983function countCategories(wikiText) {
1984 return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1);
1985}
1986
1987function popupFilterStubDetect(data, download, article) {
1988 var counts=stubCount(data, article);
1989 if (counts.real) { return popupString('stub'); }
1990 if (counts.sect) { return popupString('section stub'); }
1991 return '';
1992}
1993
1994function popupFilterDisambigDetect(data, download, article) {
1995 if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return ''; }
1996 return (isDisambig(data, article)) ? popupString('disambig') : '';
1997}
1998
1999function formatBytes(num) {
2000 return (num > 949) ? (Math.round(num/100)/10+popupString('kB')) : (num +' ' + popupString('bytes')) ;
2001}
2002//</NOLITE>
2003// ENDFILE: pageinfo.js
2004// STARTFILE: titles.js
2005/**
2006 @fileoverview Defines the {@link Title} class, and associated crufty functions.
2007
2008 <code>Title</code> deals with article titles and their various
2009 forms. {@link Stringwrapper} is the parent class of
2010 <code>Title</code>, which exists simply to make things a little
2011 neater.
2012
2013*/
2014
2015/**
2016 Creates a new Stringwrapper.
2017 @constructor
2018
2019 @class the Stringwrapper class. This base class is not really
2020 useful on its own; it just wraps various common string operations.
2021*/
2022function Stringwrapper() {
2023 /**
2024 Wrapper for this.toString().indexOf()
2025 @param {String} x
2026 @type integer
2027 */
2028 this.indexOf=function(x){return this.toString().indexOf(x);};
2029 /**
2030 Returns this.value.
2031 @type String
2032 */
2033 this.toString=function(){return this.value;};
2034 /**
2035 Wrapper for {@link String#parenSplit} applied to this.toString()
2036 @param {RegExp} x
2037 @type Array
2038 */
2039 this.parenSplit=function(x){return this.toString().parenSplit(x);};
2040 /**
2041 Wrapper for this.toString().substring()
2042 @param {String} x
2043 @param {String} y (optional)
2044 @type String
2045 */
2046 this.substring=function(x,y){
2047 if (typeof y=='undefined') { return this.toString().substring(x); }
2048 return this.toString().substring(x,y);
2049 };
2050 /**
2051 Wrapper for this.toString().split()
2052 @param {String} x
2053 @type Array
2054 */
2055 this.split=function(x){return this.toString().split(x);};
2056 /**
2057 Wrapper for this.toString().replace()
2058 @param {String} x
2059 @param {String} y
2060 @type String
2061 */
2062 this.replace=function(x,y){ return this.toString().replace(x,y); };
2063}
2064
2065
2066/**
2067 Creates a new <code>Title</code>.
2068 @constructor
2069
2070 @class The Title class. Holds article titles and converts them into
2071 various forms. Also deals with anchors, by which we mean the bits
2072 of the article URL after a # character, representing locations
2073 within an article.
2074
2075 @param {String} value The initial value to assign to the
2076 article. This must be the canonical title (see {@link
2077 Title#value}. Omit this in the constructor and use another function
2078 to set the title if this is unavailable.
2079*/
2080function Title(val) {
2081 /**
2082 The canonical article title. This must be in UTF-8 with no
2083 entities, escaping or nasties. Also, underscores should be
2084 replaced with spaces.
2085 @type String
2086 @private
2087 */
2088 this.value=null;
2089 /**
2090 The canonical form of the anchor. This should be exactly as
2091 it appears in the URL, i.e. with the .C3.0A bits in.
2092 @type String
2093 */
2094 this.anchor='';
2095
2096 this.setUtf(val);
2097}
2098Title.prototype=new Stringwrapper();
2099/**
2100 Returns the canonical representation of the article title, optionally without anchor.
2101 @param {boolean} omitAnchor
2102 @fixme Decide specs for anchor
2103 @return String The article title and the anchor.
2104*/
2105Title.prototype.toString=function(omitAnchor) {
2106 return this.value + ( (!omitAnchor && this.anchor) ? '#' + this.anchorString() : '' );
2107};
2108Title.prototype.anchorString=function() {
2109 if (!this.anchor) { return ''; }
2110 var split=this.anchor.parenSplit(/((?:[.][0-9A-F]{2})+)/);
2111 var len=split.length;
2112 for (var j=1; j<len; j+=2) {
2113 // FIXME s/decodeURI/decodeURIComponent/g ?
2114 split[j]=decodeURIComponent(split[j].split('.').join('%')).split('_').join(' ');
2115 }
2116 return split.join('');
2117};
2118Title.prototype.urlAnchor=function() {
2119 var split=this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/');
2120 var len=split.length;
2121 for (var j=1; j<len; j+=2) {
2122 split[j]=split[j].split('%').join('.');
2123 }
2124 return split.join('');
2125};
2126Title.prototype.anchorFromUtf=function(str) {
2127 this.anchor=encodeURIComponent(str.split(' ').join('_'))
2128 .split('%3A').join(':').split("'").join('%27').split('%').join('.');
2129};
2130Title.fromURL=function(h) {
2131 return new Title().fromURL(h);
2132};
2133Title.prototype.fromURL=function(h) {
2134 if (typeof h != 'string') {
2135 this.value=null;
2136 return this;
2137 }
2138
2139 // NOTE : playing with decodeURI, encodeURI, escape, unescape,
2140 // we seem to be able to replicate the IE borked encoding
2141
2142 // IE doesn't do this new-fangled utf-8 thing.
2143 // and it's worse than that.
2144 // IE seems to treat the query string differently to the rest of the url
2145 // the query is treated as bona-fide utf8, but the first bit of the url is pissed around with
2146
2147 // we fix up & for all browsers, just in case.
2148 var splitted=h.split('?');
2149 splitted[0]=splitted[0].split('&').join('%26');
2150
2151 h=splitted.join('?');
2152
2153 var contribs=pg.re.contribs.exec(h);
2154 if (contribs) {
2155 if (contribs[1]=='title=') { contribs[3]=contribs[3].split('+').join(' '); }
2156 var u=new Title(contribs[3]);
2157 this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + u.stripNamespace()));
2158 return this;
2159 }
2160
2161 var email=pg.re.email.exec(h);
2162 if (email) {
2163 this.setUtf(this.decodeNasties(mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(email[3]).stripNamespace()));
2164 return this;
2165 }
2166
2167 var backlinks=pg.re.backlinks.exec(h);
2168 if (backlinks) {
2169 this.setUtf(this.decodeNasties(new Title(backlinks[3])));
2170 return this;
2171 }
2172
2173 //A dummy title object for a Special:Diff link.
2174 var specialdiff=pg.re.specialdiff.exec(h);
2175 if (specialdiff) {
2176 this.setUtf(this.decodeNasties(new Title(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Diff')));
2177 return this;
2178 }
2179
2180 // no more special cases to check --
2181 // hopefully it's not a disguised user-related or specially treated special page
2182 var m=pg.re.main.exec(h);
2183 if(m === null) { this.value=null; }
2184 else {
2185 var fromBotInterface = /[?](.+[&])?title=/.test(h);
2186 if (fromBotInterface) {
2187 m[2]=m[2].split('+').join('_');
2188 }
2189 var extracted = m[2] + (m[3] ? '#' + m[3] : '');
2190 if (pg.flag.isSafari && /%25[0-9A-Fa-f]{2}/.test(extracted)) {
2191 // Fix Safari issue
2192 // Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3.
2193 this.setUtf(decodeURIComponent(unescape(extracted)));
2194 } else {
2195 this.setUtf(this.decodeNasties(extracted));
2196 }
2197 }
2198 return this;
2199};
2200Title.prototype.decodeNasties=function(txt) {
2201 var ret= this.decodeEscapes(decodeURI(txt));
2202 ret = ret.replace(/[_ ]*$/, '');
2203 return ret;
2204};
2205Title.prototype.decodeEscapes=function(txt) {
2206 var split=txt.parenSplit(/((?:[%][0-9A-Fa-f]{2})+)/);
2207 var len=split.length;
2208 for (var i=1; i<len; i=i+2) {
2209 // FIXME is decodeURIComponent better?
2210 split[i]=unescape(split[i]);
2211 }
2212 return split.join('');
2213};
2214Title.fromAnchor=function(a) {
2215 return new Title().fromAnchor(a);
2216};
2217Title.prototype.fromAnchor=function(a) {
2218 if (!a) { this.value=null; return this; }
2219 return this.fromURL(a.href);
2220};
2221Title.fromWikiText=function(txt) {
2222 return new Title().fromWikiText(txt);
2223};
2224Title.prototype.fromWikiText=function(txt) {
2225 // FIXME - testing needed
2226 txt=myDecodeURI(txt);
2227 this.setUtf(txt);
2228 return this;
2229};
2230Title.prototype.hintValue=function(){
2231 if(!this.value) { return ''; }
2232 return safeDecodeURI(this.value);
2233};
2234//<NOLITE>
2235Title.prototype.toUserName=function(withNs) {
2236 if (this.namespaceId() != pg.nsUserId && this.namespaceId() != pg.nsUsertalkId) {
2237 this.value=null;
2238 return;
2239 }
2240 this.value = (withNs ? mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' : '') + this.stripNamespace().split('/')[0];
2241};
2242Title.prototype.userName=function(withNs) {
2243 var t=(new Title(this.value));
2244 t.toUserName(withNs);
2245 if (t.value) { return t; }
2246 return null;
2247};
2248Title.prototype.toTalkPage=function() {
2249 // convert article to a talk page, or if we can't, return null
2250 // In other words: return null if this ALREADY IS a talk page
2251 // and return the corresponding talk page otherwise
2252 //
2253 // Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces
2254 // * All discussion namespaces have odd-integer indices
2255 // * The discussion namespace index for a specific namespace with index n is n + 1
2256 if (this.value === null) { return null; }
2257
2258 var namespaceId = this.namespaceId();
2259 if (namespaceId>=0 && namespaceId % 2 === 0) //non-special and subject namespace
2260 {
2261 var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId+1];
2262 if (typeof localizedNamespace!=='undefined')
2263 {
2264 if (localizedNamespace === '') {
2265 this.value = this.stripNamespace();
2266 } else {
2267 this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
2268 }
2269 return this.value;
2270 }
2271 }
2272
2273 this.value=null;
2274 return null;
2275};
2276//</NOLITE>
2277// Return canonical, localized namespace
2278Title.prototype.namespace=function() {
2279 return mw.config.get('wgFormattedNamespaces')[this.namespaceId()];
2280};
2281Title.prototype.namespaceId=function() {
2282 var n=this.value.indexOf(':');
2283 if (n<0) { return 0; } //mainspace
2284 var namespaceId = mw.config.get('wgNamespaceIds')[this.value.substring(0,n).split(' ').join('_').toLowerCase()];
2285 if (typeof namespaceId=='undefined') return 0; //mainspace
2286 return namespaceId;
2287};
2288//<NOLITE>
2289Title.prototype.talkPage=function() {
2290 var t=new Title(this.value);
2291 t.toTalkPage();
2292 if (t.value) { return t; }
2293 return null;
2294};
2295Title.prototype.isTalkPage=function() {
2296 if (this.talkPage()===null) { return true; }
2297 return false;
2298};
2299Title.prototype.toArticleFromTalkPage=function() {
2300 //largely copy/paste from toTalkPage above.
2301 if (this.value === null) { return null; }
2302
2303 var namespaceId = this.namespaceId();
2304 if (namespaceId >= 0 && namespaceId % 2 == 1) //non-special and talk namespace
2305 {
2306 var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId-1];
2307 if (typeof localizedNamespace!=='undefined')
2308 {
2309 if (localizedNamespace === '') {
2310 this.value = this.stripNamespace();
2311 } else {
2312 this.value = localizedNamespace.split(' ').join('_') + ':' + this.stripNamespace();
2313 }
2314 return this.value;
2315 }
2316 }
2317
2318 this.value=null;
2319 return null;
2320};
2321Title.prototype.articleFromTalkPage=function() {
2322 var t=new Title(this.value);
2323 t.toArticleFromTalkPage();
2324 if (t.value) { return t; }
2325 return null;
2326};
2327Title.prototype.articleFromTalkOrArticle=function() {
2328 var t=new Title(this.value);
2329 if ( t.toArticleFromTalkPage() ) { return t; }
2330 return this;
2331};
2332Title.prototype.isIpUser=function() {
2333 return pg.re.ipUser.test(this.userName());
2334};
2335//</NOLITE>
2336Title.prototype.stripNamespace=function(){ // returns a string, not a Title
2337 var n=this.value.indexOf(':');
2338 if (n<0) { return this.value; }
2339 var namespaceId = this.namespaceId();
2340 if (namespaceId === pg.nsMainspaceId) return this.value;
2341 return this.value.substring(n+1);
2342};
2343Title.prototype.setUtf=function(value){
2344 if (!value) { this.value=''; return; }
2345 var anch=value.indexOf('#');
2346 if(anch < 0) { this.value=value.split('_').join(' '); this.anchor=''; return; }
2347 this.value=value.substring(0,anch).split('_').join(' ');
2348 this.anchor=value.substring(anch+1);
2349 this.ns=null; // wait until namespace() is called
2350};
2351Title.prototype.setUrl=function(urlfrag) {
2352 var anch=urlfrag.indexOf('#');
2353 this.value=safeDecodeURI(urlfrag.substring(0,anch));
2354 this.anchor=this.value.substring(anch+1);
2355};
2356Title.prototype.append=function(x){
2357 this.setUtf(this.value + x);
2358};
2359Title.prototype.urlString=function(x) {
2360 if(!x) { x={}; }
2361 var v=this.toString(true);
2362 if (!x.omitAnchor && this.anchor) { v+= '#' + this.urlAnchor(); }
2363 if (!x.keepSpaces) { v=v.split(' ').join('_'); }
2364 return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B');
2365};
2366Title.prototype.removeAnchor=function() {
2367 return new Title(this.toString(true));
2368};
2369Title.prototype.toUrl=function() {
2370 return pg.wiki.titlebase + this.urlString();
2371};
2372
2373function parseParams(url) {
2374 var specialDiff = pg.re.specialdiff.exec(url);
2375 if (specialDiff)
2376 {
2377 var split= specialDiff[1].split('/');
2378 if (split.length==1) return {oldid:split[0], diff: 'prev'};
2379 else if (split.length==2) return {oldid: split[0], diff: split[1]};
2380 }
2381
2382 var ret={};
2383 if (url.indexOf('?')==-1) { return ret; }
2384 url = url.split('#')[0];
2385 var s=url.split('?').slice(1).join();
2386 var t=s.split('&');
2387 for (var i=0; i<t.length; ++i) {
2388 var z=t[i].split('=');
2389 z.push(null);
2390 ret[z[0]]=z[1];
2391 }
2392 //Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki
2393 if (ret.diff && typeof(ret.oldid)==='undefined')
2394 {
2395 ret.oldid = "prev";
2396 }
2397 //Documentation seems to say something different, but oldid can also accept prev/next, and Echo is emitting such URLs. Simple fixup during parameter decoding:
2398 if (ret.oldid && (ret.oldid==='prev' || ret.oldid==='next' || ret.oldid==='cur'))
2399 {
2400 var helper = ret.diff;
2401 ret.diff = ret.oldid;
2402 ret.oldid = helper;
2403 }
2404 return ret;
2405}
2406
2407// (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup)
2408// (b) change spaces to underscores
2409// (c) encodeURI (just the straight one, no pg.re.urlNoPopup)
2410
2411function myDecodeURI (str) {
2412 var ret;
2413 // FIXME decodeURIComponent??
2414 try { ret=decodeURI(str.toString()); }
2415 catch (summat) { return str; }
2416 for (var i=0; i<pg.misc.decodeExtras.length; ++i) {
2417 var from=pg.misc.decodeExtras[i].from;
2418 var to=pg.misc.decodeExtras[i].to;
2419 ret=ret.split(from).join(to);
2420 }
2421 return ret;
2422}
2423
2424function safeDecodeURI(str) { var ret=myDecodeURI(str); return ret || str; }
2425
2426///////////
2427// TESTS //
2428///////////
2429
2430//<NOLITE>
2431function isDisambig(data, article) {
2432 if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; }
2433 return ! article.isTalkPage() && pg.re.disambig.test(data);
2434}
2435
2436function stubCount(data, article) {
2437 if (!getValueOf('popupAllDabsStubs') && article.namespace()) { return false; }
2438 var sectStub=0;
2439 var realStub=0;
2440 if (pg.re.stub.test(data)) {
2441 var s=data.parenSplit(pg.re.stub);
2442 for (var i=1; i<s.length; i=i+2) {
2443 if (s[i]) { ++sectStub; }
2444 else { ++realStub; }
2445 }
2446 }
2447 return { real: realStub, sect: sectStub };
2448}
2449
2450function isValidImageName(str){ // extend as needed...
2451 return ( str.indexOf('{') == -1 );
2452}
2453
2454function isInStrippableNamespace(article) {
2455 // Does the namespace allow subpages
2456 // Note, would be better if we had access to wgNamespacesWithSubpages
2457 return ( article.namespaceId() !== 0 );
2458}
2459
2460function isInMainNamespace(article) { return article.namespaceId() === 0; }
2461
2462function anchorContainsImage(a) {
2463 // iterate over children of anchor a
2464 // see if any are images
2465 if (a === null) { return false; }
2466 var kids=a.childNodes;
2467 for (var i=0; i<kids.length; ++i) { if (kids[i].nodeName=='IMG') { return true; } }
2468 return false;
2469}
2470//</NOLITE>
2471function isPopupLink(a) {
2472 // NB for performance reasons, TOC links generally return true
2473 // they should be stripped out later
2474
2475 if (!markNopopupSpanLinks.done) { markNopopupSpanLinks(); }
2476 if (a.inNopopupSpan) { return false; }
2477
2478 // FIXME is this faster inline?
2479 if (a.onmousedown || a.getAttribute('nopopup')) { return false; }
2480 var h=a.href;
2481 if (h === document.location.href+'#') { return false; }
2482 if (!pg.re.basenames.test(h)) { return false; }
2483 if (!pg.re.urlNoPopup.test(h)) { return true; }
2484 return (
2485 (pg.re.email.test(h) || pg.re.contribs.test(h) || pg.re.backlinks.test(h) || pg.re.specialdiff.test(h)) &&
2486 h.indexOf('&limit=') == -1 );
2487}
2488
2489function markNopopupSpanLinks() {
2490 if( !getValueOf('popupOnlyArticleLinks'))
2491 fixVectorMenuPopups();
2492
2493 var s = $('.nopopups').toArray();
2494 for (var i=0; i<s.length; ++i) {
2495 var as=s[i].getElementsByTagName('a');
2496 for (var j=0; j<as.length; ++j) {
2497 as[j].inNopopupSpan=true;
2498 }
2499 }
2500
2501 markNopopupSpanLinks.done=true;
2502}
2503
2504function fixVectorMenuPopups() {
2505 $('div.vectorMenu h3:first a:first').prop('inNopopupSpan', true);
2506}
2507// ENDFILE: titles.js
2508// STARTFILE: getpage.js
2509//////////////////////////////////////////////////
2510// Wiki-specific downloading
2511//
2512
2513// Schematic for a getWiki call
2514//
2515// getPageWithCaching
2516// |
2517// false | true
2518// getPage<-[findPictureInCache]->-onComplete(a fake download)
2519// \.
2520// (async)->addPageToCache(download)->-onComplete(download)
2521
2522// check cache to see if page exists
2523
2524function getPageWithCaching(url, onComplete, owner) {
2525 log('getPageWithCaching, url='+url);
2526 var i=findInPageCache(url);
2527 var d;
2528 if (i > -1) {
2529 d=fakeDownload(url, owner.idNumber, onComplete,
2530 pg.cache.pages[i].data, pg.cache.pages[i].lastModified,
2531 owner);
2532 } else {
2533 d=getPage(url, onComplete, owner);
2534 if (d && owner && owner.addDownload) {
2535 owner.addDownload(d);
2536 d.owner=owner;
2537 }
2538 }
2539}
2540
2541function getPage(url, onComplete, owner) {
2542 log('getPage');
2543 var callback= function (d) { if (!d.aborted) {addPageToCache(d); onComplete(d);} };
2544 return startDownload(url, owner.idNumber, callback);
2545}
2546
2547function findInPageCache(url) {
2548 for (var i=0; i<pg.cache.pages.length; ++i) {
2549 if (url==pg.cache.pages[i].url) { return i; }
2550 }
2551 return -1;
2552}
2553
2554function addPageToCache(download) {
2555 log('addPageToCache '+download.url);
2556 var page = {url: download.url, data: download.data, lastModified: download.lastModified};
2557 return pg.cache.pages.push(page);
2558}
2559// ENDFILE: getpage.js
2560// STARTFILE: parensplit.js
2561//////////////////////////////////////////////////
2562// parenSplit
2563
2564// String.prototype.parenSplit should do what ECMAscript says String.prototype.split does,
2565// interspersing paren matches (regex capturing groups) between the split elements.
2566// i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c']
2567
2568if (String('abc'.split(/(b)/))!='a,b,c') {
2569 // broken String.split, e.g. konq, IE < 10
2570 String.prototype.parenSplit=function (re) {
2571 re=nonGlobalRegex(re);
2572 var s=this;
2573 var m=re.exec(s);
2574 var ret=[];
2575 while (m && s) {
2576 // without the following loop, we have
2577 // 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/)
2578 for(var i=0; i<m.length; ++i) {
2579 if (typeof m[i]=='undefined') m[i]='';
2580 }
2581 ret.push(s.substring(0,m.index));
2582 ret = ret.concat(m.slice(1));
2583 s=s.substring(m.index + m[0].length);
2584 m=re.exec(s);
2585 }
2586 ret.push(s);
2587 return ret;
2588 };
2589} else {
2590 String.prototype.parenSplit=function (re) { return this.split(re); };
2591 String.prototype.parenSplit.isNative=true;
2592}
2593
2594function nonGlobalRegex(re) {
2595 var s=re.toString();
2596 var flags='';
2597 for (var j=s.length; s.charAt(j) != '/'; --j) {
2598 if (s.charAt(j) != 'g') { flags += s.charAt(j); }
2599 }
2600 var t=s.substring(1,j);
2601 return RegExp(t,flags);
2602}
2603// ENDFILE: parensplit.js
2604// STARTFILE: tools.js
2605// IE madness with encoding
2606// ========================
2607//
2608// suppose throughout that the page is in utf8, like wikipedia
2609//
2610// if a is an anchor DOM element and a.href should consist of
2611//
2612// http://host.name.here/wiki/foo?bar=baz
2613//
2614// then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie))
2615// but IE gives bar=baz correctly as plain utf8
2616//
2617// ---------------------------------
2618//
2619// IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here.
2620//
2621// ---------------------------------
2622//
2623// summat else
2624
2625// Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm
2626
2627//<NOLITE>
2628
2629
2630function getJsObj(json) {
2631 try {
2632 var json_ret = JSON.parse(json);
2633 if( json_ret.warnings ) {
2634 for( var w=0; w < json_ret.warnings.length; w++ ) {
2635 if( json_ret.warnings[w]['*'] ) {
2636 log( json_ret.warnings[w]['*'] );
2637 } else {
2638 log( json_ret.warnings[w]['warnings'] );
2639 }
2640 }
2641 } else if ( json_ret.error ) {
2642 errlog( json_ret.error.code + ': ' + json_ret.error.info );
2643 }
2644 return json_ret;
2645 } catch (someError) {
2646 errlog('Something went wrong with getJsObj, json='+json);
2647 return 1;
2648 }
2649}
2650
2651function anyChild(obj) {
2652 for (var p in obj) {
2653 return obj[p];
2654 }
2655 return null;
2656}
2657
2658//</NOLITE>
2659
2660function upcaseFirst(str) {
2661 if (typeof str != typeof '' || str === '') return '';
2662 return str.charAt(0).toUpperCase() + str.substring(1);
2663}
2664
2665
2666function findInArray(arr, foo) {
2667 if (!arr || !arr.length) { return -1; }
2668 var len=arr.length;
2669 for (var i=0; i<len; ++i) { if (arr[i]==foo) { return i; } }
2670 return -1;
2671}
2672
2673/* eslint-disable no-unused-vars */
2674function nextOne (array, value) {
2675 // NB if the array has two consecutive entries equal
2676 // then this will loop on successive calls
2677 var i=findInArray(array, value);
2678 if (i<0) { return null; }
2679 return array[i+1];
2680}
2681/* eslint-enable no-unused-vars */
2682
2683function literalizeRegex(str){
2684 return mw.RegExp.escape(str);
2685}
2686
2687String.prototype.entify=function() {
2688 //var shy='­';
2689 return this.split('&').join('&').split('<').join('<').split('>').join('>'/*+shy*/).split('"').join('"');
2690};
2691
2692// Array filter function
2693function removeNulls(val) { return val !== null; }
2694
2695function joinPath(list) {
2696 return list.filter(removeNulls).join('/');
2697}
2698
2699
2700function simplePrintf(str, subs) {
2701 if (!str || !subs) { return str; }
2702 var ret=[];
2703 var s=str.parenSplit(/(%s|\$[0-9]+)/);
2704 var i=0;
2705 do {
2706 ret.push(s.shift());
2707 if ( !s.length ) { break; }
2708 var cmd=s.shift();
2709 if (cmd == '%s') {
2710 if ( i < subs.length ) { ret.push(subs[i]); } else { ret.push(cmd); }
2711 ++i;
2712 } else {
2713 var j=parseInt( cmd.replace('$', ''), 10 ) - 1;
2714 if ( j > -1 && j < subs.length ) { ret.push(subs[j]); } else { ret.push(cmd); }
2715 }
2716 } while (s.length > 0);
2717 return ret.join('');
2718}
2719/* eslint-disable no-unused-vars */
2720function isString(x) { return (typeof x === 'string' || x instanceof String); }
2721function isNumber(x) { return (typeof x === 'number' || x instanceof Number); }
2722function isRegExp(x) { return x instanceof RegExp; }
2723function isArray (x) { return x instanceof Array; }
2724function isObject(x) { return x instanceof Object; }
2725function isFunction(x) {
2726 return !isRegExp(x) && ($.isFunction(x) || x instanceof Function);
2727}
2728/* eslint-enable no-unused-vars */
2729
2730function repeatString(s,mult) {
2731 var ret='';
2732 for (var i=0; i<mult; ++i) { ret += s; }
2733 return ret;
2734}
2735
2736function zeroFill(s, min) {
2737 min = min || 2;
2738 var t=s.toString();
2739 return repeatString('0', min - t.length) + t;
2740}
2741
2742function map(f, o) {
2743 if (isArray(o)) { return map_array(f,o); }
2744 return map_object(f,o);
2745}
2746function map_array(f,o) {
2747 var ret=[];
2748 for (var i=0; i<o.length; ++i) {
2749 ret.push(f(o[i]));
2750 }
2751 return ret;
2752}
2753function map_object(f,o) {
2754 var ret={};
2755 for (var i in o) { ret[o]=f(o[i]); }
2756 return ret;
2757}
2758
2759pg.escapeQuotesHTML = function ( text ) {
2760 return text
2761 .replace(/&/g, "&")
2762 .replace(/"/g, """)
2763 .replace(/</g, "<")
2764 .replace(/>/g, ">");
2765};
2766
2767// ENDFILE: tools.js
2768// STARTFILE: dab.js
2769//<NOLITE>
2770//////////////////////////////////////////////////
2771// Dab-fixing code
2772//
2773
2774
2775function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) {
2776 log('retargetDab: newTarget='+newTarget + ' oldTarget=' + oldTarget);
2777 return changeLinkTargetLink(
2778 {newTarget: newTarget,
2779 text: newTarget.split(' ').join(' '),
2780 hint: tprintf('disambigHint', [newTarget]),
2781 summary: simplePrintf(
2782 getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget ]),
2783 clickButton: getValueOf('popupDabsAutoClick'), minor: true, oldTarget: oldTarget,
2784 watch: getValueOf('popupWatchDisambiggedPages'),
2785 title: titleToEdit});
2786}
2787
2788function listLinks(wikitext, oldTarget, titleToEdit) {
2789 // mediawiki strips trailing spaces, so we do the same
2790 // testcase: https://en.wikipedia.org/w/index.php?title=Radial&oldid=97365633
2791 var reg=RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi');
2792 var ret=[];
2793 var splitted=wikitext.parenSplit(reg);
2794 // ^[a-z]+ should match interwiki links, hopefully (case-insensitive)
2795 // and ^[a-z]* should match those and [[:Category...]] style links too
2796 var omitRegex=RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory');
2797 var friendlyCurrentArticleName= oldTarget.toString();
2798 var wikPos = getValueOf('popupDabWiktionary');
2799
2800 for (var i=1; i<splitted.length; i=i+3) {
2801 if (typeof splitted[i] == typeof 'string' && splitted[i].length>0 && !omitRegex.test(splitted[i])) {
2802 ret.push( retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit) );
2803 } /* if */
2804 } /* for loop */
2805
2806 ret = rmDupesFromSortedList(ret.sort());
2807
2808 if (wikPos) {
2809 var wikTarget='wiktionary:' +
2810 friendlyCurrentArticleName.replace( RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1' );
2811
2812 var meth;
2813 if (wikPos.toLowerCase() == 'first') { meth = 'unshift'; }
2814 else { meth = 'push'; }
2815
2816 ret[meth]( retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) );
2817 }
2818
2819 ret.push(changeLinkTargetLink(
2820 { newTarget: null,
2821 text: popupString('remove this link').split(' ').join(' '),
2822 hint: popupString("remove all links to this disambig page from this article"),
2823 clickButton: "wpDiff", oldTarget: oldTarget,
2824 summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]),
2825 watch: getValueOf('popupWatchDisambiggedPages'),
2826 title: titleToEdit
2827 }));
2828 return ret;
2829}
2830
2831function rmDupesFromSortedList(list) {
2832 var ret=[];
2833 for (var i=0; i<list.length; ++i) {
2834 if (ret.length === 0 || list[i]!=ret[ret.length-1]) { ret.push(list[i]); }
2835 }
2836 return ret;
2837}
2838
2839function makeFixDab(data, navpop) {
2840 // grab title from parent popup if there is one; default exists in changeLinkTargetLink
2841 var titleToEdit=(navpop.parentPopup && navpop.parentPopup.article.toString());
2842 var list=listLinks(data, navpop.originalArticle, titleToEdit);
2843 if (list.length === 0) { log('listLinks returned empty list'); return null; }
2844 var html='<hr />' + popupString('Click to disambiguate this link to:') + '<br>';
2845 html+=list.join(', ');
2846 return html;
2847}
2848
2849
2850function makeFixDabs(wikiText, navpop) {
2851 if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) &&
2852 Title.fromURL(location.href).namespaceId() != pg.nsSpecialId &&
2853 navpop.article.talkPage() ) {
2854 setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber);
2855 }
2856}
2857
2858function popupRedlinkHTML(article) {
2859 return changeLinkTargetLink(
2860 { newTarget: null, text: popupString('remove this link').split(' ').join(' '),
2861 hint: popupString("remove all links to this page from this article"),
2862 clickButton: "wpDiff",
2863 oldTarget: article.toString(),
2864 summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])});
2865}
2866//</NOLITE>
2867// ENDFILE: dab.js
2868// STARTFILE: htmloutput.js
2869
2870// this has to use a timer loop as we don't know if the DOM element exists when we want to set the text
2871function setPopupHTML (str, elementId, popupId, onSuccess, append) {
2872 if (typeof popupId === 'undefined') {
2873 //console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100));
2874 popupId = pg.idNumber;
2875 }
2876
2877 var popupElement=document.getElementById(elementId+popupId);
2878 if (popupElement) {
2879 if (!append) { popupElement.innerHTML=''; }
2880 if (isString(str)) {
2881 popupElement.innerHTML+=str;
2882 } else {
2883 popupElement.appendChild(str);
2884 }
2885 if (onSuccess) { onSuccess(); }
2886 setTimeout(checkPopupPosition, 100);
2887 return true;
2888 } else {
2889 // call this function again in a little while...
2890 setTimeout(function(){
2891 setPopupHTML(str,elementId,popupId,onSuccess);
2892 }, 600);
2893 }
2894 return null;
2895}
2896
2897//<NOLITE>
2898function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);}
2899//</NOLITE>
2900
2901// args.navpopup is mandatory
2902// optional: args.redir, args.redirTarget
2903// FIXME: ye gods, this is ugly stuff
2904function fillEmptySpans(args) {
2905 // if redir is present and true then redirTarget is mandatory
2906 var redir=true;
2907 var rcid;
2908 if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) { redir=false; }
2909 var a=args.navpopup.parentAnchor;
2910
2911 var article, hint=null, oldid=null, params={};
2912 if (redir && typeof args.redirTarget == typeof {}) {
2913 article=args.redirTarget;
2914 //hint=article.hintValue();
2915 } else {
2916 article=(new Title()).fromAnchor(a);
2917 hint=a.originalTitle || article.hintValue();
2918 params=parseParams(a.href);
2919 oldid=(getValueOf('popupHistoricalLinks')) ? params.oldid : null;
2920 rcid=params.rcid;
2921 }
2922 var x={ article:article, hint: hint, oldid: oldid, rcid: rcid, navpop:args.navpopup, params:params };
2923
2924 var structure=pg.structures[getValueOf('popupStructure')];
2925 if (typeof structure != 'object') {
2926 setPopupHTML('popupError', 'Unknown structure (this should never happen): '+
2927 pg.option.popupStructure, args.navpopup.idNumber);
2928 return;
2929 }
2930 var spans=flatten(pg.misc.layout);
2931 var numspans = spans.length;
2932 var redirs=pg.misc.redirSpans;
2933
2934 for (var i=0; i<numspans; ++i) {
2935 var found = redirs && (redirs.indexOf( spans[i] ) !== -1);
2936 //log('redir='+redir+', found='+found+', spans[i]='+spans[i]);
2937 if ( (found && !redir) || (!found && redir) ) {
2938 //log('skipping this set of the loop');
2939 continue;
2940 }
2941 var structurefn=structure[spans[i]];
2942 var setfn = setPopupHTML;
2943 if (getValueOf('popupActiveNavlinks') &&
2944 (spans[i].indexOf('popupTopLinks')===0 || spans[i].indexOf('popupRedirTopLinks')===0)
2945 ) {
2946 setfn = setPopupTipsAndHTML;
2947 }
2948 switch (typeof structurefn) {
2949 case 'function':
2950 log('running '+spans[i]+'({article:'+x.article+', hint:'+x.hint+', oldid: '+x.oldid+'})');
2951 setfn(structurefn(x), spans[i], args.navpopup.idNumber);
2952 break;
2953 case 'string':
2954 setfn(structurefn, spans[i], args.navpopup.idNumber);
2955 break;
2956 default:
2957 errlog('unknown thing with label '+spans[i] + ' (span index was ' + i + ')');
2958 break;
2959 }
2960 }
2961}
2962
2963// flatten an array
2964function flatten(list, start) {
2965 var ret=[];
2966 if (typeof start == 'undefined') { start=0; }
2967 for (var i=start; i<list.length; ++i) {
2968 if (typeof list[i] == typeof []) {
2969 return ret.concat(flatten(list[i])).concat(flatten(list, i+1));
2970 }
2971 else { ret.push(list[i]); }
2972 }
2973 return ret;
2974}
2975
2976// Generate html for whole popup
2977function popupHTML (a) {
2978 getValueOf('popupStructure');
2979 var structure=pg.structures[pg.option.popupStructure];
2980 if (typeof structure != 'object') {
2981 //return 'Unknown structure: '+pg.option.popupStructure;
2982 // override user choice
2983 pg.option.popupStructure=pg.optionDefault.popupStructure;
2984 return popupHTML(a);
2985 }
2986 if (typeof structure.popupLayout != 'function') { return 'Bad layout'; }
2987 pg.misc.layout=structure.popupLayout();
2988 if ($.isFunction(structure.popupRedirSpans)) { pg.misc.redirSpans=structure.popupRedirSpans(); }
2989 else { pg.misc.redirSpans=[]; }
2990 return makeEmptySpans(pg.misc.layout, a.navpopup);
2991}
2992
2993function makeEmptySpans (list, navpop) {
2994 var ret='';
2995 for (var i=0; i<list.length; ++i) {
2996 if (typeof list[i] == typeof '') {
2997 ret += emptySpanHTML(list[i], navpop.idNumber, 'div');
2998 } else if (typeof list[i] == typeof [] && list[i].length > 0 ) {
2999 ret = ret.parenSplit(RegExp('(</[^>]*?>$)')).join(makeEmptySpans(list[i], navpop));
3000 } else if (typeof list[i] == typeof {} && list[i].nodeType ) {
3001 ret += emptySpanHTML(list[i].name, navpop.idNumber, list[i].nodeType);
3002 }
3003 }
3004 return ret;
3005}
3006
3007
3008function emptySpanHTML(name, id, tag, classname) {
3009 tag = tag || 'span';
3010 if (!classname) { classname = emptySpanHTML.classAliases[name]; }
3011 classname = classname || name;
3012 if (name == getValueOf('popupDragHandle')) { classname += ' popupDragHandle'; }
3013 return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]);
3014}
3015emptySpanHTML.classAliases={ 'popupSecondPreview': 'popupPreview' };
3016
3017// generate html for popup image
3018// <a id="popupImageLinkn"><img id="popupImagen">
3019// where n=idNumber
3020function imageHTML(article, idNumber) {
3021 return simplePrintf('<a id="popupImageLink$1">' +
3022 '<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' +
3023 '</a>', [ idNumber ]);
3024}
3025
3026function popTipsSoonFn(id, when, popData) {
3027 if (!when) { when=250; }
3028 var popTips=function(){ setupTooltips(document.getElementById(id), false, true, popData); };
3029 return function() { setTimeout( popTips, when, popData ); };
3030}
3031
3032function setPopupTipsAndHTML(html, divname, idnumber, popData) {
3033 setPopupHTML(html, divname, idnumber,
3034 getValueOf('popupSubpopups') ?
3035 popTipsSoonFn(divname + idnumber, null, popData) :
3036 null);
3037}
3038// ENDFILE: htmloutput.js
3039// STARTFILE: mouseout.js
3040//////////////////////////////////////////////////
3041// fuzzy checks
3042
3043function fuzzyCursorOffMenus(x,y, fuzz, parent) {
3044 if (!parent) { return null; }
3045 var uls=parent.getElementsByTagName('ul');
3046 for (var i=0; i<uls.length; ++i) {
3047 if (uls[i].className=='popup_menu') {
3048 if (uls[i].offsetWidth > 0) return false;
3049 } // else {document.title+='.';}
3050 }
3051 return true;
3052}
3053
3054function checkPopupPosition () { // stop the popup running off the right of the screen
3055 // FIXME avoid pg.current.link
3056 if (pg.current.link && pg.current.link.navpopup)
3057 pg.current.link.navpopup.limitHorizontalPosition();
3058}
3059
3060function mouseOutWikiLink () {
3061 //console ('mouseOutWikiLink');
3062 var a=this;
3063 if (a.navpopup === null || typeof a.navpopup === 'undefined') return;
3064 if ( ! a.navpopup.isVisible() ) {
3065 a.navpopup.banish();
3066 return;
3067 }
3068 restoreTitle(a);
3069 Navpopup.tracker.addHook(posCheckerHook(a.navpopup));
3070}
3071
3072function posCheckerHook(navpop) {
3073 return function() {
3074 if (!navpop.isVisible()) { return true; /* remove this hook */ }
3075 if (Navpopup.tracker.dirty) {
3076 return false;
3077 }
3078 var x=Navpopup.tracker.x, y=Navpopup.tracker.y;
3079 var mouseOverNavpop = navpop.isWithin(x,y,navpop.fuzz, navpop.mainDiv) ||
3080 !fuzzyCursorOffMenus(x,y,navpop.fuzz, navpop.mainDiv);
3081
3082 // FIXME it'd be prettier to do this internal to the Navpopup objects
3083 var t=getValueOf('popupHideDelay');
3084 if (t) { t = t * 1000; }
3085 if (!t) {
3086 if(!mouseOverNavpop) {
3087 if(navpop.parentAnchor) {
3088 restoreTitle( navpop.parentAnchor );
3089 }
3090 navpop.banish();
3091 return true; /* remove this hook */
3092 }
3093 return false;
3094 }
3095 // we have a hide delay set
3096 var d=+(new Date());
3097 if ( !navpop.mouseLeavingTime ) {
3098 navpop.mouseLeavingTime = d;
3099 return false;
3100 }
3101 if ( mouseOverNavpop ) {
3102 navpop.mouseLeavingTime=null;
3103 return false;
3104 }
3105 if (d - navpop.mouseLeavingTime > t) {
3106 navpop.mouseLeavingTime=null;
3107 navpop.banish(); return true; /* remove this hook */
3108 }
3109 return false;
3110 };
3111}
3112
3113function runStopPopupTimer(navpop) {
3114 // at this point, we should have left the link but remain within the popup
3115 // so we call this function again until we leave the popup.
3116 if (!navpop.stopPopupTimer) {
3117 navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500);
3118 navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);},
3119 'hide', 'before');
3120 }
3121}
3122// ENDFILE: mouseout.js
3123// STARTFILE: previewmaker.js
3124/**
3125 @fileoverview
3126 Defines the {@link Previewmaker} object, which generates short previews from wiki markup.
3127*/
3128
3129/**
3130 Creates a new Previewmaker
3131 @constructor
3132 @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext.
3133 @param {String} wikiText The Wikitext source of the page we wish to preview.
3134 @param {String} baseUrl The url we should prepend when creating relative urls.
3135 @param {Navpopup} owner The navpop associated to this preview generator
3136*/
3137function Previewmaker(wikiText, baseUrl, owner) {
3138 /** The wikitext which is manipulated to generate the preview. */
3139 this.originalData=wikiText;
3140 this.baseUrl=baseUrl;
3141 this.owner=owner;
3142
3143 this.maxCharacters=getValueOf('popupMaxPreviewCharacters');
3144 this.maxSentences=getValueOf('popupMaxPreviewSentences');
3145
3146 this.setData();
3147}
3148Previewmaker.prototype.setData=function() {
3149 var maxSize=Math.max(10000, 2*this.maxCharacters);
3150 this.data=this.originalData.substring(0,maxSize);
3151};
3152/** Remove HTML comments
3153 @private
3154*/
3155Previewmaker.prototype.killComments = function () {
3156 // this also kills one trailing newline, eg [[diamyo]]
3157 this.data=this.data.replace(RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), '');
3158};
3159/**
3160 @private
3161*/
3162Previewmaker.prototype.killDivs = function () {
3163 // say goodbye, divs (can be nested, so use * not *?)
3164 this.data=this.data.replace(RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>',
3165 'gi'), '');
3166};
3167/**
3168 @private
3169*/
3170Previewmaker.prototype.killGalleries = function () {
3171 this.data=this.data.replace(RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>',
3172 'gi'), '');
3173};
3174/**
3175 @private
3176*/
3177Previewmaker.prototype.kill = function(opening, closing, subopening, subclosing, repl) {
3178 var oldk=this.data;
3179 var k=this.killStuff(this.data, opening, closing, subopening, subclosing, repl);
3180 while (k.length < oldk.length) {
3181 oldk=k;
3182 k=this.killStuff(k, opening, closing, subopening, subclosing, repl);
3183 }
3184 this.data=k;
3185};
3186/**
3187 @private
3188*/
3189Previewmaker.prototype.killStuff = function (txt, opening, closing, subopening, subclosing, repl) {
3190 var op=this.makeRegexp(opening);
3191 var cl=this.makeRegexp(closing, '^');
3192 var sb=subopening ? this.makeRegexp(subopening, '^') : null;
3193 var sc=subclosing ? this.makeRegexp(subclosing, '^') : cl;
3194 if (!op || !cl) {
3195 alert('Navigation Popups error: op or cl is null! something is wrong.');
3196 return;
3197 }
3198 if (!op.test(txt)) { return txt; }
3199 var ret='';
3200 var opResult = op.exec(txt);
3201 ret = txt.substring(0,opResult.index);
3202 txt=txt.substring(opResult.index+opResult[0].length);
3203 var depth = 1;
3204 while (txt.length > 0) {
3205 var removal=0;
3206 if (depth==1 && cl.test(txt)) {
3207 depth--;
3208 removal=cl.exec(txt)[0].length;
3209 } else if (depth > 1 && sc.test(txt)) {
3210 depth--;
3211 removal=sc.exec(txt)[0].length;
3212 }else if (sb && sb.test(txt)) {
3213 depth++;
3214 removal=sb.exec(txt)[0].length;
3215 }
3216 if ( !removal ) { removal = 1; }
3217 txt=txt.substring(removal);
3218 if (depth === 0) { break; }
3219 }
3220 return ret + (repl || '') + txt;
3221};
3222/**
3223 @private
3224*/
3225Previewmaker.prototype.makeRegexp = function (x, prefix, suffix) {
3226 prefix = prefix || '';
3227 suffix = suffix || '';
3228 var reStr='';
3229 var flags='';
3230 if (isString(x)) {
3231 reStr=prefix + literalizeRegex(x) + suffix;
3232 } else if (isRegExp(x)) {
3233 var s=x.toString().substring(1);
3234 var sp=s.split('/');
3235 flags=sp[sp.length-1];
3236 sp[sp.length-1]='';
3237 s=sp.join('/');
3238 s=s.substring(0,s.length-1);
3239 reStr= prefix + s + suffix;
3240 } else {
3241 log ('makeRegexp failed');
3242 }
3243
3244 log ('makeRegexp: got reStr=' + reStr + ', flags=' + flags);
3245 return RegExp(reStr, flags);
3246};
3247/**
3248 @private
3249*/
3250Previewmaker.prototype.killBoxTemplates = function () {
3251
3252 // taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general
3253 // also, have float_begin, ... float_end
3254 this.kill(RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /[}][}]\s*/, '{{');
3255
3256 // infoboxes etc
3257 // from [[User:Zyxw/popups.js]]: kill frames too
3258 this.kill(RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /[}][}]\s*/, '{{');
3259
3260};
3261/**
3262 @private
3263*/
3264Previewmaker.prototype.killTemplates = function () {
3265 this.kill('{{', '}}', '{', '}', ' ');
3266};
3267/**
3268 @private
3269*/
3270Previewmaker.prototype.killTables = function () {
3271 // tables are bad, too
3272 // this can be slow, but it's an inprovement over a browser hang
3273 // torture test: [[Comparison_of_Intel_Central_Processing_Units]]
3274 this.kill('{|', /[|]}\s*/, '{|');
3275 this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i);
3276 // remove lines starting with a pipe for the hell of it (?)
3277 this.data=this.data.replace(RegExp('^[|].*$', 'mg'), '');
3278};
3279/**
3280 @private
3281*/
3282Previewmaker.prototype.killImages = function () {
3283 var forbiddenNamespaceAliases = [];
3284 jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
3285 if (_namespaceId!=pg.nsImageId && _namespaceId!=pg.nsCategoryId) return;
3286 forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments!
3287 });
3288
3289 // images and categories are a nono
3290 this.kill(RegExp('[[][[]\\s*(' + forbiddenNamespaceAliases.join('|') + ')\\s*:', 'i'),
3291 /\]\]\s*/, '[', ']');
3292};
3293/**
3294 @private
3295*/
3296Previewmaker.prototype.killHTML = function () {
3297 // kill <ref ...>...</ref>
3298 this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i);
3299
3300 // let's also delete entire lines starting with <. it's worth a try.
3301 this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n');
3302
3303 // and those pesky html tags, but not <nowiki> or <blockquote>
3304 var splitted=this.data.parenSplit(/(<[\w\W]*?(?:>|$|(?=<)))/);
3305 var len=splitted.length;
3306 for (var i=1; i<len; i=i+2) {
3307 switch (splitted[i]) {
3308 case '<nowiki>':
3309 case '</nowiki>':
3310 case '<blockquote>':
3311 case '</blockquote>':
3312 break;
3313 default:
3314 splitted[i]='';
3315 }
3316 }
3317 this.data=splitted.join('');
3318};
3319/**
3320 @private
3321*/
3322Previewmaker.prototype.killChunks = function() { // heuristics alert
3323 // chunks of italic text? you crazy, man?
3324 var italicChunkRegex=new RegExp
3325 ("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g');
3326 // keep stuff separated, though, so stick in \n (fixes [[Union Jack]]?
3327 this.data=this.data.replace(italicChunkRegex, '\n');
3328};
3329/**
3330 @private
3331*/
3332Previewmaker.prototype.mopup = function () {
3333 // we simply *can't* be doing with horizontal rules right now
3334 this.data=this.data.replace(RegExp('^-{4,}','mg'),'');
3335
3336 // no indented lines
3337 this.data=this.data.replace(RegExp('(^|\\n) *:[^\\n]*','g'), '');
3338
3339 // replace __TOC__, __NOTOC__ and whatever else there is
3340 // this'll probably do
3341 this.data=this.data.replace(RegExp('^__[A-Z_]*__ *$', 'gmi'),'');
3342};
3343/**
3344 @private
3345*/
3346Previewmaker.prototype.firstBit = function () {
3347 // dont't be givin' me no subsequent paragraphs, you hear me?
3348 /// first we "normalize" section headings, removing whitespace after, adding before
3349 var d=this.data;
3350
3351 if (getValueOf('popupPreviewCutHeadings')) {
3352 this.data=this.data.replace(RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 ');
3353 /// then we want to get rid of paragraph breaks whose text ends badly
3354 this.data=this.data.replace(RegExp('([:;]) *\\n{2,}', 'g'), '$1\n');
3355
3356 this.data=this.data.replace(RegExp('^[\\s\\n]*'), '');
3357 var stuff=(RegExp('^([^\\n]|\\n[^\\n\\s])*')).exec(this.data);
3358 if (stuff) { d = stuff[0]; }
3359 if (!getValueOf('popupPreviewFirstParOnly')) { d = this.data; }
3360
3361 /// now put \n\n after sections so that bullets and numbered lists work
3362 d=d.replace(RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n');
3363 }
3364
3365
3366 // Split sentences. Superfluous sentences are RIGHT OUT.
3367 // note: exactly 1 set of parens here needed to make the slice work
3368 d = d.parenSplit(RegExp('([!?.]+["'+"'"+']*\\s)','g'));
3369 // leading space is bad, mmkay?
3370 d[0]=d[0].replace(RegExp('^\\s*'), '');
3371
3372 var notSentenceEnds=RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i');
3373 d = this.fixSentenceEnds(d, notSentenceEnds);
3374
3375 this.fullLength=d.join('').length;
3376 var n=this.maxSentences;
3377 var dd=this.firstSentences(d,n);
3378
3379 do {
3380 dd=this.firstSentences(d,n); --n;
3381 } while ( dd.length > this.maxCharacters && n !== 0 );
3382
3383 this.data = dd;
3384};
3385/**
3386 @private
3387*/
3388Previewmaker.prototype.fixSentenceEnds = function(strs, reg) {
3389 // take an array of strings, strs
3390 // join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg
3391
3392 for (var i=0; i<strs.length-2; ++i) {
3393 if (reg.test(strs[i])) {
3394 var a=[];
3395 for (var j=0; j<strs.length; ++j) {
3396 if (j<i) a[j]=strs[j];
3397 if (j==i) a[i]=strs[i]+strs[i+1]+strs[i+2];
3398 if (j>i+2) a[j-2]=strs[j];
3399 }
3400 return this.fixSentenceEnds(a,reg);
3401 }
3402 }
3403 return strs;
3404};
3405/**
3406 @private
3407*/
3408Previewmaker.prototype.firstSentences = function(strs, howmany) {
3409 var t=strs.slice(0, 2*howmany);
3410 return t.join('');
3411};
3412/**
3413 @private
3414*/
3415Previewmaker.prototype.killBadWhitespace = function() {
3416 // also cleans up isolated '''', eg [[Suntory Sungoliath]]
3417 this.data=this.data.replace(RegExp('^ *\'+ *$', 'gm'), '');
3418};
3419/**
3420 Runs the various methods to generate the preview.
3421 The preview is stored in the <code>html</html> field.
3422 @private
3423*/
3424Previewmaker.prototype.makePreview = function() {
3425 if (this.owner.article.namespaceId()!=pg.nsTemplateId &&
3426 this.owner.article.namespaceId()!=pg.nsImageId ) {
3427 this.killComments();
3428 this.killDivs();
3429 this.killGalleries();
3430 this.killBoxTemplates();
3431
3432 if (getValueOf('popupPreviewKillTemplates')) {
3433 this.killTemplates();
3434 } else {
3435 this.killMultilineTemplates();
3436 }
3437 this.killTables();
3438 this.killImages();
3439 this.killHTML();
3440 this.killChunks();
3441 this.mopup();
3442
3443 this.firstBit();
3444 this.killBadWhitespace();
3445 }
3446 else
3447 {
3448 this.killHTML();
3449 }
3450 this.html=wiki2html(this.data, this.baseUrl); // needs livepreview
3451 this.fixHTML();
3452 this.stripLongTemplates();
3453};
3454/**
3455 @private
3456*/
3457Previewmaker.prototype.esWiki2HtmlPart = function(data) {
3458 var reLinks = /(?:\[\[([^|\]]*)(?:\|([^|\]]*))*]]([a-z]*))/gi; //match a wikilink
3459 reLinks.lastIndex = 0; //reset regex
3460
3461 var match;
3462 var result = "";
3463 var postfixIndex = 0;
3464 while ((match = reLinks.exec(data))) //match all wikilinks
3465 {
3466 //FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases.
3467 result += pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)) +
3468 '<a href="'+Insta.conf.paths.articles+pg.escapeQuotesHTML(match[1])+'">'+pg.escapeQuotesHTML((match[2]?match[2]:match[1])+match[3])+"</a>";
3469 postfixIndex = reLinks.lastIndex;
3470 }
3471 //append the rest
3472 result += pg.escapeQuotesHTML(data.substring(postfixIndex));
3473
3474 return result;
3475};
3476Previewmaker.prototype.editSummaryPreview=function() {
3477 var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker
3478 reAes.lastIndex = 0; //reset regex
3479
3480 var match;
3481
3482 match = reAes.exec(this.data);
3483 if (match)
3484 {
3485 //we have a section link. Split it, process it, combine it.
3486 var prefix = this.data.substring(0,match.index-1);
3487 var section = match[1];
3488 var postfix = this.data.substring(reAes.lastIndex);
3489
3490 var start = "<span class='autocomment'>";
3491 var end = "</span>";
3492 if (prefix.length>0) start = this.esWiki2HtmlPart(prefix) + " " + start + "- ";
3493 if (postfix.length>0) end = ": " + end + this.esWiki2HtmlPart(postfix);
3494
3495
3496 var t=new Title().fromURL(this.baseUrl);
3497 t.anchorFromUtf(section);
3498 var sectionLink = Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)) + '#' + pg.escapeQuotesHTML(t.anchor);
3499 return start + '<a href="'+sectionLink+'">→</a> '+pg.escapeQuotesHTML(section) + end;
3500 }
3501
3502 //else there's no section link, htmlify the whole thing.
3503 return this.esWiki2HtmlPart(this.data);
3504};
3505
3506//<NOLITE>
3507/** Test function for debugging preview problems one step at a time.
3508 */
3509/*eslint-disable */
3510function previewSteps(txt) {
3511 try {
3512 txt=txt || document.editform.wpTextbox1.value;
3513 } catch (err) {
3514 if (pg.cache.pages.length > 0) {
3515 txt=pg.cache.pages[pg.cache.pages.length-1].data;
3516 } else {
3517 alert('provide text or use an edit page');
3518 }
3519 }
3520 txt=txt.substring(0,10000);
3521 var base=pg.wiki.articlebase + Title.fromURL(document.location.href).urlString();
3522 var p=new Previewmaker(txt, base, pg.current.link.navpopup);
3523 if (this.owner.article.namespaceId() != pg.nsTemplateId) {
3524 p.killComments(); if (!confirm('done killComments(). Continue?\n---\n' + p.data)) { return; }
3525 p.killDivs(); if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) { return; }
3526 p.killGalleries(); if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) { return; }
3527 p.killBoxTemplates(); if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) { return; }
3528
3529 if (getValueOf('popupPreviewKillTemplates')) {
3530 p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; }
3531 } else {
3532 p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; }
3533 }
3534
3535 p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; }
3536 p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; }
3537 p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; }
3538 p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; }
3539 p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; }
3540
3541 p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; }
3542 p.killBadWhitespace(); if (!confirm('done killBadWhitespace(). Continue?\n---\n' + p.data)) { return; }
3543 }
3544
3545 p.html=wiki2html(p.data, base); // needs livepreview
3546 p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; }
3547 p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; }
3548 alert('finished preview - end result follows.\n---\n' + p.html);
3549}
3550/*eslint-enable */
3551//</NOLITE>
3552
3553/**
3554 Works around livepreview bugs.
3555 @private
3556*/
3557Previewmaker.prototype.fixHTML = function() {
3558 if(!this.html) return;
3559
3560 var ret = this.html;
3561
3562 // fix question marks in wiki links
3563 // maybe this'll break some stuff :-(
3564 ret=ret.replace(RegExp('(<a href="' + pg.wiki.articlePath + '/[^"]*)[?](.*?")', 'g'), '$1%3F$2');
3565 ret=ret.replace(RegExp('(<a href=\'' + pg.wiki.articlePath + '/[^\']*)[?](.*?\')', 'g'), '$1%3F$2');
3566 // FIXME fix up % too
3567
3568
3569 this.html=ret;
3570};
3571/**
3572 Generates the preview and displays it in the current popup.
3573
3574 Does nothing if the generated preview is invalid or consists of whitespace only.
3575 Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true.
3576*/
3577Previewmaker.prototype.showPreview = function () {
3578 this.makePreview();
3579 if (typeof this.html != typeof '') return;
3580 if (RegExp('^\\s*$').test(this.html)) return;
3581 setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber);
3582 setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, { owner: this.owner });
3583 var more = (this.fullLength > this.data.length) ? this.moreLink() : '';
3584 setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber);
3585};
3586/**
3587 @private
3588*/
3589Previewmaker.prototype.moreLink=function() {
3590 var a=document.createElement('a');
3591 a.className='popupMoreLink';
3592 a.innerHTML=popupString('more...');
3593 var savedThis=this;
3594 a.onclick=function() {
3595 savedThis.maxCharacters+=12000;
3596 savedThis.maxSentences+=120;
3597 savedThis.setData();
3598 savedThis.showPreview();
3599 };
3600 return a;
3601};
3602
3603/**
3604 @private
3605*/
3606Previewmaker.prototype.stripLongTemplates = function() {
3607 // operates on the HTML!
3608 this.html=this.html.replace(RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), '');
3609 this.html=this.html.split('\n').join(' '); // workaround for <pre> templates
3610 this.html=this.html.replace(RegExp('[{][{][^}]*<pre>[^}]*[}][}]','gi'), '');
3611};
3612/**
3613 @private
3614*/
3615Previewmaker.prototype.killMultilineTemplates = function() {
3616 this.kill('{{{', '}}}');
3617 this.kill(RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{');
3618};
3619// ENDFILE: previewmaker.js
3620// STARTFILE: querypreview.js
3621function loadAPIPreview(queryType, article, navpop) {
3622 var art=new Title(article).urlString();
3623 var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';
3624 var htmlGenerator=function(/*a, d*/){alert('invalid html generator');};
3625 var usernameart = '';
3626 switch (queryType) {
3627 case 'history':
3628 url += 'titles=' + art + '&prop=revisions&rvlimit=' +
3629 getValueOf('popupHistoryPreviewLimit');
3630 htmlGenerator=APIhistoryPreviewHTML;
3631 break;
3632 case 'category':
3633 url += 'list=categorymembers&cmtitle=' + art;
3634 htmlGenerator=APIcategoryPreviewHTML;
3635 break;
3636 case 'userinfo':
3637 var username = new Title( article ).userName();
3638 usernameart = encodeURIComponent( username );
3639 if (pg.re.ipUser.test(username)) {
3640 url += 'list=blocks&bkprop=range&bkip=' + usernameart;
3641 } else {
3642 url += 'list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=' + usernameart + "&meta=globaluserinfo&guiprop=groups|unattached&guiuser="+ usernameart + "&uclimit=1&ucprop=timestamp&ucuser=" + usernameart;
3643 }
3644 htmlGenerator=APIuserInfoPreviewHTML;
3645 break;
3646 case 'contribs':
3647 usernameart = encodeURIComponent( new Title( article ).userName() );
3648 url += 'list=usercontribs&ucuser=' + usernameart +
3649 '&uclimit=' + getValueOf('popupContribsPreviewLimit');
3650 htmlGenerator=APIcontribsPreviewHTML;
3651 break;
3652 case 'imagepagepreview':
3653 var trail='';
3654 if (getValueOf('popupImageLinks')) { trail = '&list=imageusage&iutitle=' + art; }
3655 url += 'titles=' + art + '&prop=revisions|imageinfo&rvprop=content' + trail;
3656 htmlGenerator=APIimagepagePreviewHTML;
3657 break;
3658 case 'backlinks':
3659 url += 'list=backlinks&bltitle=' + art;
3660 htmlGenerator=APIbacklinksPreviewHTML;
3661 break;
3662 case 'revision':
3663 if (article.oldid) {
3664 url += 'revids=' + article.oldid;
3665 } else {
3666 url += 'titles=' + article.removeAnchor().urlString();
3667 }
3668 url += '&prop=revisions|pageprops|info|images|categories&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max';
3669 htmlGenerator=APIrevisionPreviewHTML;
3670 break;
3671 }
3672 pendingNavpopTask(navpop);
3673 var callback=function(d){
3674 log( "callback of API functions was hit" );
3675 showAPIPreview(queryType, htmlGenerator(article,d,navpop), navpop.idNumber, navpop, d);
3676 };
3677 var go = function(){
3678 getPageWithCaching(url, callback, navpop);
3679 return true;
3680 };
3681
3682 if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
3683 else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_'+queryType+'_QUERY_DATA'); }
3684}
3685
3686function linkList(list) {
3687 list.sort(function(x,y) { return (x==y ? 0 : (x<y ? -1 : 1)); });
3688 var buf=[];
3689 for (var i=0; i<list.length; ++i) {
3690 buf.push(wikiLink({article: new Title(list[i]),
3691 text: list[i].split(' ').join(' '),
3692 action: 'view'}));
3693 }
3694 return buf.join(', ');
3695}
3696
3697function getTimeOffset() {
3698 var tz = mw.user.options.get('timecorrection');
3699
3700 if(tz) {
3701 if( tz.indexOf('|') > -1 ) {
3702 // New format
3703 return parseInt(tz.split('|')[1],10);
3704 } else if ( tz.indexOf(':') > -1 ) {
3705 // Old format
3706 return( parseInt(tz,10)*60 + parseInt(tz.split(':')[1],10) );
3707 }
3708 }
3709 return 0;
3710}
3711
3712/*
3713 * Creates a HTML table that's shown in the history and user-contribs popups.
3714 * @param {Object[]} h - a list of revisions, returned from the API
3715 * @param {boolean} reallyContribs - true only if we're displaying user contributions
3716 */
3717function editPreviewTable(article, h, reallyContribs, timeOffset) {
3718 var html=['<table>'];
3719 var day=null;
3720 var curart=article;
3721 var page=null;
3722
3723 var makeFirstColumnLinks;
3724 if(reallyContribs) {
3725
3726 // We're showing user contributions, so make (diff | hist) links
3727 makeFirstColumnLinks = function(currentRevision) {
3728 var result = '(';
3729 result += '<a href="' + pg.wiki.titlebase +
3730 new Title(currentRevision.title).urlString() + '&diff=prev' +
3731 '&oldid=' + currentRevision.revid + '">' + popupString('diff') + '</a>';
3732 result += ' | ';
3733 result += '<a href="' + pg.wiki.titlebase +
3734 new Title(currentRevision.title).urlString() + '&action=history">' +
3735 popupString('hist') + '</a>';
3736 result += ')';
3737 return result;
3738 };
3739 } else {
3740
3741 // It's a regular history page, so make (cur | last) links
3742 var firstRevid = h[0].revid;
3743 makeFirstColumnLinks = function(currentRevision) {
3744 var result = '(';
3745 result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() +
3746 '&diff=' + firstRevid + '&oldid=' + currentRevision.revid + '">' + popupString('cur') + '</a>';
3747 result += ' | ';
3748 result += '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() +
3749 '&diff=prev&oldid=' + currentRevision.revid + '">' + popupString('last') + '</a>';
3750 result += ')';
3751 return result;
3752 };
3753 }
3754
3755 for (var i=0; i<h.length; ++i) {
3756 if (reallyContribs) {
3757 page = h[i].title;
3758 curart = new Title(page);
3759 }
3760 var minor = h[i].minor ? '<b>m </b>' : '';
3761 var editDate = adjustDate(getDateFromTimestamp(h[i].timestamp), timeOffset);
3762 var thisDay = dayFormat(editDate);
3763 var thisTime = timeFormat(editDate);
3764 if (thisDay == day) {
3765 thisDay = '';
3766 } else {
3767 day = thisDay;
3768 }
3769 if (thisDay) {
3770 html.push( '<tr><td colspan=3><span class="popup_history_date">' +
3771 thisDay+'</span></td></tr>' );
3772 }
3773 html.push('<tr class="popup_history_row_' + ( (i%2) ? 'odd' : 'even') + '">');
3774 html.push('<td>' + makeFirstColumnLinks(h[i]) + '</td>');
3775 html.push('<td>' +
3776 '<a href="' + pg.wiki.titlebase + new Title(curart).urlString() +
3777 '&oldid=' + h[i].revid + '">' + thisTime + '</a></td>');
3778 var col3url='', col3txt='';
3779 if (!reallyContribs) {
3780 var user=h[i].user;
3781 if( !h[i].userhidden ) {
3782 if( pg.re.ipUser.test(user) ) {
3783 col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId] + ':Contributions&target=' + new Title(user).urlString();
3784 } else {
3785 col3url=pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsUserId] + ':' + new Title(user).urlString();
3786 }
3787 col3txt=pg.escapeQuotesHTML(user);
3788 } else {
3789 col3url=getValueOf('popupRevDelUrl');
3790 col3txt=pg.escapeQuotesHTML( popupString('revdel'));
3791 }
3792 } else {
3793 col3url=pg.wiki.titlebase + curart.urlString();
3794 col3txt=pg.escapeQuotesHTML(page);
3795 }
3796 html.push('<td>' + (reallyContribs ? minor : '') +
3797 '<a href="' + col3url + '">' + col3txt + '</a></td>');
3798 var comment='';
3799 var c=h[i].comment || h[i].content;
3800 if (c) {
3801 comment=new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview();
3802 } else if ( h[i].commenthidden ) {
3803 comment=popupString('revdel');
3804 }
3805 html.push('<td>' + (!reallyContribs ? minor : '') + comment + '</td>');
3806 html.push('</tr>');
3807 html=[html.join('')];
3808 }
3809 html.push('</table>');
3810 return html.join('');
3811}
3812
3813function getDateFromTimestamp(t) {
3814 var s=t.split(/[^0-9]/);
3815 switch(s.length) {
3816 case 0: return null;
3817 case 1: return new Date(s[0]);
3818 case 2: return new Date(s[0], s[1]-1);
3819 case 3: return new Date(s[0], s[1]-1, s[2]);
3820 case 4: return new Date(s[0], s[1]-1, s[2], s[3]);
3821 case 5: return new Date(s[0], s[1]-1, s[2], s[3], s[4]);
3822 case 6: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5]);
3823 default: return new Date(s[0], s[1]-1, s[2], s[3], s[4], s[5], s[6]);
3824 }
3825}
3826
3827function adjustDate(d, offset) {
3828 // offset is in minutes
3829 var o=offset * 60 * 1000;
3830 return new Date( +d + o);
3831}
3832
3833function dayFormat(editDate, utc) {
3834 if (utc) { return map(zeroFill, [editDate.getUTCFullYear(), editDate.getUTCMonth()+1, editDate.getUTCDate()]).join('-'); }
3835 return map(zeroFill, [editDate.getFullYear(), editDate.getMonth()+1, editDate.getDate()]).join('-');
3836}
3837
3838function timeFormat(editDate, utc) {
3839 if (utc) { return map(zeroFill, [editDate.getUTCHours(), editDate.getUTCMinutes(), editDate.getUTCSeconds()]).join(':'); }
3840 return map(zeroFill, [editDate.getHours(), editDate.getMinutes(), editDate.getSeconds()]).join(':');
3841}
3842
3843function showAPIPreview(queryType, html, id, navpop, download) {
3844 // DJ: done
3845 var target='popupPreview';
3846 completedNavpopTask(navpop);
3847
3848 switch (queryType) {
3849 case 'imagelinks':
3850 case 'category':
3851 case 'userinfo':
3852 target='popupPostPreview'; break;
3853 case 'revision':
3854 insertPreview(download);
3855 return;
3856 }
3857 setPopupTipsAndHTML(html, target, id);
3858}
3859
3860function APIrevisionPreviewHTML(article, download) {
3861 try{
3862 var jsObj=getJsObj(download.data);
3863 var page=anyChild(jsObj.query.pages);
3864 if( page.missing ) {
3865 // TODO we need to fix this proper later on
3866 download.owner = null;
3867 return;
3868 }
3869 var content = (
3870 page &&
3871 page.revisions &&
3872 page.revisions[0].contentmodel === 'wikitext'
3873 ) ? page.revisions[0].content : null;
3874 if( typeof content === 'string' )
3875 {
3876 download.data = content;
3877 download.lastModified = new Date(page.revisions[0].timestamp);
3878 }
3879 } catch(someError) {
3880 return 'Revision preview failed :(';
3881 }
3882}
3883
3884function APIbacklinksPreviewHTML(article, download/*, navpop*/ ) {
3885 try {
3886 var jsObj=getJsObj(download.data);
3887 var list=jsObj.query.backlinks;
3888
3889 var html=[];
3890 if (!list) { return popupString('No backlinks found'); }
3891 for ( var i=0; i < list.length; i++ ) {
3892 var t=new Title(list[i].title);
3893 html.push('<a href="' + pg.wiki.titlebase + t.urlString() + '">' + t + '</a>');
3894 }
3895 html=html.join(', ');
3896 if (jsObj['continue'] && jsObj['continue'].blcontinue) {
3897 html += popupString(' and more');
3898 }
3899 return html;
3900 } catch (someError) {
3901 return 'backlinksPreviewHTML went wonky';
3902 }
3903}
3904
3905pg.fn.APIsharedImagePagePreviewHTML = function APIsharedImagePagePreviewHTML(obj) {
3906 log( "APIsharedImagePagePreviewHTML" );
3907 var popupid = obj.requestid;
3908 if( obj.query && obj.query.pages )
3909 {
3910 var page=anyChild(obj.query.pages );
3911 var content = (
3912 page &&
3913 page.revisions &&
3914 page.revisions[0].contentmodel === 'wikitext'
3915 ) ? page.revisions[0].content : null;
3916 if( typeof content === 'string' )
3917 {
3918 /* Not entirely safe, but the best we can do */
3919 var p=new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup);
3920 p.makePreview();
3921 setPopupHTML( p.html, "popupSecondPreview", popupid );
3922 }
3923 }
3924};
3925
3926function APIimagepagePreviewHTML(article, download, navpop) {
3927 try {
3928 var jsObj=getJsObj(download.data);
3929 var page=anyChild(jsObj.query.pages);
3930 var content= (
3931 page &&
3932 page.revisions &&
3933 page.revisions[0].contentmodel === 'wikitext'
3934 ) ? page.revisions[0].content : null;
3935 var ret='';
3936 var alt='';
3937 try{alt=navpop.parentAnchor.childNodes[0].alt;} catch(e){}
3938 if (alt) {
3939 ret = ret + '<hr /><b>' + popupString('Alt text:') + '</b> ' + pg.escapeQuotesHTML(alt);
3940 }
3941 if (typeof content === 'string') {
3942 var p=prepPreviewmaker(content, article, navpop);
3943 p.makePreview();
3944 if (p.html) { ret += '<hr />' + p.html; }
3945 if (getValueOf('popupSummaryData')) {
3946 var info=getPageInfo(content, download);
3947 log(info);
3948 setPopupTrailer(info, navpop.idNumber);
3949 }
3950 }
3951 if (page && page.imagerepository == "shared" ) {
3952 var art=new Title(article);
3953 var encart = encodeURIComponent( "File:" + art.stripNamespace() );
3954 var shared_url = pg.wiki.apicommonsbase + '?format=json&formatversion=2' +
3955 '&callback=pg.fn.APIsharedImagePagePreviewHTML' +
3956 '&requestid=' + navpop.idNumber +
3957 '&action=query&prop=revisions&rvprop=content&titles=' + encart;
3958
3959 ret = ret +'<hr />' + popupString( 'Image from Commons') +
3960 ': <a href="' + pg.wiki.commonsbase + '?title=' + encart + '">' +
3961 popupString( 'Description page') + '</a>';
3962 mw.loader.load( shared_url );
3963 }
3964 showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article,download), navpop.idNumber, download);
3965 return ret;
3966 } catch (someError) {
3967 return 'API imagepage preview failed :(';
3968 }
3969}
3970
3971function APIimagelinksPreviewHTML(article, download) {
3972 try {
3973 var jsobj=getJsObj(download.data);
3974 var list=jsobj.query.imageusage;
3975 if (list) {
3976 var ret=[];
3977 for (var i=0; i < list.length; i++) {
3978 ret.push(list[i].title);
3979 }
3980 if (ret.length === 0) { return popupString('No image links found'); }
3981 return '<h2>' + popupString('File links') + '</h2>' + linkList(ret);
3982 } else {
3983 return popupString('No image links found');
3984 }
3985 } catch(someError) {
3986 return 'Image links preview generation failed :(';
3987 }
3988}
3989
3990function APIcategoryPreviewHTML(article, download) {
3991 try{
3992 var jsobj=getJsObj(download.data);
3993 var list=jsobj.query.categorymembers;
3994 var ret=[];
3995 for (var p=0; p < list.length; p++) {
3996 ret.push(list[p].title);
3997 }
3998 if (ret.length === 0) { return popupString('Empty category'); }
3999 ret = '<h2>' + tprintf('Category members (%s shown)', [ret.length]) + '</h2>' +linkList(ret);
4000 if (jsobj['continue'] && jsobj['continue'].cmcontinue) {
4001 ret += popupString(' and more');
4002 }
4003 return ret;
4004 } catch(someError) {
4005 return 'Category preview failed :(';
4006 }
4007}
4008
4009function APIuserInfoPreviewHTML(article, download) {
4010 var ret=[];
4011 var queryobj = {};
4012 try{
4013 queryobj=getJsObj(download.data).query;
4014 } catch(someError) { return 'Userinfo preview failed :('; }
4015
4016 var user=anyChild(queryobj.users);
4017 if (user) {
4018 var globaluserinfo=queryobj.globaluserinfo;
4019 if (user.invalid === '') {
4020 ret.push( popupString( 'Invalid user') );
4021 } else if (user.missing === '') {
4022 ret.push( popupString( 'Not a registered username') );
4023 }
4024 if( user.blockedby )
4025 ret.push('<b>' + popupString('BLOCKED') + '</b>');
4026 if( globaluserinfo && ( 'locked' in globaluserinfo || 'hidden' in globaluserinfo ) ) {
4027 var lockedSulAccountIsAttachedToThis = true;
4028 for( var i=0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) {
4029 if ( globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname') ) {
4030 lockedSulAccountIsAttachedToThis=false;
4031 break;
4032 }
4033 }
4034 if (lockedSulAccountIsAttachedToThis) {
4035 if ( 'locked' in globaluserinfo ) ret.push('<b><i>' + popupString('LOCKED') + '</i></b>');
4036 if ( 'hidden' in globaluserinfo ) ret.push('<b><i>' + popupString('HIDDEN') + '</i></b>');
4037 }
4038 }
4039 if( getValueOf('popupShowGender') && user.gender ) {
4040 switch( user.gender ) {
4041 case "male": ret.push( popupString( "\u2642" ) ); break;
4042 case "female": ret.push( popupString( "\u2640" ) ); break;
4043 }
4044 }
4045 if( user.groups ) {
4046 for( var j=0; j < user.groups.length; j++) {
4047 var currentGroup = user.groups[j];
4048 if( ["*", "user", "autoconfirmed", "extendedconfirmed"].indexOf( currentGroup ) === -1 ) {
4049 ret.push( pg.escapeQuotesHTML(user.groups[j]) );
4050 }
4051 }
4052 }
4053 if( globaluserinfo && globaluserinfo.groups ) {
4054 for( var k=0; k < globaluserinfo.groups.length; k++) {
4055 ret.push( '<i>'+pg.escapeQuotesHTML(globaluserinfo.groups[k])+'</i>' );
4056 }
4057 }
4058 if( user.registration )
4059 ret.push( pg.escapeQuotesHTML((user.editcount?user.editcount:'0') + popupString(' edits since: ') + (user.registration?dayFormat(getDateFromTimestamp(user.registration)):'')) );
4060 }
4061
4062 if (queryobj.usercontribs && queryobj.usercontribs.length) {
4063 ret.push( popupString('last edit on ') + dayFormat(getDateFromTimestamp(queryobj.usercontribs[0].timestamp)) );
4064 }
4065
4066 if (queryobj.blocks) {
4067 ret.push( popupString( 'IP user') ); //we only request list=blocks for IPs
4068 for (var l=0; l<queryobj.blocks.length; l++) {
4069 ret.push('<b>' + popupString(queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCKED' : 'RANGEBLOCKED') + '</b>' );
4070 }
4071 }
4072
4073 ret = '<hr />' + ret.join( ', ' );
4074 return ret;
4075}
4076
4077function APIcontribsPreviewHTML(article, download, navpop) {
4078 return APIhistoryPreviewHTML(article, download, navpop, true);
4079}
4080
4081function APIhistoryPreviewHTML(article, download, navpop, reallyContribs) {
4082 try {
4083 var jsobj=getJsObj(download.data);
4084 var edits = [];
4085 if( reallyContribs ) {
4086 edits=jsobj.query.usercontribs;
4087 } else {
4088 edits=anyChild(jsobj.query.pages).revisions;
4089 }
4090
4091 var ret=editPreviewTable(article, edits, reallyContribs, getTimeOffset());
4092 return ret;
4093 } catch (someError) {
4094 return 'History preview failed :-(';
4095 }
4096}
4097
4098
4099//</NOLITE>
4100// ENDFILE: querypreview.js
4101// STARTFILE: debug.js
4102////////////////////////////////////////////////////////////////////
4103// Debugging functions
4104////////////////////////////////////////////////////////////////////
4105
4106function setupDebugging() {
4107//<NOLITE>
4108 if (window.popupDebug) { // popupDebug is set from .version
4109 window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; };
4110 window.console.log(x);
4111 };
4112 window.errlog=function(x) {
4113 window.console.error(x);
4114 };
4115 log('Initializing logger');
4116 } else {
4117//</NOLITE>
4118 window.log = function() {};
4119 window.errlog = function() {};
4120//<NOLITE>
4121 }
4122//</NOLITE>
4123}
4124// ENDFILE: debug.js
4125// STARTFILE: images.js
4126
4127// load image of type Title.
4128function loadImage(image, navpop) {
4129 if (typeof image.stripNamespace != 'function') { alert('loadImages bad'); }
4130 // API call to retrieve image info.
4131
4132 if ( !getValueOf('popupImages') ) return;
4133 if ( !isValidImageName(image) ) return false;
4134
4135 var art=image.urlString();
4136
4137 var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query';
4138 url += '&prop=imageinfo&iiprop=url|mime&iiurlwidth=' + getValueOf('popupImageSizeLarge');
4139 url += '&titles=' + art;
4140
4141 pendingNavpopTask(navpop);
4142 var callback=function(d){
4143 popupsInsertImage(navpop.idNumber, navpop, d);
4144 };
4145 var go = function(){
4146 getPageWithCaching(url, callback, navpop);
4147 return true;
4148 };
4149 if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
4150 else { navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA'); }
4151
4152}
4153
4154function popupsInsertImage(id, navpop, download) {
4155 log( "popupsInsertImage");
4156 var imageinfo;
4157 try {
4158 var jsObj=getJsObj(download.data);
4159 var imagepage=anyChild(jsObj.query.pages);
4160 if (typeof imagepage.imageinfo === 'undefined') return;
4161 imageinfo = imagepage.imageinfo[0];
4162 } catch (someError) {
4163 log( "popupsInsertImage failed :(" );
4164 return;
4165 }
4166
4167 var popupImage = document.getElementById("popupImg"+id);
4168 if (!popupImage) {
4169 log( "could not find insertion point for image");
4170 return;
4171 }
4172
4173 popupImage.width=getValueOf('popupImageSize');
4174 popupImage.style.display='inline';
4175
4176 // Set the source for the image.
4177 if( imageinfo.thumburl )
4178 popupImage.src=imageinfo.thumburl;
4179 else if( imageinfo.mime.indexOf("image") === 0 ){
4180 popupImage.src=imageinfo.url;
4181 log( "a thumb could not be found, using original image" );
4182 } else log( "fullsize imagethumb, but not sure if it's an image");
4183
4184
4185 var a=document.getElementById("popupImageLink"+id);
4186 if (a === null) { return null; }
4187
4188 // Determine the action of the surrouding imagelink.
4189 switch (getValueOf('popupThumbAction')) {
4190 case 'imagepage':
4191 if (pg.current.article.namespaceId()!=pg.nsImageId) {
4192 a.href=imageinfo.descriptionurl;
4193 // FIXME: unreliable pg.idNumber
4194 popTipsSoonFn('popupImage' + id)();
4195 break;
4196 }
4197 /* falls through */
4198 case 'sizetoggle':
4199 a.onclick=toggleSize;
4200 a.title=popupString('Toggle image size');
4201 return;
4202 case 'linkfull':
4203 a.href = imageinfo.url;
4204 a.title=popupString('Open full-size image');
4205 return;
4206 }
4207
4208}
4209
4210// Toggles the image between inline small and navpop fullwidth.
4211// It's the same image, no actual sizechange occurs, only display width.
4212function toggleSize() {
4213 var imgContainer=this;
4214 if (!imgContainer) {
4215 alert('imgContainer is null :/');
4216 return;
4217 }
4218 var img=imgContainer.firstChild;
4219 if (!img) {
4220 alert('img is null :/');
4221 return;
4222 }
4223
4224 if (!img.style.width || img.style.width==='') {
4225 img.style.width='100%';
4226 } else {
4227 img.style.width='';
4228 }
4229}
4230
4231// Returns one title of an image from wikiText.
4232function getValidImageFromWikiText(wikiText) {
4233 // nb in pg.re.image we're interested in the second bracketed expression
4234 // this may change if the regex changes :-(
4235 //var match=pg.re.image.exec(wikiText);
4236 var matched=null;
4237 var match;
4238 // strip html comments, used by evil bots :-(
4239 var t = removeMatchesUnless(wikiText, RegExp('(<!--[\\s\\S]*?-->)'), 1,
4240 RegExp('^<!--[^[]*popup', 'i'));
4241
4242 while ( ( match = pg.re.image.exec(t) ) ) {
4243 // now find a sane image name - exclude templates by seeking {
4244 var m = match[2] || match[6];
4245 if ( isValidImageName(m) ) {
4246 matched=m;
4247 break;
4248 }
4249 }
4250 pg.re.image.lastIndex=0;
4251 if (!matched) { return null; }
4252 return mw.config.get('wgFormattedNamespaces')[pg.nsImageId]+':'+upcaseFirst(matched);
4253}
4254
4255function removeMatchesUnless(str, re1, parencount, re2) {
4256 var split=str.parenSplit(re1);
4257 var c=parencount + 1;
4258 for (var i=0; i<split.length; ++i) {
4259 if ( i%c === 0 || re2.test(split[i]) ) { continue; }
4260 split[i]='';
4261 }
4262 return split.join('');
4263}
4264
4265//</NOLITE>
4266// ENDFILE: images.js
4267// STARTFILE: namespaces.js
4268// Set up namespaces and other non-strings.js localization
4269// (currently that means redirs too)
4270
4271function setNamespaces() {
4272 pg.nsSpecialId = -1;
4273 pg.nsMainspaceId = 0;
4274 pg.nsImageId = 6;
4275 pg.nsUserId = 2;
4276 pg.nsUsertalkId = 3;
4277 pg.nsCategoryId = 14;
4278 pg.nsTemplateId = 10;
4279}
4280
4281
4282function setRedirs() {
4283 var r='redirect';
4284 var R='REDIRECT';
4285 var redirLists={
4286//<NOLITE>
4287 'ar': [ R, 'تØÙˆÙŠÙ„' ],
4288 'be': [ r, 'перанакіраваньне' ],
4289 'bg': [ r, 'пренаÑочване', 'виж' ],
4290 'bs': [ r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI' ],
4291 'cs': [ R, 'PŘESMĚRUJ' ],
4292 'cy': [ r, 'ail-cyfeirio' ],
4293 'de': [ R, 'WEITERLEITUNG' ],
4294 'el': [ R, 'ΑÎΑΚΑΤΕΥΘΥÎΣΗ'],
4295 'eo': [ R, 'ALIDIREKTU', 'ALIDIREKTI' ],
4296 'es': [ R, 'REDIRECCIÓN' ],
4297 'et': [ r, 'suuna' ],
4298 'ga': [ r, 'athsheoladh' ],
4299 'gl': [ r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'],
4300 'he': [ R, '×”×¤× ×™×”' ],
4301 'hu': [ R, 'ÃTIRÃNYÃTÃS' ],
4302 'is': [ r, 'tilvÃsun', 'TILVÃSUN' ],
4303 'it': [ R, 'RINVIA', 'Rinvia'],
4304 'ja': [ R, '転é€' ],
4305 'mk': [ r, 'пренаÑочување', 'види' ],
4306 'nds': [ r, 'wiederleiden' ],
4307 'nl': [ R, 'DOORVERWIJZING' ],
4308 'nn': [ r, 'omdiriger' ],
4309 'pl': [ R, 'PATRZ', 'PRZEKIERUJ', 'TAM' ],
4310 'pt': [ R, 'redir' ],
4311 'ru': [ R, 'ПЕРЕÐÐПРÐВЛЕÐИЕ', 'ПЕРЕÐÐПР' ],
4312 'sk': [ r, 'presmeruj' ],
4313 'sr': [ r, 'ПреуÑмери', 'преуÑмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI' ],
4314 'tt': [ R, 'yünältü', 'перенаправление', 'перенапр' ],
4315 'uk': [ R, 'ПЕРЕÐÐПРÐВЛЕÐÐЯ', 'ПЕРЕÐÐПР' ],
4316 'vi': [ r, 'đổi' ],
4317 'zh': [ R, 'é‡å®šå‘'] // no comma
4318//</NOLITE>
4319 };
4320 var redirList=redirLists[ pg.wiki.lang ] || [r, R];
4321 // Mediawiki is very tolerant about what comes after the #redirect at the start
4322 pg.re.redirect=RegExp('^\\s*[#](' + redirList.join('|') + ').*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)', 'i');
4323}
4324
4325function setInterwiki() {
4326 if (pg.wiki.wikimedia) {
4327 // From https://meta.wikimedia.org/wiki/List_of_Wikipedias
4328 pg.wiki.interwiki='aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu';
4329 pg.re.interwiki=RegExp('^'+pg.wiki.interwiki+':');
4330 } else {
4331 pg.wiki.interwiki=null;
4332 pg.re.interwiki=RegExp('^$');
4333 }
4334}
4335
4336// return a regexp pattern matching all variants to write the given namespace
4337function nsRe(namespaceId) {
4338 var imageNamespaceVariants = [];
4339 jQuery.each(mw.config.get('wgNamespaceIds'), function(_localizedNamespaceLc, _namespaceId) {
4340 if (_namespaceId!=namespaceId) return;
4341 _localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc);
4342 imageNamespaceVariants.push(mw.RegExp.escape(_localizedNamespaceLc).split(' ').join('[ _]'));
4343 imageNamespaceVariants.push(mw.RegExp.escape(encodeURI(_localizedNamespaceLc)));
4344 });
4345
4346 return '(?:' + imageNamespaceVariants.join('|') + ')';
4347}
4348
4349function nsReImage() {
4350 return nsRe(pg.nsImageId);
4351}
4352// ENDFILE: namespaces.js
4353// STARTFILE: selpop.js
4354//<NOLITE>
4355function getEditboxSelection() {
4356 // see http://www.webgurusforum.com/8/12/0
4357 var editbox;
4358 try {
4359 editbox=document.editform.wpTextbox1;
4360 } catch (dang) { return; }
4361 // IE, Opera
4362 if (document.selection) { return document.selection.createRange().text; }
4363 // Mozilla
4364 var selStart = editbox.selectionStart;
4365 var selEnd = editbox.selectionEnd;
4366 return (editbox.value).substring(selStart, selEnd);
4367}
4368
4369function doSelectionPopup() {
4370 // popup if the selection looks like [[foo|anything afterwards at all
4371 // or [[foo|bar]]text without ']]'
4372 // or [[foo|bar]]
4373 var sel=getEditboxSelection();
4374 var open=sel.indexOf('[[');
4375 var pipe=sel.indexOf('|');
4376 var close=sel.indexOf(']]');
4377 if (open == -1 || ( pipe == -1 && close == -1) ) { return; }
4378 if (pipe != -1 && open > pipe || close != -1 && open > close) { return; }
4379 if (getValueOf('popupOnEditSelection')=='boxpreview') {
4380 return doSeparateSelectionPopup(sel);
4381 }
4382 var article=new Title(sel.substring(open+2, (pipe < 0) ? close : pipe)).urlString();
4383 if (close > 0 && sel.substring(close+2).indexOf('[[') >= 0) {
4384 return;
4385 }
4386 var a=document.createElement('a');
4387 a.href=pg.wiki.titlebase + article;
4388 mouseOverWikiLink2(a);
4389 if (a.navpopup) {
4390 a.navpopup.addHook(function(){runStopPopupTimer(a.navpopup);}, 'unhide', 'after');
4391 }
4392}
4393
4394function doSeparateSelectionPopup(str) {
4395 var div=document.getElementById('selectionPreview');
4396 if (!div) {
4397 div = document.createElement('div');
4398 div.id='selectionPreview';
4399 try {
4400 var box=document.editform.wpTextbox1;
4401 box.parentNode.insertBefore(div, box);
4402 } catch (error) {
4403 return;
4404 }
4405 }
4406 div.innerHTML=wiki2html(str);
4407 div.ranSetupTooltipsAlready = false;
4408 popTipsSoonFn('selectionPreview')();
4409}
4410//</NOLITE>
4411// ENDFILE: selpop.js
4412// STARTFILE: navpopup.js
4413/**
4414 @fileoverview Defines two classes: {@link Navpopup} and {@link Mousetracker}.
4415
4416 <code>Navpopup</code> describes popups: when they appear, where, what
4417 they look like and so on.
4418
4419 <code>Mousetracker</code> "captures" the mouse using
4420 <code>document.onmousemove</code>.
4421*/
4422
4423
4424/**
4425 Creates a new Mousetracker.
4426 @constructor
4427 @class The Mousetracker class. This monitors mouse movements and manages associated hooks.
4428*/
4429function Mousetracker() {
4430 /**
4431 Interval to regularly run the hooks anyway, in milliseconds.
4432 @type Integer
4433 */
4434 this.loopDelay=400;
4435
4436 /**
4437 Timer for the loop.
4438 @type Timer
4439 */
4440 this.timer=null;
4441
4442 /**
4443 Flag - are we switched on?
4444 @type Boolean
4445 */
4446 this.active=false;
4447 /**
4448 Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position?
4449 */
4450 this.dirty=true;
4451 /**
4452 Array of hook functions.
4453 @private
4454 @type Array
4455 */
4456 this.hooks=[];
4457}
4458
4459/**
4460 Adds a hook, to be called when we get events.
4461 @param {Function} f A function which is called as
4462 <code>f(x,y)</code>. It should return <code>true</code> when it
4463 wants to be removed, and <code>false</code> otherwise.
4464*/
4465Mousetracker.prototype.addHook = function (f) {
4466 this.hooks.push(f);
4467};
4468
4469/**
4470 Runs hooks, passing them the x
4471 and y coords of the mouse. Hook functions that return true are
4472 passed to {@link Mousetracker#removeHooks} for removal.
4473 @private
4474*/
4475Mousetracker.prototype.runHooks = function () {
4476 if (!this.hooks || !this.hooks.length) { return; }
4477 //log('Mousetracker.runHooks; we got some hooks to run');
4478 var remove=false;
4479 var removeObj={};
4480 // this method gets called a LOT -
4481 // pre-cache some variables
4482 var x=this.x, y=this.y, len = this.hooks.length;
4483
4484 for (var i=0; i<len; ++i) {
4485 //~ run the hook function, and remove it if it returns true
4486 if (this.hooks[i](x, y)===true) {
4487 remove=true;
4488 removeObj[i]=true;
4489 }
4490 }
4491 if (remove) { this.removeHooks(removeObj); }
4492};
4493
4494/**
4495 Removes hooks.
4496 @private
4497 @param {Object} removeObj An object whose keys are the index
4498 numbers of functions for removal, with values that evaluate to true
4499*/
4500Mousetracker.prototype.removeHooks = function(removeObj) {
4501 var newHooks=[];
4502 var len = this.hooks.length;
4503 for (var i=0; i<len; ++i) {
4504 if (! removeObj[i]) { newHooks.push(this.hooks[i]); }
4505 }
4506 this.hooks=newHooks;
4507};
4508
4509
4510/**
4511 Event handler for mouse wiggles.
4512 We simply grab the event, set x and y and run the hooks.
4513 This makes the cpu all hot and bothered :-(
4514 @private
4515 @param {Event} e Mousemove event
4516*/
4517Mousetracker.prototype.track=function (e) {
4518 //~ Apparently this is needed in IE.
4519 e = e || window.event;
4520 var x, y;
4521 if (e) {
4522 if (e.pageX) { x=e.pageX; y=e.pageY; }
4523 else if (typeof e.clientX!='undefined') {
4524 var left, top, docElt = document.documentElement;
4525
4526 if (docElt) { left=docElt.scrollLeft; }
4527 left = left || document.body.scrollLeft || document.scrollLeft || 0;
4528
4529 if (docElt) { top=docElt.scrollTop; }
4530 top = top || document.body.scrollTop || document.scrollTop || 0;
4531
4532 x=e.clientX + left;
4533 y=e.clientY + top;
4534 } else { return; }
4535 this.setPosition(x,y);
4536 }
4537};
4538
4539/**
4540 Sets the x and y coordinates stored and takes appropriate action,
4541 running hooks as appropriate.
4542 @param {Integer} x, y Screen coordinates to set
4543*/
4544
4545Mousetracker.prototype.setPosition=function(x,y) {
4546 this.x = x;
4547 this.y = y;
4548 if (this.dirty || this.hooks.length === 0) { this.dirty=false; return; }
4549 if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; }
4550 var diff = (this.lastHook_x - x)*(this.lastHook_y - y);
4551 diff = (diff >= 0) ? diff : -diff;
4552 if ( diff > 1 ) {
4553 this.lastHook_x=x;
4554 this.lastHook_y=y;
4555 if (this.dirty) { this.dirty = false; }
4556 else { this.runHooks(); }
4557 }
4558};
4559
4560/**
4561 Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>.
4562 A half-hearted attempt is made to preserve the old event handler if there is one.
4563*/
4564Mousetracker.prototype.enable = function () {
4565 if (this.active) { return; }
4566 this.active=true;
4567 //~ Save the current handler for mousemove events. This isn't too
4568 //~ robust, of course.
4569 this.savedHandler=document.onmousemove;
4570 //~ Gotta save @tt{this} again for the closure, and use apply for
4571 //~ the member function.
4572 var savedThis=this;
4573 document.onmousemove=function (e) {savedThis.track.apply(savedThis, [e]);};
4574 if (this.loopDelay) { this.timer = setInterval(function() { //log('loop delay in mousetracker is working');
4575 savedThis.runHooks();}, this.loopDelay); }
4576};
4577
4578/**
4579 Disables the tracker, removing the event handler.
4580*/
4581Mousetracker.prototype.disable = function () {
4582 if (!this.active) { return; }
4583 if ($.isFunction(this.savedHandler)) {
4584 document.onmousemove=this.savedHandler;
4585 } else { delete document.onmousemove; }
4586 if (this.timer) { clearInterval(this.timer); }
4587 this.active=false;
4588};
4589
4590/**
4591 Creates a new Navpopup.
4592 Gets a UID for the popup and
4593 @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable.
4594 @constructor
4595 @class The Navpopup class. This generates popup hints, and does some management of them.
4596*/
4597function Navpopup(/*init*/) {
4598 //alert('new Navpopup(init)');
4599 /** UID for each Navpopup instance.
4600 Read-only.
4601 @type integer
4602 */
4603 this.uid=Navpopup.uid++;
4604 /**
4605 Read-only flag for current visibility of the popup.
4606 @type boolean
4607 @private
4608 */
4609 this.visible=false;
4610 /** Flag to be set when we want to cancel a previous request to
4611 show the popup in a little while.
4612 @private
4613 @type boolean
4614 */
4615 this.noshow=false;
4616 /** Categorised list of hooks.
4617 @see #runHooks
4618 @see #addHook
4619 @private
4620 @type Object
4621 */
4622 this.hooks={
4623 'create': [],
4624 'unhide': [],
4625 'hide': []
4626 };
4627 /** list of unique IDs of hook functions, to avoid duplicates
4628 @private
4629 */
4630 this.hookIds={};
4631 /** List of downloads associated with the popup.
4632 @private
4633 @type Array
4634 */
4635 this.downloads=[];
4636 /** Number of uncompleted downloads.
4637 @type integer
4638 */
4639 this.pending=null;
4640 /** Tolerance in pixels when detecting whether the mouse has left the popup.
4641 @type integer
4642 */
4643 this.fuzz=5;
4644 /** Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position.
4645 @type boolean
4646 */
4647 this.constrained=true;
4648 /** The popup width in pixels.
4649 @private
4650 @type integer
4651 */
4652 this.width=0;
4653 /** The popup width in pixels.
4654 @private
4655 @type integer
4656 */
4657 this.height=0;
4658 /** The main content DIV element.
4659 @type HTMLDivElement
4660 */
4661 this.mainDiv=null;
4662 this.createMainDiv();
4663
4664// if (!init || typeof init.popups_draggable=='undefined' || init.popups_draggable) {
4665// this.makeDraggable(true);
4666// }
4667}
4668
4669/**
4670 A UID for each Navpopup. This constructor property is just a counter.
4671 @type integer
4672 @private
4673*/
4674Navpopup.uid=0;
4675
4676/**
4677 Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible.
4678 @type boolean
4679*/
4680Navpopup.prototype.isVisible=function() {
4681 return this.visible;
4682};
4683
4684/**
4685 Repositions popup using CSS style.
4686 @private
4687 @param {integer} x x-coordinate (px)
4688 @param {integer} y y-coordinate (px)
4689 @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition}
4690*/
4691Navpopup.prototype.reposition= function (x,y, noLimitHor) {
4692 log ('reposition('+x+','+y+','+noLimitHor+')');
4693 if (typeof x != 'undefined' && x !== null) { this.left=x; }
4694 if (typeof y != 'undefined' && y !== null) { this.top=y; }
4695 if (typeof this.left != 'undefined' && typeof this.top != 'undefined') {
4696 this.mainDiv.style.left=this.left + 'px';
4697 this.mainDiv.style.top=this.top + 'px';
4698 }
4699 if (!noLimitHor) { this.limitHorizontalPosition(); }
4700 //console.log('navpop'+this.uid+' - (left,top)=(' + this.left + ',' + this.top + '), css=('
4701 //+ this.mainDiv.style.left + ',' + this.mainDiv.style.top + ')');
4702};
4703
4704/**
4705 Prevents popups from being in silly locations. Hopefully.
4706 Should not be run if {@link #constrained} is true.
4707 @private
4708*/
4709Navpopup.prototype.limitHorizontalPosition=function() {
4710 if (!this.constrained || this.tooWide) { return; }
4711 this.updateDimensions();
4712 var x=this.left;
4713 var w=this.width;
4714 var cWidth=document.body.clientWidth;
4715
4716
4717// log('limitHorizontalPosition: x='+x+
4718// ', this.left=' + this.left +
4719// ', this.width=' + this.width +
4720// ', cWidth=' + cWidth);
4721
4722
4723 if ( (x+w) >= cWidth ||
4724 ( x > 0 &&
4725 this.maxWidth &&
4726 this.width < this.maxWidth &&
4727 this.height > this.width &&
4728 x > cWidth - this.maxWidth ) ) {
4729 // This is a very nasty hack. There has to be a better way!
4730 // We find the "natural" width of the div by positioning it at the far left
4731 // then reset it so that it should be flush right (well, nearly)
4732 this.mainDiv.style.left='-10000px';
4733 this.mainDiv.style.width = this.maxWidth + 'px';
4734 var naturalWidth=parseInt(this.mainDiv.offsetWidth, 10);
4735 var newLeft=cWidth - naturalWidth - 1;
4736 if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups?
4737 log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth);
4738 this.reposition(newLeft, null, true);
4739 }
4740};
4741
4742/**
4743 Counter indicating the z-order of the "highest" popup.
4744 We start the z-index at 1000 so that popups are above everything
4745 else on the screen.
4746 @private
4747 @type integer
4748*/
4749Navpopup.highest=1000;
4750
4751/**
4752 Brings popup to the top of the z-order.
4753 We increment the {@link #highest} property of the contructor here.
4754 @private
4755*/
4756Navpopup.prototype.raise = function () {
4757 this.mainDiv.style.zIndex=Navpopup.highest + 1;
4758 ++Navpopup.highest;
4759};
4760
4761/**
4762 Shows the popup provided {@link #noshow} is not true.
4763 Updates the position, brings the popup to the top of the z-order and unhides it.
4764*/
4765Navpopup.prototype.show = function () {
4766 //document.title+='s';
4767 if (this.noshow) { return; }
4768 //document.title+='t';
4769 this.reposition();
4770 this.raise();
4771 this.unhide();
4772};
4773
4774/**
4775 Checks to see if the mouse pointer has
4776 stabilised (checking every <code>time</code>/2 milliseconds) and runs the
4777 {@link #show} method if it has.
4778 @param {integer} time The minimum time (ms) before the popup may be shown.
4779*/
4780Navpopup.prototype.showSoonIfStable = function (time) {
4781 log ('showSoonIfStable, time='+time);
4782 if (this.visible) { return; }
4783 this.noshow = false;
4784
4785 //~ initialize these variables so that we never run @tt{show} after
4786 //~ just half the time
4787 this.stable_x = -10000; this.stable_y = -10000;
4788
4789 var stableShow = function() {
4790 log('stableShow called');
4791 var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y;
4792 var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y;
4793 var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz;
4794 //document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] ';
4795 if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) {
4796 log ('mouse is stable');
4797 clearInterval(savedThis.showSoonStableTimer);
4798 savedThis.reposition.apply(savedThis, [new_x + 2, new_y + 2]);
4799 savedThis.show.apply(savedThis, []);
4800 savedThis.limitHorizontalPosition.apply(savedThis, []);
4801 return;
4802 }
4803 savedThis.stable_x = new_x; savedThis.stable_y = new_y;
4804 };
4805 var savedThis = this;
4806 this.showSoonStableTimer = setInterval(stableShow, time/2);
4807};
4808
4809/**
4810 Sets the {@link #noshow} flag and hides the popup. This should be called
4811 when the mouse leaves the link before
4812 (or after) it's actually been displayed.
4813*/
4814Navpopup.prototype.banish = function () {
4815 log ('banish called');
4816 // hide and prevent showing with showSoon in the future
4817 this.noshow=true;
4818 if (this.showSoonStableTimer) {
4819 log('clearing showSoonStableTimer');
4820 clearInterval(this.showSoonStableTimer);
4821 }
4822 this.hide();
4823};
4824
4825/**
4826 Runs hooks added with {@link #addHook}.
4827 @private
4828 @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
4829 @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
4830*/
4831Navpopup.prototype.runHooks = function (key, when) {
4832 if (!this.hooks[key]) { return; }
4833 var keyHooks=this.hooks[key];
4834 var len=keyHooks.length;
4835 for (var i=0; i< len; ++i) {
4836 if (keyHooks[i] && keyHooks[i].when == when) {
4837 if (keyHooks[i].hook.apply(this, [])) {
4838 // remove the hook
4839 if (keyHooks[i].hookId) {
4840 delete this.hookIds[keyHooks[i].hookId];
4841 }
4842 keyHooks[i]=null;
4843 }
4844 }
4845 }
4846};
4847
4848/**
4849 Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments.
4850 @param {Function} hook The hook function. Functions that return true are deleted.
4851 @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
4852 @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
4853 @param {String} uid A truthy string identifying the hook function; if it matches another hook in this position, it won't be added again.
4854*/
4855Navpopup.prototype.addHook = function ( hook, key, when, uid ) {
4856 when = when || 'after';
4857 if (!this.hooks[key]) { return; }
4858 // if uid is specified, don't add duplicates
4859 var hookId=null;
4860 if (uid) {
4861 hookId=[key,when,uid].join('|');
4862 if (this.hookIds[hookId]) {
4863 return;
4864 }
4865 this.hookIds[hookId]=true;
4866 }
4867 this.hooks[key].push( {hook: hook, when: when, hookId: hookId} );
4868};
4869
4870/**
4871 Creates the main DIV element, which contains all the actual popup content.
4872 Runs hooks with key 'create'.
4873 @private
4874*/
4875Navpopup.prototype.createMainDiv = function () {
4876 if (this.mainDiv) { return; }
4877 this.runHooks('create', 'before');
4878 var mainDiv=document.createElement('div');
4879
4880 var savedThis=this;
4881 mainDiv.onclick=function(e) {savedThis.onclickHandler(e);};
4882 mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv';
4883 mainDiv.id=mainDiv.className + this.uid;
4884
4885 mainDiv.style.position='absolute';
4886 mainDiv.style.minWidth = '350px';
4887 mainDiv.style.display='none';
4888 mainDiv.className='navpopup';
4889
4890 // easy access to javascript object through DOM functions
4891 mainDiv.navpopup=this;
4892
4893 this.mainDiv=mainDiv;
4894 document.body.appendChild(mainDiv);
4895 this.runHooks('create', 'after');
4896};
4897/**
4898 Calls the {@link #raise} method.
4899 @private
4900*/
4901Navpopup.prototype.onclickHandler=function(/*e*/) {
4902 this.raise();
4903};
4904/**
4905 Makes the popup draggable, using a {@link Drag} object.
4906 @private
4907*/
4908Navpopup.prototype.makeDraggable=function(handleName) {
4909 if (!this.mainDiv) { this.createMainDiv(); }
4910 var drag=new Drag();
4911 if (!handleName) {
4912 drag.startCondition=function(e) {
4913 try { if (!e.shiftKey) { return false; } } catch (err) { return false; }
4914 return true;
4915 };
4916 }
4917 var dragHandle;
4918 if (handleName) dragHandle = document.getElementById(handleName);
4919 if (!dragHandle) dragHandle = this.mainDiv;
4920 var np=this;
4921 drag.endHook=function(x,y) {
4922 Navpopup.tracker.dirty=true;
4923 np.reposition(x,y);
4924 };
4925 drag.init(dragHandle,this.mainDiv);
4926};
4927
4928/** Hides the popup using CSS. Runs hooks with key 'hide'.
4929 Sets {@link #visible} appropriately. {@link #banish} should be called externally instead of this method.
4930
4931 @private
4932*/
4933Navpopup.prototype.hide = function () {
4934 this.runHooks('hide', 'before');
4935 this.abortDownloads();
4936 if (typeof this.visible != 'undefined' && this.visible) {
4937 this.mainDiv.style.display='none';
4938 this.visible=false;
4939 }
4940 this.runHooks('hide', 'after');
4941};
4942
4943/** Shows the popup using CSS. Runs hooks with key 'unhide'.
4944 Sets {@link #visible} appropriately. {@link #show} should be called externally instead of this method.
4945 @private
4946*/
4947Navpopup.prototype.unhide = function () {
4948 this.runHooks('unhide', 'before');
4949 if (typeof this.visible != 'undefined' && !this.visible) {
4950 this.mainDiv.style.display='inline';
4951 this.visible=true;
4952 }
4953 this.runHooks('unhide', 'after');
4954};
4955
4956/**
4957 Sets the <code>innerHTML</code> attribute of the main div containing the popup content.
4958 @param {String} html The HTML to set.
4959*/
4960Navpopup.prototype.setInnerHTML = function (html) {
4961 this.mainDiv.innerHTML = html;
4962};
4963
4964/**
4965 Updates the {@link #width} and {@link #height} attributes with the CSS properties.
4966 @private
4967*/
4968Navpopup.prototype.updateDimensions = function () {
4969 this.width=parseInt(this.mainDiv.offsetWidth, 10);
4970 this.height=parseInt(this.mainDiv.offsetHeight, 10);
4971};
4972
4973/**
4974 Checks if the point (x,y) is within {@link #fuzz} of the
4975 {@link #mainDiv}.
4976 @param {integer} x x-coordinate (px)
4977 @param {integer} y y-coordinate (px)
4978 @type boolean
4979*/
4980Navpopup.prototype.isWithin = function(x,y) {
4981 //~ If we're not even visible, no point should be considered as
4982 //~ being within the popup.
4983 if (!this.visible) { return false; }
4984 this.updateDimensions();
4985 var fuzz=this.fuzz || 0;
4986 //~ Use a simple box metric here.
4987 return (x+fuzz >= this.left && x-fuzz <= this.left + this.width &&
4988 y+fuzz >= this.top && y-fuzz <= this.top + this.height);
4989};
4990
4991/**
4992 Adds a download to {@link #downloads}.
4993 @param {Downloader} download
4994*/
4995Navpopup.prototype.addDownload=function(download) {
4996 if (!download) { return; }
4997 this.downloads.push(download);
4998};
4999/**
5000 Aborts the downloads listed in {@link #downloads}.
5001 @see Downloader#abort
5002*/
5003Navpopup.prototype.abortDownloads=function() {
5004 for(var i=0; i<this.downloads.length; ++i) {
5005 var d=this.downloads[i];
5006 if (d && d.abort) { d.abort(); }
5007 }
5008 this.downloads=[];
5009};
5010
5011
5012/**
5013 A {@link Mousetracker} instance which is a property of the constructor (pseudo-global).
5014*/
5015Navpopup.tracker=new Mousetracker();
5016// ENDFILE: navpopup.js
5017// STARTFILE: diff.js
5018//<NOLITE>
5019/*
5020 * Javascript Diff Algorithm
5021 * By John Resig (http://ejohn.org/) and [[:en:User:Lupin]]
5022 *
5023 * More Info:
5024 * http://ejohn.org/projects/javascript-diff-algorithm/
5025 */
5026
5027function delFmt(x) {
5028 if (!x.length) { return ''; }
5029 return "<del class='popupDiff'>" + x.join('') +"</del>";
5030}
5031function insFmt(x) {
5032 if (!x.length) { return ''; }
5033 return "<ins class='popupDiff'>" + x.join('') +"</ins>";
5034}
5035
5036function countCrossings(a, b, i, eject) {
5037 // count the crossings on the edge starting at b[i]
5038 if (!b[i].row && b[i].row !== 0) { return -1; }
5039 var count=0;
5040 for (var j=0; j<a.length; ++j) {
5041 if (!a[j].row && a[j].row !== 0) { continue; }
5042 if ( (j-b[i].row)*(i-a[j].row) > 0) {
5043 if(eject) { return true; }
5044 count++;
5045 }
5046 }
5047 return count;
5048}
5049
5050function shortenDiffString(str, context) {
5051 var re=RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)');
5052 var splitted=str.parenSplit(re);
5053 var ret=[''];
5054 for (var i=0; i<splitted.length; i+=2) {
5055 if (splitted[i].length < 2*context) {
5056 ret[ret.length-1] += splitted[i];
5057 if (i+1<splitted.length) { ret[ret.length-1] += splitted[i+1]; }
5058 continue;
5059 }
5060 else {
5061 if (i > 0) { ret[ret.length-1] += splitted[i].substring(0,context); }
5062 if (i+1 < splitted.length) {
5063 ret.push(splitted[i].substring(splitted[i].length-context) +
5064 splitted[i+1]);
5065 }
5066 }
5067 }
5068 while (ret.length > 0 && !ret[0]) { ret = ret.slice(1); }
5069 return ret;
5070}
5071
5072
5073function diffString( o, n, simpleSplit ) {
5074 var splitRe=RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)');
5075
5076 // We need to split the strings o and n first, and entify() the parts
5077 // individually, so that the HTML entities are never cut apart. (AxelBoldt)
5078 var out, i, oSplitted, nSplitted;
5079 if (simpleSplit) {
5080 oSplitted=o.split(/\b/);
5081 nSplitted=n.split(/\b/);
5082 } else {
5083 oSplitted=o.parenSplit(splitRe);
5084 nSplitted=n.parenSplit(splitRe);
5085 }
5086 for (i=0; i<oSplitted.length; ++i) {oSplitted[i]=oSplitted[i].entify();}
5087 for (i=0; i<nSplitted.length; ++i) {nSplitted[i]=nSplitted[i].entify();}
5088
5089 out = diff (oSplitted, nSplitted);
5090 var str = "";
5091 var acc=[]; // accumulator for prettier output
5092
5093 // crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out
5094 // this doesn't always do things optimally but it should be fast enough
5095 var maxOutputPair=0;
5096 for (i=0; i<out.n.length; ++i) {
5097 if ( out.n[i].paired ) {
5098 if( maxOutputPair > out.n[i].row ) {
5099 // tangle - delete pairing
5100 out.o[ out.n[i].row ]=out.o[ out.n[i].row ].text;
5101 out.n[i]=out.n[i].text;
5102 }
5103 if (maxOutputPair < out.n[i].row) { maxOutputPair = out.n[i].row; }
5104 }
5105 }
5106
5107 // output the stuff preceding the first paired old line
5108 for (i=0; i<out.o.length && !out.o[i].paired; ++i) { acc.push( out.o[i] ); }
5109 str += delFmt(acc); acc=[];
5110
5111 // main loop
5112 for ( i = 0; i < out.n.length; ++i ) {
5113 // output unpaired new "lines"
5114 while ( i < out.n.length && !out.n[i].paired ) { acc.push( out.n[i++] ); }
5115 str += insFmt(acc); acc=[];
5116 if ( i < out.n.length ) { // this new "line" is paired with the (out.n[i].row)th old "line"
5117 str += out.n[i].text;
5118 // output unpaired old rows starting after this new line's partner
5119 var m = out.n[i].row + 1;
5120 while ( m < out.o.length && !out.o[m].paired ) { acc.push ( out.o[m++] ); }
5121 str += delFmt(acc); acc=[];
5122 }
5123 }
5124 return str;
5125}
5126
5127// see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object
5128// FIXME: use obj.hasOwnProperty instead of this kludge!
5129var jsReservedProperties=RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' +
5130 '|eval|hasOwnProperty|propertyIsEnumerable' +
5131 '|to(Source|String|LocaleString)|(un)?watch|valueOf)$');
5132function diffBugAlert(word) {
5133 if (!diffBugAlert.list[word]) {
5134 diffBugAlert.list[word]=1;
5135 alert('Bad word: '+word+'\n\nPlease report this bug.');
5136 }
5137}
5138diffBugAlert.list={};
5139
5140function makeDiffHashtable(src) {
5141 var ret={};
5142 for ( var i = 0; i < src.length; i++ ) {
5143 if ( jsReservedProperties.test(src[i]) ) { src[i] += '<!-- -->'; }
5144 if ( !ret[ src[i] ] ) { ret[ src[i] ] = []; }
5145 try { ret[ src[i] ].push( i ); } catch (err) { diffBugAlert(src[i]); }
5146 }
5147 return ret;
5148}
5149
5150function diff( o, n ) {
5151
5152 // pass 1: make hashtable ns with new rows as keys
5153 var ns = makeDiffHashtable(n);
5154
5155 // pass 2: make hashtable os with old rows as keys
5156 var os = makeDiffHashtable(o);
5157
5158 // pass 3: pair unique new rows and matching unique old rows
5159 var i;
5160 for ( i in ns ) {
5161 if ( ns[i].length == 1 && os[i] && os[i].length == 1 ) {
5162 n[ ns[i][0] ] = { text: n[ ns[i][0] ], row: os[i][0], paired: true };
5163 o[ os[i][0] ] = { text: o[ os[i][0] ], row: ns[i][0], paired: true };
5164 }
5165 }
5166
5167 // pass 4: pair matching rows immediately following paired rows (not necessarily unique)
5168 for ( i = 0; i < n.length - 1; i++ ) {
5169 if ( n[i].paired && ! n[i+1].paired && n[i].row + 1 < o.length && ! o[ n[i].row + 1 ].paired &&
5170 n[i+1] == o[ n[i].row + 1 ] ) {
5171 n[i+1] = { text: n[i+1], row: n[i].row + 1, paired: true };
5172 o[n[i].row+1] = { text: o[n[i].row+1], row: i + 1, paired: true };
5173 }
5174 }
5175
5176 // pass 5: pair matching rows immediately preceding paired rows (not necessarily unique)
5177 for ( i = n.length - 1; i > 0; i-- ) {
5178 if ( n[i].paired && ! n[i-1].paired && n[i].row > 0 && ! o[ n[i].row - 1 ].paired &&
5179 n[i-1] == o[ n[i].row - 1 ] ) {
5180 n[i-1] = { text: n[i-1], row: n[i].row - 1, paired: true };
5181 o[n[i].row-1] = { text: o[n[i].row-1], row: i - 1, paired: true };
5182 }
5183 }
5184
5185 return { o: o, n: n };
5186}
5187//</NOLITE>
5188// ENDFILE: diff.js
5189// STARTFILE: init.js
5190function setSiteInfo() {
5191 if (window.popupLocalDebug) {
5192 pg.wiki.hostname = 'en.wikipedia.org';
5193 } else {
5194 pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?)
5195 }
5196 pg.wiki.wikimedia=RegExp('(wiki([pm]edia|source|books|news|quote|versity)|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname);
5197 pg.wiki.wikia=RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname);
5198 pg.wiki.isLocal=RegExp('^localhost').test(pg.wiki.hostname);
5199 pg.wiki.commons=( pg.wiki.wikimedia && pg.wiki.hostname != 'commons.wikimedia.org') ? 'commons.wikimedia.org' : null;
5200 pg.wiki.lang = mw.config.get('wgContentLanguage');
5201 var port = location.port ? ':' + location.port : '';
5202 pg.wiki.sitebase = pg.wiki.hostname + port;
5203}
5204
5205function setTitleBase() {
5206 var protocol = ( window.popupLocalDebug ? 'http:' : location.protocol );
5207 pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ""); // as in http://some.thing.com/wiki/Article
5208 pg.wiki.botInterfacePath = mw.config.get('wgScript');
5209 pg.wiki.APIPath = mw.config.get('wgScriptPath') +"/api.php";
5210 // default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo
5211
5212 var titletail = pg.wiki.botInterfacePath + '?title=';
5213 //var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']);
5214
5215 // other sites may need to add code here to set titletail depending on how their urls work
5216
5217 pg.wiki.titlebase = protocol + '//' + pg.wiki.sitebase + titletail;
5218 //pg.wiki.titlebase2 = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]);
5219 pg.wiki.wikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.botInterfacePath;
5220 pg.wiki.apiwikibase = protocol + '//' + pg.wiki.sitebase + pg.wiki.APIPath;
5221 pg.wiki.articlebase = protocol + '//' + pg.wiki.sitebase + pg.wiki.articlePath;
5222 pg.wiki.commonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.botInterfacePath;
5223 pg.wiki.apicommonsbase = protocol + '//' + pg.wiki.commons + pg.wiki.APIPath;
5224 pg.re.basenames = RegExp( '^(' +
5225 map( literalizeRegex, [ pg.wiki.titlebase, //pg.wiki.titlebase2,
5226 pg.wiki.articlebase ]).join('|') + ')' );
5227}
5228
5229
5230//////////////////////////////////////////////////
5231// Global regexps
5232
5233function setMainRegex() {
5234 var reStart='[^:]*://';
5235 var preTitles = literalizeRegex( mw.config.get('wgScriptPath') ) + '/(?:index[.]php|wiki[.]phtml)[?]title=';
5236 preTitles += '|' + literalizeRegex( pg.wiki.articlePath + '/' );
5237
5238 var reEnd='(' + preTitles + ')([^&?#]*)[^#]*(?:#(.+))?';
5239 pg.re.main = RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd);
5240}
5241
5242function setRegexps() {
5243 setMainRegex();
5244 var sp=nsRe(pg.nsSpecialId);
5245 pg.re.urlNoPopup=RegExp('((title=|/)' + sp + '(?:%3A|:)|section=[0-9]|^#$)') ;
5246 pg.re.contribs =RegExp('(title=|/)' + sp + '(?:%3A|:)Contributions' + '(&target=|/|/' + nsRe(pg.nsUserId)+':)(.*)') ;
5247 pg.re.email =RegExp('(title=|/)' + sp + '(?:%3A|:)EmailUser' + '(&target=|/|/(?:' + nsRe(pg.nsUserId)+':)?)(.*)') ;
5248 pg.re.backlinks =RegExp('(title=|/)' + sp + '(?:%3A|:)WhatLinksHere' + '(&target=|/)([^&]*)');
5249 pg.re.specialdiff=RegExp('/' + sp + '(?:%3A|:)Diff/([^?#]*)');
5250
5251//<NOLITE>
5252 var im=nsReImage();
5253 // note: tries to get images in infobox templates too, e.g. movie pages, album pages etc
5254 // (^|\[\[)image: *([^|\]]*[^|\] ]) *
5255 // (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)?
5256 // $4 = 120 as in 120px
5257 pg.re.image = RegExp('(^|\\[\\[)' + im + ': *([^|\\]]*[^|\\] ])' +
5258 '([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *' +
5259 '(' + getValueOf('popupImageVarsRegexp') + ')' +
5260 ' *= *(?:\\[\\[ *)?(?:' + im + ':)?' +
5261 '([^|]*?)(?:\\]\\])? *[|]? *\\n', 'img') ;
5262 pg.re.imageBracketCount = 6;
5263
5264 pg.re.category = RegExp('\\[\\[' +nsRe(pg.nsCategoryId) +
5265 ': *([^|\\]]*[^|\\] ]) *', 'i');
5266 pg.re.categoryBracketCount = 1;
5267
5268 pg.re.ipUser=RegExp('^' +
5269 // IPv6
5270 '(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' +
5271 // IPv4
5272 '|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' +
5273 '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$');
5274
5275 pg.re.stub= RegExp(getValueOf('popupStubRegexp'), 'im');
5276 pg.re.disambig=RegExp(getValueOf('popupDabRegexp'), 'im');
5277
5278//</NOLITE>
5279 // FIXME replace with general parameter parsing function, this is daft
5280 pg.re.oldid=RegExp('[?&]oldid=([^&]*)');
5281 pg.re.diff=RegExp('[?&]diff=([^&]*)');
5282}
5283
5284
5285//////////////////////////////////////////////////
5286// miscellany
5287
5288function setupCache() {
5289 // page caching
5290 pg.cache.pages = [];
5291}
5292
5293function setMisc() {
5294 pg.current.link=null;
5295 pg.current.links=[];
5296 pg.current.linksHash={};
5297
5298 setupCache();
5299
5300 pg.timer.checkPopupPosition=null;
5301 pg.counter.loop=0;
5302
5303 // ids change with each popup: popupImage0, popupImage1 etc
5304 pg.idNumber=0;
5305
5306 // for myDecodeURI
5307 pg.misc.decodeExtras = [
5308 {from: '%2C', to: ',' },
5309 {from: '_', to: ' ' },
5310 {from: '%24', to: '$'},
5311 {from: '%26', to: '&' } // no ,
5312 ];
5313
5314 pg.misc.userAgent = 'Navigation popups/1.0 (' + mw.config.get( 'wgServerName' ) +')';
5315}
5316
5317// We need a callback since this might end up asynchronous because of
5318// the mw.loader.using() call.
5319function setupPopups( callback ) {
5320 if ( setupPopups.completed ) {
5321 if ( $.isFunction( callback ) ) {
5322 callback();
5323 }
5324 return;
5325 }
5326 // These dependencies are also enforced from the gadget,
5327 // but not everyone loads this as a gadget, so double check
5328 mw.loader.using( ['mediawiki.util', 'mediawiki.user', 'user.options', 'mediawiki.RegExp'] ).then( function() {
5329 // NB translatable strings should be set up first (strings.js)
5330 // basics
5331 setupDebugging();
5332 setSiteInfo();
5333 setTitleBase();
5334 setOptions(); // see options.js
5335
5336 // namespaces etc
5337 setNamespaces();
5338 setInterwiki();
5339
5340 // regexps
5341 setRegexps();
5342 setRedirs();
5343
5344 // other stuff
5345 setMisc();
5346 setupLivePreview();
5347
5348 // main deal here
5349 setupTooltips();
5350 log('In setupPopups(), just called setupTooltips()');
5351 Navpopup.tracker.enable();
5352
5353 setupPopups.completed = true;
5354 if ( $.isFunction( callback ) ) {
5355 callback();
5356 }
5357 });
5358}
5359// ENDFILE: init.js
5360// STARTFILE: navlinks.js
5361//<NOLITE>
5362//////////////////////////////////////////////////
5363// navlinks... let the fun begin
5364//
5365
5366function defaultNavlinkSpec() {
5367 var str='';
5368 str += '<b><<mainlink|shortcut= >></b>';
5369 if (getValueOf('popupLastEditLink')) {
5370 str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}';
5371 }
5372
5373 // user links
5374 // contribs - log - count - email - block
5375 // count only if applicable; block only if popupAdminLinks
5376 str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>';
5377 str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}';
5378 str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}';
5379
5380 // editing links
5381 // talkpage -> edit|new - history - un|watch - article|edit
5382 // other page -> edit - history - un|watch - talk|edit|new
5383 var editstr='<<edit|shortcut=e>>';
5384 var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}';
5385 var historystr='<<history|shortcut=h>>|<<editors|shortcut=E|>>';
5386 var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
5387
5388 str+='<br>if(talk){' +
5389 editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' +
5390 '<b><<article|shortcut=a>></b>|<<editArticle|edit>>' +
5391 '}else{' + // not a talk page
5392 editOldidStr + '*' + historystr + '*' + watchstr + '*' +
5393 '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}';
5394
5395 // misc links
5396 str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';
5397
5398 // admin links
5399 str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' +
5400 '<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}';
5401 return str;
5402}
5403
5404function navLinksHTML (article, hint, params) { //oldid, rcid) {
5405 var str = '<span class="popupNavLinks">' + defaultNavlinkSpec() + '</span>';
5406 // BAM
5407 return navlinkStringToHTML(str, article, params);
5408}
5409
5410function expandConditionalNavlinkString(s,article,z,recursionCount) {
5411 var oldid=z.oldid, rcid=z.rcid, diff=z.diff;
5412 // nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out)
5413 if (typeof recursionCount!=typeof 0) { recursionCount=0; }
5414 var conditionalSplitRegex=RegExp(
5415 //(1 if \\( (2 2) \\) {(3 3)} (4 else {(5 5)} 4)1)
5416 '(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i');
5417 var splitted=s.parenSplit(conditionalSplitRegex);
5418 // $1: whole conditional
5419 // $2: test condition
5420 // $3: true expansion
5421 // $4: else clause (possibly empty)
5422 // $5: false expansion (possibly null)
5423 var numParens=5;
5424 var ret = splitted[0];
5425 for (var i=1; i<splitted.length; i=i+numParens+1) {
5426
5427 var testString=splitted[i+2-1];
5428 var trueString=splitted[i+3-1];
5429 var falseString=splitted[i+5-1];
5430 if (typeof falseString=='undefined' || !falseString) { falseString=''; }
5431 var testResult=null;
5432
5433 switch (testString) {
5434 case 'user':
5435 testResult=(article.userName())?true:false;
5436 break;
5437 case 'talk':
5438 testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages
5439 break;
5440 case 'admin':
5441 testResult=getValueOf('popupAdminLinks')?true:false;
5442 break;
5443 case 'oldid':
5444 testResult=(typeof oldid != 'undefined' && oldid)?true:false;
5445 break;
5446 case 'rcid':
5447 testResult=(typeof rcid != 'undefined' && rcid)?true:false;
5448 break;
5449 case 'ipuser':
5450 testResult=(article.isIpUser())?true:false;
5451 break;
5452 case 'mainspace_en':
5453 testResult=isInMainNamespace(article) &&
5454 pg.wiki.hostname=='en.wikipedia.org';
5455 break;
5456 case 'wikimedia':
5457 testResult=(pg.wiki.wikimedia) ? true : false;
5458 break;
5459 case 'diff':
5460 testResult=(typeof diff != 'undefined' && diff)?true:false;
5461 break;
5462 }
5463
5464 switch(testResult) {
5465 case null: ret+=splitted[i]; break;
5466 case true: ret+=trueString; break;
5467 case false: ret+=falseString; break;
5468 }
5469
5470 // append non-conditional string
5471 ret += splitted[i+numParens];
5472 }
5473 if (conditionalSplitRegex.test(ret) && recursionCount < 10) {
5474 return expandConditionalNavlinkString(ret,article,z,recursionCount+1);
5475 }
5476 return ret;
5477}
5478
5479function navlinkStringToArray(s, article, params) {
5480 s=expandConditionalNavlinkString(s,article,params);
5481 var splitted=s.parenSplit(RegExp('<<(.*?)>>'));
5482 var ret=[];
5483 for (var i=0; i<splitted.length; ++i) {
5484 if (i%2) { // i odd, so s is a tag
5485 var t=new navlinkTag();
5486 var ss=splitted[i].split('|');
5487 t.id=ss[0];
5488 for (var j=1; j<ss.length; ++j) {
5489 var sss=ss[j].split('=');
5490 if (sss.length>1) {
5491 t[sss[0]]=sss[1];
5492 }
5493 else { // no assignment (no "="), so treat this as a title (overwriting the last one)
5494 t.text=popupString(sss[0]);
5495 }
5496 }
5497 t.article=article;
5498 var oldid=params.oldid, rcid=params.rcid, diff=params.diff;
5499 if (typeof oldid !== 'undefined' && oldid !== null) { t.oldid=oldid; }
5500 if (typeof rcid !== 'undefined' && rcid !== null) { t.rcid=rcid; }
5501 if (typeof diff !== 'undefined' && diff !== null) { t.diff=diff; }
5502 if (!t.text && t.id !== 'mainlink') { t.text=popupString(t.id); }
5503 ret.push(t);
5504 }
5505 else { // plain HTML
5506 ret.push(splitted[i]);
5507 }
5508 }
5509 return ret;
5510}
5511
5512
5513function navlinkSubstituteHTML(s) {
5514 return s.split('*').join(getValueOf('popupNavLinkSeparator'))
5515 .split('<menurow>').join('<li class="popup_menu_row">')
5516 .split('</menurow>').join('</li>')
5517 .split('<menu>').join('<ul class="popup_menu">')
5518 .split('</menu>').join('</ul>');
5519
5520}
5521
5522function navlinkDepth(magic,s) {
5523 return s.split('<' + magic + '>').length - s.split('</' + magic + '>').length;
5524}
5525
5526
5527// navlinkString: * becomes the separator
5528// <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz'
5529// and visible text 'fubar'
5530// if(test){...} and if(test){...}else{...} work too (nested ok)
5531
5532function navlinkStringToHTML(s,article,params) {
5533 //limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article));
5534 var p=navlinkStringToArray(s,article,params);
5535 var html='';
5536 var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it
5537 var menurowdepth = 0;
5538 for (var i=0; i<p.length; ++i) {
5539 if (typeof p[i] == typeof '') {
5540 html+=navlinkSubstituteHTML(p[i]);
5541 menudepth += navlinkDepth('menu', p[i]);
5542 menurowdepth += navlinkDepth('menurow', p[i]);
5543// if (menudepth === 0) {
5544// tagType='span';
5545// } else if (menurowdepth === 0) {
5546// tagType='li';
5547// } else {
5548// tagType = null;
5549// }
5550 } else if (typeof p[i].type != 'undefined' && p[i].type=='navlinkTag') {
5551 if (menudepth > 0 && menurowdepth === 0) {
5552 html += '<li class="popup_menu_item">' + p[i].html() + '</li>';
5553 } else {
5554 html+=p[i].html();
5555 }
5556 }
5557 }
5558 return html;
5559}
5560
5561function navlinkTag() {
5562 this.type='navlinkTag';
5563}
5564
5565navlinkTag.prototype.html=function () {
5566 this.getNewWin();
5567 this.getPrintFunction();
5568 var html='';
5569 var opening, closing;
5570 var tagType='span';
5571 if (!tagType) {
5572 opening = ''; closing = '';
5573 } else {
5574 opening = '<' + tagType + ' class="popup_' + this.id + '">';
5575 closing = '</' + tagType + '>';
5576 }
5577 if (typeof this.print!='function') {
5578 errlog ('Oh dear - invalid print function for a navlinkTag, id='+this.id);
5579 } else {
5580 html=this.print(this);
5581 if (typeof html != typeof '') {html='';}
5582 else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut);
5583 }
5584 return opening + html + closing;
5585};
5586
5587navlinkTag.prototype.getNewWin=function() {
5588 getValueOf('popupLinksNewWindow');
5589 if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') { this.newWin=null; }
5590 this.newWin=pg.option.popupLinksNewWindow[this.id];
5591};
5592
5593navlinkTag.prototype.getPrintFunction=function() { //think about this some more
5594 // this.id and this.article should already be defined
5595 if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) { return; }
5596
5597 this.noPopup=1;
5598 switch (this.id) {
5599 case 'contribs': case 'history': case 'whatLinksHere':
5600 case 'userPage': case 'monobook': case 'userTalk':
5601 case 'talk': case 'article': case 'lastEdit':
5602 this.noPopup=null;
5603 }
5604 switch (this.id) {
5605 case 'email': case 'contribs': case 'block': case 'unblock':
5606 case 'userlog': case 'userSpace': case 'deletedContribs':
5607 this.article=this.article.userName();
5608 }
5609
5610 switch (this.id) {
5611 case 'userTalk': case 'newUserTalk': case 'editUserTalk':
5612 case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog':
5613 this.article=this.article.userName(true);
5614 /* fall through */
5615 case 'pagelog': case 'deletelog': case 'protectlog':
5616 delete this.oldid;
5617 }
5618
5619 if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); }
5620
5621 if (this.id != 'mainlink') {
5622 // FIXME anchor handling should be done differently with Title object
5623 this.article=this.article.removeAnchor();
5624 // if (typeof this.text=='undefined') this.text=popupString(this.id);
5625 }
5626
5627 switch (this.id) {
5628 case 'undelete': this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break;
5629 case 'whatLinksHere': this.print=specialLink; this.specialpage='Whatlinkshere'; break;
5630 case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break;
5631 case 'move': this.print=specialLink; this.specialpage='Movepage'; break;
5632 case 'contribs': this.print=specialLink; this.specialpage='Contributions'; break;
5633 case 'deletedContribs':this.print=specialLink; this.specialpage='Deletedcontributions'; break;
5634 case 'email': this.print=specialLink; this.specialpage='EmailUser'; this.sep='/'; break;
5635 case 'block': this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break;
5636 case 'unblock': this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break;
5637 case 'userlog': this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break;
5638 case 'blocklog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break;
5639 case 'pagelog': this.print=specialLink; this.specialpage='Log'; this.sep='&page='; break;
5640 case 'protectlog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=protect&page='; break;
5641 case 'deletelog': this.print=specialLink; this.specialpage='Log'; this.sep='&type=delete&page='; break;
5642 case 'userSpace': this.print=specialLink; this.specialpage='PrefixIndex'; this.sep='&namespace=2&prefix='; break;
5643 case 'search': this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break;
5644 case 'thank': this.print=specialLink; this.specialpage='Thanks'; this.sep='/'; this.article.value = this.diff; break;
5645 case 'unwatch': case 'watch':
5646 this.print=magicWatchLink; this.action=this.id+'&autowatchlist=1&autoimpl=' + popupString('autoedit_version') + '&actoken='+autoClickToken(); break;
5647 case 'history': case 'historyfeed':
5648 case 'unprotect': case 'protect':
5649 this.print=wikiLink; this.action=this.id; break;
5650
5651 case 'delete':
5652 this.print=wikiLink; this.action='delete';
5653 if (this.article.namespaceId()==pg.nsImageId) {
5654 var img=this.article.stripNamespace();
5655 this.action+='&image='+img;
5656 }
5657 break;
5658
5659 case 'markpatrolled':
5660 case 'edit': // editOld should keep the oldid, but edit should not.
5661 delete this.oldid;
5662 /* fall through */
5663 case 'view': case 'purge': case 'render':
5664 this.print=wikiLink;
5665 this.action=this.id; break;
5666 case 'raw':
5667 this.print=wikiLink; this.action='raw'; break;
5668 case 'new':
5669 this.print=wikiLink; this.action='edit§ion=new'; break;
5670 case 'mainlink':
5671 if (typeof this.text=='undefined') { this.text=this.article.toString().entify(); }
5672 if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) {
5673 // only show the /subpage part of the title text
5674 var s=this.text.split('/'); this.text=s[s.length-1];
5675 if (this.text==='' && s.length > 1) { this.text=s[s.length-2]; }
5676 }
5677 this.print=titledWikiLink;
5678 if (typeof this.title==='undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') {
5679 this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article);
5680 if (typeof this.oldid !== 'undefined' && this.oldid) {
5681 this.title=tprintf('Revision %s of %s', [this.oldid, this.title]);
5682 }
5683 }
5684 this.action='view'; break;
5685 case 'userPage':
5686 case 'article':
5687 case 'monobook':
5688 case 'editMonobook':
5689 case 'editArticle':
5690 delete this.oldid;
5691 //alert(this.id+'\n'+this.article + '\n'+ typeof this.article);
5692 this.article=this.article.articleFromTalkOrArticle();
5693 //alert(this.id+'\n'+this.article + '\n'+ typeof this.article);
5694 this.print=wikiLink;
5695 if (this.id.indexOf('edit')===0) {
5696 this.action='edit';
5697 } else { this.action='view';}
5698 break;
5699 case 'userTalk':
5700 case 'talk':
5701 this.article=this.article.talkPage();
5702 delete this.oldid;
5703 this.print=wikiLink;
5704 this.action='view'; break;
5705 case 'arin':
5706 this.print=arinLink; break;
5707 case 'count':
5708 this.print=editCounterLink; break;
5709 case 'google':
5710 this.print=googleLink; break;
5711 case 'editors':
5712 this.print=editorListLink; break;
5713 case 'globalsearch':
5714 this.print=globalSearchLink; break;
5715 case 'lastEdit':
5716 this.print=titledDiffLink;
5717 this.title=popupString('Show the last edit');
5718 this.from='prev'; this.to='cur'; break;
5719 case 'oldEdit':
5720 this.print=titledDiffLink;
5721 this.title=popupString('Show the edit made to get revision') + ' ' + this.oldid;
5722 this.from='prev'; this.to=this.oldid; break;
5723 case 'editOld':
5724 this.print=wikiLink; this.action='edit'; break;
5725 case 'undo':
5726 this.print=wikiLink; this.action='edit&undo='; break;
5727 case 'revert':
5728 this.print=wikiLink; this.action='revert'; break;
5729 case 'nullEdit':
5730 this.print=wikiLink; this.action='nullEdit'; break;
5731 case 'diffCur':
5732 this.print=titledDiffLink;
5733 this.title=tprintf('Show changes since revision %s', [this.oldid]);
5734 this.from=this.oldid; this.to='cur'; break;
5735 case 'editUserTalk':
5736 case 'editTalk':
5737 delete this.oldid;
5738 this.article=this.article.talkPage();
5739 this.action='edit'; this.print=wikiLink; break;
5740 case 'newUserTalk':
5741 case 'newTalk':
5742 this.article=this.article.talkPage();
5743 this.action='edit§ion=new'; this.print=wikiLink; break;
5744 case 'lastContrib':
5745 case 'sinceMe':
5746 this.print=magicHistoryLink;
5747 break;
5748 case 'togglePreviews':
5749 this.text=popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews');
5750 /* fall through */
5751 case 'disablePopups': case 'purgePopups':
5752 this.print=popupMenuLink;
5753 break;
5754 default:
5755 this.print=function () {return 'Unknown navlink type: '+this.id+'';};
5756 }
5757};
5758//
5759// end navlinks
5760//////////////////////////////////////////////////
5761//</NOLITE>
5762// ENDFILE: navlinks.js
5763// STARTFILE: shortcutkeys.js
5764//<NOLITE>
5765function popupHandleKeypress(evt) {
5766 var keyCode = window.event ? window.event.keyCode : ( evt.keyCode ? evt.keyCode : evt.which);
5767 if (!keyCode || !pg.current.link || !pg.current.link.navpopup) { return; }
5768 if (keyCode==27) { // escape
5769 killPopup();
5770 return false; // swallow keypress
5771 }
5772
5773 var letter=String.fromCharCode(keyCode);
5774 var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A');
5775 var startLink=0;
5776 var i,j;
5777
5778 if (popupHandleKeypress.lastPopupLinkSelected) {
5779 for (i=0; i<links.length; ++i) {
5780 if (links[i]==popupHandleKeypress.lastPopupLinkSelected) { startLink=i; }
5781 }
5782 }
5783 for (j=0; j<links.length; ++j) {
5784 i=(startLink + j + 1) % links.length;
5785 if (links[i].getAttribute('popupkey')==letter) {
5786 if (evt && evt.preventDefault) evt.preventDefault();
5787 links[i].focus();
5788 popupHandleKeypress.lastPopupLinkSelected=links[i];
5789 return false; // swallow keypress
5790 }
5791 }
5792
5793 // pass keypress on
5794 if (document.oldPopupOnkeypress) { return document.oldPopupOnkeypress(evt); }
5795 return true;
5796}
5797
5798function addPopupShortcuts() {
5799 if (document.onkeypress!=popupHandleKeypress) {
5800 document.oldPopupOnkeypress=document.onkeypress;
5801 }
5802 document.onkeypress=popupHandleKeypress;
5803}
5804
5805function rmPopupShortcuts() {
5806 popupHandleKeypress.lastPopupLinkSelected=null;
5807 try {
5808 if (document.oldPopupOnkeypress && document.oldPopupOnkeypress==popupHandleKeypress) {
5809 // panic
5810 document.onkeypress=null; //function () {};
5811 return;
5812 }
5813 document.onkeypress=document.oldPopupOnkeypress;
5814 } catch (nasties) { /* IE goes here */ }
5815}
5816
5817
5818function addLinkProperty(html, property) {
5819 // take "<a href=...>...</a> and add a property
5820 // not sophisticated at all, easily broken
5821 var i=html.indexOf('>');
5822 if (i<0) { return html; }
5823 return html.substring(0,i) + ' ' + property + html.substring(i);
5824}
5825
5826function addPopupShortcut(html, key) {
5827 if (!getValueOf('popupShortcutKeys')) { return html; }
5828 var ret= addLinkProperty(html, 'popupkey="'+key+'"');
5829 if (key==' ') { key=popupString('spacebar'); }
5830 return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 ['+key+']$4');
5831}
5832//</NOLITE>
5833// ENDFILE: shortcutkeys.js
5834// STARTFILE: diffpreview.js
5835//<NOLITE>
5836//lets jump through hoops to find the rev ids we need to retrieve
5837function loadDiff(article, oldid, diff, navpop) {
5838 navpop.diffData={ oldRev: {}, newRev: {} };
5839 mw.loader.using( 'mediawiki.api' ).then( function() {
5840 var api = new mw.Api( {
5841 ajax: {
5842 headers: { 'Api-User-Agent': pg.misc.userAgent }
5843 }
5844 } );
5845 var params = {
5846 action: 'compare',
5847 prop: 'ids|title'
5848 };
5849 if(article.title){
5850 params.fromtitle = article.title;
5851 }
5852
5853 switch (diff) {
5854 case 'cur':
5855 switch ( oldid ) {
5856 case null:
5857 case '':
5858 case 'prev':
5859 // this can only work if we have the title
5860 // cur -> prev
5861 params.torelative = 'prev';
5862 break;
5863 default:
5864 params.fromrev = oldid;
5865 params.torelative = 'cur';
5866 break;
5867 }
5868 break;
5869 case 'prev':
5870 if( oldid ) {
5871 params.fromrev = oldid;
5872 } else {
5873 params.fromtitle;
5874 }
5875 params.torelative = 'prev';
5876 break;
5877 case 'next':
5878 params.fromrev = oldid || 0;
5879 params.torelative = 'next';
5880 break;
5881 default:
5882 params.fromrev = oldid || 0;
5883 params.torev = diff || 0;
5884 break;
5885 }
5886
5887 api.get( params ).then( function( data ) {
5888 navpop.diffData.oldRev.revid = data.compare.fromrevid;
5889 navpop.diffData.newRev.revid = data.compare.torevid;
5890 var go = function() {
5891 pendingNavpopTask(navpop);
5892 var url=pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&';
5893
5894 url += 'revids=' + navpop.diffData.oldRev.revid + '|' + navpop.diffData.newRev.revid;
5895 url += '&prop=revisions&rvprop=ids|timestamp|content';
5896
5897 getPageWithCaching(url, doneDiff, navpop);
5898
5899 return true; // remove hook once run
5900 };
5901 if (navpop.visible || !getValueOf('popupLazyDownloads')) { go(); }
5902 else { navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS'); }
5903 } );
5904 } );
5905}
5906
5907function doneDiff(download) {
5908 if (!download.owner || !download.owner.diffData) { return; }
5909 var navpop=download.owner;
5910 completedNavpopTask(navpop);
5911
5912 var pages, revisions=[];
5913 try{
5914 // Process the downloads
5915 pages = getJsObj(download.data).query.pages;
5916 for(var i=0; i < pages.length; i++ ) {
5917 revisions = revisions.concat(pages[i].revisions);
5918 }
5919 for(i=0; i< revisions.length; i++){
5920 if(revisions[i].revid == navpop.diffData.oldRev.revid) {
5921 navpop.diffData.oldRev.revision = revisions[i];
5922 } else if (revisions[i].revid == navpop.diffData.newRev.revid) {
5923 navpop.diffData.newRev.revision = revisions[i];
5924 }
5925 }
5926 } catch(someError) {
5927 errlog( 'Could not get diff' );
5928 }
5929
5930 insertDiff(navpop);
5931}
5932
5933function rmBoringLines(a,b,context) {
5934
5935 if (typeof context == 'undefined') { context=2; }
5936 // this is fairly slow... i think it's quicker than doing a word-based diff from the off, though
5937 var aa=[], aaa=[];
5938 var bb=[], bbb=[];
5939 var i, j;
5940
5941 // first, gather all disconnected nodes in a and all crossing nodes in a and b
5942 for (i=0; i<a.length; ++i ) {
5943 if(!a[i].paired) { aa[i]=1; }
5944 else if (countCrossings(b,a,i, true)) {
5945 aa[i]=1;
5946 bb[ a[i].row ] = 1;
5947 }
5948 }
5949
5950 // pick up remaining disconnected nodes in b
5951 for (i=0; i<b.length; ++i ) {
5952 if (bb[i]==1) { continue; }
5953 if(!b[i].paired) { bb[i]=1; }
5954 }
5955
5956 // another pass to gather context: we want the neighbours of included nodes which are not yet included
5957 // we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass
5958 for (i=0; i<b.length; ++i) {
5959 if ( bb[i] == 1 ) {
5960 for (j=Math.max(0,i-context); j < Math.min(b.length, i+context); ++j) {
5961 if ( !bb[j] ) { bb[j] = 1; aa[ b[j].row ] = 0.5; }
5962 }
5963 }
5964 }
5965
5966 for (i=0; i<a.length; ++i) {
5967 if ( aa[i] == 1 ) {
5968 for (j=Math.max(0,i-context); j < Math.min(a.length, i+context); ++j) {
5969 if ( !aa[j] ) { aa[j] = 1; bb[ a[j].row ] = 0.5; }
5970 }
5971 }
5972 }
5973
5974 for (i=0; i<bb.length; ++i) {
5975 if (bb[i] > 0) { // it's a row we need
5976 if (b[i].paired) { bbb.push(b[i].text); } // joined; partner should be in aa
5977 else {
5978 bbb.push(b[i]);
5979 }
5980 }
5981 }
5982 for (i=0; i<aa.length; ++i) {
5983 if (aa[i] > 0) { // it's a row we need
5984 if (a[i].paired) { aaa.push(a[i].text); } // joined; partner should be in aa
5985 else {
5986 aaa.push(a[i]);
5987 }
5988 }
5989 }
5990
5991 return { a: aaa, b: bbb};
5992}
5993
5994function stripOuterCommonLines(a,b,context) {
5995 var i=0;
5996 while (i<a.length && i < b.length && a[i]==b[i]) { ++i; }
5997 var j=a.length-1; var k=b.length-1;
5998 while ( j>=0 && k>=0 && a[j]==b[k] ) { --j; --k; }
5999
6000 return { a: a.slice(Math.max(0,i - 1 - context), Math.min(a.length+1, j + context+1)),
6001 b: b.slice(Math.max(0,i - 1 - context), Math.min(b.length+1, k + context+1)) };
6002}
6003
6004function insertDiff(navpop) {
6005 // for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff
6006 // FIXME: sometimes this gives misleading diffs as distant chunks are squashed together
6007 var oldlines = navpop.diffData.oldRev.revision.content.split('\n');
6008 var newlines = navpop.diffData.newRev.revision.content.split('\n');
6009 var inner=stripOuterCommonLines(oldlines,newlines,getValueOf('popupDiffContextLines'));
6010 oldlines=inner.a; newlines=inner.b;
6011 var truncated=false;
6012 getValueOf('popupDiffMaxLines');
6013 if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) {
6014 // truncate
6015 truncated=true;
6016 inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines),
6017 newlines.slice(0,pg.option.popupDiffMaxLines),
6018 pg.option.popupDiffContextLines);
6019 oldlines=inner.a; newlines=inner.b;
6020 }
6021
6022 var lineDiff=diff(oldlines, newlines);
6023 var lines2=rmBoringLines(lineDiff.o, lineDiff.n);
6024 var oldlines2=lines2.a; var newlines2=lines2.b;
6025
6026 var simpleSplit = !String.prototype.parenSplit.isNative;
6027 var html='<hr />';
6028 if (getValueOf('popupDiffDates')) {
6029 html += diffDatesTable(navpop);
6030 html += '<hr />';
6031 }
6032 html += shortenDiffString(
6033 diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),
6034 getValueOf('popupDiffContextCharacters') ).join('<hr />');
6035 setPopupTipsAndHTML(html.split('\n').join('<br>') +
6036 (truncated ? '<hr /><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') ,
6037 'popupPreview', navpop.idNumber);
6038}
6039
6040function diffDatesTable( navpop ) {
6041 var html='<table class="popup_diff_dates">';
6042 html += diffDatesTableRow( navpop.diffData.newRev.revision, tprintf('New revision'));
6043 html += diffDatesTableRow( navpop.diffData.oldRev.revision, tprintf('Old revision'));
6044 html += '</table>';
6045 return html;
6046}
6047function diffDatesTableRow( revision, label ) {
6048 var txt='';
6049 var lastModifiedDate = new Date(revision.timestamp);
6050 var datePrint=getValueOf('popupDiffDatePrinter');
6051 if (typeof lastModifiedDate[datePrint] == 'function') {
6052 var d2 = adjustDate(lastModifiedDate, getTimeOffset());
6053 txt = dayFormat(d2, true) + ' ' + timeFormat(d2, true);
6054 } else {
6055 txt = tprintf('Invalid %s %s', ['popupDiffDatePrinter', datePrint]);
6056 }
6057 var revlink = generalLink({url: mw.config.get('wgScript') + '?oldid='+revision.revid,
6058 text: label, title: label});
6059 return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [ revlink, txt ]);
6060}
6061//</NOLITE>
6062// ENDFILE: diffpreview.js
6063// STARTFILE: links.js
6064//<NOLITE>
6065/////////////////////
6066// LINK GENERATION //
6067/////////////////////
6068
6069// titledDiffLink --> titledWikiLink --> generalLink
6070// wikiLink --> titledWikiLink --> generalLink
6071// editCounterLink --> generalLink
6072
6073// TODO Make these functions return Element objects, not just raw HTML strings.
6074
6075function titledDiffLink(l) { // article, text, title, from, to) {
6076 return titledWikiLink({article: l.article, action: l.to + '&oldid=' + l.from,
6077 newWin: l.newWin,
6078 noPopup: l.noPopup,
6079 text: l.text, title: l.title,
6080 /* hack: no oldid here */
6081 actionName: 'diff'});
6082}
6083
6084
6085function wikiLink(l) {
6086 //{article:article, action:action, text:text, oldid, newid}) {
6087 if (! (typeof l.article == typeof {} &&
6088 typeof l.action == typeof '' &&
6089 typeof l.text==typeof '')) return null;
6090 if (typeof l.oldid == 'undefined') { l.oldid=null; }
6091 var savedOldid = l.oldid;
6092 if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) { l.oldid=null; }
6093 var hint=popupString(l.action + 'Hint'); // revertHint etc etc etc
6094 var oldidData=[l.oldid, safeDecodeURI(l.article)];
6095 var revisionString = tprintf('revision %s of %s', oldidData);
6096 log('revisionString='+revisionString);
6097 switch (l.action) {
6098 case 'edit§ion=new': hint = popupString('newSectionHint'); break;
6099 case 'edit&undo=':
6100 if (l.diff && l.diff != 'prev' && savedOldid ) {
6101 l.action += l.diff + '&undoafter=' + savedOldid;
6102 } else if (savedOldid) {
6103 l.action += savedOldid;
6104 }
6105 hint = popupString('undoHint');
6106 break;
6107 case 'raw&ctype=text/css': hint=popupString('rawHint'); break;
6108 case 'revert':
6109 var p=parseParams(pg.current.link.href);
6110 l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=' + revertSummary(l.oldid, p.diff);
6111 if (p.diff=='prev') {
6112 l.action += '&direction=prev';
6113 revisionString = tprintf('the revision prior to revision %s of %s', oldidData);
6114 }
6115 if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; }
6116 if (getValueOf('popupMinorReverts')) { l.action += '&autominor=true'; }
6117 log('revisionString is now '+revisionString);
6118 break;
6119 case 'nullEdit':
6120 l.action='edit&autoclick=wpSave&actoken=' + autoClickToken() + '&autoimpl=' + popupString('autoedit_version') + '&autosummary=null';
6121 break;
6122 case 'historyfeed':
6123 l.action='history&feed=rss';
6124 break;
6125 case 'markpatrolled':
6126 l.action='markpatrolled&rcid='+l.rcid;
6127 }
6128
6129 if (hint) {
6130 if (l.oldid) {
6131 hint = simplePrintf(hint, [revisionString]);
6132 }
6133 else {
6134 hint = simplePrintf(hint, [safeDecodeURI(l.article)]);
6135 }
6136 }
6137 else {
6138 hint = safeDecodeURI(l.article + '&action=' + l.action) + (l.oldid) ? '&oldid='+l.oldid : '';
6139 }
6140
6141 return titledWikiLink({article: l.article, action: l.action, text: l.text, newWin:l.newWin,
6142 title: hint, oldid: l.oldid, noPopup: l.noPopup, onclick: l.onclick});
6143}
6144
6145function revertSummary(oldid, diff) {
6146 var ret='';
6147 if (diff == 'prev') {
6148 ret=getValueOf('popupQueriedRevertToPreviousSummary');
6149 } else { ret = getValueOf('popupQueriedRevertSummary'); }
6150 return ret + '&autorv=' + oldid;
6151}
6152
6153function titledWikiLink(l) {
6154 // possible properties of argument:
6155 // article, action, text, title, oldid, actionName, className, noPopup
6156 // oldid = null is fine here
6157
6158 // article and action are mandatory args
6159
6160 if (typeof l.article == 'undefined' || typeof l.action=='undefined') {
6161 errlog('got undefined article or action in titledWikiLink');
6162 return null;
6163 }
6164
6165 var base = pg.wiki.titlebase + l.article.urlString();
6166 var url=base;
6167
6168 if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; }
6169
6170 // no need to add &action=view, and this confuses anchors
6171 if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; }
6172
6173 if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; }
6174
6175 var cssClass=pg.misc.defaultNavlinkClassname;
6176 if (typeof l.className!='undefined' && l.className) { cssClass=l.className; }
6177
6178 return generalNavLink({url: url, newWin: l.newWin,
6179 title: (typeof l.title != 'undefined') ? l.title : null,
6180 text: (typeof l.text!='undefined')?l.text:null,
6181 className: cssClass, noPopup:l.noPopup, onclick:l.onclick});
6182}
6183
6184pg.fn.getLastContrib = function getLastContrib(wikipage, newWin) {
6185 getHistoryInfo(wikipage, function(x) {
6186 processLastContribInfo(x, {page: wikipage, newWin: newWin});
6187 });
6188};
6189
6190function processLastContribInfo(info, stuff) {
6191 if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; }
6192 if(!info.firstNewEditor) {
6193 alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor,info.edits.length]));
6194 return;
6195 }
6196 var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+info.firstNewEditor.oldid;
6197 displayUrl(newUrl, stuff.newWin);
6198}
6199
6200pg.fn.getDiffSinceMyEdit = function getDiffSinceMyEdit(wikipage, newWin) {
6201 getHistoryInfo(wikipage, function(x){
6202 processDiffSinceMyEdit(x, {page: wikipage, newWin: newWin});
6203 });
6204};
6205
6206function processDiffSinceMyEdit(info, stuff) {
6207 if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; }
6208 var friendlyName=stuff.page.split('_').join(' ');
6209 if(!info.myLastEdit) {
6210 alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s',
6211 [info.userName, getValueOf('popupHistoryLimit'), friendlyName]));
6212 return;
6213 }
6214 if(info.myLastEdit.index === 0) {
6215 alert(tprintf("%s seems to be the last editor to the page %s", [info.userName, friendlyName]));
6216 return;
6217 }
6218 var newUrl=pg.wiki.titlebase + new Title(stuff.page).urlString() + '&diff=cur&oldid='+ info.myLastEdit.oldid;
6219 displayUrl(newUrl, stuff.newWin);
6220}
6221
6222function displayUrl(url, newWin){
6223 if(newWin) { window.open(url); }
6224 else { document.location=url; }
6225}
6226
6227pg.fn.purgePopups = function purgePopups() {
6228 processAllPopups(true);
6229 setupCache(); // deletes all cached items (not browser cached, though...)
6230 pg.option={};
6231 abortAllDownloads();
6232};
6233
6234function processAllPopups(nullify, banish) {
6235 for (var i=0; pg.current.links && i<pg.current.links.length; ++i) {
6236 if (!pg.current.links[i].navpopup) { continue; }
6237 if (nullify || banish) pg.current.links[i].navpopup.banish();
6238 pg.current.links[i].simpleNoMore=false;
6239 if (nullify) pg.current.links[i].navpopup=null;
6240 }
6241}
6242
6243pg.fn.disablePopups = function disablePopups(){
6244 processAllPopups(false, true);
6245 setupTooltips(null, true);
6246};
6247
6248pg.fn.togglePreviews = function togglePreviews() {
6249 processAllPopups(true, true);
6250 pg.option.simplePopups=!pg.option.simplePopups;
6251 abortAllDownloads();
6252};
6253
6254function magicWatchLink(l) {
6255 //Yuck!! Would require a thorough redesign to add this as a click event though ...
6256 l.onclick = simplePrintf( 'pg.fn.modifyWatchlist(\'%s\',\'%s\');return false;', [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), this.id] );
6257 return wikiLink(l);
6258}
6259
6260pg.fn.modifyWatchlist = function modifyWatchlist(title, action) {
6261 var reqData = {
6262 'action': 'watch',
6263 'formatversion': 2,
6264 'titles': title,
6265 'uselang': mw.config.get('wgUserLanguage')
6266 };
6267 if ( action === 'unwatch' ) reqData.unwatch = true;
6268
6269 var api = new mw.Api( {
6270 ajax: {
6271 headers: { 'Api-User-Agent': pg.misc.userAgent }
6272 }
6273 } );
6274 // Load the Addedwatchtext or Removedwatchtext message and show it
6275 var mwTitle = mw.Title.newFromText( title );
6276 var messageName;
6277 if ( mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1 ) {
6278 messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk';
6279 } else {
6280 messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext';
6281 }
6282 $.when(
6283 api.postWithToken( 'watch', reqData ),
6284 mw.loader.using( [ 'mediawiki.api.messages', 'mediawiki.jqueryMsg' ] ).then( function () {
6285 return api.loadMessagesIfMissing( [ messageName ] );
6286 } )
6287 ).done( function () {
6288 mw.notify( mw.message( messageName, title ).parseDom() );
6289 } );
6290};
6291
6292function magicHistoryLink(l) {
6293 // FIXME use onclick change href trick to sort this out instead of window.open
6294
6295 var jsUrl='', title='', onClick='';
6296 switch(l.id) {
6297 case 'lastContrib':
6298 onClick=simplePrintf('pg.fn.getLastContrib(\'%s\',%s)',
6299 [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);
6300 title=popupString('lastContribHint');
6301 break;
6302 case 'sinceMe':
6303 onClick=simplePrintf('pg.fn.getDiffSinceMyEdit(\'%s\',%s)',
6304 [l.article.toString(true).split("\\").join("\\\\").split("'").join("\\'"), l.newWin]);
6305 title=popupString('sinceMeHint');
6306 break;
6307 }
6308 jsUrl = 'javascript:' + onClick; // jshint ignore:line
6309 onClick += ';return false;';
6310
6311 return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think
6312 title: title, text: l.text, noPopup: l.noPopup, onclick: onClick });
6313}
6314
6315function popupMenuLink(l) {
6316 var jsUrl=simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line
6317 var title=popupString(simplePrintf('%sHint', [l.id]));
6318 var onClick=simplePrintf('pg.fn.%s();return false;', [l.id]);
6319 return generalNavLink({url: jsUrl, newWin:false, title:title, text:l.text, noPopup:l.noPopup, onclick: onClick});
6320}
6321
6322function specialLink(l) {
6323 // properties: article, specialpage, text, sep
6324 if (typeof l.specialpage=='undefined'||!l.specialpage) return null;
6325 var base = pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId]+':'+l.specialpage;
6326 if (typeof l.sep == 'undefined' || l.sep === null) l.sep='&target=';
6327 var article=l.article.urlString({keepSpaces: l.specialpage=='Search'});
6328 var hint=popupString(l.specialpage+'Hint');
6329 switch (l.specialpage) {
6330 case 'Log':
6331 switch (l.sep) {
6332 case '&user=': hint=popupString('userLogHint'); break;
6333 case '&type=block&page=': hint=popupString('blockLogHint'); break;
6334 case '&page=': hint=popupString('pageLogHint'); break;
6335 case '&type=protect&page=': hint=popupString('protectLogHint'); break;
6336 case '&type=delete&page=': hint=popupString('deleteLogHint'); break;
6337 default: log('Unknown log type, sep=' + l.sep); hint='Missing hint (FIXME)';
6338 }
6339 break;
6340 case 'PrefixIndex': article += '/'; break;
6341 }
6342 if (hint) hint = simplePrintf(hint, [safeDecodeURI(l.article)]);
6343 else hint = safeDecodeURI(l.specialpage+':'+l.article) ;
6344
6345 var url = base + l.sep + article;
6346 return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin, noPopup:l.noPopup});
6347}
6348
6349function generalLink(l) {
6350 // l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick
6351 if (typeof l.url=='undefined') return null;
6352
6353 // only quotation marks in the url can screw us up now... I think
6354 var url=l.url.split('"').join('%22');
6355
6356 var ret='<a href="' + url + '"';
6357 if (typeof l.title!='undefined' && l.title) { ret += ' title="' + pg.escapeQuotesHTML(l.title) + '"'; }
6358 if (typeof l.onclick!='undefined' && l.onclick) { ret += ' onclick="' + pg.escapeQuotesHTML(l.onclick) + '"'; }
6359 if (l.noPopup) { ret += ' noPopup=1'; }
6360 var newWin;
6361 if (typeof l.newWin=='undefined' || l.newWin === null) { newWin=getValueOf('popupNewWindows'); }
6362 else { newWin=l.newWin; }
6363 if (newWin) { ret += ' target="_blank"'; }
6364 if (typeof l.className!='undefined'&&l.className) { ret+=' class="'+l.className+'"'; }
6365 ret += '>';
6366 if (typeof l.text==typeof '') { ret+= l.text; }
6367 ret +='</a>';
6368 return ret;
6369}
6370
6371function appendParamsToLink(linkstr, params) {
6372 var sp=linkstr.parenSplit(RegExp('(href="[^"]+?)"', 'i'));
6373 if (sp.length<2) return null;
6374 var ret=sp.shift() + sp.shift();
6375 ret += '&' + params + '"';
6376 ret += sp.join('');
6377 return ret;
6378}
6379
6380function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor, title (optional) {
6381 if (x.newTarget) {
6382 log ('changeLinkTargetLink: newTarget=' + x.newTarget);
6383 }
6384 if (x.oldTarget !== decodeURIComponent( x.oldTarget ) ) {
6385 log ('This might be an input problem: ' + x.oldTarget );
6386 }
6387
6388 // FIXME: first character of page title as well as namespace should be case insensitive
6389 // eg [[category:X1]] and [[Category:X1]] are equivalent
6390 // this'll break if charAt(0) is nasty
6391 var cA=literalizeRegex(x.oldTarget);
6392 var chs=cA.charAt(0).toUpperCase();
6393 chs='['+chs + chs.toLowerCase()+']';
6394 var currentArticleRegexBit=chs+cA.substring(1);
6395 currentArticleRegexBit=currentArticleRegexBit
6396 .split(RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)')
6397 .split('\\(').join('(?:%28|\\()')
6398 .split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ?
6399 // leading and trailing space should be ignored, and anchor bits optional:
6400 currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + '(?:#[^\\[\\|]*)?)\\s*';
6401 // e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s*
6402
6403 // autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g
6404
6405 var title=x.title || mw.config.get('wgPageName').split('_').join(' ');
6406 var lk=titledWikiLink({article: new Title(title), newWin:x.newWin,
6407 action: 'edit',
6408 text: x.text,
6409 title: x.hint,
6410 className: 'popup_change_title_link'
6411 });
6412 var cmd='';
6413 if (x.newTarget) {
6414 // escape '&' and other nasties
6415 var t=x.newTarget;
6416 var s=literalizeRegex(x.newTarget);
6417 cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~[['+t+'|$1]]~g;';
6418 cmd += 's~\\[\\['+currentArticleRegexBit+'[|]~[['+t+'|~g;';
6419 cmd += 's~\\[\\['+s + '\\|' + s + '\\]\\]~[[' + t + ']]~g';
6420 } else {
6421 cmd += 's~\\[\\['+currentArticleRegexBit+'\\]\\]~$1~g;';
6422 cmd += 's~\\[\\['+currentArticleRegexBit+'[|](.*?)\\]\\]~$2~g';
6423 }
6424 // Build query
6425 cmd = 'autoedit=' + encodeURIComponent ( cmd );
6426 cmd += '&autoclick='+ encodeURIComponent( x.clickButton ) + '&actoken=' + encodeURIComponent( autoClickToken() );
6427 cmd += ( x.minor === null ) ? '' : '&autominor='+ encodeURIComponent( x.minor );
6428 cmd += ( x.watch === null ) ? '' : '&autowatch='+ encodeURIComponent( x.watch );
6429 cmd += '&autosummary='+encodeURIComponent(x.summary);
6430 cmd += '&autoimpl='+encodeURIComponent( popupString('autoedit_version') );
6431 return appendParamsToLink(lk, cmd);
6432}
6433
6434
6435function redirLink(redirMatch, article) {
6436 // NB redirMatch is in wikiText
6437 var ret='';
6438
6439 if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) {
6440 ret += '<hr />';
6441 if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) {
6442 log('redirLink: newTarget=' + redirMatch);
6443 ret += addPopupShortcut(changeLinkTargetLink({
6444 newTarget: redirMatch,
6445 text: popupString('Redirects'),
6446 hint: popupString('Fix this redirect'),
6447 summary: simplePrintf(getValueOf('popupFixRedirsSummary'),[article.toString(), redirMatch]),
6448 oldTarget: article.toString(),
6449 clickButton: getValueOf('popupRedirAutoClick'),
6450 minor: true,
6451 watch: getValueOf('popupWatchRedirredPages')
6452 }), 'R');
6453 ret += popupString(' to ');
6454 }
6455 else ret += popupString('Redirects') + popupString(' to ');
6456 return ret;
6457 }
6458
6459 else return '<br> ' + popupString('Redirects') + popupString(' to ') +
6460 titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view', /* FIXME: newWin */
6461 text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')});
6462}
6463
6464function arinLink(l) {
6465 if (!saneLinkCheck(l)) { return null; }
6466 if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null;
6467
6468 var uN=l.article.userName();
6469
6470 return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + encodeURIComponent(uN), newWin:l.newWin,
6471 title: tprintf('Look up %s in ARIN whois database', [uN]),
6472 text: l.text, noPopup:1});
6473}
6474
6475function toolDbName(cookieStyle) {
6476 var ret = mw.config.get('wgDBname');
6477 if (!cookieStyle) { ret+= '_p'; }
6478 return ret;
6479}
6480
6481function saneLinkCheck(l) {
6482 if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; }
6483 return true;
6484}
6485function editCounterLink(l) {
6486 if(!saneLinkCheck(l)) return null;
6487 if (! pg.wiki.wikimedia) return null;
6488 var uN=l.article.userName();
6489 var tool=getValueOf('popupEditCounterTool');
6490 var url;
6491 var defaultToolUrl='//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3';
6492
6493 switch(tool) {
6494 case 'custom':
6495 url=simplePrintf(getValueOf('popupEditCounterUrl'), [ encodeURIComponent(uN), toolDbName() ]);
6496 break;
6497 case 'soxred': // no longer available
6498 case 'kate': // no longer available
6499 case 'interiot':// no longer available
6500 /* fall through */
6501 case 'supercount':
6502 default:
6503 var theWiki=pg.wiki.hostname.split('.');
6504 url=simplePrintf(defaultToolUrl, [ encodeURIComponent(uN), theWiki[0], theWiki[1] ]);
6505 }
6506 return generalNavLink({url:url, title: tprintf('editCounterLinkHint', [uN]),
6507 newWin:l.newWin, text: l.text, noPopup:1});
6508}
6509
6510
6511function globalSearchLink(l) {
6512 if(!saneLinkCheck(l)) return null;
6513
6514 var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search=';
6515 var article=l.article.urlString({keepSpaces:true});
6516
6517 return generalNavLink({url:base + article, newWin:l.newWin,
6518 title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]),
6519 text: l.text, noPopup:1});
6520}
6521
6522function googleLink(l) {
6523 if(!saneLinkCheck(l)) return null;
6524
6525 var base='https://www.google.com/search?q=';
6526 var article=l.article.urlString({keepSpaces:true});
6527
6528 return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin,
6529 title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]),
6530 text: l.text, noPopup:1});
6531}
6532
6533function editorListLink(l) {
6534 if(!saneLinkCheck(l)) return null;
6535 var article= l.article.articleFromTalkPage() || l.article;
6536 var url='https://xtools.wmflabs.org/articleinfo/' +
6537 encodeURI( pg.wiki.hostname ) + '/' +
6538 article.urlString() +
6539 '?uselang=' + mw.config.get('wgUserLanguage');
6540 return generalNavLink({url:url,
6541 title: tprintf('editorListHint', [article]),
6542 newWin:l.newWin, text: l.text, noPopup:1});
6543}
6544
6545function generalNavLink(l) {
6546 l.className = (l.className === null) ? 'popupNavLink' : l.className;
6547 return generalLink(l);
6548}
6549
6550//////////////////////////////////////////////////
6551// magic history links
6552//
6553
6554function getHistoryInfo(wikipage, whatNext) {
6555 log('getHistoryInfo');
6556 getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory);
6557}
6558
6559// FIXME eliminate pg.idNumber ... how? :-(
6560
6561function getHistory(wikipage, onComplete) {
6562 log('getHistory');
6563 var url = pg.wiki.apiwikibase + '?format=json&formatversion=2&action=query&prop=revisions&titles=' +
6564 new Title(wikipage).urlString() + '&rvlimit=' + getValueOf('popupHistoryLimit');
6565 log('getHistory: url='+url);
6566 return startDownload(url, pg.idNumber+'history', onComplete);
6567}
6568
6569function processHistory(download) {
6570 var jsobj = getJsObj(download.data);
6571 try {
6572 var revisions = anyChild(jsobj.query.pages).revisions;
6573 var edits=[];
6574 for (var i=0; i<revisions.length; ++i) {
6575 edits.push({ oldid: revisions[i].revid, editor: revisions[i].user });
6576 }
6577 log('processed ' + edits.length + ' edits');
6578 return finishProcessHistory( edits, mw.config.get('wgUserName') );
6579 } catch (someError) {
6580 log('Something went wrong with JSON business');
6581 return finishProcessHistory([]);
6582 }
6583}
6584
6585
6586function finishProcessHistory(edits, userName) {
6587 var histInfo={};
6588
6589 histInfo.edits=edits;
6590 histInfo.userName=userName;
6591
6592 for (var i=0; i<edits.length; ++i) {
6593 if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor==userName) {
6594 histInfo.myLastEdit={index: i, oldid: edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)};
6595 }
6596 if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor != edits[0].editor) {
6597 histInfo.firstNewEditor={index:i, oldid:edits[i].oldid, previd: (i === 0 ? null : edits[i-1].oldid)};
6598 }
6599 }
6600 //pg.misc.historyInfo=histInfo;
6601 return histInfo;
6602}
6603//</NOLITE>
6604// ENDFILE: links.js
6605// STARTFILE: options.js
6606//////////////////////////////////////////////////
6607// options
6608
6609// check for existing value, else use default
6610function defaultize(x) {
6611 if (pg.option[x]===null || typeof pg.option[x]=='undefined') {
6612 if (typeof window[x] != 'undefined' ) pg.option[x]=window[x];
6613 else pg.option[x]=pg.optionDefault[x];
6614 }
6615}
6616
6617function newOption(x, def) {
6618 pg.optionDefault[x]=def;
6619}
6620
6621function setDefault(x, def) {
6622 return newOption(x, def);
6623}
6624
6625function getValueOf(varName) {
6626 defaultize(varName);
6627 return pg.option[varName];
6628}
6629
6630/*eslint-disable */
6631function useDefaultOptions() { // for testing
6632 for (var p in pg.optionDefault) {
6633 pg.option[p]=pg.optionDefault[p];
6634 if (typeof window[p]!='undefined') { delete window[p]; }
6635 }
6636}
6637/*eslint-enable */
6638
6639function setOptions() {
6640 // user-settable parameters and defaults
6641 var userIsSysop = false;
6642 if ( mw.config.get('wgUserGroups') ) {
6643 for ( var g = 0; g < mw.config.get('wgUserGroups').length; ++g ) {
6644 if ( mw.config.get('wgUserGroups')[g] == "sysop" )
6645 userIsSysop = true;
6646 }
6647 }
6648
6649 // Basic options
6650 newOption('popupDelay', 0.5);
6651 newOption('popupHideDelay', 0.5);
6652 newOption('simplePopups', false);
6653 newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true
6654 newOption('popupActionsMenu', true);
6655 newOption('popupSetupMenu', true);
6656 newOption('popupAdminLinks', userIsSysop);
6657 newOption('popupShortcutKeys', false);
6658 newOption('popupHistoricalLinks', true);
6659 newOption('popupOnlyArticleLinks', true);
6660 newOption('removeTitles', true);
6661 newOption('popupMaxWidth', 350);
6662 newOption('popupSimplifyMainLink', true);
6663 newOption('popupAppendRedirNavLinks', true);
6664 newOption('popupTocLinks', false);
6665 newOption('popupSubpopups', true);
6666 newOption('popupDragHandle', false /* 'popupTopLinks'*/);
6667 newOption('popupLazyPreviews', true);
6668 newOption('popupLazyDownloads', true);
6669 newOption('popupAllDabsStubs', false);
6670 newOption('popupDebugging', false);
6671 newOption('popupActiveNavlinks', true);
6672 newOption('popupModifier', false); // ctrl, shift, alt or meta
6673 newOption('popupModifierAction', 'enable'); // or 'disable'
6674 newOption('popupDraggable', true);
6675
6676//<NOLITE>
6677 // images
6678 newOption('popupImages', true);
6679 newOption('imagePopupsForImages', true);
6680 newOption('popupNeverGetThumbs', false);
6681 //newOption('popupImagesToggleSize', true);
6682 newOption('popupThumbAction', 'imagepage'); //'sizetoggle');
6683 newOption('popupImageSize', 60);
6684 newOption('popupImageSizeLarge', 200);
6685
6686 // redirs, dabs, reversion
6687 newOption('popupFixRedirs', false);
6688 newOption('popupRedirAutoClick', 'wpDiff');
6689 newOption('popupFixDabs', false);
6690 newOption('popupDabsAutoClick', 'wpDiff');
6691 newOption('popupRevertSummaryPrompt', false);
6692 newOption('popupMinorReverts', false);
6693 newOption('popupRedlinkRemoval', false);
6694 newOption('popupWatchDisambiggedPages', null);
6695 newOption('popupWatchRedirredPages', null);
6696 newOption('popupDabWiktionary', 'last');
6697
6698 // navlinks
6699 newOption('popupNavLinks', true);
6700 newOption('popupNavLinkSeparator', ' ⋅ ');
6701 newOption('popupLastEditLink', true);
6702 newOption('popupEditCounterTool', 'supercount');
6703 newOption('popupEditCounterUrl', '');
6704//</NOLITE>
6705
6706 // previews etc
6707 newOption('popupPreviews', true);
6708 newOption('popupSummaryData', true);
6709 newOption('popupMaxPreviewSentences', 5);
6710 newOption('popupMaxPreviewCharacters', 600);
6711 newOption('popupLastModified', true);
6712 newOption('popupPreviewKillTemplates', true);
6713 newOption('popupPreviewRawTemplates', true);
6714 newOption('popupPreviewFirstParOnly', true);
6715 newOption('popupPreviewCutHeadings', true);
6716 newOption('popupPreviewButton', false);
6717 newOption('popupPreviewButtonEvent', 'click');
6718
6719//<NOLITE>
6720 // diffs
6721 newOption('popupPreviewDiffs', true);
6722 newOption('popupDiffMaxLines', 100);
6723 newOption('popupDiffContextLines', 2);
6724 newOption('popupDiffContextCharacters', 40);
6725 newOption('popupDiffDates', true);
6726 newOption('popupDiffDatePrinter', 'toLocaleString');
6727
6728 // edit summaries. God, these are ugly.
6729 newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary') );
6730 newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary') );
6731 newOption('popupRevertSummary', popupString('defaultpopupRevertSummary') );
6732 newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary') );
6733 newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary') );
6734 newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary') );
6735 newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary') );
6736 newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary') );
6737 newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary') );
6738//</NOLITE>
6739 // misc
6740 newOption('popupHistoryLimit', 50);
6741//<NOLITE>
6742 newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect,
6743 popupFilterPageSize, popupFilterCountLinks,
6744 popupFilterCountImages, popupFilterCountCategories,
6745 popupFilterLastModified]);
6746 newOption('extraPopupFilters', []);
6747 newOption('popupOnEditSelection', 'cursor');
6748 newOption('popupPreviewHistory', true);
6749 newOption('popupImageLinks', true);
6750 newOption('popupCategoryMembers', true);
6751 newOption('popupUserInfo', true);
6752 newOption('popupHistoryPreviewLimit', 25);
6753 newOption('popupContribsPreviewLimit',25);
6754 newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion');
6755 newOption('popupShowGender', true);
6756//</NOLITE>
6757
6758 // new windows
6759 newOption('popupNewWindows', false);
6760 newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true});
6761
6762 // regexps
6763 newOption('popupDabRegexp', '(\\{\\{\\s*disambig(?!uation needed)|disambig(uation|)\\s*\\}\\}|disamb\\s*\\}\\}|dab\\s*\\}\\})|\\{\\{\\s*(((geo|hn|road?|school|number)dis)|[234][lc][acw]|(road|ship)index)(\\s*[|][^}]*)?\\s*[}][}]|is a .*disambiguation.*page');
6764 newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template
6765 newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub');
6766 newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo');
6767}
6768// ENDFILE: options.js
6769// STARTFILE: strings.js
6770//<NOLITE>
6771//////////////////////////////////////////////////
6772// Translatable strings
6773//////////////////////////////////////////////////
6774//
6775// See instructions at
6776// https://en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation
6777
6778pg.string = {
6779 /////////////////////////////////////
6780 // summary data, searching etc.
6781 /////////////////////////////////////
6782 'article': 'article',
6783 'category': 'category',
6784 'categories': 'categories',
6785 'image': 'image',
6786 'images': 'images',
6787 'stub': 'stub',
6788 'section stub': 'section stub',
6789 'Empty page': 'Empty page',
6790 'kB': 'kB',
6791 'bytes': 'bytes',
6792 'day': 'day',
6793 'days': 'days',
6794 'hour': 'hour',
6795 'hours': 'hours',
6796 'minute': 'minute',
6797 'minutes': 'minutes',
6798 'second': 'second',
6799 'seconds': 'seconds',
6800 'week': 'week',
6801 'weeks': 'weeks',
6802 'search': 'search',
6803 'SearchHint': 'Find English Wikipedia articles containing %s',
6804 'web': 'web',
6805 'global': 'global',
6806 'globalSearchHint': 'Search across Wikipedias in different languages for %s',
6807 'googleSearchHint': 'Google for %s',
6808 /////////////////////////////////////
6809 // article-related actions and info
6810 // (some actions also apply to user pages)
6811 /////////////////////////////////////
6812 'actions': 'actions', ///// view articles and view talk
6813 'popupsMenu': 'popups',
6814 'togglePreviewsHint': 'Toggle preview generation in popups on this page',
6815 'enable previews': 'enable previews',
6816 'disable previews': 'disable previews',
6817 'toggle previews': 'toggle previews',
6818 'show preview': 'show preview',
6819 'reset': 'reset',
6820 'more...': 'more...',
6821 'disable': 'disable popups',
6822 'disablePopupsHint': 'Disable popups on this page. Reload page to re-enable.',
6823 'historyfeedHint': 'RSS feed of recent changes to this page',
6824 'purgePopupsHint': 'Reset popups, clearing all cached popup data.',
6825 'PopupsHint': 'Reset popups, clearing all cached popup data.',
6826 'spacebar': 'space',
6827 'view': 'view',
6828 'view article': 'view article',
6829 'viewHint': 'Go to %s',
6830 'talk': 'talk',
6831 'talk page': 'talk page',
6832 'this revision': 'this revision',
6833 'revision %s of %s': 'revision %s of %s',
6834 'Revision %s of %s': 'Revision %s of %s',
6835 'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s',
6836 'Toggle image size': 'Click to toggle image size',
6837 'del': 'del', ///// delete, protect, move
6838 'delete': 'delete',
6839 'deleteHint': 'Delete %s',
6840 'undeleteShort': 'un',
6841 'UndeleteHint': 'Show the deletion history for %s',
6842 'protect': 'protect',
6843 'protectHint': 'Restrict editing rights to %s',
6844 'unprotectShort': 'un',
6845 'unprotectHint': 'Allow %s to be edited by anyone again',
6846 'send thanks': 'send thanks',
6847 'ThanksHint': 'Send a thank you notification to this user',
6848 'move': 'move',
6849 'move page': 'move page',
6850 'MovepageHint': 'Change the title of %s',
6851 'edit': 'edit', ///// edit articles and talk
6852 'edit article': 'edit article',
6853 'editHint': 'Change the content of %s',
6854 'edit talk': 'edit talk',
6855 'new': 'new',
6856 'new topic': 'new topic',
6857 'newSectionHint': 'Start a new section on %s',
6858 'null edit': 'null edit',
6859 'nullEditHint': 'Submit an edit to %s, making no changes ',
6860 'hist': 'hist', ///// history, diffs, editors, related
6861 'history': 'history',
6862 'historyHint': 'List the changes made to %s',
6863 'last': 'prev', // For labelling the previous revision in history pages; the key is "last" for backwards compatibility
6864 'lastEdit': 'lastEdit',
6865 'mark patrolled': 'mark patrolled',
6866 'markpatrolledHint': 'Mark this edit as patrolled',
6867 'show last edit': 'most recent edit',
6868 'Show the last edit': 'Show the effects of the most recent change',
6869 'lastContrib': 'lastContrib',
6870 'last set of edits': 'latest edits',
6871 'lastContribHint': 'Show the net effect of changes made by the last editor',
6872 'cur': 'cur',
6873 'diffCur': 'diffCur',
6874 'Show changes since revision %s': 'Show changes since revision %s',
6875 '%s old': '%s old', // as in 4 weeks old
6876 'oldEdit': 'oldEdit',
6877 'purge': 'purge',
6878 'purgeHint': 'Demand a fresh copy of %s',
6879 'raw': 'source',
6880 'rawHint': 'Download the source of %s',
6881 'render': 'simple',
6882 'renderHint': 'Show a plain HTML version of %s',
6883 'Show the edit made to get revision': 'Show the edit made to get revision',
6884 'sinceMe': 'sinceMe',
6885 'changes since mine': 'diff my edit',
6886 'sinceMeHint': 'Show changes since my last edit',
6887 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s',
6888 'eds': 'eds',
6889 'editors': 'editors',
6890 'editorListHint': 'List the users who have edited %s',
6891 'related': 'related',
6892 'relatedChanges': 'relatedChanges',
6893 'related changes': 'related changes',
6894 'RecentchangeslinkedHint': 'Show changes in articles related to %s',
6895 'editOld': 'editOld', ///// edit old version, or revert
6896 'rv': 'rv',
6897 'revert': 'revert',
6898 'revertHint': 'Revert to %s',
6899 'defaultpopupRedlinkSummary': 'Removing link to empty page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
6900 'defaultpopupFixDabsSummary': 'Disambiguate [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
6901 'defaultpopupFixRedirsSummary': 'Redirect bypass from [[%s]] to [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
6902 'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
6903 'defaultpopupRevertToPreviousSummary': 'Revert to the revision prior to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
6904 'defaultpopupRevertSummary': 'Revert to revision %s using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
6905 'defaultpopupQueriedRevertToPreviousSummary': 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
6906 'defaultpopupQueriedRevertSummary': 'Revert to revision $1 dated $2 by $3 using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
6907 'defaultpopupRmDabLinkSummary': 'Remove link to dab page [[%s]] using [[:en:Wikipedia:Tools/Navigation_popups|popups]]',
6908 'Redirects': 'Redirects', // as in Redirects to ...
6909 ' to ': ' to ', // as in Redirects to ...
6910 'Bypass redirect': 'Bypass redirect',
6911 'Fix this redirect': 'Fix this redirect',
6912 'disambig': 'disambig', ///// add or remove dab etc.
6913 'disambigHint': 'Disambiguate this link to [[%s]]',
6914 'Click to disambiguate this link to:': 'Click to disambiguate this link to:',
6915 'remove this link': 'remove this link',
6916 'remove all links to this page from this article': 'remove all links to this page from this article',
6917 'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article',
6918 'mainlink': 'mainlink', ///// links, watch, unwatch
6919 'wikiLink': 'wikiLink',
6920 'wikiLinks': 'wikiLinks',
6921 'links here': 'links here',
6922 'whatLinksHere': 'whatLinksHere',
6923 'what links here': 'what links here',
6924 'WhatlinkshereHint': 'List the pages that are hyperlinked to %s',
6925 'unwatchShort': 'un',
6926 'watchThingy': 'watch', // called watchThingy because {}.watch is a function
6927 'watchHint': 'Add %s to my watchlist',
6928 'unwatchHint': 'Remove %s from my watchlist',
6929 'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits',
6930 '%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s',
6931 'rss': 'rss',
6932 /////////////////////////////////////
6933 // diff previews
6934 /////////////////////////////////////
6935 'Diff truncated for performance reasons': 'Diff truncated for performance reasons',
6936 'Old revision': 'Old revision',
6937 'New revision': 'New revision',
6938 'Something went wrong :-(': 'Something went wrong :-(',
6939 'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent',
6940 'Unknown date': 'Unknown date',
6941 /////////////////////////////////////
6942 // other special previews
6943 /////////////////////////////////////
6944 'Empty category': 'Empty category',
6945 'Category members (%s shown)': 'Category members (%s shown)',
6946 'No image links found': 'No image links found',
6947 'File links': 'File links',
6948 'No image found': 'No image found',
6949 'Image from Commons': 'Image from Commons',
6950 'Description page': 'Description page',
6951 'Alt text:': 'Alt text:',
6952 'revdel':'Hidden revision',
6953 /////////////////////////////////////
6954 // user-related actions and info
6955 /////////////////////////////////////
6956 'user': 'user', ///// user page, talk, email, space
6957 'user page': 'user page',
6958 'user talk': 'user talk',
6959 'edit user talk': 'edit user talk',
6960 'leave comment': 'leave comment',
6961 'email': 'email',
6962 'email user': 'email user',
6963 'EmailuserHint': 'Send an email to %s',
6964 'space': 'space', // short form for userSpace link
6965 'PrefixIndexHint': 'Show pages in the userspace of %s',
6966 'count': 'count', ///// contributions, log
6967 'edit counter': 'edit counter',
6968 'editCounterLinkHint': 'Count the contributions made by %s',
6969 'contribs': 'contribs',
6970 'contributions': 'contributions',
6971 'deletedContribs': 'deleted contributions',
6972 'DeletedcontributionsHint': 'List deleted edits made by %s',
6973 'ContributionsHint': 'List the contributions made by %s',
6974 'log': 'log',
6975 'user log': 'user log',
6976 'userLogHint': 'Show %s\'s user log',
6977 'arin': 'ARIN lookup', ///// ARIN lookup, block user or IP
6978 'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database',
6979 'unblockShort': 'un',
6980 'block': 'block',
6981 'block user': 'block user',
6982 'IpblocklistHint': 'Unblock %s',
6983 'BlockipHint': 'Prevent %s from editing',
6984 'block log': 'block log',
6985 'blockLogHint': 'Show the block log for %s',
6986 'protectLogHint': 'Show the protection log for %s',
6987 'pageLogHint': 'Show the page log for %s',
6988 'deleteLogHint': 'Show the deletion log for %s',
6989 'Invalid %s %s': 'The option %s is invalid: %s',
6990 'No backlinks found': 'No backlinks found',
6991 ' and more': ' and more',
6992 'undo': 'undo',
6993 'undoHint': 'undo this edit',
6994 'Download preview data': 'Download preview data',
6995 'Invalid or IP user': 'Invalid or IP user',
6996 'Not a registered username': 'Not a registered username',
6997 'BLOCKED': 'BLOCKED',
6998 ' edits since: ': ' edits since: ',
6999 'last edit on ': 'last edit on ',
7000 /////////////////////////////////////
7001 // Autoediting
7002 /////////////////////////////////////
7003 'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort',
7004 'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n',
7005 'The %s button has been automatically clicked. Please wait for the next page to load.': 'The %s button has been automatically clicked. Please wait for the next page to load.',
7006 'Could not find button %s. Please check the settings in your javascript file.': 'Could not find button %s. Please check the settings in your javascript file.',
7007 /////////////////////////////////////
7008 // Popups setup
7009 /////////////////////////////////////
7010 'Open full-size image': 'Open full-size image',
7011 'zxy': 'zxy',
7012 'autoedit_version': 'np20140416'
7013};
7014
7015
7016function popupString(str) {
7017 if (typeof popupStrings != 'undefined' && popupStrings && popupStrings[str]) { return popupStrings[str]; }
7018 if (pg.string[str]) { return pg.string[str]; }
7019 return str;
7020}
7021
7022
7023function tprintf(str,subs) {
7024 if (typeof subs != typeof []) { subs = [subs]; }
7025 return simplePrintf(popupString(str), subs);
7026}
7027
7028//</NOLITE>
7029// ENDFILE: strings.js
7030// STARTFILE: run.js
7031////////////////////////////////////////////////////////////////////
7032// Run things
7033////////////////////////////////////////////////////////////////////
7034
7035
7036// For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some.
7037// The old addOnloadHook did something similar to the below
7038if (document.readyState=="complete")
7039 autoEdit(); //will setup popups
7040else
7041 $( window ).on( 'load', autoEdit );
7042
7043
7044// Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout.
7045( function () {
7046 var once = true;
7047 function dynamicContentHandler( $content ) {
7048 // Try to detect the hook fired on initial page load and disregard
7049 // it, we already hook to onload (possibly to different parts of
7050 // page - it's configurable) and running twice might be bad. Ugly…
7051 if ( $content.attr( 'id' ) == 'mw-content-text' ) {
7052 if ( once ) {
7053 once = false;
7054 return;
7055 }
7056 }
7057
7058 function registerHooksForVisibleNavpops () {
7059 for (var i=0; pg.current.links && i<pg.current.links.length; ++i) {
7060 var navpop = pg.current.links[i].navpopup;
7061 if (!navpop || !navpop.isVisible()) { continue; }
7062
7063 Navpopup.tracker.addHook(posCheckerHook(navpop));
7064 }
7065 }
7066
7067 function doIt () {
7068 registerHooksForVisibleNavpops();
7069 $content.each( function () {
7070 this.ranSetupTooltipsAlready = false;
7071 setupTooltips( this );
7072 } );
7073 }
7074
7075 setupPopups( doIt );
7076 }
7077
7078 // This hook is also fired after page load.
7079 mw.hook( 'wikipage.content' ).add( dynamicContentHandler );
7080
7081 mw.hook( 'ext.echo.overlay.beforeShowingOverlay' ).add( function($overlay){
7082 dynamicContentHandler( $overlay.find(".mw-echo-state") );
7083 });
7084} )();
7085
7086});
7087// ENDFILE: run.js