Jump to content

User:V111P/js/smartLinking.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <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, '&lt;') != '<a>&lt;bd&lt;/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)
							+ '&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('{{:|'
				+ 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>