User:V111P/js/smartLinking.js

// /* * 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  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 = $(' ', imageProps.attentionImg); images.edit = $(' ', imageProps.editImg); images.open = $(' ', imageProps.newWinImg); images.related = $(' ', imageProps.relatedImg); images.toc = $(' ', imageProps.tocImg); images.up = $(' ', imageProps.upImg); images.ok = $(' ', 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 (''.replace(/<(?!\/?(a|b)>)/g, '&lt;') != '&lt;bd&lt;/e>') { 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 = $('', {			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 = $(' ', {'class': 'smrtL_tocAndOtherDiv'}); var $div1; var $div = $div1 = $(' '); $mainDiv.append(' ') .append($(' ', { text: msgs.tocAndOtherAndMainArticles, css: {color: '#050', fontWeight: 'bold', textDecoration: 'underline'} }));

// back-to-top button $mainDiv.append($('', { 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($('', { 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(' ');

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 = $(' ', {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(''						+ ''						+ otherMeaningTemplates[j] + ' '); continue; }

var spl = otherMeaningTemplates[j].slice(2, -2) .match(re.splitTemplateParamsG); // split on |, except within links & templates: // (?:[^|[{}]|\^\*]|\{\{[^}]*}}|\{[^{]|}[^}])+|(?=\|\|)/g

$span = $(' ', {'class': 'smrtL_otherMeaningOrMainTempl'}); $span.append(text('')).append(' '); $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(							$(' ', {'class': 'smrtL_otherMeaningOrMainTempl'})								.append(browsableLink(val)).append(' ')						); pgsArr.push(val); }				});			}			$mainDiv.append($div1).append($div).append(' ');			display.append($mainDiv);

return pgsArr; } // addTocAndOtherMeaningLinks

function processStr(str) { str = $.trim(str); str = wikiParser.escCharsForNowikiTags(str); str = str.replace(locale.anchorTemplateRegExG, '<$1>'); // /g str = wikiParser.removeElements(str,			         'tables, files, references, templates, behavior switches, others'); // wikiCode bold and italic to html ( to , ' to ): str = str.replace(/<(anchor\|[\S\s]+?)>/g, ''); 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($(' ', { text: articleTitle, css: {color: (noData ? miscStyle.noDataReceivedLinkColor : 'green')} }));

$topMenu = $(' ', {			'class': 'smrtL_topMenu'		}) .append($('', { // Open in new window href: articleUrl, title: articleTitle + ': ' + msgs.openInNewWin, target: '_blank', css: {margin: '0 2px'} }).keypress(keyPressed).append(images.open)) .append($('', { // 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 = ' '; 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' : )					+ '">' + eq + safeHeading + eq + ' ' : '') + (safeContents.replace(re.oneOrMoreEmptyLinesG, emptyLineDiv) // ([^\S\n]*\n){2,}/g						.replace(re.nAsteriskG, ' *') // \n\*/g						.replace(re.nHashSymbolG, ' #') // \n#/g						.replace(re.nSemicolonG, ' ;') // \n;/g						.replace(re.nColonG, ' :') // \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)							+ '&section=' + (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('');			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[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($(' ', { 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; //