Misplaced Pages

User:Lupin/popups.js: Difference between revisions

Article snapshot taken from Wikipedia with creative commons attribution-sharealike license. Give it a read and then ask your questions in the chat. We can research this topic together.
< User:Lupin Browse history interactively← Previous editNext edit →Content deleted Content added
Revision as of 03:52, 24 February 2006 view sourceLupin (talk | contribs)19,513 edits try to fix nasty redir bug← Previous edit Revision as of 01:56, 4 March 2006 view source Lupin (talk | contribs)19,513 edits misc bugfixes, dates for diffs, more (all?) diffs now work, ARIN reinstated, souped up revert summary (from history pages only)Next edit →
Line 1: Line 1:
var popupVersion="Thu Feb 23 22:45:55 EST 2006"; var popupVersion="Thu Mar 2 19:40:47 EST 2006";
// STARTFILE: main.js // STARTFILE: main.js
// ********************************************************************** // **********************************************************************
Line 15: Line 15:
// //


document.write('<link rel="stylesheet" type="text/css" href="' +
'http://en.wikipedia.org/search/?title=User:Lupin/navpop.css' +
'&action=raw&ctype=text/css&dontcountme=s">');


if (0) {
//document.write('<link rel="stylesheet" type="text/css" href="http://localhost:8080/js/navpop.css">');
document.write('<link rel="stylesheet" type="text/css" href="' +
'http://en.wikipedia.org/search/?title=User:Lupin/navpop.css' +
'&action=raw&ctype=text/css&dontcountme=s">');
} else {
document.write('<link rel="stylesheet" type="text/css" href="http://localhost:8080/js/navpop.css">');
}


////////////////////////////////////////////////// //////////////////////////////////////////////////
Line 36: Line 39:
flag: {}, // misc flags flag: {}, // misc flags
cache: {}, // page and image cache cache: {}, // page and image cache
diffdata: {}, // support for diff previews diffData: {}, // support for diff previews
structures: {}, // navlink structures structures: {}, // navlink structures
timer: {}, // all sorts of timers (too damn many) timer: {}, // all sorts of timers (too damn many)
Line 63: Line 66:
// STARTFILE: actions.js // STARTFILE: actions.js
function setupTooltips(container) { function setupTooltips(container) {
if (!container) { if (!container) {
// the main initial call // the main initial call
if (getValueOf('popupOnEditSelection')) { if (getValueOf('popupOnEditSelection')) {
Line 91: Line 94:
var remTitles = getValueOf('removeTitles'); var remTitles = getValueOf('removeTitles');
var j=loopend - begin; var j=loopend - begin;
log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin + log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin +
', howmany=' + howmany + ', loopend=' + loopend); ', howmany=' + howmany + ', loopend=' + loopend);
// for (var i=begin; i < loopend; ++i) {
// try a faster (?) loop construct // try a faster (?) loop construct
if (j > 0) { if (j > 0) {
do { do {
var i = loopend - j; var a=anchors;
var a=anchors; if (!a || !a.href) {
log('got null anchor at index ' + loopend - j);
if (!a || !a.href) {
continue;
log('got null anchor at index ' + i);
continue;
} }
//log ('anchors.href=' + anchors.href);
if ( isPopupLink(a) ) { if ( isPopupLink(a) ) {
a.onmouseover=mouseOverWikiLink; a.onmouseover=mouseOverWikiLink;
a.onmouseout= mouseOutWikiLink; a.onmouseout= mouseOutWikiLink;
a.onclick= killPopup; a.onclick= killPopup;
if (remTitles && typeof a.originalTitle=='undefined') { if (remTitles && typeof a.originalTitle=='undefined') {
a.originalTitle=a.title; a.originalTitle=a.title;
a.title=''; a.title='';
}
}
} }
} while (--j); } while (--j);
Line 123: Line 123:
var toc=document.getElementById('toc'); var toc=document.getElementById('toc');
if (toc) { if (toc) {
var tocLinks=toc.getElementsByTagName('A'); var tocLinks=toc.getElementsByTagName('A');
var tocLen = tocLinks.lenght; var tocLen = tocLinks.length;
for (var j=0; j<tocLen; ++j) { for (var j=0; j<tocLen; ++j) {
log ('killing popup for toclinks'); log ('killing popup for toclinks');
a=tocLinks; a=tocLinks;
a.onmouseover=null; a.onmouseout=null; a.onclick=null; a.onmouseover=null; a.onmouseout=null; a.onclick=null;
if (a.originalTitle) { a.title=a.originalTitle; } if (a.originalTitle) { a.title=a.originalTitle; }
}
}
// looks like we just killed any onclick stuff that used to be going on in the toc // looks like we just killed any onclick stuff that used to be going on in the toc
// life is hard // life is hard
} }
} }

pg.flag.finishedLoading=true; pg.flag.finishedLoading=true;
} }
Line 156: Line 156:
if (typeof popupMaxWidth == 'number') { if (typeof popupMaxWidth == 'number') {
var setMaxWidth = function () { var setMaxWidth = function () {
//np.mainDiv.style.width='auto';
//np.mainDiv.style.maxWidth = popupMaxWidth+'px';
//np.mainDiv.style.marginRight = (document.body.clientWidth - popupMaxWidth) + 'px';
np.mainDiv.style.maxWidth = popupMaxWidth + 'px'; np.mainDiv.style.maxWidth = popupMaxWidth + 'px';
np.maxWidth = popupMaxWidth; np.maxWidth = popupMaxWidth;
//np.mainDiv.style.minWidth = '150px';


// hack for IE // hack for IE
// see http://www.svendtofte.com/code/max_width_in_ie/ // see http://www.svendtofte.com/code/max_width_in_ie/
// who knows if this will work? not me.
// use setExpression as documented here on msdn: http://tinyurl dot com/dqljn // use setExpression as documented here on msdn: http://tinyurl dot com/dqljn


// this seems to be the source of that error.... grr.....
if (np.mainDiv.style.setExpression) { if (np.mainDiv.style.setExpression) {
np.mainDiv.style.setExpression('width', 'document.body.clientWidth > ' + np.mainDiv.style.setExpression('width', 'document.body.clientWidth > ' +
popupMaxWidth + ' ? "' +popupMaxWidth + 'px": "auto"'); popupMaxWidth + ' ? "' +popupMaxWidth + 'px": "auto"');
} }
}; };
Line 187: Line 181:
} }



function makeOverDraggable() {
// FIXME fails on IE. probably just the keyboard handling? investigate.
if (typeof over == 'undefined' || !over) { return null; }
if (typeof over.draggable != 'undefined' && over.draggable) { return null; }
Drag.originalEnd=Drag.end;
Drag.end=function(e) { Drag.originalEnd(); olMouseMove(e); return false; };
Drag.originalStart=Drag.start;
Drag.start=function (e) {
try { if (!e.shiftKey) { return; } } catch (err) { return; }
Drag.originalStart.apply(this, );
return false; };
Drag.init(over);
}


function mouseOverWikiLink() { function mouseOverWikiLink() {
Line 214: Line 194:


// try not to duplicate effort // try not to duplicate effort
if (a==pg.current.link && a.navpopup && a.navpopup.visible) { return; } if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; }
pg.current.link=a; pg.current.link=a;


Line 228: Line 208:
var diff=null; var diff=null;
var oldid=oldidFromAnchor(a); var oldid=oldidFromAnchor(a);
if (getValueOf('popupPreviewDiffs')) {
bug=false;
if (! (/^+$/.test(oldid)) ) {
if (oldid=='prev') { bug=true; }
oldid=null;
} else if (getValueOf('popupPreviewDiffs')) {
diff=diffFromAnchor(a); diff=diffFromAnchor(a);
if (! (/^+$/.test(diff)) ) {
switch (diff) {
case 'cur':
diff=0;
break;
case 'prev':
case 'next':
bug=true;
diff=null;
break;
default: diff=null;
}
}
} }

if (pg.timer.image !== null) { if (pg.timer.image !== null) {
clearInterval(pg.timer.image); clearInterval(pg.timer.image);
Line 255: Line 218:
} }


//log('running overlib now');

// try { alert(a.navpopup.visible) } catch(oopsie) {}
if (!a.navpopup) { if (!a.navpopup) {
log ('mouseoverwikilink2: creating new Navpopup'); log ('mouseoverwikilink2: creating new Navpopup');
Line 273: Line 233:
a.navpopup.setInnerHTML(popupHTML(a)); a.navpopup.setInnerHTML(popupHTML(a));
fillEmptySpans(); fillEmptySpans();
if (bug) { /* if (bug) {
setPopupHTML('Cannot generate diff until ' + setPopupHTML('Cannot generate diff until ' +
'<a href="http://bugzilla.wikimedia.org/show_bug.cgi?id=4838">this patch</a> ' + '<a href="http://bugzilla.wikimedia.org/show_bug.cgi?id=4838">this patch</a> ' +
'is applied to Mediawiki. Sorry!<hr>', 'popupError'); 'is applied to Mediawiki. Sorry!<hr>', 'popupError');
} }
*/


} else { } else {
Line 291: Line 252:
if (getValueOf('popupLiveOptions')) { if (getValueOf('popupLiveOptions')) {
setPopupHTML(popupLiveOptionsHTML(), 'popupLiveOptions', pg.idNumber, setPopupHTML(popupLiveOptionsHTML(), 'popupLiveOptions', pg.idNumber,
function () { popupToggleShowOptions(true); } ); function () { popupToggleShowOptions(true); } );
} }
if (getValueOf('popupRedlinkRemoval') && a.className=='new') { if (getValueOf('popupRedlinkRemoval') && a.className=='new') {
Line 307: Line 268:


pg.misc.gImage=null; pg.misc.gImage=null;
//alert(diff+'\n'+oldid);

if (diff===null) { if ( diff===null ) {
if (isImage(article) && ( getValueOf('imagePopupsForImages') || ! anchorContainsImage(a) )) { if (isImage(article) && ( getValueOf('imagePopupsForImages') || ! anchorContainsImage(a) )) {
loadImages(article); loadImages(article);
} }
else if (!isImage(article) && previewImage && !diff) { else if (!isImage(article) && previewImage ) {
pg.counter.redir=0; pg.counter.redir=0;
loadPreview(article, oldid, diff, a.navpopup); loadPreview(article, oldid, diff, a.navpopup);
} }
} }
Line 328: Line 289:


if (getValueOf('popupUnsimplifyLink')) { return; } if (getValueOf('popupUnsimplifyLink')) { return; }
if (!a.navpopup.unsimplified || a.navpopup.pending!==0 ) { if (!a.navpopup.unsimplified || a.navpopup.pending!==0 ) {
log ('running pop.unsimplify(), unsimplified='+a.navpopup.unsimplified + log ('running pop.unsimplify(), unsimplified='+a.navpopup.unsimplified +
', pending='+a.navpopup.pending); ', pending='+a.navpopup.pending);
a.navpopup.pending=0; a.navpopup.pending=0;
pop.unsimplify(); pop.unsimplify();
} }
} }
Line 354: Line 315:
fillEmptySpans({redir: true, redirTarget: target}); fillEmptySpans({redir: true, redirTarget: target});


//if (!pg.flag.isIE) target=myEncodeURI(target);
return loadPreview(target, null, null, navpop); return loadPreview(target, null, null, navpop);
} }
Line 404: Line 364:
if (download && typeof download.data == typeof ''){ if (download && typeof download.data == typeof ''){
if (isInNamespace(pg.current.article, 'Template') && getValueOf('popupPreviewRawTemplates')) { if (isInNamespace(pg.current.article, 'Template') && getValueOf('popupPreviewRawTemplates')) {
// FIXME compare/consolidate with diff escaping code for wikitext // FIXME compare/consolidate with diff escaping code for wikitext
var h='<hr><tt>' + var h='<hr><tt>' +
download.data.split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;").split('\\n').join('<br>\\n') + download.data.split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;").split('\\n').join('<br>\\n') +
'</tt>'; '</tt>';
setPopupHTML(h, 'popupPreview'); setPopupHTML(h, 'popupPreview');
} }
else { else {
// deal with tricksy anchors // deal with tricksy anchors
var anch=decodeAnchor(pg.current.article); var anch=decodeAnchor(pg.current.article);
var d=download.data; var d=download.data;
if (anch) { if (anch) {
var anchRe=RegExp('=+\\s*' + literalizeRegex(anch).replace(//g, '') + '\\s*=+'); var anchRe=RegExp('=+\\s*' + literalizeRegex(anch).replace(//g, '') + '\\s*=+');
var match=d.match(anchRe); var match=d.match(anchRe);
if(match && match.length > 0 && match) { d=d.substring(d.indexOf(match)); } if(match && match.length > 0 && match) { d=d.substring(d.indexOf(match)); }
else { // try to deal with == foo ] boom == -> #foo_baz_boom else { // try to deal with == foo ] boom == -> #foo_baz_boom
var lines=d.split('\n'); var lines=d.split('\n');
for (var i=0; i<lines.length; ++i) { for (var i=0; i<lines.length; ++i) {
lines=lines.replace(RegExp('{2}(]*?)?(.*?)]{2}', 'g'), '$2'); lines=lines.replace(RegExp('{2}(]*?)?(.*?)]{2}', 'g'), '$2');
if (lines.match(anchRe)) { if (lines.match(anchRe)) {
d=d.split('\n').slice(i).join('\n').replace(RegExp('^*'), ''); d=d.split('\n').slice(i).join('\n').replace(RegExp('^*'), '');
break; break;
} }
}
}
}
}
var p=new Previewmaker(d.substring(0,10000));
}
p.showPreview();
}
var p=new Previewmaker(d.substring(0,10000));
p.showPreview();
} }
} }
Line 438: Line 398:
function killPopup() { function killPopup() {
if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); } if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); }
//cClick(); // overlib function to kill the popup
pg.current.link.navpopup.banish(); pg.current.link.navpopup.banish();
pg.current.link=null; pg.current.link=null;
Line 454: Line 413:
// ENDFILE: actions.js // ENDFILE: actions.js
// STARTFILE: domdrag.js // STARTFILE: domdrag.js
/**
/**************************************************
@fileoverview
* dom-drag.js
The {@link Drag} object, which enables objects to be dragged around.
* 09.25.2001
* www.youngpup.net
**************************************************
* 10.28.2001 - fixed minor bug where events
* sometimes fired off the handle, not the root.
**************************************************/


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


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


*/

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


/**
Gets an event in a cross-browser manner.
@param {Event} e
@private
*/
Drag.prototype.fixE = function(e) { Drag.prototype.fixE = function(e) {
if (typeof e == 'undefined') { e = window.event; } if (typeof e == 'undefined') { e = window.event; }
Line 475: Line 465:
return e; return e;
}; };
/**
Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by.
@param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged.
@param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted.
*/
Drag.prototype.init = function(o, oRoot) { Drag.prototype.init = function(o, oRoot) {
var dragObj = this; var dragObj = this;
Line 483: Line 478:
o.hmode = true ; o.hmode = true ;
o.vmode = true ; o.vmode = true ;

o.root = oRoot && oRoot !== null ? oRoot : o ; o.root = oRoot && oRoot !== null ? oRoot : o ;

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

o.root.onthisStart = function(){}; o.root.onthisStart = function(){};
o.root.onthisEnd = function(){}; o.root.onthisEnd = function(){};
Line 494: Line 489:
}; };


/**
Starts the drag.
@private
@param {Event} e
*/
Drag.prototype.start = function(e) { Drag.prototype.start = function(e) {
var o = this.obj; // = this; var o = this.obj; // = this;
Line 515: Line 515:
return false; return false;
}; };
/**

Does the drag.
@param {Event} e
@private
*/
Drag.prototype.drag = function(e) { Drag.prototype.drag = function(e) {
e = this.fixE(e); e = this.fixE(e);
Line 541: Line 545:
}; };


/**
Ends the drag.
@private
*/
Drag.prototype.end = function() { Drag.prototype.end = function() {
//Document.onmousemove = null; //Document.onmousemove = null;
Line 549: Line 557:
this.obj.dragging = false; this.obj.dragging = false;
if (this.endHook) { if (this.endHook) {
this.endHook( parseInt(this.obj.root.style, 10), this.endHook( parseInt(this.obj.root.style, 10),
parseInt(this.obj.root.style, 10)); parseInt(this.obj.root.style, 10));
} }
}; };

// Copyright Aaron Boodman. Saying stupid things daily since March 2001.


// ENDFILE: domdrag.js // ENDFILE: domdrag.js
Line 564: Line 570:
function popupToggleVar(varstring) { function popupToggleVar(varstring) {
pg.option = ! getValueOf(varstring); pg.option = ! getValueOf(varstring);
if (getValueOf('popupCookies')) { if (getValueOf('popupCookies')) {
createCookie(pg.option, String(pg.option)); Cookie.create(pg.option, String(pg.option));
} }
} }
Line 574: Line 580:
if (!dummy) { pg.option.popupLiveOptionsExpanded=!pg.option.popupLiveOptionsExpanded; } if (!dummy) { pg.option.popupLiveOptionsExpanded=!pg.option.popupLiveOptionsExpanded; }
setPopupHTML((pg.option.popupLiveOptionsExpanded) ? '&lt;&lt;' : '&gt;&gt;', setPopupHTML((pg.option.popupLiveOptionsExpanded) ? '&lt;&lt;' : '&gt;&gt;',
'optionPopped'); 'optionPopped');
var s=document.getElementById('popupOptionsDiv'); var s=document.getElementById('popupOptionsDiv');
// if (!s) return; // if (!s) return;
Line 586: Line 592:
html += '<input type="checkbox" id="'+varstring+'Checkbox" '; html += '<input type="checkbox" id="'+varstring+'Checkbox" ';
html += (window) ? 'checked="checked" ' : ''; html += (window) ? 'checked="checked" ' : '';
html += 'onClick="javascript:popupToggleVar(' + "'" + varstring + "'" + html += 'onClick="javascript:popupToggleVar(' + "'" + varstring + "'" +
')">' + label + '</input></span>'; ')">' + label + '</input></span>';
return html; return html;
Line 600: Line 606:
html += '</span>'; html += '</span>';
html += '<div style="display: none" id="popupOptionsDiv">'; html += '<div style="display: none" id="popupOptionsDiv">';
html += popupOptionsCheckboxHTML('simplePopups', popupString('Simple popups'), html += popupOptionsCheckboxHTML('simplePopups', popupString('Simple popups'),
popupString('Never download extra stuff for images/previews')); popupString('Never download extra stuff for images/previews'));
html += popupOptionsCheckboxHTML('popupUnsimplifyLink', popupString('Preview only on click'), html += popupOptionsCheckboxHTML('popupUnsimplifyLink', popupString('Preview only on click'),
popupString('Only start downloading when told to do so')); popupString('Only start downloading when told to do so'));
//html += popupOptionsCheckboxHTML('popupCookies', popupString('cookies'), //html += popupOptionsCheckboxHTML('popupCookies', popupString('cookies'),
// popupString('Use cookies to store popups options')); // popupString('Use cookies to store popups options'));
html += popupOptionsCheckboxHTML('popupNavLinks', popupString('Show navigation links'), html += popupOptionsCheckboxHTML('popupNavLinks', popupString('Show navigation links'),
popupString('Display navigation links at the top of the popup')); popupString('Display navigation links at the top of the popup'));
html += popupOptionsCheckboxHTML('popupImages', popupString('Show image previews'), html += popupOptionsCheckboxHTML('popupImages', popupString('Show image previews'),
popupString('Load images')); popupString('Load images'));
html += popupOptionsCheckboxHTML('popupSummaryData', popupString('Show summary data'), html += popupOptionsCheckboxHTML('popupSummaryData', popupString('Show summary data'),
popupString('Show page summary data')); popupString('Show page summary data'));
html += popupOptionsCheckboxHTML('popupPreviews', popupString('Show text previews'), html += popupOptionsCheckboxHTML('popupPreviews', popupString('Show text previews'),
popupString('Show previews')); popupString('Show previews'));

var extraOptions=[ var extraOptions=[
'imagePopupsForImages', 'imagePopupsForImages',
'popupAdminLinks', 'popupAdminLinks',
'popupAppendRedirNavLinks', 'popupAppendRedirNavLinks',
'popupCookies', 'popupCookies',
'popupFixDabs', 'popupFixDabs',
'popupFixRedirs', 'popupFixRedirs',
'popupHistoricalLinks', 'popupHistoricalLinks',
'popupImagesFromThisWikiOnly', 'popupImagesFromThisWikiOnly',
'popupImagesToggleSize', 'popupImagesToggleSize',
'popupLastEditLink', 'popupLastEditLink',
'popupLiveOptions', 'popupLiveOptions',
'popupLoadImagesSequentially', 'popupLoadImagesSequentially',
'popupNeverGetThumbs', 'popupNeverGetThumbs',
'popupOnlyArticleLinks', 'popupOnlyArticleLinks',
'popupPreviewKillTemplates', 'popupPreviewKillTemplates',
'popupPreviewFirstParOnly', 'popupPreviewFirstParOnly',
'popupShortcutKeys', 'popupShortcutKeys',
'popupSimplifyMainLink', 'popupSimplifyMainLink',
'removeTitles' // no , 'removeTitles' // no ,
]; ];
for (var i=0; i<extraOptions.length; ++i) { for (var i=0; i<extraOptions.length; ++i) {
html += popupOptionsCheckboxHTML(extraOptions, extraOptions, popupString('Toggle this option')); html += popupOptionsCheckboxHTML(extraOptions, extraOptions, popupString('Toggle this option'));
} }

html += '</div>'; html += '</div>';
return html; return html;
Line 649: Line 655:
pg.structures.original.popupLayout=function () { pg.structures.original.popupLayout=function () {
return ['popupBar', 'popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupData', 'popupOtherLinks', return ['popupBar', 'popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupData', 'popupOtherLinks',
'popupRedir', , 'popupRedir', ,
'popupMiscTools', , 'popupMiscTools', ,
'popupPreview', 'popupFixDab']; 'popupPreview', 'popupFixDab'];
}; };
pg.structures.original.popupRedirSpans=function () { pg.structures.original.popupRedirSpans=function () {
Line 700: Line 706:
var move='<<move|shortcut=m|move>>'; var move='<<move|shortcut=m|move>>';
return navlinkStringToHTML('if(talk){' + return navlinkStringToHTML('if(talk){' +
'<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' + '<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' +
'<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move + '<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move +
'}else{<<edit|shortcut=e>>*' + hist + '}else{<<edit|shortcut=e>>*' + hist +
'*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' + '*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' +
'*' + watch + '*' + move+'}<br>', x.article, x.oldid); '*' + watch + '*' + move+'}<br>', x.article, x.oldid);
}; };
pg.structures.fancy.popupOtherLinks=function(x) { pg.structures.fancy.popupOtherLinks=function(x) {
Line 714: Line 720:
var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>'; var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>';
return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal, return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal,
x.article, x.oldid); x.article, x.oldid);
}; };
pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle; pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle;
Line 729: Line 735:
pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title
return ['popupBar', 'popupError', 'popupImage', 'popupTitle', 'popupData', 'popupTopLinks', 'popupOtherLinks', return ['popupBar', 'popupError', 'popupImage', 'popupTitle', 'popupData', 'popupTopLinks', 'popupOtherLinks',
'popupRedir', , 'popupRedir', ,
'popupMiscTools', , 'popupMiscTools', ,
'popupPreview', 'popupFixDab']; 'popupPreview', 'popupFixDab'];
}; };


Line 738: Line 744:
pg.structures.menus.popupLayout=function () { pg.structures.menus.popupLayout=function () {
return ['popupBar', 'popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks', return ['popupBar', 'popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks',
'popupRedir', , 'popupRedir', ,
'popupData', 'popupMiscTools', , 'popupData', 'popupMiscTools', ,
'popupPreview', 'popupFixDab']; 'popupPreview', 'popupFixDab'];
}; };
pg.structures.menus.popupBar = function (x) { pg.structures.menus.popupBar = function (x) {
return ''; //<a href="javascript:toggleSticky(' + pg.current.link.navpopup.uid + ')">(un)stick</a>'; // return <a href="javascript:toggleSticky(' + pg.current.link.navpopup.uid + ')">(un)stick</a>';
return '';
}; };
function toggleSticky(uid) { function toggleSticky(uid) {
Line 776: Line 783:
'if(oldid){<line><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></line>' + 'if(oldid){<line><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></line>' +
'<<revert|shortcut=v>>' + '<<revert|shortcut=v>>' +
'}else{<<edit|shortcut=e>>}' + '}else{<<edit|shortcut=e>>}' +
'if(talk){<<new|shortcut=+|new topic>>}' + 'if(talk){<<new|shortcut=+|new topic>>}' +
hist + lastedit + move + linkshere + related + hist + lastedit + move + linkshere + related +
'<<nullEdit|shortcut=n|null edit>>' + '<<nullEdit|shortcut=n|null edit>>' +
'<line>' + search + '</line>' + '<line>' + search + '</line>' +
'<hr>' + '<hr>' +
'<line>' + watch + '</line>' + '<line>' + watch + '</line>' +
'if(admin){<line>' + protect + '</line><line>' + del + '</line>}' + 'if(admin){<line>' + protect + '</line><line>' + del + '</line>}' +
'if(talk){' + 'if(talk){' +
'<hr>' + '<hr>' +
'<<article|shortcut=a|view article>>' + '<<article|shortcut=a|view article>>' +
'<<editArticle|edit article>>' + '<<editArticle|edit article>>' +
'}else{' + '}else{' +
'<hr>' + '<hr>' +
'<<talk|shortcut=t|talk page>>' + '<<talk|shortcut=t|talk page>>' +
'<<editTalk|edit talk>>' + '<<editTalk|edit talk>>' +
'<<newTalk|shortcut=+|new topic>>' + '<<newTalk|shortcut=+|new topic>>' +
'}' + '}' +
endspan + enddiv; endspan + enddiv;
s+='if(user){*' + dropdiv + '<a href="#">'+popupString('user')+'</a>' + menuspan + s+='if(user){*' + dropdiv + '<a href="#">'+popupString('user')+'</a>' + menuspan +
'<line><<userPage|shortcut=u|user page>>|<<userSpace|space>></line>' + '<line><<userPage|shortcut=u|user page>>|<<userSpace|space>></line>' +
'<<userTalk|shortcut=t|user talk>>' + '<<userTalk|shortcut=t|user talk>>' +
'<<editUserTalk|edit user talk>>' + '<<editUserTalk|edit user talk>>' +
'<<newUserTalk|shortcut=+|leave comment>>' + '<<newUserTalk|shortcut=+|leave comment>>' +
'if(ipuser){<<arin>>}else{<<email|shortcut=E|email user>>}' + 'if(ipuser){<<arin>>}else{<<email|shortcut=E|email user>>}' +
'<hr>' + '<hr>' +
'if(wikimedia){<line>}' + 'if(wikimedia){<line>}' +
'<<contribs|shortcut=c|contributions>>' + '<<contribs|shortcut=c|contributions>>' +
'if(wikimedia){|<<contribsTree|tree>></line>}' + 'if(wikimedia){|<<contribsTree|tree>></line>}' +
'<<userlog|shortcut=L|user log>>' + '<<userlog|shortcut=L|user log>>' +
'if(wikimedia){<<count|shortcut=#|edit counter>>}' + 'if(wikimedia){<<count|shortcut=#|edit counter>>}' +
'if(admin){<line><<unblock|unblockShort>>|<<block|shortcut=b|block user>></line>}' + 'if(admin){<line><<unblock|unblockShort>>|<<block|shortcut=b|block user>></line>}' +
'<<blocklog|shortcut=B|block log>>' + '<<blocklog|shortcut=B|block log>>' +
getValueOf('popupExtraUserMenu') + getValueOf('popupExtraUserMenu') +
endspan + enddiv + '}'; endspan + enddiv + '}';
Line 824: Line 831:
var m=cmdRe.exec(h); var m=cmdRe.exec(h);
if (m) { if (m) {
try { try {
return decodeURI(m); return decodeURI(m);
} catch (someError) {} } catch (someError) {}
Line 889: Line 896:
var sep=str.charAt(1); var sep=str.charAt(1);
str=str.substring(2); str=str.substring(2);

tmp=skipOver(str,sep); tmp=skipOver(str,sep);
if (tmp) { code=tmp.segment.split('\n').join('\\n'); str=tmp.remainder; } if (tmp) { code=tmp.segment.split('\n').join('\\n'); str=tmp.remainder; }
Line 913: Line 920:


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


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


Line 925: Line 932:
if (tmp) {flags=tmp.segment; str=tmp.remainder; } if (tmp) {flags=tmp.segment; str=tmp.remainder; }
} }

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


Line 968: Line 975:
var cmdString=getParamValue('autoedit'); var cmdString=getParamValue('autoedit');
if (cmdString) { if (cmdString) {
try { try {
var editbox=document.editform.wpTextbox1; var editbox=document.editform.wpTextbox1;
} catch (dang) { return; } } catch (dang) { return; }
Line 996: Line 1,003:
var headings=document.getElementsByTagName('h1'); var headings=document.getElementsByTagName('h1');
if (headings) { if (headings) {
var div=document.createElement('div'); var div=document.createElement('div');
var button=document.editform; var button=document.editform;
div.innerHTML='<font size=+1><b>The "' + button.value + div.innerHTML='<font size=+1><b>The "' + button.value +
'" button has been automatically clicked.' + '" button has been automatically clicked.' +
' Please wait for the next page to load.</b></font>'; ' Please wait for the next page to load.</b></font>';
document.title='('+document.title+')'; document.title='('+document.title+')';
headings.parentNode.insertBefore(div, headings); headings.parentNode.insertBefore(div, headings);
button.click(); button.click();
} }
} else { } else {
alert('autoedit.js\n\nautoclick: could not find button "'+ btn +'".'); alert('autoedit.js\n\nautoclick: could not find button "'+ btn +'".');
} }
} }
} }
Line 1,015: Line 1,022:
// ENDFILE: autoedit.js // ENDFILE: autoedit.js
// STARTFILE: downloader.js // STARTFILE: downloader.js
/**
pg.misc.downloadsInProgress = { };
@fileoverview
{@link Downloader}, a xmlhttprequest wrapper, and helper functions.
*/


/**
Creates a new Downloader
@constructor
@class The Downloader class. Create a new instance of this class to download stuff.
@param {String} url The url to download. This can be omitted and supplied later.
*/
function Downloader(url) { function Downloader(url) {
// Source: http://jibbering.com/2002/4/httprequest.html // Source: http://jibbering.com/2002/4/httprequest.html
/** xmlhttprequest object which we're wrapping */
this.http = false; this.http = false;


Line 1,038: Line 1,055:


if (! this.http && typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); } if (! this.http && typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); }
/** The url to download */

this.url = url; this.id=null; this.url = url;
/** A universally unique ID number */
this.id=null;
/** Modification date, to be culled from the incoming headers */
this.lastModified = null; this.lastModified = null;
/** What to do when the download completes successfully */
this.callbackFunction = null; this.callbackFunction = null;
/** Flag set on <code>abort</code> */
this.aborted = false;
} }


new Downloader(); new Downloader();


/** Submits the http request. */
Downloader.prototype.send = function (x) {if (!this.http) { return null; } return this.http.send(x);};
Downloader.prototype.send = function (x) {
if (!this.http) { return null; }
return this.http.send(x);
};
/** Aborts the download, setting the <code>aborted</code> field to true. */
Downloader.prototype.abort = function () { Downloader.prototype.abort = function () {
if (!this.http) { return null; } if (!this.http) { return null; }
this.aborted=true; this.aborted=true;
return this.http.abort(); return this.http.abort();
}; };
/** Returns the downloaded data. */
Downloader.prototype.runCallback = function () {this.callbackFunction(this);};
Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;}; Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;};
/** Prepares the download. */
Downloader.prototype.setTarget = function () {if (!this.http) { return null; } this.http.open("GET", this.url, true);}; Downloader.prototype.setTarget = function () {if (!this.http) { return null; } this.http.open("GET", this.url, true);};
/** Gets the state of the download. */
Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;}; Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;};


pg.misc.downloadsInProgress = { };
Downloader.prototype.noCache=function () { // FIXME make date variable - "5 minutes ago" type stuff
if(!this.http) { return null; }
try { this.http.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"); }
catch (err) {}
};


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


/** Gets the 'Last-Modified' date from the download headers.
Should be run after the download completes.
Returns <code>null</code> on failure.
@return {Date}
*/
Downloader.prototype.getLastModifiedDate=function () { Downloader.prototype.getLastModifiedDate=function () {
if(!this.http) { return null; } if(!this.http) { return null; }
Line 1,079: Line 1,113:
}; };


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


Line 1,088: Line 1,124:
// helper functions // helper functions


/** Creates a new {@link Downloader} and prepares it for action.
@param {String} url The url to download
@param {integer} id The ID of the {@link Downloader} object
@param {Function} callback The callback function invoked on success
@return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
*/
function newDownload(url, id, callback) { function newDownload(url, id, callback) {
var d=new Downloader(url); var d=new Downloader(url);
Line 1,102: Line 1,144:
}; };
d.setCallback(f); d.setCallback(f);
return d;//d.start(); return d;
} }
/** Simulates a download from cached data.

The supplied data is put into a {@link Downloader} as if it had downloaded it.
@param {String} url The url.
@param {integer} id The ID.
@param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>,
where <code>d</code> is the new {@link Downloader}.
@param {String} data The (cached) data.
@param {Date} lastModified The (cached) last modified date.
*/
function fakeDownload(url, id, callback, data, lastModified) { function fakeDownload(url, id, callback, data, lastModified) {
var d=newDownload(url,callback); var d=newDownload(url,callback);
Line 1,112: Line 1,162:
} }


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


/**
Aborts all downloads which have been started.
*/
function abortAllDownloads() { function abortAllDownloads() {
for ( var x in pg.misc.downloadsInProgress ) { for ( var x in pg.misc.downloadsInProgress ) {
Line 1,154: Line 1,214:
window.lp.defaultThumbWidth=getValueOf('popupImageSize'); window.lp.defaultThumbWidth=getValueOf('popupImageSize');
window.lp.skinMagnifyClip=window.lp.skinMagnifyClip||'/skins/common/images/magnify-clip.png'; window.lp.skinMagnifyClip=window.lp.skinMagnifyClip||'/skins/common/images/magnify-clip.png';

} }


Line 1,163: Line 1,224:
return w.html; return w.html;
} }
window.lp.signature=']';
window.lp.blockImage=new RegExp('^\\[\\['+window.lp.imageNamespace+':.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)','i');


function WikiCode() { function WikiCode() {
this.lines=; this.lines=;
this.html=''; this.html='';
window.lp.signature=']';
window.lp.blockImage=new RegExp('^\\[\\['+window.lp.imageNamespace+':.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)','i');
} }
WikiCode.prototype._parse_table_data=function(){ WikiCode.prototype._parse_table_data=function(){
Line 1,176: Line 1,237:
else { this.html+='<t'+((td_match=='|')?'d':'h'); } else { this.html+='<t'+((td_match=='|')?'d':'h'); }
if (typeof td_match!='undefined') { this.html+=' '+td_match;td_line=td_match.split('||'); } if (typeof td_match!='undefined') { this.html+=' '+td_match;td_line=td_match.split('||'); }
else { td_line=td_match.split('||'); } else { td_line=td_match.split('||'); }
this.html+='>'; this.html+='>';
while(td_line.length>1){ this.lines.unshift(td_match+td_line.pop()); } while(td_line.length>1){ this.lines.unshift(td_match+td_line.pop()); }
Line 1,182: Line 1,243:
var td=new WikiCode(); var td=new WikiCode();
var table_count=0; var table_count=0;
while(this.lines.length){ while(this.lines.length){
if(this.lines.charAt(0)=='|') { if(this.lines.charAt(0)=='|') {
if(table_count===0) {break;} if(table_count===0) {break;}
Line 1,215: Line 1,276:
p=false; p=false;
this._endline('<h'+h_match.length+'>' + _parse_inline_nowiki(h_match)+ this._endline('<h'+h_match.length+'>' + _parse_inline_nowiki(h_match)+
'</h'+h_match.length+'>' + h_match); '</h'+h_match.length+'>' + h_match);
}else if(this.lines.match(/^/)){ }else if(this.lines.match(/^/)){
p=false; p=false;
Line 1,233: Line 1,294:
}else{ }else{
if(this.lines===''){ if(this.lines===''){
p=(this.lines.length>1&&this.lines===''); p=(this.lines.length>1&&this.lines==='');
if(p){this._endline('<p><br />');} if(p){this._endline('<p><br />');}
} else { } else {
if(!p){this.html+='<p>'; p=true;} if(!p){this.html+='<p>'; p=true;}
this.html+=_parse_inline_nowiki(this.lines)+' '; this.html+=_parse_inline_nowiki(this.lines)+' ';
} }
this.lines.shift(); this.lines.shift();
Line 1,253: Line 1,314:
else if(prev.charAt(i)=='#'){this.html+='</ol>';} else if(prev.charAt(i)=='#'){this.html+='</ol>';}
else{ else{
this.html+='</d'+((prev.charAt(i)==';')?'t':'d')+'>'; this.html+='</d'+((prev.charAt(i)==';')?'t':'d')+'>';
switch(l_match.charAt(i)){ switch(l_match.charAt(i)){
case'': case'*': case'#': case'': case'*': case'#':
this.html+='</dl>'; this.html+='</dl>';
}
}
} }
} }
for (i=imatch;i<l_match.length;i++){ for (i=imatch;i<l_match.length;i++){
if(l_match.charAt(i)=='*'){this.html+='<ul>';} if(l_match.charAt(i)=='*'){this.html+='<ul>';}
else if(l_match.charAt(i)=='#'){this.html+='<ol>';} else if(l_match.charAt(i)=='#'){this.html+='<ol>';}
else{ else{
switch(prev.charAt(i)){ switch(prev.charAt(i)){
case'':case'*':case'#': case'':case'*':case'#':
this.html+='<dl>'; this.html+='<dl>';
}
}
this.html+='<d'+((l_match.charAt(i)==';')?'t':'d')+'>'; this.html+='<d'+((l_match.charAt(i)==';')?'t':'d')+'>';
} }
} }
Line 1,274: Line 1,335:
case'*': case'#': this.html+='<li>'+_parse_inline_nowiki(l_match); break; case'*': case'#': this.html+='<li>'+_parse_inline_nowiki(l_match); break;
case';': case';':
dt_match=l_match.match(/(.*?) (:.*?)$/); dt_match=l_match.match(/(.*?) (:.*?)$/);
if(dt_match) { if(dt_match) {
this.html+=_parse_inline_nowiki(dt_match); this.html+=_parse_inline_nowiki(dt_match);
this.lines.unshift(dt_match);} this.lines.unshift(dt_match);}
break; break;
case':': case':':
this.html+=_parse_inline_nowiki(l_match); this.html+=_parse_inline_nowiki(l_match);
} }
prev=l_match; prev=l_match;
Line 1,313: Line 1,374:
.replace(/~{3}(?!~)/g,window.lp.signature) .replace(/~{3}(?!~)/g,window.lp.signature)
.replace(RegExp('\\\\]','gi'), .replace(RegExp('\\\\]','gi'),
"<a href='"+window.lp.baseArticlePath+"$1'>$1</a>") "<a href='"+window.lp.baseArticlePath+"$1'>$1</a>")
.replace(RegExp('\\\\]','gi'),'') .replace(RegExp('\\\\]','gi'),'')
.replace(/\*?)\]\](\w*)/g,"<a href='"+window.lp.baseArticlePath+"$1'>$1$2</a>") .replace(/\*?)\]\](\w*)/g,"<a href='"+window.lp.baseArticlePath+"$1'>$1$2</a>")
.replace(/\]+?)\]\](\w*)/g,"<a href='"+window.lp.baseArticlePath+"$1'>$2$3</a>") .replace(/\]+?)\]\](\w*)/g,"<a href='"+window.lp.baseArticlePath+"$1'>$2$3</a>")
.replace(/\]*?:)?(.*?)( *\(.*?\))?\|\]\]/g,"<a href='"+window.lp.baseArticlePath+"$1$2$3'>$2</a>") .replace(/\]*?:)?(.*?)( *\(.*?\))?\|\]\]/g,"<a href='"+window.lp.baseArticlePath+"$1$2$3'>$2</a>")
.replace(/\]*?)) (.*?)\]/g,"<a class='external' title='$1' href='$1'>$5</a>") .replace(/\]*?)) (.*?)\]/g,"<a class='external' title='$1' href='$1'>$5</a>")
.replace(/\/g,"<a class='external' title='$1' href='$1'></a>") .replace(/\/g,"<a class='external' title='$1' href='$1'></a>")
Line 1,324: Line 1,385:
.replace('__NOTOC__','') .replace('__NOTOC__','')
.replace('__NOEDITSECTION__',''); .replace('__NOEDITSECTION__','');
} /* comment for enscript */
}
function _strip_inline_wiki(str){return str.replace(/\]*\|(.*?)\]\]/g,'$1').replace(/\\]/g,'$1').replace(/''(.*?)''/g,'$1');} function _strip_inline_wiki(str){return str.replace(/\]*\|(.*?)\]\]/g,'$1').replace(/\\]/g,'$1').replace(/''(.*?)''/g,'$1');}
function max(a,b){if(a>b){return a;}return b;} function max(a,b){if(a>b){return a;}return b;}
Line 1,432: Line 1,493:
// ENDFILE: pageinfo.js // ENDFILE: pageinfo.js
// STARTFILE: titles.js // STARTFILE: titles.js
/**
function Title(value) {
@fileoverview Defines the {@link Title} class, and associated crufty functions.
this.setUtf(value);

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

*/

/**
Creates a new Stringwrapper.
@constructor

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


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

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

@param {String} value The initial value to assign to the
article. This must be the canonical title (see {@link
Title#value}. Omit this in the constructor and use another function
to set the title if this is unavailable.
*/
function Title(val) {
/**
The canonical article title. This must be in UTF-8 with no
entities, escaping or nasties. Also, underscores should be
replaced with spaces.
@type String
@private
*/
this.value=null;
this.setUtf(val);
/**
The canonical form of the anchor. This should by decoded utf-8 text.
@type String
*/
this.anchor=''; this.anchor='';
} }
Title.prototype=new Stringwrapper();
/**
Returns the canonical representation of the article title with anchor.
@fixme Decide specs for anchor
@return String The article title and the anchor.
*/
Title.prototype.toString=function() {
return this.value + ( this.anchor ? '#' + this.anchor : '' );
};
Title.urlAnchor=function() {
// FIXME FIXME FIXME
return this.anchor;
}
Title.fromURL=function(h) {
return new Title().fromURL(h);
};
Title.prototype.fromURL=function(h) { Title.prototype.fromURL=function(h) {
if (typeof h != 'string') { if (typeof h != 'string') {
Line 1,450: Line 1,605:
return this; return this;
} }

if (pg.flag.isIE) {
// NOTE : playing with decodeURI, encodeURI, escape, unescape,
// IE doesn't do this new-fangled utf-8 thing.
// we seem to be able to replicate the IE borked encoding
// and it's worse than that.

// IE seems to treat the query string differently to the rest of the url
// IE doesn't do this new-fangled utf-8 thing.
// the query is treated as bona-fide utf8, but the first bit of the url is pissed around with
// and it's worse than that.
var splitted=h.split('?');
// IE seems to treat the query string differently to the rest of the url
h=decode_utf8(decodeURI(splitted));
// the query is treated as bona-fide utf8, but the first bit of the url is pissed around with
if (splitted.length>1) {

splitted='';
// we fix up & for all browsers, just in case.
var query=splitted.join('?');
var splitted=h.split('?');
h+=decodeURI(query);
splitted=splitted.split('&').join('%26');
}

if (pg.flag.linksLikeIE) {
splitted=encodeURI(decode_utf8(splitted));
} }

else { h=decodeURI(h); }
h=splitted.join('?');


var contribs=pg.re.contribs.exec(h); var contribs=pg.re.contribs.exec(h);
if (contribs !== null) { if (contribs !== null) {
this.value = pg.ns.user + ':' + contribs; this.value = this.decodeNasties(pg.ns.user + ':' + contribs);
return this; return this;
} }
Line 1,473: Line 1,632:
var email=pg.re.email.exec(h); var email=pg.re.email.exec(h);
if (email !== null) { if (email !== null) {
this.value=pg.ns.user + ':' + email; this.value=this.decodeNasties(pg.ns.user + ':' + email);
return this; return this;
} }
Line 1,481: Line 1,640:
var m=pg.re.main.exec(h); var m=pg.re.main.exec(h);
if(m===null) { this.value=null; } if(m===null) { this.value=null; }
else { this.value=m; } else { this.value=this.decodeNasties(m); }

return this; return this;
};
Title.prototype.decodeNasties=function(txt) {
return this.decodeEscapes(decodeURI(txt));
}
Title.prototype.decodeEscapes=function(txt) {
var split=txt.parenSplit(/({2})/);
var len=split.length;
for (var i=1; i<len; i=i+2) {
split=unescape(split);
}
return split.join('');
};
Title.fromAnchor=function(a) {
return new Title().fromAnchor(a);
}; };
Title.prototype.fromAnchor=function(a) { Title.prototype.fromAnchor=function(a) {
Line 1,491: Line 1,663:
Title.prototype.fromWikiText=function(txt) { Title.prototype.fromWikiText=function(txt) {
// FIXME!!! // FIXME!!!
if (!pg.flag.isIE) { txt=myDecodeURI(txt); } if (!pg.flag.linksLikeIE) { txt=myDecodeURI(txt); }
this.value=txt; this.value=txt;
return this; return this;
Line 1,566: Line 1,738:
for (var i=1; i< pg.ns.talkList.length; ++i) { for (var i=1; i< pg.ns.talkList.length; ++i) {
if (splitted==pg.ns.talkList if (splitted==pg.ns.talkList
|| splitted==pg.ns.talkList.split(' ').join('_')) { || splitted==pg.ns.talkList.split(' ').join('_')) {
splitted=pg.ns.withTalkList; splitted=pg.ns.withTalkList;
this.value= splitted.join(':').substring(1).split(' ').join('_'); this.value= splitted.join(':').substring(1).split(' ').join('_');
Line 1,615: Line 1,787:
return pg.re.ipUser.test(this.userName()); return pg.re.ipUser.test(this.userName());
}; };
Title.prototype.urlString=function() {

return encodeURI(this.value).split('&').join('%26').split('?').join('%3F').split('+').join('%2B');
}




Line 1,621: Line 1,795:
var s=url.parenSplit(RegExp('' + literalizeRegex(param) + '=(*)')); var s=url.parenSplit(RegExp('' + literalizeRegex(param) + '=(*)'));
if (!url) { return null; } if (!url) { return null; }
return s; return s || null;
} }


Line 1,676: Line 1,850:
return ret; return ret;
} }

function Titleanchor() {
// FIXME
this.fromEncoded=function(str) {
// FIXME probably incorrect!
this.encoded=str;
this.value=unesape(str.replace(RegExp('()', 'g'), '%$1'));
};
this.getEncoded=function(str) {
if (this.encoded) {
return this.encoded;
}
// FIXME
return 'Not yet implemented';
};
this.setUTF=function(str) {
this.value=str;
this.encoded=this.getEncoded();
};
}
Titleanchor.prototype=new Stringwrapper();


function removeAnchor(article) { function removeAnchor(article) {
Line 1,759: Line 1,954:
var h=a.href; var h=a.href;
return ( return (
( ( ( (
( h.indexOf(pg.wiki.titlebase) === 0 || h.indexOf(pg.wiki.wikibase) === 0 ) ( h.indexOf(pg.wiki.titlebase) === 0 || h.indexOf(pg.wiki.wikibase) === 0 )
&& !pg.re.urlNoPopup.test(h)) && !pg.re.urlNoPopup.test(h))
|| ||
(pg.re.contribs.test(h) && h.indexOf('&limit=') == -1 ) (pg.re.contribs.test(h) && h.indexOf('&limit=') == -1 )
) )
); );
} }


Line 1,772: Line 1,967:
////////////////////////////////////////////////// //////////////////////////////////////////////////
// Cookie handling // Cookie handling
// pasted straight from http://www.quirksmode.org/js/cookies.html // from http://www.quirksmode.org/js/cookies.html


var Cookie= {
function createCookie(name,value,days)
create: function(name,value,days)
{
var expires; {
var expires;
if (days) if (days)
{ {
var date = new Date(); var date = new Date();
Line 1,783: Line 1,979:
expires = "; expires="+date.toGMTString(); expires = "; expires="+date.toGMTString();
} }
else { expires = ""; } else { expires = ""; }
document.cookie = name+"="+value+expires+"; path=/"; document.cookie = name+"="+value+expires+"; path=/";
} },


function readCookie(name) read: function(name)
{ {
var nameEQ = name + "="; var nameEQ = name + "=";
var ca = document.cookie.split(';'); var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) for(var i=0;i < ca.length;i++)
{ {
var c = ca; var c = ca;
Line 1,797: Line 1,993:
if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); } if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
} }
return null; return null;
} },


function eraseCookie(name) erase: function(name)
{ {
createCookie(name,"",-1); Cookie.create(name,"",-1);
} }
};


// ENDFILE: cookies.js // ENDFILE: cookies.js
Line 1,820: Line 2,017:
// (async)->addPageToCache(download)->-onComplete(download) // (async)->addPageToCache(download)->-onComplete(download)


function getWiki(wikipage, onComplete, oldid, owner) { function getWiki(article, onComplete, oldid, owner) {
log('getWiki, wikipage='+wikipage); // NB wikipage is a Title object
log('getWiki, article='+article);
// IE needs a little help here
var wikipage=article.urlString();
// but even this is buggy :-( FIXME FIXME FIXME eg links on ]
if (pg.flag.isIE) { wikipage=encodeURI(wikipage); }
// set ctype=text/css to get around opera bug // set ctype=text/css to get around opera bug
var url = pg.wiki.titlebase + removeAnchor(wikipage) + '&action=raw&ctype=text/css'; var url = pg.wiki.titlebase + removeAnchor(wikipage) + '&action=raw&ctype=text/css';
if (oldid!=null && oldid!='' && oldid != 0) { url += '&oldid='+oldid;} if (oldid || oldid===0 || oldid==='0') { url += '&oldid='+oldid; }
else { url += '&maxage=0&smaxage=0'; } else { url += '&maxage=0&smaxage=0'; }


Line 1,840: Line 2,036:
if (i > -1) return fakeDownload(url, pg.idNumber, onComplete, pg.cache.pages.data, pg.cache.pages.lastModified); if (i > -1) return fakeDownload(url, pg.idNumber, onComplete, pg.cache.pages.data, pg.cache.pages.lastModified);
var d=getPage(url, onComplete, owner); var d=getPage(url, onComplete, owner);
if (d && owner && owner.addDownload) { if (d && owner && owner.addDownload) {
owner.addDownload(d); owner.addDownload(d);
d.owner=owner; d.owner=owner;
} }
Line 2,245: Line 2,441:
// parenSplit // parenSplit


// String.prototype.parenSplit should do what ECMAscript says // String.prototype.parenSplit should do what ECMAscript says
// String.prototype.split does, interspersing paren matches between // String.prototype.split does, interspersing paren matches between
// the split elements // the split elements
Line 2,257: Line 2,453:
var ret=; var ret=;
while (m && s) { while (m && s) {
// without the following loop, we have // without the following loop, we have
// 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/) // 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/)
for(var i=0; i<m.length; ++i) { for(var i=0; i<m.length; ++i) {
if (typeof m=='undefined') m=''; if (typeof m=='undefined') m='';
} }
ret.push(s.substring(0,m.index)); ret.push(s.substring(0,m.index));
Line 2,287: Line 2,483:
// IE madness with encoding // IE madness with encoding
// ======================== // ========================
// //
// suppose throughout that the page is in utf8, like wikipedia // suppose throughout that the page is in utf8, like wikipedia
// //
// if a is an anchor DOM element and a.href should consist of // if a is an anchor DOM element and a.href should consist of
// //
// http://host.name.here/foo?bar=baz // http://host.name.here/foo?bar=baz
// //
// then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie)) // then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie))
// but IE gives bar=baz correctly as plain utf8 // but IE gives bar=baz correctly as plain utf8
// //
// --------------------------------- // ---------------------------------
// //
// IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here. // IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here.
// //
// --------------------------------- // ---------------------------------
// //
// summat else // summat else


Line 2,316: Line 2,512:
// alle Zeichen von 0-127 => 1byte // alle Zeichen von 0-127 => 1byte
if (c<128) if (c<128)
utftext += String.fromCharCode(c); utftext += String.fromCharCode(c);
// alle Zeichen von 127 bis 2047 => 2byte // alle Zeichen von 127 bis 2047 => 2byte
else if((c>127) && (c<2048)) { else if((c>127) && (c<2048)) {
utftext += String.fromCharCode((c>>6)|192); utftext += String.fromCharCode((c>>6)|192);
utftext += String.fromCharCode((c&63)|128);} utftext += String.fromCharCode((c&63)|128);}
// alle Zeichen von 2048 bis 66536 => 3byte // alle Zeichen von 2048 bis 66536 => 3byte
else { else {
utftext += String.fromCharCode((c>>12)|224); utftext += String.fromCharCode((c>>12)|224);
utftext += String.fromCharCode(((c>>6)&63)|128); utftext += String.fromCharCode(((c>>6)&63)|128);
utftext += String.fromCharCode((c&63)|128);} utftext += String.fromCharCode((c&63)|128);}
} }
return utftext; return utftext;
Line 2,337: Line 2,533:
c = utftext.charCodeAt(i); c = utftext.charCodeAt(i);
if (c<128) { if (c<128) {
plaintext += String.fromCharCode(c); plaintext += String.fromCharCode(c);
i++;} i++;}
else if((c>191) && (c<224)) { else if((c>191) && (c<224)) {
c2 = utftext.charCodeAt(i+1); c2 = utftext.charCodeAt(i+1);
plaintext += String.fromCharCode(((c&31)<<6) | (c2&63)); plaintext += String.fromCharCode(((c&31)<<6) | (c2&63));
i+=2;} i+=2;}
else { else {
c2 = utftext.charCodeAt(i+1); c3 = utftext.charCodeAt(i+2); c2 = utftext.charCodeAt(i+1); c3 = utftext.charCodeAt(i+2);
plaintext += String.fromCharCode(((c&15)<<12) | ((c2&63)<<6) | (c3&63)); plaintext += String.fromCharCode(((c&15)<<12) | ((c2&63)<<6) | (c3&63));
i+=3;} i+=3;}
} }
return plaintext; return plaintext;
Line 2,383: Line 2,579:
var omitRegex=RegExp('^*:|^pecial:|^mage|^ategory'); var omitRegex=RegExp('^*:|^pecial:|^mage|^ategory');
var friendlyCurrentArticleName=pg.current.article.split('_').join(' '); var friendlyCurrentArticleName=pg.current.article.split('_').join(' ');

for (var i=1; i<splitted.length; i=i+3) { for (var i=1; i<splitted.length; i=i+3) {
if (typeof splitted == typeof 'string' && splitted.length>0 && !omitRegex.test(splitted)) { if (typeof splitted == typeof 'string' && splitted.length>0 && !omitRegex.test(splitted)) {
ret.push(changeLinkTargetLink({newTarget: splitted, ret.push(changeLinkTargetLink({newTarget: splitted,
text: splitted.split(' ').join('&nbsp;'), text: splitted.split(' ').join('&nbsp;'),
hint: tprintf('disambigHint', ]), hint: tprintf('disambigHint', ]),
summary: simplePrintf(getValueOf('popupFixDabsSummary'), ]), summary: simplePrintf(getValueOf('popupFixDabsSummary'), ]),
clickButton: 'wpDiff', minor: true, oldTarget: oldTarget, watch: getValueOf('popupWatchDisambiggedPages') clickButton: 'wpDiff', minor: true, oldTarget: oldTarget, watch: getValueOf('popupWatchDisambiggedPages')
})); }));
} /* if */ } /* if */
} /* for loop */ } /* for loop */

ret = rmDupesFromSortedList(ret.sort()); ret = rmDupesFromSortedList(ret.sort());

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


Line 2,427: Line 2,623:
function makeFixDabs(wikiText, oldTarget) function makeFixDabs(wikiText, oldTarget)
{ {
if (getValueOf('popupFixDabs') && isDisambig(wikiText) && if (getValueOf('popupFixDabs') && isDisambig(wikiText) &&
location.href.indexOf(pg.ns.special+':') == -1 && location.href.indexOf(pg.ns.special+':') == -1 &&
pg.current.article.talkPage() ) { pg.current.article.talkPage() ) {
Line 2,480: Line 2,676:
if (redir && typeof args.redirTarget == typeof {}) { if (redir && typeof args.redirTarget == typeof {}) {
article=args.redirTarget; hint=null; article=args.redirTarget; hint=null;
// Gah. FIXME put into Title
// if (pg.flag.isIE) article=decodeURI(article);
} else { } else {
article=(new Title()).fromAnchor(a); article=(new Title()).fromAnchor(a);
Line 2,649: Line 2,843:
return curtop; return curtop;
} }


function fuzzyCursorOffOver(fuzz) {
// FIXME firefox bugs with wide previews
if (!over) return null;
var left = parseInt(over.style.left);
var top = parseInt(over.style.top);
var right = left +
(over.offsetWidth >= parseInt(o3_width) ? over.offsetWidth : parseInt(o3_width));
var bottom = top +
(over.offsetHeight >= o3_aboveheight ? over.offsetHeight : o3_aboveheight);

if (o3_x < left-fuzz || o3_x > right+fuzz || o3_y < top-fuzz || o3_y > bottom + fuzz)
return true;
return false;
};

window.fuzzyCursorOff=function(fuzz) {
return fuzzyCursorOffOver(fuzz) && fuzzyCursorOffMenus(fuzz);
}

// seems that fuzzyCursorOff should precede mouseOutWikiLink in the source
// or sometimes during page loads, errors are generated


function mouseOutWikiLink () { function mouseOutWikiLink () {
//if (typeof over != 'undefined' && over && over.dragging) return null;
log ('mouseOutWikiLink'); log ('mouseOutWikiLink');
var a=this; var a=this;
if (a.navpopup==null) return; if (a.navpopup==null) return;
if (!a.navpopup.visible) { if ( ! a.navpopup.isVisible() ) {
a.navpopup.banish(); a.navpopup.banish();
return; return;
Line 2,690: Line 2,860:
//~ being... //~ being...
if ( if (
!navpop.visible || !navpop.isVisible() ||
(!navpop.isWithin(Navpopup.tracker.x, Navpopup.tracker.y, navpop.fuzz) (!navpop.isWithin(Navpopup.tracker.x, Navpopup.tracker.y)
&& fuzzyCursorOffMenus(Navpopup.tracker.x, Navpopup.tracker.y, navpop.fuzz, navpop.mainDiv)) && fuzzyCursorOffMenus(Navpopup.tracker.x, Navpopup.tracker.y, navpop.fuzz, navpop.mainDiv))
) { ) {
Line 2,714: Line 2,884:
// ENDFILE: mouseout.js // ENDFILE: mouseout.js
// STARTFILE: previewmaker.js // STARTFILE: previewmaker.js
/**
function Previewmaker(wikiText) {this.data=wikiText;}
@fileoverview
Defines the {@link Previewmaker} object, which generates short previews from wiki markup.
*/

/**
Creates a new Previewmaker
@constructor
@class The Previewmaker class. Use an instance of this to generate short previews from Wikitext.
@param {String} wikiText The Wikitext source of the page we wish to preview.
*/
function Previewmaker(wikiText) {
/** The wikitext which is manipulated to generate the preview. */
this.data=wikiText;
}
/** Remove HTML comments
@private
*/
Previewmaker.prototype.killComments = function () { Previewmaker.prototype.killComments = function () {
// this also kills trailing spaces and one trailing newline, eg ] // this also kills trailing spaces and one trailing newline, eg ]
this.data=this.data.replace(RegExp('<!--(\\n|.)*?--> *\\n?', 'g'), ''); this.data=this.data.replace(RegExp('<!--(\\n|.)*?--> *\\n?', 'g'), '');
} }
/**
@private
*/
Previewmaker.prototype.killDivs=function () { Previewmaker.prototype.killDivs=function () {
// say goodbye, divs (can be nested, so use * not *?) // say goodbye, divs (can be nested, so use * not *?)
this.data=this.data.replace(RegExp('< *div* *>*?< */ *div *>', this.data=this.data.replace(RegExp('< *div* *>*?< */ *div *>',
'gi'), ''); 'gi'), '');
} }
/**
@private
*/
Previewmaker.prototype.killGalleries=function () { Previewmaker.prototype.killGalleries=function () {
this.data=this.data.replace(RegExp('< *gallery* *>*?< */ *gallery *>', this.data=this.data.replace(RegExp('< *gallery* *>*?< */ *gallery *>',
'gi'), ''); 'gi'), '');
} }
/**
@private
*/
Previewmaker.prototype.killBoxTemplates=function () { Previewmaker.prototype.killBoxTemplates=function () {
// taxobox hack... in fact, there's a saudiprincebox_begin, so let's be more general // taxobox hack... in fact, there's a saudiprincebox_begin, so let's be more general
this.data=this.data.replace(RegExp('*box(begin|start)' + this.data=this.data.replace(RegExp('*box(begin|start)' +
'*?*box(end|finish) *\\s', 'gi'), ''); '*?*box(end|finish) *\\s', 'gi'), '');


// infoboxes etc // infoboxes etc
// this should cope with templates in infoboxes, but only nested once // this should cope with templates in infoboxes, but only nested once
this.data=this.data.replace(RegExp('*(info|element)box' + this.data=this.data.replace(RegExp('*(info|element)box' +
'\(\|\|*?\)*?\\s*', '\(\|\|*?\)*?\\s*',
'gi'), ''); 'gi'), '');


// also, have float_begin, ... float_end // also, have float_begin, ... float_end
this.data=this.data.replace(RegExp('*floatbegin' + this.data=this.data.replace(RegExp('*floatbegin' +
'*?*floatend.*?', '*?*floatend.*?',
'gi'), ''); 'gi'), '');


// from ]: kill frames too // from ]: kill frames too
this.data=this.data.replace(RegExp('*frame' + this.data=this.data.replace(RegExp('*frame' +
'*?*?end+frame', 'gi'), ''); '*?*?end+frame', 'gi'), '');
} }
/**
@private
*/
Previewmaker.prototype.killTemplates = function () { Previewmaker.prototype.killTemplates = function () {
this.data=this.data.replace(RegExp('(*|)*', 'g'), ' '); this.data=this.data.replace(RegExp('(*|)*', 'g'), ' ');
} }
/**
@private
*/
Previewmaker.prototype.killTables=function () { Previewmaker.prototype.killTables=function () {
// tables are bad, too // tables are bad, too
this.data=this.data.replace this.data=this.data.replace
(RegExp('\\|((|\\|)*||\\|)*\\|', 'g') (RegExp('\\|((|\\||)*||\\||\\|)*\\|', 'g')
, ''); , '');
// remove lines starting with a pipe // remove lines starting with a pipe
this.data=this.data.replace(RegExp('^.*$', 'mg'), ''); this.data=this.data.replace(RegExp('^.*$', 'mg'), '');
} }
/**
@private
*/
Previewmaker.prototype.killImages=function () { Previewmaker.prototype.killImages=function () {
// images are a nono // images are a nono
Line 2,765: Line 2,970:
// ] where ....... consists of repetitions of any of: // ] where ....... consists of repetitions of any of:
// 1. not // 1. not
// 2. )* ]] // 2. )* ]]
// 3. )* ] // 3. )* ]
var imagedetector = var imagedetector =
Line 2,774: Line 2,979:
this.data=this.data.replace(crudeImageRegex, ''); this.data=this.data.replace(crudeImageRegex, '');
} }
/**
@private
*/
Previewmaker.prototype.killHTML=function () { Previewmaker.prototype.killHTML=function () {
// kill <ref>...</ref> // kill <ref>...</ref>
this.data=this.data.replace(RegExp('<ref>*?</ref>', 'gi'), ''); this.data=this.data.replace(RegExp('<ref>*?</ref>', 'gi'), '');

// kill html tables // this doesn't cope with embedded tables // kill html tables // this doesn't cope with embedded tables
// may this is good enough? // may this is good enough?
this.data=this.data.replace(RegExp('<table.*?>*?</\\s*table\\s*>\\n+', 'gi'), ''); this.data=this.data.replace(RegExp('<table.*?>*?</\\s*table\\s*>\\n+', 'gi'), '');

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


// and those pesky html tags // and those pesky html tags, but not <nowiki>
this.data=this.data.replace(RegExp('<.*?>','g'),''); var splitted=this.data.parenSplit(/(<.*?>)/);
var len=splitted.length;
for (var i=1; i<len; i=i+2) {
switch (splitted) {
case '<nowiki>':
case '</nowiki>':
break;
default:
splitted='';
}
}
this.data=splitted.join('');
} }
/**
@private
*/
Previewmaker.prototype.killChunks=function() { // heuristics alert Previewmaker.prototype.killChunks=function() { // heuristics alert
// chunks of italic text? you crazy, man? // chunks of italic text? you crazy, man?
Line 2,794: Line 3,016:
this.data=this.data.replace(italicChunkRegex, ''); this.data=this.data.replace(italicChunkRegex, '');
} }
/**
@private
*/
Previewmaker.prototype.mopup=function () { Previewmaker.prototype.mopup=function () {
// we simply *can't* be doing with horizontal rules right now // we simply *can't* be doing with horizontal rules right now
this.data=this.data.replace(RegExp('^-{4,}','mg'),''); this.data=this.data.replace(RegExp('^-{4,}','mg'),'');


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


Line 2,805: Line 3,030:
this.data=this.data.replace(RegExp('^__*__ *$', 'gmi'),''); this.data=this.data.replace(RegExp('^__*__ *$', 'gmi'),'');
} }
/**
@private
*/
Previewmaker.prototype.firstBit=function () { Previewmaker.prototype.firstBit=function () {
// dont't be givin' me no subsequent paragraphs, you hear me? // dont't be givin' me no subsequent paragraphs, you hear me?
/// first we "normalize" section headings, removing whitespace after, adding before /// first we "normalize" section headings, removing whitespace after, adding before

this.data=this.data.replace(RegExp('\\s*(==+*==+)\\s*', 'g'), '\n\n$1 '); this.data=this.data.replace(RegExp('\\s*(==+*==+)\\s*', 'g'), '\n\n$1 ');


Line 2,817: Line 3,045:
stuff=(RegExp('^(|\\n)*')).exec(this.data); stuff=(RegExp('^(|\\n)*')).exec(this.data);
var d; var d;
if (stuff) d = stuff; if (stuff) d = stuff;
if (!getValueOf('popupPreviewFirstParOnly')) d = this.data; if (!getValueOf('popupPreviewFirstParOnly')) d = this.data;

/// now put \n\n after sections so that bullets and numbered lists work /// now put \n\n after sections so that bullets and numbered lists work
d=d.replace(RegExp('(==+*==+)\\s*', 'g'), '$1\n\n'); d=d.replace(RegExp('(==+*==+)\\s*', 'g'), '$1\n\n');
Line 2,830: Line 3,058:


var notSentenceEnds=RegExp('(|etc|sic|Dr|Mr|Mrs|Ms|St|\\]*|\\s)$', 'i'); var notSentenceEnds=RegExp('(|etc|sic|Dr|Mr|Mrs|Ms|St|\\]*|\\s)$', 'i');

d = this.fixSentenceEnds(d, notSentenceEnds); d = this.fixSentenceEnds(d, notSentenceEnds);


var maxChars=getValueOf('popupMaxPreviewCharacters'); var maxChars=getValueOf('popupMaxPreviewCharacters');
var n=getValueOf('popupMaxPreviewSentences'); var n=getValueOf('popupMaxPreviewSentences');
var dd; var dd;

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

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

for (var i=0; i<strs.length-2; ++i) { for (var i=0; i<strs.length-2; ++i) {
if (reg.test(strs)) { if (reg.test(strs)) {
a=; a=;
for (var j=0; j<strs.length; ++j) { for (var j=0; j<strs.length; ++j) {
if (j<i) a=strs; if (j<i) a=strs;
if (j==i) a=strs+strs+strs; if (j==i) a=strs+strs+strs;
if (j>i+2) a=strs; if (j>i+2) a=strs;
} }
return this.fixSentenceEnds(a,reg); return this.fixSentenceEnds(a,reg);
Line 2,859: Line 3,090:
return strs; return strs;
} }
/**
@private
*/
Previewmaker.prototype.firstSentences=function(strs, howmany) { Previewmaker.prototype.firstSentences=function(strs, howmany) {
var t=strs.slice(0, 2*howmany); var t=strs.slice(0, 2*howmany);
return t.join(''); return t.join('');
} }
/**

Runs the various methods to generate the preview.
The preview is stored in the <code>html</html> field.
@private
*/
Previewmaker.prototype.makePreview=function() { Previewmaker.prototype.makePreview=function() {
if (!isInNamespace(pg.current.article, 'Template')) { if (!isInNamespace(pg.current.article, 'Template')) {
Line 2,882: Line 3,120:
this.killChunks(); this.killChunks();
this.mopup(); this.mopup();

this.firstBit(); this.firstBit();
} }
Line 2,891: Line 3,129:
} }


/** Test function for debugging preview problems one step at a time.
*/
function previewSteps(txt) { function previewSteps(txt) {
try { try {
txt=txt || document.editform.wpTextbox1.value; txt=txt || document.editform.wpTextbox1.value;
} catch (err) { } catch (err) {
if (pg.cache.pages.length > 0) { if (pg.cache.pages.length > 0) {
txt=pg.cache.pages.data; txt=pg.cache.pages.data;
} else { } else {
alert('provide text or use an edit page'); alert('provide text or use an edit page');
} }
} }
Line 2,919: Line 3,159:
p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; } p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; }
p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; } p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; }

p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; } p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; }
} }
Line 2,929: Line 3,169:
} }


/**
Works around a quoting bug in livepreview.
<code>wiki2html(']')</code> gives @literal{<a href='Foo's "bar"'>}
which doesn't do very well. We change this into @literal{<a href="Foo's %22bar%22">}
@private
*/
Previewmaker.prototype.fixHTML=function() { Previewmaker.prototype.fixHTML=function() {
// work around quoting bug in livepreview:
// wiki2html(']') gives <a href='Foo's "bar"'> which doesn't do very well
// we change this into <a href="Foo's %22bar%22">
if(!this.html) return; if(!this.html) return;
var splitted=this.html.parenSplit(/href='(*)'/g); var splitted=this.html.parenSplit(/href='(*)'/g);
Line 2,942: Line 3,185:
this.html=ret; this.html=ret;
} }
/**
Generates the preview and displays it in the current popup.

Does nothing if the generated preview is invalid or consists of whitespace only.
Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true.
*/
Previewmaker.prototype.showPreview=function () { Previewmaker.prototype.showPreview=function () {
this.makePreview(); this.makePreview();
if (typeof this.html != typeof '') return; if (typeof this.html != typeof '') return;
if (RegExp('^\\s*$').test(this.html)) return; if (RegExp('^\\s*$').test(this.html)) return;
//if (getValueOf('popupNavLinks') || getValueOf('popupSummaryData')) //if (getValueOf('popupNavLinks') || getValueOf('popupSummaryData'))
var popTips=function() { setupTooltips(document.getElementById('popupPreview' + pg.idNumber)); }; var popTips=function() { setupTooltips(document.getElementById('popupPreview' + pg.idNumber)); };
setPopupHTML('<hr>'+this.html, 'popupPreview', pg.idNumber, setPopupHTML('<hr>'+this.html, 'popupPreview', pg.idNumber,
getValueOf('popupSubpopups') ? function() { pop.runOnce( popTips, 250 ); } : null ); getValueOf('popupSubpopups') ? function() { pop.runOnce( popTips, 250 ); } : null );
} }
/**
@private
*/
Previewmaker.prototype.stripLongTemplates=function() { Previewmaker.prototype.stripLongTemplates=function() {
// operates on the HTML! // operates on the HTML!
Line 2,960: Line 3,212:
//alert(this.html); //alert(this.html);
} }
/**
@private
*/
Previewmaker.prototype.killMultilineTemplates=function() { Previewmaker.prototype.killMultilineTemplates=function() {
this.data=this.data.replace(RegExp('\\s**\\n*', 'g'), ' '); this.data=this.data.replace(RegExp('\\s**\\n*', 'g'), ' ');
//this.data=this.data.replace(RegExp('(*|)*\\n(*|)*', 'g'), ' '); //this.data=this.data.replace(RegExp('(*|)*\\n(*|)*', 'g'), ' ');
} }


Line 2,983: Line 3,238:
if (window.popupDebug) { // popupDebug is set from .version if (window.popupDebug) { // popupDebug is set from .version
window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; }; window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; };
if (pg.debugLevel != log.None) window.logger.debug(x); if (pg.debugLevel != log.None) window.logger.debug(x);
} }
window.errlog=function(x) { window.errlog=function(x) {
if (pg.debugLevel != log.None) window.logger.error(x); if (pg.debugLevel != log.None) window.logger.error(x);
} }
pg.debugLevel=Log.DEBUG; pg.debugLevel=Log.DEBUG;
window.logger = new Log(pg.debugLevel, Log.popupLogger); window.logger = new Log(pg.debugLevel, Log.popupLogger);
log('Initializing logger'); log('Initializing logger');
Line 3,061: Line 3,316:
var popupImage=document.getElementById("popupImage"+this.pg.idNumber); var popupImage=document.getElementById("popupImage"+this.pg.idNumber);
if (popupImage && typeof popupImage.src != 'undefined') { if (popupImage && typeof popupImage.src != 'undefined') {
clearInterval(pg.timer.image); clearInterval(pg.timer.image);
popupImage.src=goodSrc; popupImage.src=goodSrc;
popupImage.width=getValueOf('popupImageSize'); popupImage.width=getValueOf('popupImageSize');
popupImage.style.display='inline'; popupImage.style.display='inline';
setPopupImageLink(image, pg.wiki.imageSources.wiki); setPopupImageLink(image, pg.wiki.imageSources.wiki);
return true; return true;
} else return false; } else return false;
}; };
Line 3,090: Line 3,345:
newSrc=nextOne(imageUrls, newSrc); newSrc=nextOne(imageUrls, newSrc);
if (!newSrc) { if (!newSrc) {
setImageStatus (' :-('); setImageStatus (' :-(');
return; return;
} }
} }
Line 3,158: Line 3,413:


if(isImage(pg.misc.gImage)) { if(isImage(pg.misc.gImage)) {
popupImage.src=pg.misc.imageArray.src; popupImage.src=pg.misc.imageArray.src;
popupImage.width=getValueOf('popupImageSize'); popupImage.width=getValueOf('popupImageSize');
popupImage.style.display='inline'; popupImage.style.display='inline';
// should we check to see if it's already there? maybe... // should we check to see if it's already there? maybe...
pg.cache.images.push(pg.misc.imageArray.src); pg.cache.images.push(pg.misc.imageArray.src);


setPopupImageLink(pg.misc.gImage, pg.wiki.imageSources.wiki); setPopupImageLink(pg.misc.gImage, pg.wiki.imageSources.wiki);
stopImagesDownloading(); stopImagesDownloading();
} }


Line 3,258: Line 3,513:
default: // unsupported - take a guess default: // unsupported - take a guess
if (pg.wiki.wikimedia) { if (pg.wiki.wikimedia) {
return 'http://upload.wikimedia.org/wikipedia/' + pg.wiki.lang +'/'; return 'http://upload.wikimedia.org/wikipedia/' + pg.wiki.lang +'/';
} }
else /* this should work for wikicities */ else /* this should work for wikicities */
return 'http://' + wiki + '/images/'; return 'http://' + wiki + '/images/';
} }
} }
Line 3,315: Line 3,570:
for (var j=0; j<pg.cache.images.length; ++j) { for (var j=0; j<pg.cache.images.length; ++j) {
if (url == pg.cache.images) { if (url == pg.cache.images) {
loadThisImageAtThisUrl(image, url); loadThisImageAtThisUrl(image, url);
return null; return null;
} }
} }
Line 3,356: Line 3,611:
var match; var match;
while ( match = pg.re.image.exec(wikiText) ) { while ( match = pg.re.image.exec(wikiText) ) {
// now find a sane image name - exclude templates by seeking { // now find a sane image name - exclude templates by seeking {
var m = match || pg.ns.image + ':' + match; var m = match || pg.ns.image + ':' + match;
var pxWidth=match; var pxWidth=match;
Line 3,468: Line 3,723:
// if the number of namespaces changes then this will have to be changed // if the number of namespaces changes then this will have to be changed
// maybe the easiest way is to specify the arrays by hand as in the comments following the loop // maybe the easiest way is to specify the arrays by hand as in the comments following the loop

for (var i=3; i+1<pg.ns.list.length; i=i+2) { for (var i=3; i+1<pg.ns.list.length; i=i+2) {
pg.ns.withTalkList.push(pg.ns.list); pg.ns.withTalkList.push(pg.ns.list);
pg.ns.talkList.push(pg.ns.list); pg.ns.talkList.push(pg.ns.list);
} }

// ALERT! SILLY HARDCODED VALUES FOLLOW! // ALERT! SILLY HARDCODED VALUES FOLLOW!
pg.ns.special = pg.ns.list; pg.ns.special = pg.ns.list;
Line 3,487: Line 3,742:
var R='REDIRECT'; var R='REDIRECT';
var redirLists={ var redirLists={
'be': , 'be': ,
'bg': , 'bg': ,
'cs': , 'cs': ,
'cy': , 'cy': ,
'et': , 'et': ,
'ga': , 'ga': ,
'is': , 'is': ,
'mk': , 'mk': ,
'nds': , 'nds': ,
'nn': , 'nn': ,
'pt': , 'pt': ,
'ru': , 'ru': ,
'sk': , 'sk': ,
'sr': , 'sr': ,
'tt': , 'tt': ,
'vi': // no comma 'vi': // no comma
} }
Line 3,509: Line 3,764:
} }


function setInterwiki() { function setInterwiki() {
if (pg.wiki.wikimedia) { if (pg.wiki.wikimedia) {
pg.wiki.interwiki='aa|ab|af|ak|als|am|an|ang|ar|arc|as|ast|av|ay|az|ba|be|ber|bg|bh|bi|bm|bn|bdf|bo|br|bs|ca|ce|ceb|ch|cho|chr|chy|co|commons|cr|cs|csb|cu|cv|cy|da|de|dv|dz|el|en|eo|es|et|eu|fa|ff|fi|fiu-vro|fj|fo|fr|fur|fy|ga|gd|gil|gl|gn|got|gu|gv|ha|haw|he|hi|ho|hr|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|io|is|it|iu|ja|jbo|jv|ka|kg|ki|kj|kk|kl|km|kn|ko|kr|ks|ku|kv|kw|ky|la|lad|lan|lb|lg|li|ln|lo|lt|lu|lv|mg|mh|mi|mk|ml|mn|mo|mr|ms|mt|mus|my|na|nah|nap|nb|nd|nds|ne|ng|nl|nn|no|nr|nv|ny|oc|oj|om|or|os|pa|pam|pi|pl|ps|pt|qu|rm|rn|ro|roa-rup|ru|rw|sa|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|smg|sn|so|sq|sr|ss|st|su|sv|sw|ta|te|tg|th|ti|tk|tl|tlh|tn|to|tpi|tr|ts|tt|tum|tw|ty|ug|uk|ur|uz|ve|vi|vk|vo|wa|war|wen|wo|xh|yi|yo|za|zh|zh-min-nan|zu'; pg.wiki.interwiki='aa|ab|af|ak|als|am|an|ang|ar|arc|as|ast|av|ay|az|ba|be|ber|bg|bh|bi|bm|bn|bdf|bo|br|bs|ca|ce|ceb|ch|cho|chr|chy|co|commons|cr|cs|csb|cu|cv|cy|da|de|dv|dz|el|en|eo|es|et|eu|fa|ff|fi|fiu-vro|fj|fo|fr|fur|fy|ga|gd|gil|gl|gn|got|gu|gv|ha|haw|he|hi|ho|hr|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|io|is|it|iu|ja|jbo|jv|ka|kg|ki|kj|kk|kl|km|kn|ko|kr|ks|ku|kv|kw|ky|la|lad|lan|lb|lg|li|ln|lo|lt|lu|lv|mg|mh|mi|mk|ml|mn|mo|mr|ms|mt|mus|my|na|nah|nap|nb|nd|nds|ne|ng|nl|nn|no|nr|nv|ny|oc|oj|om|or|os|pa|pam|pi|pl|ps|pt|qu|rm|rn|ro|roa-rup|ru|rw|sa|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|smg|sn|so|sq|sr|ss|st|su|sv|sw|ta|te|tg|th|ti|tk|tl|tlh|tn|to|tpi|tr|ts|tt|tum|tw|ty|ug|uk|ur|uz|ve|vi|vk|vo|wa|war|wen|wo|xh|yi|yo|za|zh|zh-min-nan|zu';
Line 3,521: Line 3,776:
// ENDFILE: namespaces.js // ENDFILE: namespaces.js
// STARTFILE: selpop.js // STARTFILE: selpop.js

window.getEditboxSelection=function() { window.getEditboxSelection=function() {
// see http://www.webgurusforum.com/8/12/0 // see http://www.webgurusforum.com/8/12/0
try { try {
var editbox=document.editform.wpTextbox1; var editbox=document.editform.wpTextbox1;
} catch (dang) { return; } } catch (dang) { return; }
Line 3,533: Line 3,787:
var selEnd = editbox.selectionEnd; var selEnd = editbox.selectionEnd;
return (editbox.value).substring(selStart, selEnd); return (editbox.value).substring(selStart, selEnd);
} }
window.doSelectionPopup=function() { window.doSelectionPopup=function() {
// popup if the selection looks like [[foo|anything afterwards at all // popup if the selection looks like [[foo|anything afterwards at all
Line 3,555: Line 3,809:
// ENDFILE: selpop.js // ENDFILE: selpop.js
// STARTFILE: navpopup.js // STARTFILE: navpopup.js
/**
//~ @h1{navpopup.js}
@fileoverview Defines two classes: {@link Navpopup} and {@link Mousetracker}.


<code>Navpopup</code> describes popups: when they appear, where, what
//~ Prereq: @link{helpers.js} <p>
they look like and so on.


<code>Mousetracker</code> "captures" the mouse using
//~ Define two classes: @tt{Navpopup} and @tt{Mousetracker}.
<code>document.onmousemove</code>.
*/


//~ <p>@tt{Navpopup} describes popups: when they appear, where, what
//~ they look like and so on.

//~ <p>@tt{Mousetracker} "captures" the mouse using
//~ @tt{document.onmousemove}.

//~ <hr>

//~ @h2{Mousetracker}
//~ Class for monitoring mousemove events.

//~ <p>Constructor: we're not active and there are no hooks at the
//~ moment.


/**
Creates a new Mousetracker.
@constructor
@class The Mousetracker class. This monitors mouse movements and manages associated hooks.
*/
function Mousetracker() { function Mousetracker() {
/**
Flag - are we switched on?
@type Boolean
*/
this.active=false; this.active=false;
/**
Array of hook functions.
@private
@type Array
*/
this.hooks=; this.hooks=;
} }


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


/**
//~ Run the hooks if they are in fact functions, passing them the x
//~ and y coords of the mouse. Hook functions that return true are Runs hooks, passing them the x
and y coords of the mouse. Hook functions that return true are
//~ passed to removeHooks for removal. passed to {@link Mousetracker#removeHooks} for removal.
@private
*/
Mousetracker.prototype.runHooks = function () { Mousetracker.prototype.runHooks = function () {
var remove=false; var remove=false;
Line 3,605: Line 3,872:
}; };


/**
//~ Hook removal. Pass an object whose keys are index numbers, with
Removes hooks.
//~ values that evaluate to true
@private
@param {Object} removeObj An object whose keys are the index
numbers of functions for removal, with values that evaluate to true
*/
Mousetracker.prototype.removeHooks = function(removeObj) { Mousetracker.prototype.removeHooks = function(removeObj) {
var newHooks=; var newHooks=;
Line 3,617: Line 3,888:




/**
//~ The @tt{track} method is called when we get the mouse wiggling
//~ events. We simply grab the event, set x and y and run the hooks. Event handler for mouse wiggles.
We simply grab the event, set x and y and run the hooks.
//~ This makes the cpu all hot and bothered :-( This makes the cpu all hot and bothered :-(
@private
@param {Event} e Mousemove event
*/
Mousetracker.prototype.track=function (e) { Mousetracker.prototype.track=function (e) {
//log ('Mousetracker.track'); //log ('Mousetracker.track');
Line 3,648: Line 3,923:
this.lastHook_x=x; this.lastHook_x=x;
this.lastHook_y=y; this.lastHook_y=y;
this.runHooks(); this.runHooks();
} }
} }
}; };


/**
//~ Set things in motion, unless they are already that is.
Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>.
A half-hearted attempt is made to preserve the old event handler if there is one.
*/
Mousetracker.prototype.enable = function () { Mousetracker.prototype.enable = function () {
if (this.active) { return; } if (this.active) { return; }
Line 3,666: Line 3,944:
}; };


/**
Disables the tracker, removing the event handler.
*/
Mousetracker.prototype.disable = function () { Mousetracker.prototype.disable = function () {
if (!this.active) { return; } if (!this.active) { return; }
Line 3,674: Line 3,955:
}; };


/**
//~ @h2{Navpopup class}
Creates a new Navpopup.
//~ Constructor: save and increment uid, set other vars and create the
//~ main div element in which the whole popup lives. Gets a UID for the popup and
@param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable.
@constructor
@class The Navpopup class. This generates popup hints, and does some management of them.
*/
function Navpopup(init) { function Navpopup(init) {
//alert('new Navpopup(init)'); //alert('new Navpopup(init)');
/** UID for each Navpopup instance.
Read-only.
@type integer
*/
this.uid=Navpopup.uid++; this.uid=Navpopup.uid++;
/**
Read-only flag for current visibility of the popup.
@type boolean
@private
*/
this.visible=false; this.visible=false;
/** Flag to be set when we want to cancel a previous request to
show the popup in a little while.
@private
@type boolean
*/
this.noshow=false; this.noshow=false;
/** Categorised list of hooks.
@see #runHooks
@see #addHook
@private
@type Object
*/
this.hooks={ this.hooks={
'create': , 'create': ,
Line 3,687: Line 3,992:
'hide': 'hide':
}; };
/** List of downloads associated with the popup.
@private
@type Array
*/
this.downloads=; this.downloads=;
/** Number of uncompleted downloads.
this.pending=null; // tasks yet to be completed
@type integer
*/
this.pending=null;
/** Tolerance in pixels when detecting whether the mouse has left the popup.
@type integer
*/
this.fuzz=5; this.fuzz=5;
/** Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position.
this.delay=500;
@type boolean
*/
this.constrained=true; this.constrained=true;
/** The popup width in pixels.

@private
@type integer
*/
this.width=0;
/** The popup width in pixels.
@private
@type integer
*/
this.height=0;
/** The main content DIV element.
@type HTMLDivElement
*/
this.mainDiv=null;
this.createMainDiv(); this.createMainDiv();

if (!init || typeof init.draggable=='undefined' || init.draggable) {
if (!init || typeof init.draggable=='undefined' || init.draggable) {
this.makeDraggable();
this.makeDraggable();
} }
} }


/**
//~ This property of the constructor is just a counter, intended to be
A UID for each Navpopup. This constructor property is just a counter.
//~ unique to each popup.
@type integer
@private
*/
Navpopup.uid=0; Navpopup.uid=0;


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

/**
Repositions popup using CSS style.
@private
@param {integer} x x-coordinate (px)
@param {integer} y y-coordinate (px)
@param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition}
*/
Navpopup.prototype.reposition= function (x,y, noLimitHor) { Navpopup.prototype.reposition= function (x,y, noLimitHor) {
log ('reposition('+x+','+y+','+noLimitHor+')'); log ('reposition('+x+','+y+','+noLimitHor+')');
Line 3,715: Line 4,063:
}; };


/**
Prevents popups from being in silly locations. Hopefully.
Should not be run if {@link #constrained} is true.
@private
*/
Navpopup.prototype.limitHorizontalPosition=function() { Navpopup.prototype.limitHorizontalPosition=function() {
if (!this.constrained || this.tooWide) { return; } if (!this.constrained || this.tooWide) { return; }
//var x=this.left; //findPosX(this.mainDiv); //var x=this.left; //findPosX(this.mainDiv);
var x=parseInt(this.mainDiv.style.left, 10); var x=parseInt(this.mainDiv.style.left, 10);
var w=parseInt(this.mainDiv.offsetWidth, 10); var w=parseInt(this.mainDiv.offsetWidth, 10);
var cWidth=document.body.clientWidth; var cWidth=document.body.clientWidth;
log('limitHorizontalPosition: x='+x+ log('limitHorizontalPosition: x='+x+
', this.left=' + this.left + ', this.left=' + this.left +
', this.width=' + this.width + ', this.width=' + this.width +
', cWidth=' + cWidth); ', cWidth=' + cWidth);
if ( (x+w) >= cWidth || if ( (x+w) >= cWidth ||
( x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width ) ) { ( x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width
&& x > cWidth - this.maxWidth ) ) {
// This is a very nasty hack. There has to be a better way! // This is a very nasty hack. There has to be a better way!
// We find the "natural" width of the div by positioning it at the far left // We find the "natural" width of the div by positioning it at the far left
Line 3,735: Line 4,089:
var newLeft=cWidth - naturalWidth - 1; var newLeft=cWidth - naturalWidth - 1;
if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups? if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups?
log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' + log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' +
' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth); ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth);
this.reposition(newLeft, null, true); this.reposition(newLeft, null, true);
Line 3,741: Line 4,095:
}; };


/**
//~ Primitive error handling.
Counter indicating the z-order of the "highest" popup.
Navpopup.prototype.error = function(s) {
We start the z-index at 1000 so that popups are above everything
alert(s);
else on the screen.
};
@private
@type integer
*/
Navpopup.highest=1000;


/**
//~ Bring popup to the top of the z-order. Again, we use a property of
Brings popup to the top of the z-order.
//~ the contructor instead of a global variable.
We increment the {@link #highest} property of the contructor here.
@private
*/
Navpopup.prototype.raise = function () { Navpopup.prototype.raise = function () {
this.mainDiv.style.zIndex=Navpopup.highest + 1; this.mainDiv.style.zIndex=Navpopup.highest + 1;
Line 3,753: Line 4,114:
}; };


/**
//~ Unless @tt{this.noshow} is set, we update its position, bring it
//~ to the top of the z-order and unhide it. @tt{this.noshow} is Shows the popup provided {@link #noshow} is not true.
Updates the position, brings the popup to the top of the z-order and unhides it.
//~ intended to be set when we want to cancel a previous request to
*/
//~ show the popup in a little while.
Navpopup.prototype.show = function () { Navpopup.prototype.show = function () {
//document.title+='s'; //document.title+='s';
Line 3,766: Line 4,127:
}; };



//~ @tt{showSoon}: run the @tt{show} method in a bit, unless we're
/**
//~ already visible.
Runs the {@link #show} method in a little while, unless we're
already visible.
@param {integer} time Delay in milliseconds
@see #showSoonIfStable
*/
Navpopup.prototype.showSoon = function (time) { Navpopup.prototype.showSoon = function (time) {
if (this.visible) { return; } if (this.visible) { return; }
Line 3,785: Line 4,151:
}; };


/**
//~ @tt{showSoonIfStable}: check to see if the mouse pointer has
Checks to see if the mouse pointer has
//~ stabilized (checking every time/2 milliseconds) and run the
stabilised (checking every <code>time</code>/2 milliseconds) and runs the
//~ @tt{show} method if it has.
{@link #show} method if it has. This method makes {@link #showSoon} redundant.
@param {integer} time The minimum time (ms) before the popup may be shown.
*/
Navpopup.prototype.showSoonIfStable = function (time) { Navpopup.prototype.showSoonIfStable = function (time) {
log ('showSoonIfStable, time='+time); log ('showSoonIfStable, time='+time);
Line 3,816: Line 4,185:
}; };


/**
//~ @tt{stick}: make popup unhidable until we call @tt{unstick}
Makes the popup unhidable until we call {@link #unstick}.
*/
Navpopup.prototype.stick=function() { Navpopup.prototype.stick=function() {
this.noshow=false; this.noshow=false;
Line 3,822: Line 4,193:
}; };


/**
Allows the popup to be hidden.
*/
Navpopup.prototype.unstick=function() { Navpopup.prototype.unstick=function() {
this.sticky=false; this.sticky=false;
}; };


/**
//~ @tt{banish}: what happens when the mouse leaves the link before
Sets the {@link #noshow} flag and hides the popup. This should be called
//~ (or after) it's actually been displayed - we set @tt{this.noshow}
when the mouse leaves the link before
//~ to stop it being displayed, and hide it.
(or after) it's actually been displayed.
*/
Navpopup.prototype.banish = function () { Navpopup.prototype.banish = function () {
log ('banish called'); log ('banish called');
Line 3,840: Line 4,216:
}; };


/**
//~ Run hooks
Runs hooks added with {@link #addHook}.
@private
@param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
@param {String} when Controls exactly when the hook is run: either 'before' or 'after'
*/
Navpopup.prototype.runHooks = function (key, when) { Navpopup.prototype.runHooks = function (key, when) {
if (!this.hooks) { return; } if (!this.hooks) { return; }
Line 3,850: Line 4,231:
}; };


/**
//~ Add a hook
Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments.
@param {Function} hook The hook function.
@param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
@param {String} when Controls exactly when the hook is run: either 'before' or 'after'
*/
Navpopup.prototype.addHook = function ( hook, key, when ) { Navpopup.prototype.addHook = function ( hook, key, when ) {
when = when || 'after'; when = when || 'after';
Line 3,857: Line 4,243:
}; };


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

var mainDiv=document.createElement('div'); var mainDiv=document.createElement('div');


//var savedThis=this; var savedThis=this;
//mainDiv.onclick=function(e) {savedThis.onclickHandler(e);}; mainDiv.onclick=function(e) {savedThis.onclickHandler(e);};
mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv'; mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv';
mainDiv.id=mainDiv.className + this.uid; mainDiv.id=mainDiv.className + this.uid;
Line 3,879: Line 4,269:
this.runHooks('create', 'after'); this.runHooks('create', 'after');
}; };
/**

Calls the {@link #raise} method.
@private
*/
Navpopup.prototype.onclickHandler=function(e) {
this.raise();
};
/**
Makes the popup draggable, using a {@link Drag} object.
@private
*/
Navpopup.prototype.makeDraggable=function(e) { Navpopup.prototype.makeDraggable=function(e) {
if (!this.mainDiv) { this.createMainDiv(); } if (!this.mainDiv) { this.createMainDiv(); }
Line 3,892: Line 4,292:
}; };


//~ Hide using CSS. Keep track with an instance property. It's /** Hides the popup using CSS. Runs hooks with key 'hide'.
Sets {@link #visible} appropriately. {@link #banish} should be called externally instead of this method.
//~ unlikely that this should be called externally. Use @tt{banish}

//~ instead.
@private
*/
Navpopup.prototype.hide = function () { Navpopup.prototype.hide = function () {
this.runHooks('hide', 'before'); this.runHooks('hide', 'before');
Line 3,906: Line 4,308:
}; };


//~ Show using CSS. Externally, use @tt{show} instead. /** Shows the popup using CSS. Runs hooks with key 'unhide'.
Sets {@link #visible} appropriately. {@link #show} should be called externally instead of this method.
@private
*/
Navpopup.prototype.unhide = function () { Navpopup.prototype.unhide = function () {
this.runHooks('unhide', 'before'); this.runHooks('unhide', 'before');
Line 3,916: Line 4,321:
}; };


/**
//~ Append a DOM element to the main div.
Sets the <code>innerHTML</code> attribute of the main div containing the popup content.
Navpopup.prototype.append = function (elt) {
@param {String} html The HTML to set.
if (typeof elt==typeof && elt.length && elt && elt.nodeName) {
*/
//alert(elt.length);
for (var i=0; i<elt.length; ++i) {
this.mainDiv.appendChild(elt);
}
}
else { this.mainDiv.appendChild(elt); }
};


//~ Manipulate the innerHTML attribute of the main div
Navpopup.prototype.getInnerHTML = function () {
return this.mainDiv.innerHTML;
};
Navpopup.prototype.setInnerHTML = function (html) { Navpopup.prototype.setInnerHTML = function (html) {
this.mainDiv.innerHTML = html; this.mainDiv.innerHTML = html;
};
Navpopup.prototype.appendHTML = function (html) {
this.setInnerHTML( this.getInnerHTML() + html );
};

//~ toggle popup visibility
Navpopup.prototype.toggleVisibility = function () {
if (this.visible) { this.hide(); }
else { this.unhide(); }
}; };


/**
//~ get width and height using CSS
Updates the {@link #width} and {@link #height} attributes with the CSS properties.
@private
*/
Navpopup.prototype.updateDimensions = function () { Navpopup.prototype.updateDimensions = function () {
this.width=parseInt(this.mainDiv.offsetWidth, 10); this.width=parseInt(this.mainDiv.offsetWidth, 10);
Line 3,951: Line 4,338:
}; };


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


/**
Adds a download to {@link #downloads}.
@param {Downloader} download
*/
Navpopup.prototype.addDownload=function(download) { Navpopup.prototype.addDownload=function(download) {
if (!download) { return; } if (!download) { return; }
this.downloads.push(download); this.downloads.push(download);
} };
/**
Aborts the downloads listed in {@link #downloads}.
@see Downloader#abort
*/
Navpopup.prototype.abortDownloads=function() { Navpopup.prototype.abortDownloads=function() {
for(var i=0; i<this.downloads.length; ++i) { for(var i=0; i<this.downloads.length; ++i) {
Line 3,978: Line 4,377:
} }


//~ @h3{Navpopup constructor properties}


/**
//~ Make a mousetracker which is a property of the constructor
A {@link Mousetracker} instance which is a property of the constructor (pseudo-global).
//~ (pseudo-global).
*/
Navpopup.tracker=new Mousetracker(); Navpopup.tracker=new Mousetracker();

//~ Start the z-index quite high so that popups are above everything
//~ else on the screen.
Navpopup.highest=1000;

//~ a suggestion for an onmouseover handler for links

//~ <p>Remember, @tt{this} here refers to whatever this handler gets
//~ attached to, most likely an anchor DOM element.
Navpopup.mouseoverlink=function(e) {
if ( !e && !window.event) { return; }
e = e || window.event;
//~ If there's no navpopup associated with this element, make one
//~ and slap it on.
if (!this.navpopup) {
this.navpopup=new Navpopup();
this.navpopup.fuzz=20;
}
this.navpopup.showSoon(this.navpopup.delay);
};

//~ and the same for onmouseout. In this function, we're using the
//~ Navpopup.tracker object defined above (the same tracker for all
//~ popups). If you wanted a different mouseout function you could
//~ use (a) different tracker(s), although running several trackers at
//~ once probably wouldn't work.
Navpopup.mouseoutlink=function(e) {
if (!this.navpopup) { return; }
//~ closure coming up, so save @tt{this}
var thisAnchor=this;
var f= function () {
//~ if the navpopup isn't visible, or if it is and should stop
//~ being...
if (!thisAnchor.navpopup.isWithin(Navpopup.tracker.x, Navpopup.tracker.y, thisAnchor.navpopup.fuzz)) {
//~ stop it being visible or appearing from a @tt{showSoon} call
thisAnchor.navpopup.banish();
return true;
}
};
Navpopup.tracker.addHook(f);
};

//~ <hr>Test function (example usage).
//~ <p> Note that we don't explicitly
//~ create any new Navpopup objects - it's all done dynamically behind
//~ the scenes.
function testNavpopup() {
//~ We have to explicitly turn on the tracker associated with the
//~ @b{constructor}.
Navpopup.tracker.enable();

for (var i=0; i<document.links.length; ++i) {
var a=document.links;
a.onmouseover=Navpopup.mouseoverlink;
a.onmouseout=Navpopup.mouseoutlink;
}
}

/// Local Variables: ///
/// mode:c ///
/// fill-prefix:"//~ " ///
/// End: ///


// ENDFILE: navpopup.js // ENDFILE: navpopup.js
Line 4,061: Line 4,398:
function delFmt(x) { function delFmt(x) {
if (!x.length) { return ''; } if (!x.length) { return ''; }
return "<del style='background:#FFE6E6;'>" + diffEscape(x.join('')).split('\n').join('&para;\n') +"</del>"; return "<del style='background:#FFE6E6;'>" + /*diffEscape*/(x.join('')).split('\n').join('&para;\n') +"</del>";
} }
function insFmt(x) { function insFmt(x) {
if (!x.length) { return ''; } if (!x.length) { return ''; }
return "<ins style='background:#AAFFEE;'>" + diffEscape(x.join('')).split('\n').join('&para;\n') +"</ins>"; return "<ins style='background:#AAFFEE;'>" + /*diffEscape*/(x.join('')).split('\n').join('&para;\n') +"</ins>";
} }


Line 4,086: Line 4,423:
for (var j=0; j<a.length; ++j) { for (var j=0; j<a.length; ++j) {
if (!a.row || a.row === 0) { continue; } if (!a.row || a.row === 0) { continue; }
if ( (j-b.row)*(i-a.row) > 0) { if ( (j-b.row)*(i-a.row) > 0) {
if(eject) { return true; } if(eject) { return true; }
count++; count++;
} }
} }
Line 4,143: Line 4,480:
if (i+1 < splitted.length) { ret.push(splitted.substring(splitted.length-context) + splitted); } if (i+1 < splitted.length) { ret.push(splitted.substring(splitted.length-context) + splitted); }
} }
}
while (ret.length > 0 && !ret) {
ret = ret.slice(1);
} }
return ret; return ret;
Line 4,150: Line 4,490:
function diffString( o, n, simpleSplit, slow ) { function diffString( o, n, simpleSplit, slow ) {
var splitRe=RegExp('({2}|]{2}|{2,3}|{2,3}||=|+|\\b)'); var splitRe=RegExp('({2}|]{2}|{2,3}|{2,3}||=|+|\\b)');

o=diffEscape(o); n=diffEscape(n);
var out, i; var out, i;
if (simpleSplit) { out = diff( o.split(/\b/), n.split(/\b/) ); } if (simpleSplit) { out = diff( o.split(/\b/), n.split(/\b/) ); }
Line 4,164: Line 4,506:
for (i=0; i<out.n.length; ++i) { for (i=0; i<out.n.length; ++i) {
if ( out.n.row != null) { if ( out.n.row != null) {
if( maxOutputPair > out.n.row ) { if( maxOutputPair > out.n.row ) {
// tangle - delete pairing // tangle - delete pairing
out.o.row ]=out.o.row ].text; out.o.row ]=out.o.row ].text;
out.n=out.n.text; out.n=out.n.text;
}
}
if (maxOutputPair < out.n.row) { maxOutputPair = out.n.row; } if (maxOutputPair < out.n.row) { maxOutputPair = out.n.row; }
} }
} }
Line 4,239: Line 4,581:
for ( i = 0; i < n.length - 1; i++ ) { for ( i = 0; i < n.length - 1; i++ ) {
if ( n.text != null && n.text == null && if ( n.text != null && n.text == null &&
n.row < o.length - 1 && o.row + 1 ].text == null && n.row < o.length - 1 && o.row + 1 ].text == null &&
n == o.row + 1 ] ) { n == o.row + 1 ] ) {
n = { text: n, row: n.row + 1 }; n = { text: n, row: n.row + 1 };
o.row+1] = { text: o.row+1], row: i + 1 }; o.row+1] = { text: o.row+1], row: i + 1 };
Line 4,249: Line 4,591:
for ( i = n.length - 1; i > 0; i-- ) { for ( i = n.length - 1; i > 0; i-- ) {
if ( n.text != null && n.text == null && if ( n.text != null && n.text == null &&
n.row > 0 && o.row - 1 ].text == null && n.row > 0 && o.row - 1 ].text == null &&
n == o.row - 1 ] ) { n == o.row - 1 ] ) {
n = { text: n, row: n.row - 1 }; n = { text: n, row: n.row - 1 };
o.row-1] = { text: o.row-1], row: i - 1 }; o.row-1] = { text: o.row-1], row: i - 1 };
Line 4,301: Line 4,643:
pg.re.stub= RegExp('stub|This .*-related article is a .*stub', 'im') ; pg.re.stub= RegExp('stub|This .*-related article is a .*stub', 'im') ;
pg.re.disambig=RegExp('(\\s*disambig|disambig\\s*|disamb\\s*|dab\\s*)' + pg.re.disambig=RegExp('(\\s*disambig|disambig\\s*|disamb\\s*|dab\\s*)' +
'|\\s*(geo|hn)dis\\s*' + // explicit, whole template names on this line '|\\s*(geo|hn)dis\\s*' + // explicit, whole template names on this line
'|is a .*disambiguation.*page', 'im') ; '|is a .*disambiguation.*page', 'im') ;


pg.re.urlNoPopup=RegExp('((title=|/)' + pg.ns.special + ':|section=)') ; pg.re.urlNoPopup=RegExp('((title=|/)' + pg.ns.special + ':|section=)') ;
Line 4,309: Line 4,651:


// note: tries to get images in infobox templates too, e.g. movie pages, album pages etc // note: tries to get images in infobox templates too, e.g. movie pages, album pages etc
// (^|\]* ]) * // (^|\]* ]) *
// (^|\]* ])(]*(+) *px)? // (^|\]* ])(]*(+) *px)?
// $4 = 120 as in 120px // $4 = 120 as in 120px
Line 4,318: Line 4,660:
pg.re.categoryBracketCount = 1; pg.re.categoryBracketCount = 1;


pg.re.ipUser=RegExp('('+pg.ns.user+':)?' + '((25|2|1||)\\.){3}' + pg.re.ipUser=RegExp('('+pg.ns.user+':)?' + '((25|2|1||)\\.){3}' +
'(25|2|1||)'); '(25|2|1||)');


// FIXME replace with general parameter parsing function, this is daft // FIXME replace with general parameter parsing function, this is daft
Line 4,346: Line 4,688:
); );
} }

// unusual thumb sizes and full-size // unusual thumb sizes and full-size
pg.wiki.imageSources.push( pg.wiki.imageSources.push(
Line 4,375: Line 4,717:
pg.cache.pages = ; pg.cache.pages = ;
pg.cache.images = ; pg.cache.images = ;

// FIXME what is this for? // FIXME what is this for?
pg.misc.gImage=null; // global for image pg.misc.gImage=null; // global for image
Line 4,393: Line 4,735:
// for myDecodeURI // for myDecodeURI
pg.misc.decodeExtras = [ pg.misc.decodeExtras = [
{from: '%2C', to: ',' }, {from: '%2C', to: ',' },
{from: '_', to: ' ' }, {from: '_', to: ' ' },
{from: '%24', to: '$'}, {from: '%24', to: '$'},
{from: '%26', to: '&' } // no , {from: '%26', to: '&' } // no ,
]; ];
Line 4,411: Line 4,753:
if (typeof window.opera != 'undefined') { if (typeof window.opera != 'undefined') {
setDefault('popupStructure','original'); setDefault('popupStructure','original');
} else if ((self.navigator.appName)=='Konqueror') { } else if (navigator.appName=='Konqueror') {
setDefault('popupNavLinkSeparator', ' &bull; '); setDefault('popupNavLinkSeparator', ' &bull; ');
pg.flag.isKonq=true;
} else if ((self.navigator.appName).indexOf("Microsoft")!=-1) {
} else if ( navigator.vendor && navigator.vendor.toLowerCase().indexOf('apple computer')===0) {
pg.flag.isSafari=true;
} else if (navigator.appName.indexOf("Microsoft")!=-1) {
setDefault('popupNavLinkSeparator', ' &#183; '); setDefault('popupNavLinkSeparator', ' &#183; ');
setDefault('popupStructure','original'); setDefault('popupStructure','original');
pg.flag.isIE=true; pg.flag.isIE=true;
}
if (pg.flag.isIE || pg.flag.isKonq || pg.flag.isSafari) {
pg.flag.linksLikeIE=true;
} }
} }
Line 4,431: Line 4,779:
setInterwiki(); setInterwiki();


// regexps // regexps
setRegexps(); setRegexps();
setRedirs(); setRedirs();
Line 4,460: Line 4,808:
if (getValueOf('popupLastEditLink')) str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}'; if (getValueOf('popupLastEditLink')) str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}';
str+='<<imagestatus>>'; str+='<<imagestatus>>';

// user links // user links
// contribs - log - count - email - block // contribs - log - count - email - block
Line 4,467: Line 4,815:
str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}'; str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}';
str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}'; str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}';

// editing links // editing links
// talkpage -> edit|new - history - un|watch - article|edit // talkpage -> edit|new - history - un|watch - article|edit
// other page -> edit - history - un|watch - talk|edit|new // other page -> edit - history - un|watch - talk|edit|new
var editstr='<<edit|shortcut=e>>'; var editstr='<<edit|shortcut=e>>';
Line 4,476: Line 4,824:
var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>'; var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';


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

// misc links // misc links
str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>'; str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';
Line 4,522: Line 4,870:
ret = splitted; ret = splitted;
for (var i=1; i<splitted.length; i=i+numParens+1) { for (var i=1; i<splitted.length; i=i+numParens+1) {

var testString=splitted; var testString=splitted;
var trueString=splitted; var trueString=splitted;
Line 4,559: Line 4,907:
} }


// append non-conditional string // append non-conditional string
ret += splitted; ret += splitted;
} }
if (conditionalSplitRegex.test(ret) && recursionCount < 10) if (conditionalSplitRegex.test(ret) && recursionCount < 10)
return expandConditionalNavlinkString(ret,article,oldid,recursionCount+1); return expandConditionalNavlinkString(ret,article,oldid,recursionCount+1);
else return ret; else return ret;
Line 4,577: Line 4,925:
t.id=ss; t.id=ss;
for (var j=1; j<ss.length; ++j) { for (var j=1; j<ss.length; ++j) {
var sss=ss.split('='); var sss=ss.split('=');
if (sss.length>1) if (sss.length>1)
t]=sss; t]=sss;
else // no assignment (no "="), so treat this as a title (overwriting the last one) else // no assignment (no "="), so treat this as a title (overwriting the last one)
t.text=popupString(sss); t.text=popupString(sss);
} }
t.article=article; t.article=article;
Line 4,590: Line 4,938:
else { // plain HTML else { // plain HTML
ret.push(splitted.split('*').join(getValueOf('popupNavLinkSeparator')) ret.push(splitted.split('*').join(getValueOf('popupNavLinkSeparator'))
.split('<line>').join('<span class="popup_menu_row">').split('</line>').join('</span>')); .split('<line>').join('<span class="popup_menu_row">').split('</line>').join('</span>'));
} }
} }
Line 4,651: Line 4,999:
switch (this.id) { switch (this.id) {
case 'userTalk': case 'newUserTalk': case 'editUserTalk': case 'userTalk': case 'newUserTalk': case 'editUserTalk':
case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog': case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog':
delete this.oldid; delete this.oldid;
this.article=this.article.userName(true); this.article=this.article.userName(true);
} }

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


Line 4,678: Line 5,026:
case 'search': this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break; case 'search': this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break;
case 'history': case 'unwatch': case 'watch': case 'history': case 'unwatch': case 'watch':
case 'unprotect': case 'protect': case 'unprotect': case 'protect':
this.print=wikiLink; this.action=this.id; break; this.print=wikiLink; this.action=this.id; break;


Line 4,697: Line 5,045:
case 'mainlink': case 'mainlink':
if (typeof this.text=='undefined') this.text=safeDecodeURI(this.article); if (typeof this.text=='undefined') this.text=safeDecodeURI(this.article);
if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) { if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) {
var s=this.text.split('/'); this.text=s; var s=this.text.split('/'); this.text=s;
if (this.text=='' && s.length > 1) this.text=s; if (this.text=='' && s.length > 1) this.text=s;
} }
this.print=titledWikiLink; this.print=titledWikiLink;
if (typeof this.title=='undefined' && pg.current.link && typeof pg.current.link.href != 'undefined') { if (typeof this.title=='undefined' && pg.current.link && typeof pg.current.link.href != 'undefined') {
this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article); this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article);
if (typeof this.oldid != 'undefined' && this.oldid) this.title='Revision ' + this.oldid + ' of ' + this.title; if (typeof this.oldid != 'undefined' && this.oldid) {
this.title=tprintf('Revision %s of %s', );
}
} }
this.action='view'; break; this.action='view'; break;
case 'userPage': case 'userPage':
case 'article': case 'article':
case 'monobook': case 'monobook':
case 'editMonobook': case 'editMonobook':
case 'editArticle': case 'editArticle':
//alert(this.id+'\n'+this.article + '\n'+ typeof this.article); //alert(this.id+'\n'+this.article + '\n'+ typeof this.article);
this.article=this.article.articleFromTalkOrArticle(); this.article=this.article.articleFromTalkOrArticle();
Line 4,718: Line 5,068:
this.action='view'; break; this.action='view'; break;
if (this.id.indexOf('edit')==0) { if (this.id.indexOf('edit')==0) {
this.action='edit'; this.action='edit';
} else { this.action='view';} } else { this.action='view';}
break; break;
case 'userTalk': case 'userTalk':
case 'talk': case 'talk':
this.article=this.article.talkPage(); this.article=this.article.talkPage();
this.print=wikiLink; this.print=wikiLink;
this.action='view'; break; this.action='view'; break;
case 'arin': case 'arin':
Line 4,758: Line 5,108:
case 'editUserTalk': case 'editUserTalk':
case 'editTalk': case 'editTalk':
this.article=this.article.talkPage(); this.article=this.article.talkPage();
this.action='edit'; this.print=wikiLink; break; this.action='edit'; this.print=wikiLink; break;
case 'newUserTalk': case 'newUserTalk':
case 'newTalk': case 'newTalk':
this.article=this.article.talkPage(); this.article=this.article.talkPage();
this.action='edit&section=new'; this.print=wikiLink; break; this.action='edit&section=new'; this.print=wikiLink; break;
case 'imagestatus': case 'imagestatus':
Line 4,794: Line 5,144:
var startLink=0; var startLink=0;
var i,j; var i,j;

if (popupHandleKeypress.lastPopupLinkSelected) { if (popupHandleKeypress.lastPopupLinkSelected) {
for (i=0; i<links.length; ++i) { for (i=0; i<links.length; ++i) {
Line 4,809: Line 5,159:
} }
} }

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


Line 4,852: Line 5,202:
// STARTFILE: diffpreview.js // STARTFILE: diffpreview.js
function loadDiff(article, oldid, diff) { function loadDiff(article, oldid, diff) {
pg.diffData={}; // FIXME this is very unreliable; should be done on a per-popup basis
pg.diffdata={};
var oldRev, newRev;
getWiki(article, doneDiffNew, diff);
switch (diff) {
getWiki(article, doneDiffOld, oldid);
case 'cur':
if ( oldid===null || oldid=='' ) {
// eg newmessages diff link
oldRev='0&direction=prev';
newRev=0;
} else {
oldRev = oldid;
newRev = 0;
}
break;
case 'prev':
oldRev = ( oldid || 0 ) + '&direction=prev'; newRev = oldid; break;
case 'next':
oldRev = oldid; newRev = oldid + '&direction=next';
break;
default:
oldRev = oldid || 0; newRev = diff || 0; break;
}
oldRev = oldRev || 0;
newRev = newRev || 0;

getWiki(article, doneDiffNew, newRev);
getWiki(article, doneDiffOld, oldRev);
} }


function doneDiffNew(download) { function doneDiffNew(download) {
if (download.id != pg.idNumber) { return; } if (download.id != pg.idNumber) { return; }
pg.diffdata.New=download; pg.diffData.New=download;
if (pg.diffdata.Old && pg.diffdata.Old.id == pg.idNumber) { insertDiff(); } if (pg.diffData.Old && pg.diffData.Old.id == pg.idNumber) { insertDiff(); }
} }


function doneDiffOld(download) { function doneDiffOld(download) {
if (download.id != pg.idNumber) { return; } if (download.id != pg.idNumber) { return; }
pg.diffdata.Old=download; pg.diffData.Old=download;
if (pg.diffdata.New && pg.diffdata.New.id == pg.idNumber) { insertDiff(); } if (pg.diffData.New && pg.diffData.New.id == pg.idNumber) { insertDiff(); }
} }


Line 4,882: Line 5,255:
if(!a.row || a.row===0) { aa=1; } if(!a.row || a.row===0) { aa=1; }
else if (countCrossings(b,a,i, true)) { else if (countCrossings(b,a,i, true)) {
aa=1; aa=1;
bb.row ] = 1; bb.row ] = 1;
} }
Line 4,898: Line 5,271:
if ( bb == 1 ) { if ( bb == 1 ) {
for (j=max(0,i-context); j < min(b.length, i+context); ++j) { for (j=max(0,i-context); j < min(b.length, i+context); ++j) {
if ( !bb ) { bb = 1; aa.row ] = 0.5; } if ( !bb ) { bb = 1; aa.row ] = 0.5; }
} }
} }
} }

for (i=0; i<a.length; ++i) { for (i=0; i<a.length; ++i) {
if ( aa == 1 ) { if ( aa == 1 ) {
for (j=max(0,i-context); j < min(a.length, i+context); ++j) { for (j=max(0,i-context); j < min(a.length, i+context); ++j) {
if ( !aa ) { aa = 1; bb.row ] = 0.5; } if ( !aa ) { aa = 1; bb.row ] = 0.5; }
} }
} }
Line 4,913: Line 5,286:
for (i=0; i<bb.length; ++i) { for (i=0; i<bb.length; ++i) {
if (bb > 0) { // it's a row we need if (bb > 0) { // it's a row we need
if (b.row || b.row===0) { bbb.push(b.text); } // joined; partner should be in aa if (b.row || b.row===0) { bbb.push(b.text); } // joined; partner should be in aa
else { else {
bbb.push(b); bbb.push(b);
} }
} }
Line 4,922: Line 5,295:
if (aa > 0) { // it's a row we need if (aa > 0) { // it's a row we need
if (a.row || a.row===0) { aaa.push(a.text); } // joined; partner should be in aa if (a.row || a.row===0) { aaa.push(a.text); } // joined; partner should be in aa
else { else {
aaa.push(a); aaa.push(a);
} }
} }
} }

return { a: aaa, b: bbb}; return { a: aaa, b: bbb};
} }
Line 4,936: Line 5,309:
var j=a.length-1; var k=b.length-1; var j=a.length-1; var k=b.length-1;
while ( j>=0 && k>=0 && a==b ) { --j; --k; } while ( j>=0 && k>=0 && a==b ) { --j; --k; }

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


Line 4,944: Line 5,317:
// for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff // for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff
// FIXME: sometimes this gives misleading diffs as distant chunks are squashed together // FIXME: sometimes this gives misleading diffs as distant chunks are squashed together
var oldlines=pg.diffdata.Old.data.split('\n'); var oldlines=pg.diffData.Old.data.split('\n');
var newlines=pg.diffdata.New.data.split('\n'); var newlines=pg.diffData.New.data.split('\n');
getValueOf('popupDiffContextLines'); getValueOf('popupDiffContextLines');
var inner=stripOuterCommonLines(oldlines,newlines,pg.option.popupDiffContextLines); var inner=stripOuterCommonLines(oldlines,newlines,pg.option.popupDiffContextLines);
Line 4,954: Line 5,327:
// truncate // truncate
truncated=true; truncated=true;
inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines), inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines),
newlines.slice(0,pg.option.popupDiffMaxLines), newlines.slice(0,pg.option.popupDiffMaxLines),
pg.option.popupDiffContextLines); pg.option.popupDiffContextLines);
oldlines=inner.a; newlines=inner.b; oldlines=inner.a; newlines=inner.b;
} }

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

var simpleSplit = (String.prototype.parenSplit.toString().indexOf('native code')==-1); var simpleSplit = (String.prototype.parenSplit.toString().indexOf('native code')==-1);
var html='<hr>';
var html='<hr>'+shortenDiffString(diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),
try {
getValueOf('popupDiffContextCharacters')).join('<hr>');
if (getValueOf('popupDiffDates')) {
setPopupHTML(html.split('\n').join('<br>') +
html+='<table class="popup_diff_dates">';
(truncated ? '<hr><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') ,
html += '<tr><td>' + tprintf('New revision') + '</td><td>' + pg.diffData.New.lastModified.toLocaleString() + '</td></tr>';
'popupPreview');
html += '<tr><td>' + tprintf('Old revision') + '</td><td>' + pg.diffData.Old.lastModified.toLocaleString() + '</td></tr>';
html += '</table><hr>';
}
} catch (cockup) {
// nothing here - maybe the download failed or something. anyway, not too fussed.
}
html += shortenDiffString(
diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),
getValueOf('popupDiffContextCharacters') ).join('<hr>');
setPopupHTML(html.split('\n').join('<br>') +
(truncated ? '<hr><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') ,
'popupPreview');
} }


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




window.wikiLink=function(l) { window.wikiLink=function(l) {
//{article:article, action:action, text:text, oldid}) { //{article:article, action:action, text:text, oldid}) {
if (! (typeof l.article == typeof {} if (! (typeof l.article == typeof {}
&& typeof l.action == typeof '' && typeof l.text==typeof '')) return null; && typeof l.action == typeof '' && typeof l.text==typeof '')) return null;
if (typeof l.oldid == 'undefined') l.oldid=null; if (typeof l.oldid == 'undefined') l.oldid=null;
if (l.action!='edit' && l.action!='view' && l.action != 'revert') l.oldid=null; if (l.action!='edit' && l.action!='view' && l.action != 'revert') l.oldid=null;
Line 5,001: Line 5,386:
case 'edit&section=new': hint = popupString('newSectionHint'); break; case 'edit&section=new': hint = popupString('newSectionHint'); break;
case 'revert': case 'revert':
l.action='edit&autoclick=wpSave&autosummary=' + simplePrintf(getValueOf('popupRevertSummary'), ); l.action='edit&autoclick=wpSave&autosummary=' + revertSummary(l.oldid);
if (getValueOf('popupRevertSummaryPrompt')) l.action += '&autosummaryprompt=true'; if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; }
break; break;
case 'nullEdit': case 'nullEdit':
l.action='edit&autoclick=wpSave&autosummary=null'; l.action='edit&autoclick=wpSave&autosummary=null';
break; break;
} }


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

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


function revertSummary(oldid) {
var historyPage=(document.title.split(' - ') === popupString('History'));
if (historyPage) {
var links=document.links;
var numlinks=links.length;
var date=null, editor=null;
for (var i=0; i<numlinks-1; ++i) {
if (RegExp('oldid='+oldid).test(links.href)
&& RegExp('^{2}:{2},.*{3}$').test(links.innerHTML)) {
date=links.innerHTML;
editor=Title.fromURL(links.href).userName();
break;
}
}
if (date && editor) {
return simplePrintf(getValueOf('popupExtendedRevertSummary'), );
}
}
return simplePrintf(getValueOf('popupRevertSummary'), );
}


function titledWikiLink(l) { function titledWikiLink(l) {
Line 5,027: Line 5,436:


// article and action are mandatory args // article and action are mandatory args
//limitAlert(titledWikiLink, 5, 'titledWikiLink: l.article='+l.article+'\n'+(typeof l.article));


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

return null;
//if (typeof l.article == typeof {}) { alert(l.article=l.article.toString()); /* FIXME */ }

if(pg.flag.isIE) {
var v=encodeURI(l.article);
l.article=v;
} }

l.article=l.article.split('&').join('%26');
var base = pg.wiki.titlebase + l.article; var base = pg.wiki.titlebase + l.article.urlString();
var url=base; var url=base;


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

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

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


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

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


Line 5,075: Line 5,480:
var friendlyName=stuff.page.split('_').join(' '); var friendlyName=stuff.page.split('_').join(' ');
if(!info.myLastEdit) { if(!info.myLastEdit) {
alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s', alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s',
)); ));
return; return;
} }
if(info.myLastEdit.index==0) { if(info.myLastEdit.index==0) {
Line 5,098: Line 5,503:
switch(l.id) { switch(l.id) {
case 'lastContrib': case 'lastContrib':
jsUrl=simplePrintf('javascript:getLastContrib(\'%s\', %s)', ); jsUrl=simplePrintf('javascript:getLastContrib(\'%s\', %s)', );
title=popupString('lastContribHint'); title=popupString('lastContribHint');
break; break;
case 'sinceMe': case 'sinceMe':
jsUrl=simplePrintf('javascript:getDiffSinceMyEdit(\'%s\', %s)', ); jsUrl=simplePrintf('javascript:getDiffSinceMyEdit(\'%s\', %s)', );
title=popupString('sinceMeHint'); title=popupString('sinceMeHint');
break; break;
Line 5,108: Line 5,513:


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


Line 5,117: Line 5,522:
var base = pg.wiki.titlebase + pg.ns.special+':'+l.specialpage; var base = pg.wiki.titlebase + pg.ns.special+':'+l.specialpage;
if (typeof l.sep == 'undefined' || l.sep===null) l.sep='&target='; if (typeof l.sep == 'undefined' || l.sep===null) l.sep='&target=';
var article=l.article; var article=l.article.urlString();
if(pg.flag.isIE) {
var v=encodeURI(article);
article=v;
}
var hint=popupString(l.specialpage+'Hint'); var hint=popupString(l.specialpage+'Hint');
switch (l.specialpage) { switch (l.specialpage) {
case 'Log': hint=(l.sep=='&user=') ? popupString('userLogHint') : popupString('blockLogHint'); break; case 'Log': hint=(l.sep=='&user=') ? popupString('userLogHint') : popupString('blockLogHint'); break;
case 'Search': article=safeDecodeURI(article); break; case 'Search': article=l.article.toString(); break;
} }
if (hint) hint = simplePrintf(hint, ); if (hint) hint = simplePrintf(hint, );
Line 5,136: Line 5,537:
function generalLink(l) { function generalLink(l) {
// l.url, l.text, l.title, l.newWin, l.className // l.url, l.text, l.title, l.newWin, l.className

if (typeof l.url=='undefined') return null; if (typeof l.url=='undefined') return null;


Line 5,145: Line 5,546:
if (typeof l.title!='undefined' && l.title) ret += ' title="' + l.title + '"'; if (typeof l.title!='undefined' && l.title) ret += ' title="' + l.title + '"';
var newWin; var newWin;
if (typeof l.newWin=='undefined' || l.newWin===null) newWin=getValueOf('popupNewWindows'); if (typeof l.newWin=='undefined' || l.newWin===null) newWin=getValueOf('popupNewWindows');
else newWin=l.newWin; else newWin=l.newWin;
if (newWin) ret += ' target="_blank"'; if (newWin) ret += ' target="_blank"';
Line 5,165: Line 5,566:


function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor) { function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor) {
if (x.newTarget) { if (x.newTarget) {
log ('changeLinkTargetLink: newTarget=' + x.newTarget); log ('changeLinkTargetLink: newTarget=' + x.newTarget);
} }
Line 5,191: Line 5,592:
currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + ')\\s*'; currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + ')\\s*';
// e.g. Computer (archaic) -> \s*(omputer(?:%2528|\()archaic(?:%2528|\)))\s* // e.g. Computer (archaic) -> \s*(omputer(?:%2528|\()archaic(?:%2528|\)))\s*

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


// get the page to edit from the title // get the page to edit from the title
try { try {
//var title=document.getElementsByTagName('h1').innerHTML.replace(RegExp(' ', 'g'), '_'); //var title=document.getElementsByTagName('h1').innerHTML.replace(RegExp(' ', 'g'), '_');
var title=document.title.split(' - '); var title=document.title.split(' - ');
title=''; title='';
Line 5,202: Line 5,603:
} catch (err) { return; } } catch (err) { return; }


var lk=titledWikiLink({article: title, newWin:x.newWin, var lk=titledWikiLink({article: new Title(title), newWin:x.newWin,
action: 'edit', action: 'edit',
text: x.text, text: x.text,
title: x.hint title: x.hint,
className: 'popup_change_title_link'
});
});
var cmd=''; var cmd='';
if (x.newTarget) { if (x.newTarget) {
Line 5,223: Line 5,625:




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


Line 5,232: Line 5,634:
log('redirLink: newTarget=' + redirMatch); log('redirLink: newTarget=' + redirMatch);
ret += addPopupShortcut( ret += addPopupShortcut(
changeLinkTargetLink({newTarget: redirMatch, text: popupString('Redirects'), hint: popupString('Fix this redirect'), changeLinkTargetLink({newTarget: redirMatch, text: popupString('Redirects'), hint: popupString('Fix this redirect'),
summary: simplePrintf(getValueOf('popupFixRedirsSummary'), summary: simplePrintf(getValueOf('popupFixRedirsSummary'),
), ),
clickButton: getValueOf('popupRedirAutoClick'), minor: true, clickButton: getValueOf('popupRedirAutoClick'), minor: true,
watch: getValueOf('popupWatchRedirredPages')}) watch: getValueOf('popupWatchRedirredPages')})
, 'R'); , 'R');
ret += popupString(' to '); ret += popupString(' to ');
} }
Line 5,243: Line 5,645:
return ret; return ret;
} }

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


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


var uN=safeDecodeURI(l.article.userName()); var uN=safeDecodeURI(l.article.userName());

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


Line 5,275: Line 5,677:
} }


function saneLinkCheck(l) { function saneLinkCheck(l) {
if (typeof l.article != typeof {} || typeof l.text != typeof '') return false; return true; if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; }
return true;
} }
function kateLink(l) { function kateLink(l) {
Line 5,282: Line 5,685:
if (! pg.wiki.wikimedia) return null; if (! pg.wiki.wikimedia) return null;
var uN=safeDecodeURI(l.article.userName()); var uN=safeDecodeURI(l.article.userName());

var url='http://tools.wikimedia.de/~' + getValueOf('popupEditCounterTool') + '/cgi-bin/count_edits?dbname='; var url='http://tools.wikimedia.de/~' + getValueOf('popupEditCounterTool') + '/cgi-bin/count_edits?dbname=';
url += toolDbName() + '&user=' + uN; url += toolDbName() + '&user=' + uN;
Line 5,294: Line 5,697:
if (! pg.wiki.wikimedia) return null; if (! pg.wiki.wikimedia) return null;
var uN=safeDecodeURI(l.article.userName()); var uN=safeDecodeURI(l.article.userName());

var url='http://tools.wikimedia.de/~interiot/cgi-bin/contribution_tree?dbname='; var url='http://tools.wikimedia.de/~interiot/cgi-bin/contribution_tree?dbname=';
url += toolDbName() + '&user='+ uN; url += toolDbName() + '&user='+ uN;
Line 5,308: Line 5,711:


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


Line 5,319: Line 5,722:


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


Line 5,349: Line 5,752:
// screen scrape alert // screen scrape alert
var histInfo={}; var histInfo={};

var d=download.data; var d=download.data;
// pg.misc.data=d; // for debugging // pg.misc.data=d; // for debugging
Line 5,358: Line 5,761:
for (var i=0; i<split.length; ++i) { for (var i=0; i<split.length; ++i) {
var match=RegExp('^.*?type="radio" value="(*)".*?class=\'history-user\'><a href="(/User:|/search/title=(Special:Contributions&amp;target=|User:))(*)').exec(split); var match=RegExp('^.*?type="radio" value="(*)".*?class=\'history-user\'><a href="(/User:|/search/title=(Special:Contributions&amp;target=|User:))(*)').exec(split);
if (match) { if (match) {
edits.push({ oldid: match, editor: match }); edits.push({ oldid: match, editor: match });
} }
Line 5,364: Line 5,767:
histInfo.edits=edits; histInfo.edits=edits;


var userName=getValueOf('popupUserName') || readCookie('enwikiUserName').split('+').join('_'); var userName=getValueOf('popupUserName') || Cookie.read('enwikiUserName').split('+').join('_');
histInfo.userName=userName; histInfo.userName=userName;


for (var i=0; i<edits.length; ++i) { for (var i=0; i<edits.length; ++i) {
if (typeof histInfo.myLastEdit == 'undefined' && userName && edits.editor==userName) if (typeof histInfo.myLastEdit == 'undefined' && userName && edits.editor==userName)
histInfo.myLastEdit={index: i, oldid: edits.oldid, previd: (i==0 ? null : edits.oldid)} ; histInfo.myLastEdit={index: i, oldid: edits.oldid, previd: (i==0 ? null : edits.oldid)} ;
if (typeof histInfo.firstNewEditor == 'undefined' && edits.editor != edits.editor) if (typeof histInfo.firstNewEditor == 'undefined' && edits.editor != edits.editor)
histInfo.firstNewEditor={index:i, oldid:edits.oldid, previd: (i==0 ? null : edits.oldid)}; histInfo.firstNewEditor={index:i, oldid:edits.oldid, previd: (i==0 ? null : edits.oldid)};
} }
Line 5,387: Line 5,790:
if (x!='popupCookies') { if (x!='popupCookies') {
defaultize('popupCookies'); defaultize('popupCookies');
if (pg.option.popupCookies && (val=readCookie(x))) { if (pg.option.popupCookies && (val=Cookie.read(x))) {
pg.option=val; pg.option=val;
return; return;
Line 5,399: Line 5,802:


function newOption(x, def) { function newOption(x, def) {
if (typeof pg.option=='undefined') { if (typeof pg.option=='undefined') {
pg.option=null; pg.option=null;
} }
pg.optionDefault=def; pg.optionDefault=def;
Line 5,408: Line 5,811:


function getValueOf(varName) { function getValueOf(varName) {
defaultize(varName); defaultize(varName);
return pg.option; return pg.option;
} }
Line 5,422: Line 5,825:
newOption('popupAdminLinks', false); newOption('popupAdminLinks', false);
newOption('popupShortcutKeys', false); newOption('popupShortcutKeys', false);
newOption('popupDragging', true); newOption('popupDragging', true);
newOption('popupHistoricalLinks', true); newOption('popupHistoricalLinks', true);
newOption('popupOnlyArticleLinks', true); newOption('popupOnlyArticleLinks', true);
Line 5,447: Line 5,850:
newOption('popupRedirAutoClick', 'wpDiff'); newOption('popupRedirAutoClick', 'wpDiff');
newOption('popupFixDabs', false); newOption('popupFixDabs', false);
newOption('popupRevertSummaryPrompt', false); newOption('popupRevertSummaryPrompt', false);
newOption('popupRedlinkRemoval', false); newOption('popupRedlinkRemoval', false);
newOption('popupWatchDisambiggedPages', null); newOption('popupWatchDisambiggedPages', null);
Line 5,475: Line 5,878:
newOption('popupDiffContextLines', 2); newOption('popupDiffContextLines', 2);
newOption('popupDiffContextCharacters', 40); newOption('popupDiffContextCharacters', 40);
newOption('popupDiffDates', true);


// edit summaries // edit summaries
newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary') ); newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary') );
newOption('popupRevertSummary', popupString('defaultpopupRevertSummary') ); newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary') );
newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary') ); newOption('popupRevertSummary', popupString('defaultpopupRevertSummary') );
newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary') ); newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary') );
newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary') ); newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary') );
newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary') );


// misc // misc
Line 5,490: Line 5,895:
newOption('popupHistoryLimit', 50); newOption('popupHistoryLimit', 50);
newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect, newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect,
popupFilterPageSize, popupFilterCountLinks, popupFilterPageSize, popupFilterCountLinks,
popupFilterCountImages, popupFilterCountCategories, popupFilterCountImages, popupFilterCountCategories,
popupFilterLastModified]); popupFilterLastModified]);
newOption('extraPopupFilters', ); newOption('extraPopupFilters', );
newOption('popupOnEditSelection', true); newOption('popupOnEditSelection', true);

// new windows // new windows
newOption('popupNewWindows', false); newOption('popupNewWindows', false);
Line 5,529: Line 5,934:


pg.string = { pg.string = {
/////////////////////////////////////
// summary data, searching etc.
/////////////////////////////////////
'#': '#', '#': '#',
'actions': 'actions',
'arin': 'ARIN lookup',
'article': 'article', 'article': 'article',
'block': 'block',
'BlockipHint': 'Prevent %s from editing',
'block log': 'block log',
'blockLogHint': 'Show the block log for %s',
'block user': 'block user',
'Bypass redirect': 'Bypass redirect',
'bytes': 'bytes',
'category': 'category', 'category': 'category',
'categories': 'categories', 'categories': 'categories',
'image': 'image',
'Click to disambiguate this link to:': 'Click to disambiguate this link to:',
'contribs': 'contribs', 'images': 'images',
'contribsTree': 'contribsTree', 'stub': 'stub',
'tree': 'tree', 'Empty page': 'Empty page',
'kB': 'kB',
'contribsTreeHint': 'Explore %s\'s contributions by namespace and by article',
'contributions': 'contributions', 'bytes': 'bytes',
'ContributionsHint': 'List the contributions made by %s',
'cookies': 'cookies',
'count': 'count',
'cur': 'cur',
'day': 'day', 'day': 'day',
'days': 'days', 'days': 'days',
'hour': 'hour',
'hours': 'hours',
'minute': 'minute',
'minutes': 'minutes',
'second': 'second',
'seconds': 'seconds',
'week': 'week',
'weeks': 'weeks',
'search': 'search',
'SearchHint': 'Find English Misplaced Pages articles containing %s',
'web': 'web',
'global': 'global',
'globalSearchHint': 'Search across Wikipedias in different languages for %s',
'google': 'google',
'googleSearchHint': 'Google for %s',
/////////////////////////////////////
// article-related actions and info
// (some actions also apply to user pages)
/////////////////////////////////////
'actions': 'actions', ///// view articles and view talk
'space': 'space',
'spacebar': 'space',
'view article': 'view article',
'viewHint': 'Go to %s',
'talk': 'talk',
'talk page': 'talk page',
'this&nbsp;revision': 'this&nbsp;revision',
'revision %s of %s': 'revision %s of %s',
'Revision %s of %s': 'Revision %s of %s',
'Toggle image size': 'Toggle image size',
'del': 'del', ///// delete, protect, move
'delete': 'delete',
'deleteHint': 'Delete %s',
'undeleteShort': 'un',
'UndeleteHint': 'Show the deletion history for %s',
'protect': 'protect',
'protectHint': 'Restrict editing rights to %s',
'unprotectShort': 'un',
'unprotectHint': 'Allow %s to be edited by anyone again',
'move': 'move',
'move page': 'move page',
'MovepageHint': 'Change the title of %s',
'edit': 'edit', ///// edit articles and talk
'edit article': 'edit article',
'editHint': 'Change the content of %s',
'edit talk': 'edit talk',
'new': 'new',
'new topic': 'new topic',
'newSectionHint': 'Start a new section on %s',
'null edit': 'null edit',
'nullEditHint': 'Submit an edit to %s, making no changes ',
'hist': 'hist', ///// history, diffs, editors, related
'history': 'history',
'History': 'History', // what appears in the titles of history pages
'historyHint': 'List the changes made to %s',
'last': 'last',
'lastEdit': 'lastEdit',
'show last edit': 'most recent edit',
'Show the last edit': 'Show the effects of the most recent change',
'lastContrib': 'lastContrib',
'last set of edits': 'latest edits',
'lastContribHint': 'Show the net effect of changes made by the last editor',
'cur': 'cur',
'diffCur': 'diffCur',
'Show changes since revision %s': 'Show changes since revision %s',
'Diff truncated for performance reasons': 'Diff truncated for performance reasons',
'old': 'old',
'oldEdit': 'oldEdit',
'Show the edit made to get revision': 'Show the edit made to get revision',
'sinceMe': 'sinceMe',
'changes since mine': 'diff my edit',
'sinceMeHint': 'Show changes since my last edit',
'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s',
'eds': 'eds',
'editors': 'editors',
'editorListHint': 'List the users who have edited %s',
'related': 'related',
'relatedChanges': 'relatedChanges',
'related changes': 'related changes',
'RecentchangeslinkedHint': 'Show changes in articles related to %s',
'editOld': 'editOld', ///// edit old version, or revert
'rv': 'rv',
'revert': 'revert',
'revertHint': 'Revert to %s',
'defaultpopupRedlinkSummary': 'Removing link to empty page ] using ]', 'defaultpopupRedlinkSummary': 'Removing link to empty page ] using ]',
'defaultpopupFixDabsSummary': 'Disambiguate ] to ] using ]', 'defaultpopupFixDabsSummary': 'Disambiguate ] to ] using ]',
'defaultpopupFixRedirsSummary': 'Redirect bypass from ] to ] using ]', 'defaultpopupFixRedirsSummary': 'Redirect bypass from ] to ] using ]',
'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using ]',
'defaultpopupRevertSummary': 'Revert to revision %s using ]', 'defaultpopupRevertSummary': 'Revert to revision %s using ]',
'defaultpopupRmDabLinkSummary': 'Remove link to dab page ] using ]', 'defaultpopupRmDabLinkSummary': 'Remove link to dab page ] using ]',
'Redirects': 'Redirects', // as in Redirects to ...
'del': 'del',
' to ': ' to ', // as in Redirects to ...
'delete': 'delete',
'deleteHint': 'Delete %s', 'Bypass redirect': 'Bypass redirect',
'diffCur': 'diffCur', 'Fix this redirect': 'Fix this redirect',
'disambig': 'disambig', 'disambig': 'disambig', ///// add or remove dab etc.
'disambigHint': 'Disambiguate this link to ]', 'disambigHint': 'Disambiguate this link to ]',
'Click to disambiguate this link to:': 'Click to disambiguate this link to:',
'Display navigation links at the top of the popup': 'Display navigation links at the top of the popup',
'remove this link': 'remove this link',
'Download preview data': 'Download preview data from the Misplaced Pages servers',
'edit article': 'edit article', 'remove all links to this page from this article': 'remove all links to this page from this article',
'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article',
'edit counter': 'edit counter',
'mainlink': 'mainlink', ///// links, watch, unwatch
'edit': 'edit',
'editHint': 'Change the content of %s', 'wikiLinks': 'wikiLinks',
'editOld': 'editOld', 'links here': 'links here',
'whatLinksHere': 'whatLinksHere',
'editorListHint': 'List the users who have edited %s',
'editors': 'editors', 'what links here': 'what links here',
'WhatlinkshereHint': 'List the pages that are hyperlinked to %s',
'edit talk': 'edit talk',
'unwatchShort': 'un',
'watchThingy': 'watch', // called watchThingy because {}.watch is a function
'watchHint': 'Add %s to my watchlist',
'unwatchHint': 'Remove %s from my watchlist',
/////////////////////////////////////
// user-related actions and info
/////////////////////////////////////
'user': 'user', ///// user page, talk, email, space
'user page': 'user page',
'user talk': 'user talk',
'edit user talk': 'edit user talk', 'edit user talk': 'edit user talk',
'eds': 'eds', 'leave comment': 'leave comment',
'email': 'email', 'email': 'email',
'email user': 'email user', 'email user': 'email user',
'EmailuserHint': 'Send an email to %s', 'EmailuserHint': 'Send an email to %s',
'Empty page': 'Empty page', 'space': 'userspace',
'PrefixindexHint': 'Show pages in the userspace of %s',
'Fix this redirect': 'Fix this redirect',
'count': 'count', ///// contributions, tree, log
'global': 'global',
'edit counter': 'edit counter',
'globalSearchHint': 'Search across Wikipedias in different languages for %s',
'hist': 'hist',
'historyHint': 'List the changes made to %s',
'history': 'history',
'hour': 'hour',
'hours': 'hours',
'IpblocklistHint': 'Unblock %s',
'image': 'image',
'images': 'images',
'katelinkHint': 'Count the countributions made by %s', 'katelinkHint': 'Count the countributions made by %s',
'kB': 'kB', 'contribs': 'contribs',
'lastEdit': 'lastEdit', 'contributions': 'contributions',
'ContributionsHint': 'List the contributions made by %s',
'last': 'last',
'leave comment': 'leave comment', 'tree': 'tree',
'links here': 'links here', 'contribsTree': 'contribsTree',
'contribsTreeHint': 'Explore %s\'s contributions by namespace and by article',
'Load images': 'Load images',
'log': 'log', 'log': 'log',
'user log': 'user log',
'Look up %s in ARIN whois database': 'Look up %s in ARIN whois database',
'mainlink': 'mainlink', 'userLogHint': 'Show %s\'s user log',
'arin': 'ARIN lookup', ///// ARIN lookup, block user or IP
'minute': 'minute',
'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database',
'minutes': 'minutes',
'move': 'move', 'unblockShort': 'un',
'MovepageHint': 'Change the title of %s', 'block': 'block',
'move page': 'move page', 'block user': 'block user',
'IpblocklistHint': 'Unblock %s',
'BlockipHint': 'Prevent %s from editing',
'block log': 'block log',
'blockLogHint': 'Show the block log for %s',
/////////////////////////////////////
// Popups setup
/////////////////////////////////////
'Display navigation links at the top of the popup': 'Display navigation links at the top of the popup',
'Download preview data': 'Download preview data from the Misplaced Pages servers',
'Load images': 'Load images',
'Never download extra stuff for images/previews': 'Never download extra stuff for images/previews', 'Never download extra stuff for images/previews': 'Never download extra stuff for images/previews',
'Only start downloading when told to do so': 'Only start downloading when told to do so',
'new': 'new',
'newSectionHint': 'Start a new section on %s',
'new topic': 'new topic',
'nullEditHint': 'Submit an edit to %s, making no changes ',
'null edit': 'null edit',
'old': 'old',
'oldEdit': 'oldEdit',
'Open full-size image': 'Open full-size image', 'Open full-size image': 'Open full-size image',
'Only start downloading when told to do so': 'Only start downloading when told to do so',
'PrefixindexHint': 'Show pages in the userspace of %s',
'Preview only on click': 'Preview only on click', 'Preview only on click': 'Preview only on click',
'protectHint': 'Restrict editing rights to %s',
'protect': 'protect',
'RecentchangeslinkedHint': 'Show changes in articles related to %s',
'Redirects': 'Redirects', // as in Redirect to ...
'related changes': 'related changes',
'relatedChanges': 'relatedChanges',
'related': 'related',
'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article',
'remove all links to this page from this article': 'remove all links to this page from this article',
'remove this link': 'remove this link',
'revertHint': 'Revert to %s',
'revert': 'revert',
'revision %s of %s': 'revision %s of %s',
'rv': 'rv',
'SearchHint': 'Find English Misplaced Pages articles containing %s',
'search': 'search',
'second': 'second',
'seconds': 'seconds',
'Show changes since revision %s': 'Show changes since revision %s',
'Show/hide options': 'Show/hide options', 'Show/hide options': 'Show/hide options',
'Show image previews': 'Show image previews', 'Show image previews': 'Show image previews',
'show last edit': 'most recent edit',
'last set of edits': 'latest edits',
'Show navigation links': 'Show navigation links', 'Show navigation links': 'Show navigation links',
'Show page summary data': 'Show page summary data', 'Show page summary data': 'Show page summary data',
Line 5,647: Line 6,110:
'Show summary data': 'Show summary data', 'Show summary data': 'Show summary data',
'Show text previews': 'Show text previews', 'Show text previews': 'Show text previews',
'Show the edit made to get revision': 'Show the edit made to get revision',
'Show the last edit': 'Show the effects of the most recent change',
'Simple popups': 'Simple popups', 'Simple popups': 'Simple popups',
'spacebar': 'space',
'space': 'space',
'stub': 'stub',
'talk page': 'talk page',
'talk': 'talk',
'this&nbsp;revision': 'this&nbsp;revision',
'Toggle image size': 'Toggle image size',
'Toggle this option': 'Toggle this option', 'Toggle this option': 'Toggle this option',
'cookies': 'cookies',
' to ': ' to ', // as in Redirects to ...
'unblockShort': 'un',
'UndeleteHint': 'Show the deletion history for %s',
'undeleteShort': 'un',
'unprotectHint': 'Allow %s to be edited by anyone again',
'unprotectShort': 'un',
'unwatchHint': 'Remove %s from my watchlist',
'unwatchShort': 'un',
'Use cookies to store popups options': 'Use cookies to store popups options', 'Use cookies to store popups options': 'Use cookies to store popups options',
'userLogHint': 'Show %s\'s user log', 'zxy': 'zxy'
};
'user log': 'user log',
'user page': 'user page',
'user talk': 'user talk',
'user': 'user',
'view article': 'view article',
'viewHint': 'Go to %s',
'watchHint': 'Add %s to my watchlist',
'watchThingy': 'watch', // called watchThingy because {}.watch is a function
'week': 'week',
'weeks': 'weeks',
'what links here': 'what links here',
'whatLinksHere': 'whatLinksHere',
'WhatlinkshereHint': 'List the pages that are hyperlinked to %s',
'wikiLinks': 'wikiLinks',
'sinceMeHint': 'Show changes since my last edit',
'lastContribHint': 'Show the net effect of changes made by the last editor',
'changes since mine': 'diff my edit',
'Diff truncated for performance reasons': 'Diff truncated for performance reasons',
'googleSearchHint': 'Google for %s',
'web': 'web',
'google': 'google',
'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'
}


function popupString(str) { function popupString(str) {

Revision as of 01:56, 4 March 2006

var popupVersion="Thu Mar 2 19:40:47 EST 2006";
// STARTFILE: main.js
// **********************************************************************
// **                             Warning                              **
// **********************************************************************
// ** if you edit this file, be sure that your editor recognizes it as **
// **   utf8, or the weird and wonderful characters in the namespaces  **
// **   below will be completely broken. You can check with the show   **
// **            changes button before submitting the edit.            **
// **                      test: مدیا מיוחד Мэдыя                      **
// **********************************************************************
////////////////////////////////////////////////////////////////////
// Import stylesheet(s)
//
if (0) {
  document.write('<link rel="stylesheet" type="text/css" href="' +
		 'http://en.wikipedia.org/search/?title=User:Lupin/navpop.css' +
		 '&action=raw&ctype=text/css&dontcountme=s">');
} else {
  document.write('<link rel="stylesheet" type="text/css" href="http://localhost:8080/js/navpop.css">');
}
//////////////////////////////////////////////////
// Globals
//
// Trying to shove as many of these as possible into the pg (popup globals) object
window.pg = {
  re: {},               // regexps
  ns: {},               // namespaces
  string: {},           // translatable strings
  wiki: {},             // local site info
  misc: {},             // YUCK PHOOEY
  option: {},           // options, see newOption etc
  optionDefault: {},    // default option values
  flag: {},             // misc flags
  cache: {},            // page and image cache
  diffData: {},         // support for diff previews
  structures: {},       // navlink structures
  timer: {},            // all sorts of timers (too damn many)
  counter: {},          // .. and all sorts of counters
  current: {},          // state info
 endoflist: null
};
window.pop = {          // wrap various functions in here
  init: {},
  util: {},
 endoflist: null
};
////////////////////////////////////////////////////////////////////
// Run things
////////////////////////////////////////////////////////////////////
addOnloadHook(setupPopups);
/// Local Variables: ///
/// mode:c ///
/// End: ///
// ENDFILE: main.js
// STARTFILE: actions.js
function setupTooltips(container) {
  if (!container) {
    // the main initial call
    if (getValueOf('popupOnEditSelection')) {
      try {
	document.editform.wpTextbox1.onmouseup=function() { doSelectionPopup(); };
      } catch (neverMind) {}
    }
    // article/content is a structure-dependent thing
    if (getValueOf('popupOnlyArticleLinks')) {
      container = document.getElementById('article') || document.getElementById('content') || document;
    } else {
      container = document;
    }
  }
  if (container.ranSetupTooltipsAlready) { return; }
  container.ranSetupTooltipsAlready=true;
  var anchors;
  anchors=container.getElementsByTagName('A');
  setupTooltipsLoop(anchors, 0, 250, 100);
}
function setupTooltipsLoop(anchors,begin,howmany,sleep) {
  var finish=begin+howmany;
  var loopend = min(finish, anchors.length);
  var remTitles = getValueOf('removeTitles');
  var j=loopend - begin;
  log ('setupTooltips: anchors.length=' + anchors.length + ', begin=' + begin +
       ', howmany=' + howmany + ', loopend=' + loopend);
  // try a faster (?) loop construct
  if (j > 0) {
    do {
      var a=anchors;
      if (!a || !a.href) {
	log('got null anchor at index ' + loopend - j);
	continue;
      }
      if ( isPopupLink(a) ) {
	a.onmouseover=mouseOverWikiLink;
	a.onmouseout= mouseOutWikiLink;
	a.onclick= killPopup;
	if (remTitles && typeof a.originalTitle=='undefined') {
	  a.originalTitle=a.title;
	  a.title='';
	}
      }
    } while (--j);
  }
  if (finish < anchors.length) {
    pop.runOnce(function() {setupTooltipsLoop(anchors,finish,howmany,sleep);}, sleep);
  }
  else {
    // now eliminate popups from the TOC
    if (! getValueOf('popupTocLinks')) {
      var toc=document.getElementById('toc');
      if (toc) {
	var tocLinks=toc.getElementsByTagName('A');
	var tocLen = tocLinks.length;
	for (var j=0; j<tocLen; ++j) {
	  log ('killing popup for toclinks');
	  a=tocLinks;
	  a.onmouseover=null; a.onmouseout=null; a.onclick=null;
	  if (a.originalTitle) { a.title=a.originalTitle; }
	}
	// looks like we just killed any onclick stuff that used to be going on in the toc
	// life is hard
      }
    }
    pg.flag.finishedLoading=true;
  }
}
// add CSS class to table
function addPopupStylesheetClasses () {
  var tables=over.getElementsByTagName('table');
  tables.className='popupBorderTable';
  tables.className='popupTable';
  var fonts=over.getElementsByTagName('font');
  fonts.className='popupFont';
  fonts.id='overFontWrapper';
}
function registerHooks(np) {
  var popupMaxWidth=getValueOf('popupMaxWidth');
  if (typeof popupMaxWidth == 'number') {
    var setMaxWidth = function () {
      np.mainDiv.style.maxWidth = popupMaxWidth + 'px';
      np.maxWidth = popupMaxWidth;
      // hack for IE
      // see http://www.svendtofte.com/code/max_width_in_ie/
      // use setExpression as documented here on msdn: http://tinyurl dot com/dqljn
      if (np.mainDiv.style.setExpression) {
	np.mainDiv.style.setExpression('width', 'document.body.clientWidth > ' +
				       popupMaxWidth + ' ? "' +popupMaxWidth + 'px": "auto"');
      }
    };
    np.addHook(setMaxWidth, 'unhide', 'before');
  }
  np.addHook(addPopupShortcuts, 'unhide', 'after');
  np.addHook(rmPopupShortcuts, 'hide', 'before');
  //don't do this here...
  //np.addHook(window.fillEmptySpans, 'create', 'after');
  //registerHook("createPopup", window.addPopupStylesheetClasses, FAFTER);
}
function mouseOverWikiLink() {
  if (!pg.flag.finishedLoading) { return; }
  return mouseOverWikiLink2(this);
}
function mouseOverWikiLink2(a) {
  // FIXME: should not generate the HTML until the delay has elapsed,
  //        and then popup immediately. Can be a CPU hog otherwise.
  //log('mouseOverWikiLink: a='+a+', pg.current.link='+pg.current.link);
  // try not to duplicate effort
  if ( a==pg.current.link && a.navpopup && a.navpopup.isVisible() ) { return; }
  pg.current.link=a;
  if (getValueOf('simplePopups') && pg.option.popupStructure===null) {
    // reset *default value* of popupStructure
    //log ('simplePopups is true and no popupStructure selected. Defaulting to "original"');
    setDefault('popupStructure', 'original');
  }
  var article=(new Title()).fromAnchor(a);
  // set global variable (ugh) to hold article (wikipage)
  pg.current.article = article;
  var diff=null;
  var oldid=oldidFromAnchor(a);
  if (getValueOf('popupPreviewDiffs')) {
    diff=diffFromAnchor(a);
  }
  if (pg.timer.image !== null) {
    clearInterval(pg.timer.image);
    pg.timer.image=null;
    pg.counter.checkImages=0;
  }
  if (!a.navpopup) {
    log ('mouseoverwikilink2: creating new Navpopup');
    a.navpopup = new Navpopup();
    a.navpopup.fuzz=5;
    a.navpopup.delay=getValueOf('popupDelay')*1000;
    // a.navpopup.append($t(a.navpopup.uid));
    // increment global counter now
    a.navpopup.popupIdNumber = ++pg.idNumber;
    registerHooks(a.navpopup);
  }
  if (a.navpopup.pending===null || a.navpopup.pending!==0) {
    // either fresh popups or those with unfinshed business are redone from scratch
    log ('doing popup content from scratch; a.navpopup.pending='+a.navpopup.pending);
    a.navpopup.setInnerHTML(popupHTML(a));
    fillEmptySpans();
    /*    if (bug) {
      setPopupHTML('Cannot generate diff until ' +
		   '<a href="http://bugzilla.wikimedia.org/show_bug.cgi?id=4838">this patch</a> ' +
		   'is applied to Mediawiki. Sorry!<hr>', 'popupError');
    }
    */
  } else {
    log ('using existing popup content - just showing');
  }
  a.navpopup.showSoonIfStable(a.navpopup.delay);
  getValueOf('popupInitialWidth');
  if (typeof pg.timer.checkPopupPosition==typeof 1) { clearInterval(pg.timer.checkPopupPosition); }
  pg.timer.checkPopupPosition=setInterval(checkPopupPosition, 600);
  if (getValueOf('popupLiveOptions')) {
    setPopupHTML(popupLiveOptionsHTML(), 'popupLiveOptions', pg.idNumber,
		 function () { popupToggleShowOptions(true); } );
  }
  if (getValueOf('popupRedlinkRemoval') && a.className=='new') {
    setPopupHTML('<br>'+popupRedlinkHTML(), 'popupRedlink', pg.idNumber);
  }
  if(getValueOf('simplePopups')) { return; }
  // We're creating a closure with all our data in it because... we're lazy
  // is this a bad idea?
  pop.unsimplify = function () {
    log('pop.unsimplify');
    a.navpopup.unsimplified=true;
    var previewImage=true;
    pg.misc.gImage=null;
    //alert(diff+'\n'+oldid);
    if ( diff===null ) {
      if (isImage(article) && ( getValueOf('imagePopupsForImages') || ! anchorContainsImage(a) )) {
	loadImages(article);
      }
      else if (!isImage(article) && previewImage ) {
	pg.counter.redir=0;
	loadPreview(article, oldid, diff, a.navpopup);
      }
    }
    else {
      loadDiff(article, oldid, diff);
    }
    var s=document.getElementById('popupUnsimplify' + pg.idNumber);
    if (s && s.style) {
      s.style.display='none';
    }
  };
  if (getValueOf('popupUnsimplifyLink')) { return; }
  if (!a.navpopup.unsimplified || a.navpopup.pending!==0 ) {
    log ('running pop.unsimplify(), unsimplified='+a.navpopup.unsimplified +
	 ', pending='+a.navpopup.pending);
    a.navpopup.pending=0;
    pop.unsimplify();
  }
}
function loadPreview(article, oldid, diff, navpop) {
  log('loadPreview(' + article + ', ' + oldid + ', ' + navpop + ')');
  if (navpop && navpop.pending===null) { navpop.pending=0; }
  ++navpop.pending;
  getWiki(article, insertPreview, oldid, navpop);
}
function loadPreviewFromRedir(redirMatch, navpop) {
  // redirMatch is a regex match
  //log('loadPreviewFromRedir, pg.counter.redir='+pg.counter.redir);
  var target = new Title().fromWikiText(redirMatch);
  var trailingRubbish=redirMatch;
  pg.counter.redir++;
  var warnRedir = redirLink(target);
  setPopupHTML(warnRedir, 'popupWarnRedir');
  fillEmptySpans({redir: true, redirTarget: target});
  return loadPreview(target, null, null, navpop);
}
function insertPreview(download) {
  //log('insertPreview, pg.counter.redir='+pg.counter.redir);
  if (download.id != pg.idNumber) {
    //log ('insertPreview: download.id='+download.id+' but pg.idNumber='+pg.idNumber+'. Bailing...');
    return;
  }
  var wikiText=download.data;
  var navpop=download.owner;
  if (navpop && navpop.pending) { --navpop.pending; }
  var redirMatch = pg.re.redirect.exec(wikiText);
  var art=pg.current.article;
  if (pg.counter.redir===0 && redirMatch) {
    pg.current.redirSource=pg.current.article;
    loadPreviewFromRedir(redirMatch, navpop);
    return;
  }
  var redirSource=pg.current.redirSource||'';
  if (pg.counter.redir===0) { // not a redir, so we don't have to specify an oldTarget
    makeFixDabs(wikiText);
  } else {
    makeFixDabs(wikiText, redirSource);
  }
  pg.current.redirSource=null;
  pg.counter.redir=0;
  if (getValueOf('popupSummaryData')) {
    var pgInfo=getPageInfo(wikiText, download);
    setPopupTrailer('<br>' + pgInfo);
  }
  var imagePage=getValidImageFromWikiText(wikiText);
  if(imagePage) {
    // loadThisImage expects an "address fragment"
    imagePage = wikiMarkupToAddressFragment(imagePage);
    loadThisImage(imagePage);
  }
  if (getValueOf('popupPreviews')) {
    if (download && typeof download.data == typeof ''){
      if (isInNamespace(pg.current.article, 'Template') && getValueOf('popupPreviewRawTemplates')) {
	// FIXME compare/consolidate with diff escaping code for wikitext
	var h='<hr><tt>' +
	  download.data.split("&").join("&amp;").split("<").join("&lt;").split(">").join("&gt;").split('\\n').join('<br>\\n') +
	  '</tt>';
	setPopupHTML(h, 'popupPreview');
      }
      else {
	// deal with tricksy anchors
	var anch=decodeAnchor(pg.current.article);
	var d=download.data;
	if (anch) {
	  var anchRe=RegExp('=+\\s*' + literalizeRegex(anch).replace(//g, '') + '\\s*=+');
	  var match=d.match(anchRe);
	  if(match && match.length > 0 && match) { d=d.substring(d.indexOf(match)); }
	  else { // try to deal with == foo ] boom == -> #foo_baz_boom
	    var lines=d.split('\n');
	    for (var i=0; i<lines.length; ++i) {
	      lines=lines.replace(RegExp('{2}(]*?)?(.*?)]{2}', 'g'), '$2');
	      if (lines.match(anchRe)) {
		d=d.split('\n').slice(i).join('\n').replace(RegExp('^*'), '');
		break;
	      }
	    }
	  }
	}
	var p=new Previewmaker(d.substring(0,10000));
	p.showPreview();
      }
    }
  }
}
function killPopup() {
  if (getValueOf('popupShortcutKeys')) { rmPopupShortcuts(); }
  pg.current.link.navpopup.banish();
  pg.current.link=null;
  abortAllDownloads();
  stopImagesDownloading();
  if (pg.timer.checkPopupPosition !== null) {
    clearInterval(pg.timer.checkPopupPosition);
    pg.timer.checkPopupPosition=null;
  }
  if (pg.timer.checkImages !== null) { clearInterval(pg.timer.checkImages); pg.timer.checkImages=null; }
  if (pg.timer.image !== null) { clearInterval(pg.timer.image); pg.timer.image=null; }
  return true; // preserve default action (eg from onclick)
}
// ENDFILE: actions.js
// STARTFILE: domdrag.js
/**
   @fileoverview
   The {@link Drag} object, which enables objects to be dragged around.
   <pre>
   *************************************************
   dom-drag.js
   09.25.2001
   www.youngpup.net
   **************************************************
   10.28.2001 - fixed minor bug where events
   sometimes fired off the handle, not the root.
   *************************************************
   Pared down, some hooks added by ]
   Copyright Aaron Boodman.
   Saying stupid things daily since March 2001.
   </pre>
*/
/**
   Creates a new Drag object. This is used to make various DOM elements draggable.
   @constructor
*/
function Drag () {
  /**
     Condition to determine whether or not to drag. This function should take one parameter, an Event.
     To disable this, set it to <code>null</code>.
     @type Function
  */
  this.startCondition = null;
  /**
     Hook to be run when the drag finishes. This is passed the final coordinates of the dragged object (two integers, x and y).
     To disables this, set it to <code>null</code>.
     @type Function
  */
  this.endHook = null;
}
/**
   Gets an event in a cross-browser manner.
   @param {Event} e
   @private
 */
Drag.prototype.fixE = function(e) {
  if (typeof e == 'undefined') { e = window.event; }
  if (typeof e.layerX == 'undefined') { e.layerX = e.offsetX; }
  if (typeof e.layerY == 'undefined') { e.layerY = e.offsetY; }
  return e;
};
/**
   Initialises the Drag instance by telling it which object you want to be draggable, and what you want to drag it by.
   @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged.
   @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted.
*/
Drag.prototype.init = function(o, oRoot) {
  var dragObj      = this;
  this.obj = o;
  o.onmousedown    = function(e) { dragObj.start.apply( dragObj, ); };
  o.dragging       = false;
  o.draggable      = true;
  o.hmode          = true ;
  o.vmode          = true ;
  o.root = oRoot && oRoot !== null ? oRoot : o ;
  if (isNaN(parseInt(o.root.style.left, 10))) { o.root.style.left   = "0px"; }
  if (isNaN(parseInt(o.root.style.top,  10))) { o.root.style.top    = "0px"; }
  o.root.onthisStart  = function(){};
  o.root.onthisEnd    = function(){};
  o.root.onthis       = function(){};
};
/**
   Starts the drag.
   @private
   @param {Event} e
*/
Drag.prototype.start = function(e) {
  var o = this.obj; // = this;
  e = this.fixE(e);
  if (this.startCondition && !this.startCondition(e)) { return; }
  var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom, 10);
  var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right,  10);
  o.root.onthisStart(x, y);
  o.lastMouseX    = e.clientX;
  o.lastMouseY    = e.clientY;
  var dragObj      = this;
  o.onmousemoveDefault    = document.onmousemove;
  o.dragging              = true;
  document.onmousemove    = function(e) { dragObj.drag.apply( dragObj,  ); };
  document.onmouseup      = function(e) { dragObj.end.apply( dragObj,  ); };
  //  document.onclick        = document.onmouseup;
  //  document.onclick        = document.onmouseup;
  return false;
};
/**
   Does the drag.
   @param {Event} e
   @private
*/
Drag.prototype.drag = function(e) {
  e = this.fixE(e);
  var o = this.obj;
  var ey    = e.clientY;
  var ex    = e.clientX;
  var y = parseInt(o.vmode ? o.root.style.top  : o.root.style.bottom, 10);
  var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right,  10 );
  var nx, ny;
  nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
  ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));
  if (o.xMapper)        { nx = o.xMapper(y); }
  else if (o.yMapper)   { ny = o.yMapper(x); }
  this.obj.root.style = nx + "px";
  this.obj.root.style = ny + "px";
  this.obj.lastMouseX    = ex;
  this.obj.lastMouseY    = ey;
  this.obj.root.onthis(nx, ny);
  return false;
};
/**
   Ends the drag.
   @private
*/
Drag.prototype.end = function()  {
  //Document.onmousemove = null;
  document.onmousemove=this.obj.onmousemoveDefault;
  document.onmouseup   = null;
  // document.onclick     = null;
  // document.onmousedown = null;
  this.obj.dragging    = false;
  if (this.endHook) {
    this.endHook( parseInt(this.obj.root.style, 10),
		  parseInt(this.obj.root.style, 10));
  }
};
// ENDFILE: domdrag.js
// STARTFILE: liveoptions.js
//////////////////////////////////////////////////
// live options nonsense
//
function popupToggleVar(varstring) {
  pg.option = ! getValueOf(varstring);
  if (getValueOf('popupCookies')) {
    Cookie.create(pg.option, String(pg.option));
  }
}
function popupToggleShowOptions (dummy) {
  // just update state if dummy is true
  getValueOf('popupLiveOptionsExpanded');
  if (!dummy) { pg.option.popupLiveOptionsExpanded=!pg.option.popupLiveOptionsExpanded; }
  setPopupHTML((pg.option.popupLiveOptionsExpanded) ? '&lt;&lt;' : '&gt;&gt;',
	       'optionPopped');
  var s=document.getElementById('popupOptionsDiv');
  // if (!s) return;
  if (pg.option.popupLiveOptionsExpanded) { s.style.display='inline'; }
  else { s.style.display='none'; }
}
function popupOptionsCheckboxHTML(varstring, label, title) {
  var html='<br>';
  html += '<span title="'+title+'">';
  html += '<input type="checkbox" id="'+varstring+'Checkbox" ';
  html += (window) ? 'checked="checked" ' : '';
  html += 'onClick="javascript:popupToggleVar(' + "'" + varstring + "'" +
    ')">' + label + '</input></span>';
  return html;
}
function popupLiveOptionsHTML() {
  var html = '';
  html += '<br>';
  html += '<span title="' + popupString('Show/hide options') + '" ';
  html += 'style="border: thin dotted black; cursor: pointer" ';
  html += 'onClick="javascript:popupToggleShowOptions()">';
  html += 'Options <span id="optionPopped' + pg.idNumber + '"></span>';
  html += '</span>';
  html += '<div style="display: none" id="popupOptionsDiv">';
  html += popupOptionsCheckboxHTML('simplePopups', popupString('Simple popups'),
	       popupString('Never download extra stuff for images/previews'));
  html += popupOptionsCheckboxHTML('popupUnsimplifyLink', popupString('Preview only on click'),
	       popupString('Only start downloading when told to do so'));
  //html += popupOptionsCheckboxHTML('popupCookies', popupString('cookies'),
  //             popupString('Use cookies to store popups options'));
  html += popupOptionsCheckboxHTML('popupNavLinks', popupString('Show navigation links'),
	       popupString('Display navigation links at the top of the popup'));
  html += popupOptionsCheckboxHTML('popupImages', popupString('Show image previews'),
	       popupString('Load images'));
  html += popupOptionsCheckboxHTML('popupSummaryData', popupString('Show summary data'),
	       popupString('Show page summary data'));
  html += popupOptionsCheckboxHTML('popupPreviews', popupString('Show text previews'),
	       popupString('Show previews'));
  var extraOptions=[
		    'imagePopupsForImages',
		    'popupAdminLinks',
		    'popupAppendRedirNavLinks',
		    'popupCookies',
		    'popupFixDabs',
		    'popupFixRedirs',
		    'popupHistoricalLinks',
		    'popupImagesFromThisWikiOnly',
		    'popupImagesToggleSize',
		    'popupLastEditLink',
		    'popupLiveOptions',
		    'popupLoadImagesSequentially',
		    'popupNeverGetThumbs',
		    'popupOnlyArticleLinks',
		    'popupPreviewKillTemplates',
		    'popupPreviewFirstParOnly',
		    'popupShortcutKeys',
		    'popupSimplifyMainLink',
		    'removeTitles' // no ,
  ];
  for (var i=0; i<extraOptions.length; ++i) {
    html += popupOptionsCheckboxHTML(extraOptions, extraOptions, popupString('Toggle this option'));
  }
  html += '</div>';
  return html;
}
// ENDFILE: liveoptions.js
// STARTFILE: structures.js
pg.structures.original={};
pg.structures.original.popupLayout=function () {
  return ['popupBar', 'popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupData', 'popupOtherLinks',
	  'popupRedir', ,
	  'popupMiscTools', ,
	  'popupPreview', 'popupFixDab'];
};
pg.structures.original.popupRedirSpans=function () {
  return ;
};
pg.structures.original.popupTitle=function (x) {
  log ('defaultstructure.popupTitle');
  if (!getValueOf('popupNavLinks')) { return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.oldid); }
  return '';
};
pg.structures.original.popupTopLinks=function (x) {
  log ('defaultstructure.popupTopLinks');
  if (getValueOf('popupNavLinks')) { return navLinksHTML(x.article, x.hint, x.oldid); }
  return '';
};
pg.structures.original.popupImage=function(x) {
  log ('original.popupImage, x.article='+x.article+', pg.idNumber='+pg.idNumber);
  return imageHTML(x.article);
};
pg.structures.original.popupUnsimplify=function(x) {
  if (getValueOf('popupUnsimplifyLink')) {
    return '<br><span onClick="javascript:pop.unsimplify()" ' +
    'style="cursor:pointer; border: thin dotted black" '  +
    'title="' +popupString('Download preview data') + '">' +
    'Get preview data' + '</span>';
  }
  return '';
};
pg.structures.original.popupRedirTitle=pg.structures.original.popupTitle;
pg.structures.original.popupRedirTopLinks=pg.structures.original.popupTopLinks;
function copyStructure(oldStructure, newStructure) {
  pg.structures={};
  for (var prop in pg.structures) {
    pg.structures=pg.structures;
  }
}
/** -- fancy -- **/
copyStructure('original', 'fancy');
pg.structures.fancy.popupTitle=function (x) {
  return navlinkStringToHTML('<font size=+0><<mainlink>></font>',x.article,x.oldid);
};
pg.structures.fancy.popupTopLinks=function(x) {
  var hist='<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>if(mainspace){|<<editors|shortcut=E|eds>>}';
  var watch='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
  var move='<<move|shortcut=m|move>>';
  return navlinkStringToHTML('if(talk){' +
			     '<<edit|shortcut=e>>|<<new|shortcut=+|+>>*' + hist + '*' +
			     '<<article|shortcut=a>>|<<editArticle|edit>>' + '*' + watch + '*' + move +
			     '}else{<<edit|shortcut=e>>*' + hist +
			     '*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>' +
			     '*' + watch + '*' + move+'}<br>', x.article, x.oldid);
};
pg.structures.fancy.popupOtherLinks=function(x) {
  var admin='<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>';
  var user='<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}';
  user+='if(ipuser){|<<arin>>}else{*<<email|shortcut=E|'+
    popupString('email')+'>>}if(admin){*<<block|shortcut=b>>}';
  var normal='<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>';
  return navlinkStringToHTML('<br>if(user){' + user + '*}if(admin){'+admin+'if(user){<br>}else{*}}' + normal,
			     x.article, x.oldid);
};
pg.structures.fancy.popupRedirTitle=pg.structures.fancy.popupTitle;
pg.structures.fancy.popupRedirTopLinks=pg.structures.fancy.popupTopLinks;
pg.structures.fancy.popupRedirOtherLinks=pg.structures.fancy.popupOtherLinks;
/** -- fancy2 -- **/
// hack for ]
copyStructure('fancy', 'fancy2');
pg.structures.fancy2.popupTopLinks=function(x) { // hack out the <br> at the end and put one at the beginning
  return '<br>'+pg.structures.fancy.popupTopLinks(x).replace(RegExp('<br>$','i'),'');
};
pg.structures.fancy2.popupLayout=function () { // move toplinks to after the title
  return ['popupBar', 'popupError', 'popupImage', 'popupTitle', 'popupData', 'popupTopLinks', 'popupOtherLinks',
	  'popupRedir', ,
	  'popupMiscTools', ,
	  'popupPreview', 'popupFixDab'];
};
/** -- menus -- **/
copyStructure('original', 'menus');
pg.structures.menus.popupLayout=function () {
  return ['popupBar', 'popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks',
	  'popupRedir', ,
	  'popupData', 'popupMiscTools', ,
	  'popupPreview', 'popupFixDab'];
};
pg.structures.menus.popupBar = function (x) {
  // return <a href="javascript:toggleSticky(' + pg.current.link.navpopup.uid + ')">(un)stick</a>';
  return '';
};
function toggleSticky(uid) {
  var popDiv=document.getElementById('navpopup_maindiv'+uid);
  if (!popDiv) { return; }
  if (!popDiv.navpopup.sticky) { popDiv.navpopup.stick(); }
  else {
	popDiv.navpopup.unstick();
	popDiv.navpopup.hide();
  }
}
pg.structures.menus.popupTopLinks = function (x) {
  var s='';
  var dropdiv='<div class="popup_drop">';
  var menuspan='<span class="popup_menu">';
  var enddiv='</div>';
  var endspan='</span>';
  var hist='if(mainspace){<line>}<<history|shortcut=h>>if(mainspace){|<<editors|shortcut=E>></line>}';
  var lastedit='<<lastEdit|shortcut=/|show last edit>><<lastContrib|last set of edits>><<sinceMe|changes since mine>>';
  var linkshere='<<whatLinksHere|shortcut=l|what links here>>';
  var related='<<relatedChanges|shortcut=r|related changes>>';
  var search='<<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}';
  search += '|<<google|shortcut=G|web>>';
  var watch='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
  var protect='<<unprotect|unprotectShort>>|<<protect|shortcut=p>>';
  var del='<<undelete|undeleteShort>>|<<delete|shortcut=d>>';
  var move='<<move|shortcut=m|move page>>';
  if (getValueOf('popupActionsMenu')) {
    s += '<<mainlink>>*' + dropdiv + '<a href="#">'+popupString('actions') + '</a>';
  } else { s+= dropdiv + '<<mainlink>>'; }
  s+= menuspan +
  'if(oldid){<line><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></line>' +
  '<<revert|shortcut=v>>' +
	'}else{<<edit|shortcut=e>>}' +
	'if(talk){<<new|shortcut=+|new topic>>}' +
  hist + lastedit + move + linkshere + related +
	'<<nullEdit|shortcut=n|null edit>>' +
	'<line>' + search + '</line>' +
	'<hr>' +
	'<line>' + watch + '</line>' +
	'if(admin){<line>' + protect + '</line><line>' + del + '</line>}' +
	'if(talk){' +
	'<hr>' +
	'<<article|shortcut=a|view article>>' +
	'<<editArticle|edit article>>' +
	'}else{' +
	'<hr>' +
	'<<talk|shortcut=t|talk page>>' +
	'<<editTalk|edit talk>>' +
	'<<newTalk|shortcut=+|new topic>>' +
	'}' +
	endspan + enddiv;
  s+='if(user){*' + dropdiv + '<a href="#">'+popupString('user')+'</a>' + menuspan +
	'<line><<userPage|shortcut=u|user page>>|<<userSpace|space>></line>' +
	'<<userTalk|shortcut=t|user talk>>' +
	'<<editUserTalk|edit user talk>>' +
	'<<newUserTalk|shortcut=+|leave comment>>' +
	'if(ipuser){<<arin>>}else{<<email|shortcut=E|email user>>}' +
	'<hr>' +
	'if(wikimedia){<line>}' +
	'<<contribs|shortcut=c|contributions>>' +
	'if(wikimedia){|<<contribsTree|tree>></line>}' +
	'<<userlog|shortcut=L|user log>>' +
	'if(wikimedia){<<count|shortcut=#|edit counter>>}' +
	'if(admin){<line><<unblock|unblockShort>>|<<block|shortcut=b|block user>></line>}' +
	'<<blocklog|shortcut=B|block log>>' +
  getValueOf('popupExtraUserMenu') +
  endspan  + enddiv + '}';
  return navlinkStringToHTML(s, x.article, x.oldid);
};
pg.structures.menus.popupRedirTitle=pg.structures.menus.popupTitle;
pg.structures.menus.popupRedirTopLinks=pg.structures.menus.popupTopLinks;
// ENDFILE: structures.js
// STARTFILE: autoedit.js
function getParamValue(paramName) {
  var cmdRe=RegExp(''+paramName+'=(*)');
  var h=document.location;
  var m=cmdRe.exec(h);
  if (m) {
    try {
      return decodeURI(m);
    } catch (someError) {}
  }
  return null;
}
function substitute(data,cmdBody) {
  // alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags);
  var fromRe=RegExp(cmdBody.from, cmdBody.flags);
  return data.replace(fromRe, cmdBody.to);
}
function execCmds(data, cmdList) {
  for (var i=0; i<cmdList.length; ++i) {
    data=cmdList.action(data, cmdList);
  }
  return data;
}
function parseCmd(str) {
  // returns a list of commands
  if (!str.length) { return ; }
  var p=false;
  switch (str.charAt(0)) {
  case 's':
    p=parseSubstitute(str);
    break;
  case 'j':
    p=parseJavascript(str);
    break;
  default:
    return false;
  }
  if (p) { return .concat(parseCmd(p.remainder)); }
  return false;
}
function unEscape(str, sep) {
  return str.split('\\\\').join('\\').split('\\'+sep).join(sep).split('\\n').join('\n');
}
function runJavascript(data, argWrapper) {
  // flags aren't used (yet)
  // from the user's viewpoint,
  // data is a special variable may appear inside code
  // and gets assigned the text in the edit box
  // alert('eval-ing '+argWrapper.code);
  // commenting this out - it's something of a security nightmare and it's not actually used
  //return eval(argWrapper.code);
}
function parseJavascript(str) {
  // takes a string like j/code/;othercmds and parses it
  var tmp,code,flags;
  if (str.length<3) { return false; }
  var sep=str.charAt(1);
  str=str.substring(2);
  tmp=skipOver(str,sep);
  if (tmp) { code=tmp.segment.split('\n').join('\\n'); str=tmp.remainder; }
  else { return false; }
  flags='';
  if (str.length) {
    tmp=skipOver(str,';') || skipToEnd(str, ';');
    if (tmp) {flags=tmp.segment; str=tmp.remainder; }
  }
  return { action: runJavascript, code: code, flags: flags, remainder: str };
}
function parseSubstitute(str) {
  // takes a string like s/a/b/flags;othercmds and parses it
  var from,to,flags,tmp;
  if (str.length<4) { return false; }
  var sep=str.charAt(1);
  str=str.substring(2);
  tmp=skipOver(str,sep);
  if (tmp) { from=tmp.segment; str=tmp.remainder; }
  else { return false; }
  tmp=skipOver(str,sep);
  if (tmp) { to=tmp.segment; str=tmp.remainder; }
  else { return false; }
  flags='';
  if (str.length) {
    tmp=skipOver(str,';') || skipToEnd(str, ';');
    if (tmp) {flags=tmp.segment; str=tmp.remainder; }
  }
  return {action: substitute, from: from, to: to, flags: flags, remainder: str};
}
function skipOver(str,sep) {
  var endSegment=findNext(str,sep);
  if (endSegment<0) { return false; }
  var segment=unEscape(str.substring(0,endSegment), sep);
  return {segment: segment, remainder: str.substring(endSegment+1)};
}
function skipToEnd(str,sep) {
  return {segment: str, remainder: ''};
}
function findNext(str, ch) {
  for (var i=0; i<str.length; ++i) {
    if (str.charAt(i)=='\\') { i+=2; }
    if (str.charAt(i)==ch) { return i; }
  }
  return -1;
}
function setCheckbox(param, box) {
  var val=getParamValue(param);
  if (val!==null) {
	switch (val) {
    case '1': case 'yes': case 'true':
      box.checked=true;
      break;
    case '0': case 'no':  case 'false':
      box.checked=false;
    }
  }
}
function autoEdit() {
  if (!document.editform) { return false; }
  if (window.autoEdit.alreadyRan) { return false; }
  window.autoEdit.alreadyRan=true;
  var cmdString=getParamValue('autoedit');
  if (cmdString) {
    try {
      var editbox=document.editform.wpTextbox1;
    } catch (dang) { return; }
    var cmdList=parseCmd(cmdString);
    var input=editbox.value;
    var output=execCmds(input, cmdList);
    editbox.value=output;
  }
  var summary=getParamValue('autosummary');
  var summaryprompt=getParamValue('autosummaryprompt');
  if (summaryprompt) {
    var txt='Enter a non-empty edit summary or press cancel to abort';
    if (popupString) { txt=popupString(txt); }
    var response=prompt(txt, summary);
    if (response) { summary=response; }
    else { return; }
  }
  if (summary) { document.editform.wpSummary.value=summary; }
  setCheckbox('autominor', document.editform.wpMinoredit);
  setCheckbox('autowatch', document.editform.wpWatchthis);
  var btn=getParamValue('autoclick');
  if (btn) {
    if (document.editform && document.editform) {
      var headings=document.getElementsByTagName('h1');
      if (headings) {
	var div=document.createElement('div');
	var button=document.editform;
	div.innerHTML='<font size=+1><b>The "' + button.value +
	  '" button has been automatically clicked.' +
	  ' Please wait for the next page to load.</b></font>';
	document.title='('+document.title+')';
	headings.parentNode.insertBefore(div, headings);
	button.click();
      }
    } else {
      alert('autoedit.js\n\nautoclick: could not find button "'+ btn +'".');
    }
  }
}
addOnloadHook(autoEdit);
// ENDFILE: autoedit.js
// STARTFILE: downloader.js
/**
   @fileoverview
   {@link Downloader}, a xmlhttprequest wrapper, and helper functions.
*/
/**
   Creates a new Downloader
   @constructor
   @class The Downloader class. Create a new instance of this class to download stuff.
   @param {String} url The url to download. This can be omitted and supplied later.
*/
function Downloader(url) {
  // Source: http://jibbering.com/2002/4/httprequest.html
  /** xmlhttprequest object which we're wrapping */
  this.http = false;
  /*@cc_on @*/
  /*@if (@_jscript_version >= 5)
  // JScript gives us Conditional compilation,
  // we can cope with old IE versions.
  // and security blocked creation of the objects.
  try {
  this.http = new ActiveXObject("Msxml2.XMLHTTP");
  } catch (e) {
  try {
  this.http = new ActiveXObject("Microsoft.XMLHTTP");
  } catch (E) {
  // this.http = false;
  }
  }
  @end @*/
  if (! this.http && typeof XMLHttpRequest!='undefined') { this.http = new XMLHttpRequest(); }
  /** The url to download */
  this.url = url;
  /** A universally unique ID number */
  this.id=null;
  /** Modification date, to be culled from the incoming headers */
  this.lastModified = null;
  /** What to do when the download completes successfully */
  this.callbackFunction = null;
  /** Flag set on <code>abort</code> */
  this.aborted = false;
}
new Downloader();
/** Submits the http request. */
Downloader.prototype.send = function (x) {
    if (!this.http) { return null; }
    return this.http.send(x);
};
/** Aborts the download, setting the <code>aborted</code> field to true.  */
Downloader.prototype.abort = function () {
  if (!this.http) { return null; }
  this.aborted=true;
  return this.http.abort();
};
/** Returns the downloaded data. */
Downloader.prototype.getData = function () {if (!this.http) { return null; } return this.http.responseText;};
/** Prepares the download. */
Downloader.prototype.setTarget = function () {if (!this.http) { return null; } this.http.open("GET", this.url, true);};
/** Gets the state of the download. */
Downloader.prototype.getReadyState=function () {if (!this.http) { return null; } return this.http.readyState;};
pg.misc.downloadsInProgress = { };
/** Starts the download.
    Note that setTarget {@link Downloader#setTarget} must be run first
*/
Downloader.prototype.start=function () {
  if (!this.http) { return; }
  pg.misc.downloadsInProgress = this;
  this.http.send(null);
};
/** Gets the 'Last-Modified' date from the download headers.
    Should be run after the download completes.
    Returns <code>null</code> on failure.
    @return {Date}
*/
Downloader.prototype.getLastModifiedDate=function () {
  if(!this.http) { return null; }
  var lastmod=null;
  try {
    lastmod=this.http.getResponseHeader('Last-Modified');
  } catch (err) {}
  if (lastmod) { return new Date(lastmod); }
  return null;
};
/** Sets the callback function.
    @param {Function} f callback function, called as <code>f(this)</code> on success
 */
Downloader.prototype.setCallback = function (f) {
  if(!this.http) { return; }
  this.http.onreadystatechange = f;
};
//////////////////////////////////////////////////
// helper functions
/** Creates a new {@link Downloader} and prepares it for action.
    @param {String} url The url to download
    @param {integer} id The ID of the {@link Downloader} object
    @param {Function} callback The callback function invoked on success
    @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
*/
function newDownload(url, id, callback) {
  var d=new Downloader(url);
  if (!d.http) { return 'ohdear'; }
  d.id=id;
  d.setTarget();
  var f = function () {
    if (d.getReadyState() == 4) {
      delete pg.misc.downloadsInProgress;
      d.data=d.getData();
      d.lastModified=d.getLastModifiedDate();
      callback(d);
    }
  };
  d.setCallback(f);
  return d;
}
/** Simulates a download from cached data.
    The supplied data is put into a {@link Downloader} as if it had downloaded it.
    @param {String} url The url.
    @param {integer} id The ID.
    @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>,
			       where <code>d</code> is the new {@link Downloader}.
    @param {String} data The (cached) data.
    @param {Date} lastModified The (cached) last modified date.
*/
function fakeDownload(url, id, callback, data, lastModified) {
  var d=newDownload(url,callback);
  d.id=id; d.data=data;
  d.lastModified=lastModified;
  return callback(d);
}
/**
   Starts a download.
    @param {String} url The url to download
    @param {integer} id The ID of the {@link Downloader} object
    @param {Function} callback The callback function invoked on success
    @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
 */
function startDownload(url, id, callback) {
  var d=newDownload(url, id, callback);
  if (typeof d == typeof '' ) { return d; }
  d.start();
  return d;
}
/**
   Aborts all downloads which have been started.
 */
function abortAllDownloads() {
  for ( var x in pg.misc.downloadsInProgress ) {
    try {
      pg.misc.downloadsInProgress.aborted=true;
      pg.misc.downloadsInProgress.abort();
      delete pg.misc.downloadsInProgress;
    } catch (e) { }
  }
}
// ENDFILE: downloader.js
// STARTFILE: livepreview.js
// Last update: 21:51, 15 Feb 2005 (UTC)
var lp={};
function setupLivePreview() {
  // User options
  window.lp.userName=window.lp.userName||'Wikipedian';
  window.lp.userSignature=window.lp.userSignature||window.lp.userName;
  window.lp.showImages=false; //window.lp.showImages||true;
  // System options
  window.lp.languageCode=window.lp.languageCode||'en';
  window.lp.interwikiCodes=pg.wiki.interwiki;
  window.lp.baseArticlePath=pg.wiki.articlePath;
  window.lp.userNamespace=pg.ns.user;
  window.lp.imageNamespace=pg.ns.image;
  window.lp.categoryNamespace=pg.ns.category;
  window.lp.imageBasePath=getImageUrlStart(pg.wiki.hostname);
  window.lp.mathBasePath=window.lp.mathBasePath||'/math/';
  window.lp.imageFallbackPath=window.lp.imageFallbackPath||'http://upload.wikimedia.org/wikipedia/commons/';
  window.lp.defaultThumbWidth=getValueOf('popupImageSize');
  window.lp.skinMagnifyClip=window.lp.skinMagnifyClip||'/skins/common/images/magnify-clip.png';
}
function wiki2html(str){
  str=strip_cr(str);
  var w=new WikiCode();
  w.lines=str.split(/\n/);
  w.parse();
  return w.html;
}
function WikiCode() {
  this.lines=;
  this.html='';
  window.lp.signature=']';
  window.lp.blockImage=new RegExp('^\\[\\['+window.lp.imageNamespace+':.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)','i');
}
WikiCode.prototype._parse_table_data=function(){
  var td_match,td_line;
  td_match=this.lines.shift().match(RegExp(/^(\|\+|\||!)((?:(*?)\|(?!\|))?(.*))$/));
  if (td_match=='|+'){ this.html+='<caption'; }
  else { this.html+='<t'+((td_match=='|')?'d':'h'); }
  if (typeof td_match!='undefined') { this.html+=' '+td_match;td_line=td_match.split('||'); }
  else { td_line=td_match.split('||'); }
  this.html+='>';
  while(td_line.length>1){ this.lines.unshift(td_match+td_line.pop()); }
  this.html+=_parse_inline_nowiki(td_line);
  var td=new WikiCode();
  var table_count=0;
  while(this.lines.length){
    if(this.lines.charAt(0)=='|') {
      if(table_count===0) {break;}
      else if(this.lines.charAt(1)=='}'){--table_count;}
    }
    else if(this.lines.charAt(0)=='!'&&table_count===0) {break;}
    else if(this.lines.substr(0,2)=='{|') {table_count++;}
    td.lines.push(this.lines.shift());
  }
  if(td.lines.length) {td.parse();}
  this.html+=td.html;
};
WikiCode.prototype._parse_pre=function(){
  this.html+='<pre>';
  do{
    this._endline(_parse_inline_nowiki(this.lines.substring(1,this.lines.length))+"\n");
  } while(this.lines.length&&this.lines.charAt(0)==' ');
  this.html+='</pre>';
};
WikiCode.prototype._parse_block_image=function(){
  this.html+=_parse_image(this.lines.shift());
};
WikiCode.prototype._endline=function(str){
  this.html+=str;
  this.lines.shift();
};
WikiCode.prototype.parse=function() {
  var p=false;
  do{
    var h_match=this.lines.match(/^(={1,6})(.*)\1(.*)$/);
    if(h_match){
      p=false;
      this._endline('<h'+h_match.length+'>' + _parse_inline_nowiki(h_match)+
		    '</h'+h_match.length+'>' + h_match);
    }else if(this.lines.match(/^/)){
      p=false;
      this._parse_list();
    }else if(this.lines.charAt(0)==' '){
      p=false;
      this._parse_pre();
    }else if(this.lines.substr(0,2)=='{|'){
      p=false;
      this._parse_table();
    }else if(this.lines.match(/^----+$/)){
      p=false;
      this._endline('<hr/>');
    }else if(this.lines.match(window.lp.blockImage)){
      p=false;
      this._parse_block_image();
    }else{
      if(this.lines===''){
	p=(this.lines.length>1&&this.lines==='');
	if(p){this._endline('<p><br />');}
      } else {
	if(!p){this.html+='<p>'; p=true;}
	this.html+=_parse_inline_nowiki(this.lines)+' ';
      }
      this.lines.shift();
    }
  }while(this.lines.length);
};
WikiCode.prototype._parse_list=function(){
  var prev='';
  var l_match,imatch,dt_match;
  while(this.lines.length&&(l_match=this.lines.match(/^(+)(.*)$/))){
    this.lines.shift();
    imatch=str_imatch(prev,l_match);
    for(var i=prev.length-1;i>=imatch;i--){
      if(prev.charAt(i)=='*'){this.html+='</ul>';}
      else if(prev.charAt(i)=='#'){this.html+='</ol>';}
      else{
	this.html+='</d'+((prev.charAt(i)==';')?'t':'d')+'>';
	switch(l_match.charAt(i)){
	  case'': case'*': case'#':
	    this.html+='</dl>';
	}
      }
    }
    for (i=imatch;i<l_match.length;i++){
      if(l_match.charAt(i)=='*'){this.html+='<ul>';}
      else if(l_match.charAt(i)=='#'){this.html+='<ol>';}
      else{
	switch(prev.charAt(i)){
	  case'':case'*':case'#':
	    this.html+='<dl>';
	}
	this.html+='<d'+((l_match.charAt(i)==';')?'t':'d')+'>';
      }
    }
    switch(l_match.charAt(l_match.length-1)) {
      case'*': case'#': this.html+='<li>'+_parse_inline_nowiki(l_match); break;
      case';':
	dt_match=l_match.match(/(.*?) (:.*?)$/);
      if(dt_match) {
	this.html+=_parse_inline_nowiki(dt_match);
	this.lines.unshift(dt_match);}
      break;
      case':':
	this.html+=_parse_inline_nowiki(l_match);
    }
    prev=l_match;
  }
  for(i=prev.length-1;i>=0;i--){
    if(prev.charAt(i)=='*'){this.html+='</ul>';}
    else if(prev.charAt(i)=='#'){this.html+='</ol>';}
    else{this.html+='</d'+((prev.charAt(i)==';')?'t':'d')+'></dl>';}
  }
};
WikiCode.prototype._parse_table=function(){var table_match=this.lines.match(/^\{\|( .*)$/);if(table_match){this._endline('<table'+table_match+'>');}else{this._endline('<table>');}do{if(this.lines.charAt(0)=='|'){switch(this.lines.charAt(1)){case'}':this._endline('</table>');return;case'-':this._endline('<tr '+this.lines.match(/\|-*(.*)/)+'>');break;default:this._parse_table_data();}}else if(this.lines.charAt(0)=='!'){this._parse_table_data();}else{this.lines.shift();}}while(this.lines.length);};
function _parse_image(str){var attr=str.substring(window.lp.imageNamespace.length+3,str.length-2).split(/\s*\|\s*/);var filename=attr;var caption=attr;var width,w_match;var thumb=false;var frame=false;var center=false;var align='';var html='';do{w_match=attr.match(/^(\d*)px$/);if(w_match){width=w_match;}else switch(attr){case'thumb':case'thumbnail':thumb=true;case'frame':frame=true;break;case'none':case'right':case'left':center=false;align=attr;break;case'center':center=true;align='none';}attr.shift();}while(attr.length);if(frame){if(align===''){align='right';}html+="<div class='thumb t"+align+"'>";if(thumb){if(!width){width=window.lp.defaultThumbWidth;}html+="<div style='width:"+(2+parseInt(width,10))+"px;'>";html+=_make_image(filename,caption,width);html+="<div class='thumbcaption'><div class='magnify' style='float:right'><a href='"+window.lp.baseArticlePath+window.lp.imageNamespace+':'+filename+"' class='internal' title='Enlarge'><img src='"+window.lp.skinMagnifyClip+"' /></a></div>"+_parse_inline_nowiki(caption)+"</div>";}else{html+='<div>';html+=_make_image(filename,caption);html+="<div class='thumbcaption'>"+_parse_inline_nowiki(caption)+"</div>";}html+='</div></div>';}else if(align){html+="<div class='float"+align+"'><span>"+_make_image(filename,caption,width)+"</span></div>";}else{return _make_image(filename,caption,width);}if(center){return"<div class='center'>"+html+'</div>';}else{return html;}}
function _parse_inline_nowiki(str){var start,lastend=0;var substart=0,nestlev=0,open,close,subloop;var html='';while(-1!=(start=str.indexOf('<'+'nowiki>',substart))){html+=_parse_inline_wiki(str.substring(lastend,start));start+=8;substart=start;subloop=true;do{open=str.indexOf('<'+'nowiki>',substart);close=str.indexOf('</nowiki>',substart);if(close<=open||open==-1){if(close==-1){return html+html_entities(str.substr(start));}substart=close+9;if(nestlev){nestlev--;}else{lastend=substart;html+=html_entities(str.substring(start,lastend-9));subloop=false;}}else{substart=open+8;nestlev++;}}while(subloop);}return html+_parse_inline_wiki(str.substr(lastend));};
function _make_image(filename,caption,width){filename=filename.charAt(0).toUpperCase()+filename.substr(1);filename=filename.replace(/ /g,'_');var md5=hex_md5(filename);var source=md5.charAt(0)+'/'+md5.substr(0,2)+'/'+filename;var img;if(window.lp.showImages){if(width){width="width='"+width+"px'";}img="<img onerror='this.onerror=null;this.src=\""+window.lp.imageFallbackPath+source+"\";' src='"+window.lp.imageBasePath+source+"' alt='"+caption+"' "+width+"/>";}else{img=window.lp.imageNamespace+':'+filename+" <em style='color:red;'>(images disabled)</em>";}caption=_strip_inline_wiki(caption);return"<a class='image' title='"+caption+"' href='"+window.lp.baseArticlePath+window.lp.imageNamespace+':'+filename+"'>"+img+"</a>";}
function _parse_inline_images(str){var start,substart=0,nestlev=0;var loop,close,open,wiki,html;while(-1!=(start=str.indexOf(']',substart);open=str.indexOf('[[',substart);if(close<=open||open==-1){if(close==-1){return str;}substart=close;if(nestlev){nestlev--;}else{wiki=str.substring(start,close+2);html=_parse_image(wiki);str=str.replace(wiki,html);substart=start+html.length;loop=false;}}else{substart=open;nestlev++;}}while(loop);}else{break;}}return str;}
function _parse_inline_wiki(str){
  var aux_match,math_md5;
  str=_parse_inline_images(str);
  while (aux_match=str.match(/<(?:)math>(.*?)<\/math>/i)) {
    math_md5=hex_md5(aux_match);
    str=str.replace(aux_match,"<img src='"+window.lp.mathBasePath+math_md5+'.png'+"' />");
  }
  //  log('livepreview: str='+str);
  return str.replace(/'''''(.*?)''(.*?)'''/g,'<strong><em>$1</em>$2</strong>')
    .replace(/'''''(.*?)'''(.*?)''/g,'<em><strong>$1</strong>$2</em>')
    .replace(/'''(.*?)''(.*?)'''''/g,'<strong>$1<em>$2</em></strong>')
    .replace(/'''(.*?)'''/g,'<strong>$1</strong>')
    .replace(/''(.*?)''/g,'<em>$1</em>')
    .replace(/~{5}(?!~)/g,Date())
    .replace(/~{4}(?!~)/g,window.lp.signature+' '+Date())
    .replace(/~{3}(?!~)/g,window.lp.signature)
    .replace(RegExp('\\\\]','gi'),
	     "<a href='"+window.lp.baseArticlePath+"$1'>$1</a>")
    .replace(RegExp('\\\\]','gi'),'')
    .replace(/\*?)\]\](\w*)/g,"<a href='"+window.lp.baseArticlePath+"$1'>$1$2</a>")
    .replace(/\]+?)\]\](\w*)/g,"<a href='"+window.lp.baseArticlePath+"$1'>$2$3</a>")
    .replace(/\]*?:)?(.*?)( *\(.*?\))?\|\]\]/g,"<a href='"+window.lp.baseArticlePath+"$1$2$3'>$2</a>")
    .replace(/\]*?)) (.*?)\]/g,"<a class='external' title='$1' href='$1'>$5</a>")
    .replace(/\/g,"<a class='external' title='$1' href='$1'></a>")
    .replace(/\/g,"<a class='external' title='$1' href='$1'>$1</a>")
    .replace(/(^| )((https?|news|ftp|mailto|gopher|irc):(\/*)(*))/g,"$1<a class='external' title='$2' href='$2'>$2</a>")
    .replace('__NOTOC__','')
    .replace('__NOEDITSECTION__','');
} /* comment for enscript */
function _strip_inline_wiki(str){return str.replace(/\]*\|(.*?)\]\]/g,'$1').replace(/\\]/g,'$1').replace(/''(.*?)''/g,'$1');}
function max(a,b){if(a>b){return a;}return b;}
function min(a,b){if(a<b){return a;}return b;}
function str_imatch(str_a,str_b){var lim=min(str_a.length,str_b.length);for(var i=0;i<lim;i++){if(str_a.charAt(i)!=str_b.charAt(i)){return i;}}return i;}
function strip_cr(str){return str.replace(/\n\r/g,"\n").replace(/\r/g,'');}
function html_entities(str){return str.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");}
//</math>
// ENDFILE: livepreview.js
// STARTFILE: pageinfo.js
function popupFilterPageSize(data) {return formatBytes(data.length);}
function popupFilterCountLinks(data) {
  var num=countLinks(data);return String(num) + '&nbsp;' + ((num!=1)?popupString('wikiLinks'):popupString('wikiLink'));
}
function popupFilterCountImages(data) {
  var num=countImages(data);return String(num) + '&nbsp;' + ((num!=1)?popupString('images'):popupString('image'));
}
function popupFilterCountCategories(data) {
  var num=countCategories(data); return String(num) + '&nbsp;' + ((num!=1)?popupString('categories'):popupString('category'));
}
function popupFilterLastModified(data,download) {
  var lastmod=download.lastModified;
  var now=new Date();
  var age=now-lastmod;
  if (lastmod && getValueOf('popupLastModified')) {
    return (formatAge(age) + ' ' + popupString('old')).replace(RegExp(' ','g'), '&nbsp;');
  }
  return '';
}
function formatAge(age) {
  // coerce into a number
  var a=0+age, aa=a;
  var seclen  = 1000;
  var minlen  = 60*seclen;
  var hourlen = 60*minlen;
  var daylen  = 24*hourlen;
  var weeklen = 7*daylen;
  var numweeks = (a-a%weeklen)/weeklen; a = a-numweeks*weeklen; var sweeks = addunit(numweeks, 'week');
  var numdays  = (a-a%daylen)/daylen;   a = a-numdays*daylen;   var sdays  = addunit(numdays, 'day');
  var numhours = (a-a%hourlen)/hourlen; a = a-numhours*hourlen; var shours = addunit(numhours,'hour');
  var nummins  = (a-a%minlen)/minlen;   a = a-nummins*minlen;   var smins  = addunit(nummins, 'minute');
  var numsecs  = (a-a%seclen)/seclen;   a = a-numsecs*seclen;   var ssecs  = addunit(numsecs, 'second');
  //alert( .join(':') );
  if (aa > 4*weeklen) { return sweeks; }
  if (aa > weeklen)   { return sweeks + ' ' + sdays; }
  if (aa > daylen)    { return sdays  + ' ' + shours; }
  if (aa > 6*hourlen) { return shours; }
  if (aa > hourlen)   { return shours + ' ' + smins; }
  if (aa > 10*minlen) { return smins; }
  if (aa > minlen)    { return smins  + ' ' + ssecs; }
  return ssecs;
}
function addunit(num,str) { return '' + num + ' ' + ((num!=1) ? popupString(str+'s') : popupString(str)) ;}
function runPopupFilters(list, data, download) {
  var ret=;
  for (var i=0; i<list.length; ++i) {
    if (list && typeof list == 'function') {
      var s=list(data, download);
      if (s) { ret.push(s); }
    }
  }
  return ret;
}
function getPageInfo(data, download) {
  if (!data || data.length === 0) { return popupString('Empty page'); }
  var popupFilters=getValueOf('popupFilters') || ;
  var extraPopupFilters = getValueOf('extraPopupFilters') || ;
  var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download);
  var pageInfo=pageInfoArray.join(', ');
  if (pageInfo !== '' ) { pageInfo = upcaseFirst(pageInfo); }
  return pageInfo;
}
// this could be improved!
function countLinks(wikiText) { return wikiText.split('[[').length - 1; }
// if N = # matches, n = # brackets, then
// String.parenSplit(regex) intersperses the N+1 split elements
// with Nn other elements. So total length is
// L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1).
function countImages(wikiText) {return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1);}
function countCategories(wikiText) {return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1); }
function popupFilterStubDetect(data)     { return (isStub(data))     ? popupString('stub')     : ''; }
function popupFilterDisambigDetect(data) { return (isDisambig(data)) ? popupString('disambig') : ''; }
function formatBytes(num) {return (num > 949) ? (Math.round(num/100)/10+popupString('kB')) : (num +'&nbsp;' + popupString('bytes')) ;}
// ENDFILE: pageinfo.js
// STARTFILE: titles.js
/**
   @fileoverview Defines the {@link Title} class, and associated crufty functions.
   <code>Title</code> deals with article titles and their various
   forms.  {@link Stringwrapper} is the parent class of
   <code>Title</code>, which exists simply to make things a little
   neater.
*/
/**
   Creates a new Stringwrapper.
   @constructor
   @class the Stringwrapper class. This base class is not really
   useful on its own; it just wraps various common string operations.
*/
function Stringwrapper() {
  /**
     Wrapper for this.toString().indexOf()
     @param {String} x
     @type integer
  */
  this.indexOf=function(x){return this.toString().indexOf(x);};
  /**
     Returns this.value.
     @type String
   */
     this.toString=function(){return this.value;};
  /**
     Wrapper for {@link String#parenSplit} applied to this.toString()
     @param {RegExp} x
     @type Array
  */
  this.parenSplit=function(x){return this.toString().parenSplit(x);};
  /**
     Wrapper for this.toString().substring()
     @param {String} x
     @param {String} y (optional)
     @type String
  */
  this.substring=function(x,y){
	if (typeof y=='undefined') { return this.toString().substring(x); }
	return this.toString().substring(x,y);
  };
  /**
     Wrapper for this.toString().split()
     @param {String} x
     @type Array
  */
  this.split=function(x){return this.toString().split(x);};
  /**
     Wrapper for this.toString().replace()
     @param {String} x
     @param {String} y
     @type String
  */
  this.replace=function(x,y){ return this.toString().replace(x,y); };
}
/**
   Creates a new <code>Title</code>.
   @constructor
   @class The Title class. Holds article titles and converts them into
   various forms. Also deals with anchors, by which we mean the bits
   of the article URL after a # character, representing locations
   within an article.
   @param {String} value The initial value to assign to the
   article. This must be the canonical title (see {@link
   Title#value}. Omit this in the constructor and use another function
   to set the title if this is unavailable.
*/
function Title(val) {
  /**
     The canonical article title. This must be in UTF-8 with no
     entities, escaping or nasties. Also, underscores should be
     replaced with spaces.
     @type String
     @private
  */
  this.value=null;
  this.setUtf(val);
  /**
     The canonical form of the anchor. This should by decoded utf-8 text.
     @type String
  */
  this.anchor='';
}
Title.prototype=new Stringwrapper();
/**
   Returns the canonical representation of the article title with anchor.
   @fixme Decide specs for anchor
   @return String The article title and the anchor.
*/
Title.prototype.toString=function() {
  return this.value + ( this.anchor ? '#' + this.anchor : '' );
};
Title.urlAnchor=function() {
  // FIXME FIXME FIXME
  return this.anchor;
}
Title.fromURL=function(h) {
  return new Title().fromURL(h);
};
Title.prototype.fromURL=function(h) {
  if (typeof h != 'string') {
    this.value=null;
    return this;
  }
  // NOTE : playing with decodeURI, encodeURI, escape, unescape,
  // we seem to be able to replicate the IE borked encoding
  // IE doesn't do this new-fangled utf-8 thing.
  // and it's worse than that.
  // IE seems to treat the query string differently to the rest of the url
  // the query is treated as bona-fide utf8, but the first bit of the url is pissed around with
  // we fix up & for all browsers, just in case.
  var splitted=h.split('?');
  splitted=splitted.split('&').join('%26');
  if (pg.flag.linksLikeIE) {
    splitted=encodeURI(decode_utf8(splitted));
  }
  h=splitted.join('?');
  var contribs=pg.re.contribs.exec(h);
  if (contribs !== null) {
    this.value = this.decodeNasties(pg.ns.user + ':' + contribs);
    return this;
  }
  var email=pg.re.email.exec(h);
  if (email !== null) {
    this.value=this.decodeNasties(pg.ns.user + ':' + email);
    return this;
  }
  // no more special cases to check --
  // hopefully it's not a disguised user-related page
  var m=pg.re.main.exec(h);
  if(m===null) { this.value=null; }
  else { this.value=this.decodeNasties(m); }
  return this;
};
Title.prototype.decodeNasties=function(txt) {
  return this.decodeEscapes(decodeURI(txt));
}
Title.prototype.decodeEscapes=function(txt) {
  var split=txt.parenSplit(/({2})/);
  var len=split.length;
  for (var i=1; i<len; i=i+2) {
    split=unescape(split);
  }
  return split.join('');
};
Title.fromAnchor=function(a) {
  return new Title().fromAnchor(a);
};
Title.prototype.fromAnchor=function(a) {
  if (!a) { this.value=null; return this; }
  return this.fromURL(a.href);
};
Title.prototype.fromWikiText=function(txt) {
  // FIXME!!!
  if (!pg.flag.linksLikeIE) { txt=myDecodeURI(txt); }
  this.value=txt;
  return this;
};
Title.prototype.hintValue=function(){
  if(!this.value) { return ''; }
  return safeDecodeURI(this.value);
};
Title.prototype.toUserName=function(withNs) {
  //limitAlert(Title.prototype.toUserName, 4, 'toUserName'+'\nwithNs='+withNs+'\nthis.value='+this.value);
  if (!this.value) { return null; }
  var i=this.value.indexOf(pg.ns.user);
  var j=this.value.indexOf(':');
  if  (i !== 0 || j == -1) {
    this.value=null;
    return null;
  }
  var k=this.value.indexOf('/');
  if (k==-1) { this.value=this.value.substring(j+1); }
  else { this.value=this.value.substring(j+1,k); }
  if (withNs) { this.value = pg.ns.user + ':' + this.value; }
  //limitAlert(Title.prototype.toUserName, 4,'end of toUserName'+'\nwithNs='+withNs+'\nthis.value='+this.value);
  return this.value;
};
Title.prototype.userName=function(withNs) {
  var t=(new Title(this.value));
  t.toUserName(withNs);
  if (t.value) { return t; }
  return null;
};
Title.prototype.toTalkPage=function() {
  // convert article to a talk page, or if we can't return null
  // or, in other words, return null if this ALREADY IS a talk page
  // and return the corresponding talk page otherwise
  if (this.value===null) { return null; }
  var talkRegex=namespaceListToRegex(pg.ns.talkList);
  if (talkRegex.exec(this.value)) { this.value=null; return null;}
  var nsRegex=namespaceListToRegex(pg.ns.withTalkList);
  var splitted=this.value.parenSplit(nsRegex);
  if (splitted.length<2) {
    this.value= (pg.ns.talkList+':'+this.value).split(' ').join('_');
    return this.value;
  }
  for (var i=0; i< pg.ns.withTalkList.length; ++i) {
    if (splitted==pg.ns.withTalkList) {
      splitted=pg.ns.talkList;
      this.value=splitted.join(':').substring(1).split(' ').join('_');
      return this.value;
    }
  }
  this.value=null;
  return null;
};
Title.prototype.talkPage=function() {
  var t=new Title(this.value);
  t.toTalkPage();
  if (t.value) { return t; }
  return null;
};
Title.prototype.isTalkPage=function() {
  if (this.talkPage()===null) { return true; }
  return false;
};
Title.prototype.toArticleFromTalkPage=function() {
  var talkRegex=namespaceListToRegex(pg.ns.talkList);
  var splitted=this.value.parenSplit(talkRegex);
  if (splitted.length < 2 || splitted.length > 0) { this.value=null; return null; }
  if (splitted==pg.ns.talkList) {
    splitted='';
    this.value=splitted.join(':').substring(2).split(' ').join('_');
    return this.value;
  }
  for (var i=1; i< pg.ns.talkList.length; ++i) {
    if (splitted==pg.ns.talkList
	|| splitted==pg.ns.talkList.split(' ').join('_')) {
      splitted=pg.ns.withTalkList;
      this.value= splitted.join(':').substring(1).split(' ').join('_');
      return this.value;
    }
  }
  this.value=null;
  return this.value;
};
Title.prototype.articleFromTalkPage=function() {
  var t=new Title(this.value);
  t.toArticleFromTalkPage();
  if (t.value) { return t; }
  return null;
};
Title.prototype.articleFromTalkOrArticle=function() {
  var t=new Title(this.value);
  if ( t.toArticleFromTalkPage() ) { return t; }
  return this;
};
Title.prototype.stripNamespace=function(){ // returns a string, not a Title
  // this isn't very sophisticated
  // it just removes everything up to the final :
  // BUG: probably does silly things for images with colons in the name - check it out
  var list = this.value.split(':');
  return list;
};
Title.prototype.setUtf=function(value){
  if (!value) { this.value=''; return; }
  var anch=value.indexOf('#');
  if(anch < 0) { this.value=value; this.anchor=''; return; }
  this.value=value.substring(0,anch);
  this.anchor=value.substring(anch+1);
  this.ns=null; // wait until namespace() is called
};
Title.prototype.namespace=function() {
  // FIXME
};
Title.prototype.setUrl=function(urlfrag) {
  var anch=urlfrag.indexOf('#');
  this.value=safeDecodeURI(urlfrag.substring(0,anch));
  this.anchor=value.substring(anch+1);
};
Title.prototype.append=function(x){
  this.setUtf(this.value + x);
};
Title.prototype.isIpUser=function() {
  return pg.re.ipUser.test(this.userName());
};
Title.prototype.urlString=function() {
  return encodeURI(this.value).split('&').join('%26').split('?').join('%3F').split('+').join('%2B');
}
function paramValue(param, url) {
  var s=url.parenSplit(RegExp('' + literalizeRegex(param) + '=(*)'));
  if (!url) { return null; }
  return s || null;
}
// all sorts of stuff here
// almost everything needs to be rewritten
function oldidFromAnchor(a) { return paramValue('oldid', a.href); }
function diffFromAnchor(a) { return paramValue('diff', a.href); }
function stripNamespace(article) {
  // FIXME used in images.js.... extend Title class to Image class?
  // this isn't very sophisticated
  // it just removes everything up to the final :
  // BUG: probably does silly things for images with colons in the name - check it out
  var list = article.split(':');
  return list;
}
// (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup)
// (b) change spaces to underscores
// (c) encodeURI (just the straight one, no pg.re.urlNoPopup)
function wikiMarkupToAddressFragment (str) { // for images
  var ret = safeDecodeURI(str);
  ret = ret.split(' ').join('_');
  ret = encodeURI(ret);
  return ret;
}
function myDecodeURI (str) {
  var ret;
  try { ret=decodeURI(str.toString()); }
  catch (summat) { return str; }
  for (var i=0; i<pg.misc.decodeExtras.length; ++i) {
    var from=pg.misc.decodeExtras.from;
    var to=pg.misc.decodeExtras.to;
    ret=ret.split(from).join(to);
  }
  return ret;
}
function safeDecodeURI(str) { var ret=myDecodeURI(str); return ret || str; }
function myEncodeURI (str) {
  log ('myEncodeURI: str='+str);
  var ret=str;
  ret=encodeURI(ret);
  for (var i=0; i<pg.misc.decodeExtras.length; ++i) {
    var from=pg.misc.decodeExtras.from;
    var to=pg.misc.decodeExtras.to;
    ret=ret.split(to).join(from);
  }
  return ret;
}
function Titleanchor() {
  // FIXME
  this.fromEncoded=function(str) {
    // FIXME probably incorrect!
    this.encoded=str;
    this.value=unesape(str.replace(RegExp('()', 'g'), '%$1'));
  };
  this.getEncoded=function(str) {
    if (this.encoded) {
      return this.encoded;
    }
    // FIXME
    return 'Not yet implemented';
  };
  this.setUTF=function(str) {
    this.value=str;
    this.encoded=this.getEncoded();
  };
}
Titleanchor.prototype=new Stringwrapper();
function removeAnchor(article) {
  if (!article) { return null; }
  // is there a #? if not, we're done
  var i=article.indexOf('#');
  if (i == -1) { return article; }
  return article.substring(0,i);
}
function getAnchor(article) {
  if(!article) { return null; }
  var i=article.indexOf('#');
  if (i == -1) { return ''; }
  return article.substring(i+1);
}
function decodeAnchor(article) {
  var anch=getAnchor(article);
  if(!anch) { return ''; }
  anch=anch.replace(RegExp('()', 'g'), '%$1');
  return unescape(anch);
}
///////////
// TESTS //
///////////
function isIpUser(user) {return pg.re.ipUser.test(user);}
function isStub(data) { return pg.re.stub.test(data); }
function isDisambig(data) {
  return ! pg.current.article.isTalkPage() && pg.re.disambig.test(data);
}
function isValidImageName(str){ // extend as needed...
  return ( str.indexOf('{') == -1 );
}
function isInNamespaceOrTalk(article, namespace) {
  if (!article) { return false; }
  if (isInNamespace(article, namespace)) { return true; }
  // FIXME: internationalize this
  if  (article.indexOf(namespace+'_talk:') === 0) { return true; }
  return false;
};
function isInNamespace(article, namespace) {
  if (!article) { return false; }
  var list=namespace;
  if (typeof list == typeof '') list=;
  for (var i=0; i<list.length; ++i) {
    if (article.indexOf(list+':')===0) { return true; }
    // should also respect namespaces encoded with wikiMarkupToAddressFragment
    if (article.indexOf(wikiMarkupToAddressFragment(list+':'))===0) { return true; }
  }
  return false;
};
function isInStrippableNamespace(article) {
  return isInNamespace(article, pg.ns.nonArticleList);
};
function isInMainNamespace(article) {
  return !isInStrippableNamespace(article);
}
function isImage(thing)     {return isInNamespaceOrTalk(thing, pg.ns.image);};
function isImagePage(thing) {return isInNamespace      (thing, pg.ns.image);};
function anchorContainsImage(a) {
  // iterate over children of anchor a
  // see if any are images
  if (a===null) { return false; }
  kids=a.childNodes;
  for (var i=0; i<kids.length; ++i) { if (kids.nodeName=='IMG') { return true; } }
  return false;
};
function isPopupLink(a) {
  // NB for performance reasons, TOC links generally return true
  // they should be stripped out later
  // FIXME is this faster inline?
  if (a.onclick) { return false; }
  var h=a.href;
  return (
	  ( (
	     ( h.indexOf(pg.wiki.titlebase) === 0 || h.indexOf(pg.wiki.wikibase) === 0 )
	     && !pg.re.urlNoPopup.test(h))
	    ||
	    (pg.re.contribs.test(h) && h.indexOf('&limit=') == -1 )
	    )
	  );
}
// ENDFILE: titles.js
// STARTFILE: cookies.js
//////////////////////////////////////////////////
// Cookie handling
// from http://www.quirksmode.org/js/cookies.html
var Cookie= {
  create: function(name,value,days)
  {
    var expires;
    if (days)
    {
      var date = new Date();
      date.setTime(date.getTime()+(days*24*60*60*1000));
      expires = "; expires="+date.toGMTString();
    }
    else { expires = ""; }
    document.cookie = name+"="+value+expires+"; path=/";
  },
  read: function(name)
  {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++)
    {
      var c = ca;
      while (c.charAt(0)==' ') { c = c.substring(1,c.length); }
      if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length,c.length); }
    }
    return null;
  },
  erase: function(name)
  {
    Cookie.create(name,"",-1);
  }
};
// ENDFILE: cookies.js
// STARTFILE: getpage.js
//////////////////////////////////////////////////
// Wiki-specific downloading
//
// Schematic for a getWiki call
//
//   getWiki->-getPageWithCaching
//                    |
//       false        |          true
// getPage<-->-onComplete(a fake download)
//   \.
//     (async)->addPageToCache(download)->-onComplete(download)
function getWiki(article, onComplete, oldid, owner) {
  // NB wikipage is a Title object
  log('getWiki, article='+article);
  var wikipage=article.urlString();
  // set ctype=text/css to get around opera bug
  var url = pg.wiki.titlebase + removeAnchor(wikipage) + '&action=raw&ctype=text/css';
  if (oldid || oldid===0 || oldid==='0') { url += '&oldid='+oldid; }
  else { url += '&maxage=0&smaxage=0'; }
  getPageWithCaching(url, onComplete, owner);
};
// check cache to see if page exists
function getPageWithCaching(url, onComplete, owner) {
  log('getPageWithCaching, url='+url);
  var i=findInPageCache(url);
  if (i > -1) return fakeDownload(url, pg.idNumber, onComplete, pg.cache.pages.data, pg.cache.pages.lastModified);
  var d=getPage(url, onComplete, owner);
  if (d && owner && owner.addDownload) {
    owner.addDownload(d);
    d.owner=owner;
  }
};
function getPage(url, onComplete, owner) {
  log('getPage');
  var callback= function (d) { if (!d.aborted) {addPageToCache(d); onComplete(d)} };
  return startDownload(url, pg.idNumber, callback);
};
function findInPageCache(url) {
  for (var i=0; i<pg.cache.pages.length; ++i) if (url==pg.cache.pages.url) return i;
  return -1;
};
function addPageToCache(download) {
  log('addPageToCache '+download.url);
  var page = {url: download.url, data: download.data, lastModified: download.lastModified};
  return pg.cache.pages.push(page);
};
// ENDFILE: getpage.js
// STARTFILE: md5-2.2alpha.js
/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.2-alpha Copyright (C) Paul Johnston 1999 - 2005
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */
/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;   /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_md5(s)    { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
function b64_md5(s)    { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
function hex_hmac_md5(k, d)
  { return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function b64_hmac_md5(k, d)
  { return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
function any_hmac_md5(k, d, e)
  { return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
/*
 * Perform a simple self-test to see if the VM is working
 */
function md5_vm_test()
{
  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}
/*
 * Calculate the MD5 of a raw string
 */
function rstr_md5(s)
{
  return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}
/*
 * Calculate the HMAC-MD5, of a key and some data (raw strings)
 */
function rstr_hmac_md5(key, data)
{
  var bkey = rstr2binl(key);
  if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad = bkey ^ 0x36363636;
    opad = bkey ^ 0x5C5C5C5C;
  }
  var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
  return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}
/*
 * Convert a raw string to a hex string
 */
function rstr2hex(input)
{
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var output = "";
  var x;
  for(var i = 0; i < input.length; i++)
  {
    x = input.charCodeAt(i);
    output += hex_tab.charAt((x >>> 4) & 0x0F)
           +  hex_tab.charAt( x        & 0x0F);
  }
  return output;
}
/*
 * Convert a raw string to a base-64 string
 */
function rstr2b64(input)
{
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var output = "";
  var len = input.length;
  for(var i = 0; i < len; i += 3)
  {
    var triplet = (input.charCodeAt(i) << 16)
                | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
                | (i + 2 < len ? input.charCodeAt(i+2)      : 0);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > input.length * 8) output += b64pad;
      else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
    }
  }
  return output;
}
/*
 * Convert a raw string to an arbitrary string encoding
 */
function rstr2any(input, encoding)
{
  var divisor = encoding.length;
  var remainders = Array();
  var i, q, x, quotient;
  /* Convert to an array of 16-bit big-endian values, forming the dividend */
  var dividend = Array(input.length / 2);
  for(i = 0; i < dividend.length; i++)
  {
    dividend = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
  }
  /*
   * Repeatedly perform a long division. The binary array forms the dividend,
   * the length of the encoding is the divisor. Once computed, the quotient
   * forms the dividend for the next step. We stop when the dividend is zero.
   * All remainders are stored for later use.
   */
  while(dividend.length > 0)
  {
    quotient = Array();
    x = 0;
    for(i = 0; i < dividend.length; i++)
    {
      x = (x << 16) + dividend;
      q = Math.floor(x / divisor);
      x -= q * divisor;
      if(quotient.length > 0 || q > 0)
        quotient = q;
    }
    remainders = x;
    dividend = quotient;
  }
  /* Convert the remainders to the output string */
  var output = "";
  for(i = remainders.length - 1; i >= 0; i--)
    output += encoding.charAt(remainders);
  return output;
}
/*
 * Encode a string as utf-8.
 * For efficiency, this assumes the input is valid utf-16.
 */
function str2rstr_utf8(input)
{
  var output = "";
  var i = -1;
  var x, y;
  while(++i < input.length)
  {
    /* Decode utf-16 surrogate pairs */
    x = input.charCodeAt(i);
    y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
    if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
    {
      x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
      i++;
    }
    /* Encode output as utf-8 */
    if(x <= 0x7F)
      output += String.fromCharCode(x);
    else if(x <= 0x7FF)
      output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
                                    0x80 | ( x         & 0x3F));
    else if(x <= 0xFFFF)
      output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
                                    0x80 | ((x >>> 6 ) & 0x3F),
                                    0x80 | ( x         & 0x3F));
    else if(x <= 0x1FFFFF)
      output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
                                    0x80 | ((x >>> 12) & 0x3F),
                                    0x80 | ((x >>> 6 ) & 0x3F),
                                    0x80 | ( x         & 0x3F));
  }
  return output;
}
/*
 * Encode a string as utf-16
 */
function str2rstr_utf16le(input)
{
  var output = "";
  for(var i = 0; i < input.length; i++)
    output += String.fromCharCode( input.charCodeAt(i)        & 0xFF,
                                  (input.charCodeAt(i) >>> 8) & 0xFF);
  return output;
}
function str2rstr_utf16be(input)
{
  var output = "";
  for(var i = 0; i < input.length; i++)
    output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
                                   input.charCodeAt(i)        & 0xFF);
  return output;
}
/*
 * Convert a raw string to an array of little-endian words
 * Characters >255 have their high-byte silently ignored.
 */
function rstr2binl(input)
{
  var output = Array(input.length >> 2);
  for(var i = 0; i < output.length; i++)
    output = 0;
  for(var i = 0; i < input.length * 8; i += 8)
    output |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
  return output;
}
/*
 * Convert an array of little-endian words to a string
 */
function binl2rstr(input)
{
  var output = "";
  for(var i = 0; i < input.length * 32; i += 8)
    output += String.fromCharCode((input >>> (i % 32)) & 0xFF);
  return output;
}
/*
 * Calculate the MD5 of an array of little-endian words, and a bit length.
 */
function binl_md5(x, len)
{
  /* append padding */
  x |= 0x80 << ((len) % 32);
  x = len;
  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;
  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;
    a = md5_ff(a, b, c, d, x, 7 , -680876936);
    d = md5_ff(d, a, b, c, x, 12, -389564586);
    c = md5_ff(c, d, a, b, x, 17,  606105819);
    b = md5_ff(b, c, d, a, x, 22, -1044525330);
    a = md5_ff(a, b, c, d, x, 7 , -176418897);
    d = md5_ff(d, a, b, c, x, 12,  1200080426);
    c = md5_ff(c, d, a, b, x, 17, -1473231341);
    b = md5_ff(b, c, d, a, x, 22, -45705983);
    a = md5_ff(a, b, c, d, x, 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x, 12, -1958414417);
    c = md5_ff(c, d, a, b, x, 17, -42063);
    b = md5_ff(b, c, d, a, x, 22, -1990404162);
    a = md5_ff(a, b, c, d, x, 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x, 12, -40341101);
    c = md5_ff(c, d, a, b, x, 17, -1502002290);
    b = md5_ff(b, c, d, a, x, 22,  1236535329);
    a = md5_gg(a, b, c, d, x, 5 , -165796510);
    d = md5_gg(d, a, b, c, x, 9 , -1069501632);
    c = md5_gg(c, d, a, b, x, 14,  643717713);
    b = md5_gg(b, c, d, a, x, 20, -373897302);
    a = md5_gg(a, b, c, d, x, 5 , -701558691);
    d = md5_gg(d, a, b, c, x, 9 ,  38016083);
    c = md5_gg(c, d, a, b, x, 14, -660478335);
    b = md5_gg(b, c, d, a, x, 20, -405537848);
    a = md5_gg(a, b, c, d, x, 5 ,  568446438);
    d = md5_gg(d, a, b, c, x, 9 , -1019803690);
    c = md5_gg(c, d, a, b, x, 14, -187363961);
    b = md5_gg(b, c, d, a, x, 20,  1163531501);
    a = md5_gg(a, b, c, d, x, 5 , -1444681467);
    d = md5_gg(d, a, b, c, x, 9 , -51403784);
    c = md5_gg(c, d, a, b, x, 14,  1735328473);
    b = md5_gg(b, c, d, a, x, 20, -1926607734);
    a = md5_hh(a, b, c, d, x, 4 , -378558);
    d = md5_hh(d, a, b, c, x, 11, -2022574463);
    c = md5_hh(c, d, a, b, x, 16,  1839030562);
    b = md5_hh(b, c, d, a, x, 23, -35309556);
    a = md5_hh(a, b, c, d, x, 4 , -1530992060);
    d = md5_hh(d, a, b, c, x, 11,  1272893353);
    c = md5_hh(c, d, a, b, x, 16, -155497632);
    b = md5_hh(b, c, d, a, x, 23, -1094730640);
    a = md5_hh(a, b, c, d, x, 4 ,  681279174);
    d = md5_hh(d, a, b, c, x, 11, -358537222);
    c = md5_hh(c, d, a, b, x, 16, -722521979);
    b = md5_hh(b, c, d, a, x, 23,  76029189);
    a = md5_hh(a, b, c, d, x, 4 , -640364487);
    d = md5_hh(d, a, b, c, x, 11, -421815835);
    c = md5_hh(c, d, a, b, x, 16,  530742520);
    b = md5_hh(b, c, d, a, x, 23, -995338651);
    a = md5_ii(a, b, c, d, x, 6 , -198630844);
    d = md5_ii(d, a, b, c, x, 10,  1126891415);
    c = md5_ii(c, d, a, b, x, 15, -1416354905);
    b = md5_ii(b, c, d, a, x, 21, -57434055);
    a = md5_ii(a, b, c, d, x, 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x, 10, -1894986606);
    c = md5_ii(c, d, a, b, x, 15, -1051523);
    b = md5_ii(b, c, d, a, x, 21, -2054922799);
    a = md5_ii(a, b, c, d, x, 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x, 10, -30611744);
    c = md5_ii(c, d, a, b, x, 15, -1560198380);
    b = md5_ii(b, c, d, a, x, 21,  1309151649);
    a = md5_ii(a, b, c, d, x, 6 , -145523070);
    d = md5_ii(d, a, b, c, x, 10, -1120210379);
    c = md5_ii(c, d, a, b, x, 15,  718787259);
    b = md5_ii(b, c, d, a, x, 21, -343485551);
    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);
}
/*
 * These functions implement the four basic operations the algorithm uses.
 */
function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}
/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}
/*
 * Bitwise rotate a 32-bit number to the left.
 */
function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}
// ENDFILE: md5-2.2alpha.js
// STARTFILE: parensplit.js
//////////////////////////////////////////////////
// parenSplit
// String.prototype.parenSplit should do what ECMAscript says
// String.prototype.split does, interspersing paren matches between
// the split elements
if (String('abc'.split(/(b)/))!='a,b,c') {
  // broken String.split, e.g. konq, IE
  String.prototype.parenSplit=function (re) {
    re=nonGlobalRegex(re);
    var s=this;
    var m=re.exec(s);
    var ret=;
    while (m && s) {
      // without the following loop, we have
      // 'ab'.parenSplit(/a|(b)/) != 'ab'.split(/a|(b)/)
      for(var i=0; i<m.length; ++i) {
	if (typeof m=='undefined') m='';
      }
      ret.push(s.substring(0,m.index));
      ret = ret.concat(m.slice(1));
      s=s.substring(m.index + m.length);
      m=re.exec(s);
    }
    ret.push(s);
    return ret;
  };
} else {
  String.prototype.parenSplit=function (re) {return this.split(re);};
}
function nonGlobalRegex(re) {
  var s=re.toString();
  flags='';
  for (var j=s.length; s.charAt(j) != '/'; --j) {
    if (s.charAt(j) != 'g') flags += s.charAt(j);
  }
  var t=s.substring(1,j);
  return RegExp(t,flags);
}
// ENDFILE: parensplit.js
// STARTFILE: tools.js
// IE madness with encoding
// ========================
//
// suppose throughout that the page is in utf8, like wikipedia
//
// if a is an anchor DOM element and a.href should consist of
//
// http://host.name.here/foo?bar=baz
//
// then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie))
// but IE gives bar=baz correctly as plain utf8
//
// ---------------------------------
//
// IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here.
//
// ---------------------------------
//
// summat else
// Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm
function encode_utf8(rohtext) {
  // dient der Normalisierung des Zeilenumbruchs
  rohtext = rohtext.replace(/\r\n/g,"\n");
  var utftext = "";
  for(var n=0; n<rohtext.length; n++)
    {
      // ermitteln des Unicodes des  aktuellen Zeichens
      var c=rohtext.charCodeAt(n);
      // alle Zeichen von 0-127 => 1byte
      if (c<128)
	utftext += String.fromCharCode(c);
      // alle Zeichen von 127 bis 2047 => 2byte
      else if((c>127) && (c<2048)) {
	utftext += String.fromCharCode((c>>6)|192);
	utftext += String.fromCharCode((c&63)|128);}
      // alle Zeichen von 2048 bis 66536 => 3byte
      else {
	utftext += String.fromCharCode((c>>12)|224);
	utftext += String.fromCharCode(((c>>6)&63)|128);
	utftext += String.fromCharCode((c&63)|128);}
    }
  return utftext;
}
function decode_utf8(utftext) {
  var plaintext = ""; var i=0; var c=c1=c2=0;
  // while-Schleife, weil einige Zeichen uebersprungen werden
  while(i<utftext.length)
    {
      c = utftext.charCodeAt(i);
      if (c<128) {
	plaintext += String.fromCharCode(c);
	i++;}
      else if((c>191) && (c<224)) {
	c2 = utftext.charCodeAt(i+1);
	plaintext += String.fromCharCode(((c&31)<<6) | (c2&63));
	i+=2;}
      else {
	c2 = utftext.charCodeAt(i+1); c3 = utftext.charCodeAt(i+2);
	plaintext += String.fromCharCode(((c&15)<<12) | ((c2&63)<<6) | (c3&63));
	i+=3;}
    }
  return plaintext;
}
function upcaseFirst(str) {
  if (typeof str != typeof '' || str=='') return '';
  return str.charAt(0).toUpperCase() + str.substring(1);
};
pop.runOnce=function(f, time) {
  var i=pop.runOnce.timers.length;
  var ff = function () { clearInterval(pop.runOnce.timers); f() };
  var timer=setInterval(ff, time);
  pop.runOnce.timers.push(timer);
}
pop.runOnce.timers=;
function literalizeRegex(str){
  return str.replace(RegExp('(])', 'g'), '\\$1');
}
// ENDFILE: tools.js
// STARTFILE: dab.js
//////////////////////////////////////////////////
// Dab-fixing code
//
function listLinks(wikitext, oldTarget) {
  var reg=RegExp('\\*?)(\\||\\]\\])', 'gi');
  var ret=;
  var splitted=wikitext.parenSplit(reg);
  // ^+ should match interwiki links, hopefully (case-insensitive)
  // and ^* should match those and ] style links too
  var omitRegex=RegExp('^*:|^pecial:|^mage|^ategory');
  var friendlyCurrentArticleName=pg.current.article.split('_').join(' ');
  for (var i=1; i<splitted.length; i=i+3) {
    if (typeof splitted == typeof 'string' && splitted.length>0 && !omitRegex.test(splitted)) {
      ret.push(changeLinkTargetLink({newTarget: splitted,
	       text: splitted.split(' ').join('&nbsp;'),
	       hint: tprintf('disambigHint', ]),
	       summary: simplePrintf(getValueOf('popupFixDabsSummary'),  ]),
	       clickButton: 'wpDiff', minor: true, oldTarget: oldTarget, watch: getValueOf('popupWatchDisambiggedPages')
	       }));
    } /* if */
  } /* for loop */
  ret = rmDupesFromSortedList(ret.sort());
  ret.push(changeLinkTargetLink({ newTarget: null,
				  text: popupString('remove this link').split(' ').join('&nbsp;'),
				  hint: popupString("remove all links to this disambig page from this article"),
				  clickButton: "wpDiff", oldTarget: oldTarget,
				  summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), ),
				  watch: getValueOf('popupWatchDisambiggedPages')}));
  return ret;
}
function rmDupesFromSortedList(list) {
  var ret=;
  for (var i=0; i<list.length; ++i) {
    if (ret.length===0 || list!=ret) { ret.push(list); }
  }
  return ret;
}
function makeFixDab(data, oldTarget) {
  var list=listLinks(data, oldTarget);
  if (list.length===0) { return null; }
  var html='<hr>' + popupString('Click to disambiguate this link to:') + '<br>';
  html+=list;
  for (var i=1; i<list.length; ++i) { html += ', '+list; }
  return html;
}
function makeFixDabs(wikiText, oldTarget)
{
  if (getValueOf('popupFixDabs') && isDisambig(wikiText) &&
      location.href.indexOf(pg.ns.special+':') == -1 &&
      pg.current.article.talkPage() ) {
    setPopupHTML(makeFixDab(wikiText, oldTarget), 'popupFixDab', pg.idNumber);
  }
}
// ENDFILE: dab.js
// STARTFILE: htmloutput.js
// this has to use a timer loop as we don't know if the DOM element exists when we want to set the text
function setPopupHTML (str, elementId, popupId, onSuccess) {
  //log('setPopupHTML, str='+str.substring(0,100)+'..., \n elementId='+elementId+', popupId='+popupId);
  if (typeof popupId === 'undefined') popupId = pg.idNumber;
  var popupElement=document.getElementById(elementId+popupId);
  var timer;
  if (typeof pg.timer.popupHTML == 'undefined') timer=null;
  else timer=pg.timer.popupHTML;
  if (popupElement != null) {
    if(timer) clearInterval(timer);
    pg.timer.popupHTML=null;
    popupElement.innerHTML=str;
    if (onSuccess) onSuccess();
    return true;
  } else {
    // call this function again in a little while...
    var loopFunction=function() { setPopupHTML(str,elementId,popupId,onSuccess);}
    pg.misc.popupHTMLLoopFunctions = loopFunction;
    if (!timer) {
      var doThis = 'pg.misc.popupHTMLLoopFunctions()';
      pg.timer.popupHTML = setInterval(doThis, 600);
    }
  }
  return null;
}
function setImageStatus(str, id) {return; } // setPopupHTML(str, 'popupImageStatus', id);};
function setPopupTrailer(str,id) {return setPopupHTML(str, 'popupData', id);};
function fillEmptySpans(args) { return fillEmptySpans2(args); }
function fillEmptySpans2(args) { // if redir is present and true then redirTarget is mandatory
  var redir=true;
  if (typeof args != 'object' || typeof args.redir == 'undefined' || !args.redir) redir=false;
  var a=pg.current.link;
  //log('fillEmptySpans: a='+a+', pg.current.link='+pg.current.link);
  if (!a) { log('*****\nfillEmptySpans: a is no good\n*****'); return; }
  var article, hint, oldid;
  if (redir && typeof args.redirTarget == typeof {}) {
    article=args.redirTarget; hint=null;
  } else {
    article=(new Title()).fromAnchor(a);
    hint=a.originalTitle || article.hintValue();
    oldid=(getValueOf('popupHistoricalLinks')) ? oldidFromAnchor(a) : null;
  }
  //limitAlert(fillEmptySpans2, 4, 'fillEmptySpans2\n' + article+'\n'+(typeof article));
  var x={ article:article,
          hint: hint, oldid: oldid };
  //log('fillEmptySpans: redir='+redir+', article='+article);
  var structure=pg.structures;
  if (typeof structure != 'object') {
    setPopupHTML('popupError', 'Unknown structure (this should never happen): '+ popupStructure);
    return;
  }
  var spans=flatten(pg.misc.layout);
  var numspans = spans.length;
  var redirs=pg.misc.redirSpans;
  //log('fillEmptySpans: spans.length='+spans.length);
  for (var i=0; i<numspans; ++i) {
    var f=findThis(redirs, spans);
    //log('redir='+redir+', f='+f+', spans='+spans);
    if ( (f!=null && !redir) || (f==null && redir) ) {
      //log('skipping this set of the loop');
      continue;
    }
    var structurefn=structure];
    switch (typeof structurefn) {
    case 'function':
      //log('running '+spans+'({article:'+x.article+', hint:'+x.hint+', oldid: '+x.oldid+'})');
      setPopupHTML(structurefn(x), spans);
      break;
    case 'string':
      setPopupHTML(structurefn, spans);
      break;
    default:
      errlog('unknown thing with label '+spans);
      break;
    }
  }
}
// flatten an array
function flatten(list, start) {
  var ret=;
  if (typeof start == 'undefined') start=0;
  for (var i=start; i<list.length; ++i) {
    if (typeof list == typeof ) {
      return ret.concat(flatten(list)).concat(flatten(list, i+1));
    }
    else ret.push(list);
  }
  return ret;
};
// Generate html for whole popup
function popupHTML (a) {
  getValueOf('popupStructure');
  var structure=pg.structures;
  if (typeof structure != 'object') return 'Unknown structure: '+popupStructure +
                                      ( RegExp('^').test(popupStructure) ? '<p>There has been a breaking change - please do not<br>' +
                                        ' quote strings in options like popupStructure twice any longer<br> in your user javascript file.' : '');
  if (typeof structure.popupLayout != 'function') return 'Bad layout';
  pg.misc.layout=structure.popupLayout();
  if (typeof structure.popupRedirSpans == 'function') pg.misc.redirSpans=structure.popupRedirSpans();
  else pg.misc.redirSpans=;
  return makeEmptySpans(pg.misc.layout);
};
function makeEmptySpans (list) {
  var ret='';
  for (var i=0; i<list.length; ++i) {
    if (typeof list == typeof '') {
      ret += emptySpanHTML(list, pg.idNumber);
    } else if (typeof list == typeof  && list.length > 0 ) {
      ret = ret.parenSplit(RegExp('(</*?>$)')).join(makeEmptySpans(list));
    } else if (typeof list == typeof {} && list.nodeType ) {
      ret += emptySpanHTML(list.name, pg.idNumber, list.nodeType);
    }
  }
  return ret;
};
function popupRedlinkHTML() {
  var friendlyCurrentArticleName=pg.current.article.split('_').join(' ');
  return changeLinkTargetLink({ newTarget: null,
        text: popupString('remove this link').split(' ').join('&nbsp;'),
        hint: popupString("remove all links to this page from this article"),
        clickButton: "wpDiff",
                                               //oldTarget: oldTarget,
        summary: simplePrintf(getValueOf('popupRedlinkSummary'), )});
}
function emptySpanHTML(name, id, tag, classname) {
  tag = tag || 'span';
  classname = classname || name;
  return simplePrintf('<%s id="%s" class="%s"></%s>', );
}
// generate html for popup image
// <a id="popupImageLinkn"><img id="popupImagen">
// where n=pg.idNumber
function imageHTML(article) {
  var ret='';
  ret += '<a id="popupImageLink' + pg.idNumber + '">';
  ret += '<img align="right" valign="top" id="popupImg' + pg.idNumber + '" '
    + 'style="display: none;"></img>';
  ret += '</a>';
  return ret;
};
// ENDFILE: htmloutput.js
// STARTFILE: mouseout.js
//////////////////////////////////////////////////
// fuzzy checks
function fuzzyCursorOffMenus(x,y, fuzz, parent) {
  if (!parent) parent=over;
  if (!parent) return null;
  var spans=parent.getElementsByTagName('span');
  for (var i=0; i<spans.length; ++i) {
    if (spans.className=='popup_menu') {
      if (spans.offsetWidth > 0) return false;
    } // else {document.title+='.';}
  }
  return true;
};
function checkPopupPosition () { // stop the popup running off the right of the screen
  pg.current.link.navpopup.limitHorizontalPosition();
};
function findPosX(obj)
{
    var curleft = 0;
    if (obj.offsetParent)
    {
        while (obj.offsetParent)
        {
            curleft += obj.offsetLeft
            obj = obj.offsetParent;
        }
    }
    else if (obj.x)
        curleft += obj.x;
    return curleft;
}
function findPosY(obj)
{
    var curtop = 0;
    if (obj.offsetParent)
    {
        while (obj.offsetParent)
        {
            curtop += obj.offsetTop
            obj = obj.offsetParent;
        }
    }
    else if (obj.y)
        curtop += obj.y;
    return curtop;
}
function mouseOutWikiLink () {
  log ('mouseOutWikiLink');
  var a=this;
  if (a.navpopup==null) return;
  if ( ! a.navpopup.isVisible() ) {
    a.navpopup.banish();
    return;
  }
  Navpopup.tracker.addHook(posCheckerHook(a.navpopup));
}
function posCheckerHook(navpop) {
  return function() {
    //~ if the navpopup isn't visible, or if it is and should stop
    //~ being...
    if (
        !navpop.isVisible() ||
        (!navpop.isWithin(Navpopup.tracker.x, Navpopup.tracker.y)
         && fuzzyCursorOffMenus(Navpopup.tracker.x, Navpopup.tracker.y, navpop.fuzz, navpop.mainDiv))
        ) {
          //~ stop it being visible or appearing from a @tt{showSoon} call
          log('calling banish() from tracker hook function');
          navpop.banish();
          return true; // remove this hook
        }
    // return true; // remove this immediately
  };
}
function runStopPopupTimer(navpop) {
  // at this point, we should have left the link but remain within the popup
  // so we call this function again until we leave the popup.
  if (!navpop.stopPopupTimer) {
    navpop.stopPopupTimer=setInterval(posCheckerHook(navpop), 500);
    navpop.addHook(function(){clearInterval(navpop.stopPopupTimer);}, 'hide', 'before');
  }
}
// ENDFILE: mouseout.js
// STARTFILE: previewmaker.js
/**
   @fileoverview
   Defines the {@link Previewmaker} object, which generates short previews from wiki markup.
*/
/**
   Creates a new Previewmaker
   @constructor
   @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext.
   @param {String} wikiText The Wikitext source of the page we wish to preview.
*/
function Previewmaker(wikiText) {
  /** The wikitext which is manipulated to generate the preview. */
  this.data=wikiText;
}
/** Remove HTML comments
    @private
 */
Previewmaker.prototype.killComments = function () {
  // this also kills trailing spaces and one trailing newline, eg ]
  this.data=this.data.replace(RegExp('<!--(\\n|.)*?--> *\\n?', 'g'), '');
}
/**
    @private
 */
Previewmaker.prototype.killDivs=function () {
  // say goodbye, divs (can be nested, so use * not *?)
  this.data=this.data.replace(RegExp('< *div* *>*?< */ *div *>',
			   'gi'), '');
}
/**
    @private
 */
Previewmaker.prototype.killGalleries=function () {
  this.data=this.data.replace(RegExp('< *gallery* *>*?< */ *gallery *>',
			   'gi'), '');
}
/**
    @private
 */
Previewmaker.prototype.killBoxTemplates=function () {
  // taxobox hack... in fact, there's a saudiprincebox_begin, so let's be more general
  this.data=this.data.replace(RegExp('*box(begin|start)' +
			   '*?*box(end|finish) *\\s', 'gi'), '');
  // infoboxes etc
  // this should cope with templates in infoboxes, but only nested once
  this.data=this.data.replace(RegExp('*(info|element)box' +
				     '\(\|\|*?\)*?\\s*',
				     'gi'), '');
  // also, have float_begin, ... float_end
  this.data=this.data.replace(RegExp('*floatbegin' +
			   '*?*floatend.*?',
			   'gi'), '');
  // from ]: kill frames too
  this.data=this.data.replace(RegExp('*frame' +
				     '*?*?end+frame', 'gi'), '');
}
/**
    @private
 */
Previewmaker.prototype.killTemplates = function () {
  this.data=this.data.replace(RegExp('(*|)*', 'g'), ' ');
}
/**
    @private
 */
Previewmaker.prototype.killTables=function () {
  // tables are bad, too
  this.data=this.data.replace
  (RegExp('\\|((|\\||)*||\\||\\|)*\\|', 'g')
   , '');
  // remove lines starting with a pipe
  this.data=this.data.replace(RegExp('^.*$', 'mg'), '');
}
/**
    @private
 */
Previewmaker.prototype.killImages=function () {
  // images are a nono
  // who says regexes aren't fun?
  // i think we should match:
  // ] where ....... consists of repetitions of any of:
  // 1. not 
  // 2. )* ]]
  // 3. )* ]
  var imagedetector =
  '\\s*('+
    pg.ns.image + '|' + pg.ns.category+
  ')\\s*:(]|\\]*\\]\\]|\\]*\\])*\\]\\]\\s*';
  var crudeImageRegex = RegExp(imagedetector, 'gi');
  this.data=this.data.replace(crudeImageRegex, '');
}
/**
    @private
 */
Previewmaker.prototype.killHTML=function () {
  // kill <ref>...</ref>
  this.data=this.data.replace(RegExp('<ref>*?</ref>', 'gi'), '');
  // kill html tables // this doesn't cope with embedded tables
  // may this is good enough?
  this.data=this.data.replace(RegExp('<table.*?>*?</\\s*table\\s*>\\n+', 'gi'), '');
  // let's also delete entire lines starting with <. it's worth a try.
  this.data=this.data.replace(RegExp('(^|\\n) *<.*', 'g'), '\n');
  // and those pesky html tags, but not <nowiki>
  var splitted=this.data.parenSplit(/(<.*?>)/);
  var len=splitted.length;
  for (var i=1; i<len; i=i+2) {
    switch (splitted) {
    case '<nowiki>':
    case '</nowiki>':
      break;
    default:
      splitted='';
    }
  }
  this.data=splitted.join('');
}
/**
    @private
 */
Previewmaker.prototype.killChunks=function() { // heuristics alert
  // chunks of italic text? you crazy, man?
  var italicChunkRegex=new RegExp
    ("((^|\\n)\\s*:*\\s*''(|'''|'){20}(.|\\n)*''*\\n)*", 'g');
  this.data=this.data.replace(italicChunkRegex, '');
}
/**
    @private
 */
Previewmaker.prototype.mopup=function () {
  // we simply *can't* be doing with horizontal rules right now
  this.data=this.data.replace(RegExp('^-{4,}','mg'),'');
  // no indented lines
  this.data=this.data.replace(RegExp('(^|\\n) *:*','g'), '\n');
  // replace __TOC__, __NOTOC__ and whatever else there is
  // this'll probably do
  this.data=this.data.replace(RegExp('^__*__ *$', 'gmi'),'');
}
/**
    @private
 */
Previewmaker.prototype.firstBit=function () {
  // dont't be givin' me no subsequent paragraphs, you hear me?
  /// first we "normalize" section headings, removing whitespace after, adding before
  this.data=this.data.replace(RegExp('\\s*(==+*==+)\\s*', 'g'), '\n\n$1 ');
  /// then we want to get rid of paragraph breaks whose text ends badly
  this.data=this.data.replace(RegExp('() *\\n{2,}', 'g'), '$1\n');
  this.data=this.data.replace(RegExp('^*'), '');
  stuff=(RegExp('^(|\\n)*')).exec(this.data);
  var d;
  if (stuff) d = stuff;
  if (!getValueOf('popupPreviewFirstParOnly')) d = this.data;
  /// now put \n\n after sections so that bullets and numbered lists work
  d=d.replace(RegExp('(==+*==+)\\s*', 'g'), '$1\n\n');
  // superfluous sentences are RIGHT OUT.
  // note: exactly 1 set of parens here needed to make the slice work
  d = d.parenSplit(RegExp('(+*\\s)','g'));
  // leading space is bad, mmkay?
  d=d.replace(RegExp('^\\s*'), '');
  var notSentenceEnds=RegExp('(|etc|sic|Dr|Mr|Mrs|Ms|St|\\]*|\\s)$', 'i');
  d = this.fixSentenceEnds(d, notSentenceEnds);
  var maxChars=getValueOf('popupMaxPreviewCharacters');
  var n=getValueOf('popupMaxPreviewSentences');
  var dd;
  do {dd=this.firstSentences(d,n); --n; }
  while ( dd.length > maxChars && n > 0 );
  this.data = dd;
}
/**
    @private
 */
Previewmaker.prototype.fixSentenceEnds=function(strs, reg) {
  // take an array of strings, strs
  // join strs to strs & strs if strs matches regex reg
  for (var i=0; i<strs.length-2; ++i) {
    if (reg.test(strs)) {
      a=;
      for (var j=0; j<strs.length; ++j) {
	if (j<i)   a=strs;
	if (j==i)  a=strs+strs+strs;
	if (j>i+2) a=strs;
      }
      return this.fixSentenceEnds(a,reg);
    }
  }
  return strs;
}
/**
    @private
 */
Previewmaker.prototype.firstSentences=function(strs, howmany) {
  var t=strs.slice(0, 2*howmany);
  return t.join('');
}
/**
   Runs the various methods to generate the preview.
   The preview is stored in the <code>html</html> field.
    @private
 */
Previewmaker.prototype.makePreview=function() {
  if (!isInNamespace(pg.current.article, 'Template')) {
    this.killComments();
    this.killDivs();
    this.killGalleries();
    this.killBoxTemplates();
    if (getValueOf('popupPreviewKillTemplates')) {
      this.killTemplates();
    } else {
      this.killMultilineTemplates();
    }
    this.killTables();
    this.killImages();
    this.killHTML();
    this.killChunks();
    this.mopup();
    this.firstBit();
  }
  this.html=wiki2html(this.data); // needs livepreview
  this.fixHTML();
  this.stripLongTemplates();
}
/** Test function for debugging preview problems one step at a time.
 */
function previewSteps(txt) {
  try {
    txt=txt || document.editform.wpTextbox1.value;
  } catch (err) {
    if (pg.cache.pages.length > 0) {
      txt=pg.cache.pages.data;
    } else {
      alert('provide text or use an edit page');
    }
  }
  var p=new Previewmaker(txt);
  if (!isInNamespace(pg.current.article, 'Template')) {
    p.killComments(); if (!confirm('done killComments(). Continue?\n---\n' + p.data)) { return; }
    p.killDivs(); if (!confirm('done killDivs(). Continue?\n---\n' + p.data)) { return; }
    p.killGalleries(); if (!confirm('done killGalleries(). Continue?\n---\n' + p.data)) { return; }
    p.killBoxTemplates(); if (!confirm('done killBoxTemplates(). Continue?\n---\n' + p.data)) { return; }
    if (getValueOf('popupPreviewKillTemplates')) {
      p.killTemplates(); if (!confirm('done killTemplates(). Continue?\n---\n' + p.data)) { return; }
    } else {
      p.killMultilineTemplates(); if (!confirm('done killMultilineTemplates(). Continue?\n---\n' + p.data)) { return; }
    }
    p.killTables(); if (!confirm('done killTables(). Continue?\n---\n' + p.data)) { return; }
    p.killImages(); if (!confirm('done killImages(). Continue?\n---\n' + p.data)) { return; }
    p.killHTML(); if (!confirm('done killHTML(). Continue?\n---\n' + p.data)) { return; }
    p.killChunks(); if (!confirm('done killChunks(). Continue?\n---\n' + p.data)) { return; }
    p.mopup(); if (!confirm('done mopup(). Continue?\n---\n' + p.data)) { return; }
    p.firstBit(); if (!confirm('done firstBit(). Continue?\n---\n' + p.data)) { return; }
  }
  p.html=wiki2html(p.data); // needs livepreview
  p.fixHTML(); if (!confirm('done fixHTML(). Continue?\n---\n' + p.html)) { return; }
  p.stripLongTemplates(); if (!confirm('done stripLongTemplates(). Continue?\n---\n' + p.html)) { return; }
  alert('finished preview - end result follows.\n---\n' + p.html);
}
/**
    Works around a quoting bug in livepreview.
    <code>wiki2html(']')</code> gives @literal{<a href='Foo's "bar"'>}
    which doesn't do very well. We change this into @literal{<a href="Foo's %22bar%22">}
    @private
 */
Previewmaker.prototype.fixHTML=function() {
  if(!this.html) return;
  var splitted=this.html.parenSplit(/href='(*)'/g);
  var ret='';
  for (var i=0; i<splitted.length; ++i) {
    if(i%2==0) { ret += splitted; continue; }
    if(i%2==1) { ret += 'href="' + splitted.split('"').join('%22') + '"'; }
  }
  this.html=ret;
}
/**
    Generates the preview and displays it in the current popup.
    Does nothing if the generated preview is invalid or consists of whitespace only.
    Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true.
 */
Previewmaker.prototype.showPreview=function () {
  this.makePreview();
  if (typeof this.html != typeof '') return;
  if (RegExp('^\\s*$').test(this.html)) return;
  //if (getValueOf('popupNavLinks') || getValueOf('popupSummaryData'))
  var popTips=function() { setupTooltips(document.getElementById('popupPreview' + pg.idNumber)); };
  setPopupHTML('<hr>'+this.html, 'popupPreview', pg.idNumber,
    getValueOf('popupSubpopups') ? function() { pop.runOnce( popTips, 250 ); } : null );
}
/**
    @private
 */
Previewmaker.prototype.stripLongTemplates=function() {
  // operates on the HTML!
  this.html=this.html.replace(RegExp('^.{0,1000}*?(<(p|br)( /)?>\\s*){2,}(*?)?', 'gi'), '');
  this.html=this.html.split('\n').join(' '); // workaround for <pre> templates
  this.html=this.html.replace(RegExp('*<pre>*','gi'), '');
  //this.html=this.html.replace(RegExp('(*|\\s){0,50}?<pre>*?', 'gi'), '');
  //window.h=this.html;
  //alert(this.html);
}
/**
    @private
 */
Previewmaker.prototype.killMultilineTemplates=function() {
  this.data=this.data.replace(RegExp('\\s**\\n*', 'g'), ' ');
  //this.data=this.data.replace(RegExp('(*|)*\\n(*|)*', 'g'), ' ');
}
// ENDFILE: previewmaker.js
// STARTFILE: debug.js
////////////////////////////////////////////////////////////////////
// Debugging functions
////////////////////////////////////////////////////////////////////
function time() { var d=new Date();  return d.getHours() + ':' + d.getMinutes() + ':' + d.getSeconds() +  '.' + (d.getTime() % 1000); };
var gMsg='';
function limitAlert(fn,max,s) {
  if (!fn.alertCount) fn.alertCount=0;
  if (true && fn.alertCount < max) {alert(s); fn.alertCount++;}
}
function setupDebugging() {
  // debugging - change DEBUG to NONE to switch off
  if (window.popupDebug) { // popupDebug is set from .version
    window.log=function(x) { //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; };
      if (pg.debugLevel != log.None) window.logger.debug(x);
    }
     window.errlog=function(x) {
      if (pg.debugLevel != log.None) window.logger.error(x);
    }
    pg.debugLevel=Log.DEBUG;
    window.logger = new Log(pg.debugLevel, Log.popupLogger);
    log('Initializing logger');
  } else {
    window.log = function(x) {};
    window.errlog = function(x) {};
  }
}
// ENDFILE: debug.js
// STARTFILE: images.js
// How the URLs for images in the popup come about
//   loadPreview
//          |
//       getWiki
//          |<----------------see other schematic for details
//    insertPreview      (insertPreview = onComplete)
//          |
//          |            insertPreview gets a wikiText fragment from
//          |                       the wikiText downloaded by getWiki
//          |
//  
//       |
//       |                     mouseOverWikiLink  (gets an "address fragment",
//       |                            |            no processing needed)
//       \->-loadThisImage---<----loadImages
//                 |
//           -->--hopefully valid image urls
function nextOne (array, value) {
  // NB if the array has two consecutive entries equal
  //    then this will loop on successive calls
  if (typeof array.length == 'undefined') return null;
  for (var i=0; i<array.length-1; ++i) {
    if (array==value) return array;
  }
  return null;
};
function findThis(array, value) {
  if (typeof array.length == 'undefined') return null;
  for (var i=0; i<array.length; ++i) {
    if (array==value) return i;
  }
  return null;
};
// global array for 404'ed image urls
pg.cache.badImageUrls=;
function sequentialLoadThisImage (image) {
  if (!getValueOf('popupImages')) return false;
  if (!isValidImageName(image)) return false;
  var imageUrls=getImageUrls(image);
  if (!imageUrls) return null;
  var img=new Image();
  img.isNew=true;
  img.pg.idNumber=pg.idNumber;
  img.counter=1;
  img.onload = function () {
    // clear status thingy
    setImageStatus('');
    var i=findThis(imageUrls, this.src);
    var goodSrc=this.src;
    var setPopupImage=function () {
      var popupImage=document.getElementById("popupImage"+this.pg.idNumber);
      if (popupImage && typeof popupImage.src != 'undefined') {
	clearInterval(pg.timer.image);
	popupImage.src=goodSrc;
	popupImage.width=getValueOf('popupImageSize');
	popupImage.style.display='inline';
	setPopupImageLink(image, pg.wiki.imageSources.wiki);
	return true;
      } else return false;
    };
    pg.timer.image=setInterval(setPopupImage, 250);
    pg.cache.images.push(goodSrc);
  };
  img.onerror = function () {
    pg.cache.badImageUrls.push(this.src);
    // if the popup is visible, move on
    if (over && over.style.visibility=='visible' && this.pg.idNumber==pg.idNumber) this.setNext();
  };
  img.setNext = function () {
    var currentSrc=null;
    var newSrc;
    if (!this.isNew) currentSrc=this.src;
    this.isNew=false;
    newSrc= (currentSrc) ? nextOne(imageUrls, currentSrc) : imageUrls;
    while (findThis(pg.cache.badImageUrls, newSrc))  {
      newSrc=nextOne(imageUrls, newSrc);
      if (!newSrc) {
	setImageStatus (' :-(');
	return;
      }
    }
    setImageStatus(' '+findThis(imageUrls, newSrc));
    this.src=newSrc;
  }
  // start the ball rolling
  img.setNext();
};
function loadThisImageAtThisUrl(image, url) {
  log('loading "best" image:\n'+url);
  pg.misc.gImage=image;
  pg.misc.imageArray = ;
  pg.misc.imageArray = new Image();
  pg.misc.imageArray.src=url;
  if (pg.timer.image != null) {
    clearInterval(pg.timer.image);
    pg.counter.checkImages=0;
  }
  pg.timer.image=setInterval("checkImages()", 250);
  return;
};
function loadImages(article) {if(! isImage(article) ) return null; return loadThisImage(article);};
// methinks this is unbelievably silly
// it dovetails with the parallel image loader function
function checkImages() {
  //log('checkImages: pg.counter.loop='+pg.counter.loop+'; pg.counter.checkImages='+pg.counter.checkImages);
  if (pg.timer.checkImages!=null) {
    clearInterval(pg.timer.checkImages);
    pg.timer.checkImages=null;
    if (pg.counter.loop > 10); {pg.counter.loop=0; log('too many iterations of checkImages'); return;}
    pg.counter.loop++;
  } else pg.counter.checkImages++;
  var status =  ( pg.counter.checkImages % 2 ) ? ':' : '.' ;
  setImageStatus(status);
  if (pg.counter.checkImages > 100) {pg.counter.checkImages = 0; log ('pg.counter.checkImages too big in checkImages; returning'); clearInterval(pg.timer.image);}
  var popupImage=null;
  popupImage=document.getElementById("popupImg"+pg.idNumber);
  if (popupImage == null) {
    // this doesn't seem to happen any more in practise for some reason
    // still, I'll leave it in
    log('checkImages: document.getElementById("popupImg'+pg.idNumber+'") is null! retrying in 333ms...');
    pg.timer.checkImages=setInterval("checkImages()",333);
    return;
  }
  log('checkImages: found element popupImg'+pg.idNumber+', and src='+popupImage.src);
  // get the first image to successfully load
  // and put it in the popupImage
  for(var i = 0; i < pg.misc.imageArray.length; ++i) {
    if(isImageOk(pg.misc.imageArray)) {
      // stop all the gubbins, assign the image and return
      log('checkImages: got at pos '+i+', src='+pg.misc.imageArray.src);
      clearInterval(pg.timer.image);
      if(isImage(pg.misc.gImage)) {
	popupImage.src=pg.misc.imageArray.src;
	popupImage.width=getValueOf('popupImageSize');
	popupImage.style.display='inline';
	// should we check to see if it's already there? maybe...
	pg.cache.images.push(pg.misc.imageArray.src);
	setPopupImageLink(pg.misc.gImage, pg.wiki.imageSources.wiki);
	stopImagesDownloading();
      }
      setImageStatus('');
      // reset evil nonconstant globals
      delete pg.misc.imageArray; pg.misc.imageArray=;
      pg.timer.image=null;
      pg.counter.checkImages=0;
      pg.counter.loop=0;
      return popupImage.src;
    }
  }
  log('checkImages: no good image found. retrying in a tic...');
  pg.timer.checkImages=setInterval("checkImages()",333);
};
function stopImagesDownloading() {
  pg.misc.gImage=null;
  if (pg.misc.imageArray == null) return null;
  var i;
  for (i=0; i<pg.misc.imageArray.length; ++i) {
    //pg.misc.imageArray.src=''; // this is a REALLY BAD IDEA
    delete pg.misc.imageArray;
    //pg.misc.imageArray = new Image();
  }
  pg.misc.imageArray = ;
  return i;
};
function toggleSize() {
  var imgContainer=this;
  if (!imgContainer) { alert('imgContainer is null :/'); return;}
  img=imgContainer.firstChild;
  if (!img) { alert('img is null :/'); return;}
  if (!img.style.width || img.style.width=='') img.style.width='100%';
  else img.style.width=''; // popupImageSize+'px';
};
function setPopupImageLink (img, wiki) {
  if( wiki === null || img === null ) return null;
  var a=document.getElementById("popupImageLink"+pg.idNumber);
  if (a === null) return null;
  var linkURL = imageURL(img, wiki);
  if (linkURL != null) {
    if (getValueOf('popupImagesToggleSize')) { a.onclick=toggleSize; a.title=popupString('Toggle image size'); }
    else { a.href=linkURL; a.title=popupString('Open full-size image'); }
  }
  return linkURL;
};
function isImageOk(img)
{
  // IE test
  if (!img.complete)
    return false;
  // gecko test
  if (typeof img.naturalWidth != "undefined" && img.naturalWidth == 0)
    return false;
  // test for konqueror and opera
  // note that img.width must not be defined in the html with a width="..."
  // for this to work.
  // konq seems to give "broken images" width 16, presumably an icon width
  // this test would probably work in gecko too, *except for very small images*
  if (typeof img.width == 'undefined' || img.width <=  16) return false;
  // No other way of checking: assume it's ok.
  return true;
};
// those odd a/a5/ bits of image urls
function imagePathComponent(article) {
  var stripped=stripNamespace(article);
  var forhash=safeDecodeURI(stripped).split(' ').join('_');
  var hash=hex_md5(forhash);
  return hash.substring(0,1) + '/' + hash.substring(0,2) + '/';
}
function getImageUrlStart(wiki) { // this returns a trailing slash
  switch (wiki) {
    case 'en.wikipedia.org':  return 'http://upload.wikimedia.org/wikipedia/en/';
    case pg.wiki.commons:     return 'http://upload.wikimedia.org/wikipedia/commons/';
    case 'en.wiktionary.org': return 'http://en.wiktionary.org/upload/en/';
    default: // unsupported - take a guess
      if (pg.wiki.wikimedia) {
	return 'http://upload.wikimedia.org/wikipedia/' + pg.wiki.lang +'/';
      }
      else /* this should work for wikicities */
	return 'http://' + wiki + '/images/';
  }
}
function imageURL(img, wiki) {
  if (getValueOf('popupImagesFromThisWikiOnly') && wiki != pg.wiki.hostname) return null;
  var imgurl=null;
  if (isImage(img)) {
    var pathcpt = imagePathComponent(img);
    var stripped=stripNamespace(img);
    imgurl=getImageUrlStart(wiki) + pathcpt + stripped;
  }
  return imgurl;
}
function imageThumbURL(img, wiki, width) {
  //
  // eg http://upload.wikimedia.org/wikipedia/en/thumb/6/61/
  //           Rubiks_cube_solved.jpg/120px-Rubiks_cube_solved.jpg
  //           ^^^^^^^^^^^^^^^^^^^^^^^
  //          wikicities omits this bit
  //  AND wikicities needs a new pathcpt for each filename including thumbs
  if (getValueOf('popupImagesFromThisWikiOnly') && wiki != pg.wiki.hostname) return null;
  if (getValueOf('popupNeverGetThumbs')) return null;
  var imgurl=null;
  if (isImage(img)) {
    var stripped=stripNamespace(img);
    var pathcpt;
    if (pg.wiki.wikimedia) pathcpt = imagePathComponent(stripped);
    else pathcpt = imagePathComponent(width+'px-'+stripped);
    imgurl=getImageUrlStart(wiki) +  "thumb/" + pathcpt;
    if (pg.wiki.wikimedia) imgurl += stripped + '/';
    imgurl += width +"px-" + stripped;
  }
  return imgurl;
};
function loadThisImage(image) {
  if (getValueOf('popupLoadImagesSequentially')) return sequentialLoadThisImage(image);
  else return parallelLoadThisImage(image);
};
function getImageUrls(image) {
  var imageUrls=;
  for (var i=0; i<pg.wiki.imageSources.length; ++i) {
    var url;
    if (pg.wiki.imageSources.thumb)
      url=imageThumbURL(image, pg.wiki.imageSources.wiki, pg.wiki.imageSources.width);
    else
      url=imageURL(image, pg.wiki.imageSources.wiki);
    for (var j=0; j<pg.cache.images.length; ++j) {
      if (url == pg.cache.images) {
	loadThisImageAtThisUrl(image, url);
	return null;
      }
    }
    if (url!=null) imageUrls.push(url);
  }
  return imageUrls;
};
// this is probably very wasteful indeed of bandwidth
// hey ho
function parallelLoadThisImage (image) {
  if (!getValueOf('popupImages')) return;
  if (!isValidImageName(image)) return false;
  var imageUrls=getImageUrls(image);
  if (!imageUrls) return null;
  for (var i=0; i<imageUrls.length; ++i) {
    var url = imageUrls;
    pg.misc.imageArray=new Image();
    pg.misc.imageArray.src=url;
  }
  if (pg.timer.image != null) {
    clearInterval(pg.timer.image);
    pg.counter.checkImages=0;
  }
  pg.misc.gImage=image;
  pg.timer.image=setInterval("checkImages()", 250);
  return true;
};
function getValidImageFromWikiText(wikiText) {
  var imagePage=null;
  // nb in pg.re.image we're interested in the second bracketed expression
  // this may change if the regex changes :-(
  //var match=pg.re.image.exec(wikiText);
  var matched=null;
  var match;
  while ( match = pg.re.image.exec(wikiText) ) {
    // now find a sane image name - exclude templates by seeking {
    var m = match || pg.ns.image + ':' + match;
    var pxWidth=match;
    if ( isValidImageName(m) && (!pxWidth || parseInt(pxWidth) >= getValueOf('popupMinImageWidth')) ) {
      matched=m;
      break;
    }
  }
  pg.re.image.lastIndex=0;
  if (!matched) return null;
  return pg.ns.image+':'+upcaseFirst(matched);
};
// ENDFILE: images.js
// STARTFILE: namespaces.js
// Set up namespaces and other non-strings.js localization
// (currently that means redirs too)
// Put the right namespace list into pg.ns.list, based on pg.wiki.lang
// Default to english if nothing seems to fit
function setNamespaceList() {
  var m="Media";
  var list = ;
  var nsLists = {
    "af": ,
    "als": ,
    "ar": ,
    "ast": ,
    "be": ,
    "bg": ,
    "bm": ,
    "bn": ,
    "br": ,
    "ca": ,
    "cs": ,
    "csb": ,
    "cv": ,
    "cy": ,
    "da": ,
    "de": ,
    "el": ,
    "eo": ,
    "es": ,
    "et": ,
    "eu": ,
    "fa": ,
    "fi": ,
    "fo": ,
    "fr": ,
    "fur": ,
    "fy": ,
    "ga": ,
    "gu": ,
    "he": ,
    "hi": ,
    "hr": ,
    "hu": ,
    "ia": ,
    "id": ,
    "is": ,
    "it": ,
    "ja": ,
    "ka": ,
    "ko": ,
    "ku": ,
    "la": ,
    "li": ,
    "lt": ,
    "mk": ,
    "ms": ,
    "mt": ,
    "nap": ,
    "nds": ,
    "nl": ,
    "nn": ,
    "no": ,
    "nv": ,
    "oc": ,
    "os": ,
    "pa": ,
    "pl": ,
    "pt": ,
    "ro": ,
    "ru": ,
    "sc": ,
    "sk": ,
    "sl": ,
    "sq": ,
    "sr": ,
    "sv": ,
    "ta": ,
    "th": ,
    "tlh": ,
    "tr": ,
    "tt": ,
    "uk": ,
    "vi": ,
    "wa": 
  }
  pg.ns.list = nsLists || list;
}
function namespaceListToRegex(list) {return RegExp('^('+list.join('|').split(' ').join('')+'):');};
// function setNamespaceList is ugly as sin, moved to later in the code
function setNamespaces() {
  setNamespaceList();
  pg.ns.withTalkList=; // NB root (article) corresponds with this entry, null
  pg.ns.talkList=];
  // if the number of namespaces changes then this will have to be changed
  // maybe the easiest way is to specify the arrays by hand as in the comments following the loop
  for (var i=3; i+1<pg.ns.list.length; i=i+2) {
    pg.ns.withTalkList.push(pg.ns.list);
    pg.ns.talkList.push(pg.ns.list);
  }
  // ALERT! SILLY HARDCODED VALUES FOLLOW!
  pg.ns.special   = pg.ns.list;
  pg.ns.image     = pg.ns.list;
  pg.ns.user      = pg.ns.list;
  pg.ns.category  = pg.ns.list;
  pg.ns.nonArticleList=pg.ns.list.slice(0,2).concat(pg.ns.list.slice(2));
}
function setRedirs() {
  var r='redirect';
  var R='REDIRECT';
  var redirLists={
    'be':  ,
    'bg':  ,
    'cs':  ,
    'cy':  ,
    'et':  ,
    'ga':  ,
    'is':  ,
    'mk':  ,
    'nds': ,
    'nn':  ,
    'pt':  ,
    'ru':  ,
    'sk':  ,
    'sr':  ,
    'tt':  ,
    'vi':   // no comma
  }
  var redirList=redirLists || ;
  // Mediawiki is very tolerant about what comes after the #redirect at the start
  pg.re.redirect=RegExp('^\\s*(' + redirList.join('|') + ').*?\\]*)(|]*)?\\]{2}\\s*(.*)', 'i');
}
function setInterwiki() {
  if (pg.wiki.wikimedia) {
    pg.wiki.interwiki='aa|ab|af|ak|als|am|an|ang|ar|arc|as|ast|av|ay|az|ba|be|ber|bg|bh|bi|bm|bn|bdf|bo|br|bs|ca|ce|ceb|ch|cho|chr|chy|co|commons|cr|cs|csb|cu|cv|cy|da|de|dv|dz|el|en|eo|es|et|eu|fa|ff|fi|fiu-vro|fj|fo|fr|fur|fy|ga|gd|gil|gl|gn|got|gu|gv|ha|haw|he|hi|ho|hr|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|io|is|it|iu|ja|jbo|jv|ka|kg|ki|kj|kk|kl|km|kn|ko|kr|ks|ku|kv|kw|ky|la|lad|lan|lb|lg|li|ln|lo|lt|lu|lv|mg|mh|mi|mk|ml|mn|mo|mr|ms|mt|mus|my|na|nah|nap|nb|nd|nds|ne|ng|nl|nn|no|nr|nv|ny|oc|oj|om|or|os|pa|pam|pi|pl|ps|pt|qu|rm|rn|ro|roa-rup|ru|rw|sa|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|smg|sn|so|sq|sr|ss|st|su|sv|sw|ta|te|tg|th|ti|tk|tl|tlh|tn|to|tpi|tr|ts|tt|tum|tw|ty|ug|uk|ur|uz|ve|vi|vk|vo|wa|war|wen|wo|xh|yi|yo|za|zh|zh-min-nan|zu';
    pg.re.interwiki=RegExp('^'+pg.wiki.interwiki+':');
  } else {
    pg.wiki.interwiki=null;
    pg.re.interwiki=RegExp('^$');
  }
}
// ENDFILE: namespaces.js
// STARTFILE: selpop.js
window.getEditboxSelection=function() {
  // see http://www.webgurusforum.com/8/12/0
  try {
    var editbox=document.editform.wpTextbox1;
  } catch (dang) { return; }
  // IE, Opera
  if (document.selection) { return document.selection.createRange().text; }
  // Mozilla
  var selStart = editbox.selectionStart;
  var selEnd = editbox.selectionEnd;
  return (editbox.value).substring(selStart, selEnd);
}
window.doSelectionPopup=function() {
  // popup if the selection looks like [[foo|anything afterwards at all
  // or ]text without ']]'
  // or ]
  var sel=getEditboxSelection();
  var open=sel.indexOf(']');
  if (open == -1 || ( pipe == -1 && close == -1) ) return;
  if (pipe != -1 && open > pipe || close != -1 && open > close) return;
  var article=sel.substring(open+2, (pipe < 0) ? close : pipe);
  if (close > 0 && sel.substring(close+2).indexOf('[[') >= 0) return;
  var a=document.createElement('a');
  a.href=pg.wiki.titlebase + article;
  mouseOverWikiLink2(a);
  //mouseOutWikiLink.apply(a, );
  if (a.navpopup) {
    a.navpopup.addHook(function(){runStopPopupTimer(a.navpopup);}, 'unhide', 'after');
  }
}
// ENDFILE: selpop.js
// STARTFILE: navpopup.js
/**
   @fileoverview  Defines two classes: {@link Navpopup} and {@link Mousetracker}.
   <code>Navpopup</code> describes popups: when they appear, where, what
   they look like and so on.
   <code>Mousetracker</code> "captures" the mouse using
   <code>document.onmousemove</code>.
*/
/**
    Creates a new Mousetracker.
    @constructor
    @class The Mousetracker class. This monitors mouse movements and manages associated hooks.
*/
function Mousetracker() {
  /**
     Flag - are we switched on?
     @type Boolean
   */
  this.active=false;
  /**
      Array of hook functions.
      @private
      @type Array
  */
  this.hooks=;
}
/**
   Adds a hook, to be called when we get events.
   @param {Function} f A function which is called as
   <code>f(x,y)</code>. It should return <code>true</code> when it
   wants to be removed, and <code>false</code> otherwise.
*/
Mousetracker.prototype.addHook = function (f) {
  this.hooks.push(f);
};
/**
   Runs hooks, passing them the x
   and y coords of the mouse.  Hook functions that return true are
   passed to {@link Mousetracker#removeHooks} for removal.
   @private
*/
Mousetracker.prototype.runHooks = function () {
  var remove=false;
  var removeObj={};
  // this method gets called a LOT -
  // pre-cache some variables
  var x=this.x, y=this.y, len = this.hooks.length;
  for (var i=0; i<len; ++i) {
    //~ run the hook function, and remove it if it returns true
    if (this.hooks(x, y)===true) {
      remove=true;
      removeObj=true;
    }
  }
  if (remove) { this.removeHooks(removeObj); }
};
/**
   Removes hooks.
   @private
   @param {Object} removeObj An object whose keys are the index
   numbers of functions for removal, with values that evaluate to true
*/
Mousetracker.prototype.removeHooks = function(removeObj) {
  var newHooks=;
  var len = this.hooks.length;
  for (var i=0; i<len; ++i) {
    if (! removeObj) { newHooks.push(this.hooks); }
  }
  this.hooks=newHooks;
};
/**
   Event handler for mouse wiggles.
   We simply grab the event, set x and y and run the hooks.
   This makes the cpu all hot and bothered :-(
   @private
   @param {Event} e Mousemove event
*/
Mousetracker.prototype.track=function (e) {
  //log ('Mousetracker.track');
  //~ Apparently this is needed in IE.
  e = e || window.event;
  var x, y;
  if (e) {
    if (e.pageX) { x=e.pageX; y=e.pageY; }
    else if (typeof e.clientX!='undefined') {
      var left, top, docElt = window.document.documentElement;
      if (docElt) { left=docElt.scrollLeft; }
      left = left || window.document.body.scrollLeft || window.document.scrollLeft || 0;
      if (docElt) { top=docElt.scrollTop; }
      top = top || window.document.body.scrollTop || window.document.scrollTop || 0;
      x=e.clientX + left;
      y=e.clientY + top;
    } else { return; }
    this.x = x;
    this.y = y;
    if (this.hooks.length === 0) { return; }
    if (typeof this.lastHook_x != 'number') { this.lastHook_x = -100; this.lastHook_y=-100; }
    var diff = (this.lastHook_x - x)*(this.lastHook_y - y);
    diff = (diff >= 0) ? diff : -diff;
    if ( diff > 1 ) {
      this.lastHook_x=x;
      this.lastHook_y=y;
      this.runHooks();
    }
  }
};
/**
    Sets things in motion, unless they are already that is, registering an event handler on <code>document.onmousemove</code>.
    A half-hearted attempt is made to preserve the old event handler if there is one.
*/
Mousetracker.prototype.enable = function () {
  if (this.active) { return; }
  this.active=true;
  //~ Save the current handler for mousemove events. This isn't too
  //~ robust, of course.
  this.savedHandler=document.onmousemove;
  //~ Gotta save @tt{this} again for the closure, and use apply for
  //~ the member function.
  var savedThis=this;
  document.onmousemove=function (e) {savedThis.track.apply(savedThis, );};
};
/**
   Disables the tracker, removing the event handler.
*/
Mousetracker.prototype.disable = function () {
  if (!this.active) { return; }
  if (typeof this.savedHandler=='function') {
    document.onmousemove=this.savedHandler;
  } else { delete document.onmousemove; }
  this.active=false;
};
/**
   Creates a new Navpopup.
   Gets a UID for the popup and
   @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable.
   @constructor
   @class The Navpopup class. This generates popup hints, and does some management of them.
*/
function Navpopup(init) {
  //alert('new Navpopup(init)');
  /** UID for each Navpopup instance.
      Read-only.
      @type integer
   */
  this.uid=Navpopup.uid++;
  /**
     Read-only flag for current visibility of the popup.
     @type boolean
     @private
   */
  this.visible=false;
  /** Flag to be set when we want to cancel a previous request to
      show the popup in a little while.
      @private
      @type boolean
  */
  this.noshow=false;
  /** Categorised list of hooks.
      @see #runHooks
      @see #addHook
      @private
      @type Object
  */
  this.hooks={
    'create': ,
    'unhide': ,
    'hide': 
  };
  /** List of downloads associated with the popup.
      @private
      @type Array
   */
  this.downloads=;
  /** Number of uncompleted downloads.
      @type integer
  */
  this.pending=null;
  /** Tolerance in pixels when detecting whether the mouse has left the popup.
      @type integer
   */
  this.fuzz=5;
  /** Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position.
      @type boolean
  */
  this.constrained=true;
  /** The popup width in pixels.
      @private
      @type integer
  */
  this.width=0;
  /** The popup width in pixels.
      @private
      @type integer
  */
  this.height=0;
  /** The main content DIV element.
      @type HTMLDivElement
  */
  this.mainDiv=null;
  this.createMainDiv();
  if (!init || typeof init.draggable=='undefined' || init.draggable) {
    this.makeDraggable();
  }
}
/**
   A UID for each Navpopup. This constructor property is just a counter.
   @type integer
   @private
*/
Navpopup.uid=0;
/**
   Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible.
   @type boolean
*/
Navpopup.prototype.isVisible=function() {
  return this.visible;
};
/**
   Repositions popup using CSS style.
   @private
   @param {integer} x x-coordinate (px)
   @param {integer} y y-coordinate (px)
   @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition}
*/
Navpopup.prototype.reposition= function (x,y, noLimitHor) {
  log ('reposition('+x+','+y+','+noLimitHor+')');
  if (typeof x != 'undefined' && x!==null) { this.left=x; }
  if (typeof y != 'undefined' && y!==null) { this.top=y; }
  if (typeof this.left != 'undefined' && typeof this.top != 'undefined') {
    this.mainDiv.style.left=this.left + 'px';
    this.mainDiv.style.top=this.top + 'px';
  }
  if (!noLimitHor) { this.limitHorizontalPosition(); }
};
/**
   Prevents popups from being in silly locations. Hopefully.
   Should not be run if {@link #constrained} is true.
   @private
*/
Navpopup.prototype.limitHorizontalPosition=function() {
  if (!this.constrained || this.tooWide) { return; }
  //var x=this.left; //findPosX(this.mainDiv);
  var x=parseInt(this.mainDiv.style.left, 10);
  var w=parseInt(this.mainDiv.offsetWidth, 10);
  var cWidth=document.body.clientWidth;
  log('limitHorizontalPosition: x='+x+
      ', this.left=' + this.left +
      ', this.width=' + this.width +
      ', cWidth=' + cWidth);
  if ( (x+w) >= cWidth ||
       ( x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width
	 && x > cWidth - this.maxWidth ) ) {
    // This is a very nasty hack. There has to be a better way!
    // We find the "natural" width of the div by positioning it at the far left
    // then reset it so that it should be flush right (well, nearly)
    this.mainDiv.style.left='-10000px';
    this.mainDiv.style.width = this.maxWidth + 'px';
    var naturalWidth=parseInt(this.mainDiv.offsetWidth, 10);
    var newLeft=cWidth - naturalWidth - 1;
    if (newLeft < 0) { newLeft = 0; this.tooWide=true; } // still unstable for really wide popups?
    log ('limitHorizontalPosition: moving to ('+newLeft + ','+ this.top+');' +
	 ' naturalWidth=' + naturalWidth + ', clientWidth=' + cWidth);
    this.reposition(newLeft, null, true);
  }
};
/**
 Counter indicating the z-order of the "highest" popup.
 We start the z-index at 1000 so that popups are above everything
 else on the screen.
 @private
 @type integer
*/
Navpopup.highest=1000;
/**
 Brings popup to the top of the z-order.
 We increment the {@link #highest} property of the contructor here.
 @private
*/
Navpopup.prototype.raise = function () {
  this.mainDiv.style.zIndex=Navpopup.highest + 1;
  ++Navpopup.highest;
};
/**
   Shows the popup provided {@link #noshow} is not true.
   Updates the position, brings the popup to the top of the z-order and unhides it.
*/
Navpopup.prototype.show = function () {
  //document.title+='s';
  if (this.noshow) { return; }
  //document.title+='t';
  this.reposition();
  this.raise();
  this.unhide();
};
/**
    Runs the {@link #show} method in a little while, unless we're
    already visible.
    @param {integer} time Delay in milliseconds
    @see #showSoonIfStable
*/
Navpopup.prototype.showSoon = function (time) {
  if (this.visible) { return; }
  this.noshow=false;
  //~ We have to save the value of @tt{this} so that the closure below
  //~ works.
  var savedThis=this;
  //this.start_x = Navpopup.tracker.x;
  //this.start_y = Navpopup.tracker.y;
  pop.runOnce(function () {
    if (Navpopup.tracker.active) {
      savedThis.reposition.apply(savedThis, );
    }
    //~ Have to use apply to invoke his member function here
    savedThis.show.apply(savedThis, );
  }, time);
};
/**
 Checks to see if the mouse pointer has
 stabilised (checking every <code>time</code>/2 milliseconds) and runs the
 {@link #show} method if it has. This method makes {@link #showSoon} redundant.
 @param {integer} time The minimum time (ms) before the popup may be shown.
*/
Navpopup.prototype.showSoonIfStable = function (time) {
  log ('showSoonIfStable, time='+time);
  if (this.visible) { return; }
  this.noshow = false;
  //~ initialize these variables so that we never run @tt{show} after
  //~ just half the time
  this.stable_x = -10000; this.stable_y = -10000;
  var stableShow = function() {
    log('stableShow called');
    var new_x = Navpopup.tracker.x, new_y = Navpopup.tracker.y;
    var dx = savedThis.stable_x - new_x, dy = savedThis.stable_y - new_y;
    var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz;
    //document.title += '.join(',') + '] ';
    if ( dx * dx <= fuzz2 && dy * dy <= fuzz2 ) {
      log ('mouse is stable');
      clearInterval(savedThis.showSoonStableTimer);
      savedThis.reposition.apply(savedThis, );
      savedThis.show.apply(savedThis, );
      return;
    }
    savedThis.stable_x = new_x; savedThis.stable_y = new_y;
  };
  var savedThis = this;
  this.showSoonStableTimer = setInterval(stableShow, time/2);
};
/**
    Makes the popup unhidable until we call {@link #unstick}.
 */
Navpopup.prototype.stick=function() {
  this.noshow=false;
  this.sticky=true;
};
/**
    Allows the popup to be hidden.
 */
Navpopup.prototype.unstick=function() {
  this.sticky=false;
};
/**
   Sets the {@link #noshow} flag and hides the popup. This should be called
   when the mouse leaves the link before
   (or after) it's actually been displayed.
*/
Navpopup.prototype.banish = function () {
  log ('banish called');
  // hide and prevent showing with showSoon in the future
  this.noshow=true;
  if (this.showSoonStableTimer) {
    log('clearing showSoonStableTimer');
    clearInterval(this.showSoonStableTimer);
  }
  this.hide();
};
/**
   Runs hooks added with {@link #addHook}.
   @private
   @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
   @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
*/
Navpopup.prototype.runHooks = function (key, when) {
  if (!this.hooks) { return; }
  var keyHooks=this.hooks;
  var len=keyHooks.length;
  for (var i=0; i< len; ++i) {
    if (keyHooks.when == when) { keyHooks.hook.apply(this, ); }
  }
};
/**
   Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the Navpopup instance, and no arguments.
   @param {Function} hook The hook function.
   @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
   @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
*/
Navpopup.prototype.addHook = function ( hook, key, when ) {
  when = when || 'after';
  if (!this.hooks) { return; }
  this.hooks.push( {hook: hook, when: when} );
};
/**
   Creates the main DIV element, which contains all the actual popup content.
   Runs hooks with key 'create'.
   @private
*/
Navpopup.prototype.createMainDiv = function () {
  if (this.mainDiv) { return; }
  this.runHooks('create', 'before');
  var mainDiv=document.createElement('div');
  var savedThis=this;
  mainDiv.onclick=function(e) {savedThis.onclickHandler(e);};
  mainDiv.className=(this.className) ? this.className : 'navpopup_maindiv';
  mainDiv.id=mainDiv.className + this.uid;
  mainDiv.style.position='absolute';
  mainDiv.style.display='none';
  mainDiv.className='navpopup';
  // easy access to javascript object through DOM functions
  mainDiv.navpopup=this;
  this.mainDiv=mainDiv;
  document.body.appendChild(mainDiv);
  this.runHooks('create', 'after');
};
/**
   Calls the {@link #raise} method.
   @private
*/
Navpopup.prototype.onclickHandler=function(e) {
  this.raise();
};
/**
   Makes the popup draggable, using a {@link Drag} object.
   @private
*/
Navpopup.prototype.makeDraggable=function(e) {
  if (!this.mainDiv) { this.createMainDiv(); }
  var drag=new Drag();
  drag.startCondition=function(e) {
    try { if (!e.shiftKey) { return false; } } catch (err) { return false; }
    return true;
  };
  var np=this;
  drag.endHook=function(x,y) { np.reposition(x,y); };
  drag.init(this.mainDiv);
};
/** Hides the popup using CSS. Runs hooks with key 'hide'.
    Sets {@link #visible} appropriately.     {@link #banish} should be called externally instead of this method.
    @private
*/
Navpopup.prototype.hide = function () {
  this.runHooks('hide', 'before');
  this.abortDownloads();
  if (this.sticky) { return; }
  if (typeof this.visible != 'undefined' && this.visible) {
    this.mainDiv.style.display='none';
    this.visible=false;
  }
  this.runHooks('hide', 'after');
};
/** Shows the popup using CSS. Runs hooks with key 'unhide'.
    Sets {@link #visible} appropriately.   {@link #show} should be called externally instead of this method.
    @private
*/
Navpopup.prototype.unhide = function () {
  this.runHooks('unhide', 'before');
  if (typeof this.visible != 'undefined' && !this.visible) {
    this.mainDiv.style.display='inline';
    this.visible=true;
  }
  this.runHooks('unhide', 'after');
};
/**
 Sets the <code>innerHTML</code> attribute of the main div containing the popup content.
 @param {String} html The HTML to set.
*/
Navpopup.prototype.setInnerHTML = function (html) {
  this.mainDiv.innerHTML = html;
};
/**
   Updates the {@link #width} and {@link #height} attributes with the CSS properties.
   @private
*/
Navpopup.prototype.updateDimensions = function () {
  this.width=parseInt(this.mainDiv.offsetWidth, 10);
  this.height=parseInt(this.mainDiv.offsetHeight, 10);
};
/**
   Checks if the point (x,y) is within {@link #fuzz} of the
   {@link #mainDiv}.
   @param {integer} x x-coordinate (px)
   @param {integer} y y-coordinate (px)
   @type boolean
*/
Navpopup.prototype.isWithin = function(x,y) {
  //~ If we're not even visible, no point should be considered as
  //~ being within the popup.
  if (!this.visible) { return false; }
  this.updateDimensions();
  var fuzz=this.fuzz || 0;
  //  document.title=''+fuzz+';'+x+';'+y+'|'+this.left+','+(this.left+this.width)+';'+this.top+','+(this.top+this.height);
  //~ Use a simple box metric here.
  return (x+fuzz >= this.left && x-fuzz <= this.left + this.width &&
	  y+fuzz >= this.top  && y-fuzz <= this.top  + this.height);
};
/**
   Adds a download to {@link #downloads}.
   @param {Downloader} download
*/
Navpopup.prototype.addDownload=function(download) {
  if (!download) { return; }
  this.downloads.push(download);
};
/**
    Aborts the downloads listed in {@link #downloads}.
    @see Downloader#abort
*/
Navpopup.prototype.abortDownloads=function() {
  for(var i=0; i<this.downloads.length; ++i) {
    var d=this.downloads;
    if (d && d.abort) { d.abort(); }
  }
  this.downloads=;
}
/**
 A {@link Mousetracker} instance which is a property of the constructor (pseudo-global).
*/
Navpopup.tracker=new Mousetracker();
// ENDFILE: navpopup.js
// STARTFILE: diff.js
/*
 * Javascript Diff Algorithm
 *  By John Resig (http://ejohn.org/) and ]
 *
 * More Info:
 *  http://ejohn.org/projects/javascript-diff-algorithm/
 */
function diffEscape(n) {
    return n.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(RegExp('"','g'), "&quot;");
}
function delFmt(x) {
  if (!x.length) { return ''; }
  return "<del style='background:#FFE6E6;'>" + /*diffEscape*/(x.join('')).split('\n').join('&para;\n') +"</del>";
}
function insFmt(x) {
  if (!x.length) { return ''; }
  return "<ins style='background:#AAFFEE;'>" + /*diffEscape*/(x.join('')).split('\n').join('&para;\n') +"</ins>";
}
function copyDiffObj(x){
  var ret=;
  for (var i=0; i<x.length; ++i) {
    if (typeof x == 'string') { ret=x; }
    else {
      ret={};
      for (var prop in x) { ret=x; }
    }
  }
  return ret;
}
function countCrossings(a, b, i, eject) {
  // count the crossings on the edge starting at b
  if (b.row==null) { return -1; }
  var count=0;
  for (var j=0; j<a.length; ++j) {
    if (!a.row || a.row === 0) { continue; }
    if ( (j-b.row)*(i-a.row) > 0) {
      if(eject) { return true; }
      count++;
    }
  }
  return count;
}
//de=document.createElement('div');
//de.id='debug'
//ti=document.getElementsByTagName('h1');
//ti.parentNode.appendChild(de);
//function debug(s){
//  try {document.getElementById('debug').innerHTML+=s+'<br>'; } catch(foo) {};
//}
function untangle( a, b) {
  // try to remove crossing edges from an ordered bipartite graph,
  // removing the least number possible
  var aa=copyDiffObj(a);
  var bb=copyDiffObj(b);
  // remove the edge with the largest number of crossings until no
  // crossings remain
  do {
    var maxCrossings=0;
    var worstEdge=null;
    for (var i=0; i<bb.length; ++i) {
      var c=countCrossings(aa,bb,i);
      if (c > maxCrossings) { maxCrossings=c; worstEdge=i; }
    }
    if (worstEdge!=null) {
      aa.row ] = aa.row ].text;
      bb = bb.text;
    }
  } while (maxCrossings > 0);
  return { a: aa, b: bb };
}
function max(a,b){return a<b ? b : a;}
function min(a,b){return a>b ? b : a;}
function shortenDiffString(str, context) {
  var re=RegExp('(<del*?</del>|<ins*?</ins>)');
  var splitted=str.parenSplit(re);
  var ret=;
  for (var i=0; i<splitted.length; i+=2) {
    if (splitted.length < 2*context) {
      ret += splitted;
      if (i+1<splitted.length) { ret += splitted; }
      continue;
    }
    else {
      if (i > 0) { ret += splitted.substring(0,context); }
      if (i+1 < splitted.length) { ret.push(splitted.substring(splitted.length-context) + splitted); }
    }
  }
  while (ret.length > 0 && !ret) {
    ret = ret.slice(1);
  }
  return ret;
}
function diffString( o, n, simpleSplit, slow ) {
  var splitRe=RegExp('({2}|]{2}|{2,3}|{2,3}||=|+|\\b)');
  o=diffEscape(o); n=diffEscape(n);
  var out, i;
  if (simpleSplit) { out = diff( o.split(/\b/), n.split(/\b/) ); }
  else out = diff( o.parenSplit(splitRe), n.parenSplit(splitRe) );
  var str = "";
  var acc=; // accumulator for prettier output
  // crossing pairings -- 'A B' vs 'B A' -- cause problems, so let's iron them out
  if (slow) {
    var untangled=untangle(out.o, out.n); // <-- too slow!
    out.o=untangled.a; out.n=untangled.b;
  } else {
    var maxOutputPair=0;
    for (i=0; i<out.n.length; ++i) {
      if ( out.n.row != null) {
	if( maxOutputPair > out.n.row ) {
	  // tangle - delete pairing
	  out.o.row ]=out.o.row ].text;
	  out.n=out.n.text;
	}
	if (maxOutputPair < out.n.row) { maxOutputPair = out.n.row; }
      }
    }
  }
  // output the stuff preceding the first paired old line
  for (i=0; i<out.o.length && out.o.text == null; ++i) acc.push( out.o );
  str += delFmt(acc); acc=;
  // main loop
  for ( i = 0; i < out.n.length; ++i ) {
    // output unpaired new "lines"
    while ( i < out.n.length && out.n.text == null ) { acc.push( out.n ); }
    str += insFmt(acc); acc=;
    if ( i < out.n.length ) { // this new "line" is paired with the (out.n.row)th old "line"
      str += out.n.text;
      // output unpaired old rows starting after this new line's partner
      var m = out.n.row + 1;
      while ( m < out.o.length && out.o.text == null ) { acc.push ( out.o ); }
      str += delFmt(acc); acc=;
    }
  }
  return str;
}
window.jsReservedProperties=RegExp('^((un)?watch|toString|eval)$');
window.diffBugAlerts='';
function diffBugAlert(word) {
  if (diffBugAlerts.indexOf(word+'\n')==-1) {
    diffBugAlerts+=word+'\n';
    alert('Bad word: '+word+'\n\nPlease report this bug.');
  }
}
function diff( o, n ) {
  var ns = {};
  var os = {};
  var i;
  // pass 1: make hashtable ns with new rows as keys
  for ( i = 0; i < n.length; i++ ) {
    if ( jsReservedProperties.test(n) ) { n += '<!-- -->'; }
    if ( ns ] == null ) {
      ns ] = { rows: , o: null };
    }
    try { ns ].rows.push( i ); } catch (err) { diffBugAlert(n); }
  }
  // pass 2: make hashtable os with old rows as keys
  for ( i = 0; i < o.length; i++ ) {
    if ( jsReservedProperties.test(o) ) { o += '<!-- -->'; }
    if ( os ] == null ) {
      os ] = { rows: , n: null };
    }
    try {os ].rows.push( i ); } catch (err) { diffBugAlert(n); }
  }
  // pass 3: pair unique new rows and matching unique old rows
  for ( i in ns ) {
    if ( ns.rows.length == 1 && typeof(os) != "undefined" && os.rows.length == 1 ) {
      n.rows ] = { text: n.rows ], row: os.rows };
      o.rows ] = { text: o.rows ], row: ns.rows };
    }
  }
  // pass 4: pair matching rows immediately following paired rows (not necessarily unique)
  for ( i = 0; i < n.length - 1; i++ ) {
    if ( n.text != null && n.text == null &&
	 n.row < o.length - 1 && o.row + 1 ].text == null &&
	 n == o.row + 1 ] ) {
      n = { text: n, row: n.row + 1 };
      o.row+1] = { text: o.row+1], row: i + 1 };
    }
  }
  // pass 5: pair matching rows immediately preceding paired rows (not necessarily unique)
  for ( i = n.length - 1; i > 0; i-- ) {
    if ( n.text != null && n.text == null &&
	 n.row > 0 && o.row - 1 ].text == null &&
	 n == o.row - 1 ] ) {
      n = { text: n, row: n.row - 1 };
      o.row-1] = { text: o.row-1], row: i - 1 };
    }
  }
  return { o: o, n: n };
}
// ENDFILE: diff.js
// STARTFILE: init.js
function setSiteInfo() {
  pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?)
  pg.wiki.lang = pg.wiki.hostname.split('.');
  pg.wiki.wikimedia=RegExp('wiki(edia|source|books|news|quote)\\.org|wiktionary\\.org').test(pg.wiki.hostname);
  pg.wiki.wikicites=RegExp('wikicitiescom$', 'i').test(pg.wiki.hostname);
  pg.wiki.isLocal=RegExp('^localhost').test(pg.wiki.hostname);
  pg.wiki.commons='commons.wikimedia.org';
}
function setTitleBase() {
  pg.wiki.articlePath='/';
  pg.wiki.botInterfacePath = '/wiki';
  if (pg.wiki.wikimedia) { pg.wiki.botInterfacePath = '/w' }
  else if (pg.wiki.wikicities) { pg.wiki.botInterfacePath = ''; }
  titletail =  pg.wiki.botInterfacePath + '/index.php?title=';
  // other sites may need to add code here to set titletail depending on how their urls work
  pg.wiki.titlebase = location.protocol + '//' + pg.wiki.hostname + titletail;
  pg.wiki.wikibase  = location.protocol + '//' + pg.wiki.hostname + pg.wiki.botInterfacePath;
}
//////////////////////////////////////////////////
// Global regexps
function setMainRegex() {
  var reStart='*://';
  var preTitles='wiki/|w/index\\.php\\?title=';
  if (!pg.wiki.wikimedia) {
    preTitles = 'wiki/index\\.php\\?title=|wiki/index\\.php/|' + preTitles + '|index\\.php\\?title=' ;
  }
  var reEnd='/(' + preTitles + ')(*)';
  pg.re.main = RegExp(reStart + pg.wiki.hostname.split('.').join('\\.') + reEnd);
}
function setRegexps() {
  setMainRegex();
  pg.re.stub= RegExp('stub|This .*-related article is a .*stub', 'im') ;
  pg.re.disambig=RegExp('(\\s*disambig|disambig\\s*|disamb\\s*|dab\\s*)' +
			'|\\s*(geo|hn)dis\\s*' + // explicit, whole template names on this line
			'|is a .*disambiguation.*page', 'im') ;
  pg.re.urlNoPopup=RegExp('((title=|/)' + pg.ns.special + ':|section=)') ;
  pg.re.contribs  =RegExp('(title=|/)'  + pg.ns.special + ':Contributions' + '(&target=|/|/' + pg.ns.user+':)(.*)') ;
  pg.re.email     =RegExp('(title=|/)'  + pg.ns.special + ':Emailuser'     + '(&target=|/|/' + pg.ns.user+':)(.*)') ;
  // note: tries to get images in infobox templates too, e.g. movie pages, album pages etc
  //                      (^|\]* ]) *
  //                      (^|\]* ])(]*(+) *px)?
  //                                                        $4 = 120 as in 120px
   pg.re.image = RegExp('(^|\\]* ])(]*(+) *px)?|\\n *? *(image|cover) *= *(*?) *? *\\n', 'img') ;
   pg.re.imageBracketCount = 6;
   pg.re.category = RegExp('\\]* ]) *', 'i');
   pg.re.categoryBracketCount = 1;
   pg.re.ipUser=RegExp('('+pg.ns.user+':)?' + '((25|2|1||)\\.){3}' +
		       '(25|2|1||)');
   // FIXME replace with general parameter parsing function, this is daft
   pg.re.oldid=RegExp('oldid=(*)');
   pg.re.diff=RegExp('diff=(*)');
}
//////////////////////////////////////////////////
// Image sources
function setImageSources() {
  pg.wiki.imageSources=;
  // frequently seen thumbs
  pg.wiki.imageSources.push(
  {wiki: pg.wiki.hostname, thumb: true,  width: 180}, // default
  {wiki: pg.wiki.hostname, thumb: true,  width: 120} // gallery
  );
  // frequently seen thumbs on commons
  if (pg.wiki.wikimedia && pg.wiki.hostname!=pg.wiki.commons) {
    pg.wiki.imageSources.push(
    {wiki: pg.wiki.commons, thumb: true,  width: 180},
    {wiki: pg.wiki.commons, thumb: true,  width: 120}
    );
  }
  // unusual thumb sizes and full-size
  pg.wiki.imageSources.push(
  {wiki: pg.wiki.hostname, thumb: true,  width: 200}, // common?
  {wiki: pg.wiki.hostname, thumb: true,  width: 250}, // common?
  {wiki: pg.wiki.hostname, thumb: true,  width: 300},
  {wiki: pg.wiki.hostname, thumb: true,  width: 210},
  {wiki: pg.wiki.hostname, thumb: true,  width: 230},
  {wiki: pg.wiki.hostname, thumb: false, width: 0} // no comma
  );
  // full-size on commons
  if (pg.wiki.wikimedia && pg.wiki.hostname!=pg.wiki.commons) {
    pg.wiki.imageSources.push({wiki: pg.wiki.commons, thumb: false, width: 0});
  }
}
//////////////////////////////////////////////////
// miscellany
function setMisc() {
  pg.current.link=null;
  // downloading images are put here
  pg.misc.imageArray=;
  // page caching
  pg.cache.pages = ;
  pg.cache.images = ;
  // FIXME what is this for?
  pg.misc.gImage=null; // global for image
  // check to see if images are done with this timer
  pg.timer.image=null;
  // These are for checkImages()
  pg.counter.checkImages=0;
  pg.timer.checkImages=null;
  pg.timer.checkPopupPosition=null;
  pg.counter.loop=0;
  // ids change with each popup: popupImage0, popupImage1 etc
  pg.idNumber=0;
  // for myDecodeURI
  pg.misc.decodeExtras = [
      {from: '%2C', to: ',' },
      {from: '_',   to: ' ' },
      {from: '%24', to: '$'},
      {from: '%26',   to: '&' } // no ,
  ];
  // for setPopupHTML - needed for timers and stuff
  pg.timer.popupHTML=;
  pg.misc.popupHTMLLoopFunctions = ;
  // FIXME - eliminate this
  pg.counter.redir=0;
}
function setBrowserHacks() {
  // browser-specific hacks
  if (typeof window.opera != 'undefined') {
    setDefault('popupStructure','original');
  } else if (navigator.appName=='Konqueror') {
    setDefault('popupNavLinkSeparator', ' &bull; ');
    pg.flag.isKonq=true;
  } else if ( navigator.vendor && navigator.vendor.toLowerCase().indexOf('apple computer')===0) {
    pg.flag.isSafari=true;
  } else if (navigator.appName.indexOf("Microsoft")!=-1) {
    setDefault('popupNavLinkSeparator', ' &#183; ');
    setDefault('popupStructure','original');
    pg.flag.isIE=true;
  }
  if (pg.flag.isIE || pg.flag.isKonq || pg.flag.isSafari) {
    pg.flag.linksLikeIE=true;
  }
}
function setupPopups() {
  // NB translatable strings should be set up first (strings.js)
  // basics
  setSiteInfo();
  setTitleBase();
  // namespaces etc
  setNamespaces();
  setInterwiki();
  // regexps
  setRegexps();
  setRedirs();
  // other stuff
  setImageSources();
  setOptions(); // see options.js
  setBrowserHacks();
  setMisc();
  setupDebugging();
  setupLivePreview();
  // main deal here
  setupTooltips();
  Navpopup.tracker.enable();
}
// ENDFILE: init.js
// STARTFILE: navlinks.js
//////////////////////////////////////////////////
// navlinks... let the fun begin
//
function defaultNavlinkSpec() {
  var str='';
  str += '<b><<mainlink|shortcut= >></b>';
  if (getValueOf('popupLastEditLink')) str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}';
  str+='<<imagestatus>>';
  // user links
  // contribs - log - count - email - block
  // count only if applicable; block only if popupAdminLinks
  str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>';
  str+='if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}';
  str+='if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}';
  // editing links
  // talkpage   -> edit|new - history - un|watch - article|edit
  // other page -> edit - history - un|watch - talk|edit|new
  var editstr='<<edit|shortcut=e>>';
  var editOldidStr='if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{' + editstr + '}'
  var historystr='<<history|shortcut=h>>if(mainspace){|<<editors|shortcut=E|>>}';
  var watchstr='<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
  str+='<br>if(talk){' +
    editOldidStr+'|<<new|shortcut=+>>' + '*' + historystr+'*'+watchstr + '*' + '<b><<article|shortcut=a>></b>|<<editArticle|edit>>'
    + '}else{' // not a talk page
    + editOldidStr + '*' + historystr + '*' + watchstr + '*' + '<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>'
    + '}';
  // misc links
  str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';
  // admin links
  str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>}';
  return str;
};
function navLinksHTML (article, hint, oldid) {
  var str = '<span class="popupNavLinks">';
  var style=getValueOf('popupNavLinkStyle');
  switch (style) {
  case 'default':
    str += defaultNavlinkSpec();
    break;
  default:
    if (typeof style == 'function') str += style();
    else str+=String(style);
  }
  str += '</span>';
  // BAM
  return navlinkStringToHTML(str, article, oldid);
};
function expandConditionalNavlinkString(s,article,oldid,recursionCount) {
  // nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out)
  if (typeof recursionCount!=typeof 0) recursionCount=0;
  var conditionalSplitRegex=RegExp(
   //(1     if    \\(    (2    2)    \\)      {(3    3)}  (4   else      {(5     5)}  4)1)
    '(;?\\s*if\\s*\\(\\s*(*)\\s*\\)\\s*\\{(*)\\}(\\s*else\\s*\\{(*?)\\}|))', 'i');
  var splitted=s.parenSplit(conditionalSplitRegex);
  // $1: whole conditional
  // $2: test condition
  // $3: true expansion
  // $4: else clause (possibly empty)
  // $5: false expansion (possibly null)
  var numParens=5;
  ret = splitted;
  for (var i=1; i<splitted.length; i=i+numParens+1) {
    var testString=splitted;
    var trueString=splitted;
    var falseString=splitted;
    if (typeof falseString=='undefined' || !falseString) falseString='';
    var testResult=null;
    switch (testString) {
    case 'user':
      testResult=(article.userName())?true:false;
      break;
    case 'talk':
      testResult=(article.talkPage())?false:true; // talkPage converts _articles_ to talkPages
      break;
    case 'admin':
      testResult=getValueOf('popupAdminLinks')?true:false;
      break;
    case 'oldid':
      testResult=(typeof oldid != 'undefined' && oldid)?true:false;
      break;
    case 'ipuser':
      testResult=(article.isIpUser())?true:false;
      break;
    case 'mainspace':
      testResult=isInMainNamespace(article);
      break;
    case 'wikimedia':
      testResult=(pg.wiki.wikimedia) ? true : false;
      break;
    }
    switch(testResult) {
    case null: ret+=splitted;  break;
    case true: ret+=trueString;   break;
    case false: ret+=falseString; break;
    }
    // append non-conditional string
    ret += splitted;
  }
  if (conditionalSplitRegex.test(ret) && recursionCount < 10)
    return expandConditionalNavlinkString(ret,article,oldid,recursionCount+1);
  else return ret;
};
function navlinkStringToArray(s, article, oldid) {
  s=expandConditionalNavlinkString(s,article,oldid);
  var splitted=s.parenSplit(RegExp('<<(.*?)>>'));
  var ret=;
  for (var i=0; i<splitted.length; ++i) {
    if (i%2) { // i odd, so s is a tag
      var t=new navlinkTag();
      var ss=splitted.split('|');
      t.id=ss;
      for (var j=1; j<ss.length; ++j) {
	var sss=ss.split('=');
	if (sss.length>1)
	  t]=sss;
	else // no assignment (no "="), so treat this as a title (overwriting the last one)
	  t.text=popupString(sss);
      }
      t.article=article;
      if (typeof oldid != 'undefined' && oldid != null) t.oldid=oldid;
      if (!t.text && t.id != 'mainlink') t.text=popupString(t.id);
      ret.push(t);
    }
    else { // plain HTML
      ret.push(splitted.split('*').join(getValueOf('popupNavLinkSeparator'))
	       .split('<line>').join('<span class="popup_menu_row">').split('</line>').join('</span>'));
    }
  }
  return ret;
};
// navlinkString: * becomes the separator
//                <<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz'
//                                      and visible text 'fubar'
//                if(test){...} and if(test){...}else{...} work too (nested ok)
function navlinkStringToHTML(s,article,oldid) {
  //limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article));
  var p=navlinkStringToArray(s,article,oldid);
  var html='';
  for (var i=0; i<p.length; ++i) {
    if (typeof p == typeof '') {
      html+=p;
    } else if (typeof p.type != 'undefined' && p.type=='navlinkTag') {
      html+=p.html();
    }
  }
  return html;
}
function navlinkTag() {this.type='navlinkTag';};
navlinkTag.prototype.html=function () {
  this.getNewWin();
  this.getPrintFunction();
  var html='';
  if (typeof this.print!='function') {
    errlog ('Oh dear - invalid print function for a navlinkTag, id='+this.id);
  } else {
    html=this.print(this);
    if (typeof html != typeof '') {html='';}
    else if (typeof this.shortcut!='undefined') html=addPopupShortcut(html, this.shortcut);
  }
  return '<span class="popup_'+this.id+'">'+html+'</span>';
};
navlinkTag.prototype.getNewWin=function() {
  getValueOf('popupLinksNewWindow');
  if (typeof pg.option.popupLinksNewWindow === 'undefined') this.newWin=null;
  this.newWin=pg.option.popupLinksNewWindow;
}
navlinkTag.prototype.getPrintFunction=function() { //think about this some more
  // this.id and this.article should already be defined
  if (typeof this.id!=typeof '' || typeof this.article!=typeof {} ) return;
  var html='';
  var a,t;
  switch (this.id) {
  case 'email':     case 'contribs':  case 'block':     case 'unblock':
  case 'userlog':   case 'userSpace':
    this.article=this.article.userName();
  }
  switch (this.id) {
  case 'userTalk': case 'newUserTalk': case 'editUserTalk':
  case 'userPage': case 'monobook': case 'editMonobook': case 'blocklog':
    delete this.oldid;
    this.article=this.article.userName(true);
  }
  if (this.id=='editMonobook' || this.id=='monobook') { this.article.append('/monobook.js'); }
  if (this.id != 'mainlink') {
    // FIXME anchor handling should be done differently with Title object
    // this.article=removeAnchor(this.article);
    // if (typeof this.text=='undefined') this.text=popupString(this.id);
  }
  switch (this.id) {
  case 'undelete':       this.print=specialLink; this.specialpage='Undelete'; this.sep='/'; break;
  case 'whatLinksHere':  this.print=specialLink; this.specialpage='Whatlinkshere'; break;
  case 'relatedChanges': this.print=specialLink; this.specialpage='Recentchangeslinked'; break;
  case 'move':           this.print=specialLink; this.specialpage='Movepage'; break;
  case 'contribs':       this.print=specialLink; this.specialpage='Contributions'; break;
  case 'email':          this.print=specialLink; this.specialpage='Emailuser'; break;
  case 'block':          this.print=specialLink; this.specialpage='Blockip'; this.sep='&ip='; break;
  case 'unblock':        this.print=specialLink; this.specialpage='Ipblocklist'; this.sep='&action=unblock&ip='; break;
  case 'userlog':        this.print=specialLink; this.specialpage='Log'; this.sep='&user='; break;
  case 'blocklog':       this.print=specialLink; this.specialpage='Log'; this.sep='&type=block&page='; break;
  case 'userSpace':      this.print=specialLink; this.specialpage='Prefixindex'; this.sep='&namespace=2&from='; break;
  case 'search':         this.print=specialLink; this.specialpage='Search'; this.sep='&fulltext=Search&search='; break;
  case 'history': case 'unwatch': case 'watch':
  case 'unprotect': case 'protect':
    this.print=wikiLink; this.action=this.id; break;
  case 'delete':
    this.print=wikiLink; this.action='delete';
    if (isImagePage(this.article)) {
      var img=this.article.stripNamespace();
      this.action+='&image='+img;
    }
  break;
  case 'edit': case 'view':
    this.print=wikiLink;
    delete this.oldid;
    this.action=this.id; break;
  case 'new':
    this.print=wikiLink; this.action='edit&section=new'; break;
  case 'mainlink':
    if (typeof this.text=='undefined') this.text=safeDecodeURI(this.article);
    if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) {
      var s=this.text.split('/'); this.text=s;
      if (this.text=='' && s.length > 1) this.text=s;
    }
    this.print=titledWikiLink;
    if (typeof this.title=='undefined' && pg.current.link && typeof pg.current.link.href != 'undefined') {
      this.title=safeDecodeURI((pg.current.link.originalTitle)?pg.current.link.originalTitle:this.article);
      if (typeof this.oldid != 'undefined' && this.oldid) {
	this.title=tprintf('Revision %s of %s', );
      }
    }
    this.action='view'; break;
  case 'userPage':
  case 'article':
  case 'monobook':
  case 'editMonobook':
  case 'editArticle':
  //alert(this.id+'\n'+this.article + '\n'+ typeof this.article);
    this.article=this.article.articleFromTalkOrArticle();
  //alert(this.id+'\n'+this.article + '\n'+ typeof this.article);
    this.print=wikiLink;
    this.action='view'; break;
    if (this.id.indexOf('edit')==0) {
      this.action='edit';
    } else { this.action='view';}
    break;
  case 'userTalk':
  case 'talk':
    this.article=this.article.talkPage();
    this.print=wikiLink;
    this.action='view'; break;
  case 'arin':
    this.print=arinLink; break;
  case 'count':
    this.print=kateLink; break;
  case 'google':
    this.print=googleLink; break;
  case 'contribsTree':
    this.print=contribsTreeLink; break
  case 'editors':
    this.print=editorListLink; break;
  case 'globalsearch':
    this.print=globalSearchLink; break;
  case 'lastEdit':
    this.print=titledDiffLink;
    this.title=popupString('Show the last edit');
    this.from='prev'; this.to='cur'; break;
  case 'oldEdit':
    this.print=titledDiffLink;
    this.title=popupString('Show the edit made to get revision') + ' ' + this.oldid;
    this.from='prev'; this.to=this.oldid; break;
  case 'editOld':
    this.print=wikiLink; this.action='edit'; break;
  case 'revert':
    this.print=wikiLink; this.action='revert'; break;
  case 'nullEdit':
    this.print=wikiLink; this.action='nullEdit'; break;
  case 'diffCur':
    this.print=titledDiffLink;
    this.title=tprintf('Show changes since revision %s', );
    this.from=this.oldid; this.to='cur'; break;
  case 'editUserTalk':
  case 'editTalk':
    this.article=this.article.talkPage();
    this.action='edit'; this.print=wikiLink; break;
  case 'newUserTalk':
  case 'newTalk':
    this.article=this.article.talkPage();
    this.action='edit&section=new'; this.print=wikiLink; break;
  case 'imagestatus':
    this.print=function () { return emptySpanHTML('popupImageStatus', pg.idNumber); }
    break;
  case 'lastContrib':
  case 'sinceMe':
    this.print=magicHistoryLink;
  break;
  default:
    this.print=function () {return 'Unknown navlink type: '+this.id+''};
  }
};
//
//  end navlinks
//////////////////////////////////////////////////
// ENDFILE: navlinks.js
// STARTFILE: shortcutkeys.js
function popupHandleKeypress(evt) {
  var keyCode = window.event ? window.event.keyCode : ( evt.keyCode ? evt.keyCode : evt.which);
  if (!keyCode || !pg.current.link || !pg.current.link.navpopup) { return; }
  if (keyCode==27) { // escape
    killPopup();
    return false; // swallow keypress
  }
  var letter=String.fromCharCode(keyCode);
  var links=pg.current.link.navpopup.mainDiv.getElementsByTagName('A');
  var startLink=0;
  var i,j;
  if (popupHandleKeypress.lastPopupLinkSelected) {
    for (i=0; i<links.length; ++i) {
      if (links==popupHandleKeypress.lastPopupLinkSelected) startLink=i;
    }
  }
  for (j=0; j<links.length; ++j) {
    i=(startLink + j + 1) % links.length;
    if (links.getAttribute('popupkey')==letter) {
      if (evt && evt.preventDefault) evt.preventDefault();
      links.focus();
      popupHandleKeypress.lastPopupLinkSelected=links;
      return false; // swallow keypress
    }
  }
  // pass keypress on
  if (document.oldPopupOnkeypress) return document.oldPopupOnkeypress(evt);
  else return true;
};
function addPopupShortcuts() {
  if (document.onkeypress && document.onkeypress.toString()==popupHandleKeypress.toString()) return;
  document.oldPopupOnkeypress=document.onkeypress;
  document.onkeypress=popupHandleKeypress;
};
function rmPopupShortcuts() {
  popupHandleKeypress.lastPopupLinkSelected=null;
  try {
    if (document.oldPopupOnkeypress && document.oldPopupOnkeypress.toString()==popupHandleKeypress.toString()) {
      // panic
      document.onkeypress=null; //function () {};
      return;
    }
    document.onkeypress=document.oldPopupOnkeypress;
  } catch (nasties) { /* IE goes here */ }
};
function addLinkProperty(html, property) {
  // take "<a href=...>...</a> and add a property
  // not sophisticated at all, easily broken
  var i=html.indexOf('>');
  if (i<0) return html;
  return html.substring(0,i) + ' ' + property + html.substring(i);
};
function addPopupShortcut(html, key) {
  if (!getValueOf('popupShortcutKeys')) return html;
  var ret= addLinkProperty(html, 'popupkey="'+key+'"');
  if (key==' ') key=popupString('spacebar');
  return ret.replace(RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'),'$1$2$3 $4');
};
// ENDFILE: shortcutkeys.js
// STARTFILE: diffpreview.js
function loadDiff(article, oldid, diff) {
  pg.diffData={}; // FIXME this is very unreliable; should be done on a per-popup basis
  var oldRev, newRev;
  switch (diff) {
  case 'cur':
    if (  oldid===null || oldid=='' ) {
      // eg newmessages diff link
      oldRev='0&direction=prev';
      newRev=0;
    } else {
      oldRev = oldid;
      newRev = 0;
    }
    break;
  case 'prev':
    oldRev = ( oldid || 0 ) + '&direction=prev'; newRev = oldid; break;
  case 'next':
    oldRev = oldid; newRev = oldid + '&direction=next';
    break;
  default:
    oldRev = oldid || 0; newRev = diff || 0; break;
  }
  oldRev = oldRev || 0;
  newRev = newRev || 0;
  getWiki(article, doneDiffNew, newRev);
  getWiki(article, doneDiffOld, oldRev);
}
function doneDiffNew(download) {
  if (download.id != pg.idNumber) { return; }
  pg.diffData.New=download;
  if (pg.diffData.Old && pg.diffData.Old.id == pg.idNumber) { insertDiff(); }
}
function doneDiffOld(download) {
  if (download.id != pg.idNumber) { return; }
  pg.diffData.Old=download;
  if (pg.diffData.New && pg.diffData.New.id == pg.idNumber) { insertDiff(); }
}
function rmBoringLines(a,b,context) {
  if (typeof context == 'undefined') { context=2; }
  // this is fairly slow... i think it's quicker than doing a word-based diff from the off, though
  var aa=, aaa=;
  var bb=, bbb=;
  var i, j;
  // first, gather all disconnected nodes in a and all crossing nodes in a and b
  for (i=0; i<a.length; ++i ) {
    if(!a.row || a.row===0) { aa=1; }
    else if (countCrossings(b,a,i, true)) {
      aa=1;
      bb.row ] = 1;
    }
  }
  // pick up remaining disconnected nodes in b
  for (i=0; i<b.length; ++i ) {
    if (bb==1) { continue; }
    if(!b.row || b.row===0) { bb=1; }
  }
  // another pass to gather context: we want the neighbours of included nodes which are not yet included
  // we have to add in partners of these nodes, but we don't want to add context for *those* nodes in the next pass
  for (i=0; i<b.length; ++i) {
    if ( bb == 1 ) {
      for (j=max(0,i-context); j < min(b.length, i+context); ++j) {
	if ( !bb ) { bb = 1; aa.row ] = 0.5; }
      }
    }
  }
  for (i=0; i<a.length; ++i) {
    if ( aa == 1 ) {
      for (j=max(0,i-context); j < min(a.length, i+context); ++j) {
	if ( !aa ) { aa = 1; bb.row ] = 0.5; }
      }
    }
  }
  for (i=0; i<bb.length; ++i) {
    if (bb > 0) { // it's a row we need
      if (b.row || b.row===0) { bbb.push(b.text); } // joined; partner should be in aa
      else {
	bbb.push(b);
      }
    }
  }
  for (i=0; i<aa.length; ++i) {
    if (aa > 0) { // it's a row we need
      if (a.row || a.row===0) { aaa.push(a.text); } // joined; partner should be in aa
      else {
	aaa.push(a);
      }
    }
  }
  return { a: aaa, b: bbb};
}
function stripOuterCommonLines(a,b,context) {
  var i=0;
  while (i<a.length && i < b.length && a==b) { ++i; }
  var j=a.length-1; var k=b.length-1;
  while ( j>=0 && k>=0 && a==b ) { --j; --k; }
  return { a: a.slice(max(0,i - 1 - context), min(a.length+1, j + context+1)),
	   b: b.slice(max(0,i - 1 - context), min(b.length+1, k + context+1)) };
}
function insertDiff() {
  // for speed reasons, we first do a line-based diff, discard stuff that seems boring, then do a word-based diff
  // FIXME: sometimes this gives misleading diffs as distant chunks are squashed together
  var oldlines=pg.diffData.Old.data.split('\n');
  var newlines=pg.diffData.New.data.split('\n');
  getValueOf('popupDiffContextLines');
  var inner=stripOuterCommonLines(oldlines,newlines,pg.option.popupDiffContextLines);
  oldlines=inner.a; newlines=inner.b;
  var truncated=false;
  getValueOf('popupDiffMaxLines');
  if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) {
    // truncate
    truncated=true;
    inner=stripOuterCommonLines(oldlines.slice(0,pg.option.popupDiffMaxLines),
				    newlines.slice(0,pg.option.popupDiffMaxLines),
				    pg.option.popupDiffContextLines);
    oldlines=inner.a; newlines=inner.b;
  }
  var lineDiff=diff(oldlines, newlines);
  var lines2=rmBoringLines(lineDiff.o, lineDiff.n);
  var oldlines2=lines2.a; var newlines2=lines2.b;
  var simpleSplit = (String.prototype.parenSplit.toString().indexOf('native code')==-1);
  var html='<hr>';
  try {
    if (getValueOf('popupDiffDates')) {
      html+='<table class="popup_diff_dates">';
      html += '<tr><td>' + tprintf('New revision') + '</td><td>' + pg.diffData.New.lastModified.toLocaleString() + '</td></tr>';
      html += '<tr><td>' + tprintf('Old revision') + '</td><td>' + pg.diffData.Old.lastModified.toLocaleString() + '</td></tr>';
      html += '</table><hr>';
    }
  } catch (cockup) {
    // nothing here - maybe the download failed or something. anyway, not too fussed.
  }
  html += shortenDiffString(
		      diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit),
		      getValueOf('popupDiffContextCharacters') ).join('<hr>');
  setPopupHTML(html.split('\n').join('<br>') +
	       (truncated ? '<hr><b>'+popupString('Diff truncated for performance reasons')+'</b>' : '') ,
	       'popupPreview');
}
// ENDFILE: diffpreview.js
// STARTFILE: links.js
/////////////////////
// LINK GENERATION //
/////////////////////
// titledDiffLink --> titledWikiLink --> generalLink
// wikiLink       --> titledWikiLink --> generalLink
// kateLink --> generalLink
function titledDiffLink(l) { // article, text, title, from, to) {
  return titledWikiLink({article: l.article, action: l.to + '&oldid=' + l.from,
				    newWin: l.newWin,
				    text: l.text, title: l.title,
				    /* hack: no oldid here */
				    actionName: 'diff'});
};
window.wikiLink=function(l) {
  //{article:article, action:action, text:text, oldid}) {
  if (! (typeof l.article == typeof {}
	 && typeof l.action == typeof '' && typeof l.text==typeof '')) return null;
  if (typeof l.oldid == 'undefined') l.oldid=null;
  if (l.action!='edit' && l.action!='view' && l.action != 'revert') l.oldid=null;
  var hint=popupString(l.action + 'Hint');
  switch (l.action) {
  case 'edit&section=new': hint = popupString('newSectionHint');  break;
  case 'revert':
    l.action='edit&autoclick=wpSave&autosummary=' + revertSummary(l.oldid);
    if (getValueOf('popupRevertSummaryPrompt')) { l.action += '&autosummaryprompt=true'; }
    break;
  case 'nullEdit':
    l.action='edit&autoclick=wpSave&autosummary=null';
    break;
  }
  if (hint) {
    if (l.oldid) {
      hint = simplePrintf(hint, )]);
    }
    else {
      hint = simplePrintf(hint, );
    }
  }
  else hint = safeDecodeURI(l.article + '&action=' + l.action)
	 + (l.oldid) ? '&oldid='+l.oldid : '';
  return titledWikiLink({article: l.article, action: l.action, text: l.text, newWin:l.newWin,
				    title: hint, oldid: l.oldid});
};
function revertSummary(oldid) {
  var historyPage=(document.title.split(' - ') === popupString('History'));
  if (historyPage) {
    var links=document.links;
    var numlinks=links.length;
    var date=null, editor=null;
    for (var i=0; i<numlinks-1; ++i) {
      if (RegExp('oldid='+oldid).test(links.href)
	  && RegExp('^{2}:{2},.*{3}$').test(links.innerHTML)) {
	date=links.innerHTML;
	editor=Title.fromURL(links.href).userName();
	break;
      }
    }
    if (date && editor) {
      return simplePrintf(getValueOf('popupExtendedRevertSummary'), );
    }
  }
  return simplePrintf(getValueOf('popupRevertSummary'), );
}
function titledWikiLink(l) {
  // possible properties of argument:
  // article, action, text, title, oldid, actionName, className
  // oldid = null is fine here
  // article and action are mandatory args
  if (typeof l.article == 'undefined' || typeof l.action=='undefined') {
    errlog('got undefined article or actino in titledWikiLink');
    return null;
  }
  var base = pg.wiki.titlebase +  l.article.urlString();
  var url=base;
  if (typeof l.actionName=='undefined' || !l.actionName) { l.actionName='action'; }
  // no need to add &action=view, and this confuses anchors
  if (l.action != 'view') { url = base + '&' + l.actionName + '=' + l.action; }
  if (typeof l.oldid!='undefined' && l.oldid) { url+='&oldid='+l.oldid; }
  var cssClass=pg.misc.defaultNavlinkClassname;
  if (typeof l.className!='undefined' && l.className) { cssClass=l.className; }
  return generalNavLink({url: url, newWin: l.newWin,
			     title: (typeof l.title != 'undefined') ? l.title : null,
			     text: (typeof l.text!='undefined')?l.text:null,
			     className: cssClass});
};
function getLastContrib(wikipage, newWin) {
  getHistoryInfo(wikipage, function(x){processLastContribInfo(x,{page: wikipage, newWin: newWin})});
}
function processLastContribInfo(info, stuff) {
  if(!info.edits || !info.edits.length) { alert('Popups: an odd thing happened. Please retry.'); return; }
  if(!info.firstNewEditor) {
    alert(tprintf('Only found one editor: %s made %s edits', .editor,info.edits.length]));
    return;
  }
  var newUrl=pg.wiki.titlebase + stuff.page + '&diff=cur&oldid='+info.firstNewEditor.oldid;
  displayUrl(newUrl, stuff.newWin);
}
function getDiffSinceMyEdit(wikipage, newWin) {
  getHistoryInfo(wikipage, function(x){processDiffSinceMyEdit(x,{page: wikipage, newWin: newWin})});
}
function processDiffSinceMyEdit(info, stuff) {
  if(!info.edits || !info.edits.length) { alert('Popups: something fishy happened. Please try again.'); return; }
  var friendlyName=stuff.page.split('_').join(' ');
  if(!info.myLastEdit) {
    alert(tprintf('Couldn\'t find an edit by %s\nin the last %s edits to\n%s',
		  ));
    return;
  }
  if(info.myLastEdit.index==0) {
    alert(tprintf("%s seems to be the last editor to the page %s", ));
    return;
  }
  var newUrl=pg.wiki.titlebase + stuff.page + '&diff=cur&oldid='+ info.myLastEdit.oldid;
  displayUrl(newUrl, stuff.newWin);
}
function displayUrl(url, newWin){
  if(newWin) window.open(url);
  else document.location=url;
}
function magicHistoryLink(l) {
  // FIXME use onclick change href trick to sort this out instead of window.open
  var jsUrl='', title='';
  switch(l.id) {
  case 'lastContrib':
    jsUrl=simplePrintf('javascript:getLastContrib(\'%s\', %s)', );
    title=popupString('lastContribHint');
    break;
  case 'sinceMe':
    jsUrl=simplePrintf('javascript:getDiffSinceMyEdit(\'%s\', %s)', );
    title=popupString('sinceMeHint');
    break;
  }
  return generalNavLink({url: jsUrl, newWin: false, // can't have new windows with JS links, I think
			     title: title,
			     text: l.text});
}
function specialLink(l) {
  // properties: article, specialpage, text, sep
  if (typeof l.specialpage=='undefined'||!l.specialpage) return null;
  var base = pg.wiki.titlebase +  pg.ns.special+':'+l.specialpage;
  if (typeof l.sep == 'undefined' || l.sep===null) l.sep='&target=';
  var article=l.article.urlString();
  var hint=popupString(l.specialpage+'Hint');
  switch (l.specialpage) {
  case 'Log': hint=(l.sep=='&user=') ? popupString('userLogHint') : popupString('blockLogHint'); break;
  case 'Search':  article=l.article.toString(); break;
  }
  if (hint) hint = simplePrintf(hint, );
  else hint = safeDecodeURI(l.specialpage+':'+l.article) ;
  var url = base + l.sep + article;
  return generalNavLink({url: url, title: hint, text: l.text, newWin:l.newWin});
};
function generalLink(l) {
  // l.url, l.text, l.title, l.newWin, l.className
  if (typeof l.url=='undefined') return null;
  // only quotation marks in the url can screw us up now... I think
  var url=l.url.split('"').join('%22');
  var ret='<a href="' + url + '"';
  if (typeof l.title!='undefined' && l.title) ret += ' title="' + l.title + '"';
  var newWin;
  if (typeof l.newWin=='undefined' || l.newWin===null) newWin=getValueOf('popupNewWindows');
  else newWin=l.newWin;
  if (newWin) ret += ' target="_blank"';
  if (typeof l.className!='undefined'&&l.className) ret+=' class="'+l.className+'"';
  ret += '>';
  if (typeof l.text==typeof '') ret+= l.text;
  ret +='</a>';
  return ret;
}
function appendParamsToLink(linkstr, params) {
  var sp=linkstr.parenSplit(RegExp('(href="+?)"', 'i'));
  if (sp.length<2) return null;
  var ret=sp.shift() + sp.shift();
  ret += '&' + params + '"';
  ret += sp.join('');
 return ret;
};
function changeLinkTargetLink(x) { // newTarget, text, hint, summary, clickButton, minor) {
  if (x.newTarget) {
    log ('changeLinkTargetLink: newTarget=' + x.newTarget);
  }
  // optional: oldTarget (in wikitext)
  // if x.newTarget omitted or null, remove the link
  // escape '&' and other nasties
  if(x.newTarget) {
    x.newTarget=encodeURI(x.newTarget);
    log('changeLinkTargetLink: newTarget encoded to ' + x.newTarget);
  }
  //x.text=encodeURI(x.text);  // this buggers things up on zh.wikipedia.org and doesn't seem necessary
  x.clickButton=encodeURI(x.clickButton);
  // this'll break if charAt(0) is nasty
  if (typeof x.oldTarget != typeof '') x.oldTarget=safeDecodeURI(pg.current.article);
  var cA=literalizeRegex(x.oldTarget);
  var chs=cA.charAt(0).toUpperCase();
  chs='';
  var currentArticleRegexBit=chs+cA.substring(1);
  currentArticleRegexBit=currentArticleRegexBit
    .split(RegExp('+', 'g')).join('+')
    .split('\\(').join('(?:%2528|\\()')
    .split('\\)').join('(?:%2529|\\))');
  currentArticleRegexBit = '\\s*(' + currentArticleRegexBit + ')\\s*';
  // e.g. Computer (archaic) -> \s*(omputer(?:%2528|\()archaic(?:%2528|\)))\s*
  // autoedit=s~\ad)\]\]~]~g;s~\AD)~[[Computer-aided%20design|~g
  // get the page to edit from the title
  try {
    //var title=document.getElementsByTagName('h1').innerHTML.replace(RegExp(' ', 'g'), '_');
    var title=document.title.split(' - ');
    title='';
    title=title.join(' - ').replace(/ - $/, '');
  } catch (err) { return; }
  var lk=titledWikiLink({article: new Title(title), newWin:x.newWin,
				    action:  'edit',
				    text:    x.text,
				    title:   x.hint,
				    className: 'popup_change_title_link'
				    });
  var cmd='';
  if (x.newTarget) {
    cmd +='s~\\\\]~]~g;';
    cmd +=  's~\\~[['+x.newTarget+'|~g';
  } else {
    cmd += 's~\\\\]~$1~g;';
    cmd += 's~\\(.*?)\\]\\]~$2~g';
  }
  cmd += '&autoclick='+x.clickButton;
  cmd += ( x.minor == null ) ? '' : '&autominor='+x.minor;
  cmd += ( x.watch == null ) ? '' : '&autowatch='+x.watch;
  cmd += '&autosummary='+x.summary;
  return appendParamsToLink(lk, 'autoedit='+cmd);
}
function redirLink(redirMatch) {
  // NB redirMatch is in wikiText
  var ret='';
  if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) {
    ret += '<hr>';
    if (getValueOf('popupFixRedirs') && typeof autoEdit != 'undefined' && autoEdit) {
      log('redirLink: newTarget=' + redirMatch);
      ret += addPopupShortcut(
			      changeLinkTargetLink({newTarget: redirMatch, text: popupString('Redirects'), hint: popupString('Fix this redirect'),
						   summary: simplePrintf(getValueOf('popupFixRedirsSummary'),
							  ),
						   clickButton: getValueOf('popupRedirAutoClick'), minor: true,
						   watch: getValueOf('popupWatchRedirredPages')})
			      , 'R');
      ret += popupString(' to ');
    }
    else ret += popupString('Redirects') + popupString(' to ');
    return ret;
  }
  else return '<br> ' + popupString('Redirects') + popupString(' to ') +
	 titledWikiLink({article: new Title().fromWikiText(redirMatch), action: 'view',  /* FIXME: newWin */
			text: safeDecodeURI(redirMatch), title: popupString('Bypass redirect')});
}
function arinLink(l) {
  if (!saneLinkCheck(l)) { return null; }
  if ( ! l.article.isIpUser() || ! pg.wiki.wikimedia) return null;
  var uN=safeDecodeURI(l.article.userName());
  return generalNavLink({url:'http://ws.arin.net/cgi-bin/whois.pl?queryinput=' + uN, newWin:l.newWin,
			    title: tprintf('Look up %s in ARIN whois database', ),
			    text: l.text});
}
function toolDbName() {
  var ret=null;
  var theWiki=pg.wiki.hostname.split('.');
  switch(theWiki) {
  case 'wikipedia':
    ret = pg.wiki.lang + 'wiki';
    break;
  default:
    ret = theWiki;
    break;
  }
  ret+= '_p';
  return ret;
}
function saneLinkCheck(l) {
  if (typeof l.article != typeof {} || typeof l.text != typeof '') { return false; }
  return true;
}
function kateLink(l) {
  if(!saneLinkCheck(l)) return null;
  if (! pg.wiki.wikimedia) return null;
  var uN=safeDecodeURI(l.article.userName());
  var url='http://tools.wikimedia.de/~' + getValueOf('popupEditCounterTool') + '/cgi-bin/count_edits?dbname=';
  url += toolDbName() + '&user=' + uN;
  return generalNavLink({url:url, title: tprintf('katelinkHint', ), newWin:l.newWin, text: l.text});
};
function contribsTreeLink(l) {
  if(!saneLinkCheck(l)) return null;
  if (! pg.wiki.wikimedia) return null;
  var uN=safeDecodeURI(l.article.userName());
  var url='http://tools.wikimedia.de/~interiot/cgi-bin/contribution_tree?dbname=';
  url += toolDbName() + '&user='+ uN;
  return generalNavLink({url:url, title: tprintf('contribsTreeHint', ), newWin:l.newWin, text: l.text});
}
function globalSearchLink(l) {
  if(!saneLinkCheck(l)) return null;
  var base='http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search=';
  var article=safeDecodeURI(l.article);
  return generalNavLink({url:base + article, newWin:l.newWin,
			    title: tprintf('globalSearchHint', ),
			    text: l.text});
}
function googleLink(l) {
  if(!saneLinkCheck(l)) return null;
  var base='http://www.google.com/search?q=';
  var article=safeDecodeURI(l.article);
  return generalNavLink({url:base + '%22' + article + '%22', newWin:l.newWin,
			    title: tprintf('googleSearchHint', ),
			    text: l.text});
}
function editorListLink(l) {
  if(!saneLinkCheck(l)) return null;
  var article= safeDecodeURI(l.article.articleFromTalkPage() || l.article);
  var base='http://tools.wikimedia.de/~tim/counter/?page=';
  return generalNavLink({url:base+article, title: tprintf('editorListHint', ), newWin:l.newWin, text: l.text});
}
function generalNavLink(l) {
  l.className = (l.className==null) ? 'popupNavLink' : l.className;
  return generalLink(l);
}
//////////////////////////////////////////////////
// magic history links
//
window.getHistoryInfo=function(wikipage, whatNext) {
  getHistory(wikipage, whatNext ? function(d){whatNext(processHistory(d));} : processHistory);
}
window.getHistory=function(wikipage, onComplete) {
  var url = pg.wiki.titlebase + removeAnchor(wikipage) + '&action=history' + '&limit=' + getValueOf('popupHistoryLimit');
  return startDownload(url, pg.idNumber+'history', onComplete);
}
window.processHistory=function(download) {
  // screen scrape alert
  var histInfo={};
  var d=download.data;
  // pg.misc.data=d; // for debugging
  var edits=;
  var split=d.split('<li>');
  for (var i=0; i<split.length; ++i) {
    var match=RegExp('^.*?type="radio" value="(*)".*?class=\'history-user\'><a href="(/User:|/search/title=(Special:Contributions&amp;target=|User:))(*)').exec(split);
    if (match) {
      edits.push({ oldid: match, editor: match });
    }
  }
  histInfo.edits=edits;
  var userName=getValueOf('popupUserName') || Cookie.read('enwikiUserName').split('+').join('_');
  histInfo.userName=userName;
  for (var i=0; i<edits.length; ++i) {
    if (typeof histInfo.myLastEdit == 'undefined' && userName && edits.editor==userName)
      histInfo.myLastEdit={index: i, oldid: edits.oldid, previd: (i==0 ? null : edits.oldid)} ;
    if (typeof histInfo.firstNewEditor == 'undefined' && edits.editor != edits.editor)
      histInfo.firstNewEditor={index:i, oldid:edits.oldid, previd: (i==0 ? null : edits.oldid)};
  }
  //pg.misc.historyInfo=histInfo;
  return histInfo;
}
// ENDFILE: links.js
// STARTFILE: options.js
//////////////////////////////////////////////////
// options
// check for cookies and existing value, else use default
function defaultize(x) {
  var val=null;
  if (x!='popupCookies') {
    defaultize('popupCookies');
    if (pg.option.popupCookies && (val=Cookie.read(x))) {
      pg.option=val;
      return;
    }
  }
  if (pg.option===null) {
    if (typeof window != 'undefined' ) pg.option=window;
    else pg.option=pg.optionDefault;
  }
};
function newOption(x, def) {
  if (typeof pg.option=='undefined') {
    pg.option=null;
  }
  pg.optionDefault=def;
};
function setDefault(x, def) { return newOption(x, def); }
function getValueOf(varName) {
  defaultize(varName);
  return pg.option;
}
function setOptions() {
  // user-settable parameters and defaults
  // Basic options
  newOption('popupDelay',               0.5);
  newOption('simplePopups',             false);
  newOption('popupStructure',           'menus');   // see later - default for popupStructure is 'original' if simplePopups is true
  newOption('popupActionsMenu',         true);
  newOption('popupAdminLinks',          false);
  newOption('popupShortcutKeys',        false);
  newOption('popupDragging',            true);
  newOption('popupHistoricalLinks',     true);
  newOption('popupOnlyArticleLinks',    true);
  newOption('removeTitles',             true);
  newOption('popupMaxWidth',            350);
  newOption('popupInitialWidth',        false); // integer or false
  newOption('popupSimplifyMainLink',    true);
  newOption('popupAppendRedirNavLinks', true);
  newOption('popupTocLinks',            false);
  newOption('popupSubpopups',           true);
  // images
  newOption('popupImages',                 true);
  newOption('imagePopupsForImages',        true);
  newOption('popupNeverGetThumbs',         false);
  newOption('popupImagesFromThisWikiOnly', false);
  newOption('popupMinImageWidth',          50);
  newOption('popupLoadImagesSequentially', false);
  newOption('popupImagesToggleSize',       true);
  newOption('popupImageSize',              60);
  // redirs, dabs, reversion
  newOption('popupFixRedirs',             false);
  newOption('popupRedirAutoClick',        'wpDiff');
  newOption('popupFixDabs',               false);
  newOption('popupRevertSummaryPrompt',   false);
  newOption('popupRedlinkRemoval',        false);
  newOption('popupWatchDisambiggedPages', null);
  newOption('popupWatchRedirredPages',    null);
  // navlinks
  newOption('popupNavLinks',          true);
  newOption('popupNavLinkStyle',      'default');   // FIXME what's this do?
  newOption('popupNavLinkSeparator',  ' &sdot; ');
  newOption('popupLastEditLink',      true);
  newOption('popupEditCounterTool',   'interiot');
  newOption('popupExtraUserMenu',     '');
  // previews etc
  newOption('popupPreviews',             true);
  newOption('popupSummaryData',          true);
  newOption('popupMaxPreviewSentences',  4);
  newOption('popupMaxPreviewCharacters', 600);
  newOption('popupLastModified',         true);
  newOption('popupPreviewKillTemplates', true);
  newOption('popupPreviewRawTemplates',  false);
  newOption('popupPreviewFirstParOnly',  true);
  // diffs
  newOption('popupPreviewDiffs',          true);
  newOption('popupDiffMaxLines',          100);
  newOption('popupDiffContextLines',      2);
  newOption('popupDiffContextCharacters', 40);
  newOption('popupDiffDates',             true);
  // edit summaries
  newOption('popupFixDabsSummary',           popupString('defaultpopupFixDabsSummary') );
  newOption('popupExtendedRevertSummary',    popupString('defaultpopupExtendedRevertSummary') );
  newOption('popupRevertSummary',            popupString('defaultpopupRevertSummary') );
  newOption('popupFixRedirsSummary',         popupString('defaultpopupFixRedirsSummary') );
  newOption('popupRedlinkSummary',           popupString('defaultpopupRedlinkSummary') );
  newOption('popupRmDabLinkSummary',         popupString('defaultpopupRmDabLinkSummary') );
  // misc
  newOption('popupLiveOptions',         false);
  newOption('popupLiveOptionsExpanded', false);
  newOption('popupCookies',             false);
  newOption('popupUnsimplifyLink',      false);
  newOption('popupHistoryLimit',        50);
  newOption('popupFilters',             [popupFilterStubDetect,     popupFilterDisambigDetect,
					 popupFilterPageSize,       popupFilterCountLinks,
					 popupFilterCountImages,    popupFilterCountCategories,
					 popupFilterLastModified]);
  newOption('extraPopupFilters',        );
  newOption('popupOnEditSelection',     true);
  // new windows
  newOption('popupNewWindows',     false);
  newOption('popupLinksNewWindow', {'lastContrib': true, 'sinceMe': true});
}
// ENDFILE: options.js
// STARTFILE: strings.js
//////////////////////////////////////////////////
// Translatable strings
//////////////////////////////////////////////////
//
// Notes for translators
// ---------------------
//
// If there's a string that I've not included below, please drop a
// note at ].
//
// These strings can be changed if they're impossible to translate to
// more flexible versions.  Just ask.
//
// New translation method: copy the pg.string block below, translate
// the strings on the right and change the line
//
// pg.string = {
//
// into
//
// popupStrings = {
//
// Save this file on your wiki and load that file '''as well as'''
// popups.js or popupsdev.js from your user javascript file.
pg.string = {
  /////////////////////////////////////
  // summary data, searching etc.
  /////////////////////////////////////
  '#': '#',
  'article': 'article',
  'category': 'category',
  'categories': 'categories',
  'image': 'image',
  'images': 'images',
  'stub': 'stub',
  'Empty page': 'Empty page',
  'kB': 'kB',
  'bytes': 'bytes',
  'day': 'day',
  'days': 'days',
  'hour': 'hour',
  'hours': 'hours',
  'minute': 'minute',
  'minutes': 'minutes',
  'second': 'second',
  'seconds': 'seconds',
  'week': 'week',
  'weeks': 'weeks',
  'search': 'search',
  'SearchHint': 'Find English Misplaced Pages articles containing %s',
  'web': 'web',
  'global': 'global',
  'globalSearchHint': 'Search across Wikipedias in different languages for %s',
  'google': 'google',
  'googleSearchHint': 'Google for %s',
  /////////////////////////////////////
  // article-related actions and info
  // (some actions also apply to user pages)
  /////////////////////////////////////
  'actions': 'actions',         ///// view articles and view talk
  'space': 'space',
  'spacebar': 'space',
  'view article': 'view article',
  'viewHint': 'Go to %s',
  'talk': 'talk',
  'talk page': 'talk page',
  'this&nbsp;revision': 'this&nbsp;revision',
  'revision %s of %s': 'revision %s of %s',
  'Revision %s of %s': 'Revision %s of %s',
  'Toggle image size': 'Toggle image size',
  'del': 'del',                 ///// delete, protect, move
  'delete': 'delete',
  'deleteHint': 'Delete %s',
  'undeleteShort': 'un',
  'UndeleteHint': 'Show the deletion history for %s',
  'protect': 'protect',
  'protectHint': 'Restrict editing rights to %s',
  'unprotectShort': 'un',
  'unprotectHint': 'Allow %s to be edited by anyone again',
  'move': 'move',
  'move page': 'move page',
  'MovepageHint': 'Change the title of %s',
  'edit': 'edit',               ///// edit articles and talk
  'edit article': 'edit article',
  'editHint': 'Change the content of %s',
  'edit talk': 'edit talk',
  'new': 'new',
  'new topic': 'new topic',
  'newSectionHint': 'Start a new section on %s',
  'null edit': 'null edit',
  'nullEditHint': 'Submit an edit to %s, making no changes ',
  'hist': 'hist',               ///// history, diffs, editors, related
  'history': 'history',
  'History': 'History', // what appears in the titles of history pages
  'historyHint': 'List the changes made to %s',
  'last': 'last',
  'lastEdit': 'lastEdit',
  'show last edit': 'most recent edit',
  'Show the last edit': 'Show the effects of the most recent change',
  'lastContrib': 'lastContrib',
  'last set of edits': 'latest edits',
  'lastContribHint': 'Show the net effect of changes made by the last editor',
  'cur': 'cur',
  'diffCur': 'diffCur',
  'Show changes since revision %s': 'Show changes since revision %s',
  'Diff truncated for performance reasons': 'Diff truncated for performance reasons',
  'old': 'old',
  'oldEdit': 'oldEdit',
  'Show the edit made to get revision': 'Show the edit made to get revision',
  'sinceMe': 'sinceMe',
  'changes since mine': 'diff my edit',
  'sinceMeHint': 'Show changes since my last edit',
  'Couldn\'t find an edit by %s\nin the last %s edits to\n%s': 'Couldn\'t find an edit by %s\nin the last %s edits to\n%s',
  'eds': 'eds',
  'editors': 'editors',
  'editorListHint': 'List the users who have edited %s',
  'related': 'related',
  'relatedChanges': 'relatedChanges',
  'related changes': 'related changes',
  'RecentchangeslinkedHint': 'Show changes in articles related to %s',
  'editOld': 'editOld',          ///// edit old version, or revert
  'rv': 'rv',
  'revert': 'revert',
  'revertHint': 'Revert to %s',
  'defaultpopupRedlinkSummary': 'Removing link to empty page ] using ]',
  'defaultpopupFixDabsSummary': 'Disambiguate ] to ] using ]',
  'defaultpopupFixRedirsSummary': 'Redirect bypass from ] to ] using ]',
  'defaultpopupExtendedRevertSummary': 'Revert to revision dated %s by %s, oldid %s using ]',
  'defaultpopupRevertSummary': 'Revert to revision %s using ]',
  'defaultpopupRmDabLinkSummary': 'Remove link to dab page ] using ]',
  'Redirects': 'Redirects', // as in Redirects to ...
  ' to ': ' to ',           // as in Redirects to ...
  'Bypass redirect': 'Bypass redirect',
  'Fix this redirect': 'Fix this redirect',
  'disambig': 'disambig',          ///// add or remove dab etc.
  'disambigHint': 'Disambiguate this link to ]',
  'Click to disambiguate this link to:': 'Click to disambiguate this link to:',
  'remove this link': 'remove this link',
  'remove all links to this page from this article': 'remove all links to this page from this article',
  'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article',
  'mainlink': 'mainlink',          ///// links, watch, unwatch
  'wikiLinks': 'wikiLinks',
  'links here': 'links here',
  'whatLinksHere': 'whatLinksHere',
  'what links here': 'what links here',
  'WhatlinkshereHint': 'List the pages that are hyperlinked to %s',
  'unwatchShort': 'un',
  'watchThingy': 'watch',  // called watchThingy because {}.watch is a function
  'watchHint': 'Add %s to my watchlist',
  'unwatchHint': 'Remove %s from my watchlist',
  /////////////////////////////////////
  // user-related actions and info
  /////////////////////////////////////
  'user': 'user',               ///// user page, talk, email, space
  'user page': 'user page',
  'user talk': 'user talk',
  'edit user talk': 'edit user talk',
  'leave comment': 'leave comment',
  'email': 'email',
  'email user': 'email user',
  'EmailuserHint': 'Send an email to %s',
  'space': 'userspace',
  'PrefixindexHint': 'Show pages in the userspace of %s',
  'count': 'count',             ///// contributions, tree, log
  'edit counter': 'edit counter',
  'katelinkHint': 'Count the countributions made by %s',
  'contribs': 'contribs',
  'contributions': 'contributions',
  'ContributionsHint': 'List the contributions made by %s',
  'tree': 'tree',
  'contribsTree': 'contribsTree',
  'contribsTreeHint': 'Explore %s\'s contributions by namespace and by article',
  'log': 'log',
  'user log': 'user log',
  'userLogHint': 'Show %s\'s user log',
  'arin': 'ARIN lookup',             ///// ARIN lookup, block user or IP
  'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database',
  'unblockShort': 'un',
  'block': 'block',
  'block user': 'block user',
  'IpblocklistHint': 'Unblock %s',
  'BlockipHint': 'Prevent %s from editing',
  'block log': 'block log',
  'blockLogHint': 'Show the block log for %s',
  /////////////////////////////////////
  // Popups setup
  /////////////////////////////////////
  'Display navigation links at the top of the popup': 'Display navigation links at the top of the popup',
  'Download preview data': 'Download preview data from the Misplaced Pages servers',
  'Load images': 'Load images',
  'Never download extra stuff for images/previews': 'Never download extra stuff for images/previews',
  'Only start downloading when told to do so': 'Only start downloading when told to do so',
  'Open full-size image': 'Open full-size image',
  'Preview only on click': 'Preview only on click',
  'Show/hide options': 'Show/hide options',
  'Show image previews': 'Show image previews',
  'Show navigation links': 'Show navigation links',
  'Show page summary data': 'Show page summary data',
  'Show previews': 'Show previews',
  'Show summary data': 'Show summary data',
  'Show text previews': 'Show text previews',
  'Simple popups': 'Simple popups',
  'Toggle this option': 'Toggle this option',
  'cookies': 'cookies',
  'Use cookies to store popups options': 'Use cookies to store popups options',
  'zxy': 'zxy'
};
function popupString(str) {
  if (typeof popupStrings != 'undefined' && popupStrings && popupStrings) return popupStrings;
  if (pg.string) return pg.string;
  return str;
}
function simplePrintf(str, subs) {
  if (!subs) return str;
  var ret=;
  var s=str.split('%s');
  var i=0;
  do {
    if (i >= subs.length) { ret.push(s.join('%s')); break; }
    ret.push(s.shift());
    if (s.length == 0) break;
    ret.push(subs);
    ++i;
  } while (s.length > 0);
  return ret.join('');
}
function tprintf(str,subs) {
  if (typeof subs != typeof ) { subs = ; }
  return simplePrintf(popupString(str), subs);
}
// ENDFILE: strings.js</pre>