Участник:Jack who built the house/copyWikilinks.js

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Страница персонального оформления. У этого JS-кода есть документация: Участник:Jack who built the house/copyWikilinks.
После сохранения очистите кэш браузера.
/**
 * copyWikilinks.js — 15.07.2017
 * By Jack who built the house
 * Selection-dedicated functions by Tim Down and gilly3 (see below)
 * keymaster.js by Thomas Fuchs copied here from https://github.com/madrobby/keymaster, licenced under MIT
 * 
 * Documentation: https://ru.wikipedia.org/wiki/Участник:Jack_who_built_the_house/copyWikilinks
 */

(function() {

//     keymaster.js
//     (c) 2011-2013 Thomas Fuchs
//     keymaster.js may be freely distributed under the MIT license.

;(function(global){
  var k,
    _handlers = {},
    _mods = { 16: false, 18: false, 17: false, 91: false },
    _scope = 'all',
    // modifier keys
    _MODIFIERS = {
      '⇧': 16, shift: 16,
      '⌥': 18, alt: 18, option: 18,
      '⌃': 17, ctrl: 17, control: 17,
      '⌘': 91, command: 91
    },
    // special keys
    _MAP = {
      backspace: 8, tab: 9, clear: 12,
      enter: 13, 'return': 13,
      esc: 27, escape: 27, space: 32,
      left: 37, up: 38,
      right: 39, down: 40,
      del: 46, 'delete': 46,
      home: 36, end: 35,
      pageup: 33, pagedown: 34,
      ',': 188, '.': 190, '/': 191,
      '`': 192, '-': 189, '=': 187,
      ';': 186, '\'': 222,
      '[': 219, ']': 221, '\\': 220,
      'а': 1072, 'б': 1073, 'в': 1074, 'г': 1075, 'д': 1076, 'е': 1077, 'ё': 1105, 'ж': 1078, 'з': 1079, 'и': 1080, 'й': 1081, 'к': 1082, 'л': 1083, 'м': 1084, 'н': 1085, 'о': 1086, 'п': 1087, 'р': 1088, 'с': 1089, 'т': 1090, 'у': 1091, 'ф': 1092, 'х': 1093, 'ц': 1094, 'ч': 1095, 'ш': 1096, 'щ': 1097, 'ъ': 1098, 'ы': 1099, 'ь': 1100, 'э': 1101, 'ю': 1102, 'я': 1103,
    },
    code = function(x){
      return _MAP[x] || x.toUpperCase().charCodeAt(0);
    },
    _downKeys = [];

  for(k=1;k<20;k++) _MAP['f'+k] = 111+k;

  // IE doesn't support Array#indexOf, so have a simple replacement
  function index(array, item){
    var i = array.length;
    while(i--) if(array[i]===item) return i;
    return -1;
  }

  // for comparing mods before unassignment
  function compareArray(a1, a2) {
    if (a1.length != a2.length) return false;
    for (var i = 0; i < a1.length; i++) {
        if (a1[i] !== a2[i]) return false;
    }
    return true;
  }

  var modifierMap = {
      16:'shiftKey',
      18:'altKey',
      17:'ctrlKey',
      91:'metaKey'
  };
  function updateModifierKey(event) {
      for(k in _mods) _mods[k] = event[modifierMap[k]];
  };

  // handle keydown event
  function dispatch(event) {
    var key, handler, k, i, modifiersMatch, scope;
    key = event.keyCode;

    if (index(_downKeys, key) == -1) {
        _downKeys.push(key);
    }

    // if a modifier key, set the key.<modifierkeyname> property to true and return
    if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
    if(key in _mods) {
      _mods[key] = true;
      // 'assignKey' from inside this closure is exported to window.key
      for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true;
      return;
    }
    updateModifierKey(event);

    // see if we need to ignore the keypress (filter() can can be overridden)
    // by default ignore key presses if a select, textarea, or input is focused
    if(!assignKey.filter.call(this, event)) return;

    // abort if no potentially matching shortcuts found
    if (!(key in _handlers)) return;

    scope = getScope();

    // for each potential shortcut
    for (i = 0; i < _handlers[key].length; i++) {
      handler = _handlers[key][i];

      // see if it's in the current scope
      if(handler.scope == scope || handler.scope == 'all'){
        // check if modifiers match if any
        modifiersMatch = handler.mods.length > 0;
        for(k in _mods)
          if((!_mods[k] && index(handler.mods, +k) > -1) ||
            (_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false;
        // call the handler and stop the event if neccessary
        if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){
          if(handler.method(event, handler)===false){
            if(event.preventDefault) event.preventDefault();
              else event.returnValue = false;
            if(event.stopPropagation) event.stopPropagation();
            if(event.cancelBubble) event.cancelBubble = true;
          }
        }
      }
    }
  };

  // unset modifier keys on keyup
  function clearModifier(event){
    var key = event.keyCode, k,
        i = index(_downKeys, key);

    // remove key from _downKeys
    if (i >= 0) {
        _downKeys.splice(i, 1);
    }

    if(key == 93 || key == 224) key = 91;
    if(key in _mods) {
      _mods[key] = false;
      for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false;
    }
  };

  function resetModifiers() {
    for(k in _mods) _mods[k] = false;
    for(k in _MODIFIERS) assignKey[k] = false;
  };

  // parse and assign shortcut
  function assignKey(key, scope, method){
    var keys, mods;
    keys = getKeys(key);
    if (method === undefined) {
      method = scope;
      scope = 'all';
    }

    // for each shortcut
    for (var i = 0; i < keys.length; i++) {
      // set modifier keys if any
      mods = [];
      key = keys[i].split('+');
      if (key.length > 1){
        mods = getMods(key);
        key = [key[key.length-1]];
      }
      // convert to keycode and...
      key = key[0]
      key = code(key);
      // ...store handler
      if (!(key in _handlers)) _handlers[key] = [];
      _handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods });
    }
  };

  // unbind all handlers for given key in current scope
  function unbindKey(key, scope) {
    var multipleKeys, keys,
      mods = [],
      i, j, obj;

    multipleKeys = getKeys(key);

    for (j = 0; j < multipleKeys.length; j++) {
      keys = multipleKeys[j].split('+');

      if (keys.length > 1) {
        mods = getMods(keys);
      }

      key = keys[keys.length - 1];
      key = code(key);

      if (scope === undefined) {
        scope = getScope();
      }
      if (!_handlers[key]) {
        return;
      }
      for (i = 0; i < _handlers[key].length; i++) {
        obj = _handlers[key][i];
        // only clear handlers if correct scope and mods match
        if (obj.scope === scope && compareArray(obj.mods, mods)) {
          _handlers[key][i] = {};
        }
      }
    }
  };

  // Returns true if the key with code 'keyCode' is currently down
  // Converts strings into key codes.
  function isPressed(keyCode) {
      if (typeof(keyCode)=='string') {
        keyCode = code(keyCode);
      }
      return index(_downKeys, keyCode) != -1;
  }

  function getPressedKeyCodes() {
      return _downKeys.slice(0);
  }

  function filter(event){
    var tagName = (event.target || event.srcElement).tagName;
    // ignore keypressed in any elements that support keyboard data input
    return true;  // !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
  }

  // initialize key.<modifier> to false
  for(k in _MODIFIERS) assignKey[k] = false;

  // set current scope (default 'all')
  function setScope(scope){ _scope = scope || 'all' };
  function getScope(){ return _scope || 'all' };

  // delete all handlers for a given scope
  function deleteScope(scope){
    var key, handlers, i;

    for (key in _handlers) {
      handlers = _handlers[key];
      for (i = 0; i < handlers.length; ) {
        if (handlers[i].scope === scope) handlers.splice(i, 1);
        else i++;
      }
    }
  };

  // abstract key logic for assign and unassign
  function getKeys(key) {
    var keys;
    key = key.replace(/\s/g, '');
    keys = key.split(',');
    if ((keys[keys.length - 1]) == '') {
      keys[keys.length - 2] += ',';
    }
    return keys;
  }

  // abstract mods logic for assign and unassign
  function getMods(key) {
    var mods = key.slice(0, key.length - 1);
    for (var mi = 0; mi < mods.length; mi++)
    mods[mi] = _MODIFIERS[mods[mi]];
    return mods;
  }

  // cross-browser events
  function addEvent(object, event, method) {
    if (object.addEventListener)
      object.addEventListener(event, method, false);
    else if(object.attachEvent)
      object.attachEvent('on'+event, function(){ method(window.event) });
  };

  // set the handlers globally on document
  addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48
  addEvent(document, 'keyup', clearModifier);

  // reset modifiers to false whenever the window is (re)focused.
  addEvent(window, 'focus', resetModifiers);

  // store previously defined key
  var previousKey = global.key;

  // restore previously defined key and return reference to our key object
  function noConflict() {
    var k = global.key;
    global.key = previousKey;
    return k;
  }

  // set window.key and window.key.set/get/deleteScope, and the default filter
  global.key = assignKey;
  global.key.setScope = setScope;
  global.key.getScope = getScope;
  global.key.deleteScope = deleteScope;
  global.key.filter = filter;
  global.key.isPressed = isPressed;
  global.key.getPressedKeyCodes = getPressedKeyCodes;
  global.key.noConflict = noConflict;
  global.key.unbind = unbindKey;

  if(typeof module !== 'undefined') module.exports = assignKey;

})(this);


/*** Technical functions ***/

function trim(str) {
	return str.replace(/^\s+|\s+$/g, '');
}

// By Tim Down, posted at http://stackoverflow.com/a/5379408
function getSelectionText() {
	var text = "";
	if (window.getSelection) {
		text = window.getSelection().toString();
	} else if (document.selection && document.selection.type != "Control") {
		text = document.selection.createRange().text;
	}
	return text;
}

// By Tim Down, posted at http://stackoverflow.com/a/3966822
function getInputSelection(el) {
	var start = 0, end = 0, normalizedValue, range,
		textInputRange, len, endRange;
		
	if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
		start = el.selectionStart;
		end = el.selectionEnd;
	} else {
		range = document.selection.createRange();
		
		if (range && range.parentElement() == el) {
			len = el.value.length;
			normalizedValue = el.value.replace(/\r\n/g, "\n");
			
			// Create a working TextRange that lives only in the input
			textInputRange = el.createTextRange();
			textInputRange.moveToBookmark(range.getBookmark());
			
			// Check if the start and end of the selection are at the very end
			// of the input, since moveStart/moveEnd doesn't return what we want
			// in those cases
			endRange = el.createTextRange();
			endRange.collapse(false);
			
			if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
				start = end = len;
			} else {
				start = -textInputRange.moveStart("character", -len);
				start += normalizedValue.slice(0, start).split("\n").length - 1;
				
				if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
					end = len;
				} else {
					end = -textInputRange.moveEnd("character", -len);
					end += normalizedValue.slice(0, end).split("\n").length - 1;
				}
			}
		}
    }
	
	return {
		start: start,
		end: end
	};
}

// By Tim Down, posted at http://stackoverflow.com/a/3966822
// Modified by Jack who built the house
function replaceSelectedText(el, text, restoreSelection) {
	var sel = getInputSelection(el), val = el.value;
	el.value = val.slice(0, sel.start) + text + val.slice(sel.end);
	if (restoreSelection)
		setCursorPos(el, sel.start, sel.start + text.length);
	else
		setCursorPos(el, sel.start + text.length);
}

// By gilly3, posted at http://stackoverflow.com/a/7745998
function setCursorPos(input, start, end) {
	if (arguments.length < 3) end = start;
	if ("selectionStart" in input) {
		input.selectionStart = start;
		input.selectionEnd = end;
	} else if (input.createTextRange) {
		var rng = input.createTextRange();
		rng.moveStart("character", start);
		rng.collapse();
		rng.moveEnd("character", end - start);
		rng.select();
	}
}

function dotToPercent(code) {
	return code.replace(/\.([0-9A-F][0-9A-F])/g, '%$1'); 
}

function htmlEncodeWikiMarkup(text) {
	var map = {
		'.3C': '&lt;', // <
		'.3E': '&gt;', // >
		'.5B': '&#91;', // [
		'.5D': '&#93;', // ]
		'.7B': '&#123;', // {
		'.7C': '&#124;', // |
		'.7D': '&#125;', // }
	};

	return text.replace(/\.(?:3C|3E|5B|5D|7B|7C|7D)/g, function(ch) { return map[ch]; });
}

function focusEdge() {
	if (navigator.userAgent.indexOf('Edge') !== -1) {
		document.focus();
	}
}


/*** Main functions ***/
/* Sorted from more to less specific */

function processAnchorAndSpaces(link, processAll) {
	processAll = !!processAll;
	return htmlEncodeWikiMarkup(link
		.replace(/(^|[^0-9A-F\.])(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/g, '$1$2,$3,$4,$5') // hide IP
		.replace(/\.F[0-4]\.[89AB][\dA-F]\.[89AB][\dA-F]\.[89AB][\dA-F]/g, dotToPercent) // The codes were borrowed from
		.replace(/\.E[\dA-F]\.[89AB][\dA-F]\.[89AB][\dA-F]/g, dotToPercent)              // MediaWiki:Gadget-urldecoder.js
		.replace(/\.[CD][\dA-F]\.[89AB][\dA-F]/g, dotToPercent)
		.replace(/\.[2-7][0-9A-F]/g, function(code) {
			var ch = decodeURIComponent(dotToPercent(code));
			if ('!"#$%&\'()*+,/;<=>?@\\^`~'.indexOf(ch) != -1 || processAll)
				return dotToPercent(code);
			else
				return code;
		})
		.replace(/(^|[^0-9A-F\.])(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?),(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?),(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?),(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/g, '$1$2.$3.$4.$5') // restore IP
		.replace(/_/g, ' '));
}

function handlePrefixes(wikilink) {
	// w:en:wikt (en.wikipedia.org/wiki/wikt:) → :en:wikt
	wikilink = wikilink.replace(/^([a-z]+:)([a-z]+:)((w|wikt|s|q|n|b|v|voy):)/, function(s, p1, p2, p3) {
		return ':' + p2 + p3;
	});
	// w:en:ru (en.wikipedia.org/wiki/ru:) → w:ru
	wikilink = wikilink.replace(/^([a-z]+:)([a-z]+:)([a-z]{2}:)/, function(s, p1, p2, p3) {
		return p1 + p3;
	});
	wikilink = wikilink.replace(homeProjectPrefixDetector, function(s, p1, p2) {
		return p1 + (p2 ? p2 : (homeLangPrefix ? ':' : '' ));
	});
	if (homeLangPrefix) wikilink = wikilink.replace(homeLangPrefixDetector, '$1$2');
	wikilink = wikilink.replace(/^(:?([a-z]+:){1,2}):/, '$1'); // ...::File, ...::Category
	return wikilink;
}

function processURI(uri, type, brackets, linkText) {
	if (type == 'wikilink') {
		uri = uri
			.replace(/%(5B|5D|7B|7D|7C|3C|3E)/g, '%25$1') // []{}|<>
			.replace(/%C2%A0/g, '.C2.A0');                // Non-breaking space. Otherwise parser turns it into normal space.
			                                              // Should appear only in section links, so we can use dot notation
			                                              // here (percent signs don't work).
		try {
			uri = decodeURIComponent(uri);
		} catch(e) {
			return null;
		}
	} else if (type == 'link') {
		uri = uri
			.replace(linksDetector, function(s) {
				link = s
					.replace(/%(20|0A|0D|5B|5D|3C|3E|2B|22|3F|26|3D|23|27)/g, '%25$1') // space, \r, \n, []<>+"?&=#'
					.replace(/%C2%A0/g, '%25C2%25A0');  // Non-breaking space in links is treated as space by parser,
				                                        // so it should stay encoded.
				try {
					link = decodeURIComponent(link);
				} catch(e) {
					return s;
				}
				if (brackets)
					link = '[' + link + (linkText ? ' ' + linkText : '') + ']';
				return link;
			});
	} else {  // type == 'text' || type == 'cyrillicAnchor'
		try {
			uri = decodeURIComponent(uri);
		} catch(e) {
			return null;
		}
	}
	return trim(uri);
}

function links2Wikilinks(link, isMonolithicLink, brackets, singleBrackets, linkText, useIwTemplate) {
	function constructWikilink(prefix, page, lb, rb) {
		if (lb != rb)
			return null;
		var trailingPunct = '';
		if (!isMonolithicLink) {
			var trailingPunctRegex = new RegExp(
				  '[,;\\\\\.:!\\?'               // Borrowed from MediaWiki:Gadget-urldecoder.js:
				+ (/\(/.test(page) ? '' : '\\)') // closing bracket without opening bracket
				+ ']+$|\'\'+$'                   // or possible bold/italic at the end of url
				);
			page = page.replace(trailingPunctRegex, function(s) {
				trailingPunct = s;
				return '';
			});
		}
		page = page.replace(/\?uselang=\w+($|#)/g, '$1');
		if (page.search(/\?/) != -1) {
			return null;
		} else {
			var opener = brackets ? '[[' : '';
			var closer = brackets ? ']]' : '';
			var fullpage;
			page = processURI(processAnchorAndSpaces(page), 'wikilink', false);
			if (useIwTemplate && prefix.match(/^w:/) && prefix != 'w:ru' && !page.match(/#/)) {
				return '{{iw|||' + (prefix == 'w:en' ? '' : prefix.replace(/^w:/, '')) + '|' + page + '}}';
			} else {
				if (!isMonolithicLink || copyWikilinksColonFile || page.match(/#/))
					page = page
						.replace(/^Файл:/i, ':Файл:')
						.replace(/^File:/i, ':File:');
				if (!isMonolithicLink || copyWikilinksColonCategory || page.match(/#/))
					page = page
						.replace(/^Категория:/i, ':Категория:')
						.replace(/^Category:/i, ':Category:');
				if (isMonolithicLink && !copyWikilinksColonFile && prefix == 'commons' && page.match(/^File:/) && !page.match(/#/)) {
					prefix = '';
					if (homeLangPrefix == 'ru')
						page = page.replace(/^File:/, 'Файл:');
				}
				fullpage = handlePrefixes((prefix ? prefix + ':' : '') + page);
				if (fullpage[0] == '/') {
					fullpage = ':' + fullpage;
				}
				closer =
					  (brackets && linkText && fullpage != linkText && fullpage != ':' + linkText && linkText.search(linksDetector) == -1
						? '|' + linkText
						: '')
					+ closer
					+ (trailingPunct ? trailingPunct : '');
				return opener + fullpage + closer;
			}
		}
	}
	
	useIwTemplate = !!useIwTemplate;
	return link
		.replace(/(\[?)https?:\/\/phabricator\.wikimedia\.org\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, page, rb) { return constructWikilink('phab', page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/ru\.wikimedia\.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, page, rb) { return constructWikilink('wmru', page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wikipedia\.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, langPrefix, page, rb) { return constructWikilink('w:' + langPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wikipedia\.org\/w\/index\.php\?title=([^&]+)&action=edit&redlink=1(\]?)/ig,
				// Counting red links could be done for all sites, but have to refactor this
			function(s, lb, langPrefix, page, rb) { return constructWikilink('w:' + langPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wikimedia\.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, projectPrefix, page, rb) { return constructWikilink(projectPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wikimedia\.org\/w\/index\.php\?title=([^&]+)&action=edit&redlink=1(\]?)/ig,
			function(s, lb, projectPrefix, page, rb) { return constructWikilink(projectPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wiktionary\.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, langPrefix, page, rb) { return constructWikilink('wikt:' + langPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wiktionary\.org\/w\/index\.php\?title=([^&]+)&action=edit&redlink=1(\]?)/ig,
			function(s, lb, langPrefix, page, rb) { return constructWikilink('wikt:' + langPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wikisource\.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, langPrefix, page, rb) { return constructWikilink('s:' + langPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wikiquote\.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, langPrefix, page, rb) { return constructWikilink('q:' + langPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wikinews\.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, langPrefix, page, rb) { return constructWikilink('n:' + langPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wikibooks\.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, langPrefix, page, rb) { return constructWikilink('b:' + langPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wikiversity\.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, langPrefix, page, rb) { return constructWikilink('v:' + langPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/([^\.]+)\.wikivoyage\.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, langPrefix, page, rb) { return constructWikilink('voy:' + langPrefix, page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/(?:www\.)?wikidata.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, page, rb) { return constructWikilink('d', page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/(?:www\.)?mediawiki.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, page, rb) { return constructWikilink('mw', page, !!lb, !!rb) || s; })
		.replace(/(\[?)https?:\/\/(?:www\.)?wikimediafoundation.org\/wiki\/([^\[\]\n\r<>" ]+)(\]?)/ig,
			function(s, lb, page, rb) { return constructWikilink('foundation', page, !!lb, !!rb) || s; })
		
		// The rest of the links in the selection
		.replace(linksDetector, function(s) { return processURI(s, 'link', singleBrackets, linkText); });
}

function cyrillicAnchors() {
	var sectionHeadings = document.getElementsByClassName('mw-headline');
	var processedAnchor;
	for (var i = 0; i < sectionHeadings.length; i++) {
		processedAnchor = processURI(processAnchorAndSpaces(sectionHeadings[i].id, true), 'cyrillicAnchor', false);
		if (processedAnchor)
			sectionHeadings[i].innerHTML =
				  '<a name="' + processedAnchor.replace(/ /g, ' ')                   .replace(/"/g, '&quot;') + '"></a>'
				+ '<a name="' + processedAnchor.replace(/ /g, '_').replace(/ /g, '_').replace(/"/g, '&quot;') + '"></a>'
				+ sectionHeadings[i].innerHTML;
	}
	if (location.hash.search(/[а-яё]/i) != -1)
		document.querySelector('a[name="' + location.hash.substring(1).replace(/"/g, '\\22') + '"]').scrollIntoView();
}


/************************************************************/

cyrillicAnchors();

var defaultHomeLangPrefix = 'ru';

var potentialWikilinkDetector = new RegExp('https?:\\/\\/([^\\.]+\\.)?((wikipedia|wikimedia|wiktionary|wikisource|wikiquote|wikinews'
		+ '|wikibooks|wikiversity|wikivoyage|wikidata|mediawiki|wikimediafoundation)\\.org)', 'i');
var linksDetector = new RegExp('https?:\\/\\/[^\\[\\]\\n\\r<>" ]+', 'ig');

// This is not used, but may be used to detect links without square brackets in the following fashion
// (given linkText is the link text to be displayed, extracted from somewhere):
// text = text.replace(noBracketsLinksDetector, '$1[$2' + (linkText ? ' ' + linkText : '') + ']');
// Though problems arise here as usually we decode links, and decoding after this statement would decode linkText as well,
// while decoding before it would replace %22 with " and make link boundaries incorrectly identified.
// See how this is solved in the processURI function.
// var noBracketsLinksDetector = new RegExp('([^\\[]|^)(https?:\\/\\/[^\\[\\]\\n\\r<>" ]+)', 'ig');

var linkBeforeCursorDetector = new RegExp('\\[?https?:\\/\\/[^\\[\\]\\n\\r<>" ]+\\]?[ \\t\\n\\r]*$', 'i');

var homeProjectPrefix, homeLangPrefix, homeProjectPrefixDetector, homeLangPrefixDetector, currentPrefix, cleanPagename, pagename, copyURL, temp;

/* If uncommented, this makes "public" functions globally visible, making it easier to debug */
window.cw = {
	cyrillicAnchors: cyrillicAnchors,
	links2Wikilinks: links2Wikilinks,
	processURI: processURI,
	handlePrefixes: handlePrefixes,
	processAnchorAndSpaces: processAnchorAndSpaces
};
/* */

// defaults
if (!('copyWikilinksUseUrlDecoder' in window))    window.copyWikilinksUseUrlDecoder    = true;
if (!('copyWikilinksSqBrackets' in window))       window.copyWikilinksSqBrackets       = true;
if (!('copyWikilinksSingleSqBrackets' in window)) window.copyWikilinksSingleSqBrackets = true;
if (!('copyWikilinksUseLinkText' in window))      window.copyWikilinksUseLinkText      = false;
if (!('copyWikilinksUseIwTemplate' in window))    window.copyWikilinksUseIwTemplate    = false;
if (!('copyWikilinksHomePrefix' in window))       window.copyWikilinksHomePrefix       = 'w:' + defaultHomeLangPrefix;
if (!('copyWikilinksCurrentIsHome' in window))    window.copyWikilinksCurrentIsHome    = false;
if (!('copyWikilinksColonFile' in window))        window.copyWikilinksColonFile        = false;
if (!('copyWikilinksColonCategory' in window))    window.copyWikilinksColonCategory    = false;
if (!('copyWikilinksKeyCombination1' in window))  window.copyWikilinksKeyCombination1  = 'ctrl+`';
if (!('copyWikilinksKeyCombination2' in window))  window.copyWikilinksKeyCombination2  = 'ctrl+shift+`';
if (!('copyWikilinksKeyCombination3' in window))  window.copyWikilinksKeyCombination3  = 'alt+`';

// Only sites where this script can be loaded
currentPrefix = location.href
	.replace(/^https?:\/\/ru\.wikimedia\.org\/.*/, 'wmru:')
	.replace(/^https?:\/\/([^\.]+)\.wikipedia\.org\/.*/, 'w:$1:')
	.replace(/^https?:\/\/([^\.]+)\.wikimedia\.org\/.*/, '$1:')
	.replace(/^https?:\/\/([^\.]+)\.wiktionary\.org\/.*/, 'wikt:$1:')
	.replace(/^https?:\/\/([^\.]+)\.wikisource\.org\/.*/, 's:$1:')
	.replace(/^https?:\/\/([^\.]+)\.wikiquote\.org\/.*/, 'q:$1:')
	.replace(/^https?:\/\/([^\.]+)\.wikinews\.org\/.*/, 'n:$1:')
	.replace(/^https?:\/\/([^\.]+)\.wikibooks\.org\/.*/, 'b:$1:')
	.replace(/^https?:\/\/([^\.]+)\.wikiversity\.org\/.*/, 'v:$1:')
	.replace(/^https?:\/\/([^\.]+)\.wikivoyage\.org\/.*/, 'voy:$1:')
	.replace(/^https?:\/\/(www\.)?wikidata.org\/.*/, 'd:')
	.replace(/^https?:\/\/(www\.)?mediawiki.org\/wiki\/.*/, 'mw:')
	.replace(/^https?:\/\/(www\.)?wikimediafoundation.org\/wiki\/.*/, 'foundation:')
	.replace(/^https?:\/\/.*/, '');

if (copyWikilinksCurrentIsHome) {
	copyWikilinksHomePrefix = currentPrefix.slice(0, -1);
}

if (copyWikilinksHomePrefix.search(/^(w|commons|meta|wikitech|wikt|s|q|n|b|v|voy|d|mw|foundation|wmru):?/) === -1) {
	homeProjectPrefix = 'w';
	homeLangPrefix = copyWikilinksHomePrefix;
} else if (copyWikilinksHomePrefix.search(/^(w|wikt|s|q|n|b|v|voy)$/) !== -1) {
	homeProjectPrefix = copyWikilinksHomePrefix;
	homeLangPrefix = defaultHomeLangPrefix;
} else {
	temp = copyWikilinksHomePrefix.split(':');
	homeProjectPrefix = temp[0];
	homeLangPrefix = temp[1] || '';
}
copyWikilinksHomePrefix = homeProjectPrefix + (homeLangPrefix ? ':' + homeLangPrefix : '');

homeProjectPrefixDetector = new RegExp('(^|\\[\\[)(:?[a-z]+:)?' + homeProjectPrefix + ':', 'g');
homeLangPrefixDetector    = new RegExp('(^|\\[\\[):?([a-z]+:)?' + homeLangPrefix    + ':', 'g');

// Remove home prefix
currentPrefix = handlePrefixes(currentPrefix);

if (!currentPrefix && mw.config.get('wgNamespaceNumber') == 14 && copyWikilinksColonCategory ||
		!currentPrefix && mw.config.get('wgNamespaceNumber') == 6 && copyWikilinksColonFile) {
	currentPrefix = ':';
}

cleanPagename = mw.config.get('wgPageName').replace(/_/g, ' ');
if (!copyWikilinksColonFile && currentPrefix == 'commons:' && cleanPagename.match(/^File:/)) {
	currentPrefix = '';
	if (homeLangPrefix == 'ru') {
		cleanPagename = cleanPagename.replace(/^File:/, 'Файл:');
	}
}
pagename = ((!copyWikilinksCurrentIsHome || currentPrefix === ':') ? currentPrefix : '') + cleanPagename;

if (location.pathname.search('/wiki/') == -1 &&
    location.search.search('action=edit') == -1 && location.search.search('action=submit') == -1) {
	copyURL = true;
} else {
	copyURL = false;
}

key(copyWikilinksKeyCombination1, ctrlyo);
key(copyWikilinksKeyCombination2, ctrlshiftyo);
key(copyWikilinksKeyCombination3, altyo);

function ctrlyo() {
	var link, wikilink, iwTemplate;
	
	if (!copyURL) {
		if (copyWikilinksUseIwTemplate && !currentPrefix.match(/^:?$/) && location.hostname.search('wikipedia.org') != -1 && !document.getElementsByClassName('interwiki-ru')[0]) {
			prompt('Шаблон «iw» для текущей страницы:',  '{{iw|||' + (currentPrefix.match(/^w?:en:$/) ? '' : currentPrefix.replace(/^w:/, '').replace(/:/g, '')) + '|' + cleanPagename + '}}');
		} else {
			prompt('Вики-ссылка на текущую страницу:', (copyWikilinksSqBrackets ? '[[' : '') + pagename + (copyWikilinksSqBrackets ? ']]' : ''));
		}
		focusEdge();
	} else {
		link = processURI(location.href.split("#")[0], 'link', copyWikilinksSingleSqBrackets);
		if (link) {
			prompt('Раскодированный адрес текущей страницы:', link);
			focusEdge();
		}
	}
	return false;
}

function ctrlshiftyo () {
	var link, wikilink, iwTemplate;
	
	var hoveredLink = document.querySelector('a:hover');
	if (hoveredLink) {
		var linkText;
		link = hoveredLink.href;
		if (copyWikilinksUseLinkText)
			linkText = hoveredLink.textContent;
		if (link.search(potentialWikilinkDetector) != -1) {
			link = links2Wikilinks(link, true, copyWikilinksSqBrackets, copyWikilinksSingleSqBrackets, linkText,
				copyWikilinksUseIwTemplate && !(
					hoveredLink.parentNode.className.match(/\binterwiki-/) && (
						 currentPrefix.match(/^:?$/) ||
						!currentPrefix.match(/^:?$/) && document.getElementsByClassName('interwiki-ru')[0]
					)
				)
			);
				// "iw" template is NOT generated when the hovered link is in the interwiki block on ruwiki, OR on other wiki AND there's ru interwiki
			if (copyWikilinksUseIwTemplate)
				iwTemplate = link;
			else if (link.search(linksDetector) == -1)
				wikilink = link;
		} else {
			link = processURI(link, 'link', copyWikilinksSingleSqBrackets, linkText);
				// This line is arguable and can be removed, if it will make URLs broken too often
		}
		if (iwTemplate) {
			prompt('Шаблон «iw» для страницы по этой ссылке:', iwTemplate);
		} else if (wikilink) {
			prompt('Вики-ссылка на страницу по этой ссылке:', wikilink);
		} else {
			prompt('Раскодированный адрес этой ссылки:', link);
		}
		focusEdge();
	} else {
		var hoveredElements = document.querySelectorAll(":hover");
		if (hoveredElements.length) {
			var hoveredElement = hoveredElements[hoveredElements.length-1];
			var currentElement = hoveredElement;
			var sectionHeadingElement;
			for (var i = 0; i < 300; i++) {
				switch (currentElement.nodeName) {
				case 'H2': case 'H3': case 'H4': case 'H5': case 'H6':
					sectionHeadingElement = currentElement;
					break;
				}
				if (!sectionHeadingElement) {
					if (currentElement.previousElementSibling) {
						currentElement = currentElement.previousElementSibling;
					} else {
						currentElement = currentElement.parentElement;
						if (!currentElement)
							break;
					}
				} else {
					//alert('found!' + i + ' ' + sectionHeadingElement.textContent);
					var mwHeadlineElement = sectionHeadingElement.getElementsByClassName('mw-headline')[0];
					if (mwHeadlineElement) {
						var anchor = mwHeadlineElement.id;
						if (!copyURL) {
							var processedAnchor = processURI(processAnchorAndSpaces(anchor), 'wikilink', false);
							if (processedAnchor) {
								wikilink = (copyWikilinksSqBrackets ? '[[' : '') + pagename + '#' + processedAnchor + (copyWikilinksSqBrackets ? ']]' : '');
								prompt('Вики-ссылка на этот раздел:', wikilink);
								focusEdge();
							}
						} else {
							link = processURI(location.href.split("#")[0] + '#' + anchor, 'link', copyWikilinksSingleSqBrackets);
							prompt('Раскодированная ссылка на этот раздел:', link);
							focusEdge();
						}
					}
					break;
				}
			}
		}
	}
}

function altyo() {
	var link, wikilink, iwTemplate;
	
	var selectionText = getSelectionText();
	var noSelection = false;
	var input = document.activeElement;
	var urlDecoderButton = document.querySelector('a[rel="urldecoder"]');
	var lastUrl;
	if (input.nodeName == 'TEXTAREA' && copyWikilinksUseUrlDecoder && urlDecoderButton) {
		urlDecoderButton.click();
	} else {
		var cursorPos;
		if (input.nodeName == 'TEXTAREA' ||
		    input.nodeName == 'INPUT' && (input.type == 'text' || input.type == 'search')) {
		 	if (!selectionText) {
				cursorPos = getInputSelection(input);
				if (cursorPos.start == cursorPos.end) {
					noSelection = true;
					cursorPos = cursorPos.start;
					lastUrl = input.value.substring(cursorPos - 1000, cursorPos).match(linkBeforeCursorDetector);
					if (lastUrl) {
						lastUrl = lastUrl[0];
						selectionText = lastUrl;
					}
				} else { // In Firefox, getSelectionText() doesn't work for text fields
					selectionText = input.value.substring(cursorPos.start, cursorPos.end);
				}
			}
		} else {
			selectionText = trim(selectionText);
		}
		if (!selectionText) {
		} else if (selectionText.search(lastUrl ? new RegExp('^' + potentialWikilinkDetector.source) : potentialWikilinkDetector) != -1) {
			wikilink = links2Wikilinks(selectionText, false, true, false);
			if (!wikilink) {
			} else if (input.nodeName == 'TEXTAREA' ||
			           input.nodeName == 'INPUT' && (input.type == 'text' || input.type == 'search')) {
				if (lastUrl)
					setCursorPos(input, cursorPos - lastUrl.length, cursorPos);
				replaceSelectedText(input, wikilink, !noSelection);
			} else {
				prompt('Вики-ссылка на страницу по этой ссылке:', wikilink);
			}
		} else if (selectionText.search(linksDetector) != -1) {
			link = selectionText.replace(linksDetector, function(s) { return processURI(s, 'link', false); });
			if (!link) {
			} else if (input.nodeName == 'TEXTAREA' ||
			           input.nodeName == 'INPUT' && (input.type == 'text' || input.type == 'search')) {
				if (lastUrl)
					setCursorPos(input, cursorPos - lastUrl.length, cursorPos);
				replaceSelectedText(input, link, !noSelection);
			} else {
				prompt('Раскодированный адрес этой ссылки:', link);
			}
		} else if (selectionText) {
			var decodedText = processURI(selectionText, 'text', false);
			if (decodedText && decodedText != selectionText) {
				if (input.nodeName == 'TEXTAREA' ||
				    input.nodeName == 'INPUT' && (input.type == 'text' || input.type == 'search')) {
					replaceSelectedText(input, decodedText);
				} else {
					prompt('Раскодированный текст:', decodedText);
				}
			}
		}
	}
}

var browser = navigator.userAgent.toLowerCase();
if (browser.indexOf('firefox') != -1) {
	document.addEventListener('keydown', function (event) {
		// keypress: ` is 96, ё is 1105; keydown: both are 192. In Firefox, ё is 0 for some reason,
		// along with some other keys.
		if ((copyWikilinksKeyCombination1 == 'ctrl+`' || copyWikilinksKeyCombination1 == 'ctrl+ё') &&
			event.ctrlKey && event.key == 'ё')
		{
			ctrlyo();
		}
		
		if ((copyWikilinksKeyCombination1 == 'ctrl+shift+`' || copyWikilinksKeyCombination1 == 'ctrl+shift+ё') &&
			event.ctrlKey && event.key == 'Ё')
		{
			ctrlshiftyo();
		}
		
		if ((copyWikilinksKeyCombination1 == 'alt+`' || copyWikilinksKeyCombination1 == 'alt+ё') &&
			event.altKey && event.key == 'ё')
		{
			altyo();
		}
	}, false);
}

})();