// <nowiki>
/*
* smartLinking.js
* Ver. 2015-10-05
*
* A tool for linking articles and previewing the linked pages
* while source-editing Wikipedia/MediaWiki articles.
*
* Home: http://en.wikipedia.org/wiki/User:V111P/js/Smart_Linking
*
* requires: msgDisplay.js and wikiParserV.js,
* both of which are auto loaded from this script and can also be
* found here: http://en.wikipedia.org/wiki/User:V111P/js
*
* CC0 Public Domain Dedication:
* http://creativecommons.org/publicdomain/zero/1.0/
* If you use large parts of this code, please let me know.
* You should also let others know where the code originates:
* http://en.wikipedia.org/wiki/User:V111P/js/smartLinking.js
* Thanks.
*/
window.smartLinking = (function ($) {
"use strict";
var articleNamesCapitalized = true; // always true for Wikipedia, but not for Wiktionary
var whitelistedTags = 'sup, sub, i, b, br, em, strong, tt, kbd'; // del, ins, u, mark, s, strike
var maxAgeSeconds = {
forArticles: 5, sForArticles: 1,
forOtherMeanings: 300, sForOtherMeanings: 60
};
var allPossibleOtherMeaningTemplates = false; // for testing - for checking for other templates
var linkFocusTimeoutId; // used in the function focusFn
var msgs = {
scriptName: 'Smart Linking',
noValidLink: 'No valid link was selected or focused.',
error: 'Error',
help: 'Help',
openInNewWin: 'Open in a new window',
editInNewWin: 'Edit in a new window',
editIntro: 'Edit the introduction',
history: 'History',
talk: 'Talk',
watch: 'Watch',
unwatch: 'Unwatch',
disambigPage: 'Disambiguation page',
nonExistingPage: 'Non-existing page',
errorLoadingScript: 'Error loading required script {required script}.',
backTo: 'Back to [[%1]]', // Back to [[<the previous page's title>]]
relatedArticles: 'Related articles',
tableOfContents: 'Table of contents',
tocAndOtherAndMainArticles: 'ToC and other meanings and main articles:',
backToTop: 'Back to top',
focusTextarea: 'Focus the textarea', // title attr of the button that collapses the display
close: 'Close',
toggleSectionLinks: 'Toggle displaying the section links',
aSpecialNSLink: 'A link to a page in the Special namespace.',
aLinkToSecInCurrEdPg: 'A link to a section in the page you are currently editing.',
errorOnLoading: 'Could not load page. Check your Internet connection.',
unsupportedBrowser: 'Unsupported browser'
};
var locale = {
helpUrl: '//en.wikipedia.org/wiki/User:V111P/js/Smart_Linking',
// en.wikipedia.org/wiki/Category:Disambiguation_and_redirection_templates
otherMeaningTemplateNames: ['about', 'hatnote', 'rellink', 'other uses(\\d| of)?',
'(two|three) other uses', 'see', 'see also\\d?', 'also', 'main( list)?', 'details\\d',
'for\\d?', 'redirect(-synonym|text|-distinguish)?\\d?\\d?', 'further\\d?',
'consider disambiguation', 'other people\\d?', 'other places\\d?', 'other hurricanes',
'other ships', 'distinguish\\d?', 'elect',
'year dab', 'more information'],
// not needed if disambig pages/templates have the __DISAMBIG__ magic word:
// disambigTemplateNames: ['dab', 'disamb(ig)?'],
disambigPgSuffix: ' (disambiguation)', // used to give link to that page
// in case other-meanings template is used on page
anchorTemplateNames: ['anchor']
};
var requiredScripts = [{
objLocation: mediaWiki.libs,
objName: 'msgDisplay', // [[User:V111P/js/msgDisplay.js]]
url: '//en.wikipedia.org/w/index.php?title='
+ 'User:V111P/js/msgDisplay.js&action=raw'
+ '&ctype=text/javascript&smaxage=3600&maxage=3600'
}, {
objLocation: mediaWiki.libs,
objName: 'wikiParserV', // [[User:V111P/js/wikiParserV.js]]
url: '//en.wikipedia.org/w/index.php?title='
+ 'User:V111P/js/wikiParserV.js&action=raw'
+ '&ctype=text/javascript&smaxage=3600&maxage=3600'
}];
var commonsUrl = '//upload.wikimedia.org/wikipedia/commons/';
var buttonIconUrl = commonsUrl + '9/96/Interpage_icon.png';
var buttonIconUrlClassic = commonsUrl + '5/5a/Interpage_button.png';
var imageProps = { // text messages updated in init()
attentionImg: {
src: commonsUrl + 'thumb/b/ba/Symbol_opinion_vote.svg/30px-Symbol_opinion_vote.svg.png',
alt: '!',
width: 15,
height: 15,
css: {width: '1.2em', height: '1.2em'}
},
editImg: {
src: commonsUrl + '3/31/WG.Icon.edit.png',
width: 13,
height: 13,
css: {width: '1em', height: '1em'}
},
anchorImg: { // not currently used
src: commonsUrl + 'thumb/6/62/Anchor_pictogram.svg/26px-Anchor_pictogram.svg.png',
width: 12,
height: 12,
alt: '@',
css: {width: '1em', height: '1em'}
},
newWinImg: {
src: commonsUrl
+ '/thumb/c/c1/Inkscape_icons_window_new.svg/26px-Inkscape_icons_window_new.svg.png',
width: 12,
height: 12,
alt: 'new window',
css: {width: '1em', height: '1em'}
},
externalLinkImg: { // not currently used
src: commonsUrl + 'thumb/4/44/Icon_External_Link.svg/26px-Icon_External_Link.svg.png',
width: 12,
height: 12,
css: {width: '1em', height: '1em'}
},
relatedImg: {
src: commonsUrl + '7/72/OSM_relation.png',
width: 12,
height: 12,
css: {width: '1em', height: '1em'}
},
tocImg: {
src: commonsUrl + 'f/f7/Plan.png',
width: 12,
height: 12,
css: {width: '1em', height: '1em'}
},
upImg: {
src: commonsUrl + '5/56/Icon_Arrow_Up_26x26.png',
width: 12,
height: 12,
css: {width: '1em', height: '1em'}
},
okImg: {
src: commonsUrl + 'thumb/a/ac/Approve_icon.svg/26px-Approve_icon.svg.png',
width: 12,
height: 12,
alt: '[_]',
css: {width: '1em', height: '1em'}
}
};
var images = {}; // set in setup() and init()
var toolbarButton = {
id: 'smartLinkingButton',
tooltip: msgs.scriptName, // text messages updated in init()
section: 'main',
group: 'insert',
callback: smartLinkingFn,
iconUrl: buttonIconUrl // updated in init()
};
var miscStyle = {
noDataReceivedLinkColor: '#900', // the link at the beginning that opens the article in a new window
visitedPgLinkColor: '#9370db',
notExistingPgLinkColor: '#f08080',
normalPgLinkColor: 'blue'
};
var re = {
// en:Wikipedia:Page_name#Invalid_page_names :
// ASCII 0–31 (dec), the del char 127, the Unicode replacement character U+FFFD
wikiLinkIllegalChars: /[\[\]{}<>|\u0000-\u001f\u007f\ufffd]/,
// "." or "..", or beginning "./" or "../", or containing "/./" or "/../" or 3 or more continuous tildes ~,
// or ending "/." or "/..", or exceeding 255 bytes in length (at least for ANSI names)
wikiLinkIllegalNames:
/^(\.\.?\/|\s|_|.{256,})|^(\.\.?|:)$|(\/\.\.?\/|~~\~)|(\s|\_|\/\.\.?)$/,
trimBracketsG: /^\[\[|]]$/g,
addrSectionPart: /#.*/, // includes the # // re.addrTitlePart = /(?:(?!#).)*/;
titleDisabigPart: /\s\([^)]+\)$/,
redirect: /^(#[^\[]{1,25}\[\[)([^|\]]+)(\|.*|\]\][\S\s]*)/,
textBeforeFirstBullet: /\n\*[\S\s]*/,
oneOrMoreEmptyLinesG: /([^\S\n]*\n){2,}/g,
divideSectionHeading: /^(=+)([\S\s]+)\1$/,
otherMeaningNoTemplG: /(^|=|})([^\S\n]*\n)+:[^\n]+\n/g,
otherMeaningNoTemplTrimG: /(^|\n):\s*('')?|('')?\s*$/g,
wikiLinkAddrAndLabelG: /\[\[([^\]|]+)\|?([^\]]*)]]/g, // linkifyText
wikiLinkAddr: /^\[\[([^|\]]*)/, // insertLink()
linkAddrsIn$1G: /\[\[([^|\]]+)\|[^\]]*\]\]/g,
wikiLinkRemoveOpeningBracketsAndUpToAndInclPipe: /^\[\[(.+?\|)?/, // insertLink(),
possibleOtherMeaningTemplatesG: /^\{\{[^}|\n]+\|[^}\n]+}}[^\S\n]*$/gm,
splitTemplateParamsG: /(?:[^|[{}]|\[[^\]]*]|\{\{[^}]*}}|\{[^{]|}[^}])+|(?=\|\|)/g,
pipeTemplateToEndOfParam: /{{!}}[^|}]+/,
nAsteriskG: /\n\*/g, nHashSymbolG: /\n#/g, nSemicolonG: /\n;/g, nColonG: /\n:/g, nG: /\n/g
};
// time to wait for a required script to load before error msg:
var moduleLoadingTimeout = 15000;
var wikiParser; // = wikiParser - loaded in another .js file
var display; // = msgDisplay - loaded in another .js file
var visitedPages = {}; // '+' for visited/loaded pages, '' for visited non-existing pages
var pgHistory = []; // page pgHistory for the << button
var lastLinkAddr = ''; // last inserted or found addr in the textarea.
var linkStartPos = -1; // need to save it for IE, and also if user clicks outside of link
// in the textarea and then wants to continue browsing
var sectIdPrefix = 'smrtL_section_' + Math.ceil(Math.random() * 9999) + '_'; // not really needed
// unless in the future two smartLinking windows can exist at the same time on the page
var textarea;
// prints a message to the console, and if it's an error message, to the display too
function prt(msg, isErrorMsg) {
if (typeof msg != 'object') {
msg = (isErrorMsg ? msgs.error + ': ' : '') + msg;
if (isErrorMsg && display) {
if (images.attention)
display.append(images.attention.clone().attr('title', msgs.error));
display.appendTextWrite(' ' + msg);
}
msg = msgs.scriptName + ': ' + msg;
}
if (isErrorMsg && window.console && console.error)
console.error(msg);
else if (window.console && console.log)
console.log(msg);
} // prt
function setup() {
images.attention = $('<img/>', imageProps.attentionImg);
images.edit = $('<img/>', imageProps.editImg);
images.open = $('<img/>', imageProps.newWinImg);
images.related = $('<img/>', imageProps.relatedImg);
images.toc = $('<img/>', imageProps.tocImg);
images.up = $('<img/>', imageProps.upImg);
images.ok = $('<img/>', imageProps.okImg);
var c = window.smartLinkingConfig || {};
$.extend(msgs, c.msgs || {});
// save the Special namespace names
locale.specialNsPrefixes = [];
$.each(mw.config.get('wgNamespaceIds'), function (key, val) {
if (val == '-1') { // 'special'
if ($.inArray(key, locale.specialNsPrefixes) == -1)
locale.specialNsPrefixes.push(key);
}
});
init();
} // setup
// public function, to be called after updating the config object
function init() {
var c = window.smartLinkingConfig;
if (c) {
$.extend(locale, c.locale || {});
$.extend(msgs, c.msgs || {});
allPossibleOtherMeaningTemplates = c.allPossibleOtherMeaningTemplates
|| allPossibleOtherMeaningTemplates;
}
if (locale.disambigTemplateNames)
locale.disambigTemplateNameRegEx
= new RegExp('\\{\\{\\s*(' + locale.disambigTemplateNames.join('|')
+ ')\\s*(\\||})', 'i');
locale.otherMeaningTemplateNamesRegExG
= new RegExp('\\{\\{\\s*(' + locale.otherMeaningTemplateNames.join('|')
+ ')\\s*(\\|[^}{]*|([^}{]*\\{\\{[^}]+}}[^}{]*)*)}}', 'gi');
locale.anchorTemplateRegExG = new RegExp('\\{\\{((?:'
+ locale.anchorTemplateNames.join('|') + ')\\|[\\S\\s]+?)}}', 'gi');
} // init
// get and set the text and selection in the textarea
function valParts($el, textBefore, selText, textAfter) {
if (typeof textBefore == 'string') {
$el.val(textBefore + selText + textAfter);
var beforeLen = textBefore.length;
$el.textSelection('setSelection', {
'start': beforeLen,
'end': beforeLen + selText.length
});
return;
}
else {
var s = $el.textSelection('getCaretPosition', {startAndEnd: true} );
var text = $el.val().replace(/\r/g, '');
return [text.slice(0, s[0]), text.slice(s[0], s[1]), text.slice(s[1])];
}
}; // valParts
function correctLinking(justLoadScripts) {
function allLoaded() {
// check for regexp support
if ('<a><bd</e></b>'.replace(/<(?!\/?(a|b)>)/g, '<') != '<a><bd</e></b>') {
correctLinking = function () { prt('** ' + msg.unsupportedBrowser + ' **', true); }
return;
}
textarea = $('#wpTextbox1');
textarea.off('focus.smartLinking');
textarea.on('focus.smartLinking', function () {
display && display.collapse();
});
display = display || mediaWiki.libs.msgDisplay('edit');
display.config(window.smartLinkingConfig);
correctLinking = correctLinkingNow;
wikiParser = wikiParser || mediaWiki.libs.wikiParserV;
if (!justLoadScripts)
correctLinking();
}
function waitToExecute(again) {
var TimeoutToExecute = 250; // milliseconds
var notExecuted = null;
$.each(requiredScripts, function (index, val) {
if (!val.objLocation[val.objName]) {
// script not executed yet?
notExecuted = val.objName;
return false; // break
}
});
if (notExecuted) {
if (!again) {
errorOnLoadingModule(notExecuted);
return;
}
setTimeout(function () {
waitToExecute(again - 1);
},
TimeoutToExecute);
}
else
allLoaded();
}
function errorOnLoadingModule(scriptName) {
var msg = msgs.errorLoadingScript.replace('{required script}', scriptName);
prt(msg, true);
}
function loadNext(n) {
var callback;
if (n == requiredScripts.length - 1) {
callback = waitToExecute;
}
else
callback = function () { loadNext(n + 1); };
var reqScr = requiredScripts[n];
if (reqScr.objLocation[reqScr.objName])
callback(); // this one already loaded
else {
$.ajax({
url: reqScr.url,
dataType: 'script',
cache: true,
success: callback
});
setTimeout(function () {
if (!reqScr.objLocation[reqScr.objName])
errorOnLoadingModule(reqScr.objName);
}, moduleLoadingTimeout); // jQuery's fail callback is not called
// for cross-domain scripts, so can't use it.
}
}
if (!window.wikEd || !wikEd.useWikEd) {
// hopefully it was already loaded or will download while downloading the other scripts:
mw.loader.using('jquery.textSelection');
}
else if (wikEd.config && wikEd.config.offHook
&& typeof wikEd.config.offHook.push == 'function') {
wikEd.config.offHook.push(function () {
mw.loader.using('jquery.textSelection');
});
}
if (requiredScripts.length > 0)
loadNext(0);
else
allLoaded();
} // correctLinking
// points to correctLinking after the required scripts are loaded
function correctLinkingNow() {
if (window.wikEd && wikEd.useWikEd) {
wikEdCorrectLinking();
return;
}
var before, linkText, after;
var parts = valParts(textarea);
var focusLink = wikiParser.focusedSegment(parts, 'wikilink');
linkStartPos = -1;
if (focusLink) {
before = focusLink[0];
linkText = focusLink[1];
after = focusLink[2];
linkStartPos = before.length;
}
else {
linkText = parts[1];
// if some text was selected and it does not contain illegal chars:
if (linkText) {
// separate the beginning and ending spaces
before = parts[0];
after = parts[2];
var spaces = linkText.match(/^\s*/)[0];
before += spaces;
linkText = linkText.slice(spaces.length);
spaces = linkText.match(/\s*$/)[0];
after = spaces + after;
(spaces.length > 0) && (linkText = linkText.slice(0, -spaces.length));
linkText = '[[' + linkText + ']]';
linkStartPos = before.length;
}
}
focusTextarea();
var page = linkText.split('|')[0].replace(re.trimBracketsG, ''); // ^\[\[|]]$/g
if (page && !re.wikiLinkIllegalChars.test(page)
&& !re.wikiLinkIllegalNames.test(page)) {
valParts(textarea, before + linkText, '', after);
var histL = pgHistory.length;
if (pgHistory[histL - 1] === page)
pgHistory.length = histL - 1;
else
pgHistory = []; // clear the back button pgHistory
lastLinkAddr = page;
loadAndDisplay(page);
linkStartPos = before.length;
}
else
noValidLinkError();
} // correctLinkingNow
function noValidLinkError() {
clearDisplay();
display.append(images.attention.attr('title', msgs.noValidLink))
.appendText(' ' + msgs.noValidLink).write();
}
function wikEdCorrectLinking() {
var sel = wikEd.frameWindow.getSelection().getRangeAt(0).toString();
var before = sel.match(/^\s*/)[0];
var after = sel.match(/\s*$/)[0];
var hasTextAfterTheBar = false;
var page = sel = $.trim(sel);
var hasBracketsBefore = /^\[\[/.test(sel);
var hasBracketsAfter = /\]\]$/.test(sel);
if (hasBracketsBefore && hasBracketsAfter) {
sel = sel.slice(2, -2);
before += '[[';
after = ']]' + after;
var barLen = (sel.match(/\|.*/) || [''])[0].length;
page = (barLen > 0) ? sel.slice(0, -barLen) : sel;
}
if (page && !re.wikiLinkIllegalChars.test(page)
&& !re.wikiLinkIllegalNames.test(page))
{
//if (!hasBracketsBefore)
// wikEd.FrameExecCommand('inserthtml',
// before + '[[' + page + ']]' + after);
var histL = pgHistory.length;
if (pgHistory[histL - 1] === page)
pgHistory.length = histL - 1;
else
pgHistory = []; // clear the back button pgHistory
lastLinkAddr = page;
loadAndDisplay(page);
wikEd.frameWindow.focus();
}
else
noValidLinkError();
wikEd.frameWindow.focus();
} // wikEdCorrectLinking
function clearDisplay() {
display.show().keypress(keyPressed).helpUrl(locale.helpUrl)
.onCloseOnUserAction(function () {
textarea.off('focus.smartLinking');
});
}
function loadAndDisplay(articleTitle, noRedir) {
// is it a link to a section in the currently displayed page?
if (articleTitle.charAt(0) == '#') {
clearDisplay();
addBackLink();
display.appendTextWrite(articleTitle + ' - ' + msgs.aLinkToSecInCurrEdPg);
return;
}
// is it a link to a special page?
var pgNs = articleTitle.split(':')[0];
if (pgNs != articleTitle && $.inArray(pgNs.toLowerCase(), locale.specialNsPrefixes) > -1) {
clearDisplay();
addBackLink();
display.appendTextWrite(articleTitle + ' - ' + msgs.aSpecialNSLink);
return;
}
$.ajax({
url: '/w/api.php?action=query&prop=revisions|pageprops&rvprop=content'
+ '&ppprop=disambiguation&format=json&smaxage=' + maxAgeSeconds.sForArticles
+ '&maxage=' + maxAgeSeconds.forArticles
+ '&titles=' + encodeURIComponent(articleTitle),
dataType: 'json',
success: function (result) {
receiveArticle(result, articleTitle, noRedir);
},
error: function () {
clearDisplay();
prt(msgs.errorOnLoading, true);
}
});
} // loadAndDisplay
function receiveArticle(result, articleTitle, noRedir) {
var data, isDisambig;
var section = (articleTitle.match(re.addrSectionPart) || [''])[0]; // #.*
var norm = result.query.normalized;
if (norm && norm[0].from == articleTitle)
articleTitle = norm[0].to + section;
var pageN, p, pages = result.query.pages;
if (typeof pages == 'undefined') {
clearDisplay();
addBackLink();
return;
}
else if (pages[-1]) {
data = '';
}
else {
for (p in pages)
(pages.hasOwnProperty(p)) && (pageN = p);
var page = pages[pageN];
var data = page.revisions[0]['*'];
isDisambig = page.pageprops && page.pageprops.disambiguation === '';
}
processAndDisplayArticle(data, articleTitle, noRedir, isDisambig);
} // receiveArticle
function addBackLink(currArticleTitle) {
var histL = pgHistory.length;
if (histL == 0)
return;
var prevArticleTitle = pgHistory[histL - 1];
if (typeof currArticleTitle != 'undefined'
&& prevArticleTitle === currArticleTitle) {
if (histL == 1)
return;
else
prevArticleTitle = pgHistory[histL - 2];
}
var bkLinkTitle = msgs.backTo.replace(/%1/, prevArticleTitle);
var bkLink = $('<a/>', {
text: '<<',
href: '# ' + bkLinkTitle,
tabindex: 0,
css: {cursor: 'pointer'},
title: bkLinkTitle,
click: function (e) {
e.preventDefault();
// if curr pg is not in arr, prev pg is removed, but it will be re-added
pgHistory.length = histL - 1;
insertLink(prevArticleTitle);
loadAndDisplay(prevArticleTitle, true);
},
keypress: keyPressed
});
display.appendText('(')
.append(bkLink)
.appendText(') ')
.write();
display.focus(bkLink);
} // addBackLink
function processAndDisplayArticle(data, articleTitle, noRedir, isDisambigPg) {
var maxTextLength = 2000;
var formatUrl = wikiParser.formatUrl;
var fullText = true; // whether to show the whole article or only maxTextLength chars of it
var otherMeaningTemplates = [];
var otherMeaningPages = [];
var articleUrl; // = formatUrl(articleTitle);
var disambigLinks;
var redir; // is the page a redirect?
var section; // the part after # in articleTitle
var sectionHeadingIdsNoPrefix = [];
var sections = [];
var noData = (data.length == 0); // article does not exist
var $topMenu; // the menu span with the buttons after the article title at the beginning
function shorten(text, proportionOfMax) {
var origText = text;
text = text.slice(0, maxTextLength * (proportionOfMax || 1));
var lastLinkStart = text.lastIndexOf('[[');
if (lastLinkStart > text.lastIndexOf(']]'))
text = text.slice(0, lastLinkStart);
var lastSpace = text.lastIndexOf(' ');
if (text.length < origText.length && lastSpace > text.length - 20)
text = text.slice(0, lastSpace);
(text.length < origText.length) && (text = text + ' ...');
return text;
} // shorten
function addTocAndOtherMeaningLinks(otherMeaningTemplates, sectionHeadingIds, articleTitle) {
var sectionHeadingsNum = sectionHeadingIds.length;
var $mainDiv = $('<div/>', {'class': 'smrtL_tocAndOtherDiv'});
var $div1;
var $div = $div1 = $('<div/>');
$mainDiv.append('<br/><br/>')
.append($('<span/>', {
text: msgs.tocAndOtherAndMainArticles,
css: {color: '#050', fontWeight: 'bold', textDecoration: 'underline'}
}));
// back-to-top button
$mainDiv.append($('<a/>', {
href: '# ' + msgs.backToTop,
'class': 'smrtL_tocHeadingLink',
title: msgs.backToTop,
css: {margin: '0 2px'},
click: function (e) {
e.preventDefault();
var target = display.find('.smrtL_topTocLink');
if (target.length == 0)
target = display.find('.smrtL_otherAndMainArticlesLink');
display.scrollTo(target, true);
},
keypress: keyPressed
})
.append(images.up));
// show/hide the section links button
if (sectionHeadingIds.length > 0) {
$mainDiv.append($('<a/>', {
href: '# ' + msgs.toggleSectionLinks,
title: msgs.toggleSectionLinks,
css: {margin: '0 2px'},
click: function (e) {
e.preventDefault();
$('.smrtL_tocAndOtherDiv div.smrtL_tocLinkDiv').toggle();
display.scrollTo('.smrtL_tocHeadingLink', false);
},
keypress: keyPressed
})
.append(images.toc.clone()));
}
$mainDiv.append('<br/>');
var text = function (str) { return document.createTextNode(str); }
var pgsArr = [];
var $div_;
var $span;
for (var j = 0; j < otherMeaningTemplates.length; j++) {
if (otherMeaningTemplates[j] == '----') {
$div_ = $div;
$div = $('<div/>', {css: {'background-color': 'gray'}});
continue;
}
if (otherMeaningTemplates[j] == '----/') {
$div_.append($div);
$div = $div_;
continue;
}
if (otherMeaningTemplates[j].charAt(0) == '=') {
var tocLinkId = sectionHeadingIds.shift();
$div.append('<div class="smrtL_tocLinkDiv">'
+ '<a href="#" id="' + sectIdPrefix + tocLinkId
+ 'Link" class="smrtL_tocLink smrtL_' + tocLinkId + 'Link" '
+ 'style="color:green; font-weight:bold;">'
+ otherMeaningTemplates[j] + '</a></div>');
continue;
}
var spl = otherMeaningTemplates[j].slice(2, -2)
.match(re.splitTemplateParamsG); // split on |, except within links & templates:
// (?:[^|[{}]|\[[^\]]*]|\{\{[^}]*}}|\{[^{]|}[^}])+|(?=\|\|)/g
$span = $('<span/>', {'class': 'smrtL_otherMeaningOrMainTempl'});
$span.append(text('{{' + spl[0]));
for (var k = 1; k < spl.length; k++) {
var param = $.trim(spl[k]);
$span.append(text(' | '));
if (param === '')
continue;
if (param.indexOf('[[') > -1) {
param = param.replace(re.linkAddrsIn$1G, '[[$1]]'); // \[\[([^|\]]+)\|[^\]]*\]\]/g
param = wikiParser.removeElements(param, 'bold/italic');
var linkifyObj = linkifyText(param, articleTitle, true);
pgsArr.push.apply(pgsArr, linkifyObj.pageNames);
$span.append(linkifyObj.$collection);
}
else {
var p = param.split('=');
if (p.length > 1) {
$span.append(text($.trim(p[0]) + ' = '));
}
var paramVal = p[1] || param;
if (paramVal !== articleTitle) {
pgsArr.push(paramVal);
$span.append(browsableLink(paramVal));
}
else
$span.append(paramVal);
}
}
$span.append(text('}}')).append('<br/>');
$div.append($span);
}
// check for some additional titles, but only if at least some other-meaning
// templates exist on the page (some such templates auto-add a link to a disambig page)
if (otherMeaningTemplates.length - sectionHeadingsNum > 0) {
var altPages = [];
// auto add link to the same page name without text in parentheses at the end
var titleNoDisamb = articleTitle.replace(re.titleDisabigPart, ''); // \s\([^)]+\)$
if (titleNoDisamb != articleTitle) {
altPages.push(titleNoDisamb);
}
// auto add link to the title with disambig suffix
// or, if this page already has that suffix, add the title without it:
if (locale.disambigPgSuffix !== '') {
var suff = locale.disambigPgSuffix;
var suffIndex = articleTitle.indexOf(suff);
// only if this page does not itself have the disambig suffix:
if (suffIndex == -1 // doesn't have it
|| suffIndex != articleTitle.length - suff.length) // doesn't have it at the end
altPages.push(articleTitle + suff);
else { // remove the disamb suffix from end
var withoutDisambSuffix = articleTitle.slice(0, -suff.length);
if (withoutDisambSuffix != titleNoDisamb)
altPages.push(withoutDisambSuffix);
}
}
$.each(altPages, function (i, val) {
if ($.inArray(val, pgsArr) == -1) {
$div1.prepend(
$('<span/>', {'class': 'smrtL_otherMeaningOrMainTempl'})
.append(browsableLink(val)).append('<br/>')
);
pgsArr.push(val);
}
});
}
$mainDiv.append($div1).append($div).append('<hr/>');
display.append($mainDiv);
return pgsArr;
} // addTocAndOtherMeaningLinks
function processStr(str) {
str = $.trim(str);
str = wikiParser.escCharsForNowikiTags(str);
str = str.replace(locale.anchorTemplateRegExG, '<$1>'); // {{(anchor\|[\S\s]+?)}}/g
str = wikiParser.removeElements(str,
'tables, files, references, templates, behavior switches, others');
// wikiCode bold and italic to html ('' to <i>, ''' to <b>):
str = str.replace(/<(anchor\|[\S\s]+?)>/g, '{{$1}}');
str = wikiParser.boldAndItalicToHtml(str);
// rem all tags except the whitelisted ones:
try {str = wikiParser.sanitizeHtml(str, whitelistedTags);}
catch (e) {
if (typeof e != 'number') throw e;
prt('Error while html-sanitizing the page. Error code: ' + e, true);
return '';
}
return $.trim(str);
} // processStr
section = (articleTitle.match(re.addrSectionPart) || [''])[0]; // #.*
if (section !== '') {
articleTitle = articleTitle.slice(0, -section.length);
// remove the # and encode to be used to scroll to that section at the end of this function:
section = wikiParser.encodeSectionNameForId(section.slice(1));
}
articleUrl = formatUrl(articleTitle);
visitedPages[articleTitle] = (noData ? '' : '+');
clearDisplay();
addBackLink(articleTitle);
if (!(pgHistory.length > 0 && pgHistory[pgHistory.length - 1] === articleTitle))
pgHistory.push(articleTitle);
data = $.trim(wikiParser.removeElements(data, 'comments'));
if (!isDisambigPg && locale.disambigTemplateNameRegEx)
isDisambigPg = (locale.disambigTemplateNameRegEx.test(data));
// redir[1] is "#Redirect", redir[2] is the link addr, redir[3] is the rest:
redir = data.match(re.redirect); // ^(#[^\[]{1,25}\[\[)([^|\]]+)(\|.*|\]\][\S\s]*)
if (noData)
display.append(images.attention.attr('title', msgs.nonExistingPage)).appendText(' ');
if (isDisambigPg) {
display.append(images.attention.attr('title', msgs.disambigPage)).appendText(' ');
}
display.append($('<strong/>', {
text: articleTitle,
css: {color: (noData ? miscStyle.noDataReceivedLinkColor : 'green')}
}));
$topMenu = $('<span/>', {
'class': 'smrtL_topMenu'
})
.append($('<a/>', { // Open in new window
href: articleUrl,
title: articleTitle + ': ' + msgs.openInNewWin,
target: '_blank',
css: {margin: '0 2px'}
}).keypress(keyPressed).append(images.open))
.append($('<a/>', { // Edit in new window
href: formatUrl(articleTitle, false, true),
title: articleTitle + ': ' + msgs.editInNewWin,
target: '_blank',
css: {margin: '0 2px'}
}).keypress(keyPressed).append(images.edit));
if (isDisambigPg)
$topMenu.append($('<a/>', { // OK / Return focus to the textarea
'class': 'smrtL_topMenuOkButton',
href: '# ' + msgs.focusTextarea,
title: msgs.focusTextarea,
css: {margin: '0 2px'},
click: function (e) {
e.preventDefault();
$(this).remove();
display.collapse();
focusTextarea();
},
keypress: keyPressed
}).append(images.ok));
display.appendWrite($topMenu);
if (data.length == 0)
;
else if (!wikiParser.checkRegexSupport()) {
prt(msgs.error + ': ' + 'Unsupported browser. (No regex support)', true);
data = '';
}
else if (!redir) {
// keep only the text before the start of the first section title
if (!fullText) {
data = wikiParser.beforeTheFirstSection(data);
data = processStr(data); // sanitize, etc.
data = shorten(data);
}
sections = wikiParser.divideSections(data);
display.appendText(' ');
var emptyLineDiv = '<div style="font-size:50%;"><br/></div>';
var sectionNames = {}; // two or more sections can have the same heading
var sectionUrls = [];
$.each(sections, function (i, val) {
var unsafeContents = val.contents;
var unsafeHeading = val.heading;
var eq = val.eq; // the equal signs before (and after) the heading in the wiki code
var safeHeading;
if (eq !== '') {
var h = wikiParser.removeElements(unsafeHeading, 'comments, references, templates');
h = wikiParser.boldAndItalicToHtml(h); // convert '' and ''' to <i> and <b>
try {h = wikiParser.sanitizeHtml(h, '', true);} catch (e) {return;} // remove all html tags
h = wikiParser.unlink(h); // remove wiki links
h = wikiParser.unescapeCharEntities(h);
h = $.trim(h);
if (h === '')
h = '?';
var headingId = wikiParser.encodeSectionNameForId(h);
var sectionUrl = wikiParser.encodeSectionNameForUrl(h);
var nOfSectionsWithThatName = sectionNames[sectionUrl];
if (typeof nOfSectionsWithThatName == 'undefined')
sectionNames[sectionUrl] = 1;
else {
nOfSectionsWithThatName++;
sectionNames[sectionUrl] = nOfSectionsWithThatName;
sectionUrl += '_' + nOfSectionsWithThatName;
headingId += '_' + nOfSectionsWithThatName;
}
sectionHeadingIdsNoPrefix.push(headingId);
sectionUrls.push(sectionUrl);
safeHeading = processStr(unsafeHeading) || '?';
otherMeaningTemplates.push(eq + safeHeading);
}
var othr = getOtherMeaningTemplates(unsafeContents, processStr);
var otherMeaningTemplatesInThisSection = othr.templates;
var safeContents = processStr(othr.str);
otherMeaningTemplates.push.apply(otherMeaningTemplates, otherMeaningTemplatesInThisSection);
var sectionHtmlCode = (eq !== '' ? '<span class="smrtL_sectionHeading'
+ (otherMeaningTemplatesInThisSection.length > 0
? ' smrtL_hasOtherMeaningOrMain' : '')
+ '"><b>' + eq + safeHeading + eq + '</b></span> ' : '')
+ (safeContents.replace(re.oneOrMoreEmptyLinesG, emptyLineDiv) // ([^\S\n]*\n){2,}/g
.replace(re.nAsteriskG, '<br/>*') // \n\*/g
.replace(re.nHashSymbolG, '<br/>#') // \n#/g
.replace(re.nSemicolonG, '<br/>;') // \n;/g
.replace(re.nColonG, '<br/>:') // \n:/g
.replace(re.nG, ' ')) // \n/g
+ emptyLineDiv;
display.append(linkifyText(sectionHtmlCode, articleTitle, false).$collection);
});
var sectionHeadingIdsNoPrefixTemp = sectionHeadingIdsNoPrefix.slice();
var totalSections = sectionHeadingIdsNoPrefix.length;
display.find('.smrtL_sectionHeading').each(function (i, el) {
var $span = $(el);
var otherMeaningOrMainTemplInSection = $span.hasClass('smrtL_hasOtherMeaningOrMain');
var headingId = sectionHeadingIdsNoPrefixTemp.shift();
var sectionUrl = sectionUrls.shift();
$span.append($('<a/>', {
href: articleUrl + '#' + sectionUrl,
target: '_blank',
title: msgs.openInNewWin,
css: {margin: '0 2px'}
}).append(images.open.clone())
).append($('<a/>', {
href: formatUrl(articleTitle, false, true)
+ '§ion=' + (totalSections - sectionHeadingIdsNoPrefixTemp.length),
target: '_blank',
title: msgs.editInNewWin,
css: {margin: '0 2px'}
}).append(images.edit.clone())
).append($('<a/>', {
href: '#',
id: sectIdPrefix + headingId,
'class': 'smrtL_headingAnchor smrtL_section_' + headingId,
title: msgs.tableOfContents,
css: {margin: '0 2px'}
}).append((otherMeaningOrMainTemplInSection ? images.related.clone() : images.toc.clone()))
);
});
// add the ToC icon-link at the top
if (sectionHeadingIdsNoPrefix.length > 0) {
$topMenu
.append($('<a/>', { // Table of contents
'class': 'smrtL_topTocLink',
href: '# ' + msgs.tableOfContents,
title: msgs.tableOfContents,
css: {margin: '0 2px'},
click: function f (e) {
e.preventDefault();
display.find('.smrtL_tocAndOtherDiv div.smrtL_tocLinkDiv').toggle(true);
display.expand();
display.scrollTo('.smrtL_tocHeadingLink', true);
},
keypress: keyPressed
}).append(images.toc))
}
if (otherMeaningTemplates.length > 0 || sectionHeadingIdsNoPrefix.length > 0) {
otherMeaningPages = addTocAndOtherMeaningLinks(otherMeaningTemplates,
sectionHeadingIdsNoPrefix, articleTitle);
display.write();
if (otherMeaningPages.length > 0) {
// add the OtherMeanings icon-link at the top
$topMenu.append($('<a/>', { // Related articles
'class': 'smrtL_otherAndMainArticlesLink',
title: msgs.relatedArticles,
css: {margin: '0 2px'},
href: '# ' + msgs.relatedArticles,
click: function f (e) {
e.preventDefault();
display.find('.smrtL_tocAndOtherDiv div.smrtL_tocLinkDiv')
.toggle(false);
display.expand();
display.scrollTo('.smrtL_tocHeadingLink', true);
},
keypress: keyPressed
}).append(images.related));
// check for articles in the other-meaning template arguments
otherMeaningCheckLinks(otherMeaningPages);
}
}
} // if (data && !redir)
else { // REDIRECTING PAGE
// don't auto redirect to sections because section title may change, etc.
// don't redirect back to the previous page either
if (!noRedir && redir[2].indexOf('#') == -1
&& !(pgHistory.length > 1 && pgHistory[pgHistory.length - 2] === redir[2])) {
insertLink(redir[2]);
loadAndDisplay(redir[2], true); // don't redir next time to avoid infinite loops
return;
}
display.appendText(': ' + redir[1]);
var $lnk = browsableLink(redir[2], redir[2]);
display.append($lnk);
$lnk[0].focus();
display.appendText(shorten(redir[3]));
} // else if (redir)
display.write();
// attach events to some links:
display.find('.smrtL_headingAnchor').click(function (e) {
e.preventDefault();
display.find('.smrtL_tocAndOtherDiv div.smrtL_tocLinkDiv').toggle(true);
display.expand();
display.scrollTo('#' + this.id + 'Link', true);
})
.keypress(keyPressed);
display.find('a.smrtL_tocHeadingLink').click(function (e) {
e.preventDefault();
display.scrollTo('.smrtL_topTocLink' + this.id.slice(0, -4), true);
})
display.find('a.smrtL_tocLink').click(function (e) {
e.preventDefault();
display.scrollTo('#' + this.id.slice(0, -4), true);
})
.keypress(keyPressed);
if (isDisambigPg) {
display.expand(true);
display.focus('.smrtL_topMenuOkButton');
}
if (section !== '') {
var s = $('#' + sectIdPrefix + section).after($('<a/>', {
href: '# ' + msgs.backToTop,
title: msgs.backToTop,
css: {margin: '0 2px'},
click: function (e) {
e.preventDefault();
var target = display.find('.smrtL_topTocLink');
if (target.length == 0)
target = display.find('.smrtL_otherAndMainArticlesLink');
display.scrollTo(target, true);
},
keypress: keyPressed
}).append(images.up.clone()));
display.scrollTo(s);
}
} // processAndDisplayArticle
function getOtherMeaningTemplates(unsafeStr, processStrFn) {
var templates = [];
var tempArr;
if (!allPossibleOtherMeaningTemplates) {
while (tempArr = locale.otherMeaningTemplateNamesRegExG.exec(unsafeStr)) {
templates.push('{{'
+ processStrFn(tempArr[0].slice(2)
.replace(re.pipeTemplateToEndOfParam, '')) // {{!}}[^|}]+
);
}
}
else { // need to check for all other possible templates later, so need to remove these
unsafeStr = unsafeStr.replace(locale.otherMeaningTemplateNamesRegExG,
function (match) {
templates.push('{{'
+ processStrFn(match.slice(2)
.replace(re.pipeTemplateToEndOfParam, '')) // {{!}}[^|}]+
);
return '';
});
}
if (unsafeStr.charAt(0) == ':')
unsafeStr = '\n' + unsafeStr; // for the regex
// (^|=|})([^\S\n]*\n)+:[^\n]+\n/g
unsafeStr = unsafeStr.replace(re.otherMeaningNoTemplG, function(match, $1) {
if ($1)
match = match.slice(2);
templates.push('{{:|'
+ processStrFn(
match.replace(re.otherMeaningNoTemplTrimG, '') // (^|\n):\s*('')?|''\s*$/g
) + '}}');
return $1;
});
// other possible other-meaning templates (one-line templates with at least one parameter)
if (allPossibleOtherMeaningTemplates) {
templates.push('----');
while (tempArr = re.possibleOtherMeaningTemplatesG.exec(unsafeStr)) {
// ^\{\{[^}|\n]+\|[^}\n]+}}[^\S\n]*$/gm
templates.push('{{'
+ processStrFn(tempArr[0].slice(2)
.replace(re.pipeTemplateToEndOfParam, '')) // {{!}}[^|}]+
);
}
var l = templates.length;
if (templates[l - 1] == '----')
templates.length = l - 1;
else
templates.push('----/');
}
return {templates: templates, str: unsafeStr};
} // getOtherMeaningTemplates
function otherMeaningCheckLinks(otherMeaningPages) {
for (var i = otherMeaningPages.length; i--; ) {
otherMeaningPages[i] = encodeURIComponent(otherMeaningPages[i]);
}
var requestStr = '/w/api.php?action=query&titles='
+ otherMeaningPages.join('|') + '&prop=pageprops&ppprop=disambig&format=json'
+ '&smaxage=' + maxAgeSeconds.sForOtherMeanings + '&maxage='
+ maxAgeSeconds.forOtherMeanings;
// prop=info&format=json' also works
$.ajax({
url: requestStr,
dataType: 'json',
success: function (result) {
otherMeaningCheckLinksReceiveAnswer(result);
}
});
function otherMeaningCheckLinksReceiveAnswer(result) {
var pages = result.query.pages;
var norm = result.query.normalized || [];
var denormMap = {};
for (var i = norm.length - 1; i >= 0; i--) {
denormMap[norm[i].to] = norm[i].from;
}
var missing = [], p;
for (var i = -1; p = pages[i]; i--)
missing.push( denormMap[p.title] || p.title );
// replace all links to non-existing pages with plain text
var $links = display.find('.smrtL_tocAndOtherDiv '
+ 'span.smrtL_otherMeaningOrMainTempl a');
$links.each(function (i, l) {
var $l = $(this);
var text = $l.text();
if ($.inArray(text, missing) > -1) {
$l.after(text);
$l.remove();
}
});
// remove all templates without links
display.find('.smrtL_tocAndOtherDiv '
+ 'span.smrtL_otherMeaningOrMainTempl:not(:has(a))').remove();
// if no templates remain, remove top button
if (display.find('.smrtL_tocAndOtherDiv '
+ 'span.smrtL_otherMeaningOrMainTempl').length == 0) {
display.find('.smrtL_otherAndMainArticlesLink').remove();
var $div = display.find('.smrtL_tocAndOtherDiv');
if ($div.text().indexOf('=') == -1) {
$div.remove();
}
}
}
} // otherMeaningCheckLinks
function linkifyText(text, currArticle, insertLinkAddr) {
var pageNames = [];
var linkEventFns = [];
var html = text.replace(re.wikiLinkAddrAndLabelG, // \[\[([^\]|]+)\|?([^\]]*)]]/g
function (match, prePipe, postPipe) {
var link = browsableLinkAndEventFns(prePipe, postPipe, currArticle, insertLinkAddr);
pageNames.push(prePipe);
linkEventFns.push(link.eventHandlers);
return link.$link[0].outerHTML;
});
var $span = $('<span/>');
$span[0].innerHTML = html;
// attach the event handlers to all the links in the intro
$span.find('a.browsableLink').each(function (i, el) {
var e = linkEventFns[i];
$(el).keypress(keyPressed)
.click(e.clickFn).focus(e.focusFn).blur(e.blurFn);
});
return {$collection: $span.contents(), pageNames: pageNames};
} // linkifyText
// returns a jQuery anchor element with a link that can be opened in the display
// Used by DisambigMenu and OtherMeanings
function browsableLink(linkAddr, linkText, currArticle) {
if (!linkText && linkAddr.indexOf('|') > -1) {
var arr = linkAddr.split('|');
linkAddr = arr[0];
linkText = arr[1];
}
var lnk = browsableLinkAndEventFns(linkAddr, linkText, currArticle, true);
var e = lnk.eventHandlers;
lnk.$link.click(e.clickFn)
.focus(e.focusFn).blur(e.blurFn).keypress(keyPressed);
return lnk.$link;
} // browsableLink
// used within the article intro and in a few other places
// linkText is printed as html and must be a presanitized sting
// insertLinkAddr - insert into the textarea, or only follow the link in the msgDisplay?
function browsableLinkAndEventFns(linkAddr, linkText, currArticle, insertLinkAddr) {
insertLinkAddr = true; // ignore this for now, always insert it
linkText = $.trim(linkText);
if (!linkAddr && !linkText)
return null;
var linkAddrNormalized = (articleNamesCapitalized
? linkAddr.charAt(0).toUpperCase() + linkAddr.slice(1)
: linkAddr);
var visited = visitedPages[linkAddrNormalized];
var linkColor = (visited
? miscStyle.visitedPgLinkColor
: (visited === ''
? miscStyle.notExistingPgLinkColor
: miscStyle.normalPgLinkColor));
linkText = linkText || linkAddr;
var unescapedLinkAddr = wikiParser.unescapeCharEntities(linkAddr);
var $link = $('<a/>', {
html: linkText,
title: unescapedLinkAddr,
href: '# ' + unescapedLinkAddr,
tabindex: 0,
css: {color: linkColor, cursor: 'pointer'},
'class': 'browsableLink'
});
var clickFn = function (event) {
event.preventDefault();
if (linkAddr.charAt(0) == '#') {
display.scrollTo('#' + sectIdPrefix
+ wikiParser.encodeSectionNameForId(linkAddr.slice(1)), true);
}
else {
if (insertLinkAddr)
insertLink(linkAddr);
else
lastLinkAddr = ''; // don't insert links in textarea until next smartLinking() call
loadAndDisplay(linkAddr);
}
};
var focusFn = function () {
$('#smrtL_linkFocusHint').remove();
if (linkAddr != linkText) { // add a tooltip at the bottom right corner of the screen
$(document.body).append($('<div/>', {
text: unescapedLinkAddr,
id: 'smrtL_linkFocusHint',
css: {
position: 'fixed',
bottom: 0,
right: 0,
border: '1px solid silver',
background: '#dddddd',
fontSize: 'small'
},
click: function () { $(this).remove(); }
}));
clearTimeout(linkFocusTimeoutId);
linkFocusTimeoutId = setTimeout(function () {
$('#smrtL_linkFocusHint').remove();
}, 7000);
}
};
var blurFn = function () {
$('#smrtL_linkFocusHint').remove();
};
return {
$link: $link,
eventHandlers: {clickFn: clickFn, focusFn: focusFn, blurFn: blurFn}
};
} // browsableLinkAndEventsFn
function keyPressed(event) {
var charCode = event.charCode || event.keyCode;
//var character = String.fromCharCode(charCode);
if (charCode == 13) { // Enter
event.preventDefault(); // don't follow the link (in IE)
// - but that also cancels the onclick event
$(this).trigger('click');
$('#smrtL_linkFocusHint').remove();
}
} // keyPressed
function focusTextarea() {
if (window.wikEd && wikEd.useWikEd)
wikEd.frameWindow.focus();
else {
var scroll = $(window).scrollTop();
textarea.focus();
$(window).scrollTop(scroll); // focus() causes IE to scroll the page
}
}
function insertLink(addr) {
if (window.wikEd && wikEd.useWikEd) {
return;
}
// Internet Explorer loses the cursor position on onclick.
function restoreCursorPos() {
var currSel = textarea.textSelection( 'getCaretPosition', { startAndEnd: true } );
if (currSel[0] == currSel[1]) // no selection - put cursor at start of link (+ 2)
textarea.textSelection( 'setSelection', { start: linkStartPos + 2 } );
}
if (lastLinkAddr === '')
return;
restoreCursorPos();
var ar = wikiParser.focusedSegment(valParts(textarea), 'wikilink');
if (!ar)
return;
var link = ar[1];
var oldAddr = ( link.match(re.wikiLinkAddr) || ['', ''] )[1]; // ^\[\[([^|\]]*)
if (oldAddr.toLowerCase() !== lastLinkAddr.toLowerCase())
return; // if a different link is at this position - abort
link = link.replace(re.wikiLinkRemoveOpeningBracketsAndUpToAndInclPipe, ''); // ^\[\[(.+?\|)?
if (addr) {
var addrCapitalized = addr.charAt(0).toUpperCase() + addr.slice(1);
var linkCapitalized = link.charAt(0).toUpperCase() + link.slice(1);
if (addr && addrCapitalized + ']]' != linkCapitalized)
link = addr + '|' + link;
}
lastLinkAddr = addr || link.slice(0, -2);
link = '[[' + link;
valParts(textarea, ar[0] + link, '', ar[2]);
} // insertLink
// the function/object exposed to the outside world
function smartLinkingFn() {
correctLinking();
}
// add the function for updating the messages and locale data:
smartLinkingFn.init = init;
setup();
return smartLinkingFn;
})(jQuery);
window.smartLinking.version = 1000;
// </nowiki>