MediaWiki:Gadget-Popups-main.js

MediaWiki系统消息页面
/* eslint-disable no-unused-vars */
/* eslint-disable no-use-before-define */
/* global log, errlog, popupStrings */
'use strict';

/* <nowiki> */
/**
 * SPDX-License-Identifier: CC-BY-SA-4.0
 * _addText: '{{Gadget Header|license=CC-BY-SA-4.0}}'
 *
 * @source <en.wikipedia.org/wiki/MediaWiki:Gadget-popups.js>
 */
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e2) { throw _e2; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e3) { didErr = true; err = _e3; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
(function ($, mw) {
  // STARTFILE: main.js

  // Fix later
  $(function () {
    // Globals
    //

    // Trying to shove as many of these as possible into the pg (popup globals) object
    var pg = {
      api: {},
      // MediaWiki API requests
      re: {},
      // regexps
      ns: {},
      // namespaces
      string: {},
      // translatable strings
      wiki: {},
      // local site info
      user: {},
      // current user info
      misc: {},
      // YUCK PHOOEY
      option: {},
      // options, see newOption etc
      optionDefault: {},
      // default option values
      flag: {},
      // misc flags
      cache: {},
      // page and image cache
      structures: {},
      // navlink structures
      timer: {},
      // all sorts of timers (too damn many)
      counter: {},
      // .. and all sorts of counters
      current: {},
      // state info
      fn: {},
      // functions
      endoflist: null
    };

    /* Bail if the gadget/script is being loaded twice */
    /* An element with id "pg" would add a window.pg property, ignore such property */
    if (window.pg && !(window.pg instanceof HTMLElement)) {
      return;
    }

    /* Export to global context */
    window.pg = pg;

    // ENDFILE: main.js

    // STARTFILE: actions.js
    var setupTooltips = function setupTooltips(container, remove, force, popData) {
      log("setupTooltips, container=".concat(container, ", remove=").concat(remove));
      if (!container) {
        // the main initial call
        if (getValueOf('popupOnEditSelection') && document && document.editform && document.editform.wpTextbox1) {
          document.editform.wpTextbox1.onmouseup = doSelectionPopup;
        }
        // article/content is a structure-dependent thing
        container = defaultPopupsContainer();
      }
      if (!remove && !force && container.ranSetupTooltipsAlready) {
        return;
      }
      container.ranSetupTooltipsAlready = !remove;
      var anchors = container.getElementsByTagName('a');
      setupTooltipsLoop(anchors, 0, 250, 100, remove, popData);
    };
    var defaultPopupsContainer = function defaultPopupsContainer() {
      if (getValueOf('popupOnlyArticleLinks')) {
        return document.querySelector('.skin-vector-2022 .vector-body') || document.getElementById('mw_content') || document.getElementById('content') || document.getElementById('article') || document;
      }
      return document;
    };
    var setupTooltipsLoop = function setupTooltipsLoop(anchors, begin, howmany, sleep, remove, popData) {
      log(simplePrintf('setupTooltipsLoop(%s,%s,%s,%s,%s)', arguments));
      var finish = begin + howmany;
      var loopend = Math.min(finish, anchors.length);
      var j = loopend - begin;
      log("setupTooltips: anchors.length=".concat(anchors.length, ", begin=").concat(begin, ", howmany=").concat(howmany, ", loopend=").concat(loopend, ", remove=").concat(remove));
      var doTooltip = remove ? removeTooltip : addTooltip;
      // try a faster (?) loop construct
      if (j > 0) {
        do {
          var a = anchors[loopend - j];
          if (typeof a === 'undefined' || !a || !a.href) {
            log("got null anchor at index ".concat(loopend) - j);
            continue;
          }
          doTooltip(a, popData);
        } while (--j);
      }
      if (finish < anchors.length) {
        setTimeout(function () {
          setupTooltipsLoop(anchors, finish, howmany, sleep, remove, popData);
        }, sleep);
      } else {
        if (!remove && !getValueOf('popupTocLinks')) {
          rmTocTooltips();
        }
        pg.flag.finishedLoading = true;
      }
    };

    // eliminate popups from the TOC
    // This also kills any onclick stuff that used to be going on in the toc
    var rmTocTooltips = function rmTocTooltips() {
      var toc = document.getElementById('toc');
      if (toc) {
        var tocLinks = toc.getElementsByTagName('A');
        var tocLen = tocLinks.length;
        for (var j = 0; j < tocLen; ++j) {
          removeTooltip(tocLinks[j], true);
        }
      }
    };
    var addTooltip = function addTooltip(a, popData) {
      if (!isPopupLink(a)) {
        return;
      }
      a.onmouseover = mouseOverWikiLink;
      a.onmouseout = mouseOutWikiLink;
      a.onmousedown = killPopup;
      a.hasPopup = true;
      a.popData = popData;
    };
    var removeTooltip = function removeTooltip(a) {
      if (!a.hasPopup) {
        return;
      }
      a.onmouseover = null;
      a.onmouseout = null;
      if (a.originalTitle) {
        a.title = a.originalTitle;
      }
      a.hasPopup = false;
    };
    var removeTitle = function removeTitle(a) {
      if (!a.originalTitle) {
        a.originalTitle = a.title;
      }
      a.title = '';
    };
    var restoreTitle = function restoreTitle(a) {
      if (a.title || !a.originalTitle) {
        return;
      }
      a.title = a.originalTitle;
    };
    var registerHooks = function registerHooks(np) {
      var popupMaxWidth = getValueOf('popupMaxWidth');
      if (typeof popupMaxWidth === 'number') {
        var setMaxWidth = function setMaxWidth() {
          np.mainDiv.style.maxWidth = "".concat(popupMaxWidth, "px");
          np.maxWidth = popupMaxWidth;
        };
        np.addHook(setMaxWidth, 'unhide', 'before');
      }
      np.addHook(addPopupShortcuts, 'unhide', 'after');
      np.addHook(rmPopupShortcuts, 'hide', 'before');
    };
    var removeModifierKeyHandler = function removeModifierKeyHandler(a) {
      //remove listeners for modifier key if any that were added in mouseOverWikiLink
      document.removeEventListener('keydown', a.modifierKeyHandler, false);
      document.removeEventListener('keyup', a.modifierKeyHandler, false);
    };
    var mouseOverWikiLink = function mouseOverWikiLink(evt) {
      if (!evt && window.event) {
        evt = window.event;
      }

      // if the modifier is needed, listen for it,
      // we will remove the listener when we mouseout of this link or kill popup.
      if (getValueOf('popupModifier')) {
        // if popupModifierAction = enable, we should popup when the modifier is pressed
        // if popupModifierAction = disable, we should popup unless the modifier is pressed
        var action = getValueOf('popupModifierAction');
        var key = action === 'disable' ? 'keyup' : 'keydown';
        var a = this;
        a.modifierKeyHandler = function (evt) {
          mouseOverWikiLink2(a, evt);
        };
        document.addEventListener(key, a.modifierKeyHandler, false);
      }
      return mouseOverWikiLink2(this, evt);
    };

    /**
     * Gets the references list item that the provided footnote link targets. This
     * is typically a li element within the ol.references element inside the reflist.
     * @param {Element} a - A footnote link.
     * @returns {Element|boolean} The targeted element, or false if one can't be found.
     */
    var footnoteTarget = function footnoteTarget(a) {
      var aTitle = Title.fromAnchor(a);
      // We want ".3A" rather than "%3A" or "?" here, so use the anchor property directly
      var anch = aTitle.anchor;
      if (!/^(cite_note-|_note-|endnote)/.test(anch)) {
        return false;
      }
      var lTitle = Title.fromURL(location.href);
      if (lTitle.toString(true) !== aTitle.toString(true)) {
        return false;
      }
      var el = document.getElementById(anch);
      while (el && typeof el.nodeName === 'string') {
        var nt = el.nodeName.toLowerCase();
        if (nt === 'li') {
          return el;
        } else if (nt === 'body') {
          return false;
        } else if (el.parentNode) {
          el = el.parentNode;
        } else {
          return false;
        }
      }
      return false;
    };
    var footnotePreview = function footnotePreview(x, navpop) {
      setPopupHTML("<hr />".concat(x.innerHTML), 'popupPreview', navpop.idNumber);
    };
    var modifierPressed = function modifierPressed(evt) {
      var mod = getValueOf('popupModifier');
      if (!mod) {
        return false;
      }
      if (!evt && window.event) {
        evt = window.event;
      }
      return evt && mod && evt["".concat(mod.toLowerCase(), "Key")];
    };

    // Checks if the correct modifier pressed/unpressed if needed
    var isCorrectModifier = function isCorrectModifier(_a, evt) {
      if (!getValueOf('popupModifier')) {
        return true;
      }
      // if popupModifierAction = enable, we should popup when the modifier is pressed
      // if popupModifierAction = disable, we should popup unless the modifier is pressed
      var action = getValueOf('popupModifierAction');
      return action === 'enable' && modifierPressed(evt) || action === 'disable' && !modifierPressed(evt);
    };
    var mouseOverWikiLink2 = function mouseOverWikiLink2(a, evt) {
      if (!isCorrectModifier(a, evt)) {
        return;
      }
      if (getValueOf('removeTitles')) {
        removeTitle(a);
      }
      if (a === pg.current.link && a.navpopup && a.navpopup.isVisible()) {
        return;
      }
      pg.current.link = a;
      if (getValueOf('simplePopups') && !pg.option.popupStructure) {
        // reset *default value* of popupStructure
        setDefault('popupStructure', 'original');
      }
      var article = new Title().fromAnchor(a);
      // set global variable (ugh) to hold article (wikipage)
      pg.current.article = article;
      if (!a.navpopup) {
        a.navpopup = newNavpopup(a, article);
        pg.current.linksHash[a.href] = a.navpopup;
        pg.current.links.push(a);
      }
      if (a.navpopup.pending === null || a.navpopup.pending !== 0) {
        // either fresh popups or those with unfinshed business are redone from scratch
        simplePopupContent(a, article);
      }
      a.navpopup.showSoonIfStable(a.navpopup.delay);
      clearInterval(pg.timer.checkPopupPosition);
      pg.timer.checkPopupPosition = setInterval(checkPopupPosition, 600);
      if (getValueOf('simplePopups') && getValueOf('popupPreviewButton') && !a.simpleNoMore) {
        var d = document.createElement('div');
        d.className = 'popupPreviewButtonDiv';
        var s = document.createElement('span');
        d.appendChild(s);
        s.className = 'popupPreviewButton';
        s["on".concat(getValueOf('popupPreviewButtonEvent'))] = function () {
          a.simpleNoMore = true;
          d.style.display = 'none';
          nonsimplePopupContent(a, article);
        };
        s.innerHTML = popupString('show preview');
        setPopupHTML(d, 'popupPreview', a.navpopup.idNumber);
      }
      if (a.navpopup.pending !== 0) {
        nonsimplePopupContent(a, article);
      }
    };

    // simplePopupContent: the content that do not require additional download
    // (it is shown even when simplePopups is true)
    var simplePopupContent = function simplePopupContent(a, article) {
      /* FIXME hack */
      a.navpopup.hasPopupMenu = false;
      a.navpopup.setInnerHTML(popupHTML(a));
      fillEmptySpans({
        navpopup: a.navpopup
      });
      if (getValueOf('popupDraggable')) {
        var dragHandle = getValueOf('popupDragHandle') || null;
        if (dragHandle && dragHandle !== 'all') {
          dragHandle += a.navpopup.idNumber;
        }
        setTimeout(function () {
          a.navpopup.makeDraggable(dragHandle);
        }, 150);
      }
      if (getValueOf('popupRedlinkRemoval') && a.className === 'new') {
        setPopupHTML("<br>".concat(popupRedlinkHTML(article)), 'popupRedlink', a.navpopup.idNumber);
      }
    };
    var debugData = function debugData(navpopup) {
      if (getValueOf('popupDebugging') && navpopup.idNumber) {
        setPopupHTML("idNumber=".concat(navpopup.idNumber, ", pending=").concat(navpopup.pending), 'popupError', navpopup.idNumber);
      }
    };
    var newNavpopup = function newNavpopup(a, article) {
      var navpopup = new Navpopup();
      navpopup.fuzz = 5;
      navpopup.delay = getValueOf('popupDelay') * 1000;
      // increment global counter now
      navpopup.idNumber = ++pg.idNumber;
      navpopup.parentAnchor = a;
      navpopup.parentPopup = a.popData && a.popData.owner;
      navpopup.article = article;
      registerHooks(navpopup);
      return navpopup;
    };

    // Should we show nonsimple context?
    // If simplePopups is set to true, then we do not show nonsimple context,
    // but if a bottom "show preview" was clicked we do show nonsimple context
    var shouldShowNonSimple = function shouldShowNonSimple(a) {
      return !getValueOf('simplePopups') || a.simpleNoMore;
    };

    // Should we show nonsimple context govern by the option (e.g. popupUserInfo)?
    // If the user explicitly asked for nonsimple context by setting the option to true,
    // then we show it even in nonsimple mode.
    var shouldShow = function shouldShow(a, option) {
      if (shouldShowNonSimple(a)) {
        return getValueOf(option);
      }
      return typeof window[option] !== 'undefined' && window[option];
    };
    var nonsimplePopupContent = function nonsimplePopupContent(a, article) {
      var diff = null;
      var history = null;
      var params = parseParams(a.href);
      var oldid = typeof params.oldid === 'undefined' ? null : params.oldid;
      if (shouldShow(a, 'popupPreviewDiffs')) {
        diff = params.diff;
      }
      if (shouldShow(a, 'popupPreviewHistory')) {
        history = params.action === 'history';
      }
      a.navpopup.pending = 0;
      var referenceElement = footnoteTarget(a);
      if (referenceElement) {
        footnotePreview(referenceElement, a.navpopup);
      } else if (diff || diff === 0) {
        loadDiff(article, oldid, diff, a.navpopup);
      } else if (history) {
        loadAPIPreview('history', article, a.navpopup);
      } else if (shouldShowNonSimple(a) && pg.re.contribs.test(a.href)) {
        loadAPIPreview('contribs', article, a.navpopup);
      } else if (shouldShowNonSimple(a) && pg.re.backlinks.test(a.href)) {
        loadAPIPreview('backlinks', article, a.navpopup);
      } else if (
      // FIXME should be able to get all preview combinations with options
      article.namespaceId() === pg.nsImageId && (shouldShow(a, 'imagePopupsForImages') || !anchorContainsImage(a))) {
        loadAPIPreview('imagepagepreview', article, a.navpopup);
        loadImage(article, a.navpopup);
      } else {
        if (article.namespaceId() === pg.nsCategoryId && shouldShow(a, 'popupCategoryMembers')) {
          loadAPIPreview('category', article, a.navpopup);
        } else if ((article.namespaceId() === pg.nsUserId || article.namespaceId() === pg.nsUsertalkId) && shouldShow(a, 'popupUserInfo')) {
          loadAPIPreview('userinfo', article, a.navpopup);
        }
        if (shouldShowNonSimple(a)) {
          startArticlePreview(article, oldid, a.navpopup);
        }
      }
    };
    var pendingNavpopTask = function pendingNavpopTask(navpop) {
      if (navpop && navpop.pending === null) {
        navpop.pending = 0;
      }
      ++navpop.pending;
      debugData(navpop);
    };
    var completedNavpopTask = function completedNavpopTask(navpop) {
      if (navpop && navpop.pending) {
        --navpop.pending;
      }
      debugData(navpop);
    };
    var startArticlePreview = function startArticlePreview(article, oldid, navpop) {
      navpop.redir = 0;
      loadPreview(article, oldid, navpop);
    };
    var loadPreview = function loadPreview(article, oldid, navpop) {
      if (!navpop.redir) {
        navpop.originalArticle = article;
      }
      article.oldid = oldid;
      loadAPIPreview('revision', article, navpop);
    };
    var loadPreviewFromRedir = function loadPreviewFromRedir(redirMatch, navpop) {
      // redirMatch is a regex match
      var target = new Title().fromWikiText(redirMatch[2]);
      // overwrite (or add) anchor from original target
      // mediawiki does overwrite
      if (navpop.article.anchor) {
        target.anchor = navpop.article.anchor;
      }
      navpop.redir++;
      navpop.redirTarget = target;
      var warnRedir = redirLink(target, navpop.article);
      setPopupHTML(warnRedir, 'popupWarnRedir', navpop.idNumber);
      navpop.article = target;
      fillEmptySpans({
        redir: true,
        redirTarget: target,
        navpopup: navpop
      });
      return loadPreview(target, null, navpop);
    };
    var insertPreview = function insertPreview(download) {
      if (!download.owner) {
        return;
      }
      var redirMatch = pg.re.redirect.exec(download.data);
      if (download.owner.redir === 0 && redirMatch) {
        loadPreviewFromRedir(redirMatch, download.owner);
        return;
      }
      if (download.owner.visible || !getValueOf('popupLazyPreviews')) {
        insertPreviewNow(download);
      } else {
        var id = download.owner.redir ? 'PREVIEW_REDIR_HOOK' : 'PREVIEW_HOOK';
        download.owner.addHook(function () {
          insertPreviewNow(download);
          return true;
        }, 'unhide', 'after', id);
      }
    };
    var insertPreviewNow = function insertPreviewNow(download) {
      if (!download.owner) {
        return;
      }
      var wikiText = download.data;
      var navpop = download.owner;
      var art = navpop.redirTarget || navpop.originalArticle;
      makeFixDabs(wikiText, navpop);
      if (getValueOf('popupSummaryData')) {
        getPageInfo(wikiText, download);
        setPopupTrailer(getPageInfo(wikiText, download), navpop.idNumber);
      }
      var imagePage = '';
      if (art.namespaceId() === pg.nsImageId) {
        imagePage = art.toString();
      } else {
        imagePage = getValidImageFromWikiText(wikiText);
      }
      if (imagePage) {
        loadImage(Title.fromWikiText(imagePage), navpop);
      }
      if (getValueOf('popupPreviews')) {
        insertArticlePreview(download, art, navpop);
      }
    };
    var insertArticlePreview = function insertArticlePreview(download, art, navpop) {
      if (download && _typeof(download.data) === _typeof('')) {
        if (art.namespaceId() === pg.nsTemplateId && getValueOf('popupPreviewRawTemplates')) {
          // FIXME compare/consolidate with diff escaping code for wikitext
          var h = "<hr /><span style=\"font-family: monospace;\">".concat(download.data.entify().split('\\n').join('<br />\\n'), "</span>");
          setPopupHTML(h, 'popupPreview', navpop.idNumber);
        } else {
          var p = prepPreviewmaker(download.data, art, navpop);
          p.showPreview();
        }
      }
    };
    var prepPreviewmaker = function prepPreviewmaker(data, article, navpop) {
      // deal with tricksy anchors
      var d = anchorize(data, article.anchorString());
      var urlBase = joinPath([pg.wiki.articlebase, article.urlString()]);
      var p = new Previewmaker(d, urlBase, navpop);
      return p;
    };

    // Try to imitate the way mediawiki generates HTML anchors from section titles
    var anchorize = function anchorize(d, anch) {
      if (!anch) {
        return d;
      }
      var anchRe = new RegExp("(?:=+\\s*".concat(literalizeRegex(anch).replace(/[ _]/g, '[_ ]'), "\\s*=+|\\{\\{\\s*").concat(getValueOf('popupAnchorRegexp'), "\\s*(?:\\|[^|}]*)*?\\s*").concat(literalizeRegex(anch), "\\s*(?:\\|[^}]*)?}})"));
      var match = d.match(anchRe);
      if (match && match.length > 0 && match[0]) {
        return d.slice(Math.max(0, d.indexOf(match[0])));
      }

      // now try to deal with === foo [[bar|baz]] boom === -> #foo_baz_boom
      var lines = d.split('\n');
      for (var i = 0; i < lines.length; ++i) {
        lines[i] = lines[i].replace(new RegExp('[[]{2}([^|\\]]*?[|])?(.*?)[\\]]{2}', 'g'), '$2').replace(/'''([^'])/g, '$1').replace(new RegExp("''([^'])", 'g'), '$1');
        if (anchRe.test(lines[i])) {
          return d.split('\n').slice(i).join('\n').replace(new RegExp('^[^=]*'), '');
        }
      }
      return d;
    };
    var killPopup = function killPopup() {
      removeModifierKeyHandler(this);
      if (getValueOf('popupShortcutKeys')) {
        rmPopupShortcuts();
      }
      if (!pg) {
        return;
      }
      if (pg.current.link && pg.current.link.navpopup) {
        pg.current.link.navpopup.banish();
      }
      pg.current.link = null;
      abortAllDownloads();
      if (pg.timer.checkPopupPosition) {
        clearInterval(pg.timer.checkPopupPosition);
        pg.timer.checkPopupPosition = null;
      }
      return true; // preserve default action
    };

    // ENDFILE: actions.js

    // STARTFILE: domdrag.js
    /**
    @fileoverview
    The {@link Drag} object, which enables objects to be dragged around.
    
    <pre>
    *************************************************
    dom-drag.js
    09.25.2001
    www.youngpup.net
    **************************************************
    10.28.2001 - fixed minor bug where events
    sometimes fired off the handle, not the root.
    *************************************************
    Pared down, some hooks added by Lupin
    
    Copyright Aaron Boodman.
    Saying stupid things daily since March 2001.
    </pre>
    */

    /**
     * Creates a new Drag object. This is used to make various DOM elements draggable.
     * @constructor
     */
    var Drag = /*#__PURE__*/function () {
      function Drag() {
        _classCallCheck(this, Drag);
        /**
         * Condition to determine whether or not to drag. This function should take one parameter,
         * an Event.  To disable this, set it to <code>null</code>.
         * @type Function
         */
        this.startCondition = null;

        /**
         * Hook to be run when the drag finishes. This is passed the final coordinates of the
         * dragged object (two integers, x and y). To disables this, set it to <code>null</code>.
         * @type Function
         */
        this.endHook = null;
      }

      /**
       * Gets an event in a cross-browser manner.
       * @param {Event} e
       * @private
       */
      _createClass(Drag, [{
        key: "fixE",
        value: function fixE(e) {
          if (typeof e === 'undefined') {
            e = window.event;
          }
          if (typeof e.layerX === 'undefined') {
            e.layerX = e.offsetX;
          }
          if (typeof e.layerY === 'undefined') {
            e.layerY = e.offsetY;
          }
          return e;
        }

        /**
         * Initialises the Drag instance by telling it which object you want to be draggable, and what
         * you want to drag it by.
         * @param {DOMElement} o The "handle" by which <code>oRoot</code> is dragged.
         * @param {DOMElement} oRoot The object which moves when <code>o</code> is dragged, or <code>o</code> if omitted.
         */
      }, {
        key: "init",
        value: function init(o, oRoot) {
          var dragObj = this;
          this.obj = o;
          o.onmousedown = function (e) {
            dragObj.start(e);
          };
          o.dragging = false;
          o.popups_draggable = true;
          o.hmode = true;
          o.vmode = true;
          o.root = oRoot ? oRoot : o;
          if (Number.isNaN(Number.parseInt(o.root.style.left, 10))) {
            o.root.style.left = '0px';
          }
          if (Number.isNaN(Number.parseInt(o.root.style.top, 10))) {
            o.root.style.top = '0px';
          }
          o.root.onthisStart = function () {};
          o.root.onthisEnd = function () {};
          o.root.onthis = function () {};
        }

        /**
         * Starts the drag.
         * @private
         * @param {Event} e
         */
      }, {
        key: "start",
        value: function start(e) {
          var o = this.obj; // = this;
          e = this.fixE(e);
          if (this.startCondition && !this.startCondition(e)) {
            return;
          }
          var y = Number.parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10);
          var x = Number.parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10);
          o.root.onthisStart(x, y);
          o.lastMouseX = e.clientX;
          o.lastMouseY = e.clientY;
          var dragObj = this;
          o.onmousemoveDefault = document.onmousemove;
          o.dragging = true;
          document.onmousemove = function (e) {
            dragObj.drag(e);
          };
          document.onmouseup = function (e) {
            dragObj.end(e);
          };
          return false;
        }

        /**
         * Does the drag.
         * @param {Event} e
         * @private
         */
      }, {
        key: "drag",
        value: function drag(e) {
          e = this.fixE(e);
          var o = this.obj;
          var ey = e.clientY;
          var ex = e.clientX;
          var y = Number.parseInt(o.vmode ? o.root.style.top : o.root.style.bottom, 10);
          var x = Number.parseInt(o.hmode ? o.root.style.left : o.root.style.right, 10);
          var nx = x + (ex - o.lastMouseX) * (o.hmode ? 1 : -1);
          var ny = y + (ey - o.lastMouseY) * (o.vmode ? 1 : -1);
          this.obj.root.style[o.hmode ? 'left' : 'right'] = "".concat(nx, "px");
          this.obj.root.style[o.vmode ? 'top' : 'bottom'] = "".concat(ny, "px");
          this.obj.lastMouseX = ex;
          this.obj.lastMouseY = ey;
          this.obj.root.onthis(nx, ny);
          return false;
        }

        /**
         * Ends the drag.
         * @private
         */
      }, {
        key: "end",
        value: function end() {
          document.onmousemove = this.obj.onmousemoveDefault;
          document.onmouseup = null;
          this.obj.dragging = false;
          if (this.endHook) {
            this.endHook(Number.parseInt(this.obj.root.style[this.obj.hmode ? 'left' : 'right'], 10), Number.parseInt(this.obj.root.style[this.obj.vmode ? 'top' : 'bottom'], 10));
          }
        }
      }]);
      return Drag;
    }(); // ENDFILE: domdrag.js
    // STARTFILE: structures.js
    pg.structures.original = {};
    pg.structures.original.popupLayout = function () {
      return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupUserData', 'popupData', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
    };
    pg.structures.original.popupRedirSpans = function () {
      return ['popupRedir', 'popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'];
    };
    pg.structures.original.popupTitle = function (x) {
      log('defaultstructure.popupTitle');
      if (!getValueOf('popupNavLinks')) {
        return navlinkStringToHTML('<b><<mainlink>></b>', x.article, x.params);
      }
      return '';
    };
    pg.structures.original.popupTopLinks = function (x) {
      log('defaultstructure.popupTopLinks');
      if (getValueOf('popupNavLinks')) {
        return navLinksHTML(x.article, x.hint, x.params);
      }
      return '';
    };
    pg.structures.original.popupImage = function (x) {
      log("original.popupImage, x.article=".concat(x.article, ", x.navpop.idNumber=").concat(x.navpop.idNumber));
      return imageHTML(x.article, x.navpop.idNumber);
    };
    pg.structures.original.popupRedirTitle = pg.structures.original.popupTitle;
    pg.structures.original.popupRedirTopLinks = pg.structures.original.popupTopLinks;
    var copyStructure = function copyStructure(oldStructure, newStructure) {
      pg.structures[newStructure] = {};
      for (var prop in pg.structures[oldStructure]) {
        pg.structures[newStructure][prop] = pg.structures[oldStructure][prop];
      }
    };
    copyStructure('original', 'nostalgia');
    pg.structures.nostalgia.popupTopLinks = function (x) {
      var str = '';
      str += '<b><<mainlink|shortcut= >></b>';

      // user links
      // contribs - log - count - email - block
      // count only if applicable; block only if popupAdminLinks
      str += 'if(user){<br><<contribs|shortcut=c>>';
      str += 'if(wikimedia){*<<count|shortcut=#>>}';
      str += 'if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>}}';

      // editing links
      // talkpage   -> edit|new - history - un|watch - article|edit
      // other page -> edit - history - un|watch - talk|edit|new
      var editstr = '<<edit|shortcut=e>>';
      var editOldidStr = "if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{".concat(editstr, "}");
      var historystr = '<<history|shortcut=h>>';
      var watchstr = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
      str += "<br>if(talk){".concat(editOldidStr, "|<<new|shortcut=+>>") + "*".concat(historystr, "*").concat(watchstr, "*") + "<b><<article|shortcut=a>></b>|<<editArticle|edit>>" + "}else{".concat(
      // not a talk page
      editOldidStr, "*").concat(historystr, "*").concat(watchstr, "*") + "<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}";

      // misc links
      str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>';
      str += 'if(admin){<br>}else{*}<<move|shortcut=m>>';

      // admin links
      str += 'if(admin){*<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>}';
      return navlinkStringToHTML(str, x.article, x.params);
    };
    pg.structures.nostalgia.popupRedirTopLinks = pg.structures.nostalgia.popupTopLinks;

    /** -- fancy -- **/
    copyStructure('original', 'fancy');
    pg.structures.fancy.popupTitle = function (x) {
      return navlinkStringToHTML('<font size=+0><<mainlink>></font>', x.article, x.params);
    };
    pg.structures.fancy.popupTopLinks = function (x) {
      var hist = '<<history|shortcut=h|hist>>|<<lastEdit|shortcut=/|last>>|<<editors|shortcut=E|eds>>';
      var watch = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
      var move = '<<move|shortcut=m|move>>';
      return navlinkStringToHTML("if(talk){" + "<<edit|shortcut=e>>|<<new|shortcut=+|+>>*".concat(hist, "*") + "<<article|shortcut=a>>|<<editArticle|edit>>" + "*".concat(watch, "*").concat(move, "}else{<<edit|shortcut=e>>*").concat(hist, "*<<talk|shortcut=t|>>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>") + "*".concat(watch, "*").concat(move, "}<br>"), x.article, x.params);
    };
    pg.structures.fancy.popupOtherLinks = function (x) {
      var admin = '<<unprotect|unprotectShort>>|<<protect|shortcut=p>>*<<undelete|undeleteShort>>|<<delete|shortcut=d|del>>';
      var user = '<<contribs|shortcut=c>>if(wikimedia){|<<count|shortcut=#|#>>}';
      user += "if(ipuser){|<<arin>>}else{*<<email|shortcut=E|".concat(popupString('email'), ">>}if(admin){*<<block|shortcut=b>>}");
      var normal = '<<whatLinksHere|shortcut=l|links here>>*<<relatedChanges|shortcut=r|related>>';
      return navlinkStringToHTML("<br>if(user){".concat(user, "*}if(admin){").concat(admin, "if(user){<br>}else{*}}").concat(normal), x.article, x.params);
    };
    pg.structures.fancy.popupRedirTitle = pg.structures.fancy.popupTitle;
    pg.structures.fancy.popupRedirTopLinks = pg.structures.fancy.popupTopLinks;
    pg.structures.fancy.popupRedirOtherLinks = pg.structures.fancy.popupOtherLinks;

    /** -- fancy2 -- **/
    // hack for MacGyverMagic
    copyStructure('fancy', 'fancy2');
    pg.structures.fancy2.popupTopLinks = function (x // hack out the <br> at the end and put one at the beginning
    ) {
      return "<br>".concat(pg.structures.fancy.popupTopLinks(x).replace(new RegExp('<br>$', 'i'), ''));
    };
    pg.structures.fancy2.popupLayout = function () {
      return (
        // move toplinks to after the title
        ['popupError', 'popupImage', 'popupTitle', 'popupUserData', 'popupData', 'popupTopLinks', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab']
      );
    };

    /** -- menus -- **/
    copyStructure('original', 'menus');
    pg.structures.menus.popupLayout = function () {
      return ['popupError', 'popupImage', 'popupTopLinks', 'popupTitle', 'popupOtherLinks', 'popupRedir', ['popupWarnRedir', 'popupRedirTopLinks', 'popupRedirTitle', 'popupRedirData', 'popupRedirOtherLinks'], 'popupUserData', 'popupData', 'popupMiscTools', ['popupRedlink'], 'popupPrePreviewSep', 'popupPreview', 'popupSecondPreview', 'popupPreviewMore', 'popupPostPreview', 'popupFixDab'];
    };
    pg.structures.menus.popupTopLinks = function (x, shorter) {
      // FIXME maybe this stuff should be cached
      var s = [];
      var dropdiv = '<div class="popup_drop">';
      var enddiv = '</div>';
      var hist = '<<history|shortcut=h>>';
      if (!shorter) {
        hist = "<menurow>".concat(hist, "|<<historyfeed|rss>>|<<editors|shortcut=E>></menurow>");
      }
      var lastedit = '<<lastEdit|shortcut=/|show last edit>>';
      var thank = 'if(diff){<<thank|send thanks>>}';
      var jsHistory = '<<lastContrib|last set of edits>><<sinceMe|changes since mine>>';
      var linkshere = '<<whatLinksHere|shortcut=l|what links here>>';
      var related = '<<relatedChanges|shortcut=r|related changes>>';
      var search = '<menurow><<search|shortcut=s>>if(wikimedia){|<<globalsearch|shortcut=g|global>>}' + '|<<google|shortcut=G|web>></menurow>';
      var watch = '<menurow><<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>></menurow>';
      var protect = '<menurow><<unprotect|unprotectShort>>|' + '<<protect|shortcut=p>>|<<protectlog|log>></menurow>';
      var del = '<menurow><<undelete|undeleteShort>>|<<delete|shortcut=d>>|' + '<<deletelog|log>></menurow>';
      var move = '<<move|shortcut=m|move page>>';
      var nullPurge = '<menurow><<nullEdit|shortcut=n|null edit>>|<<purge|shortcut=P>></menurow>';
      var viewOptions = '<menurow><<view|shortcut=v>>|<<render|shortcut=S>>|<<raw>></menurow>';
      var editRow = 'if(oldid){' + '<menurow><<edit|shortcut=e>>|<<editOld|shortcut=e|this&nbsp;revision>></menurow>' + '<menurow><<revert|shortcut=v>>|<<undo>></menurow>' + '}else{<<edit|shortcut=e>>}';
      var markPatrolled = 'if(rcid){<<markpatrolled|mark patrolled>>}';
      var newTopic = 'if(talk){<<new|shortcut=+|new topic>>}';
      var protectDelete = "if(admin){".concat(protect).concat(del, "}");
      if (getValueOf('popupActionsMenu')) {
        s.push("<<mainlink>>*".concat(dropdiv).concat(menuTitle('actions')));
      } else {
        s.push("".concat(dropdiv, "<<mainlink>>"));
      }
      s.push('<menu>', editRow + markPatrolled + newTopic + hist + lastedit + thank);
      if (!shorter) {
        s.push(jsHistory);
      }
      s.push(move + linkshere + related);
      if (!shorter) {
        s.push(nullPurge + search);
      }
      if (!shorter) {
        s.push(viewOptions);
      }
      s.push("<hr />".concat(watch).concat(protectDelete), "<hr />" + "if(talk){<<article|shortcut=a|view article>><<editArticle|edit article>>}" + "else{<<talk|shortcut=t|talk page>><<editTalk|edit talk>>" + "<<newTalk|shortcut=+|new topic>>}</menu>".concat(enddiv));

      // user menu starts here
      var email = '<<email|shortcut=E|email user>>';
      var contribs = 'if(wikimedia){<menurow>}<<contribs|shortcut=c|contributions>>if(wikimedia){</menurow>}' + 'if(admin){<menurow><<deletedContribs>></menurow>}';
      s.push("if(user){*".concat(dropdiv).concat(menuTitle('user')), '<menu>', '<menurow><<userPage|shortcut=u|user&nbsp;page>>|<<userSpace|space>></menurow>', '<<userTalk|shortcut=t|user talk>><<editUserTalk|edit user talk>>' + '<<newUserTalk|shortcut=+|leave comment>>');
      if (!shorter) {
        s.push("if(ipuser){<<arin>>}else{".concat(email, "}"));
      } else {
        s.push("if(ipuser){}else{".concat(email, "}"));
      }
      s.push("<hr />".concat(contribs, "<<userlog|shortcut=L|user log>>"), 'if(wikimedia){<<count|shortcut=#|edit counter>>}', 'if(admin){<menurow><<unblock|unblockShort>>|<<block|shortcut=b|block user>></menurow>}', '<<blocklog|shortcut=B|block log>>', "</menu>".concat(enddiv, "}"));

      // popups menu starts here
      if (getValueOf('popupSetupMenu') && !x.navpop.hasPopupMenu /* FIXME: hack */) {
        x.navpop.hasPopupMenu = true;
        s.push("*".concat(dropdiv).concat(menuTitle('popupsMenu'), "<menu>"), '<<togglePreviews|toggle previews>>', '<<purgePopups|reset>>', '<<disablePopups|disable>>', "</menu>".concat(enddiv));
      }
      return navlinkStringToHTML(s.join(''), x.article, x.params);
    };
    var menuTitle = function menuTitle(s) {
      return "<a href=\"#\" noPopup=1>".concat(popupString(s), "</a>");
    };
    pg.structures.menus.popupRedirTitle = pg.structures.menus.popupTitle;
    pg.structures.menus.popupRedirTopLinks = pg.structures.menus.popupTopLinks;
    copyStructure('menus', 'shortmenus');
    pg.structures.shortmenus.popupTopLinks = function (x) {
      return pg.structures.menus.popupTopLinks(x, true);
    };
    pg.structures.shortmenus.popupRedirTopLinks = pg.structures.shortmenus.popupTopLinks;
    pg.structures.lite = {};
    pg.structures.lite.popupLayout = function () {
      return ['popupTitle', 'popupPreview'];
    };
    pg.structures.lite.popupTitle = function (x) {
      log("".concat(x.article, ": structures.lite.popupTitle"));
      //return navlinkStringToHTML('<b><<mainlink>></b>',x.article,x.params);
      return "<div><span class=\"popup_mainlink\"><b>".concat(x.article.toString(), "</b></span></div>");
    };
    // ENDFILE: structures.js

    // STARTFILE: autoedit.js

    var substitute = function substitute(data, cmdBody) {
      // alert('sub\nfrom: '+cmdBody.from+'\nto: '+cmdBody.to+'\nflags: '+cmdBody.flags);
      var fromRe = new RegExp(cmdBody.from, cmdBody.flags);
      return data.replace(fromRe, cmdBody.to);
    };
    var execCmds = function execCmds(data, cmdList) {
      var _iterator = _createForOfIteratorHelper(cmdList),
        _step;
      try {
        for (_iterator.s(); !(_step = _iterator.n()).done;) {
          var element = _step.value;
          data = element.action(data, element);
        }
      } catch (err) {
        _iterator.e(err);
      } finally {
        _iterator.f();
      }
      return data;
    };
    var parseCmd = function parseCmd(str) {
      // returns a list of commands
      if (str.length === 0) {
        return [];
      }
      var p = false;
      switch (str.charAt(0)) {
        case 's':
          {
            p = parseSubstitute(str);
            break;
          }
        default:
          {
            return false;
          }
      }
      if (p) {
        return [p].concat(parseCmd(p.remainder));
      }
      return false;
    };

    // FIXME: Only used once here, confusing with native (and more widely-used) unescape, should probably be replaced
    // Then again, unescape is semi-soft-deprecated, so we should look into replacing that too
    var unEscape = function unEscape(str, sep) {
      return str.split('\\\\').join('\\').split("\\".concat(sep)).join(sep).split('\\n').join('\n');
    };
    var parseSubstitute = function parseSubstitute(str) {
      // takes a string like s/a/b/flags;othercmds and parses it
      var from;
      var to;
      var flags;
      var tmp;
      if (str.length < 4) {
        return false;
      }
      var sep = str.charAt(1);
      str = str.slice(2);
      tmp = skipOver(str, sep);
      if (tmp) {
        from = tmp.segment;
        str = tmp.remainder;
      } else {
        return false;
      }
      tmp = skipOver(str, sep);
      if (tmp) {
        to = tmp.segment;
        str = tmp.remainder;
      } else {
        return false;
      }
      flags = '';
      if (str.length > 0) {
        tmp = skipOver(str, ';') || skipToEnd(str, ';');
        if (tmp) {
          flags = tmp.segment;
          str = tmp.remainder;
        }
      }
      return {
        action: substitute,
        from: from,
        to: to,
        flags: flags,
        remainder: str
      };
    };
    var skipOver = function skipOver(str, sep) {
      var endSegment = findNext(str, sep);
      if (endSegment < 0) {
        return false;
      }
      var segment = unEscape(str.slice(0, Math.max(0, endSegment)), sep);
      return {
        segment: segment,
        remainder: str.slice(Math.max(0, endSegment + 1))
      };
    };
    var skipToEnd = function skipToEnd(str, _sep) {
      return {
        segment: str,
        remainder: ''
      };
    };
    var findNext = function findNext(str, ch) {
      for (var i = 0; i < str.length; ++i) {
        if (str.charAt(i) === '\\') {
          i += 2;
        }
        if (str.charAt(i) === ch) {
          return i;
        }
      }
      return -1;
    };
    var setCheckbox = function setCheckbox(param, box) {
      var val = mw.util.getParamValue(param);
      if (val) {
        switch (val) {
          case '1':
          case 'yes':
          case 'true':
            {
              box.checked = true;
              break;
            }
          case '0':
          case 'no':
          case 'false':
            {
              box.checked = false;
            }
        }
      }
    };
    var autoEdit = function autoEdit() {
      setupPopups(function () {
        if (mw.util.getParamValue('autoimpl') !== popupString('autoedit_version')) {
          return false;
        }
        if (mw.util.getParamValue('autowatchlist') && mw.util.getParamValue('actoken') === autoClickToken()) {
          pg.fn.modifyWatchlist(mw.util.getParamValue('title'), mw.util.getParamValue('action'));
        }
        if (!document.editform) {
          return false;
        }
        if (autoEdit.alreadyRan) {
          return false;
        }
        autoEdit.alreadyRan = true;
        var cmdString = mw.util.getParamValue('autoedit');
        if (cmdString) {
          try {
            var editbox = document.editform.wpTextbox1;
            var cmdList = parseCmd(cmdString);
            var input = editbox.value;
            var output = execCmds(input, cmdList);
            editbox.value = output;
          } catch (_unused) {
            return;
          }
        }
        setCheckbox('autominor', document.editform.wpMinoredit);
        setCheckbox('autowatch', document.editform.wpWatchthis);
        var rvid = mw.util.getParamValue('autorv');
        if (rvid) {
          var url = "".concat(pg.wiki.apiwikibase, "?action=query&format=json&formatversion=2&prop=revisions&revids=").concat(rvid);
          startDownload(url, null, autoEdit2);
        } else {
          autoEdit2();
        }
      });
    };
    var autoEdit2 = function autoEdit2(d) {
      var summary = mw.util.getParamValue('autosummary');
      var summaryprompt = mw.util.getParamValue('autosummaryprompt');
      var summarynotice = '';
      if (d && d.data && mw.util.getParamValue('autorv')) {
        var s = getRvSummary(summary, d.data);
        if (s === false) {
          summaryprompt = true;
          summarynotice = popupString('Failed to get revision information, please edit manually.\n\n');
          summary = simplePrintf(summary, [mw.util.getParamValue('autorv'), '(unknown)', '(unknown)']);
        } else {
          summary = s;
        }
      }
      if (summaryprompt) {
        var txt = summarynotice + popupString('Enter a non-empty edit summary or press cancel to abort');
        var response = prompt(txt, summary);
        if (response) {
          summary = response;
        } else {
          return;
        }
      }
      if (summary) {
        document.editform.wpSummary.value = summary;
      }
      // Attempt to avoid possible premature clicking of the save button
      // (maybe delays in updates to the DOM are to blame?? or a red herring)
      setTimeout(autoEdit3, 100);
    };
    var autoClickToken = function autoClickToken() {
      return mw.user.sessionId();
    };
    var autoEdit3 = function autoEdit3() {
      if (mw.util.getParamValue('actoken') !== autoClickToken()) {
        return;
      }
      var btn = mw.util.getParamValue('autoclick');
      if (btn) {
        if (document.editform && document.editform[btn]) {
          var button = document.editform[btn];
          var msg = tprintf('The %s button has been automatically clicked. Please wait for the next page to load.', [button.value]);
          bannerMessage(msg);
          document.title = "(".concat(document.title, ")");
          button.click();
        } else {
          alert(tprintf('Could not find button %s. Please check the settings in your javascript file.', [btn]));
        }
      }
    };
    var bannerMessage = function bannerMessage(s) {
      var headings = document.querySelectorAll('h1');
      if (headings) {
        var div = document.createElement('div');
        div.innerHTML = "<font size=+1><b>".concat(pg.escapeQuotesHTML(s), "</b></font>");
        headings[0].parentNode.insertBefore(div, headings[0]);
      }
    };
    var getRvSummary = function getRvSummary(template, json) {
      try {
        var o = getJsObj(json);
        var edit = anyChild(o.query.pages).revisions[0];
        var timestamp = edit.timestamp.split(/[A-Z]/g).join(' ').replace(/^ *| *$/g, '');
        return simplePrintf(template, [edit.revid, timestamp, edit.userhidden ? '(hidden)' : edit.user]);
      } catch (_unused2) {
        return false;
      }
    };

    // ENDFILE: autoedit.js

    // STARTFILE: downloader.js
    /**
     * @fileoverview
     * {@link Downloader}, a xmlhttprequest wrapper, and helper functions.
     */

    /**
     * Creates a new Downloader
     * @constructor
     * @class The Downloader class. Create a new instance of this class to download stuff.
     * @param {String} url The url to download. This can be omitted and supplied later.
     */
    var Downloader = /*#__PURE__*/function () {
      function Downloader(url) {
        _classCallCheck(this, Downloader);
        if (typeof XMLHttpRequest !== 'undefined') {
          this.http = new XMLHttpRequest();
        }

        /**
         * The url to download
         * @type String
         */
        this.url = url;

        /**
         * A universally unique ID number
         * @type integer
         */
        this.id = null;

        /**
         * Modification date, to be culled from the incoming headers
         * @type Date
         * @private
         */
        this.lastModified = null;

        /**
         * What to do when the download completes successfully
         * @type Function
         * @private
         */
        this.callbackFunction = null;

        /**
         * What to do on failure
         * @type Function
         * @private
         */
        this.onFailure = null;

        /**
         * Flag set on <code>abort</code>
         * @type boolean
         */
        this.aborted = false;

        /**
         * HTTP method. See https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html for
         * possibilities.
         * @type String
         */
        this.method = 'GET';
        /**
                 Async flag.
                 @type boolean
             */
        this.async = true;
      }

      /** Submits the http request. */
      _createClass(Downloader, [{
        key: "send",
        value: function send(x) {
          if (!this.http) {
            return null;
          }
          return this.http.send(x);
        }

        /** Aborts the download, setting the <code>aborted</code> field to true.  */
      }, {
        key: "abort",
        value: function abort() {
          if (!this.http) {
            return null;
          }
          this.aborted = true;
          return this.http.abort();
        }

        /** Returns the downloaded data. */
      }, {
        key: "getData",
        value: function getData() {
          if (!this.http) {
            return null;
          }
          return this.http.responseText;
        }

        /** Prepares the download. */
      }, {
        key: "setTarget",
        value: function setTarget() {
          if (!this.http) {
            return null;
          }
          this.http.open(this.method, this.url, this.async);
          this.http.setRequestHeader('Api-User-Agent', pg.api.userAgent);
        }

        /** Gets the state of the download. */
      }, {
        key: "getReadyState",
        value: function getReadyState() {
          if (!this.http) {
            return null;
          }
          return this.http.readyState;
        }

        /**
         * Starts the download.
         * Note that setTarget {@link Downloader#setTarget} must be run first
         */
      }, {
        key: "start",
        value: function start() {
          if (!this.http) {
            return;
          }
          pg.misc.downloadsInProgress[this.id] = this;
          this.http.send(null);
        }

        /**
         * Gets the 'Last-Modified' date from the download headers.
         * Should be run after the download completes.
         * Returns <code>null</code> on failure.
         * @return {Date}
         */
      }, {
        key: "getLastModifiedDate",
        value: function getLastModifiedDate() {
          if (!this.http) {
            return null;
          }
          var lastmod = null;
          try {
            lastmod = this.http.getResponseHeader('Last-Modified');
          } catch (_unused3) {}
          if (lastmod) {
            return new Date(lastmod);
          }
          return null;
        }

        /**
         * Sets the callback function.
         * @param {Function} f callback function, called as <code>f(this)</code> on success
         */
      }, {
        key: "setCallback",
        value: function setCallback(f) {
          if (!this.http) {
            return;
          }
          this.http.onreadystatechange = f;
        }
      }, {
        key: "getStatus",
        value: function getStatus() {
          if (!this.http) {
            return null;
          }
          return this.http.status;
        }
      }]);
      return Downloader;
    }();
    new Downloader();
    pg.misc.downloadsInProgress = {};

    // helper functions

    /**
     * Creates a new {@link Downloader} and prepares it for action.
     * @param {String} url The url to download
     * @param {integer} id The ID of the {@link Downloader} object
     * @param {Function} callback The callback function invoked on success
     * @return {String/Downloader} the {@link Downloader} object created, or 'ohdear' if an unsupported browser
     */
    var newDownload = function newDownload(url, id, callback, onfailure) {
      var d = new Downloader(url);
      if (!d.http) {
        return 'ohdear';
      }
      d.id = id;
      d.setTarget();
      if (!onfailure) {
        onfailure = 2;
      }
      var f = function f() {
        if (d.getReadyState() === 4) {
          delete pg.misc.downloadsInProgress[this.id];
          try {
            if (d.getStatus() === 200) {
              d.data = d.getData();
              d.lastModified = d.getLastModifiedDate();
              callback(d);
            } else if (_typeof(onfailure) === _typeof(1)) {
              if (onfailure > 0) {
                // retry
                newDownload(url, id, callback, onfailure - 1);
              }
            } else if (typeof onfailure === 'function') {
              onfailure(d, url, id, callback);
            }
          } catch (_unused4) {
            /* ignore it */
          }
        }
      };
      d.setCallback(f);
      return d;
    };
    /**
    * Simulates a download from cached data.
    * The supplied data is put into a {@link Downloader} as if it had downloaded it.
    * @param {String} url The url.
    * @param {integer} id The ID.
    * @param {Function} callback The callback, which is invoked immediately as <code>callback(d)</code>,
    * where <code>d</code> is the new {@link Downloader}.
    * @param {String} data The (cached) data.
    * @param {Date} lastModified The (cached) last modified date.
    */
    var fakeDownload = function fakeDownload(url, id, callback, data, lastModified, owner) {
      var d = newDownload(url, callback);
      d.owner = owner;
      d.id = id;
      d.data = data;
      d.lastModified = lastModified;
      return callback(d);
    };

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

    /**
     * Aborts all downloads which have been started.
     */
    var abortAllDownloads = function abortAllDownloads() {
      for (var x in pg.misc.downloadsInProgress) {
        try {
          pg.misc.downloadsInProgress[x].aborted = true;
          pg.misc.downloadsInProgress[x].abort();
          delete pg.misc.downloadsInProgress[x];
        } catch (_unused5) {}
      }
    };
    // ENDFILE: downloader.js

    // STARTFILE: livepreview.js
    // TODO: location is often not correct (eg relative links in previews)
    // NOTE: removed md5 and image and math parsing. was broken, lots of bytes.
    /**
     * InstaView - a Mediawiki to HTML converter in JavaScript
     * Version 0.6.1
     * Copyright (C) Pedro Fayolle 2005-2006
     * <en.wikipedia.org/wiki/User:Pilaf>
     * Distributed under the BSD license
     *
     * Changelog:
     *
     * 0.6.1
     * - Fixed problem caused by \r characters
     * - Improved inline formatting parser
     *
     * 0.6
     * - Changed name to InstaView
     * - Some major code reorganizations and factored out some common functions
     * - Handled conversion of relative links (i.e. [[/foo]])
     * - Fixed misrendering of adjacent definition list items
     * - Fixed bug in table headings handling
     * - Changed date format in signatures to reflect Mediawiki's
     * - Fixed handling of [[:Image:...]]
     * - Updated MD5 function (hopefully it will work with UTF-8)
     * - Fixed bug in handling of links inside images
     *
     * To do:
     * - Better support for math tags
     * - Full support for <nowiki>
     * - Parser-based (as opposed to RegExp-based) inline wikicode handling (make it one-pass and
     *   bullet-proof)
     * - Support for templates (through AJAX)
     * - Support for coloured links (AJAX)
     */

    var Insta = {};
    var setupLivePreview = function setupLivePreview() {
      var _Insta$conf$user;
      // options
      Insta.conf = {
        baseUrl: '',
        user: {},
        wiki: {
          lang: pg.wiki.lang,
          interwiki: pg.wiki.interwiki,
          default_thumb_width: 180
        },
        paths: {
          articles: "".concat(pg.wiki.articlePath, "/"),
          // Only used for Insta previews with images. (not in popups)
          math: '/math/',
          images: '//upload.wikimedia.org/wikipedia/en/',
          images_fallback: '//upload.wikimedia.org/wikipedia/commons/'
        },
        locale: {
          user: mw.config.get('wgFormattedNamespaces')[pg.nsUserId],
          image: mw.config.get('wgFormattedNamespaces')[pg.nsImageId],
          category: mw.config.get('wgFormattedNamespaces')[pg.nsCategoryId],
          // shouldn't be used in popup previews, i think
          months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
        }
      };

      // options with default values or backreferences
      (_Insta$conf$user = Insta.conf.user).name || (_Insta$conf$user.name = 'Qiuwen Baike contributors');
      Insta.conf.user.signature = "[[".concat(Insta.conf.locale.user, ":").concat(Insta.conf.user.name, "|").concat(Insta.conf.user.name, "]]");
      //Insta.conf.paths.images = '//upload.wikimedia.org/wikipedia/' + Insta.conf.wiki.lang + '/';
      // define constants
      Insta.BLOCK_IMAGE = new RegExp("^\\[\\[(?:File|Image|".concat(Insta.conf.locale.image, "):.*?\\|.*?(?:frame|thumbnail|thumb|none|right|left|center)"), 'i');
    };
    Insta.dump = function (from, to) {
      if (typeof from === 'string') {
        from = document.getElementById(from);
      }
      if (typeof to === 'string') {
        to = document.getElementById(to);
      }
      to.innerHTML = this.convert(from.value);
    };
    Insta.convert = function (wiki) {
      var ll = typeof wiki === 'string' ? wiki.replace(/\r/g, '').split(/\n/) : wiki; // lines of wikicode

      var
      // output
      o = ''; // result of passing a regexp to compareLineStringOrReg()

      var
      // para flag
      p = 0;
      var r;

      // some shorthands
      var remain = function remain() {
        return ll.length;
      };
      var sh = function sh() {
        return ll.shift();
      }; // shift
      var ps = function ps(s) {
        o += s;
      }; // push

      // similar to C's printf, uses ? as placeholders, ?? to escape question marks
      var f = function f() {
        var i = 1;
        var a = arguments;
        var f = a[0];
        var o = '';
        var c;
        var p;
        for (; i < a.length; i++) {
          if ((p = f.indexOf('?')) + 1) {
            // allow character escaping
            i -= c = f.charAt(p + 1) === '?' ? 1 : 0;
            o += f.slice(0, Math.max(0, p)) + (c ? '?' : a[i]);
            f = f.slice(p + 1 + c);
          } else {
            break;
          }
        }
        return o + f;
      };
      var html_entities = function html_entities(s) {
        return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
      };

      // Wiki text parsing to html is a nightmare.
      // The below functions deliberately don't escape the ampersand since this would make it more
      // difficult, and we don't absolutely need to for how we need it. This means that any
      // unescaped ampersands in wikitext will remain unescaped and can cause invalid HTML.
      // Browsers should all be able to handle it though. We also escape significant wikimarkup
      // characters to prevent further matching on the processed text.
      var htmlescape_text = function htmlescape_text(s) {
        return s.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/:/g, '&#58;').replace(/\[/g, '&#91;').replace(/]/g, '&#93;');
      };
      var htmlescape_attr = function htmlescape_attr(s) {
        return htmlescape_text(s).replace(/'/g, '&#39;').replace(/"/g, '&quot;');
      };

      // return the first non matching character position between two strings
      var str_imatch = function str_imatch(a, b) {
        var i;
        var l;
        for (i = 0, l = Math.min(a.length, b.length); i < l; i++) {
          if (a.charAt(i) !== b.charAt(i)) {
            break;
          }
        }
        return i;
      };

      // compare current line against a string or regexp
      // if passed a string it will compare only the first string.length characters
      // if passed a regexp the result is stored in r
      var compareLineStringOrReg = function compareLineStringOrReg(c) {
        return typeof c === 'string' ? ll[0] && ll[0].slice(0, c.length) === c : r = ll[0] && ll[0].match(c);
      };
      var compareLineString = function compareLineString(c) {
        return ll[0] === c;
      }; // compare current line against a string

      var charAtPoint = function charAtPoint(p) {
        return ll[0].charAt(p);
      }; // return char at pos p

      var endl = function endl(s) {
        ps(s);
        sh();
      };
      var parse_list = function parse_list() {
        var prev = '';
        while (remain() && compareLineStringOrReg(/^([#*:;]+)(.*)$/)) {
          var l_match = r;
          sh();
          var ipos = str_imatch(prev, l_match[1]);

          // close uncontinued lists
          for (var prevPos = prev.length - 1; prevPos >= ipos; prevPos--) {
            var pi = prev.charAt(prevPos);
            if (pi === '*') {
              ps('</ul>');
            } else if (pi === '#') {
              ps('</ol>');
            } else if ($.inArray(l_match[1].charAt(prevPos), ['', '*', '#'])) {
              // close a dl only if the new item is not a dl item (:, ; or empty)
              ps('</dl>');
            }
          }

          // open new lists
          for (var matchPos = ipos; matchPos < l_match[1].length; matchPos++) {
            var li = l_match[1].charAt(matchPos);
            if (li === '*') {
              ps('<ul>');
            } else if (li === '#') {
              ps('<ol>');
            } else if ($.inArray(prev.charAt(matchPos), ['', '*', '#'])) {
              // open a new dl only if the prev item is not a dl item (:, ; or empty)
              ps('<dl>');
            }
          }
          switch (l_match[1].charAt(l_match[1].length - 1)) {
            case '*':
            case '#':
              {
                ps("<li>".concat(parse_inline_nowiki(l_match[2])));
                break;
              }
            case ';':
              {
                ps('<dt>');
                var dt_match = l_match[2].match(/(.*?)(:.*?)$/);

                // handle ;dt :dd format
                if (dt_match) {
                  ps(parse_inline_nowiki(dt_match[1]));
                  ll.unshift(dt_match[2]);
                } else {
                  ps(parse_inline_nowiki(l_match[2]));
                }
                break;
              }
            case ':':
              {
                ps("<dd>".concat(parse_inline_nowiki(l_match[2])));
              }
          }
          prev = l_match[1];
        }

        // close remaining lists
        for (var i = prev.length - 1; i >= 0; i--) {
          ps(f('</?>', prev.charAt(i) === '*' ? 'ul' : prev.charAt(i) === '#' ? 'ol' : 'dl'));
        }
      };
      var parse_table = function parse_table() {
        endl(f('<table>', compareLineStringOrReg(/^{\|( .*)$/) ? r[1] : ''));
        for (; remain();) {
          if (compareLineStringOrReg('|')) {
            switch (charAtPoint(1)) {
              case '}':
                {
                  endl('</table>');
                  return;
                }
              case '-':
                {
                  endl(f('<tr>', compareLineStringOrReg(/\|-*(.*)/)[1]));
                  break;
                }
              default:
                {
                  parse_table_data();
                }
            }
          } else if (compareLineStringOrReg('!')) {
            parse_table_data();
          } else {
            sh();
          }
        }
      };
      var parse_table_data = function parse_table_data() {
        var td_line;
        var match_i;

        // 1: "|+", '|' or '+'
        // 2: ??
        // 3: attributes ??
        // TODO: finish commenting this regexp
        var td_match = sh().match(/^(\|\+|\||!)((?:([^[|]*?)\|(?!\|))?(.*))$/);
        if (td_match[1] === '|+') {
          ps('<caption');
        } else {
          ps("<t".concat(td_match[1] === '|' ? 'd' : 'h'));
        }
        if (typeof td_match[3] !== 'undefined') {
          //ps(' ' + td_match[3])
          match_i = 4;
        } else {
          match_i = 2;
        }
        ps('>');
        if (td_match[1] !== '|+') {
          // use || or !! as a cell separator depending on context
          // NOTE: when split() is passed a regexp make sure to use non-capturing brackets
          td_line = td_match[match_i].split(td_match[1] === '|' ? '||' : /\|\||!!/);
          ps(parse_inline_nowiki(td_line.shift()));
          while (td_line.length > 0) {
            ll.unshift(td_match[1] + td_line.pop());
          }
        } else {
          ps(parse_inline_nowiki(td_match[match_i]));
        }
        var tc = 0;
        var td = [];
        while (remain()) {
          td.push(sh());
          if (compareLineStringOrReg('|')) {
            if (!tc) {
              break;
            } else if (charAtPoint(1) === '}') {
              // we're at the outer-most level (no nested tables), skip to td parse
              tc--;
            }
          } else if (!tc && compareLineStringOrReg('!')) {
            break;
          } else if (compareLineStringOrReg('{|')) {
            tc++;
          }
        }
        if (td.length > 0) {
          ps(Insta.convert(td));
        }
      };
      var parse_pre = function parse_pre() {
        ps('<pre>');
        do {
          endl("".concat(parse_inline_nowiki(ll[0].slice(1)), "\n"));
        } while (remain() && compareLineStringOrReg(' '));
        ps('</pre>');
      };
      var parse_block_image = function parse_block_image() {
        ps(parse_image(sh()));
      };
      var parse_image = function parse_image(str) {
        // get what's in between "[[Image:" and "]]"
        var tag = str.substring(str.indexOf(':') + 1, str.length - 2);
        var width;
        var attr = [];
        var filename;
        var caption = '';
        var thumb = 0;
        var frame = 0;
        var center = 0;
        var align = '';
        if (/\|/.test(tag)) {
          // manage nested links
          var nesting = 0;
          var last_attr;
          for (var i = tag.length - 1; i > 0; i--) {
            if (tag.charAt(i) === '|' && !nesting) {
              last_attr = tag.slice(i + 1);
              tag = tag.slice(0, Math.max(0, i));
              break;
            } else {
              switch (tag.slice(i - 1, i - 1 + 2)) {
                case ']]':
                  {
                    nesting++;
                    i--;
                    break;
                  }
                case '[[':
                  {
                    nesting--;
                    i--;
                  }
              }
            }
          }
          attr = tag.split(/\s*\|\s*/);
          attr.push(last_attr);
          filename = attr.shift();
          var w_match;
          for (; attr.length > 0; attr.shift()) {
            w_match = attr[0].match(/^(\d*)(?:[px]*\d*)?px$/);
            if (w_match) {
              width = w_match[1];
            } else {
              switch (attr[0]) {
                case 'thumb':
                case 'thumbnail':
                  {
                    thumb = true;
                    frame = true;
                    break;
                  }
                case 'frame':
                  {
                    frame = true;
                    break;
                  }
                case 'none':
                case 'right':
                case 'left':
                  {
                    center = false;
                    align = attr[0];
                    break;
                  }
                case 'center':
                  {
                    center = true;
                    align = 'none';
                    break;
                  }
                default:
                  {
                    if (attr.length === 1) {
                      caption = attr[0];
                    }
                  }
              }
            }
          }
        } else {
          filename = tag;
        }
        return '';
      };
      var parse_inline_nowiki = function parse_inline_nowiki(str) {
        var start;
        var lastend = 0;
        var substart = 0;
        var nestlev = 0;
        var open;
        var close;
        var subloop;
        var html = '';
        while (-1 !== (start = str.indexOf('<nowiki>', substart))) {
          html += parse_inline_wiki(str.substring(lastend, start));
          start += 8;
          substart = start;
          subloop = true;
          do {
            open = str.indexOf('<nowiki>', substart);
            close = str.indexOf('</nowiki>', substart);
            if (close <= open || open === -1) {
              if (close === -1) {
                return html + html_entities(str.slice(start));
              }
              substart = close + 9;
              if (nestlev) {
                nestlev--;
              } else {
                lastend = substart;
                html += html_entities(str.substring(start, lastend - 9));
                subloop = false;
              }
            } else {
              substart = open + 8;
              nestlev++;
            }
          } while (subloop);
        }
        return html + parse_inline_wiki(str.slice(lastend));
      };
      var parse_inline_images = function parse_inline_images(str) {
        var start;
        var substart = 0;
        var nestlev = 0;
        var loop;
        var close;
        var open;
        var wiki;
        var html;
        while (-1 !== (start = str.indexOf('[[', substart))) {
          if (new RegExp("^(Image|File|".concat(Insta.conf.locale.image, "):"), 'i').test(str.slice(start + 2))) {
            loop = true;
            substart = start;
            do {
              substart += 2;
              close = str.indexOf(']]', substart);
              open = str.indexOf('[[', substart);
              if (close <= open || open === -1) {
                if (close === -1) {
                  return str;
                }
                substart = close;
                if (nestlev) {
                  nestlev--;
                } else {
                  wiki = str.substring(start, close + 2);
                  html = parse_image(wiki);
                  str = str.replace(wiki, html);
                  substart = start + html.length;
                  loop = false;
                }
              } else {
                substart = open;
                nestlev++;
              }
            } while (loop);
          } else {
            break;
          }
        }
        return str;
      };

      // the output of this function doesn't respect the FILO structure of HTML
      // but since most browsers can handle it I'll save myself the hassle
      var parse_inline_formatting = function parse_inline_formatting(str) {
        var em;
        var st;
        var i;
        var li;
        var o = '';
        while ((i = str.indexOf("''", li)) + 1) {
          o += str.substring(li, i);
          li = i + 2;
          if (str.charAt(i + 2) === "'") {
            li++;
            st = !st;
            o += st ? '<strong>' : '</strong>';
          } else {
            em = !em;
            o += em ? '<em>' : '</em>';
          }
        }
        return o + str.slice(li);
      };
      var parse_inline_wiki = function parse_inline_wiki(str) {
        str = parse_inline_images(str);
        str = parse_inline_formatting(str);

        // math
        str = str.replace(/<math>(.*?)<\/math>/gi, '');

        // Build a Mediawiki-formatted date string
        var date = new Date();
        var minutes = date.getUTCMinutes();
        if (minutes < 10) {
          minutes = "0".concat(minutes);
        }
        date = f('?:?, ? ? ? (UTC)', date.getUTCHours(), minutes, date.getUTCDate(), Insta.conf.locale.months[date.getUTCMonth()], date.getUTCFullYear());

        // text formatting
        return str
        // signatures
        .replace(/~{5}(?!~)/g, date).replace(/~{4}(?!~)/g, "".concat(Insta.conf.user.name, " ").concat(date)).replace(/~{3}(?!~)/g, Insta.conf.user.name)
        // [[:Category:...]], [[:Image:...]], etc...
        .replace(new RegExp("\\[\\[:((?:".concat(Insta.conf.locale.category, "|Image|File|").concat(Insta.conf.locale.image, "|").concat(Insta.conf.wiki.interwiki, "):[^|]*?)\\]\\](\\w*)"), 'gi'), function (_$0, $1, $2) {
          return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));
        })
        // remove straight category and interwiki tags
        .replace(new RegExp("\\[\\[(?:".concat(Insta.conf.locale.category, "|").concat(Insta.conf.wiki.interwiki, "):.*?\\]\\]"), 'gi'), '')
        // [[:Category:...|Links]], [[:Image:...|Links]], etc...
        .replace(new RegExp("\\[\\[:((?:".concat(Insta.conf.locale.category, "|Image|File|").concat(Insta.conf.locale.image, "|").concat(Insta.conf.wiki.interwiki, "):.*?)\\|([^\\]]+?)\\]\\](\\w*)"), 'gi'), function (_$0, $1, $2, $3) {
          return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));
        })
        // [[/Relative links]]
        .replace(/\[\[(\/[^|]*?)]]/g, function (_$0, $1) {
          return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($1));
        })
        // [[/Replaced|Relative links]]
        .replace(/\[\[(\/.*?)\|(.+?)]]/g, function (_$0, $1, $2) {
          return f("<a href='?'>?</a>", Insta.conf.baseUrl + htmlescape_attr($1), htmlescape_text($2));
        })
        // [[Common links]]
        .replace(/\[\[([^[|]*?)]](\w*)/g, function (_$0, $1, $2) {
          return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($1) + htmlescape_text($2));
        })
        // [[Replaced|Links]]
        .replace(/\[\[([^[]*?)\|([^\]]+?)]](\w*)/g, function (_$0, $1, $2, $3) {
          return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1), htmlescape_text($2) + htmlescape_text($3));
        })
        // [[Stripped:Namespace|Namespace]]
        .replace(/\[\[([^\]]*?:)?(.*?)( *\(.*?\))?\|]]/g, function (_$0, $1, $2, $3) {
          return f("<a href='?'>?</a>", Insta.conf.paths.articles + htmlescape_attr($1) + htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($2));
        })
        // External links
        .replace(/\[(https?|news|ftp|mailto|gopher|irc):(\/*)([^\]]*?) (.*?)]/g, function (_$0, $1, $2, $3, $4) {
          return f("<a class='external' href='?:?'>?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($4));
        }).replace(/\[http:\/\/(.*?)]/g, function (_$0, $1) {
          return f("<a class='external' href='http://?'>[#]</a>", htmlescape_attr($1));
        }).replace(/\[(news|ftp|mailto|gopher|irc):(\/*)(.*?)]/g, function (_$0, $1, $2, $3) {
          return f("<a class='external' href='?:?'>?:?</a>", htmlescape_attr($1), htmlescape_attr($2) + htmlescape_attr($3), htmlescape_text($1), htmlescape_text($2) + htmlescape_text($3));
        }).replace(/(^| )(https?|news|ftp|mailto|gopher|irc):(\/*)([^ $]*[^ !$,.:;?])/g, function (_$0, $1, $2, $3, $4) {
          return f("?<a class='external' href='?:?'>?:?</a>", htmlescape_text($1), htmlescape_attr($2), htmlescape_attr($3) + htmlescape_attr($4), htmlescape_text($2), htmlescape_text($3) + htmlescape_text($4));
        }).replace('__NOTOC__', '').replace('__NOINDEX__', '').replace('__INDEX__', '').replace('__NOEDITSECTION__', '');
      };

      // begin parsing
      for (; remain();) {
        if (compareLineStringOrReg(/^(={1,6})(.*)\1(.*)$/)) {
          p = 0;
          endl(f('<h?>?</h?>?', r[1].length, parse_inline_nowiki(r[2]), r[1].length, r[3]));
        } else if (compareLineStringOrReg(/^[#*:;]/)) {
          p = 0;
          parse_list();
        } else if (compareLineStringOrReg(' ')) {
          p = 0;
          parse_pre();
        } else if (compareLineStringOrReg('{|')) {
          p = 0;
          parse_table();
        } else if (compareLineStringOrReg(/^----+$/)) {
          p = 0;
          endl('<hr />');
        } else if (compareLineStringOrReg(Insta.BLOCK_IMAGE)) {
          p = 0;
          parse_block_image();
        } else {
          // handle paragraphs
          if (compareLineString('')) {
            p = remain() > 1 && ll[1] === '';
            if (p) {
              endl('<p><br>');
            }
          } else {
            if (!p) {
              ps('<p>');
              p = 1;
            }
            ps("".concat(parse_inline_nowiki(ll[0]), " "));
          }
          sh();
        }
      }
      return o;
    };
    var wiki2html = function wiki2html(txt, baseurl) {
      Insta.conf.baseUrl = baseurl;
      return Insta.convert(txt);
    };
    // ENDFILE: livepreview.js

    // STARTFILE: pageinfo.js

    var popupFilterPageSize = function popupFilterPageSize(data) {
      return formatBytes(data.length);
    };
    var popupFilterCountLinks = function popupFilterCountLinks(data) {
      var num = countLinks(data);
      return "".concat(String(num), "&nbsp;").concat(num !== 1 ? popupString('wikiLinks') : popupString('wikiLink'));
    };
    var popupFilterCountImages = function popupFilterCountImages(data) {
      var num = countImages(data);
      return "".concat(String(num), "&nbsp;").concat(num !== 1 ? popupString('images') : popupString('image'));
    };
    var popupFilterCountCategories = function popupFilterCountCategories(data) {
      var num = countCategories(data);
      return "".concat(String(num), "&nbsp;").concat(num !== 1 ? popupString('categories') : popupString('category'));
    };
    var popupFilterLastModified = function popupFilterLastModified(_data, download) {
      var lastmod = download.lastModified;
      var now = new Date();
      var age = now - lastmod;
      if (lastmod && getValueOf('popupLastModified')) {
        return tprintf('%s old', [formatAge(age)]).replace(new RegExp(' ', 'g'), '&nbsp;');
      }
      return '';
    };
    var formatAge = function formatAge(age) {
      // coerce into a number
      var a = 0 + age;
      var aa = a;
      var seclen = 1000;
      var minlen = 60 * seclen;
      var hourlen = 60 * minlen;
      var daylen = 24 * hourlen;
      var weeklen = 7 * daylen;
      var numweeks = (a - a % weeklen) / weeklen;
      a = a - numweeks * weeklen;
      var sweeks = addunit(numweeks, 'week');
      var numdays = (a - a % daylen) / daylen;
      a = a - numdays * daylen;
      var sdays = addunit(numdays, 'day');
      var numhours = (a - a % hourlen) / hourlen;
      a = a - numhours * hourlen;
      var shours = addunit(numhours, 'hour');
      var nummins = (a - a % minlen) / minlen;
      a = a - nummins * minlen;
      var smins = addunit(nummins, 'minute');
      var numsecs = (a - a % seclen) / seclen;
      a = a - numsecs * seclen;
      var ssecs = addunit(numsecs, 'second');
      if (aa > 4 * weeklen) {
        return sweeks;
      }
      if (aa > weeklen) {
        return "".concat(sweeks, " ").concat(sdays);
      }
      if (aa > daylen) {
        return "".concat(sdays, " ").concat(shours);
      }
      if (aa > 6 * hourlen) {
        return shours;
      }
      if (aa > hourlen) {
        return "".concat(shours, " ").concat(smins);
      }
      if (aa > 10 * minlen) {
        return smins;
      }
      if (aa > minlen) {
        return "".concat(smins, " ").concat(ssecs);
      }
      return ssecs;
    };
    var addunit = function addunit(num, str) {
      return "".concat(num, " ").concat(num !== 1 ? popupString("".concat(str, "s")) : popupString(str));
    };
    var runPopupFilters = function runPopupFilters(list, data, download) {
      var ret = [];
      var _iterator2 = _createForOfIteratorHelper(list),
        _step2;
      try {
        for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
          var element = _step2.value;
          if (element && typeof element === 'function') {
            var s = element(data, download, download.owner.article);
            if (s) {
              ret.push(s);
            }
          }
        }
      } catch (err) {
        _iterator2.e(err);
      } finally {
        _iterator2.f();
      }
      return ret;
    };
    var getPageInfo = function getPageInfo(data, download) {
      if (!data || data.length === 0) {
        return popupString('Empty page');
      }
      var popupFilters = getValueOf('popupFilters') || [];
      var extraPopupFilters = getValueOf('extraPopupFilters') || [];
      var pageInfoArray = runPopupFilters(popupFilters.concat(extraPopupFilters), data, download);
      var pageInfo = pageInfoArray.join(', ');
      if (pageInfo !== '') {
        pageInfo = upcaseFirst(pageInfo);
      }
      return pageInfo;
    };

    // this could be improved!
    var countLinks = function countLinks(wikiText) {
      return wikiText.split('[[').length - 1;
    };

    // if N = # matches, n = # brackets, then
    // String.parenSplit(regex) intersperses the N+1 split elements
    // with Nn other elements. So total length is
    // L= N+1 + Nn = N(n+1)+1. So N=(L-1)/(n+1).

    var countImages = function countImages(wikiText) {
      return (wikiText.parenSplit(pg.re.image).length - 1) / (pg.re.imageBracketCount + 1);
    };
    var countCategories = function countCategories(wikiText) {
      return (wikiText.parenSplit(pg.re.category).length - 1) / (pg.re.categoryBracketCount + 1);
    };
    var popupFilterStubDetect = function popupFilterStubDetect(data, _download, article) {
      var counts = stubCount(data, article);
      if (counts.real) {
        return popupString('stub');
      }
      if (counts.sect) {
        return popupString('section stub');
      }
      return '';
    };
    var popupFilterDisambigDetect = function popupFilterDisambigDetect(data, _download, article) {
      if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
        return '';
      }
      return isDisambig(data, article) ? popupString('disambig') : '';
    };
    var formatBytes = function formatBytes(num) {
      return num > 949 ? Math.round(num / 100) / 10 + popupString('kB') : "".concat(num, "&nbsp;").concat(popupString('bytes'));
    };

    // ENDFILE: pageinfo.js

    // STARTFILE: titles.js
    /**
    * @fileoverview Defines the {@link Title} class, and associated crufty functions.
    
    * <code>Title</code> deals with article titles and their various
    * forms.  {@link Stringwrapper} is the parent class of
    * <code>Title</code>, which exists simply to make things a little
    * neater.
    */

    /**
    * Creates a new Stringwrapper.
    * @constructor
    
    * @class the Stringwrapper class. This base class is not really
    * useful on its own; it just wraps various common string operations.
    */
    var Stringwrapper = function Stringwrapper() {
      /**
       * Wrapper for this.toString().indexOf()
       * @param {String} x
       * @type integer
       */
      this.indexOf = function (x) {
        return this.toString().indexOf(x);
      };

      /**
       * Returns this.value.
       * @type String
       */
      this.toString = function () {
        return this.value;
      };

      /**
       * Wrapper for {@link String#parenSplit} applied to this.toString()
       * @param {RegExp} x
       * @type Array
       */
      this.parenSplit = function (x) {
        return this.toString().parenSplit(x);
      };

      /**
       * Wrapper for this.toString().substring()
       * @param {String} x
       * @param {String} y (optional)
       * @type String
       */
      this.substring = function (x, y) {
        if (typeof y === 'undefined') {
          return this.toString().slice(Math.max(0, x));
        }
        return this.toString().substring(x, y);
      };

      /**
       * Wrapper for this.toString().split()
       * @param {String} x
       * @type Array
       */
      this.split = function (x) {
        return this.toString().split(x);
      };

      /**
       * Wrapper for this.toString().replace()
       * @param {String} x
       * @param {String} y
       * @type String
       */
      this.replace = function (x, y) {
        return this.toString().replace(x, y);
      };
    };

    /**
     * Creates a new <code>Title</code>.
     * @constructor
     *
     * @class The Title class. Holds article titles and converts them into
     * various forms. Also deals with anchors, by which we mean the bits
     * of the article URL after a # character, representing locations
     * within an article.
     *
     * @param {String} value The initial value to assign to the
     * article. This must be the canonical title (see {@link
     * Title#value}. Omit this in the constructor and use another function
     * to set the title if this is unavailable.
     */
    var Title = /*#__PURE__*/function (_Stringwrapper) {
      _inherits(Title, _Stringwrapper);
      var _super = _createSuper(Title);
      function Title(val) {
        var _this;
        _classCallCheck(this, Title);
        _this = _super.call(this);
        /**
         * The canonical article title. This must be in UTF-8 with no
         * entities, escaping or nasties. Also, underscores should be
         * replaced with spaces.
         * @type String
         * @private
         */
        _this.value = null;

        /**
         * The canonical form of the anchor. This should be exactly as
         * it appears in the URL, i.e. with the .C3.0A bits in.
         * @type String
         */
        _this.anchor = '';
        _this.setUtf(val);
        return _this;
      }

      /**
       * Returns the canonical representation of the article title, optionally without anchor.
       * @param {boolean} omitAnchor
       * @fixme Decide specs for anchor
       * @return String The article title and the anchor.
       */
      _createClass(Title, [{
        key: "toString",
        value: function toString(omitAnchor) {
          return this.value + (!omitAnchor && this.anchor ? "#".concat(this.anchorString()) : '');
        }
      }, {
        key: "anchorString",
        value: function anchorString() {
          if (!this.anchor) {
            return '';
          }
          var split = this.anchor.parenSplit(/((?:\.[\dA-F]{2})+)/);
          var len = split.length;
          var value;
          for (var j = 1; j < len; j += 2) {
            // FIXME s/decodeURI/decodeURIComponent/g ?
            value = split[j].split('.').join('%');
            try {
              value = decodeURIComponent(value);
            } catch (_unused6) {
              // cannot decode
            }
            split[j] = value.split('_').join(' ');
          }
          return split.join('');
        }
      }, {
        key: "urlAnchor",
        value: function urlAnchor() {
          var split = this.anchor.parenSplit('/((?:[%][0-9A-F]{2})+)/');
          var len = split.length;
          for (var j = 1; j < len; j += 2) {
            split[j] = split[j].split('%').join('.');
          }
          return split.join('');
        }
      }, {
        key: "anchorFromUtf",
        value: function anchorFromUtf(str) {
          this.anchor = encodeURIComponent(str.split(' ').join('_')).split('%3A').join(':').split("'").join('%27').split('%').join('.');
        }
      }, {
        key: "fromURL",
        value: function fromURL(h) {
          if (typeof h !== 'string') {
            this.value = null;
            return this;
          }

          // NOTE : playing with decodeURI, encodeURI, escape, unescape,
          // we seem to be able to replicate the IE borked encoding

          // IE doesn't do this new-fangled utf-8 thing.
          // and it's worse than that.
          // IE seems to treat the query string differently to the rest of the url
          // the query is treated as bona-fide utf8, but the first bit of the url is pissed around with

          // we fix up & for all browsers, just in case.
          var splitted = h.split('?');
          splitted[0] = splitted[0].split('&').join('%26');
          h = splitted.join('?');
          var contribs = pg.re.contribs.exec(h);
          if (contribs) {
            if (contribs[1] === 'title=') {
              contribs[3] = contribs[3].split('+').join(' ');
            }
            var u = new Title(contribs[3]);
            this.setUtf(this.decodeNasties("".concat(mw.config.get('wgFormattedNamespaces')[pg.nsUserId], ":").concat(u.stripNamespace())));
            return this;
          }
          var email = pg.re.email.exec(h);
          if (email) {
            this.setUtf(this.decodeNasties("".concat(mw.config.get('wgFormattedNamespaces')[pg.nsUserId], ":").concat(new Title(email[3]).stripNamespace())));
            return this;
          }
          var backlinks = pg.re.backlinks.exec(h);
          if (backlinks) {
            this.setUtf(this.decodeNasties(new Title(backlinks[3])));
            return this;
          }

          // A dummy title object for a Special:Diff link.
          var specialdiff = pg.re.specialdiff.exec(h);
          if (specialdiff) {
            this.setUtf(this.decodeNasties(new Title("".concat(mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId], ":Diff"))));
            return this;
          }

          // no more special cases to check --
          // hopefully it's not a disguised user-related or specially treated special page
          // Includes references
          var m = pg.re.main.exec(h);
          if (m === null) {
            this.value = null;
          } else {
            var fromBotInterface = /\?(.+&)?title=/.test(h);
            if (fromBotInterface) {
              m[2] = m[2].split('+').join('_');
            }
            var extracted = m[2] + (m[3] ? "#".concat(m[3]) : '');
            if (pg.flag.isSafari && /%25[\dA-Fa-f]{2}/.test(extracted)) {
              // Fix Safari issue
              // Safari sometimes encodes % as %25 in UTF-8 encoded strings like %E5%A3 -> %25E5%25A3.
              this.setUtf(decodeURIComponent(unescape(extracted)));
            } else {
              this.setUtf(this.decodeNasties(extracted));
            }
          }
          return this;
        }
      }, {
        key: "decodeNasties",
        value: function decodeNasties(txt) {
          // myDecodeURI uses decodeExtras, which removes _,
          // thus ruining citations previews, which are formated as "cite_note-1"
          try {
            var ret = decodeURI(this.decodeEscapes(txt));
            ret = ret.replace(/[ _]*$/, '');
            return ret;
          } catch (_unused7) {
            return txt; // cannot decode
          }
        }

        // Decode valid %-encodings, otherwise escape them
      }, {
        key: "decodeEscapes",
        value: function decodeEscapes(txt) {
          var split = txt.parenSplit(/((?:%[\dA-Fa-f]{2})+)/);
          var len = split.length;
          // No %-encoded items found, so replace the literal %
          if (len === 1) {
            return split[0].replace(/%(?![\dA-Fa-f]{2})/g, '%25');
          }
          for (var i = 1; i < len; i = i + 2) {
            split[i] = decodeURIComponent(split[i]);
          }
          return split.join('');
        }
      }, {
        key: "fromAnchor",
        value: function fromAnchor(a) {
          if (!a) {
            this.value = null;
            return this;
          }
          return this.fromURL(a.href);
        }
      }, {
        key: "fromWikiText",
        value: function fromWikiText(txt) {
          // FIXME - testing needed
          txt = myDecodeURI(txt);
          this.setUtf(txt);
          return this;
        }
      }, {
        key: "hintValue",
        value: function hintValue() {
          if (!this.value) {
            return '';
          }
          return safeDecodeURI(this.value);
        }
      }, {
        key: "toUserName",
        value: function toUserName(withNs) {
          if (this.namespaceId() !== pg.nsUserId && this.namespaceId() !== pg.nsUsertalkId) {
            this.value = null;
            return;
          }
          this.value = (withNs ? "".concat(mw.config.get('wgFormattedNamespaces')[pg.nsUserId], ":") : '') + this.stripNamespace().split('/')[0];
        }
      }, {
        key: "userName",
        value: function userName(withNs) {
          var t = new Title(this.value);
          t.toUserName(withNs);
          if (t.value) {
            return t;
          }
          return null;
        }
      }, {
        key: "toTalkPage",
        value: function toTalkPage() {
          // convert article to a talk page, or if we can't, return null
          // In other words: return null if this ALREADY IS a talk page
          // and return the corresponding talk page otherwise
          //
          // Per https://www.mediawiki.org/wiki/Manual:Namespace#Subject_and_talk_namespaces
          // * All discussion namespaces have odd-integer indices
          // * The discussion namespace index for a specific namespace with index n is n + 1
          if (this.value === null) {
            return null;
          }
          var namespaceId = this.namespaceId();
          if (namespaceId >= 0 && namespaceId % 2 === 0) {
            //non-special and subject namespace
            var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId + 1];
            if (typeof localizedNamespace !== 'undefined') {
              if (localizedNamespace === '') {
                this.value = this.stripNamespace();
              } else {
                this.value = "".concat(localizedNamespace.split(' ').join('_'), ":").concat(this.stripNamespace());
              }
              return this.value;
            }
          }
          this.value = null;
          return null;
        }

        // Return canonical, localized namespace
      }, {
        key: "namespace",
        value: function namespace() {
          return mw.config.get('wgFormattedNamespaces')[this.namespaceId()];
        }
      }, {
        key: "namespaceId",
        value: function namespaceId() {
          var n = this.value.indexOf(':');
          if (n < 0) {
            return 0; // mainspace
          }

          var namespaceId = mw.config.get('wgNamespaceIds')[this.value.slice(0, Math.max(0, n)).split(' ').join('_').toLowerCase()];
          if (typeof namespaceId === 'undefined') {
            return 0; // mainspace
          }

          return namespaceId;
        }
      }, {
        key: "talkPage",
        value: function talkPage() {
          var t = new Title(this.value);
          t.toTalkPage();
          if (t.value) {
            return t;
          }
          return null;
        }
      }, {
        key: "isTalkPage",
        value: function isTalkPage() {
          if (this.talkPage() === null) {
            return true;
          }
          return false;
        }
      }, {
        key: "toArticleFromTalkPage",
        value: function toArticleFromTalkPage() {
          // largely copy/paste from toTalkPage above.
          if (this.value === null) {
            return null;
          }
          var namespaceId = this.namespaceId();
          if (namespaceId >= 0 && namespaceId % 2 === 1) {
            // non-special and talk namespace
            var localizedNamespace = mw.config.get('wgFormattedNamespaces')[namespaceId - 1];
            if (typeof localizedNamespace !== 'undefined') {
              if (localizedNamespace === '') {
                this.value = this.stripNamespace();
              } else {
                this.value = "".concat(localizedNamespace.split(' ').join('_'), ":").concat(this.stripNamespace());
              }
              return this.value;
            }
          }
          this.value = null;
          return null;
        }
      }, {
        key: "articleFromTalkPage",
        value: function articleFromTalkPage() {
          var t = new Title(this.value);
          t.toArticleFromTalkPage();
          if (t.value) {
            return t;
          }
          return null;
        }
      }, {
        key: "articleFromTalkOrArticle",
        value: function articleFromTalkOrArticle() {
          var t = new Title(this.value);
          if (t.toArticleFromTalkPage()) {
            return t;
          }
          return this;
        }
      }, {
        key: "isIpUser",
        value: function isIpUser() {
          return pg.re.ipUser.test(this.userName());
        }
      }, {
        key: "stripNamespace",
        value: function stripNamespace() {
          // returns a string, not a Title
          var n = this.value.indexOf(':');
          if (n < 0) {
            return this.value;
          }
          var namespaceId = this.namespaceId();
          if (namespaceId === pg.nsMainspaceId) {
            return this.value;
          }
          return this.value.slice(Math.max(0, n + 1));
        }
      }, {
        key: "setUtf",
        value: function setUtf(value) {
          if (!value) {
            this.value = '';
            return;
          }
          var anch = value.indexOf('#');
          if (anch < 0) {
            this.value = value.split('_').join(' ');
            this.anchor = '';
            return;
          }
          this.value = value.slice(0, Math.max(0, anch)).split('_').join(' ');
          this.anchor = value.slice(Math.max(0, anch + 1));
          this.ns = null; // wait until namespace() is called
        }
      }, {
        key: "setUrl",
        value: function setUrl(urlfrag) {
          var anch = urlfrag.indexOf('#');
          this.value = safeDecodeURI(urlfrag.slice(0, Math.max(0, anch)));
          this.anchor = this.value.slice(Math.max(0, anch + 1));
        }
      }, {
        key: "append",
        value: function append(x) {
          this.setUtf(this.value + x);
        }
      }, {
        key: "urlString",
        value: function urlString(x) {
          if (!x) {
            x = {};
          }
          var v = this.toString(true);
          if (!x.omitAnchor && this.anchor) {
            v += "#".concat(this.urlAnchor());
          }
          if (!x.keepSpaces) {
            v = v.split(' ').join('_');
          }
          return encodeURI(v).split('&').join('%26').split('?').join('%3F').split('+').join('%2B');
        }
      }, {
        key: "removeAnchor",
        value: function removeAnchor() {
          return new Title(this.toString(true));
        }
      }, {
        key: "toUrl",
        value: function toUrl() {
          return pg.wiki.titlebase + this.urlString();
        }
      }]);
      return Title;
    }(Stringwrapper);
    Title.fromURL = function (h) {
      return new Title().fromURL(h);
    };
    Title.fromAnchor = function (a) {
      return new Title().fromAnchor(a);
    };
    Title.fromWikiText = function (txt) {
      return new Title().fromWikiText(txt);
    };
    var parseParams = function parseParams(url) {
      var specialDiff = pg.re.specialdiff.exec(url);
      if (specialDiff) {
        var split = specialDiff[1].split('/');
        if (split.length === 1) {
          return {
            oldid: split[0],
            diff: 'prev'
          };
        } else if (split.length === 2) {
          return {
            oldid: split[0],
            diff: split[1]
          };
        }
      }
      var ret = {};
      if (!url.includes('?')) {
        return ret;
      }
      url = url.split('#')[0];
      var s = url.split('?').slice(1).join(',');
      var t = s.split('&');
      var _iterator3 = _createForOfIteratorHelper(t),
        _step3;
      try {
        for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
          var element = _step3.value;
          var z = element.split('=');
          z.push(null);
          ret[z[0]] = z[1];
        }
        // Diff revision with no oldid is interpreted as a diff to the previous revision by MediaWiki
      } catch (err) {
        _iterator3.e(err);
      } finally {
        _iterator3.f();
      }
      if (ret.diff && typeof ret.oldid === 'undefined') {
        ret.oldid = 'prev';
      }
      // Documentation seems to say something different, but oldid can also accept prev/next, and
      // Echo is emitting such URLs. Simple fixup during parameter decoding:
      if (ret.oldid && (ret.oldid === 'prev' || ret.oldid === 'next' || ret.oldid === 'cur')) {
        var helper = ret.diff;
        ret.diff = ret.oldid;
        ret.oldid = helper;
      }
      return ret;
    };

    // (a) myDecodeURI (first standard decodeURI, then pg.re.urlNoPopup)
    // (b) change spaces to underscores
    // (c) encodeURI (just the straight one, no pg.re.urlNoPopup)

    var myDecodeURI = function myDecodeURI(str) {
      var ret;
      // FIXME decodeURIComponent??
      try {
        ret = decodeURI(str.toString());
      } catch (_unused8) {
        return str;
      }
      for (var i = 0; i < pg.misc.decodeExtras.length; ++i) {
        var from = pg.misc.decodeExtras[i].from;
        var to = pg.misc.decodeExtras[i].to;
        ret = ret.split(from).join(to);
      }
      return ret;
    };
    var safeDecodeURI = function safeDecodeURI(str) {
      var ret = myDecodeURI(str);
      return ret || str;
    };

    // TESTS //

    var isDisambig = function isDisambig(data, article) {
      if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
        return false;
      }
      return !article.isTalkPage() && pg.re.disambig.test(data);
    };
    var stubCount = function stubCount(data, article) {
      if (!getValueOf('popupAllDabsStubs') && article.namespace()) {
        return false;
      }
      var sectStub = 0;
      var realStub = 0;
      if (pg.re.stub.test(data)) {
        var s = data.parenSplit(pg.re.stub);
        for (var i = 1; i < s.length; i = i + 2) {
          if (s[i]) {
            ++sectStub;
          } else {
            ++realStub;
          }
        }
      }
      return {
        real: realStub,
        sect: sectStub
      };
    };
    var isValidImageName = function isValidImageName(str) {
      return (
        // extend as needed...
        str.indexOf('{') === -1
      );
    };
    var isInStrippableNamespace = function isInStrippableNamespace(article) {
      return (
        // Does the namespace allow subpages
        // Note, would be better if we had access to wgNamespacesWithSubpages
        article.namespaceId() !== 0
      );
    };
    var isInMainNamespace = function isInMainNamespace(article) {
      return article.namespaceId() === 0;
    };
    var anchorContainsImage = function anchorContainsImage(a) {
      // iterate over children of anchor a
      // see if any are images
      if (a === null) {
        return false;
      }
      var kids = a.childNodes;
      var _iterator4 = _createForOfIteratorHelper(kids),
        _step4;
      try {
        for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
          var kid = _step4.value;
          if (kid.nodeName === 'IMG') {
            return true;
          }
        }
      } catch (err) {
        _iterator4.e(err);
      } finally {
        _iterator4.f();
      }
      return false;
    };
    var isPopupLink = function isPopupLink(a) {
      // NB for performance reasons, TOC links generally return true
      // they should be stripped out later
      if (!markNopopupSpanLinks.done) {
        markNopopupSpanLinks();
      }
      if (a.inNopopupSpan) {
        return false;
      }

      // FIXME is this faster inline?
      if (a.onmousedown || a.getAttribute('nopopup')) {
        return false;
      }
      var h = a.href;
      if (h === "".concat(document.location.href, "#")) {
        return false;
      }
      if (!pg.re.basenames.test(h)) {
        return false;
      }
      if (!pg.re.urlNoPopup.test(h)) {
        return true;
      }
      return (pg.re.email.test(h) || pg.re.contribs.test(h) || pg.re.backlinks.test(h) || pg.re.specialdiff.test(h)) && !h.includes('&limit=');
    };
    var markNopopupSpanLinks = function markNopopupSpanLinks() {
      if (!getValueOf('popupOnlyArticleLinks')) {
        fixVectorMenuPopups();
      }
      var s = $('.nopopups').toArray();
      var _iterator5 = _createForOfIteratorHelper(s),
        _step5;
      try {
        for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
          var element = _step5.value;
          var as = element.querySelectorAll('a');
          var _iterator6 = _createForOfIteratorHelper(as),
            _step6;
          try {
            for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
              var a = _step6.value;
              a.inNopopupSpan = true;
            }
          } catch (err) {
            _iterator6.e(err);
          } finally {
            _iterator6.f();
          }
        }
      } catch (err) {
        _iterator5.e(err);
      } finally {
        _iterator5.f();
      }
      markNopopupSpanLinks.done = true;
    };
    var fixVectorMenuPopups = function fixVectorMenuPopups() {
      $('nav.vector-menu h3:first a:first').prop('inNopopupSpan', true);
    };
    // ENDFILE: titles.js

    // STARTFILE: getpage.js

    // Wiki-specific downloading
    //

    // Schematic for a getWiki call
    //
    //             getPageWithCaching
    //					|
    //	   false		|		  true
    // getPage<-[findPictureInCache]->-onComplete(a fake download)
    //   \.
    //	 (async)->addPageToCache(download)->-onComplete(download)

    // check cache to see if page exists

    var getPageWithCaching = function getPageWithCaching(url, onComplete, owner) {
      log("getPageWithCaching, url=".concat(url));
      var i = findInPageCache(url);
      var d;
      if (i > -1) {
        d = fakeDownload(url, owner.idNumber, onComplete, pg.cache.pages[i].data, pg.cache.pages[i].lastModified, owner);
      } else {
        d = getPage(url, onComplete, owner);
        if (d && owner && owner.addDownload) {
          owner.addDownload(d);
          d.owner = owner;
        }
      }
    };
    var getPage = function getPage(url, onComplete, owner) {
      log('getPage');
      var callback = function callback(d) {
        if (!d.aborted) {
          addPageToCache(d);
          onComplete(d);
        }
      };
      return startDownload(url, owner.idNumber, callback);
    };
    var findInPageCache = function findInPageCache(url) {
      for (var i = 0; i < pg.cache.pages.length; ++i) {
        if (url === pg.cache.pages[i].url) {
          return i;
        }
      }
      return -1;
    };
    var addPageToCache = function addPageToCache(download) {
      log("addPageToCache ".concat(download.url));
      var page = {
        url: download.url,
        data: download.data,
        lastModified: download.lastModified
      };
      return pg.cache.pages.push(page);
    };
    // ENDFILE: getpage.js

    // STARTFILE: parensplit.js

    // parenSplit

    // String.prototype.parenSplit should do what ECMAscript says String.prototype.split does,
    // interspersing paren matches (regex capturing groups) between the split elements.
    // i.e. 'abc'.split(/(b)/)) should return ['a','b','c'], not ['a','c']

    if (String('abc'.split(/(b)/)) !== 'a,b,c') {
      // broken String.split, e.g. konq, IE < 10
      String.prototype.parenSplit = function (re) {
        re = nonGlobalRegex(re);
        var s = this;
        var m = re.exec(s);
        var ret = [];
        while (m && s) {
          // without the following loop, we have
          // 'ab'.parenSplit(/a|(b)/) !== 'ab'.split(/a|(b)/)
          for (var i = 0; i < m.length; ++i) {
            if (typeof m[i] === 'undefined') {
              m[i] = '';
            }
          }
          ret.push(s.slice(0, Math.max(0, m.index)));
          ret = ret.concat(m.slice(1));
          s = s.slice(Math.max(0, m.index + m[0].length));
          m = re.exec(s);
        }
        ret.push(s);
        return ret;
      };
    } else {
      String.prototype.parenSplit = function (re) {
        return this.split(re);
      };
      String.prototype.parenSplit.isNative = true;
    }
    var nonGlobalRegex = function nonGlobalRegex(re) {
      var s = re.toString();
      var flags = '';
      var j;
      for (j = s.length; s.charAt(j) !== '/'; --j) {
        if (s.charAt(j) !== 'g') {
          flags += s.charAt(j);
        }
      }
      var t = s.substring(1, j);
      return new RegExp(t, flags);
    };
    // ENDFILE: parensplit.js

    // STARTFILE: tools.js
    // IE madness with encoding
    // ========================
    //
    // suppose throughout that the page is in utf8, like wikipedia
    //
    // if a is an anchor DOM element and a.href should consist of
    //
    // http://host.name.here/wiki/foo?bar=baz
    //
    // then IE gives foo as "latin1-encoded" utf8; we have foo = decode_utf8(decodeURI(foo_ie))
    // but IE gives bar=baz correctly as plain utf8
    //
    // ---------------------------------
    //
    // IE's xmlhttp doesn't understand utf8 urls. Have to use encodeURI here.
    //
    // ---------------------------------
    //
    // summat else

    // Source: http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm

    var getJsObj = function getJsObj(json) {
      try {
        var json_ret = JSON.parse(json);
        if (json_ret.warnings) {
          for (var w = 0; w < json_ret.warnings.length; w++) {
            if (json_ret.warnings[w]['*']) {
              log(json_ret.warnings[w]['*']);
            } else {
              log(json_ret.warnings[w].warnings);
            }
          }
        } else if (json_ret.error) {
          errlog("".concat(json_ret.error.code, ": ").concat(json_ret.error.info));
        }
        return json_ret;
      } catch (_unused9) {
        errlog("Something went wrong with getJsObj, json=".concat(json));
        return 1;
      }
    };
    var anyChild = function anyChild(obj) {
      for (var p in obj) {
        return obj[p];
      }
      return null;
    };
    var upcaseFirst = function upcaseFirst(str) {
      if (_typeof(str) !== _typeof('') || str === '') {
        return;
      }
      return str.charAt(0).toUpperCase() + str.slice(1);
    };
    var findInArray = function findInArray(arr, foo) {
      if (!arr || arr.length === 0) {
        return -1;
      }
      var len = arr.length;
      for (var i = 0; i < len; ++i) {
        if (arr[i] === foo) {
          return i;
        }
      }
      return -1;
    };
    var nextOne = function nextOne(array, value) {
      // NB if the array has two consecutive entries equal
      //	then this will loop on successive calls
      var i = findInArray(array, value);
      if (i < 0) {
        return null;
      }
      return array[i + 1];
    };
    var literalizeRegex = function literalizeRegex(str) {
      return mw.util.escapeRegExp(str);
    };
    String.prototype.entify = function () {
      // var shy='&shy;';
      return this.split('&').join('&amp;').split('<').join('&lt;').split('>').join('&gt;' /*+shy*/).split('"').join('&quot;');
    };

    // Array filter function
    var removeNulls = function removeNulls(val) {
      return val !== null;
    };
    var joinPath = function joinPath(list) {
      return list.filter(removeNulls).join('/');
    };
    var simplePrintf = function simplePrintf(str, subs) {
      if (!str || !subs) {
        return str;
      }
      var ret = [];
      var s = str.parenSplit(/(%s|\$\d+)/);
      var i = 0;
      do {
        ret.push(s.shift());
        if (s.length === 0) {
          break;
        }
        var cmd = s.shift();
        if (cmd === '%s') {
          if (i < subs.length) {
            ret.push(subs[i]);
          } else {
            ret.push(cmd);
          }
          ++i;
        } else {
          var j = Number.parseInt(cmd.replace('$', ''), 10) - 1;
          if (j > -1 && j < subs.length) {
            ret.push(subs[j]);
          } else {
            ret.push(cmd);
          }
        }
      } while (s.length > 0);
      return ret.join('');
    };
    var isString = function isString(x) {
      return typeof x === 'string' || x instanceof String;
    };
    var isNumber = function isNumber(x) {
      return typeof x === 'number' || x instanceof Number;
    };
    var isRegExp = function isRegExp(x) {
      return x instanceof RegExp;
    };
    var isArray = function isArray(x) {
      return Array.isArray(x);
    };
    var isObject = function isObject(x) {
      return x instanceof Object;
    };
    var isFunction = function isFunction(x) {
      return !isRegExp(x) && (typeof x === 'function' || x instanceof Function);
    };
    var repeatString = function repeatString(s, mult) {
      var ret = '';
      for (var i = 0; i < mult; ++i) {
        ret += s;
      }
      return ret;
    };
    var zeroFill = function zeroFill(s, min) {
      min || (min = 2);
      var t = s.toString();
      return repeatString('0', min - t.length) + t;
    };
    var map = function map(f, o) {
      if (isArray(o)) {
        return map_array(f, o);
      }
      return map_object(f, o);
    };
    var map_array = function map_array(f, o) {
      var ret = [];
      var _iterator7 = _createForOfIteratorHelper(o),
        _step7;
      try {
        for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
          var element = _step7.value;
          ret.push(f(element));
        }
      } catch (err) {
        _iterator7.e(err);
      } finally {
        _iterator7.f();
      }
      return ret;
    };
    var map_object = function map_object(f, o) {
      var ret = {};
      for (var i in o) {
        ret[o] = f(o[i]);
      }
      return ret;
    };
    pg.escapeQuotesHTML = function (text) {
      return text.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    };
    pg.unescapeQuotesHTML = function (html) {
      // From https://stackoverflow.com/a/7394787
      // This seems to be implemented correctly on all major browsers now, so we
      // don't have to make our own function.
      var txt = document.createElement('textarea');
      txt.innerHTML = html;
      return txt.value;
    };

    // ENDFILE: tools.js

    // STARTFILE: dab.js

    // Dab-fixing code
    //

    var retargetDab = function retargetDab(newTarget, oldTarget, friendlyCurrentArticleName, titleToEdit) {
      log("retargetDab: newTarget=".concat(newTarget, " oldTarget=").concat(oldTarget));
      return changeLinkTargetLink({
        newTarget: newTarget,
        text: newTarget.split(' ').join('&nbsp;'),
        hint: tprintf('disambigHint', [newTarget]),
        summary: simplePrintf(getValueOf('popupFixDabsSummary'), [friendlyCurrentArticleName, newTarget]),
        clickButton: getValueOf('popupDabsAutoClick'),
        minor: true,
        oldTarget: oldTarget,
        watch: getValueOf('popupWatchDisambiggedPages'),
        title: titleToEdit
      });
    };
    var listLinks = function listLinks(wikitext, oldTarget, titleToEdit) {
      // mediawiki strips trailing spaces, so we do the same
      // testcase: <en.wikipedia.org/w/index.php?title=Radial&oldid=97365633>
      var reg = new RegExp('\\[\\[([^|]*?) *(\\||\\]\\])', 'gi');
      var ret = [];
      var splitted = wikitext.parenSplit(reg);
      // ^[a-z]+ should match interwiki links, hopefully (case-insensitive)
      // and ^[a-z]* should match those and [[:Category...]] style links too
      var omitRegex = new RegExp('^[a-z]*:|^[Ss]pecial:|^[Ii]mage|^[Cc]ategory');
      var friendlyCurrentArticleName = oldTarget.toString();
      var wikPos = getValueOf('popupDabWiktionary');
      for (var i = 1; i < splitted.length; i = i + 3) {
        if (_typeof(splitted[i]) === _typeof('string') && splitted[i].length > 0 && !omitRegex.test(splitted[i])) {
          ret.push(retargetDab(splitted[i], oldTarget, friendlyCurrentArticleName, titleToEdit));
        } /* if */
      } /* for loop */

      ret = rmDupesFromSortedList(ret.sort());
      if (wikPos) {
        var wikTarget = "wiktionary:".concat(friendlyCurrentArticleName.replace(new RegExp('^(.+)\\s+[(][^)]+[)]\\s*$'), '$1'));
        var meth;
        if (wikPos.toLowerCase() === 'first') {
          meth = 'unshift';
        } else {
          meth = 'push';
        }
        ret[meth](retargetDab(wikTarget, oldTarget, friendlyCurrentArticleName, titleToEdit));
      }
      ret.push(changeLinkTargetLink({
        newTarget: null,
        text: popupString('remove this link').split(' ').join('&nbsp;'),
        hint: popupString('remove all links to this disambig page from this article'),
        clickButton: getValueOf('popupDabsAutoClick'),
        oldTarget: oldTarget,
        summary: simplePrintf(getValueOf('popupRmDabLinkSummary'), [friendlyCurrentArticleName]),
        watch: getValueOf('popupWatchDisambiggedPages'),
        title: titleToEdit
      }));
      return ret;
    };
    var rmDupesFromSortedList = function rmDupesFromSortedList(list) {
      var ret = [];
      var _iterator8 = _createForOfIteratorHelper(list),
        _step8;
      try {
        for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
          var element = _step8.value;
          if (ret.length === 0 || element !== ret[ret.length - 1]) {
            ret.push(element);
          }
        }
      } catch (err) {
        _iterator8.e(err);
      } finally {
        _iterator8.f();
      }
      return ret;
    };
    var makeFixDab = function makeFixDab(data, navpop) {
      // grab title from parent popup if there is one; default exists in changeLinkTargetLink
      var titleToEdit = navpop.parentPopup && navpop.parentPopup.article.toString();
      var list = listLinks(data, navpop.originalArticle, titleToEdit);
      if (list.length === 0) {
        log('listLinks returned empty list');
        return null;
      }
      var html = "<hr />".concat(popupString('Click to disambiguate this link to:'), "<br>");
      html += list.join(', ');
      return html;
    };
    var makeFixDabs = function makeFixDabs(wikiText, navpop) {
      if (getValueOf('popupFixDabs') && isDisambig(wikiText, navpop.article) && Title.fromURL(location.href).namespaceId() !== pg.nsSpecialId && navpop.article.talkPage()) {
        setPopupHTML(makeFixDab(wikiText, navpop), 'popupFixDab', navpop.idNumber);
      }
    };
    var popupRedlinkHTML = function popupRedlinkHTML(article) {
      return changeLinkTargetLink({
        newTarget: null,
        text: popupString('remove this link').split(' ').join('&nbsp;'),
        hint: popupString('remove all links to this page from this article'),
        clickButton: getValueOf('popupRedlinkAutoClick'),
        oldTarget: article.toString(),
        summary: simplePrintf(getValueOf('popupRedlinkSummary'), [article.toString()])
      });
    };

    // ENDFILE: dab.js

    // STARTFILE: htmloutput.js

    // this has to use a timer loop as we don't know if the DOM element exists when we want to set the text
    var setPopupHTML = function setPopupHTML(str, elementId, popupId, onSuccess, append) {
      if (typeof popupId === 'undefined') {
        //console.error('popupId is not defined in setPopupHTML, html='+str.substring(0,100));
        popupId = pg.idNumber;
      }
      var popupElement = document.getElementById(elementId + popupId);
      if (popupElement) {
        if (!append) {
          popupElement.innerHTML = '';
        }
        if (isString(str)) {
          popupElement.innerHTML += str;
        } else {
          popupElement.appendChild(str);
        }
        if (onSuccess) {
          onSuccess();
        }
        setTimeout(checkPopupPosition, 100);
        return true;
      }
      // call this function again in a little while...
      setTimeout(function () {
        setPopupHTML(str, elementId, popupId, onSuccess);
      }, 600);
      return null;
    };
    var setPopupTrailer = function setPopupTrailer(str, id) {
      return setPopupHTML(str, 'popupData', id);
    };

    // args.navpopup is mandatory
    // optional: args.redir, args.redirTarget
    // FIXME: ye gods, this is ugly stuff
    var fillEmptySpans = function fillEmptySpans(args) {
      // if redir is present and true then redirTarget is mandatory
      var redir = true;
      var rcid;
      if (_typeof(args) !== 'object' || typeof args.redir === 'undefined' || !args.redir) {
        redir = false;
      }
      var a = args.navpopup.parentAnchor;
      var article;
      var hint = null;
      var oldid = null;
      var params = {};
      if (redir && _typeof(args.redirTarget) === _typeof({})) {
        article = args.redirTarget;
        //hint=article.hintValue();
      } else {
        article = new Title().fromAnchor(a);
        hint = a.originalTitle || article.hintValue();
        params = parseParams(a.href);
        oldid = getValueOf('popupHistoricalLinks') ? params.oldid : null;
        rcid = params.rcid;
      }
      var x = {
        article: article,
        hint: hint,
        oldid: oldid,
        rcid: rcid,
        navpop: args.navpopup,
        params: params
      };
      var structure = pg.structures[getValueOf('popupStructure')];
      if (_typeof(structure) !== 'object') {
        setPopupHTML('popupError', "Unknown structure (this should never happen): ".concat(pg.option.popupStructure), args.navpopup.idNumber);
        return;
      }
      var spans = flatten(pg.misc.layout);
      var numspans = spans.length;
      var redirs = pg.misc.redirSpans;
      for (var i = 0; i < numspans; ++i) {
        var found = redirs && redirs.includes(spans[i]);
        // log('redir=' + redir + ', found=' + found + ', spans[i]=' + spans[i]);
        if (found && !redir || !found && redir) {
          //log('skipping this set of the loop');
          continue;
        }
        var structurefn = structure[spans[i]];
        if (structurefn === undefined) {
          // nothing to do for this structure part
          continue;
        }
        var setfn = setPopupHTML;
        if (getValueOf('popupActiveNavlinks') && (spans[i].indexOf('popupTopLinks') === 0 || spans[i].indexOf('popupRedirTopLinks') === 0)) {
          setfn = setPopupTipsAndHTML;
        }
        switch (_typeof(structurefn)) {
          case 'function':
            {
              log("running ".concat(spans[i], "({article:").concat(x.article, ", hint:").concat(x.hint, ", oldid: ").concat(x.oldid, "})"));
              setfn(structurefn(x), spans[i], args.navpopup.idNumber);
              break;
            }
          case 'string':
            {
              setfn(structurefn, spans[i], args.navpopup.idNumber);
              break;
            }
          default:
            {
              errlog("unknown thing with label ".concat(spans[i], " (span index was ").concat(i, ")"));
              break;
            }
        }
      }
    };

    // flatten an array
    var flatten = function flatten(list, start) {
      var ret = [];
      if (typeof start === 'undefined') {
        start = 0;
      }
      for (var i = start; i < list.length; ++i) {
        if (_typeof(list[i]) === _typeof([])) {
          return ret.concat(flatten(list[i])).concat(flatten(list, i + 1));
        }
        ret.push(list[i]);
      }
      return ret;
    };

    // Generate html for whole popup
    var popupHTML = function popupHTML(a) {
      getValueOf('popupStructure');
      var structure = pg.structures[pg.option.popupStructure];
      if (_typeof(structure) !== 'object') {
        //return 'Unknown structure: '+pg.option.popupStructure;
        // override user choice
        pg.option.popupStructure = pg.optionDefault.popupStructure;
        return popupHTML(a);
      }
      if (typeof structure.popupLayout !== 'function') {
        return 'Bad layout';
      }
      pg.misc.layout = structure.popupLayout();
      if (typeof structure.popupRedirSpans === 'function') {
        pg.misc.redirSpans = structure.popupRedirSpans();
      } else {
        pg.misc.redirSpans = [];
      }
      return makeEmptySpans(pg.misc.layout, a.navpopup);
    };
    var makeEmptySpans = function makeEmptySpans(list, navpop) {
      var ret = '';
      var _iterator9 = _createForOfIteratorHelper(list),
        _step9;
      try {
        for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
          var element = _step9.value;
          if (_typeof(element) === _typeof('')) {
            ret += emptySpanHTML(element, navpop.idNumber, 'div');
          } else if (_typeof(element) === _typeof([]) && element.length > 0) {
            ret = ret.parenSplit(new RegExp('(</[^>]*?>$)')).join(makeEmptySpans(element, navpop));
          } else if (_typeof(element) === _typeof({}) && element.nodeType) {
            ret += emptySpanHTML(element.name, navpop.idNumber, element.nodeType);
          }
        }
      } catch (err) {
        _iterator9.e(err);
      } finally {
        _iterator9.f();
      }
      return ret;
    };
    var emptySpanHTML = function emptySpanHTML(name, id, tag, classname) {
      tag || (tag = 'span');
      if (!classname) {
        classname = emptySpanHTML.classAliases[name];
      }
      classname || (classname = name);
      if (name === getValueOf('popupDragHandle')) {
        classname += ' popupDragHandle';
      }
      return simplePrintf('<%s id="%s" class="%s"></%s>', [tag, name + id, classname, tag]);
    };
    emptySpanHTML.classAliases = {
      popupSecondPreview: 'popupPreview'
    };

    // generate html for popup image
    // <a id="popupImageLinkn"><img id="popupImagen">
    // where n=idNumber
    var imageHTML = function imageHTML(_article, idNumber) {
      return simplePrintf('<a id="popupImageLink$1">' + '<img align="right" valign="top" id="popupImg$1" style="display: none;"></img>' + '</a>', [idNumber]);
    };
    var popTipsSoonFn = function popTipsSoonFn(id, when, popData) {
      if (!when) {
        when = 250;
      }
      var popTips = function popTips() {
        setupTooltips(document.getElementById(id), false, true, popData);
      };
      return function () {
        setTimeout(popTips, when, popData);
      };
    };
    var setPopupTipsAndHTML = function setPopupTipsAndHTML(html, divname, idnumber, popData) {
      setPopupHTML(html, divname, idnumber, getValueOf('popupSubpopups') ? popTipsSoonFn(divname + idnumber, null, popData) : null);
    };
    // ENDFILE: htmloutput.js

    // STARTFILE: mouseout.js

    // fuzzy checks

    var fuzzyCursorOffMenus = function fuzzyCursorOffMenus(_x, _y, _fuzz, parent) {
      if (!parent) {
        return null;
      }
      var uls = parent.querySelectorAll('ul');
      var _iterator10 = _createForOfIteratorHelper(uls),
        _step10;
      try {
        for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
          var ul = _step10.value;
          if (ul.className === 'popup_menu' && ul.offsetWidth > 0) {
            return false;
          } // else { document.title+='.'; }
        }
      } catch (err) {
        _iterator10.e(err);
      } finally {
        _iterator10.f();
      }
      return true;
    };
    var checkPopupPosition = function checkPopupPosition() {
      // stop the popup running off the right of the screen
      // FIXME avoid pg.current.link
      if (pg.current.link && pg.current.link.navpopup) {
        pg.current.link.navpopup.limitHorizontalPosition();
      }
    };
    var mouseOutWikiLink = function mouseOutWikiLink() {
      // console ('mouseOutWikiLink');
      var a = this;
      removeModifierKeyHandler(a);
      if (a.navpopup === null || typeof a.navpopup === 'undefined') {
        return;
      }
      if (!a.navpopup.isVisible()) {
        a.navpopup.banish();
        return;
      }
      restoreTitle(a);
      Navpopup.tracker.addHook(posCheckerHook(a.navpopup));
    };
    var posCheckerHook = function posCheckerHook(navpop) {
      return function () {
        if (!navpop.isVisible()) {
          return true; /* remove this hook */
        }

        if (Navpopup.tracker.dirty) {
          return false;
        }
        var x = Navpopup.tracker.x;
        var y = Navpopup.tracker.y;
        var mouseOverNavpop = navpop.isWithin(x, y, navpop.fuzz, navpop.mainDiv) || !fuzzyCursorOffMenus(x, y, navpop.fuzz, navpop.mainDiv);

        // FIXME it'd be prettier to do this internal to the Navpopup objects
        var t = getValueOf('popupHideDelay');
        if (t) {
          t = t * 1000;
        }
        if (!t) {
          if (!mouseOverNavpop) {
            if (navpop.parentAnchor) {
              restoreTitle(navpop.parentAnchor);
            }
            navpop.banish();
            return true; /* remove this hook */
          }

          return false;
        }
        // we have a hide delay set
        var d = Date.now();
        if (!navpop.mouseLeavingTime) {
          navpop.mouseLeavingTime = d;
          return false;
        }
        if (mouseOverNavpop) {
          navpop.mouseLeavingTime = null;
          return false;
        }
        if (d - navpop.mouseLeavingTime > t) {
          navpop.mouseLeavingTime = null;
          navpop.banish();
          return true; /* remove this hook */
        }

        return false;
      };
    };
    var runStopPopupTimer = function runStopPopupTimer(navpop) {
      // at this point, we should have left the link but remain within the popup
      // so we call this function again until we leave the popup.
      if (!navpop.stopPopupTimer) {
        navpop.stopPopupTimer = setInterval(posCheckerHook(navpop), 500);
        navpop.addHook(function () {
          clearInterval(navpop.stopPopupTimer);
        }, 'hide', 'before');
      }
    };

    // ENDFILE: mouseout.js

    // STARTFILE: previewmaker.js
    /**
     * @fileoverview
     * Defines the {@link Previewmaker} object, which generates short previews from wiki markup.
     */

    /**
     * Creates a new Previewmaker
     * @constructor
     * @class The Previewmaker class. Use an instance of this to generate short previews from Wikitext.
     * @param {String} wikiText The Wikitext source of the page we wish to preview.
     * @param {String} baseUrl The url we should prepend when creating relative urls.
     * @param {Navpopup} owner The navpop associated to this preview generator
     */
    var Previewmaker = /*#__PURE__*/function () {
      function Previewmaker(wikiText, baseUrl, owner) {
        _classCallCheck(this, Previewmaker);
        /** The wikitext which is manipulated to generate the preview. */
        this.originalData = wikiText;
        this.baseUrl = baseUrl;
        this.owner = owner;
        this.maxCharacters = getValueOf('popupMaxPreviewCharacters');
        this.maxSentences = getValueOf('popupMaxPreviewSentences');
        this.setData();
      }
      _createClass(Previewmaker, [{
        key: "setData",
        value: function setData() {
          var maxSize = Math.max(10000, 2 * this.maxCharacters);
          this.data = this.originalData.slice(0, Math.max(0, maxSize));
        }

        /**
         * Remove HTML comments
         * @private
         */
      }, {
        key: "killComments",
        value: function killComments() {
          // this also kills one trailing newline, eg [[diamyo]]
          this.data = this.data.replace(new RegExp('^<!--[^$]*?-->\\n|\\n<!--[^$]*?-->(?=\\n)|<!--[^$]*?-->', 'g'), '');
        }

        /**
         * @private
         */
      }, {
        key: "killDivs",
        value: function killDivs() {
          // say goodbye, divs (can be nested, so use * not *?)
          this.data = this.data.replace(new RegExp('< *div[^>]* *>[\\s\\S]*?< */ *div *>', 'gi'), '');
        }

        /**
         * @private
         */
      }, {
        key: "killGalleries",
        value: function killGalleries() {
          this.data = this.data.replace(new RegExp('< *gallery[^>]* *>[\\s\\S]*?< */ *gallery *>', 'gi'), '');
        }

        /**
         * @private
         */
      }, {
        key: "kill",
        value: function kill(opening, closing, subopening, subclosing, repl) {
          var oldk = this.data;
          var k = this.killStuff(this.data, opening, closing, subopening, subclosing, repl);
          while (k.length < oldk.length) {
            oldk = k;
            k = this.killStuff(k, opening, closing, subopening, subclosing, repl);
          }
          this.data = k;
        }

        /**
         * @private
         */
      }, {
        key: "killStuff",
        value: function killStuff(txt, opening, closing, subopening, subclosing, repl) {
          var op = this.makeRegexp(opening);
          var cl = this.makeRegexp(closing, '^');
          var sb = subopening ? this.makeRegexp(subopening, '^') : null;
          var sc = subclosing ? this.makeRegexp(subclosing, '^') : cl;
          if (!op || !cl) {
            alert('Navigation Popups error: op or cl is null! something is wrong.');
            return;
          }
          if (!op.test(txt)) {
            return txt;
          }
          var ret = '';
          var opResult = op.exec(txt);
          ret = txt.slice(0, Math.max(0, opResult.index));
          txt = txt.slice(Math.max(0, opResult.index + opResult[0].length));
          var depth = 1;
          while (txt.length > 0) {
            var removal = 0;
            if (depth === 1 && cl.test(txt)) {
              depth--;
              removal = cl.exec(txt)[0].length;
            } else if (depth > 1 && sc.test(txt)) {
              depth--;
              removal = sc.exec(txt)[0].length;
            } else if (sb && sb.test(txt)) {
              depth++;
              removal = sb.exec(txt)[0].length;
            }
            if (!removal) {
              removal = 1;
            }
            txt = txt.slice(Math.max(0, removal));
            if (depth === 0) {
              break;
            }
          }
          return ret + (repl || '') + txt;
        }

        /**
         * @private
         */
      }, {
        key: "makeRegexp",
        value: function makeRegexp(x, prefix, suffix) {
          prefix || (prefix = '');
          suffix || (suffix = '');
          var reStr = '';
          var flags = '';
          if (isString(x)) {
            reStr = prefix + literalizeRegex(x) + suffix;
          } else if (isRegExp(x)) {
            var s = x.toString().slice(1);
            var sp = s.split('/');
            flags = sp[sp.length - 1];
            sp[sp.length - 1] = '';
            s = sp.join('/');
            s = s.slice(0, Math.max(0, s.length - 1));
            reStr = prefix + s + suffix;
          } else {
            log('makeRegexp failed');
          }
          log("makeRegexp: got reStr=".concat(reStr, ", flags=").concat(flags));
          return new RegExp(reStr, flags);
        }

        /**
         * @private
         */
      }, {
        key: "killBoxTemplates",
        value: function killBoxTemplates() {
          // taxobox removal... in fact, there's a saudiprincebox_begin, so let's be more general
          // also, have float_begin, ... float_end
          this.kill(new RegExp('[{][{][^{}\\s|]*?(float|box)[_ ](begin|start)', 'i'), /}}\s*/, '{{');

          // infoboxes etc
          // from Zyxw: kill frames too
          this.kill(new RegExp('[{][{][^{}\\s|]*?(infobox|elementbox|frame)[_ ]', 'i'), /}}\s*/, '{{');
        }

        /**
         * @private
         */
      }, {
        key: "killTemplates",
        value: function killTemplates() {
          this.kill('{{', '}}', '{', '}', ' ');
        }

        /**
         * @private
         */
      }, {
        key: "killTables",
        value: function killTables() {
          // tables are bad, too
          // this can be slow, but it's an inprovement over a browser hang
          // torture test: [[Comparison_of_Intel_Central_Processing_Units]]
          this.kill('{|', /\|}\s*/, '{|');
          this.kill(/<table.*?>/i, /<\/table.*?>/i, /<table.*?>/i);
          // remove lines starting with a pipe for the hell of it (?)
          this.data = this.data.replace(new RegExp('^[|].*$', 'mg'), '');
        }

        /**
         * @private
         */
      }, {
        key: "killImages",
        value: function killImages() {
          var forbiddenNamespaceAliases = [];
          jQuery.each(mw.config.get('wgNamespaceIds'), function (_localizedNamespaceLc, _namespaceId) {
            if (_namespaceId !== pg.nsImageId && _namespaceId !== pg.nsCategoryId) {
              return;
            }
            forbiddenNamespaceAliases.push(_localizedNamespaceLc.split(' ').join('[ _]')); //todo: escape regexp fragments!
          });

          // images and categories are a nono
          this.kill(new RegExp("[[][[]\\s*(".concat(forbiddenNamespaceAliases.join('|'), ")\\s*:"), 'i'), /]]\s*/, '[', ']');
        }

        /**
         * @private
         */
      }, {
        key: "killHTML",
        value: function killHTML() {
          // kill <ref ...>...</ref>
          this.kill(/<ref\b[^/>]*?>/i, /<\/ref>/i);

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

          // and those pesky html tags, but not <nowiki> or <blockquote>
          var splitted = this.data.parenSplit(/(<[\W\w]*?(?:>|$|(?=<)))/);
          var len = splitted.length;
          for (var i = 1; i < len; i = i + 2) {
            switch (splitted[i]) {
              case '<nowiki>':
              case '</nowiki>':
              case '<blockquote>':
              case '</blockquote>':
                {
                  break;
                }
              default:
                {
                  splitted[i] = '';
                }
            }
          }
          this.data = splitted.join('');
        }

        /**
         * @private
         */
      }, {
        key: "killChunks",
        value: function killChunks() {
          // heuristics alert
          // chunks of italic text? you crazy, man?
          var italicChunkRegex = new RegExp("((^|\\n)\\s*:*\\s*''[^']([^']|'''|'[^']){20}(.|\\n[^\\n])*''[.!?\\s]*\\n)+", 'g');
          // keep stuff separated, though, so stick in \n (fixes [[Union Jack]]?
          this.data = this.data.replace(italicChunkRegex, '\n');
        }

        /**
         * @private
         */
      }, {
        key: "mopup",
        value: function mopup() {
          // we simply *can't* be doing with horizontal rules right now
          this.data = this.data.replace(new RegExp('^-{4,}', 'mg'), '');

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

          // replace __TOC__, __NOTOC__ and whatever else there is
          // this'll probably do
          this.data = this.data.replace(new RegExp('^__[A-Z_]*__ *$', 'gmi'), '');
        }

        /**
         * @private
         */
      }, {
        key: "firstBit",
        value: function firstBit() {
          // dont't be givin' me no subsequent paragraphs, you hear me?
          /// first we "normalize" section headings, removing whitespace after, adding before
          var d = this.data;
          if (getValueOf('popupPreviewCutHeadings')) {
            this.data = this.data.replace(new RegExp('\\s*(==+[^=]*==+)\\s*', 'g'), '\n\n$1 ');
            /// then we want to get rid of paragraph breaks whose text ends badly
            this.data = this.data.replace(new RegExp('([:;]) *\\n{2,}', 'g'), '$1\n');
            this.data = this.data.replace(new RegExp('^[\\s\\n]*'), '');
            var stuff = new RegExp('^([^\\n]|\\n[^\\n\\s])*').exec(this.data);
            if (stuff) {
              d = stuff[0];
            }
            if (!getValueOf('popupPreviewFirstParOnly')) {
              d = this.data;
            }

            /// now put \n\n after sections so that bullets and numbered lists work
            d = d.replace(new RegExp('(==+[^=]*==+)\\s*', 'g'), '$1\n\n');
          }

          // Split sentences. Superfluous sentences are RIGHT OUT.
          // note: exactly 1 set of parens here needed to make the slice work
          d = d.parenSplit(new RegExp('([!?.]+["' + "'" + ']*\\s)', 'g'));
          // leading space is bad, mmkay?
          d[0] = d[0].replace(new RegExp('^\\s*'), '');
          var notSentenceEnds = new RegExp('([^.][a-z][.] *[a-z]|etc|sic|Dr|Mr|Mrs|Ms|St|no|op|cit|\\[[^\\]]*|\\s[A-Zvclm])$', 'i');
          d = this.fixSentenceEnds(d, notSentenceEnds);
          this.fullLength = d.join('').length;
          var n = this.maxSentences;
          var dd = this.firstSentences(d, n);
          do {
            dd = this.firstSentences(d, n);
            --n;
          } while (dd.length > this.maxCharacters && n !== 0);
          this.data = dd;
        }

        /**
         * @private
         */
      }, {
        key: "fixSentenceEnds",
        value: function fixSentenceEnds(strs, reg) {
          // take an array of strings, strs
          // join strs[i] to strs[i+1] & strs[i+2] if strs[i] matches regex reg

          for (var i = 0; i < strs.length - 2; ++i) {
            if (reg.test(strs[i])) {
              var a = [];
              for (var j = 0; j < strs.length; ++j) {
                if (j < i) {
                  a[j] = strs[j];
                }
                if (j === i) {
                  a[i] = strs[i] + strs[i + 1] + strs[i + 2];
                }
                if (j > i + 2) {
                  a[j - 2] = strs[j];
                }
              }
              return this.fixSentenceEnds(a, reg);
            }
          }
          return strs;
        }

        /**
         * @private
         */
      }, {
        key: "firstSentences",
        value: function firstSentences(strs, howmany) {
          var t = strs.slice(0, 2 * howmany);
          return t.join('');
        }

        /**
         * @private
         */
      }, {
        key: "killBadWhitespace",
        value: function killBadWhitespace() {
          // also cleans up isolated ''''
          this.data = this.data.replace(new RegExp("^ *'+ *$", 'gm'), '');
        }

        /**
         * Runs the various methods to generate the preview.
         * The preview is stored in the <code>html</html> field.
         * @private
         */
      }, {
        key: "makePreview",
        value: function makePreview() {
          if (this.owner.article.namespaceId() !== pg.nsTemplateId && this.owner.article.namespaceId() !== pg.nsImageId) {
            this.killComments();
            this.killDivs();
            this.killGalleries();
            this.killBoxTemplates();
            if (getValueOf('popupPreviewKillTemplates')) {
              this.killTemplates();
            } else {
              this.killMultilineTemplates();
            }
            this.killTables();
            this.killImages();
            this.killHTML();
            this.killChunks();
            this.mopup();
            this.firstBit();
            this.killBadWhitespace();
          } else {
            this.killHTML();
          }
          this.html = wiki2html(this.data, this.baseUrl); // needs livepreview
          this.fixHTML();
          this.stripLongTemplates();
        }

        /**
         * @private
         */
      }, {
        key: "esWiki2HtmlPart",
        value: function esWiki2HtmlPart(data) {
          var reLinks = /(?:\[\[([^\]|]*)(?:\|([^\]|]*))*]]([a-z]*))/gi; //match a wikilink
          reLinks.lastIndex = 0; //reset regex

          var match;
          var result = '';
          var postfixIndex = 0;
          while ((match = reLinks.exec(data)) !== null) {
            //match all wikilinks
            //FIXME: the way that link is built here isn't perfect. It is clickable, but popups preview won't recognize it in some cases.
            result += "".concat(pg.escapeQuotesHTML(data.substring(postfixIndex, match.index)), "<a href=\"").concat(Insta.conf.paths.articles).concat(pg.escapeQuotesHTML(match[1]), "\">").concat(pg.escapeQuotesHTML((match[2] ? match[2] : match[1]) + match[3]), "</a>");
            postfixIndex = reLinks.lastIndex;
          }
          //append the rest
          result += pg.escapeQuotesHTML(data.slice(Math.max(0, postfixIndex)));
          return result;
        }
      }, {
        key: "editSummaryPreview",
        value: function editSummaryPreview() {
          var reAes = /\/\* *(.*?) *\*\//g; //match the first section marker
          reAes.lastIndex = 0; //reset regex

          var match = reAes.exec(this.data);
          if (match) {
            //we have a section link. Split it, process it, combine it.
            var prefix = this.data.slice(0, Math.max(0, match.index - 1));
            var section = match[1];
            var postfix = this.data.slice(Math.max(0, reAes.lastIndex));
            var start = "<span class='autocomment'>";
            var end = '</span>';
            if (prefix.length > 0) {
              start = "".concat(this.esWiki2HtmlPart(prefix), " ").concat(start, "- ");
            }
            if (postfix.length > 0) {
              end = ": ".concat(end).concat(this.esWiki2HtmlPart(postfix));
            }
            var t = new Title().fromURL(this.baseUrl);
            t.anchorFromUtf(section);
            var sectionLink = "".concat(Insta.conf.paths.articles + pg.escapeQuotesHTML(t.toString(true)), "#").concat(pg.escapeQuotesHTML(t.anchor));
            return "".concat(start, "<a href=\"").concat(sectionLink, "\">&rarr;</a> ").concat(pg.escapeQuotesHTML(section)).concat(end);
          }

          //else there's no section link, htmlify the whole thing.
          return this.esWiki2HtmlPart(this.data);
        }

        /**
         * Works around livepreview bugs.
         * @private
         */
      }, {
        key: "fixHTML",
        value: function fixHTML() {
          if (!this.html) {
            return;
          }
          var ret = this.html;

          // fix question marks in wiki links
          // maybe this'll break some stuff :-(
          ret = ret.replace(new RegExp("(<a href=\"".concat(pg.wiki.articlePath, "/[^\"]*)[?](.*?\")"), 'g'), '$1%3F$2');
          ret = ret.replace(new RegExp("(<a href='".concat(pg.wiki.articlePath, "/[^']*)[?](.*?')"), 'g'), '$1%3F$2');
          // FIXME fix up % too

          this.html = ret;
        }

        /**
               * Generates the preview and displays it in the current popup.
                 * Does nothing if the generated preview is invalid or consists of whitespace only.
               * Also activates wikilinks in the preview for subpopups if the popupSubpopups option is true.
               */
      }, {
        key: "showPreview",
        value: function showPreview() {
          this.makePreview();
          if (_typeof(this.html) !== _typeof('')) {
            return;
          }
          if (new RegExp('^\\s*$').test(this.html)) {
            return;
          }
          setPopupHTML('<hr />', 'popupPrePreviewSep', this.owner.idNumber);
          setPopupTipsAndHTML(this.html, 'popupPreview', this.owner.idNumber, {
            owner: this.owner
          });
          var more = this.fullLength > this.data.length ? this.moreLink() : '';
          setPopupHTML(more, 'popupPreviewMore', this.owner.idNumber);
        }

        /**
         * @private
         */
      }, {
        key: "moreLink",
        value: function moreLink() {
          var a = document.createElement('a');
          a.className = 'popupMoreLink';
          a.innerHTML = popupString('more...');
          var savedThis = this;
          a.onclick = function () {
            savedThis.maxCharacters += 2000;
            savedThis.maxSentences += 20;
            savedThis.setData();
            savedThis.showPreview();
          };
          return a;
        }

        /**
         * @private
         */
      }, {
        key: "stripLongTemplates",
        value: function stripLongTemplates() {
          // operates on the HTML!
          this.html = this.html.replace(new RegExp('^.{0,1000}[{][{][^}]*?(<(p|br)( /)?>\\s*){2,}([^{}]*?[}][}])?', 'gi'), '');
          this.html = this.html.split('\n').join(' '); // workaround for <pre> templates
          this.html = this.html.replace(new RegExp('[{][{][^}]*<pre>[^}]*[}][}]', 'gi'), '');
        }

        /**
         * @private
         */
      }, {
        key: "killMultilineTemplates",
        value: function killMultilineTemplates() {
          this.kill('{{{', '}}}');
          this.kill(new RegExp('\\s*[{][{][^{}]*\\n'), '}}', '{{');
        }
      }]);
      return Previewmaker;
    }(); // ENDFILE: previewmaker.js
    // STARTFILE: querypreview.js
    var loadAPIPreview = function loadAPIPreview(queryType, article, navpop) {
      var art = new Title(article).urlString();
      var url = "".concat(pg.wiki.apiwikibase, "?format=json&formatversion=2&action=query&");
      var htmlGenerator = function htmlGenerator() /*a, d*/{
        alert('invalid html generator');
      };
      var usernameart = '';
      switch (queryType) {
        case 'history':
          {
            url += "titles=".concat(art, "&prop=revisions&rvlimit=").concat(getValueOf('popupHistoryPreviewLimit'));
            htmlGenerator = APIhistoryPreviewHTML;
            break;
          }
        case 'category':
          {
            url += "list=categorymembers&cmtitle=".concat(art);
            htmlGenerator = APIcategoryPreviewHTML;
            break;
          }
        case 'userinfo':
          {
            var username = new Title(article).userName();
            usernameart = encodeURIComponent(username);
            if (pg.re.ipUser.test(username)) {
              url += "list=blocks&bkprop=range|restrictions&bkip=".concat(usernameart);
            } else {
              url += "list=users|usercontribs&usprop=blockinfo|groups|editcount|registration|gender&ususers=".concat(usernameart, "&meta=globaluserinfo&guiprop=groups|unattached&guiuser=").concat(usernameart, "&uclimit=1&ucprop=timestamp&ucuser=").concat(usernameart);
            }
            htmlGenerator = APIuserInfoPreviewHTML;
            break;
          }
        case 'contribs':
          {
            usernameart = encodeURIComponent(new Title(article).userName());
            url += "list=usercontribs&ucuser=".concat(usernameart, "&uclimit=").concat(getValueOf('popupContribsPreviewLimit'));
            htmlGenerator = APIcontribsPreviewHTML;
            break;
          }
        case 'imagepagepreview':
          {
            var trail = '';
            if (getValueOf('popupImageLinks')) {
              trail = "&list=imageusage&iutitle=".concat(art);
            }
            url += "titles=".concat(art, "&prop=revisions|imageinfo&rvprop=content").concat(trail);
            htmlGenerator = APIimagepagePreviewHTML;
            break;
          }
        case 'backlinks':
          {
            url += "list=backlinks&bltitle=".concat(art);
            htmlGenerator = APIbacklinksPreviewHTML;
            break;
          }
        case 'revision':
          {
            if (article.oldid) {
              url += "revids=".concat(article.oldid);
            } else {
              url += "titles=".concat(article.removeAnchor().urlString());
            }
            url += '&prop=revisions|pageprops|info|images|categories&rvprop=ids|timestamp|flags|comment|user|content&cllimit=max&imlimit=max';
            htmlGenerator = APIrevisionPreviewHTML;
            break;
          }
      }
      pendingNavpopTask(navpop);
      var callback = function callback(d) {
        log('callback of API functions was hit');
        if (queryType === 'userinfo') {
          // We need to do another API request
          fetchUserGroupNames(d.data).then(function () {
            showAPIPreview(queryType, htmlGenerator(article, d, navpop), navpop.idNumber, navpop, d);
          });
          return;
        }
        showAPIPreview(queryType, htmlGenerator(article, d, navpop), navpop.idNumber, navpop, d);
      };
      var go = function go() {
        getPageWithCaching(url, callback, navpop);
        return true;
      };
      if (navpop.visible || !getValueOf('popupLazyDownloads')) {
        go();
      } else {
        navpop.addHook(go, 'unhide', 'before', "DOWNLOAD_".concat(queryType, "_QUERY_DATA"));
      }
    };
    var linkList = function linkList(list) {
      list.sort(function (x, y) {
        return x === y ? 0 : x < y ? -1 : 1;
      });
      var buf = [];
      var _iterator11 = _createForOfIteratorHelper(list),
        _step11;
      try {
        for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
          var element = _step11.value;
          buf.push(wikiLink({
            article: new Title(element),
            text: element.split(' ').join('&nbsp;'),
            action: 'view'
          }));
        }
      } catch (err) {
        _iterator11.e(err);
      } finally {
        _iterator11.f();
      }
      return buf.join(', ');
    };
    var getTimeOffset = function getTimeOffset() {
      var tz = mw.user.options.get('timecorrection');
      if (tz && tz.includes('|')) {
        // New format
        return Number.parseInt(tz.split('|')[1], 10);
      }
      return 0;
    };
    var getTimeZone = function getTimeZone() {
      if (!pg.user.timeZone) {
        var tz = mw.user.options.get('timecorrection');
        pg.user.timeZone = 'UTC';
        if (tz) {
          var tzComponents = tz.split('|');
          if (tzComponents.length === 3 && tzComponents[0] === 'ZoneInfo') {
            pg.user.timeZone = tzComponents[2];
          } else {
            errlog("Unexpected timezone information: ".concat(tz));
          }
        }
      }
      return pg.user.timeZone;
    };

    /**
     * Should we use an offset or can we use proper timezones
     */
    var useTimeOffset = function useTimeOffset() {
      if (typeof Intl.DateTimeFormat.prototype.formatToParts === 'undefined') {
        // IE 11
        return true;
      }
      var tz = mw.user.options.get('timecorrection');
      if (tz && !tz.includes('ZoneInfo|')) {
        // System| Default system time, default for users who didn't configure timezone
        // Offset| Manual defined offset by user
        return true;
      }
      return false;
    };

    /**
     * Array of locales for the purpose of javascript locale based formatting
     * Filters down to those supported by the browser. Empty [] === System's default locale
     */
    var getLocales = function getLocales() {
      if (!pg.user.locales) {
        var userLanguage = document.querySelector('html').getAttribute('lang'); // make sure we have HTML locale
        if (getValueOf('popupLocale')) {
          userLanguage = getValueOf('popupLocale');
        } else if (userLanguage === 'en') {
          // en.wp tends to treat this as international english / unspecified
          // but we have more specific settings in user options
          if (getMWDateFormat() === 'mdy') {
            userLanguage = 'en-US';
          } else {
            userLanguage = 'en-GB';
          }
        }
        pg.user.locales = Intl.DateTimeFormat.supportedLocalesOf([userLanguage, navigator.language]);
      }
      return pg.user.locales;
    };

    /**
     * Retrieve configured MW date format for this user
     * These can be
     * default
     * dmy: time, dmy
     * mdy: time, mdy
     * ymd: time, ymd
     * dmyt: dmy, time
     * dmyts: dmy, time + seconds
     * ISO 8601: YYYY-MM-DDThh:mm:ss (local time)
     *
     * This isn't too useful for us, as JS doesn't have formatters to match these private specifiers
     */
    var getMWDateFormat = function getMWDateFormat() {
      return mw.user.options.get('date');
    };

    /**
     * Creates a HTML table that's shown in the history and user-contribs popups.
     * @param {Object[]} h - a list of revisions, returned from the API
     * @param {boolean} reallyContribs - true only if we're displaying user contributions
     */
    var editPreviewTable = function editPreviewTable(article, h, reallyContribs) {
      var html = ['<table>'];
      var day = null;
      var curart = article;
      var page = null;
      var makeFirstColumnLinks;
      if (reallyContribs) {
        // We're showing user contributions, so make (diff | hist) links
        makeFirstColumnLinks = function makeFirstColumnLinks(currentRevision) {
          var result = '(';
          result += "<a href=\"".concat(pg.wiki.titlebase).concat(new Title(currentRevision.title).urlString(), "&diff=prev") + "&oldid=".concat(currentRevision.revid, "\">").concat(popupString('diff'), "</a>");
          result += '&nbsp;|&nbsp;';
          result += "<a href=\"".concat(pg.wiki.titlebase).concat(new Title(currentRevision.title).urlString(), "&action=history\">").concat(popupString('hist'), "</a>");
          result += ')';
          return result;
        };
      } else {
        // It's a regular history page, so make (cur | last) links
        var firstRevid = h[0].revid;
        makeFirstColumnLinks = function makeFirstColumnLinks(currentRevision) {
          var result = '(';
          result += "<a href=\"".concat(pg.wiki.titlebase).concat(new Title(curart).urlString(), "&diff=").concat(firstRevid, "&oldid=").concat(currentRevision.revid, "\">").concat(popupString('cur'), "</a>");
          result += '&nbsp;|&nbsp;';
          result += "<a href=\"".concat(pg.wiki.titlebase).concat(new Title(curart).urlString(), "&diff=prev&oldid=").concat(currentRevision.revid, "\">").concat(popupString('last'), "</a>");
          result += ')';
          return result;
        };
      }
      var _iterator12 = _createForOfIteratorHelper(h.entries()),
        _step12;
      try {
        for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
          var _step12$value2 = _slicedToArray(_step12.value, 2),
            i = _step12$value2[0],
            element = _step12$value2[1];
          if (reallyContribs) {
            page = element.title;
            curart = new Title(page);
          }
          var minor = element.minor ? '<b>m </b>' : '';
          var editDate = new Date(element.timestamp);
          var thisDay = formattedDate(editDate);
          var thisTime = formattedTime(editDate);
          if (thisDay === day) {
            thisDay = '';
          } else {
            day = thisDay;
          }
          if (thisDay) {
            html.push("<tr><td colspan=3><span class=\"popup_history_date\">".concat(thisDay, "</span></td></tr>"));
          }
          html.push("<tr class=\"popup_history_row_".concat(i % 2 ? 'odd' : 'even', "\">"), "<td>".concat(makeFirstColumnLinks(element), "</td>"), "<td>" + "<a href=\"".concat(pg.wiki.titlebase).concat(new Title(curart).urlString(), "&oldid=").concat(element.revid, "\">").concat(thisTime, "</a></td>"));
          var col3url = '';
          var col3txt = '';
          if (!reallyContribs) {
            var user = element.user;
            if (!element.userhidden) {
              if (pg.re.ipUser.test(user)) {
                col3url = "".concat(pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId], ":Contributions&target=").concat(new Title(user).urlString());
              } else {
                col3url = "".concat(pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsUserId], ":").concat(new Title(user).urlString());
              }
              col3txt = pg.escapeQuotesHTML(user);
            } else {
              col3url = getValueOf('popupRevDelUrl');
              col3txt = pg.escapeQuotesHTML(popupString('revdel'));
            }
          } else {
            col3url = pg.wiki.titlebase + curart.urlString();
            col3txt = pg.escapeQuotesHTML(page);
          }
          html.push("<td>".concat(reallyContribs ? minor : '', "<a href=\"").concat(col3url, "\">").concat(col3txt, "</a></td>"));
          var comment = '';
          var c = element.comment || element.content;
          if (c) {
            comment = new Previewmaker(c, new Title(curart).toUrl()).editSummaryPreview();
          } else if (element.commenthidden) {
            comment = popupString('revdel');
          }
          html.push("<td>".concat(!reallyContribs ? minor : '').concat(comment, "</td>"), '</tr>');
          html = [html.join('')];
        }
      } catch (err) {
        _iterator12.e(err);
      } finally {
        _iterator12.f();
      }
      html.push('</table>');
      return html.join('');
    };
    var adjustDate = function adjustDate(d, offset) {
      // offset is in minutes
      var o = offset * 60 * 1000;
      return new Date(+d + o);
    };

    /**
     * This relies on the Date parser understanding en-US dates,
     * which is pretty safe assumption, but not perfect.
     */
    var convertTimeZone = function convertTimeZone(date, timeZone) {
      return new Date(date.toLocaleString('en-US', {
        timeZone: timeZone
      }));
    };
    var formattedDateTime = function formattedDateTime(date) {
      // fallback for IE11 and unknown timezones
      if (useTimeOffset()) {
        return "".concat(formattedDate(date), " ").concat(formattedTime(date));
      }
      if (getMWDateFormat() === 'ISO 8601') {
        var d2 = convertTimeZone(date, getTimeZone());
        return "".concat(map(zeroFill, [d2.getFullYear(), d2.getMonth() + 1, d2.getDate()]).join('-'), "T").concat(map(zeroFill, [d2.getHours(), d2.getMinutes(), d2.getSeconds()]).join(':'));
      }
      var options = getValueOf('popupDateTimeFormatterOptions');
      options.timeZone = getTimeZone();
      return date.toLocaleString(getLocales(), options);
    };
    var formattedDate = function formattedDate(date) {
      var d2;
      // fallback for IE11 and unknown timezones
      if (useTimeOffset()) {
        // we adjust the UTC time, so we print the adjusted UTC, but not really UTC values
        d2 = adjustDate(date, getTimeOffset());
        return map(zeroFill, [d2.getUTCFullYear(), d2.getUTCMonth() + 1, d2.getUTCDate()]).join('-');
      }
      if (getMWDateFormat() === 'ISO 8601') {
        d2 = convertTimeZone(date, getTimeZone());
        return map(zeroFill, [d2.getFullYear(), d2.getMonth() + 1, d2.getDate()]).join('-');
      }
      var options = getValueOf('popupDateFormatterOptions');
      options.timeZone = getTimeZone();
      return date.toLocaleDateString(getLocales(), options);
    };
    var formattedTime = function formattedTime(date) {
      var d2;
      // fallback for IE11 and unknown timezones
      if (useTimeOffset()) {
        // we adjust the UTC time, so we print the adjusted UTC, but not really UTC values
        d2 = adjustDate(date, getTimeOffset());
        return map(zeroFill, [d2.getUTCHours(), d2.getUTCMinutes(), d2.getUTCSeconds()]).join(':');
      }
      if (getMWDateFormat() === 'ISO 8601') {
        d2 = convertTimeZone(date, getTimeZone());
        return map(zeroFill, [d2.getHours(), d2.getMinutes(), d2.getSeconds()]).join(':');
      }
      var options = getValueOf('popupTimeFormatterOptions');
      options.timeZone = getTimeZone();
      return date.toLocaleTimeString(getLocales(), options);
    };

    // Get the proper groupnames for the technicalgroups
    var fetchUserGroupNames = function fetchUserGroupNames(userinfoResponse) {
      var queryObj = getJsObj(userinfoResponse).query;
      var user = anyChild(queryObj.users);
      var messages = [];
      if (user.groups) {
        user.groups.forEach(function (groupName) {
          if (!['*', 'user', 'autoconfirmed', 'extendedconfirmed', 'named'].includes(groupName)) {
            messages.push("group-".concat(groupName, "-member"));
          }
        });
      }
      if (queryObj.globaluserinfo && queryObj.globaluserinfo.groups) {
        queryObj.globaluserinfo.groups.forEach(function (groupName) {
          messages.push("group-".concat(groupName, "-member"));
        });
      }
      return getMwApi().loadMessagesIfMissing(messages);
    };
    var showAPIPreview = function showAPIPreview(queryType, html, id, navpop, download) {
      // DJ: done
      var target = 'popupPreview';
      completedNavpopTask(navpop);
      switch (queryType) {
        case 'imagelinks':
        case 'category':
          {
            target = 'popupPostPreview';
            break;
          }
        case 'userinfo':
          {
            target = 'popupUserData';
            break;
          }
        case 'revision':
          {
            insertPreview(download);
            return;
          }
      }
      setPopupTipsAndHTML(html, target, id);
    };
    var APIrevisionPreviewHTML = function APIrevisionPreviewHTML(_article, download) {
      try {
        var jsObj = getJsObj(download.data);
        var page = anyChild(jsObj.query.pages);
        if (page.missing) {
          // TODO we need to fix this proper later on
          download.owner = null;
          return;
        }
        var content = page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ? page.revisions[0].content : null;
        if (typeof content === 'string') {
          download.data = content;
          download.lastModified = new Date(page.revisions[0].timestamp);
        }
      } catch (_unused10) {
        return 'Revision preview failed :(';
      }
    };
    var APIbacklinksPreviewHTML = function APIbacklinksPreviewHTML(_article, download /*, navpop*/) {
      try {
        var jsObj = getJsObj(download.data);
        var list = jsObj.query.backlinks;
        var html = [];
        if (!list) {
          return popupString('No backlinks found');
        }
        var _iterator13 = _createForOfIteratorHelper(list),
          _step13;
        try {
          for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
            var element = _step13.value;
            var t = new Title(element.title);
            html.push("<a href=\"".concat(pg.wiki.titlebase).concat(t.urlString(), "\">").concat(t.toString().entify(), "</a>"));
          }
        } catch (err) {
          _iterator13.e(err);
        } finally {
          _iterator13.f();
        }
        html = html.join(', ');
        if (jsObj["continue"] && jsObj["continue"].blcontinue) {
          html += popupString(' and more');
        }
        return html;
      } catch (_unused11) {
        return 'backlinksPreviewHTML went wonky';
      }
    };
    pg.fn.APIsharedImagePagePreviewHTML = function (obj) {
      log('APIsharedImagePagePreviewHTML');
      var popupid = obj.requestid;
      if (obj.query && obj.query.pages) {
        var page = anyChild(obj.query.pages);
        var content = page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ? page.revisions[0].content : null;
        if (typeof content === 'string' && pg && pg.current && pg.current.link && pg.current.link.navpopup) {
          /* Not entirely safe, but the best we can do */
          var p = new Previewmaker(content, pg.current.link.navpopup.article, pg.current.link.navpopup);
          p.makePreview();
          setPopupHTML(p.html, 'popupSecondPreview', popupid);
        }
      }
    };
    var APIimagepagePreviewHTML = function APIimagepagePreviewHTML(article, download, navpop) {
      try {
        var jsObj = getJsObj(download.data);
        var page = anyChild(jsObj.query.pages);
        var content = page && page.revisions && page.revisions[0].contentmodel === 'wikitext' ? page.revisions[0].content : null;
        var ret = '';
        var alt = '';
        try {
          alt = navpop.parentAnchor.childNodes[0].alt;
        } catch (_unused12) {}
        if (alt) {
          ret = "".concat(ret, "<hr /><b>").concat(popupString('Alt text:'), "</b> ").concat(pg.escapeQuotesHTML(alt));
        }
        if (typeof content === 'string') {
          var p = prepPreviewmaker(content, article, navpop);
          p.makePreview();
          if (p.html) {
            ret += "<hr />".concat(p.html);
          }
          if (getValueOf('popupSummaryData')) {
            var info = getPageInfo(content, download);
            log(info);
            setPopupTrailer(info, navpop.idNumber);
          }
        }
        if (page && page.imagerepository === 'shared') {
          var art = new Title(article);
          var encart = encodeURIComponent("File:".concat(art.stripNamespace()));
          var shared_url = "".concat(pg.wiki.apicommonsbase, "?format=json&formatversion=2") + "&callback=pg.fn.APIsharedImagePagePreviewHTML" + "&requestid=".concat(navpop.idNumber, "&action=query&prop=revisions&rvprop=content&titles=").concat(encart);
          ret = "".concat(ret, "<hr />").concat(popupString('Image from Commons'), ": <a href=\"").concat(pg.wiki.commonsbase, "?title=").concat(encart, "\">").concat(popupString('Description page'), "</a>");
          mw.loader.load(shared_url);
        }
        showAPIPreview('imagelinks', APIimagelinksPreviewHTML(article, download), navpop.idNumber, download);
        return ret;
      } catch (_unused13) {
        return 'API imagepage preview failed :(';
      }
    };
    var APIimagelinksPreviewHTML = function APIimagelinksPreviewHTML(_article, download) {
      try {
        var jsobj = getJsObj(download.data);
        var list = jsobj.query.imageusage;
        if (list) {
          var ret = [];
          var _iterator14 = _createForOfIteratorHelper(list),
            _step14;
          try {
            for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
              var element = _step14.value;
              ret.push(element.title);
            }
          } catch (err) {
            _iterator14.e(err);
          } finally {
            _iterator14.f();
          }
          if (ret.length === 0) {
            return popupString('No image links found');
          }
          return "<h2>".concat(popupString('File links'), "</h2>").concat(linkList(ret));
        }
        return popupString('No image links found');
      } catch (_unused14) {
        return 'Image links preview generation failed :(';
      }
    };
    var APIcategoryPreviewHTML = function APIcategoryPreviewHTML(_article, download) {
      try {
        var jsobj = getJsObj(download.data);
        var list = jsobj.query.categorymembers;
        var ret = [];
        var _iterator15 = _createForOfIteratorHelper(list),
          _step15;
        try {
          for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
            var element = _step15.value;
            ret.push(element.title);
          }
        } catch (err) {
          _iterator15.e(err);
        } finally {
          _iterator15.f();
        }
        if (ret.length === 0) {
          return popupString('Empty category');
        }
        ret = "<h2>".concat(tprintf('Category members (%s shown)', [ret.length]), "</h2>").concat(linkList(ret));
        if (jsobj["continue"] && jsobj["continue"].cmcontinue) {
          ret += popupString(' and more');
        }
        return ret;
      } catch (_unused15) {
        return 'Category preview failed :(';
      }
    };
    var APIuserInfoPreviewHTML = function APIuserInfoPreviewHTML(_article, download) {
      var ret = [];
      var queryobj = {};
      try {
        queryobj = getJsObj(download.data).query;
      } catch (_unused16) {
        return 'Userinfo preview failed :(';
      }
      var user = anyChild(queryobj.users);
      if (user) {
        var globaluserinfo = queryobj.globaluserinfo;
        if (user.invalid === '') {
          ret.push(popupString('Invalid user'));
        } else if (user.missing === '') {
          ret.push(popupString('Not a registered username'));
        }
        if (user.blockedby) {
          if (user.blockpartial) {
            ret.push("<b>".concat(popupString('Has blocks'), "</b>"));
          } else {
            ret.push("<b>".concat(popupString('BLOCKED'), "</b>"));
          }
        }
        if (globaluserinfo && ('locked' in globaluserinfo || 'hidden' in globaluserinfo)) {
          var lockedSulAccountIsAttachedToThis = true;
          for (var i = 0; globaluserinfo.unattached && i < globaluserinfo.unattached.length; i++) {
            if (globaluserinfo.unattached[i].wiki === mw.config.get('wgDBname')) {
              lockedSulAccountIsAttachedToThis = false;
              break;
            }
          }
          if (lockedSulAccountIsAttachedToThis) {
            if ('locked' in globaluserinfo) {
              ret.push("<b><i>".concat(popupString('LOCKED'), "</i></b>"));
            }
            if ('hidden' in globaluserinfo) {
              ret.push("<b><i>".concat(popupString('HIDDEN'), "</i></b>"));
            }
          }
        }
        if (getValueOf('popupShowGender') && user.gender) {
          switch (user.gender) {
            case 'male':
              {
                ret.push("".concat(popupString('he/him'), " \xB7 "));
                break;
              }
            case 'female':
              {
                ret.push("".concat(popupString('she/her'), " \xB7 "));
                break;
              }
          }
        }
        if (user.groups) {
          user.groups.forEach(function (groupName) {
            if (!['*', 'user', 'autoconfirmed', 'extendedconfirmed', 'named'].includes(groupName)) {
              ret.push(pg.escapeQuotesHTML(mw.message("group-".concat(groupName, "-member"), user.gender).text()));
            }
          });
        }
        if (globaluserinfo && globaluserinfo.groups) {
          globaluserinfo.groups.forEach(function (groupName) {
            ret.push("<i>".concat(pg.escapeQuotesHTML(mw.message("group-".concat(groupName, "-member"), user.gender).text()), "</i>"));
          });
        }
        if (user.registration) {
          ret.push(pg.escapeQuotesHTML((user.editcount ? user.editcount : '0') + popupString(' edits since: ') + (user.registration ? formattedDate(new Date(user.registration)) : '')));
        }
      }
      if (queryobj.usercontribs && queryobj.usercontribs.length > 0) {
        ret.push(popupString('last edit on ') + formattedDate(new Date(queryobj.usercontribs[0].timestamp)));
      }
      if (queryobj.blocks) {
        ret.push(popupString('IP user')); //we only request list=blocks for IPs
        for (var l = 0; l < queryobj.blocks.length; l++) {
          var rbstr = queryobj.blocks[l].rangestart === queryobj.blocks[l].rangeend ? 'BLOCK' : 'RANGEBLOCK';
          rbstr = !Array.isArray(queryobj.blocks[l].restrictions) ? "Has ".concat(rbstr.toLowerCase(), "s") : "".concat(rbstr, "ED");
          ret.push("<b>".concat(popupString(rbstr), "</b>"));
        }
      }

      // if any element of ret ends with ' · ', merge it with the next element to avoid
      // the .join(', ') call inserting a comma after it
      for (var m = 0; m < ret.length - 1; m++) {
        if (ret[m].length > 3 && ret[m].slice(Math.max(0, ret[m].length - 3)) === ' · ') {
          ret[m] = ret[m] + ret[m + 1];
          ret.splice(m + 1, 1); // delete element at index m+1
          m--;
        }
      }
      ret = "<hr />".concat(ret.join(', '));
      return ret;
    };
    var APIcontribsPreviewHTML = function APIcontribsPreviewHTML(article, download, navpop) {
      return APIhistoryPreviewHTML(article, download, navpop, true);
    };
    var APIhistoryPreviewHTML = function APIhistoryPreviewHTML(article, download, _navpop, reallyContribs) {
      try {
        var jsobj = getJsObj(download.data);
        var edits = [];
        if (reallyContribs) {
          edits = jsobj.query.usercontribs;
        } else {
          edits = anyChild(jsobj.query.pages).revisions;
        }
        var ret = editPreviewTable(article, edits, reallyContribs);
        return ret;
      } catch (_unused17) {
        return 'History preview failed :-(';
      }
    };

    // ENDFILE: querypreview.js

    // STARTFILE: debug.js
    // Debugging functions

    var setupDebugging = function setupDebugging() {
      if (window.popupDebug) {
        // popupDebug is set from .version
        window.log = function (x) {
          //if(gMsg!='')gMsg += '\n'; gMsg+=time() + ' ' + x; };
          window.console.log(x);
        };
        window.errlog = function (x) {
          window.console.error(x);
        };
        log('Initializing logger');
      } else {
        window.log = function () {};
        window.errlog = function () {};
      }
    };
    // ENDFILE: debug.js

    // STARTFILE: images.js

    // load image of type Title.
    var loadImage = function loadImage(image, navpop) {
      if (typeof image.stripNamespace !== 'function') {
        alert('loadImages bad');
      }
      // API call to retrieve image info.
      if (!getValueOf('popupImages')) {
        return;
      }
      if (!isValidImageName(image)) {
        return false;
      }
      var art = image.urlString();
      var url = "".concat(pg.wiki.apiwikibase, "?format=json&formatversion=2&action=query");
      url += "&prop=imageinfo&iiprop=url|mime&iiurlwidth=".concat(getValueOf('popupImageSizeLarge'));
      url += "&titles=".concat(art);
      pendingNavpopTask(navpop);
      var callback = function callback(d) {
        popupsInsertImage(navpop.idNumber, navpop, d);
      };
      var go = function go() {
        getPageWithCaching(url, callback, navpop);
        return true;
      };
      if (navpop.visible || !getValueOf('popupLazyDownloads')) {
        go();
      } else {
        navpop.addHook(go, 'unhide', 'after', 'DOWNLOAD_IMAGE_QUERY_DATA');
      }
    };
    var popupsInsertImage = function popupsInsertImage(id, _navpop, download) {
      log('popupsInsertImage');
      var imageinfo;
      try {
        var jsObj = getJsObj(download.data);
        var imagepage = anyChild(jsObj.query.pages);
        if (typeof imagepage.imageinfo === 'undefined') {
          return;
        }
        imageinfo = imagepage.imageinfo[0];
      } catch (_unused18) {
        log('popupsInsertImage failed :(');
        return;
      }
      var popupImage = document.getElementById("popupImg".concat(id));
      if (!popupImage) {
        log('could not find insertion point for image');
        return;
      }
      popupImage.width = getValueOf('popupImageSize');
      popupImage.style.display = 'inline';

      // Set the source for the image.
      if (imageinfo.thumburl) {
        popupImage.src = imageinfo.thumburl;
      } else if (imageinfo.mime.indexOf('image') === 0) {
        popupImage.src = imageinfo.url;
        log('a thumb could not be found, using original image');
      } else {
        log("fullsize imagethumb, but not sure if it's an image");
      }
      var a = document.getElementById("popupImageLink".concat(id));
      if (a === null) {
        return null;
      }

      // Determine the action of the surrouding imagelink.
      switch (getValueOf('popupThumbAction')) {
        case 'imagepage':
          {
            if (pg.current.article.namespaceId() !== pg.nsImageId) {
              a.href = imageinfo.descriptionurl;
              // FIXME: unreliable pg.idNumber
              popTipsSoonFn("popupImage".concat(id))();
              break;
            }
          }
        /* falls through */
        case 'sizetoggle':
          {
            a.onclick = toggleSize;
            a.title = popupString('Toggle image size');
            return;
          }
        case 'linkfull':
          {
            a.href = imageinfo.url;
            a.title = popupString('Open full-size image');
            return;
          }
      }
    };

    // Toggles the image between inline small and navpop fullwidth.
    // It's the same image, no actual sizechange occurs, only display width.
    var toggleSize = function toggleSize() {
      var imgContainer = this;
      if (!imgContainer) {
        alert('imgContainer is null :/');
        return;
      }
      var img = imgContainer.firstChild;
      if (!img) {
        alert('img is null :/');
        return;
      }
      if (!img.style.width || img.style.width === '') {
        img.style.width = '100%';
      } else {
        img.style.width = '';
      }
    };

    // Returns one title of an image from wikiText.
    var getValidImageFromWikiText = function getValidImageFromWikiText(wikiText) {
      // nb in pg.re.image we're interested in the second bracketed expression
      // this may change if the regex changes :-(
      //var match=pg.re.image.exec(wikiText);
      var matched = null;
      var match;
      // strip html comments, used by evil bots :-(
      var t = removeMatchesUnless(wikiText, new RegExp('(<!--[\\s\\S]*?-->)'), 1, new RegExp('^<!--[^[]*popup', 'i'));
      while ((match = pg.re.image.exec(t)) !== null) {
        // now find a sane image name - exclude templates by seeking {
        var m = match[2] || match[6];
        if (isValidImageName(m)) {
          matched = m;
          break;
        }
      }
      pg.re.image.lastIndex = 0;
      if (!matched) {
        return null;
      }
      return "".concat(mw.config.get('wgFormattedNamespaces')[pg.nsImageId], ":").concat(upcaseFirst(matched));
    };
    var removeMatchesUnless = function removeMatchesUnless(str, re1, parencount, re2) {
      var split = str.parenSplit(re1);
      var c = parencount + 1;
      for (var i = 0; i < split.length; ++i) {
        if (i % c === 0 || re2.test(split[i])) {
          continue;
        }
        split[i] = '';
      }
      return split.join('');
    };

    // ENDFILE: images.js

    // STARTFILE: namespaces.js
    // Set up namespaces and other non-strings.js localization
    // (currently that means redirs too)

    var setNamespaces = function setNamespaces() {
      pg.nsSpecialId = -1;
      pg.nsMainspaceId = 0;
      pg.nsImageId = 6;
      pg.nsUserId = 2;
      pg.nsUsertalkId = 3;
      pg.nsCategoryId = 14;
      pg.nsTemplateId = 10;
    };
    var setRedirs = function setRedirs() {
      var r = 'redirect';
      var R = 'REDIRECT';
      var redirLists = {
        ar: [R, 'تحويل'],
        be: [r, 'перанакіраваньне'],
        bg: [r, 'пренасочване', 'виж'],
        bs: [r, 'Preusmjeri', 'preusmjeri', 'PREUSMJERI'],
        bn: [R, 'পুনর্নির্দেশ'],
        cs: [R, 'PŘESMĚRUJ'],
        cy: [r, 'ail-cyfeirio'],
        de: [R, 'WEITERLEITUNG'],
        el: [R, 'ΑΝΑΚΑΤΕΥΘΥΝΣΗ'],
        eo: [R, 'ALIDIREKTU', 'ALIDIREKTI'],
        es: [R, 'REDIRECCIÓN'],
        et: [r, 'suuna'],
        ga: [r, 'athsheoladh'],
        gl: [r, 'REDIRECCIÓN', 'REDIRECIONAMENTO'],
        he: [R, 'הפניה'],
        hu: [R, 'ÁTIRÁNYÍTÁS'],
        is: [r, 'tilvísun', 'TILVÍSUN'],
        it: [R, 'RINVIA', 'Rinvia'],
        ja: [R, '転送'],
        mk: [r, 'пренасочување', 'види'],
        nds: [r, 'wiederleiden'],
        'nds-nl': [R, 'DEURVERWIEZING', 'DUURVERWIEZING'],
        nl: [R, 'DOORVERWIJZING'],
        nn: [r, 'omdiriger'],
        pl: [R, 'PATRZ', 'PRZEKIERUJ', 'TAM'],
        pt: [R, 'redir'],
        ru: [R, 'ПЕРЕНАПРАВЛЕНИЕ', 'ПЕРЕНАПР'],
        sk: [r, 'presmeruj'],
        sr: [r, 'Преусмери', 'преусмери', 'ПРЕУСМЕРИ', 'Preusmeri', 'preusmeri', 'PREUSMERI'],
        tt: [R, 'yünältü', 'перенаправление', 'перенапр'],
        uk: [R, 'ПЕРЕНАПРАВЛЕННЯ', 'ПЕРЕНАПР'],
        vi: [r, 'đổi'],
        yi: [R, 'ווייטערפירן'],
        zh: [R, '重定向'] // no comma
      };

      var redirList = redirLists[pg.wiki.lang] || [r, R];
      // Mediawiki is very tolerant about what comes after the #redirect at the start
      pg.re.redirect = new RegExp("^\\s*[#](".concat(redirList.join('|'), ").*?\\[{2}([^\\|\\]]*)(|[^\\]]*)?\\]{2}\\s*(.*)"), 'i');
    };
    var setInterwiki = function setInterwiki() {
      if (pg.wiki.wikimedia) {
        // From https://meta.wikimedia.org/wiki/List_of_Wikipedias
        // en.wikipedia.org/w/api.php?action=sitematrix&format=json&smtype=language&smlangprop=code&formatversion=2
        pg.wiki.interwiki = 'aa|ab|ace|af|ak|als|am|an|ang|ar|arc|arz|as|ast|av|ay|az|ba|bar|bat-smg|bcl|be|be-x-old|bg|bh|bi|bjn|bm|bn|bo|bpy|br|bs|bug|bxr|ca|cbk-zam|cdo|ce|ceb|ch|cho|chr|chy|ckb|co|cr|crh|cs|csb|cu|cv|cy|da|de|diq|dsb|dv|dz|ee|el|eml|en|eo|es|et|eu|ext|fa|ff|fi|fiu-vro|fj|fo|fr|frp|frr|fur|fy|ga|gag|gan|gd|gl|glk|gn|got|gu|gv|ha|hak|haw|he|hi|hif|ho|hr|hsb|ht|hu|hy|hz|ia|id|ie|ig|ii|ik|ilo|io|is|it|iu|ja|jbo|jv|ka|kaa|kab|kbd|kg|ki|kj|kk|kl|km|kn|ko|koi|kr|krc|ks|ksh|ku|kv|kw|ky|la|lad|lb|lbe|lg|li|lij|lmo|ln|lo|lt|ltg|lv|map-bms|mdf|mg|mh|mhr|mi|mk|ml|mn|mo|mr|mrj|ms|mt|mus|mwl|my|myv|mzn|na|nah|nap|nds|nds-nl|ne|new|ng|nl|nn|no|nov|nrm|nv|ny|oc|om|or|os|pa|pag|pam|pap|pcd|pdc|pfl|pi|pih|pl|pms|pnb|pnt|ps|pt|qu|rm|rmy|rn|ro|roa-rup|roa-tara|ru|rue|rw|sa|sah|sc|scn|sco|sd|se|sg|sh|si|simple|sk|sl|sm|sn|so|sq|sr|srn|ss|st|stq|su|sv|sw|szl|ta|te|tet|tg|th|ti|tk|tl|tn|to|tpi|tr|ts|tt|tum|tw|ty|udm|ug|uk|ur|uz|ve|vec|vi|vls|vo|wa|war|wo|wuu|xal|xh|yi|yo|za|zea|zh|zh-classical|zh-min-nan|zh-yue|zu';
        pg.re.interwiki = new RegExp("^".concat(pg.wiki.interwiki, ":"));
      } else {
        pg.wiki.interwiki = null;
        pg.re.interwiki = new RegExp('^$');
      }
    };

    // return a regexp pattern matching all variants to write the given namespace
    var nsRe = function nsRe(namespaceId) {
      var imageNamespaceVariants = [];
      jQuery.each(mw.config.get('wgNamespaceIds'), function (_localizedNamespaceLc, _namespaceId) {
        if (_namespaceId !== namespaceId) {
          return;
        }
        _localizedNamespaceLc = upcaseFirst(_localizedNamespaceLc);
        imageNamespaceVariants.push(mw.util.escapeRegExp(_localizedNamespaceLc).split(' ').join('[ _]'), mw.util.escapeRegExp(encodeURI(_localizedNamespaceLc)));
      });
      return "(?:".concat(imageNamespaceVariants.join('|'), ")");
    };
    var nsReImage = function nsReImage() {
      return nsRe(pg.nsImageId);
    };
    // ENDFILE: namespaces.js

    // STARTFILE: selpop.js

    var getEditboxSelection = function getEditboxSelection() {
      // see http://www.webgurusforum.com/8/12/0
      var editbox;
      try {
        editbox = document.editform.wpTextbox1;
      } catch (_unused19) {
        return;
      }
      // IE, Opera
      if (document.selection) {
        return document.selection.createRange().text;
      }
      // Mozilla
      var selStart = editbox.selectionStart;
      var selEnd = editbox.selectionEnd;
      return editbox.value.substring(selStart, selEnd);
    };
    var doSelectionPopup = function doSelectionPopup() {
      // popup if the selection looks like [[foo|anything afterwards at all
      // or [[foo|bar]]text without ']]'
      // or [[foo|bar]]
      var sel = getEditboxSelection();
      var open = sel.indexOf('[[');
      var pipe = sel.indexOf('|');
      var close = sel.indexOf(']]');
      if (open === -1 || pipe === -1 && close === -1) {
        return;
      }
      if (pipe !== -1 && open > pipe || close !== -1 && open > close) {
        return;
      }
      var article = new Title(sel.substring(open + 2, pipe < 0 ? close : pipe));
      if (getValueOf('popupOnEditSelection') === 'boxpreview') {
        return doSeparateSelectionPopup(sel, article);
      }
      if (close > 0 && sel.slice(Math.max(0, close + 2)).includes('[[')) {
        return;
      }
      var a = document.createElement('a');
      a.href = pg.wiki.titlebase + article.urlString();
      mouseOverWikiLink2(a);
      if (a.navpopup) {
        a.navpopup.addHook(function () {
          runStopPopupTimer(a.navpopup);
        }, 'unhide', 'after');
      }
    };
    var doSeparateSelectionPopup = function doSeparateSelectionPopup(str, article) {
      var div = document.querySelector('#selectionPreview');
      if (!div) {
        div = document.createElement('div');
        div.id = 'selectionPreview';
        try {
          var box = document.editform.wpTextbox1;
          box.parentNode.insertBefore(div, box);
        } catch (_unused20) {
          return;
        }
      }
      var p = prepPreviewmaker(str, article, newNavpopup(document.createElement('a'), article));
      p.makePreview();
      if (p.html) {
        div.innerHTML = p.html;
      }
      div.ranSetupTooltipsAlready = false;
      popTipsSoonFn('selectionPreview')();
    };

    // ENDFILE: selpop.js

    // STARTFILE: navpopup.js
    /**
     * @fileoverview  Defines two classes: {@link Navpopup} and {@link Mousetracker}.
     *
     * <code>Navpopup</code> describes popups: when they appear, where, what
     * they look like and so on.
     *
     * <code>Mousetracker</code> "captures" the mouse using
     * <code>document.onmousemove</code>.
     */

    /**
     * Creates a new Mousetracker.
     * @constructor
     * @class The Mousetracker class. This monitors mouse movements and manages associated hooks.
     */
    var Mousetracker = /*#__PURE__*/function () {
      function Mousetracker() {
        _classCallCheck(this, Mousetracker);
        /**
         * Interval to regularly run the hooks anyway, in milliseconds.
         * @type Integer
         */
        this.loopDelay = 400;

        /**
         * Timer for the loop.
         * @type Timer
         */
        this.timer = null;

        /**
         * Flag - are we switched on?
         * @type Boolean
         */
        this.active = false;

        /**
         * Flag - are we probably inaccurate, i.e. not reflecting the actual mouse position?
         */
        this.dirty = true;

        /**
         * Array of hook functions.
         * @private
         * @type Array
         */
        this.hooks = [];
      }

      /**
       * Adds a hook, to be called when we get events.
       * @param {Function} f A function which is called as
       * <code>f(x,y)</code>. It should return <code>true</code> when it
       * wants to be removed, and <code>false</code> otherwise.
       */
      _createClass(Mousetracker, [{
        key: "addHook",
        value: function addHook(f) {
          this.hooks.push(f);
        }

        /**
         * Runs hooks, passing them the x
         * and y coords of the mouse.  Hook functions that return true are
         * passed to {@link Mousetracker#removeHooks} for removal.
         * @private
         */
      }, {
        key: "runHooks",
        value: function runHooks() {
          if (!this.hooks || this.hooks.length === 0) {
            return;
          }
          // log('Mousetracker.runHooks; we got some hooks to run');
          var remove = false;
          var removeObj = {};

          // this method gets called a LOT -
          // pre-cache some variables
          var x = this.x;
          var y = this.y;
          var len = this.hooks.length;
          for (var i = 0; i < len; ++i) {
            //~ run the hook function, and remove it if it returns true
            if (this.hooks[i](x, y) === true) {
              remove = true;
              removeObj[i] = true;
            }
          }
          if (remove) {
            this.removeHooks(removeObj);
          }
        }

        /**
         * Removes hooks.
         * @private
         * @param {Object} removeObj An object whose keys are the index
         * numbers of functions for removal, with values that evaluate to true
         */
      }, {
        key: "removeHooks",
        value: function removeHooks(removeObj) {
          var newHooks = [];
          var len = this.hooks.length;
          for (var i = 0; i < len; ++i) {
            if (!removeObj[i]) {
              newHooks.push(this.hooks[i]);
            }
          }
          this.hooks = newHooks;
        }

        /**
         * Event handler for mouse wiggles.
         * We simply grab the event, set x and y and run the hooks.
         * This makes the cpu all hot and bothered :-(
         * @private
         * @param {Event} e Mousemove event
         */
      }, {
        key: "track",
        value: function track(e) {
          //~ Apparently this is needed in IE.
          e || (e = window.event);
          var x;
          var y;
          if (e) {
            if (e.pageX) {
              x = e.pageX;
              y = e.pageY;
            } else if (typeof e.clientX !== 'undefined') {
              var left;
              var top;
              var docElt = document.documentElement;
              if (docElt) {
                left = docElt.scrollLeft;
              }
              left = left || document.body.scrollLeft || document.scrollLeft || 0;
              if (docElt) {
                top = docElt.scrollTop;
              }
              top = top || document.body.scrollTop || document.scrollTop || 0;
              x = e.clientX + left;
              y = e.clientY + top;
            } else {
              return;
            }
            this.setPosition(x, y);
          }
        }

        /**
         * Sets the x and y coordinates stored and takes appropriate action,
         * running hooks as appropriate.
         * @param {Integer} x, y Screen coordinates to set
         */
      }, {
        key: "setPosition",
        value: function setPosition(x, y) {
          this.x = x;
          this.y = y;
          if (this.dirty || this.hooks.length === 0) {
            this.dirty = false;
            return;
          }
          if (typeof this.lastHook_x !== 'number') {
            this.lastHook_x = -100;
            this.lastHook_y = -100;
          }
          var diff = (this.lastHook_x - x) * (this.lastHook_y - y);
          diff = diff >= 0 ? diff : -diff;
          if (diff > 1) {
            this.lastHook_x = x;
            this.lastHook_y = y;
            if (this.dirty) {
              this.dirty = false;
            } else {
              this.runHooks();
            }
          }
        }

        /**
         * Sets things in motion, unless they are already that is, registering an event handler on
         * <code>document.onmousemove</code>. A half-hearted attempt is made to preserve the old event
         * handler if there is one.
         */
      }, {
        key: "enable",
        value: function enable() {
          if (this.active) {
            return;
          }
          this.active = true;
          //~ Save the current handler for mousemove events. This isn't too
          //~ robust, of course.
          this.savedHandler = document.onmousemove;
          //~ Gotta save @tt{this} again for the closure, and use apply for
          //~ the member function.
          var savedThis = this;
          document.onmousemove = function (e) {
            savedThis.track(e);
          };
          if (this.loopDelay) {
            this.timer = setInterval(function () {
              //log('loop delay in mousetracker is working');
              savedThis.runHooks();
            }, this.loopDelay);
          }
        }

        /**
         * Disables the tracker, removing the event handler.
         */
      }, {
        key: "disable",
        value: function disable() {
          if (!this.active) {
            return;
          }
          if (typeof this.savedHandler === 'function') {
            document.onmousemove = this.savedHandler;
          } else {
            delete document.onmousemove;
          }
          if (this.timer) {
            clearInterval(this.timer);
          }
          this.active = false;
        }
      }]);
      return Mousetracker;
    }();
    /**
     * Creates a new Navpopup.
     * Gets a UID for the popup and
     * @param init Contructor object. If <code>init.draggable</code> is true or absent, the popup becomes draggable.
     * @constructor
     * @class The Navpopup class. This generates popup hints, and does some management of them.
     */
    var Navpopup = /*#__PURE__*/function () {
      function Navpopup() {
        _classCallCheck(this, Navpopup);
        // alert('new Navpopup(init)');

        /**
         * UID for each Navpopup instance.
         * Read-only.
         * @type integer
         */
        this.uid = Navpopup.uid++;

        /**
         * Read-only flag for current visibility of the popup.
         * @type boolean
         * @private
         */
        this.visible = false;

        /** Flag to be set when we want to cancel a previous request to
         * show the popup in a little while.
         * @private
         * @type boolean
         */
        this.noshow = false;

        /** Categorised list of hooks.
         * @see #runHooks
         * @see #addHook
         * @private
         * @type Object
         */
        this.hooks = {
          create: [],
          unhide: [],
          hide: []
        };

        /**
         * list of unique IDs of hook functions, to avoid duplicates
         * @private
         */
        this.hookIds = {};

        /** List of downloads associated with the popup.
         * @private
         * @type Array
         */
        this.downloads = [];

        /**
         * Number of uncompleted downloads.
         * @type integer
         */
        this.pending = null;

        /**
         * Tolerance in pixels when detecting whether the mouse has left the popup.
         * @type integer
         */
        this.fuzz = 5;

        /**
         * Flag to toggle running {@link #limitHorizontalPosition} to regulate the popup's position.
         * @type boolean
         */
        this.constrained = true;

        /**
         * The popup width in pixels.
         * @private
         * @type integer
         */
        this.width = 0;

        /**
         * The popup width in pixels.
         * @private
         * @type integer
         */
        this.height = 0;

        /**
         * The main content DIV element.
         * @type HTMLDivElement
         */
        this.mainDiv = null;
        this.createMainDiv();

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

      /**
       * Retrieves the {@link #visible} attribute, indicating whether the popup is currently visible.
       * @type boolean
       */
      _createClass(Navpopup, [{
        key: "isVisible",
        value: function isVisible() {
          return this.visible;
        }

        /**
         * Repositions popup using CSS style.
         * @private
         * @param {integer} x x-coordinate (px)
         * @param {integer} y y-coordinate (px)
         * @param {boolean} noLimitHor Don't call {@link #limitHorizontalPosition}
         */
      }, {
        key: "reposition",
        value: function reposition(x, y, noLimitHor) {
          log("reposition(".concat(x, ",").concat(y, ",").concat(noLimitHor, ")"));
          if (typeof x !== 'undefined' && x !== null) {
            this.left = x;
          }
          if (typeof y !== 'undefined' && y !== null) {
            this.top = y;
          }
          if (typeof this.left !== 'undefined' && typeof this.top !== 'undefined') {
            this.mainDiv.style.left = "".concat(this.left, "px");
            this.mainDiv.style.top = "".concat(this.top, "px");
          }
          if (!noLimitHor) {
            this.limitHorizontalPosition();
          }
        }

        /**
         * Prevents popups from being in silly locations. Hopefully.
         * Should not be run if {@link #constrained} is true.
         * @private
         */
      }, {
        key: "limitHorizontalPosition",
        value: function limitHorizontalPosition() {
          if (!this.constrained || this.tooWide) {
            return;
          }
          this.updateDimensions();
          var x = this.left;
          var w = this.width;
          var cWidth = document.body.clientWidth;

          //	log('limitHorizontalPosition: x='+x+
          //			', this.left=' + this.left +
          //			', this.width=' + this.width +
          //			', cWidth=' + cWidth);

          if (x + w >= cWidth || x > 0 && this.maxWidth && this.width < this.maxWidth && this.height > this.width && x > cWidth - this.maxWidth) {
            // This is a very nasty hack. There has to be a better way!
            // We find the "natural" width of the div by positioning it at the far left
            // then reset it so that it should be flush right (well, nearly)
            this.mainDiv.style.left = '-10000px';
            this.mainDiv.style.width = "".concat(this.maxWidth, "px");
            var naturalWidth = Number.parseInt(this.mainDiv.offsetWidth, 10);
            var newLeft = cWidth - naturalWidth - 1;
            if (newLeft < 0) {
              newLeft = 0;
              this.tooWide = true;
            } // still unstable for really wide popups?
            log("limitHorizontalPosition: moving to (".concat(newLeft, ",").concat(this.top, ");") + " naturalWidth=".concat(naturalWidth, ", clientWidth=").concat(cWidth));
            this.reposition(newLeft, null, true);
          }
        }

        /**
         * Brings popup to the top of the z-order.
         * We increment the {@link #highest} property of the contructor here.
         * @private
         */
      }, {
        key: "raise",
        value: function raise() {
          this.mainDiv.style.zIndex = Navpopup.highest + 1;
          ++Navpopup.highest;
        }

        /**
         * Shows the popup provided {@link #noshow} is not true.
         * Updates the position, brings the popup to the top of the z-order and unhides it.
         */
      }, {
        key: "show",
        value: function show() {
          //document.title+='s';
          if (this.noshow) {
            return;
          }
          //document.title+='t';
          this.reposition();
          this.raise();
          this.unhide();
        }

        /**
         * Checks to see if the mouse pointer has
         * stabilised (checking every <code>time</code>/2 milliseconds) and runs the
         * {@link #show} method if it has.
         * @param {integer} time The minimum time (ms) before the popup may be shown.
         */
      }, {
        key: "showSoonIfStable",
        value: function showSoonIfStable(time) {
          log("showSoonIfStable, time=".concat(time));
          if (this.visible) {
            return;
          }
          this.noshow = false;

          //~ initialize these variables so that we never run @tt{show} after
          //~ just half the time
          this.stable_x = -10000;
          this.stable_y = -10000;
          var stableShow = function stableShow() {
            log('stableShow called');
            var new_x = Navpopup.tracker.x;
            var new_y = Navpopup.tracker.y;
            var dx = savedThis.stable_x - new_x;
            var dy = savedThis.stable_y - new_y;
            var fuzz2 = 0; // savedThis.fuzz * savedThis.fuzz;
            //document.title += '[' + [savedThis.stable_x,new_x, savedThis.stable_y,new_y, dx, dy, fuzz2].join(',') + '] ';
            if (dx * dx <= fuzz2 && dy * dy <= fuzz2) {
              log('mouse is stable');
              clearInterval(savedThis.showSoonStableTimer);
              savedThis.reposition(new_x + 2, new_y + 2);
              savedThis.show();
              savedThis.limitHorizontalPosition();
              return;
            }
            savedThis.stable_x = new_x;
            savedThis.stable_y = new_y;
          };
          var savedThis = this;
          this.showSoonStableTimer = setInterval(stableShow, time / 2);
        }

        /**
         * Sets the {@link #noshow} flag and hides the popup. This should be called
         * when the mouse leaves the link before
         * (or after) it's actually been displayed.
         */
      }, {
        key: "banish",
        value: function banish() {
          log('banish called');
          // hide and prevent showing with showSoon in the future
          this.noshow = true;
          if (this.showSoonStableTimer) {
            log('clearing showSoonStableTimer');
            clearInterval(this.showSoonStableTimer);
          }
          this.hide();
        }

        /**
         * Runs hooks added with {@link #addHook}.
         * @private
         * @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
         * @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
         */
      }, {
        key: "runHooks",
        value: function runHooks(key, when) {
          if (!this.hooks[key]) {
            return;
          }
          var keyHooks = this.hooks[key];
          var len = keyHooks.length;
          for (var i = 0; i < len; ++i) {
            if (keyHooks[i] && keyHooks[i].when === when && Reflect.apply(keyHooks[i].hook, this, [])) {
              // remove the hook
              if (keyHooks[i].hookId) {
                delete this.hookIds[keyHooks[i].hookId];
              }
              keyHooks[i] = null;
            }
          }
        }

        /**
         * Adds a hook to the popup. Hook functions are run with <code>this</code> set to refer to the
         * Navpopup instance, and no arguments.
         * @param {Function} hook The hook function. Functions that return true are deleted.
         * @param {String} key Key name of the {@link #hooks} array - one of 'create', 'unhide', 'hide'
         * @param {String} when Controls exactly when the hook is run: either 'before' or 'after'
         * @param {String} uid A truthy string identifying the hook function; if it matches another hook
         * in this position, it won't be added again.
         */
      }, {
        key: "addHook",
        value: function addHook(hook, key, when, uid) {
          when || (when = 'after');
          if (!this.hooks[key]) {
            return;
          }
          // if uid is specified, don't add duplicates
          var hookId = null;
          if (uid) {
            hookId = [key, when, uid].join('|');
            if (this.hookIds[hookId]) {
              return;
            }
            this.hookIds[hookId] = true;
          }
          this.hooks[key].push({
            hook: hook,
            when: when,
            hookId: hookId
          });
        }

        /**
         * Creates the main DIV element, which contains all the actual popup content.
         * Runs hooks with key 'create'.
         * @private
         */
      }, {
        key: "createMainDiv",
        value: function createMainDiv() {
          if (this.mainDiv) {
            return;
          }
          this.runHooks('create', 'before');
          var mainDiv = document.createElement('div');
          var savedThis = this;
          mainDiv.onclick = function (e) {
            savedThis.onclickHandler(e);
          };
          mainDiv.className = this.className ? this.className : 'navpopup_maindiv';
          mainDiv.id = mainDiv.className + this.uid;
          mainDiv.style.position = 'absolute';
          mainDiv.style.minWidth = '350px';
          mainDiv.style.display = 'none';
          mainDiv.className = 'navpopup';

          // easy access to javascript object through DOM functions
          mainDiv.navpopup = this;
          this.mainDiv = mainDiv;
          document.body.appendChild(mainDiv);
          this.runHooks('create', 'after');
        }

        /**
         * Calls the {@link #raise} method.
         * @private
         */
      }, {
        key: "onclickHandler",
        value: function onclickHandler() /*e*/{
          this.raise();
        }

        /**
         * Makes the popup draggable, using a {@link Drag} object.
         * @private
         */
      }, {
        key: "makeDraggable",
        value: function makeDraggable(handleName) {
          if (!this.mainDiv) {
            this.createMainDiv();
          }
          var drag = new Drag();
          if (!handleName) {
            drag.startCondition = function (e) {
              try {
                if (!e.shiftKey) {
                  return false;
                }
              } catch (_unused21) {
                return false;
              }
              return true;
            };
          }
          var dragHandle;
          if (handleName) {
            dragHandle = document.getElementById(handleName);
          }
          if (!dragHandle) {
            dragHandle = this.mainDiv;
          }
          var np = this;
          drag.endHook = function (x, y) {
            Navpopup.tracker.dirty = true;
            np.reposition(x, y);
          };
          drag.init(dragHandle, this.mainDiv);
        }

        /**
         * Hides the popup using CSS. Runs hooks with key 'hide'.
         * Sets {@link #visible} appropriately.
         * {@link #banish} should be called externally instead of this method.
         * @private
         */
      }, {
        key: "hide",
        value: function hide() {
          this.runHooks('hide', 'before');
          this.abortDownloads();
          if (typeof this.visible !== 'undefined' && this.visible) {
            this.mainDiv.style.display = 'none';
            this.visible = false;
          }
          this.runHooks('hide', 'after');
        }

        /**
         * Shows the popup using CSS. Runs hooks with key 'unhide'.
         * Sets {@link #visible} appropriately.   {@link #show} should be called externally instead of this method.
         * @private
         */
      }, {
        key: "unhide",
        value: function unhide() {
          this.runHooks('unhide', 'before');
          if (typeof this.visible !== 'undefined' && !this.visible) {
            this.mainDiv.style.display = 'inline';
            this.visible = true;
          }
          this.runHooks('unhide', 'after');
        }

        /**
         * Sets the <code>innerHTML</code> attribute of the main div containing the popup content.
         * @param {String} html The HTML to set.
         */
      }, {
        key: "setInnerHTML",
        value: function setInnerHTML(html) {
          this.mainDiv.innerHTML = html;
        }

        /**
         * Updates the {@link #width} and {@link #height} attributes with the CSS properties.
         * @private
         */
      }, {
        key: "updateDimensions",
        value: function updateDimensions() {
          this.width = Number.parseInt(this.mainDiv.offsetWidth, 10);
          this.height = Number.parseInt(this.mainDiv.offsetHeight, 10);
        }

        /**
         * Checks if the point (x,y) is within {@link #fuzz} of the
         * {@link #mainDiv}.
         * @param {integer} x x-coordinate (px)
         * @param {integer} y y-coordinate (px)
         * @type boolean
         */
      }, {
        key: "isWithin",
        value: function isWithin(x, y) {
          //~ If we're not even visible, no point should be considered as
          //~ being within the popup.
          if (!this.visible) {
            return false;
          }
          this.updateDimensions();
          var fuzz = this.fuzz || 0;
          //~ Use a simple box metric here.
          return x + fuzz >= this.left && x - fuzz <= this.left + this.width && y + fuzz >= this.top && y - fuzz <= this.top + this.height;
        }

        /**
         * Adds a download to {@link #downloads}.
         * @param {Downloader} download
         */
      }, {
        key: "addDownload",
        value: function addDownload(download) {
          if (!download) {
            return;
          }
          this.downloads.push(download);
        }

        /**
         * Aborts the downloads listed in {@link #downloads}.
         * @see Downloader#abort
         */
      }, {
        key: "abortDownloads",
        value: function abortDownloads() {
          var _iterator16 = _createForOfIteratorHelper(this.downloads),
            _step16;
          try {
            for (_iterator16.s(); !(_step16 = _iterator16.n()).done;) {
              var d = _step16.value;
              if (d && d.abort) {
                d.abort();
              }
            }
          } catch (err) {
            _iterator16.e(err);
          } finally {
            _iterator16.f();
          }
          this.downloads = [];
        }
      }]);
      return Navpopup;
    }();
    /**
     * A UID for each Navpopup. This constructor property is just a counter.
     * @type integer
     * @private
     */
    Navpopup.uid = 0;

    /**
     * Counter indicating the z-order of the "highest" popup.
     * We start the z-index at 1000 so that popups are above everything
     * else on the screen.
     * @private
     * @type integer
     */
    Navpopup.highest = 1000;

    /**
     * A {@link Mousetracker} instance which is a property of the constructor (pseudo-global).
     */
    Navpopup.tracker = new Mousetracker();
    // ENDFILE: navpopup.js

    // STARTFILE: diff.js

    /*
     * Javascript Diff Algorithm By John Resig (http://ejohn.org/) and Lupin
     *
     * More Info: http://ejohn.org/projects/javascript-diff-algorithm/
     */

    var delFmt = function delFmt(x) {
      if (x.length === 0) {
        return '';
      }
      return "<del class='popupDiff'>".concat(x.join(''), "</del>");
    };
    var insFmt = function insFmt(x) {
      if (x.length === 0) {
        return '';
      }
      return "<ins class='popupDiff'>".concat(x.join(''), "</ins>");
    };
    var countCrossings = function countCrossings(a, b, i, eject) {
      // count the crossings on the edge starting at b[i]
      if (!b[i].row && b[i].row !== 0) {
        return -1;
      }
      var count = 0;
      var _iterator17 = _createForOfIteratorHelper(a.entries()),
        _step17;
      try {
        for (_iterator17.s(); !(_step17 = _iterator17.n()).done;) {
          var _step17$value2 = _slicedToArray(_step17.value, 2),
            j = _step17$value2[0],
            element = _step17$value2[1];
          if (!element.row && element.row !== 0) {
            continue;
          }
          if ((j - b[i].row) * (i - element.row) > 0) {
            if (eject) {
              return true;
            }
            count++;
          }
        }
      } catch (err) {
        _iterator17.e(err);
      } finally {
        _iterator17.f();
      }
      return count;
    };
    var shortenDiffString = function shortenDiffString(str, context) {
      var re = new RegExp('(<del[\\s\\S]*?</del>|<ins[\\s\\S]*?</ins>)');
      var splitted = str.parenSplit(re);
      var ret = [''];
      for (var i = 0; i < splitted.length; i += 2) {
        if (splitted[i].length < 2 * context) {
          ret[ret.length - 1] += splitted[i];
          if (i + 1 < splitted.length) {
            ret[ret.length - 1] += splitted[i + 1];
          }
          continue;
        } else {
          if (i > 0) {
            ret[ret.length - 1] += splitted[i].slice(0, Math.max(0, context));
          }
          if (i + 1 < splitted.length) {
            ret.push(splitted[i].slice(Math.max(0, splitted[i].length - context)) + splitted[i + 1]);
          }
        }
      }
      while (ret.length > 0 && !ret[0]) {
        ret = ret.slice(1);
      }
      return ret;
    };
    var diffString = function diffString(o, n, simpleSplit) {
      var splitRe = new RegExp('([[]{2}|[\\]]{2}|[{]{2,3}|[}]{2,3}|[|]|=|<|>|[*:]+|\\s|\\b)');

      //  We need to split the strings o and n first, and entify() the parts
      //  individually, so that the HTML entities are never cut apart. (AxelBoldt)
      var i;
      var oSplitted;
      var nSplitted;
      if (simpleSplit) {
        oSplitted = o.split(/\b/);
        nSplitted = n.split(/\b/);
      } else {
        oSplitted = o.parenSplit(splitRe);
        nSplitted = n.parenSplit(splitRe);
      }
      for (i = 0; i < oSplitted.length; ++i) {
        oSplitted[i] = oSplitted[i].entify();
      }
      for (i = 0; i < nSplitted.length; ++i) {
        nSplitted[i] = nSplitted[i].entify();
      }
      var out = diff(oSplitted, nSplitted);
      var str = '';
      var acc = []; // accumulator for prettier output

      // crossing pairings -- eg 'A B' vs 'B A' -- cause problems, so let's iron them out
      // this doesn't always do things optimally but it should be fast enough
      var maxOutputPair = 0;
      for (i = 0; i < out.n.length; ++i) {
        if (out.n[i].paired) {
          if (maxOutputPair > out.n[i].row) {
            // tangle - delete pairing
            out.o[out.n[i].row] = out.o[out.n[i].row].text;
            out.n[i] = out.n[i].text;
          }
          if (maxOutputPair < out.n[i].row) {
            maxOutputPair = out.n[i].row;
          }
        }
      }

      // output the stuff preceding the first paired old line
      for (i = 0; i < out.o.length && !out.o[i].paired; ++i) {
        acc.push(out.o[i]);
      }
      str += delFmt(acc);
      acc = [];

      // main loop
      for (i = 0; i < out.n.length; ++i) {
        // output unpaired new "lines"
        while (i < out.n.length && !out.n[i].paired) {
          acc.push(out.n[i++]);
        }
        str += insFmt(acc);
        acc = [];
        if (i < out.n.length) {
          // this new "line" is paired with the (out.n[i].row)th old "line"
          str += out.n[i].text;
          // output unpaired old rows starting after this new line's partner
          var m = out.n[i].row + 1;
          while (m < out.o.length && !out.o[m].paired) {
            acc.push(out.o[m++]);
          }
          str += delFmt(acc);
          acc = [];
        }
      }
      return str;
    };

    // see http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object
    // FIXME: use obj.hasOwnProperty instead of this kludge!
    var jsReservedProperties = new RegExp('^(constructor|prototype|__((define|lookup)[GS]etter)__' + '|eval|hasOwnProperty|propertyIsEnumerable' + '|to(Source|String|LocaleString)|(un)?watch|valueOf)$');
    var diffBugAlert = function diffBugAlert(word) {
      if (!diffBugAlert.list[word]) {
        diffBugAlert.list[word] = 1;
        alert("Bad word: ".concat(word, "\n\nPlease report this bug."));
      }
    };
    diffBugAlert.list = {};
    var makeDiffHashtable = function makeDiffHashtable(src) {
      var ret = {};
      for (var i = 0; i < src.length; i++) {
        if (jsReservedProperties.test(src[i])) {
          src[i] += '<!-- -->';
        }
        if (!ret[src[i]]) {
          ret[src[i]] = [];
        }
        try {
          ret[src[i]].push(i);
        } catch (_unused22) {
          diffBugAlert(src[i]);
        }
      }
      return ret;
    };
    var diff = function diff(o, n) {
      // pass 1: make hashtable ns with new rows as keys
      var ns = makeDiffHashtable(n);

      // pass 2: make hashtable os with old rows as keys
      var os = makeDiffHashtable(o);

      // pass 3: pair unique new rows and matching unique old rows
      var i;
      for (i in ns) {
        if (ns[i].length === 1 && os[i] && os[i].length === 1) {
          n[ns[i][0]] = {
            text: n[ns[i][0]],
            row: os[i][0],
            paired: true
          };
          o[os[i][0]] = {
            text: o[os[i][0]],
            row: ns[i][0],
            paired: true
          };
        }
      }

      // pass 4: pair matching rows immediately following paired rows (not necessarily unique)
      for (i = 0; i < n.length - 1; i++) {
        if (n[i].paired && !n[i + 1].paired && n[i].row + 1 < o.length && !o[n[i].row + 1].paired && n[i + 1] === o[n[i].row + 1]) {
          n[i + 1] = {
            text: n[i + 1],
            row: n[i].row + 1,
            paired: true
          };
          o[n[i].row + 1] = {
            text: o[n[i].row + 1],
            row: i + 1,
            paired: true
          };
        }
      }

      // pass 5: pair matching rows immediately preceding paired rows (not necessarily unique)
      for (i = n.length - 1; i > 0; i--) {
        if (n[i].paired && !n[i - 1].paired && n[i].row > 0 && !o[n[i].row - 1].paired && n[i - 1] === o[n[i].row - 1]) {
          n[i - 1] = {
            text: n[i - 1],
            row: n[i].row - 1,
            paired: true
          };
          o[n[i].row - 1] = {
            text: o[n[i].row - 1],
            row: i - 1,
            paired: true
          };
        }
      }
      return {
        o: o,
        n: n
      };
    };

    // ENDFILE: diff.js

    // STARTFILE: init.js
    var setSiteInfo = function setSiteInfo() {
      if (window.popupLocalDebug) {
        pg.wiki.hostname = 'en.wikipedia.org';
      } else {
        pg.wiki.hostname = location.hostname; // use in preference to location.hostname for flexibility (?)
      }

      pg.wiki.wikimedia = new RegExp('(wiki([pm]edia|source|books|news|quote|versity|species|voyage|data)|metawiki|wiktionary|mediawiki)[.]org').test(pg.wiki.hostname);
      pg.wiki.wikia = new RegExp('[.]wikia[.]com$', 'i').test(pg.wiki.hostname);
      pg.wiki.isLocal = new RegExp('^localhost').test(pg.wiki.hostname);
      pg.wiki.commons = pg.wiki.wikimedia && pg.wiki.hostname !== 'commons.wikimedia.org' ? 'commons.wikimedia.org' : null;
      pg.wiki.lang = mw.config.get('wgContentLanguage');
      var port = location.port ? ":".concat(location.port) : '';
      pg.wiki.sitebase = pg.wiki.hostname + port;
    };
    var setUserInfo = function setUserInfo() {
      var params = {
        action: 'query',
        list: 'users',
        ususers: mw.config.get('wgUserName'),
        usprop: 'rights'
      };
      pg.user.canReview = false;
      if (getValueOf('popupReview')) {
        getMwApi().get(params).done(function (data) {
          var rights = data.query.users[0].rights;
          pg.user.canReview = rights.includes('review'); // TODO: Should it be a getValueOf('ReviewRight') ?
        });
      }
    };

    var fetchSpecialPageNames = function fetchSpecialPageNames() {
      var params = {
        action: 'query',
        meta: 'siteinfo',
        siprop: 'specialpagealiases',
        formatversion: 2,
        // cache for an hour
        uselang: 'content',
        maxage: 3600
      };
      return getMwApi().get(params).then(function (data) {
        pg.wiki.specialpagealiases = data.query.specialpagealiases;
      });
    };
    var setTitleBase = function setTitleBase() {
      var protocol = window.popupLocalDebug ? 'http:' : location.protocol;
      pg.wiki.articlePath = mw.config.get('wgArticlePath').replace(/\/\$1/, ''); // as in http://some.thing.com/wiki/Article
      pg.wiki.botInterfacePath = mw.config.get('wgScript');
      pg.wiki.APIPath = "".concat(mw.config.get('wgScriptPath'), "/api.php");
      // default mediawiki setting is paths like http://some.thing.com/articlePath/index.php?title=foo
      var titletail = "".concat(pg.wiki.botInterfacePath, "?title=");
      //var titletail2 = joinPath([pg.wiki.botInterfacePath, 'wiki.phtml?title=']);
      // other sites may need to add code here to set titletail depending on how their urls work
      pg.wiki.titlebase = "".concat(protocol, "//").concat(pg.wiki.sitebase).concat(titletail);
      //pg.wiki.titlebase2  = protocol + '//' + joinPath([pg.wiki.sitebase, titletail2]);
      pg.wiki.wikibase = "".concat(protocol, "//").concat(pg.wiki.sitebase).concat(pg.wiki.botInterfacePath);
      pg.wiki.apiwikibase = "".concat(protocol, "//").concat(pg.wiki.sitebase).concat(pg.wiki.APIPath);
      pg.wiki.articlebase = "".concat(protocol, "//").concat(pg.wiki.sitebase).concat(pg.wiki.articlePath);
      pg.wiki.commonsbase = "".concat(protocol, "//").concat(pg.wiki.commons).concat(pg.wiki.botInterfacePath);
      pg.wiki.apicommonsbase = "".concat(protocol, "//").concat(pg.wiki.commons).concat(pg.wiki.APIPath);
      pg.re.basenames = new RegExp("^(".concat(map(literalizeRegex, [pg.wiki.titlebase, pg.wiki.articlebase]).join('|'), ")"));
    };

    // Global regexps

    var setMainRegex = function setMainRegex() {
      var reStart = '[^:]*://';
      var preTitles = "".concat(literalizeRegex(mw.config.get('wgScriptPath')), "/(?:index[.]php|wiki[.]phtml)[?]title=");
      preTitles += "|".concat(literalizeRegex("".concat(pg.wiki.articlePath, "/")));
      var reEnd = "(".concat(preTitles, ")([^&?#]*)[^#]*(?:#(.+))?");
      pg.re.main = new RegExp(reStart + literalizeRegex(pg.wiki.sitebase) + reEnd);
    };
    var buildSpecialPageGroup = function buildSpecialPageGroup(specialPageObj) {
      var variants = [];
      variants.push(mw.util.escapeRegExp(specialPageObj.realname), mw.util.escapeRegExp(encodeURI(specialPageObj.realname)));
      specialPageObj.aliases.forEach(function (alias) {
        variants.push(mw.util.escapeRegExp(alias), mw.util.escapeRegExp(encodeURI(alias)));
      });
      return variants.join('|');
    };
    var setRegexps = function setRegexps() {
      setMainRegex();
      var sp = nsRe(pg.nsSpecialId);
      pg.re.urlNoPopup = new RegExp("((title=|/)".concat(sp, "(?:%3A|:)|section=[0-9]|^#$)"));
      pg.wiki.specialpagealiases.forEach(function (specialpage) {
        switch (specialpage.realname) {
          case 'Contributions':
            {
              pg.re.contribs = new RegExp("(title=|/)".concat(sp, "(?:%3A|:)(?:").concat(buildSpecialPageGroup(specialpage), ")") + "(&target=|/|/".concat(nsRe(pg.nsUserId), ":)(.*)"), 'i');
              break;
            }
          case 'Diff':
            {
              pg.re.specialdiff = new RegExp("/".concat(sp, "(?:%3A|:)(?:").concat(buildSpecialPageGroup(specialpage), ")") + "/([^?#]*)", 'i');
              break;
            }
          case 'Emailuser':
            {
              pg.re.email = new RegExp("(title=|/)".concat(sp, "(?:%3A|:)(?:").concat(buildSpecialPageGroup(specialpage), ")") + "(&target=|/|/(?:".concat(nsRe(pg.nsUserId), ":)?)(.*)"), 'i');
              break;
            }
          case 'Whatlinkshere':
            {
              pg.re.backlinks = new RegExp("(title=|/)".concat(sp, "(?:%3A|:)(?:").concat(buildSpecialPageGroup(specialpage), ")") + "(&target=|/)([^&]*)", 'i');
              break;
            }
          // No default
        }
      });

      var im = nsReImage();
      // note: tries to get images in infobox templates too, e.g. movie pages, album pages etc
      //					  (^|\[\[)image: *([^|\]]*[^|\] ]) *
      //					  (^|\[\[)image: *([^|\]]*[^|\] ])([^0-9\]]*([0-9]+) *px)?
      //														$4 = 120 as in 120px
      pg.re.image = new RegExp("(^|\\[\\[)".concat(im, ": *([^|\\]]*[^|\\] ])") + "([^0-9\\]]*([0-9]+) *px)?|(?:\\n *[|]?|[|]) *" + "(".concat(getValueOf('popupImageVarsRegexp'), ")") + " *= *(?:\\[\\[ *)?(?:".concat(im, ":)?") + "([^|]*?)(?:\\]\\])? *[|]? *\\n", 'img');
      pg.re.imageBracketCount = 6;
      pg.re.category = new RegExp("\\[\\[".concat(nsRe(pg.nsCategoryId), ": *([^|\\]]*[^|\\] ]) *"), 'i');
      pg.re.categoryBracketCount = 1;
      pg.re.ipUser = new RegExp('^' +
      // IPv6
      '(?::(?::|(?::[0-9A-Fa-f]{1,4}){1,7})|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,6}::|[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){7})' +
      // IPv4
      '|(((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}' + '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9]))$');
      pg.re.stub = new RegExp(getValueOf('popupStubRegexp'), 'im');
      pg.re.disambig = new RegExp(getValueOf('popupDabRegexp'), 'im');

      // FIXME replace with general parameter parsing function, this is daft
      pg.re.oldid = new RegExp('[?&]oldid=([^&]*)');
      pg.re.diff = new RegExp('[?&]diff=([^&]*)');
    };

    // miscellany

    var setupCache = function setupCache() {
      // page caching
      pg.cache.pages = [];
    };
    var setMisc = function setMisc() {
      pg.current.link = null;
      pg.current.links = [];
      pg.current.linksHash = {};
      setupCache();
      pg.timer.checkPopupPosition = null;
      pg.counter.loop = 0;

      // ids change with each popup: popupImage0, popupImage1 etc
      pg.idNumber = 0;

      // for myDecodeURI
      pg.misc.decodeExtras = [{
        from: '%2C',
        to: ','
      }, {
        from: '_',
        to: ' '
      }, {
        from: '%24',
        to: '$'
      }, {
        from: '%26',
        to: '&'
      } // no ,
      ];
    };

    var getMwApi = function getMwApi() {
      if (!pg.api.client) {
        pg.api.userAgent = "Navigation popups/1.0 (".concat(mw.config.get('wgServerName'), ")");
        pg.api.client = new mw.Api({
          ajax: {
            headers: {
              'Api-User-Agent': pg.api.userAgent
            }
          }
        });
      }
      return pg.api.client;
    };

    // We need a callback since this might end up asynchronous because of
    // the mw.loader.using() call.
    var setupPopups = function setupPopups(callback) {
      if (setupPopups.completed) {
        if (typeof callback === 'function') {
          callback();
        }
        return;
      }
      // These dependencies should alse be enforced from the gadget,
      // but not everyone loads this as a gadget, so double check
      mw.loader.using(['mediawiki.util', 'mediawiki.api', 'mediawiki.user', 'user.options', 'mediawiki.jqueryMsg']).then(fetchSpecialPageNames).then(function () {
        // NB translatable strings should be set up first (strings.js)
        // basics
        setupDebugging();
        setSiteInfo();
        setTitleBase();
        setOptions(); // see options.js
        setUserInfo();

        // namespaces etc
        setNamespaces();
        setInterwiki();

        // regexps
        setRegexps();
        setRedirs();

        // other stuff
        setMisc();
        setupLivePreview();

        // main deal here
        setupTooltips();
        log('In setupPopups(), just called setupTooltips()');
        Navpopup.tracker.enable();
        setupPopups.completed = true;
        if (typeof callback === 'function') {
          callback();
        }
      });
    };
    // ENDFILE: init.js

    // STARTFILE: navlinks.js

    // navlinks... let the fun begin
    //

    var defaultNavlinkSpec = function defaultNavlinkSpec() {
      var str = '';
      str += '<b><<mainlink|shortcut= >></b>';
      if (getValueOf('popupLastEditLink')) {
        str += '*<<lastEdit|shortcut=/>>|<<lastContrib>>|<<sinceMe>>if(oldid){|<<oldEdit>>|<<diffCur>>}';
      }

      // user links
      // contribs - log - count - email - block
      // count only if applicable; block only if popupAdminLinks
      str += 'if(user){<br><<contribs|shortcut=c>>*<<userlog|shortcut=L|log>>';
      str += 'if(ipuser){*<<arin>>}if(wikimedia){*<<count|shortcut=#>>}';
      str += 'if(ipuser){}else{*<<email|shortcut=E>>}if(admin){*<<block|shortcut=b>>|<<blocklog|log>>}}';

      // editing links
      // talkpage   -> edit|new - history - un|watch - article|edit
      // other page -> edit - history - un|watch - talk|edit|new
      var editstr = '<<edit|shortcut=e>>';
      var editOldidStr = "if(oldid){<<editOld|shortcut=e>>|<<revert|shortcut=v|rv>>|<<edit|cur>>}else{".concat(editstr, "}");
      var historystr = '<<history|shortcut=h>>|<<editors|shortcut=E|>>';
      var watchstr = '<<unwatch|unwatchShort>>|<<watch|shortcut=w|watchThingy>>';
      str += "<br>if(talk){".concat(editOldidStr, "|<<new|shortcut=+>>") + "*".concat(historystr, "*").concat(watchstr, "*") + "<b><<article|shortcut=a>></b>|<<editArticle|edit>>" + "}else{".concat(
      // not a talk page
      editOldidStr, "*").concat(historystr, "*").concat(watchstr, "*") + "<b><<talk|shortcut=t>></b>|<<editTalk|edit>>|<<newTalk|shortcut=+|new>>}";

      // misc links
      str += '<br><<whatLinksHere|shortcut=l>>*<<relatedChanges|shortcut=r>>*<<move|shortcut=m>>';

      // admin links
      str += 'if(admin){<br><<unprotect|unprotectShort>>|<<protect|shortcut=p>>|<<protectlog|log>>*' + '<<undelete|undeleteShort>>|<<delete|shortcut=d>>|<<deletelog|log>>}';
      return str;
    };
    var navLinksHTML = function navLinksHTML(article, _hint, params) {
      // oldid, rcid) {
      var str = "<span class=\"popupNavLinks\">".concat(defaultNavlinkSpec(), "</span>");
      // BAM
      return navlinkStringToHTML(str, article, params);
    };
    var expandConditionalNavlinkString = function expandConditionalNavlinkString(s, article, z, recursionCount) {
      var oldid = z.oldid;
      var rcid = z.rcid;
      var diff = z.diff;
      // nested conditionals (up to 10 deep) are ok, hopefully! (work from the inside out)
      if (_typeof(recursionCount) !== _typeof(0)) {
        recursionCount = 0;
      }
      var conditionalSplitRegex = new RegExp(
      //(1	 if	\\(	(2	2)	\\)	  {(3	3)}  (4   else	  {(5	 5)}  4)1)
      '(;?\\s*if\\s*\\(\\s*([\\w]*)\\s*\\)\\s*\\{([^{}]*)\\}(\\s*else\\s*\\{([^{}]*?)\\}|))', 'i');
      var splitted = s.parenSplit(conditionalSplitRegex);
      // $1: whole conditional
      // $2: test condition
      // $3: true expansion
      // $4: else clause (possibly empty)
      // $5: false expansion (possibly null)
      var numParens = 5;
      var ret = splitted[0];
      for (var i = 1; i < splitted.length; i = i + numParens + 1) {
        var testString = splitted[i + 2 - 1];
        var trueString = splitted[i + 3 - 1];
        var falseString = splitted[i + 5 - 1];
        if (typeof falseString === 'undefined' || !falseString) {
          falseString = '';
        }
        var testResult = null;
        switch (testString) {
          case 'user':
            {
              testResult = article.userName() ? true : false;
              break;
            }
          case 'talk':
            {
              testResult = article.talkPage() ? false : true; // talkPage converts _articles_ to talkPages
              break;
            }
          case 'admin':
            {
              testResult = getValueOf('popupAdminLinks') ? true : false;
              break;
            }
          case 'oldid':
            {
              testResult = typeof oldid !== 'undefined' && oldid ? true : false;
              break;
            }
          case 'rcid':
            {
              testResult = typeof rcid !== 'undefined' && rcid ? true : false;
              break;
            }
          case 'ipuser':
            {
              testResult = article.isIpUser() ? true : false;
              break;
            }
          case 'mainspace_en':
            {
              testResult = isInMainNamespace(article) && pg.wiki.hostname === 'en.wikipedia.org';
              break;
            }
          case 'wikimedia':
            {
              testResult = pg.wiki.wikimedia ? true : false;
              break;
            }
          case 'diff':
            {
              testResult = typeof diff !== 'undefined' && diff ? true : false;
              break;
            }
        }
        switch (testResult) {
          case null:
            {
              ret += splitted[i];
              break;
            }
          case true:
            {
              ret += trueString;
              break;
            }
          case false:
            {
              ret += falseString;
              break;
            }
        }

        // append non-conditional string
        ret += splitted[i + numParens];
      }
      if (conditionalSplitRegex.test(ret) && recursionCount < 10) {
        return expandConditionalNavlinkString(ret, article, z, recursionCount + 1);
      }
      return ret;
    };
    var navlinkStringToArray = function navlinkStringToArray(s, article, params) {
      s = expandConditionalNavlinkString(s, article, params);
      var splitted = s.parenSplit(new RegExp('<<(.*?)>>'));
      var ret = [];
      var _iterator18 = _createForOfIteratorHelper(splitted.entries()),
        _step18;
      try {
        for (_iterator18.s(); !(_step18 = _iterator18.n()).done;) {
          var _step18$value2 = _slicedToArray(_step18.value, 2),
            i = _step18$value2[0],
            element = _step18$value2[1];
          if (i % 2) {
            // i odd, so s is a tag
            var t = new navlinkTag();
            var ss = element.split('|');
            t.id = ss[0];
            for (var j = 1; j < ss.length; ++j) {
              var sss = ss[j].split('=');
              if (sss.length > 1) {
                t[sss[0]] = sss[1];
              } else {
                // no assignment (no "="), so treat this as a title (overwriting the last one)
                t.text = popupString(sss[0]);
              }
            }
            t.article = article;
            var oldid = params.oldid;
            var rcid = params.rcid;
            var _diff = params.diff;
            if (typeof oldid !== 'undefined' && oldid !== null) {
              t.oldid = oldid;
            }
            if (typeof rcid !== 'undefined' && rcid !== null) {
              t.rcid = rcid;
            }
            if (typeof _diff !== 'undefined' && _diff !== null) {
              t.diff = _diff;
            }
            if (!t.text && t.id !== 'mainlink') {
              t.text = popupString(t.id);
            }
            ret.push(t);
          } else {
            // plain HTML
            ret.push(element);
          }
        }
      } catch (err) {
        _iterator18.e(err);
      } finally {
        _iterator18.f();
      }
      return ret;
    };
    var navlinkSubstituteHTML = function navlinkSubstituteHTML(s) {
      return s.split('*').join(getValueOf('popupNavLinkSeparator')).split('<menurow>').join('<li class="popup_menu_row">').split('</menurow>').join('</li>').split('<menu>').join('<ul class="popup_menu">').split('</menu>').join('</ul>');
    };
    var navlinkDepth = function navlinkDepth(magic, s) {
      return s.split("<".concat(magic, ">")).length - s.split("</".concat(magic, ">")).length;
    };

    // navlinkString: * becomes the separator
    //				<<foo|bar=baz|fubar>> becomes a foo-link with attribute bar='baz'
    //									  and visible text 'fubar'
    //				if(test){...} and if(test){...}else{...} work too (nested ok)

    var navlinkStringToHTML = function navlinkStringToHTML(s, article, params) {
      // limitAlert(navlinkStringToHTML, 5, 'navlinkStringToHTML\n' + article + '\n' + (typeof article));
      var p = navlinkStringToArray(s, article, params);
      var html = '';
      var menudepth = 0; // nested menus not currently allowed, but doesn't do any harm to code for it
      var menurowdepth = 0;
      var _iterator19 = _createForOfIteratorHelper(p),
        _step19;
      try {
        for (_iterator19.s(); !(_step19 = _iterator19.n()).done;) {
          var element = _step19.value;
          if (_typeof(element) === _typeof('')) {
            html += navlinkSubstituteHTML(element);
            menudepth += navlinkDepth('menu', element);
            menurowdepth += navlinkDepth('menurow', element);
            //			if (menudepth === 0) {
            //				tagType='span';
            //			} else if (menurowdepth === 0) {
            //				tagType='li';
            //			} else {
            //				tagType = null;
            //			}
          } else if (typeof element.type !== 'undefined' && element.type === 'navlinkTag') {
            if (menudepth > 0 && menurowdepth === 0) {
              html += "<li class=\"popup_menu_item\">".concat(element.html(), "</li>");
            } else {
              html += element.html();
            }
          }
        }
      } catch (err) {
        _iterator19.e(err);
      } finally {
        _iterator19.f();
      }
      return html;
    };
    var navlinkTag = /*#__PURE__*/function () {
      function navlinkTag() {
        _classCallCheck(this, navlinkTag);
        this.type = 'navlinkTag';
      }
      _createClass(navlinkTag, [{
        key: "html",
        value: function html() {
          this.getNewWin();
          this.getPrintFunction();
          var html = '';
          var opening;
          var closing;
          var tagType = 'span';
          if (!tagType) {
            opening = '';
            closing = '';
          } else {
            opening = "<".concat(tagType, " class=\"popup_").concat(this.id, "\">");
            closing = "</".concat(tagType, ">");
          }
          if (typeof this.print !== 'function') {
            errlog("Oh dear - invalid print function for a navlinkTag, id=".concat(this.id));
          } else {
            html = this.print(this);
            if (_typeof(html) !== _typeof('')) {
              html = '';
            } else if (typeof this.shortcut !== 'undefined') {
              html = addPopupShortcut(html, this.shortcut);
            }
          }
          return opening + html + closing;
        }
      }, {
        key: "getNewWin",
        value: function getNewWin() {
          getValueOf('popupLinksNewWindow');
          if (typeof pg.option.popupLinksNewWindow[this.id] === 'undefined') {
            this.newWin = null;
          }
          this.newWin = pg.option.popupLinksNewWindow[this.id];
        }
      }, {
        key: "getPrintFunction",
        value: function getPrintFunction() {
          //think about this some more
          // this.id and this.article should already be defined
          if (_typeof(this.id) !== _typeof('') || _typeof(this.article) !== _typeof({})) {
            return;
          }
          this.noPopup = 1;
          switch (this.id) {
            case 'contribs':
            case 'history':
            case 'whatLinksHere':
            case 'userPage':
            case 'userTalk':
            case 'talk':
            case 'article':
            case 'lastEdit':
              {
                this.noPopup = null;
              }
          }
          switch (this.id) {
            case 'email':
            case 'contribs':
            case 'block':
            case 'unblock':
            case 'userlog':
            case 'userSpace':
            case 'deletedContribs':
              {
                this.article = this.article.userName();
              }
          }
          switch (this.id) {
            case 'userTalk':
            case 'newUserTalk':
            case 'editUserTalk':
            case 'userPage':
            case 'blocklog':
              {
                this.article = this.article.userName(true);
              }
            /* fall through */
            case 'pagelog':
            case 'deletelog':
            case 'protectlog':
              {
                delete this.oldid;
              }
          }
          if (this.id !== 'mainlink') {
            // FIXME anchor handling should be done differently with Title object
            this.article = this.article.removeAnchor();
            // if (typeof this.text=='undefined') this.text=popupString(this.id);
          }

          switch (this.id) {
            case 'undelete':
              {
                this.print = specialLink;
                this.specialpage = 'Undelete';
                this.sep = '/';
                break;
              }
            case 'whatLinksHere':
              {
                this.print = specialLink;
                this.specialpage = 'Whatlinkshere';
                break;
              }
            case 'relatedChanges':
              {
                this.print = specialLink;
                this.specialpage = 'Recentchangeslinked';
                break;
              }
            case 'move':
              {
                this.print = specialLink;
                this.specialpage = 'Movepage';
                break;
              }
            case 'contribs':
              {
                this.print = specialLink;
                this.specialpage = 'Contributions';
                break;
              }
            case 'deletedContribs':
              {
                this.print = specialLink;
                this.specialpage = 'Deletedcontributions';
                break;
              }
            case 'email':
              {
                this.print = specialLink;
                this.specialpage = 'EmailUser';
                this.sep = '/';
                break;
              }
            case 'block':
              {
                this.print = specialLink;
                this.specialpage = 'Blockip';
                this.sep = '&ip=';
                break;
              }
            case 'unblock':
              {
                this.print = specialLink;
                this.specialpage = 'Ipblocklist';
                this.sep = '&action=unblock&ip=';
                break;
              }
            case 'userlog':
              {
                this.print = specialLink;
                this.specialpage = 'Log';
                this.sep = '&user=';
                break;
              }
            case 'blocklog':
              {
                this.print = specialLink;
                this.specialpage = 'Log';
                this.sep = '&type=block&page=';
                break;
              }
            case 'pagelog':
              {
                this.print = specialLink;
                this.specialpage = 'Log';
                this.sep = '&page=';
                break;
              }
            case 'protectlog':
              {
                this.print = specialLink;
                this.specialpage = 'Log';
                this.sep = '&type=protect&page=';
                break;
              }
            case 'deletelog':
              {
                this.print = specialLink;
                this.specialpage = 'Log';
                this.sep = '&type=delete&page=';
                break;
              }
            case 'userSpace':
              {
                this.print = specialLink;
                this.specialpage = 'PrefixIndex';
                this.sep = '&namespace=2&prefix=';
                break;
              }
            case 'search':
              {
                this.print = specialLink;
                this.specialpage = 'Search';
                this.sep = '&fulltext=Search&search=';
                break;
              }
            case 'thank':
              {
                this.print = specialLink;
                this.specialpage = 'Thanks';
                this.sep = '/';
                this.article.value = this.diff !== 'prev' ? this.diff : this.oldid;
                break;
              }
            case 'unwatch':
            case 'watch':
              {
                this.print = magicWatchLink;
                this.action = "".concat(this.id, "&autowatchlist=1&autoimpl=").concat(popupString('autoedit_version'), "&actoken=").concat(autoClickToken());
                break;
              }
            case 'history':
            case 'historyfeed':
            case 'unprotect':
            case 'protect':
              {
                this.print = wikiLink;
                this.action = this.id;
                break;
              }
            case 'delete':
              {
                this.print = wikiLink;
                this.action = 'delete';
                if (this.article.namespaceId() === pg.nsImageId) {
                  var img = this.article.stripNamespace();
                  this.action += "&image=".concat(img);
                }
                break;
              }
            case 'markpatrolled':
            case 'edit':
              {
                // editOld should keep the oldid, but edit should not.
                delete this.oldid;
              }
            /* fall through */
            case 'view':
            case 'purge':
            case 'render':
              {
                this.print = wikiLink;
                this.action = this.id;
                break;
              }
            case 'raw':
              {
                this.print = wikiLink;
                this.action = 'raw';
                break;
              }
            case 'new':
              {
                this.print = wikiLink;
                this.action = 'edit&section=new';
                break;
              }
            case 'mainlink':
              {
                if (typeof this.text === 'undefined') {
                  this.text = this.article.toString().entify();
                }
                if (getValueOf('popupSimplifyMainLink') && isInStrippableNamespace(this.article)) {
                  // only show the /subpage part of the title text
                  var s = this.text.split('/');
                  this.text = s[s.length - 1];
                  if (this.text === '' && s.length > 1) {
                    this.text = s[s.length - 2];
                  }
                }
                this.print = titledWikiLink;
                if (typeof this.title === 'undefined' && pg.current.link && typeof pg.current.link.href !== 'undefined') {
                  this.title = safeDecodeURI(pg.current.link.originalTitle ? pg.current.link.originalTitle : this.article);
                  if (typeof this.oldid !== 'undefined' && this.oldid) {
                    this.title = tprintf('Revision %s of %s', [this.oldid, this.title]);
                  }
                }
                this.action = 'view';
                break;
              }
            case 'userPage':
            case 'article':
            case 'editArticle':
              {
                delete this.oldid;
                // alert(this.id+'\n'+this.article + '\n'+ typeof this.article);
                this.article = this.article.articleFromTalkOrArticle();
                // alert(this.id+'\n'+this.article + '\n'+ typeof this.article);
                this.print = wikiLink;
                if (this.id.indexOf('edit') === 0) {
                  this.action = 'edit';
                } else {
                  this.action = 'view';
                }
                break;
              }
            case 'userTalk':
            case 'talk':
              {
                this.article = this.article.talkPage();
                delete this.oldid;
                this.print = wikiLink;
                this.action = 'view';
                break;
              }
            case 'arin':
              {
                this.print = arinLink;
                break;
              }
            case 'count':
              {
                this.print = editCounterLink;
                break;
              }
            case 'google':
              {
                this.print = googleLink;
                break;
              }
            case 'editors':
              {
                this.print = editorListLink;
                break;
              }
            case 'globalsearch':
              {
                this.print = globalSearchLink;
                break;
              }
            case 'lastEdit':
              {
                this.print = titledDiffLink;
                this.title = popupString('Show the last edit');
                this.from = 'prev';
                this.to = 'cur';
                break;
              }
            case 'oldEdit':
              {
                this.print = titledDiffLink;
                this.title = "".concat(popupString('Show the edit made to get revision'), " ").concat(this.oldid);
                this.from = 'prev';
                this.to = this.oldid;
                break;
              }
            case 'editOld':
              {
                this.print = wikiLink;
                this.action = 'edit';
                break;
              }
            case 'undo':
              {
                this.print = wikiLink;
                this.action = 'edit&undo=';
                break;
              }
            case 'revert':
              {
                this.print = wikiLink;
                this.action = 'revert';
                break;
              }
            case 'nullEdit':
              {
                this.print = wikiLink;
                this.action = 'nullEdit';
                break;
              }
            case 'diffCur':
              {
                this.print = titledDiffLink;
                this.title = tprintf('Show changes since revision %s', [this.oldid]);
                this.from = this.oldid;
                this.to = 'cur';
                break;
              }
            case 'editUserTalk':
            case 'editTalk':
              {
                delete this.oldid;
                this.article = this.article.talkPage();
                this.action = 'edit';
                this.print = wikiLink;
                break;
              }
            case 'newUserTalk':
            case 'newTalk':
              {
                this.article = this.article.talkPage();
                this.action = 'edit&section=new';
                this.print = wikiLink;
                break;
              }
            case 'lastContrib':
            case 'sinceMe':
              {
                this.print = magicHistoryLink;
                break;
              }
            case 'togglePreviews':
              {
                this.text = popupString(pg.option.simplePopups ? 'enable previews' : 'disable previews');
              }
            /* fall through */
            case 'disablePopups':
            case 'purgePopups':
              {
                this.print = popupMenuLink;
                break;
              }
            default:
              {
                this.print = function () {
                  return "Unknown navlink type: ".concat(this.id);
                };
              }
          }
        }
      }]);
      return navlinkTag;
    }(); //
    //  end navlinks
    // ENDFILE: navlinks.js
    // STARTFILE: shortcutkeys.js
    var popupHandleKeypress = function popupHandleKeypress(evt) {
      var keyCode = window.event ? window.event.keyCode : evt.keyCode ? evt.keyCode : evt.which;
      if (!keyCode || !pg.current.link || !pg.current.link.navpopup) {
        return;
      }
      if (keyCode === 27) {
        // escape
        killPopup();
        return false; // swallow keypress
      }

      var letter = String.fromCharCode(keyCode);
      var links = pg.current.link.navpopup.mainDiv.querySelectorAll('A');
      var startLink = 0;
      var i;
      var j;
      if (popupHandleKeypress.lastPopupLinkSelected) {
        for (i = 0; i < links.length; ++i) {
          if (links[i] === popupHandleKeypress.lastPopupLinkSelected) {
            startLink = i;
          }
        }
      }
      for (j = 0; j < links.length; ++j) {
        i = (startLink + j + 1) % links.length;
        if (links[i].getAttribute('popupkey') === letter) {
          if (evt && evt.preventDefault) {
            evt.preventDefault();
          }
          links[i].focus();
          popupHandleKeypress.lastPopupLinkSelected = links[i];
          return false; // swallow keypress
        }
      }

      // pass keypress on
      if (document.oldPopupOnkeypress) {
        return document.oldPopupOnkeypress(evt);
      }
      return true;
    };
    var addPopupShortcuts = function addPopupShortcuts() {
      if (document.onkeypress !== popupHandleKeypress) {
        document.oldPopupOnkeypress = document.onkeypress;
      }
      document.onkeypress = popupHandleKeypress;
    };
    var rmPopupShortcuts = function rmPopupShortcuts() {
      popupHandleKeypress.lastPopupLinkSelected = null;
      try {
        if (document.oldPopupOnkeypress && document.oldPopupOnkeypress === popupHandleKeypress) {
          // panic
          document.onkeypress = null; // function () {};
          return;
        }
        document.onkeypress = document.oldPopupOnkeypress;
      } catch (_unused23) {
        /* IE goes here */
      }
    };
    var addLinkProperty = function addLinkProperty(html, property) {
      // take "<a href=...>...</a> and add a property
      // not sophisticated at all, easily broken
      var i = html.indexOf('>');
      if (i < 0) {
        return html;
      }
      return "".concat(html.slice(0, Math.max(0, i)), " ").concat(property).concat(html.slice(Math.max(0, i)));
    };
    var addPopupShortcut = function addPopupShortcut(html, key) {
      if (!getValueOf('popupShortcutKeys')) {
        return html;
      }
      var ret = addLinkProperty(html, "popupkey=\"".concat(key, "\""));
      if (key === ' ') {
        key = popupString('spacebar');
      }
      return ret.replace(new RegExp('^(.*?)(title=")(.*?)(".*)$', 'i'), "$1$2$3 [".concat(key, "]$4"));
    };

    // ENDFILE: shortcutkeys.js

    // STARTFILE: diffpreview.js

    //lets jump through hoops to find the rev ids we need to retrieve
    var loadDiff = function loadDiff(article, oldid, diff, navpop) {
      navpop.diffData = {
        oldRev: {},
        newRev: {}
      };
      mw.loader.using(['mediawiki.api']).then(function () {
        var api = getMwApi();
        var params = {
          action: 'compare',
          prop: 'ids|title'
        };
        if (article.title) {
          params.fromtitle = article.title;
        }
        switch (diff) {
          case 'cur':
            {
              switch (oldid) {
                case null:
                case '':
                case 'prev':
                  {
                    // this can only work if we have the title
                    // cur -> prev
                    params.torelative = 'prev';
                    break;
                  }
                default:
                  {
                    params.fromrev = oldid;
                    params.torelative = 'cur';
                    break;
                  }
              }
              break;
            }
          case 'prev':
            {
              if (oldid) {
                params.fromrev = oldid;
              } else {
                params.fromtitle;
              }
              params.torelative = 'prev';
              break;
            }
          case 'next':
            {
              params.fromrev = oldid || 0;
              params.torelative = 'next';
              break;
            }
          default:
            {
              params.fromrev = oldid || 0;
              params.torev = diff || 0;
              break;
            }
        }
        api.get(params).then(function (data) {
          navpop.diffData.oldRev.revid = data.compare.fromrevid;
          navpop.diffData.newRev.revid = data.compare.torevid;
          addReviewLink(navpop, 'popupMiscTools');
          var go = function go() {
            pendingNavpopTask(navpop);
            var url = "".concat(pg.wiki.apiwikibase, "?format=json&formatversion=2&action=query&");
            url += "revids=".concat(navpop.diffData.oldRev.revid, "|").concat(navpop.diffData.newRev.revid);
            url += '&prop=revisions&rvprop=ids|timestamp|content';
            getPageWithCaching(url, doneDiff, navpop);
            return true; // remove hook once run
          };

          if (navpop.visible || !getValueOf('popupLazyDownloads')) {
            go();
          } else {
            navpop.addHook(go, 'unhide', 'before', 'DOWNLOAD_DIFFS');
          }
        });
      });
    };

    // Put a "mark patrolled" link to an element target
    // TODO: Allow patrol a revision, as well as a diff
    var addReviewLink = function addReviewLink(navpop, target) {
      if (!pg.user.canReview) {
        return;
      }
      // If 'newRev' is older than 'oldRev' than it could be confusing, so we do not show the review link.
      if (navpop.diffData.newRev.revid <= navpop.diffData.oldRev.revid) {
        return;
      }
      var params = {
        action: 'query',
        prop: 'info|flagged',
        revids: navpop.diffData.oldRev.revid,
        formatversion: 2
      };
      getMwApi().get(params).then(function (data) {
        var stable_revid = data.query.pages[0].flagged && data.query.pages[0].flagged.stable_revid || 0;
        // The diff can be reviewed if the old version is the last reviewed version
        // TODO: Other possible conditions that we may want to implement instead of this one:
        // - old version is patrolled and the new version is not patrolled
        // - old version is patrolled and the new version is more recent than the last reviewed version
        if (stable_revid === navpop.diffData.oldRev.revid) {
          var a = document.createElement('a');
          a.innerHTML = popupString('mark patrolled');
          a.title = popupString('markpatrolledHint');
          a.onclick = function () {
            var params = {
              action: 'review',
              revid: navpop.diffData.newRev.revid,
              comment: tprintf('defaultpopupReviewedSummary', [navpop.diffData.oldRev.revid, navpop.diffData.newRev.revid])
            };
            getMwApi().postWithToken('csrf', params).done(function () {
              a.style.display = 'none';
              // TODO: Update current page and other already constructed popups
            }).fail(function () {
              alert(popupString('Could not marked this edit as patrolled'));
            });
          };
          setPopupHTML(a, target, navpop.idNumber, null, true);
        }
      });
    };
    var doneDiff = function doneDiff(download) {
      if (!download.owner || !download.owner.diffData) {
        return;
      }
      var navpop = download.owner;
      completedNavpopTask(navpop);
      var pages;
      var revisions = [];
      try {
        // Process the downloads
        pages = getJsObj(download.data).query.pages;
        var _iterator20 = _createForOfIteratorHelper(pages),
          _step20;
        try {
          for (_iterator20.s(); !(_step20 = _iterator20.n()).done;) {
            var page = _step20.value;
            revisions = revisions.concat(page.revisions);
          }
        } catch (err) {
          _iterator20.e(err);
        } finally {
          _iterator20.f();
        }
        var _iterator21 = _createForOfIteratorHelper(revisions),
          _step21;
        try {
          for (_iterator21.s(); !(_step21 = _iterator21.n()).done;) {
            var revision = _step21.value;
            if (revision.revid === navpop.diffData.oldRev.revid) {
              navpop.diffData.oldRev.revision = revision;
            } else if (revision.revid === navpop.diffData.newRev.revid) {
              navpop.diffData.newRev.revision = revision;
            }
          }
        } catch (err) {
          _iterator21.e(err);
        } finally {
          _iterator21.f();
        }
      } catch (_unused24) {
        errlog('Could not get diff');
      }
      insertDiff(navpop);
    };
    var rmBoringLines = function rmBoringLines(a, b, context) {
      if (typeof context === 'undefined') {
        context = 2;
      }

      // this is fairly slow... i think it's quicker than doing a word-based diff from the off, though
      var aa = [];
      var aaa = [];
      var bb = [];
      var bbb = [];
      var i;
      var j;

      // first, gather all disconnected nodes in a and all crossing nodes in a and b
      for (i = 0; i < a.length; ++i) {
        if (!a[i].paired) {
          aa[i] = 1;
        } else if (countCrossings(b, a, i, true)) {
          aa[i] = 1;
          bb[a[i].row] = 1;
        }
      }

      // pick up remaining disconnected nodes in b
      for (i = 0; i < b.length; ++i) {
        if (bb[i] === 1) {
          continue;
        }
        if (!b[i].paired) {
          bb[i] = 1;
        }
      }

      // another pass to gather context: we want the neighbours of included nodes which are not
      // yet included we have to add in partners of these nodes, but we don't want to add context
      // for *those* nodes in the next pass
      for (i = 0; i < b.length; ++i) {
        if (bb[i] === 1) {
          for (j = Math.max(0, i - context); j < Math.min(b.length, i + context); ++j) {
            if (!bb[j]) {
              bb[j] = 1;
              aa[b[j].row] = 0.5;
            }
          }
        }
      }
      for (i = 0; i < a.length; ++i) {
        if (aa[i] === 1) {
          for (j = Math.max(0, i - context); j < Math.min(a.length, i + context); ++j) {
            if (!aa[j]) {
              aa[j] = 1;
              bb[a[j].row] = 0.5;
            }
          }
        }
      }
      for (i = 0; i < bb.length; ++i) {
        if (bb[i] > 0) {
          // it's a row we need
          if (b[i].paired) {
            bbb.push(b[i].text); // joined; partner should be in aa
          } else {
            bbb.push(b[i]);
          }
        }
      }
      for (i = 0; i < aa.length; ++i) {
        if (aa[i] > 0) {
          // it's a row we need
          if (a[i].paired) {
            aaa.push(a[i].text);
          } else {
            // joined; partner should be in aa
            aaa.push(a[i]);
          }
        }
      }
      return {
        a: aaa,
        b: bbb
      };
    };
    var stripOuterCommonLines = function stripOuterCommonLines(a, b, context) {
      var i = 0;
      while (i < a.length && i < b.length && a[i] === b[i]) {
        ++i;
      }
      var j = a.length - 1;
      var k = b.length - 1;
      while (j >= 0 && k >= 0 && a[j] === b[k]) {
        --j;
        --k;
      }
      return {
        a: a.slice(Math.max(0, i - 1 - context), Math.min(a.length + 1, j + context + 1)),
        b: b.slice(Math.max(0, i - 1 - context), Math.min(b.length + 1, k + context + 1))
      };
    };
    var insertDiff = function insertDiff(navpop) {
      // for speed reasons, we first do a line-based diff, discard stuff that seems boring, then
      // do a word-based diff
      // FIXME: sometimes this gives misleading diffs as distant chunks are squashed together
      var oldlines = navpop.diffData.oldRev.revision.content.split('\n');
      var newlines = navpop.diffData.newRev.revision.content.split('\n');
      var inner = stripOuterCommonLines(oldlines, newlines, getValueOf('popupDiffContextLines'));
      oldlines = inner.a;
      newlines = inner.b;
      var truncated = false;
      getValueOf('popupDiffMaxLines');
      if (oldlines.length > pg.option.popupDiffMaxLines || newlines.length > pg.option.popupDiffMaxLines) {
        // truncate
        truncated = true;
        inner = stripOuterCommonLines(oldlines.slice(0, pg.option.popupDiffMaxLines), newlines.slice(0, pg.option.popupDiffMaxLines), pg.option.popupDiffContextLines);
        oldlines = inner.a;
        newlines = inner.b;
      }
      var lineDiff = diff(oldlines, newlines);
      var lines2 = rmBoringLines(lineDiff.o, lineDiff.n);
      var oldlines2 = lines2.a;
      var newlines2 = lines2.b;
      var simpleSplit = !String.prototype.parenSplit.isNative;
      var html = '<hr />';
      if (getValueOf('popupDiffDates')) {
        html += diffDatesTable(navpop);
        html += '<hr />';
      }
      html += shortenDiffString(diffString(oldlines2.join('\n'), newlines2.join('\n'), simpleSplit), getValueOf('popupDiffContextCharacters')).join('<hr />');
      setPopupTipsAndHTML(html.split('\n').join('<br>') + (truncated ? "<hr /><b>".concat(popupString('Diff truncated for performance reasons'), "</b>") : ''), 'popupPreview', navpop.idNumber);
    };
    var diffDatesTable = function diffDatesTable(navpop) {
      var html = '<table class="popup_diff_dates">';
      html += diffDatesTableRow(navpop.diffData.newRev.revision, tprintf('New revision'));
      html += diffDatesTableRow(navpop.diffData.oldRev.revision, tprintf('Old revision'));
      html += '</table>';
      return html;
    };
    var diffDatesTableRow = function diffDatesTableRow(revision, label) {
      var txt = '';
      var lastModifiedDate = new Date(revision.timestamp);
      txt = formattedDateTime(lastModifiedDate);
      var revlink = generalLink({
        url: "".concat(mw.config.get('wgScript'), "?oldid=").concat(revision.revid),
        text: label,
        title: label
      });
      return simplePrintf('<tr><td>%s</td><td>%s</td></tr>', [revlink, txt]);
    };

    // ENDFILE: diffpreview.js

    // STARTFILE: links.js

    // LINK GENERATION //

    // titledDiffLink --> titledWikiLink --> generalLink
    // wikiLink	   --> titledWikiLink --> generalLink
    // editCounterLink --> generalLink

    // TODO Make these functions return Element objects, not just raw HTML strings.

    var titledDiffLink = function titledDiffLink(l) {
      return (
        // article, text, title, from, to) {
        titledWikiLink({
          article: l.article,
          action: "".concat(l.to, "&oldid=").concat(l.from),
          newWin: l.newWin,
          noPopup: l.noPopup,
          text: l.text,
          title: l.title,
          /* hack: no oldid here */
          actionName: 'diff'
        })
      );
    };
    var wikiLink = function wikiLink(l) {
      //{article:article, action:action, text:text, oldid, newid}) {
      if (!(_typeof(l.article) === _typeof({}) && _typeof(l.action) === _typeof('') && _typeof(l.text) === _typeof(''))) {
        return null;
      }
      if (typeof l.oldid === 'undefined') {
        l.oldid = null;
      }
      var savedOldid = l.oldid;
      if (!/^(edit|view|revert|render)$|^raw/.test(l.action)) {
        l.oldid = null;
      }
      var hint = popupString("".concat(l.action, "Hint")); // revertHint etc etc etc
      var oldidData = [l.oldid, safeDecodeURI(l.article)];
      var revisionString = tprintf('revision %s of %s', oldidData);
      log("revisionString=".concat(revisionString));
      switch (l.action) {
        case 'edit&section=new':
          {
            hint = popupString('newSectionHint');
            break;
          }
        case 'edit&undo=':
          {
            if (l.diff && l.diff !== 'prev' && savedOldid) {
              l.action += "".concat(l.diff, "&undoafter=").concat(savedOldid);
            } else if (savedOldid) {
              l.action += savedOldid;
            }
            hint = popupString('undoHint');
            break;
          }
        case 'raw&ctype=text/css':
          {
            hint = popupString('rawHint');
            break;
          }
        case 'revert':
          {
            var p = parseParams(pg.current.link.href);
            l.action = "edit&autoclick=wpSave&actoken=".concat(autoClickToken(), "&autoimpl=").concat(popupString('autoedit_version'), "&autosummary=").concat(revertSummary(l.oldid, p.diff));
            if (p.diff === 'prev') {
              l.action += '&direction=prev';
              revisionString = tprintf('the revision prior to revision %s of %s', oldidData);
            }
            if (getValueOf('popupRevertSummaryPrompt')) {
              l.action += '&autosummaryprompt=true';
            }
            if (getValueOf('popupMinorReverts')) {
              l.action += '&autominor=true';
            }
            log("revisionString is now ".concat(revisionString));
            break;
          }
        case 'nullEdit':
          {
            l.action = "edit&autoclick=wpSave&actoken=".concat(autoClickToken(), "&autoimpl=").concat(popupString('autoedit_version'), "&autosummary=null");
            break;
          }
        case 'historyfeed':
          {
            l.action = 'history&feed=rss';
            break;
          }
        case 'markpatrolled':
          {
            l.action = "markpatrolled&rcid=".concat(l.rcid);
          }
      }
      if (hint) {
        if (l.oldid) {
          hint = simplePrintf(hint, [revisionString]);
        } else {
          hint = simplePrintf(hint, [safeDecodeURI(l.article)]);
        }
      } else {
        hint = safeDecodeURI("".concat(l.article, "&action=").concat(l.action)) + l.oldid ? "&oldid=".concat(l.oldid) : '';
      }
      return titledWikiLink({
        article: l.article,
        action: l.action,
        text: l.text,
        newWin: l.newWin,
        title: hint,
        oldid: l.oldid,
        noPopup: l.noPopup,
        onclick: l.onclick
      });
    };
    var revertSummary = function revertSummary(oldid, diff) {
      var ret = '';
      if (diff === 'prev') {
        ret = getValueOf('popupQueriedRevertToPreviousSummary');
      } else {
        ret = getValueOf('popupQueriedRevertSummary');
      }
      return "".concat(ret, "&autorv=").concat(oldid);
    };
    var titledWikiLink = function titledWikiLink(l) {
      // possible properties of argument:
      // article, action, text, title, oldid, actionName, className, noPopup
      // oldid = null is fine here
      // article and action are mandatory args
      if (typeof l.article === 'undefined' || typeof l.action === 'undefined') {
        errlog('got undefined article or action in titledWikiLink');
        return null;
      }
      var base = pg.wiki.titlebase + l.article.urlString();
      var url = base;
      if (typeof l.actionName === 'undefined' || !l.actionName) {
        l.actionName = 'action';
      }

      // no need to add &action=view, and this confuses anchors
      if (l.action !== 'view') {
        url = "".concat(base, "&").concat(l.actionName, "=").concat(l.action);
      }
      if (typeof l.oldid !== 'undefined' && l.oldid) {
        url += "&oldid=".concat(l.oldid);
      }
      var cssClass = pg.misc.defaultNavlinkClassname;
      if (typeof l.className !== 'undefined' && l.className) {
        cssClass = l.className;
      }
      return generalNavLink({
        url: url,
        newWin: l.newWin,
        title: typeof l.title !== 'undefined' ? l.title : null,
        text: typeof l.text !== 'undefined' ? l.text : null,
        className: cssClass,
        noPopup: l.noPopup,
        onclick: l.onclick
      });
    };
    pg.fn.getLastContrib = function (wikipage, newWin) {
      getHistoryInfo(wikipage, function (x) {
        processLastContribInfo(x, {
          page: wikipage,
          newWin: newWin
        });
      });
    };
    var processLastContribInfo = function processLastContribInfo(info, stuff) {
      if (!info.edits || info.edits.length === 0) {
        alert('Popups: an odd thing happened. Please retry.');
        return;
      }
      if (!info.firstNewEditor) {
        alert(tprintf('Only found one editor: %s made %s edits', [info.edits[0].editor, info.edits.length]));
        return;
      }
      var newUrl = "".concat(pg.wiki.titlebase + new Title(stuff.page).urlString(), "&diff=cur&oldid=").concat(info.firstNewEditor.oldid);
      displayUrl(newUrl, stuff.newWin);
    };
    pg.fn.getDiffSinceMyEdit = function (wikipage, newWin) {
      getHistoryInfo(wikipage, function (x) {
        processDiffSinceMyEdit(x, {
          page: wikipage,
          newWin: newWin
        });
      });
    };
    var processDiffSinceMyEdit = function processDiffSinceMyEdit(info, stuff) {
      if (!info.edits || info.edits.length === 0) {
        alert('Popups: something fishy happened. Please try again.');
        return;
      }
      var friendlyName = stuff.page.split('_').join(' ');
      if (!info.myLastEdit) {
        alert(tprintf("Couldn't find an edit by %s\nin the last %s edits to\n%s", [info.userName, getValueOf('popupHistoryLimit'), friendlyName]));
        return;
      }
      if (info.myLastEdit.index === 0) {
        alert(tprintf('%s seems to be the last editor to the page %s', [info.userName, friendlyName]));
        return;
      }
      var newUrl = "".concat(pg.wiki.titlebase + new Title(stuff.page).urlString(), "&diff=cur&oldid=").concat(info.myLastEdit.oldid);
      displayUrl(newUrl, stuff.newWin);
    };
    var displayUrl = function displayUrl(url, newWin) {
      if (newWin) {
        window.open(url);
      } else {
        document.location = url;
      }
    };
    pg.fn.purgePopups = function () {
      processAllPopups(true);
      setupCache(); // deletes all cached items (not browser cached, though...)
      pg.option = {};
      abortAllDownloads();
    };
    var processAllPopups = function processAllPopups(nullify, banish) {
      for (var i = 0; pg.current.links && i < pg.current.links.length; ++i) {
        if (!pg.current.links[i].navpopup) {
          continue;
        }
        if (nullify || banish) {
          pg.current.links[i].navpopup.banish();
        }
        pg.current.links[i].simpleNoMore = false;
        if (nullify) {
          pg.current.links[i].navpopup = null;
        }
      }
    };
    pg.fn.disablePopups = function () {
      processAllPopups(false, true);
      setupTooltips(null, true);
    };
    pg.fn.togglePreviews = function () {
      processAllPopups(true, true);
      pg.option.simplePopups = !pg.option.simplePopups;
      abortAllDownloads();
    };
    var magicWatchLink = function magicWatchLink(l) {
      // Yuck!! Would require a thorough redesign to add this as a click event though ...
      l.onclick = simplePrintf("pg.fn.modifyWatchlist('%s','%s');return false;", [l.article.toString(true).split('\\').join('\\\\').split("'").join("\\'"), this.id]);
      return wikiLink(l);
    };
    pg.fn.modifyWatchlist = function (title, action) {
      var reqData = {
        action: 'watch',
        formatversion: 2,
        titles: title,
        uselang: mw.config.get('wgUserLanguage')
      };
      if (action === 'unwatch') {
        reqData.unwatch = true;
      }

      // Load the Addedwatchtext or Removedwatchtext message and show it
      var mwTitle = mw.Title.newFromText(title);
      var messageName;
      if (mwTitle && mwTitle.getNamespaceId() > 0 && mwTitle.getNamespaceId() % 2 === 1) {
        messageName = action === 'watch' ? 'addedwatchtext-talk' : 'removedwatchtext-talk';
      } else {
        messageName = action === 'watch' ? 'addedwatchtext' : 'removedwatchtext';
      }
      $.when(getMwApi().postWithToken('watch', reqData), getMwApi().loadMessagesIfMissing([messageName])).done(function () {
        mw.notify(mw.message(messageName, title).parseDom());
      });
    };
    var magicHistoryLink = function magicHistoryLink(l) {
      // FIXME use onclick change href trick to sort this out instead of window.open
      var jsUrl = '';
      var title = '';
      var onClick = '';
      switch (l.id) {
        case 'lastContrib':
          {
            onClick = simplePrintf("pg.fn.getLastContrib('%s',%s)", [l.article.toString(true).split('\\').join('\\\\').split("'").join("\\'"), l.newWin]);
            title = popupString('lastContribHint');
            break;
          }
        case 'sinceMe':
          {
            onClick = simplePrintf("pg.fn.getDiffSinceMyEdit('%s',%s)", [l.article.toString(true).split('\\').join('\\\\').split("'").join("\\'"), l.newWin]);
            title = popupString('sinceMeHint');
            break;
          }
      }
      jsUrl = "javascript:".concat(onClick); // jshint ignore:line
      onClick += ';return false;';
      return generalNavLink({
        url: jsUrl,
        newWin: false,
        title: title,
        text: l.text,
        noPopup: l.noPopup,
        onclick: onClick
      });
    };
    var popupMenuLink = function popupMenuLink(l) {
      var jsUrl = simplePrintf('javascript:pg.fn.%s()', [l.id]); // jshint ignore:line
      var title = popupString(simplePrintf('%sHint', [l.id]));
      var onClick = simplePrintf('pg.fn.%s();return false;', [l.id]);
      return generalNavLink({
        url: jsUrl,
        newWin: false,
        title: title,
        text: l.text,
        noPopup: l.noPopup,
        onclick: onClick
      });
    };
    var specialLink = function specialLink(l) {
      // properties: article, specialpage, text, sep
      if (typeof l.specialpage === 'undefined' || !l.specialpage) {
        return null;
      }
      var base = "".concat(pg.wiki.titlebase + mw.config.get('wgFormattedNamespaces')[pg.nsSpecialId], ":").concat(l.specialpage);
      if (typeof l.sep === 'undefined' || l.sep === null) {
        l.sep = '&target=';
      }
      var article = l.article.urlString({
        keepSpaces: l.specialpage === 'Search'
      });
      var hint = popupString("".concat(l.specialpage, "Hint"));
      switch (l.specialpage) {
        case 'Log':
          {
            switch (l.sep) {
              case '&user=':
                {
                  hint = popupString('userLogHint');
                  break;
                }
              case '&type=block&page=':
                {
                  hint = popupString('blockLogHint');
                  break;
                }
              case '&page=':
                {
                  hint = popupString('pageLogHint');
                  break;
                }
              case '&type=protect&page=':
                {
                  hint = popupString('protectLogHint');
                  break;
                }
              case '&type=delete&page=':
                {
                  hint = popupString('deleteLogHint');
                  break;
                }
              default:
                {
                  log("Unknown log type, sep=".concat(l.sep));
                  hint = 'Missing hint (FIXME)';
                }
            }
            break;
          }
        case 'PrefixIndex':
          {
            article += '/';
            break;
          }
      }
      if (hint) {
        hint = simplePrintf(hint, [safeDecodeURI(l.article)]);
      } else {
        hint = safeDecodeURI("".concat(l.specialpage, ":").concat(l.article));
      }
      var url = base + l.sep + article;
      return generalNavLink({
        url: url,
        title: hint,
        text: l.text,
        newWin: l.newWin,
        noPopup: l.noPopup
      });
    };
    var generalLink = function generalLink(l) {
      // l.url, l.text, l.title, l.newWin, l.className, l.noPopup, l.onclick
      if (typeof l.url === 'undefined') {
        return null;
      }

      // only quotation marks in the url can screw us up now... I think
      var url = l.url.split('"').join('%22');
      var ret = "<a href=\"".concat(url, "\"");
      if (typeof l.title !== 'undefined' && l.title) {
        ret += " title=\"".concat(pg.escapeQuotesHTML(l.title), "\"");
      }
      if (typeof l.onclick !== 'undefined' && l.onclick) {
        ret += " onclick=\"".concat(pg.escapeQuotesHTML(l.onclick), "\"");
      }
      if (l.noPopup) {
        ret += ' noPopup=1';
      }
      var newWin;
      if (typeof l.newWin === 'undefined' || l.newWin === null) {
        newWin = getValueOf('popupNewWindows');
      } else {
        newWin = l.newWin;
      }
      if (newWin) {
        ret += ' target="_blank"';
      }
      if (typeof l.className !== 'undefined' && l.className) {
        ret += " class=\"".concat(l.className, "\"");
      }
      ret += '>';
      if (_typeof(l.text) === _typeof('')) {
        // We need to HTML-escape this to avoid XSS, but we also want to
        // display any existing HTML entities correctly, so unescape it first.
        // For example, the display text of the user page menu item is defined
        // as "user&nbsp;page", so we need to unescape first to avoid it being
        // escaped to "user&amp;nbsp;page".
        ret += pg.escapeQuotesHTML(pg.unescapeQuotesHTML(l.text));
      }
      ret += '</a>';
      return ret;
    };
    var appendParamsToLink = function appendParamsToLink(linkstr, params) {
      var sp = linkstr.parenSplit(new RegExp('(href="[^"]+?)"', 'i'));
      if (sp.length < 2) {
        return null;
      }
      var ret = sp.shift() + sp.shift();
      ret += "&".concat(params, "\"");
      ret += sp.join('');
      return ret;
    };
    var changeLinkTargetLink = function changeLinkTargetLink(x) {
      // newTarget, text, hint, summary, clickButton, minor, title (optional), alsoChangeLabel {
      if (x.newTarget) {
        log("changeLinkTargetLink: newTarget=".concat(x.newTarget));
      }
      if (x.oldTarget !== decodeURIComponent(x.oldTarget)) {
        log("This might be an input problem: ".concat(x.oldTarget));
      }

      // FIXME: first character of page title as well as namespace should be case insensitive
      // eg [[:category:X1]] and [[:Category:X1]] are equivalent
      // this'll break if charAt(0) is nasty
      var cA = mw.util.escapeRegExp(x.oldTarget);
      var chs = cA.charAt(0).toUpperCase();
      chs = "[".concat(chs).concat(chs.toLowerCase(), "]");
      var currentArticleRegexBit = chs + cA.slice(1);
      currentArticleRegexBit = currentArticleRegexBit.split(new RegExp('(?:[_ ]+|%20)', 'g')).join('(?:[_ ]+|%20)').split('\\(').join('(?:%28|\\()').split('\\)').join('(?:%29|\\))'); // why does this need to match encoded strings ? links in the document ?

      // leading and trailing space should be ignored, and anchor bits optional:
      currentArticleRegexBit = "\\s*(".concat(currentArticleRegexBit, "(?:#[^\\[\\|]*)?)\\s*");
      // e.g. Computer (archaic) -> \s*([Cc]omputer[_ ](?:%2528|\()archaic(?:%2528|\)))\s*
      // autoedit=s~\[\[([Cc]ad)\]\]~[[Computer-aided%20design|$1]]~g;s~\[\[([Cc]AD)[|]~[[Computer-aided%20design|~g
      var title = x.title || mw.config.get('wgPageName').split('_').join(' ');
      var lk = titledWikiLink({
        article: new Title(title),
        newWin: x.newWin,
        action: 'edit',
        text: x.text,
        title: x.hint,
        className: 'popup_change_title_link'
      });
      var cmd = '';
      if (x.newTarget) {
        // escape '&' and other nasties
        var t = x.newTarget;
        var s = mw.util.escapeRegExp(x.newTarget);
        if (x.alsoChangeLabel) {
          cmd += "s~\\[\\[".concat(currentArticleRegexBit, "\\]\\]~[[").concat(t, "]]~g;");
          cmd += "s~\\[\\[".concat(currentArticleRegexBit, "[|]~[[").concat(t, "|~g;");
          cmd += "s~\\[\\[".concat(s, "\\|").concat(s, "\\]\\]~[[").concat(t, "]]~g");
        } else {
          cmd += "s~\\[\\[".concat(currentArticleRegexBit, "\\]\\]~[[").concat(t, "|$1]]~g;");
          cmd += "s~\\[\\[".concat(currentArticleRegexBit, "[|]~[[").concat(t, "|~g;");
          cmd += "s~\\[\\[".concat(s, "\\|").concat(s, "\\]\\]~[[").concat(t, "]]~g");
        }
      } else {
        cmd += "s~\\[\\[".concat(currentArticleRegexBit, "\\]\\]~$1~g;");
        cmd += "s~\\[\\[".concat(currentArticleRegexBit, "[|](.*?)\\]\\]~$2~g");
      }
      // Build query
      cmd = "autoedit=".concat(encodeURIComponent(cmd));
      cmd += "&autoclick=".concat(encodeURIComponent(x.clickButton), "&actoken=").concat(encodeURIComponent(autoClickToken()));
      cmd += x.minor === null ? '' : "&autominor=".concat(encodeURIComponent(x.minor));
      cmd += x.watch === null ? '' : "&autowatch=".concat(encodeURIComponent(x.watch));
      cmd += "&autosummary=".concat(encodeURIComponent(x.summary));
      cmd += "&autoimpl=".concat(encodeURIComponent(popupString('autoedit_version')));
      return appendParamsToLink(lk, cmd);
    };
    var redirLink = function redirLink(redirMatch, article) {
      // NB redirMatch is in wikiText
      var ret = '';
      if (getValueOf('popupAppendRedirNavLinks') && getValueOf('popupNavLinks')) {
        ret += '<hr />';
        if (getValueOf('popupFixRedirs') && typeof autoEdit !== 'undefined' && autoEdit) {
          ret += popupString('Redirects to: (Fix ');
          log("redirLink: newTarget=".concat(redirMatch));
          ret += addPopupShortcut(changeLinkTargetLink({
            newTarget: redirMatch,
            text: popupString('target'),
            hint: popupString('Fix this redirect, changing just the link target'),
            summary: simplePrintf(getValueOf('popupFixRedirsSummary'), [article.toString(), redirMatch]),
            oldTarget: article.toString(),
            clickButton: getValueOf('popupRedirAutoClick'),
            minor: true,
            watch: getValueOf('popupWatchRedirredPages')
          }), 'R');
          ret += popupString(' or ');
          ret += addPopupShortcut(changeLinkTargetLink({
            newTarget: redirMatch,
            text: popupString('target & label'),
            hint: popupString('Fix this redirect, changing the link target and label'),
            summary: simplePrintf(getValueOf('popupFixRedirsSummary'), [article.toString(), redirMatch]),
            oldTarget: article.toString(),
            clickButton: getValueOf('popupRedirAutoClick'),
            minor: true,
            watch: getValueOf('popupWatchRedirredPages'),
            alsoChangeLabel: true
          }), 'R');
          ret += popupString(')');
        } else {
          ret += popupString('Redirects') + popupString(' to ');
        }
        return ret;
      }
      return "<br> ".concat(popupString('Redirects')).concat(popupString(' to ')).concat(titledWikiLink({
        article: new Title().fromWikiText(redirMatch),
        action: 'view' /* FIXME: newWin */,
        text: safeDecodeURI(redirMatch),
        title: popupString('Bypass redirect')
      }));
    };
    var arinLink = function arinLink(l) {
      if (!saneLinkCheck(l)) {
        return null;
      }
      if (!l.article.isIpUser() || !pg.wiki.wikimedia) {
        return null;
      }
      var uN = l.article.userName();
      return generalNavLink({
        url: "http://ws.arin.net/cgi-bin/whois.pl?queryinput=".concat(encodeURIComponent(uN)),
        newWin: l.newWin,
        title: tprintf('Look up %s in ARIN whois database', [uN]),
        text: l.text,
        noPopup: 1
      });
    };
    var toolDbName = function toolDbName(cookieStyle) {
      var ret = mw.config.get('wgDBname');
      if (!cookieStyle) {
        ret += '_p';
      }
      return ret;
    };
    var saneLinkCheck = function saneLinkCheck(l) {
      if (_typeof(l.article) !== _typeof({}) || _typeof(l.text) !== _typeof('')) {
        return false;
      }
      return true;
    };
    var editCounterLink = function editCounterLink(l) {
      if (!saneLinkCheck(l)) {
        return null;
      }
      if (!pg.wiki.wikimedia) {
        return null;
      }
      var uN = l.article.userName();
      var tool = getValueOf('popupEditCounterTool');
      var url;
      var defaultToolUrl = '//tools.wmflabs.org/supercount/index.php?user=$1&project=$2.$3';
      switch (tool) {
        case 'custom':
          {
            url = simplePrintf(getValueOf('popupEditCounterUrl'), [encodeURIComponent(uN), toolDbName()]);
            break;
          }
        default:
          {
            var theWiki = pg.wiki.hostname.split('.');
            url = simplePrintf(defaultToolUrl, [encodeURIComponent(uN), theWiki[0], theWiki[1]]);
          }
      }
      return generalNavLink({
        url: url,
        title: tprintf('editCounterLinkHint', [uN]),
        newWin: l.newWin,
        text: l.text,
        noPopup: 1
      });
    };
    var globalSearchLink = function globalSearchLink(l) {
      if (!saneLinkCheck(l)) {
        return null;
      }
      var base = 'http://vs.aka-online.de/cgi-bin/globalwpsearch.pl?timeout=120&search=';
      var article = l.article.urlString({
        keepSpaces: true
      });
      return generalNavLink({
        url: base + article,
        newWin: l.newWin,
        title: tprintf('globalSearchHint', [safeDecodeURI(l.article)]),
        text: l.text,
        noPopup: 1
      });
    };
    var googleLink = function googleLink(l) {
      if (!saneLinkCheck(l)) {
        return null;
      }
      var base = 'https://www.google.com/search?q=';
      var article = l.article.urlString({
        keepSpaces: true
      });
      return generalNavLink({
        url: "".concat(base, "%22").concat(article, "%22"),
        newWin: l.newWin,
        title: tprintf('googleSearchHint', [safeDecodeURI(l.article)]),
        text: l.text,
        noPopup: 1
      });
    };
    var editorListLink = function editorListLink(l) {
      if (!saneLinkCheck(l)) {
        return null;
      }
      var article = l.article.articleFromTalkPage() || l.article;
      var url = "https://xtools.wmflabs.org/articleinfo/".concat(encodeURI(pg.wiki.hostname), "/").concat(article.urlString(), "?uselang=").concat(mw.config.get('wgUserLanguage'));
      return generalNavLink({
        url: url,
        title: tprintf('editorListHint', [article]),
        newWin: l.newWin,
        text: l.text,
        noPopup: 1
      });
    };
    var generalNavLink = function generalNavLink(l) {
      l.className = l.className === null ? 'popupNavLink' : l.className;
      return generalLink(l);
    };

    // magic history links
    //

    var getHistoryInfo = function getHistoryInfo(wikipage, whatNext) {
      log('getHistoryInfo');
      getHistory(wikipage, whatNext ? function (d) {
        whatNext(processHistory(d));
      } : processHistory);
    };

    // FIXME eliminate pg.idNumber ... how? :-(

    var getHistory = function getHistory(wikipage, onComplete) {
      log('getHistory');
      var url = "".concat(pg.wiki.apiwikibase, "?format=json&formatversion=2&action=query&prop=revisions&titles=").concat(new Title(wikipage).urlString(), "&rvlimit=").concat(getValueOf('popupHistoryLimit'));
      log("getHistory: url=".concat(url));
      return startDownload(url, "".concat(pg.idNumber, "history"), onComplete);
    };
    var processHistory = function processHistory(download) {
      var jsobj = getJsObj(download.data);
      try {
        var revisions = anyChild(jsobj.query.pages).revisions;
        var edits = [];
        var _iterator22 = _createForOfIteratorHelper(revisions),
          _step22;
        try {
          for (_iterator22.s(); !(_step22 = _iterator22.n()).done;) {
            var revision = _step22.value;
            edits.push({
              oldid: revision.revid,
              editor: revision.user
            });
          }
        } catch (err) {
          _iterator22.e(err);
        } finally {
          _iterator22.f();
        }
        log("processed ".concat(edits.length, " edits"));
        return finishProcessHistory(edits, mw.config.get('wgUserName'));
      } catch (_unused25) {
        log('Something went wrong with JSON business');
        return finishProcessHistory([]);
      }
    };
    var finishProcessHistory = function finishProcessHistory(edits, userName) {
      var histInfo = {};
      histInfo.edits = edits;
      histInfo.userName = userName;
      for (var i = 0; i < edits.length; ++i) {
        if (typeof histInfo.myLastEdit === 'undefined' && userName && edits[i].editor === userName) {
          histInfo.myLastEdit = {
            index: i,
            oldid: edits[i].oldid,
            previd: i === 0 ? null : edits[i - 1].oldid
          };
        }
        if (typeof histInfo.firstNewEditor === 'undefined' && edits[i].editor !== edits[0].editor) {
          histInfo.firstNewEditor = {
            index: i,
            oldid: edits[i].oldid,
            previd: i === 0 ? null : edits[i - 1].oldid
          };
        }
      }
      //pg.misc.historyInfo=histInfo;
      return histInfo;
    };

    // ENDFILE: links.js

    // STARTFILE: options.js

    // options

    // check for existing value, else use default
    var defaultize = function defaultize(x) {
      if (pg.option[x] === null || typeof pg.option[x] === 'undefined') {
        if (typeof window[x] !== 'undefined') {
          pg.option[x] = window[x];
        } else {
          pg.option[x] = pg.optionDefault[x];
        }
      }
    };
    var newOption = function newOption(x, def) {
      pg.optionDefault[x] = def;
    };
    var setDefault = function setDefault(x, def) {
      return newOption(x, def);
    };
    var getValueOf = function getValueOf(varName) {
      defaultize(varName);
      return pg.option[varName];
    };
    var setOptions = function setOptions() {
      // user-settable parameters and defaults
      var userIsSysop = false;
      if (mw.config.get('wgUserGroups')) {
        for (var g = 0; g < mw.config.get('wgUserGroups').length; ++g) {
          if (mw.config.get('wgUserGroups')[g] === 'sysop') {
            userIsSysop = true;
          }
        }
      }

      // Basic options
      newOption('popupDelay', 0.5);
      newOption('popupHideDelay', 0.5);
      newOption('simplePopups', false);
      newOption('popupStructure', 'shortmenus'); // see later - default for popupStructure is 'original' if simplePopups is true
      newOption('popupActionsMenu', true);
      newOption('popupSetupMenu', true);
      newOption('popupAdminLinks', userIsSysop);
      newOption('popupShortcutKeys', false);
      newOption('popupHistoricalLinks', true);
      newOption('popupOnlyArticleLinks', true);
      newOption('removeTitles', true);
      newOption('popupMaxWidth', 350);
      newOption('popupSimplifyMainLink', true);
      newOption('popupAppendRedirNavLinks', true);
      newOption('popupTocLinks', false);
      newOption('popupSubpopups', true);
      newOption('popupDragHandle', false /* 'popupTopLinks'*/);
      newOption('popupLazyPreviews', true);
      newOption('popupLazyDownloads', true);
      newOption('popupAllDabsStubs', false);
      newOption('popupDebugging', false);
      newOption('popupActiveNavlinks', true);
      newOption('popupModifier', false); // ctrl, shift, alt or meta
      newOption('popupModifierAction', 'enable'); // or 'disable'
      newOption('popupDraggable', true);
      newOption('popupReview', false);
      newOption('popupLocale', false);
      newOption('popupDateTimeFormatterOptions', {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
        hour12: false,
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit'
      });
      newOption('popupDateFormatterOptions', {
        year: 'numeric',
        month: 'long',
        day: 'numeric'
      });
      newOption('popupTimeFormatterOptions', {
        hour12: false,
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit'
      });

      // images
      newOption('popupImages', true);
      newOption('imagePopupsForImages', true);
      newOption('popupNeverGetThumbs', false);
      //newOption('popupImagesToggleSize',       true);
      newOption('popupThumbAction', 'imagepage'); //'sizetoggle');
      newOption('popupImageSize', 60);
      newOption('popupImageSizeLarge', 200);

      // redirs, dabs, reversion
      newOption('popupFixRedirs', false);
      newOption('popupRedirAutoClick', 'wpDiff');
      newOption('popupFixDabs', false);
      newOption('popupDabsAutoClick', 'wpDiff');
      newOption('popupRevertSummaryPrompt', false);
      newOption('popupMinorReverts', false);
      newOption('popupRedlinkRemoval', false);
      newOption('popupRedlinkAutoClick', 'wpDiff');
      newOption('popupWatchDisambiggedPages', null);
      newOption('popupWatchRedirredPages', null);
      newOption('popupDabWiktionary', 'last');

      // navlinks
      newOption('popupNavLinks', true);
      newOption('popupNavLinkSeparator', ' &sdot; ');
      newOption('popupLastEditLink', true);
      newOption('popupEditCounterTool', 'supercount');
      newOption('popupEditCounterUrl', '');

      // previews etc
      newOption('popupPreviews', true);
      newOption('popupSummaryData', true);
      newOption('popupMaxPreviewSentences', 5);
      newOption('popupMaxPreviewCharacters', 600);
      newOption('popupLastModified', true);
      newOption('popupPreviewKillTemplates', true);
      newOption('popupPreviewRawTemplates', true);
      newOption('popupPreviewFirstParOnly', true);
      newOption('popupPreviewCutHeadings', true);
      newOption('popupPreviewButton', false);
      newOption('popupPreviewButtonEvent', 'click');

      // diffs
      newOption('popupPreviewDiffs', true);
      newOption('popupDiffMaxLines', 100);
      newOption('popupDiffContextLines', 2);
      newOption('popupDiffContextCharacters', 40);
      newOption('popupDiffDates', true);
      newOption('popupDiffDatePrinter', 'toLocaleString'); // no longer in use

      // edit summaries. God, these are ugly.
      newOption('popupReviewedSummary', popupString('defaultpopupReviewedSummary'));
      newOption('popupFixDabsSummary', popupString('defaultpopupFixDabsSummary'));
      newOption('popupExtendedRevertSummary', popupString('defaultpopupExtendedRevertSummary'));
      newOption('popupRevertSummary', popupString('defaultpopupRevertSummary'));
      newOption('popupRevertToPreviousSummary', popupString('defaultpopupRevertToPreviousSummary'));
      newOption('popupQueriedRevertSummary', popupString('defaultpopupQueriedRevertSummary'));
      newOption('popupQueriedRevertToPreviousSummary', popupString('defaultpopupQueriedRevertToPreviousSummary'));
      newOption('popupFixRedirsSummary', popupString('defaultpopupFixRedirsSummary'));
      newOption('popupRedlinkSummary', popupString('defaultpopupRedlinkSummary'));
      newOption('popupRmDabLinkSummary', popupString('defaultpopupRmDabLinkSummary'));

      // misc
      newOption('popupHistoryLimit', 50);
      newOption('popupFilters', [popupFilterStubDetect, popupFilterDisambigDetect, popupFilterPageSize, popupFilterCountLinks, popupFilterCountImages, popupFilterCountCategories, popupFilterLastModified]);
      newOption('extraPopupFilters', []);
      newOption('popupOnEditSelection', 'cursor');
      newOption('popupPreviewHistory', true);
      newOption('popupImageLinks', true);
      newOption('popupCategoryMembers', true);
      newOption('popupUserInfo', true);
      newOption('popupHistoryPreviewLimit', 25);
      newOption('popupContribsPreviewLimit', 25);
      newOption('popupRevDelUrl', '//en.wikipedia.org/wiki/Wikipedia:Revision_deletion');
      newOption('popupShowGender', true);

      // new windows
      newOption('popupNewWindows', false);
      newOption('popupLinksNewWindow', {
        lastContrib: true,
        sinceMe: true
      });

      // regexps
      newOption('popupDabRegexp', '\\{\\{\\s*(d(ab|isamb(ig(uation)?)?)|(((geo|hn|road?|school|number)dis)|[234][lc][acw]|(road|ship)index))\\s*(\\|[^}]*)?\\}\\}|is a .*disambiguation.*page');
      newOption('popupAnchorRegexp', 'anchors?'); //how to identify an anchors template
      newOption('popupStubRegexp', '(sect)?stub[}][}]|This .*-related article is a .*stub');
      newOption('popupImageVarsRegexp', 'image|image_(?:file|skyline|name|flag|seal)|cover|badge|logo');
    };
    // ENDFILE: options.js

    // STARTFILE: strings.js

    // Translatable strings

    //
    // See instructions at
    // <en.wikipedia.org/wiki/Wikipedia:Tools/Navigation_popups/Translation>

    pg.string = {
      // summary data, searching etc.
      article: 'article',
      category: 'category',
      categories: 'categories',
      image: 'image',
      images: 'images',
      stub: 'stub',
      'section stub': 'section stub',
      'Empty page': 'Empty page',
      kB: 'kB',
      bytes: 'bytes',
      day: 'day',
      days: 'days',
      hour: 'hour',
      hours: 'hours',
      minute: 'minute',
      minutes: 'minutes',
      second: 'second',
      seconds: 'seconds',
      week: 'week',
      weeks: 'weeks',
      search: 'search',
      SearchHint: 'Find English Wikipedia articles containing %s',
      web: 'web',
      global: 'global',
      globalSearchHint: 'Search across Wikipedias in different languages for %s',
      googleSearchHint: 'Google for %s',
      // article-related actions and info
      // (some actions also apply to user pages)
      actions: 'actions',
      ///// view articles and view talk
      popupsMenu: 'popups',
      togglePreviewsHint: 'Toggle preview generation in popups on this page',
      'enable previews': 'enable previews',
      'disable previews': 'disable previews',
      'toggle previews': 'toggle previews',
      'show preview': 'show preview',
      reset: 'reset',
      'more...': 'more...',
      disable: 'disable popups',
      disablePopupsHint: 'Disable popups on this page. Reload page to re-enable.',
      historyfeedHint: 'RSS feed of recent changes to this page',
      purgePopupsHint: 'Reset popups, clearing all cached popup data.',
      PopupsHint: 'Reset popups, clearing all cached popup data.',
      spacebar: 'space',
      view: 'view',
      'view article': 'view article',
      viewHint: 'Go to %s',
      talk: 'talk',
      'talk page': 'talk page',
      'this&nbsp;revision': 'this&nbsp;revision',
      'revision %s of %s': 'revision %s of %s',
      'Revision %s of %s': 'Revision %s of %s',
      'the revision prior to revision %s of %s': 'the revision prior to revision %s of %s',
      'Toggle image size': 'Click to toggle image size',
      del: 'del',
      ///// delete, protect, move
      'delete': 'delete',
      deleteHint: 'Delete %s',
      undeleteShort: 'un',
      UndeleteHint: 'Show the deletion history for %s',
      protect: 'protect',
      protectHint: 'Restrict editing rights to %s',
      unprotectShort: 'un',
      unprotectHint: 'Allow %s to be edited by anyone again',
      'send thanks': 'send thanks',
      ThanksHint: 'Send a thank you notification to this user',
      move: 'move',
      'move page': 'move page',
      MovepageHint: 'Change the title of %s',
      edit: 'edit',
      ///// edit articles and talk
      'edit article': 'edit article',
      editHint: 'Change the content of %s',
      'edit talk': 'edit talk',
      'new': 'new',
      'new topic': 'new topic',
      newSectionHint: 'Start a new section on %s',
      'null edit': 'null edit',
      nullEditHint: 'Submit an edit to %s, making no changes ',
      hist: 'hist',
      ///// history, diffs, editors, related
      history: 'history',
      historyHint: 'List the changes made to %s',
      last: 'prev',
      // For labelling the previous revision in history pages; the key is "last" for backwards compatibility
      lastEdit: 'lastEdit',
      'mark patrolled': 'mark patrolled',
      markpatrolledHint: 'Mark this edit as patrolled',
      'Could not marked this edit as patrolled': 'Could not marked this edit as patrolled',
      'show last edit': 'most recent edit',
      'Show the last edit': 'Show the effects of the most recent change',
      lastContrib: 'lastContrib',
      'last set of edits': 'latest edits',
      lastContribHint: 'Show the net effect of changes made by the last editor',
      cur: 'cur',
      diffCur: 'diffCur',
      'Show changes since revision %s': 'Show changes since revision %s',
      '%s old': '%s old',
      // as in 4 weeks old
      oldEdit: 'oldEdit',
      purge: 'purge',
      purgeHint: 'Demand a fresh copy of %s',
      raw: 'source',
      rawHint: 'Download the source of %s',
      render: 'simple',
      renderHint: 'Show a plain HTML version of %s',
      'Show the edit made to get revision': 'Show the edit made to get revision',
      sinceMe: 'sinceMe',
      'changes since mine': 'diff my edit',
      sinceMeHint: 'Show changes since my last edit',
      "Couldn't find an edit by %s\nin the last %s edits to\n%s": "Couldn't find an edit by %s\nin the last %s edits to\n%s",
      eds: 'eds',
      editors: 'editors',
      editorListHint: 'List the users who have edited %s',
      related: 'related',
      relatedChanges: 'relatedChanges',
      'related changes': 'related changes',
      RecentchangeslinkedHint: 'Show changes in articles related to %s',
      editOld: 'editOld',
      ///// edit old version, or revert
      rv: 'rv',
      revert: 'revert',
      revertHint: 'Revert to %s',
      defaultpopupReviewedSummary: 'Accepted by reviewing the [[Special:diff/%s/%s|difference]] between this version and previously accepted version using [[:enwiki:Wikipedia:Tools/Navigation_popups|popups]]',
      defaultpopupRedlinkSummary: 'Removing link to empty page [[%s]] using [[:enwiki:Wikipedia:Tools/Navigation_popups|popups]]',
      defaultpopupFixDabsSummary: 'Disambiguate [[%s]] to [[%s]] using [[:enwiki:Wikipedia:Tools/Navigation_popups|popups]]',
      defaultpopupFixRedirsSummary: 'Redirect bypass from [[%s]] to [[%s]] using [[:enwiki:Wikipedia:Tools/Navigation_popups|popups]]',
      defaultpopupExtendedRevertSummary: 'Revert to revision dated %s by %s, oldid %s using [[:enwiki:Wikipedia:Tools/Navigation_popups|popups]]',
      defaultpopupRevertToPreviousSummary: 'Revert to the revision prior to revision %s using [[:enwiki:Wikipedia:Tools/Navigation_popups|popups]]',
      defaultpopupRevertSummary: 'Revert to revision %s using [[:enwiki:Wikipedia:Tools/Navigation_popups|popups]]',
      defaultpopupQueriedRevertToPreviousSummary: 'Revert to the revision prior to revision $1 dated $2 by $3 using [[:enwiki:Wikipedia:Tools/Navigation_popups|popups]]',
      defaultpopupQueriedRevertSummary: 'Revert to revision $1 dated $2 by $3 using [[:enwiki:Wikipedia:Tools/Navigation_popups|popups]]',
      defaultpopupRmDabLinkSummary: 'Remove link to dab page [[%s]] using [[:enwiki:Wikipedia:Tools/Navigation_popups|popups]]',
      Redirects: 'Redirects',
      // as in Redirects to ...
      ' to ': ' to ',
      // as in Redirects to ...
      'Bypass redirect': 'Bypass redirect',
      'Fix this redirect': 'Fix this redirect',
      disambig: 'disambig',
      ///// add or remove dab etc.
      disambigHint: 'Disambiguate this link to [[%s]]',
      'Click to disambiguate this link to:': 'Click to disambiguate this link to:',
      'remove this link': 'remove this link',
      'remove all links to this page from this article': 'remove all links to this page from this article',
      'remove all links to this disambig page from this article': 'remove all links to this disambig page from this article',
      mainlink: 'mainlink',
      ///// links, watch, unwatch
      wikiLink: 'wikiLink',
      wikiLinks: 'wikiLinks',
      'links here': 'links here',
      whatLinksHere: 'whatLinksHere',
      'what links here': 'what links here',
      WhatlinkshereHint: 'List the pages that are hyperlinked to %s',
      unwatchShort: 'un',
      watchThingy: 'watch',
      // called watchThingy because {}.watch is a function
      watchHint: 'Add %s to my watchlist',
      unwatchHint: 'Remove %s from my watchlist',
      'Only found one editor: %s made %s edits': 'Only found one editor: %s made %s edits',
      '%s seems to be the last editor to the page %s': '%s seems to be the last editor to the page %s',
      rss: 'rss',
      // diff previews
      'Diff truncated for performance reasons': 'Diff truncated for performance reasons',
      'Old revision': 'Old revision',
      'New revision': 'New revision',
      'Something went wrong :-(': 'Something went wrong :-(',
      'Empty revision, maybe non-existent': 'Empty revision, maybe non-existent',
      'Unknown date': 'Unknown date',
      // other special previews
      'Empty category': 'Empty category',
      'Category members (%s shown)': 'Category members (%s shown)',
      'No image links found': 'No image links found',
      'File links': 'File links',
      'No image found': 'No image found',
      'Image from Commons': 'Image from Commons',
      'Description page': 'Description page',
      'Alt text:': 'Alt text:',
      revdel: 'Hidden revision',
      // user-related actions and info
      user: 'user',
      ///// user page, talk, email, space
      'user&nbsp;page': 'user&nbsp;page',
      'user talk': 'user talk',
      'edit user talk': 'edit user talk',
      'leave comment': 'leave comment',
      email: 'email',
      'email user': 'email user',
      EmailuserHint: 'Send an email to %s',
      space: 'space',
      // short form for userSpace link
      PrefixIndexHint: 'Show pages in the userspace of %s',
      count: 'count',
      ///// contributions, log
      'edit counter': 'edit counter',
      editCounterLinkHint: 'Count the contributions made by %s',
      contribs: 'contribs',
      contributions: 'contributions',
      deletedContribs: 'deleted contributions',
      DeletedcontributionsHint: 'List deleted edits made by %s',
      ContributionsHint: 'List the contributions made by %s',
      log: 'log',
      'user log': 'user log',
      userLogHint: "Show %s's user log",
      arin: 'ARIN lookup',
      ///// ARIN lookup, block user or IP
      'Look up %s in ARIN whois database': 'Look up %s in the ARIN whois database',
      unblockShort: 'un',
      block: 'block',
      'block user': 'block user',
      IpblocklistHint: 'Unblock %s',
      BlockipHint: 'Prevent %s from editing',
      'block log': 'block log',
      blockLogHint: 'Show the block log for %s',
      protectLogHint: 'Show the protection log for %s',
      pageLogHint: 'Show the page log for %s',
      deleteLogHint: 'Show the deletion log for %s',
      'Invalid %s %s': 'The option %s is invalid: %s',
      'No backlinks found': 'No backlinks found',
      ' and more': ' and more',
      undo: 'undo',
      undoHint: 'undo this edit',
      'Download preview data': 'Download preview data',
      'Invalid or IP user': 'Invalid or IP user',
      'Not a registered username': 'Not a registered username',
      BLOCKED: 'BLOCKED',
      'Has blocks': 'Has blocks',
      ' edits since: ': ' edits since: ',
      'last edit on ': 'last edit on ',
      'he/him': 'he/him',
      'she/her': 'she/her',
      // Autoediting
      'Enter a non-empty edit summary or press cancel to abort': 'Enter a non-empty edit summary or press cancel to abort',
      'Failed to get revision information, please edit manually.\n\n': 'Failed to get revision information, please edit manually.\n\n',
      'The %s button has been automatically clicked. Please wait for the next page to load.': 'The %s button has been automatically clicked. Please wait for the next page to load.',
      'Could not find button %s. Please check the settings in your javascript file.': 'Could not find button %s. Please check the settings in your javascript file.',
      // Popups setup
      'Open full-size image': 'Open full-size image',
      zxy: 'zxy',
      autoedit_version: 'np20140416'
    };
    var popupString = function popupString(str) {
      if (typeof popupStrings !== 'undefined' && popupStrings && popupStrings[str]) {
        return popupStrings[str];
      }
      if (pg.string[str]) {
        return pg.string[str];
      }
      return str;
    };
    var tprintf = function tprintf(str, subs) {
      if (_typeof(subs) !== _typeof([])) {
        subs = [subs];
      }
      return simplePrintf(popupString(str), subs);
    };

    // ENDFILE: strings.js

    // STARTFILE: run.js
    // Run things

    // For some reason popups requires a fully loaded page jQuery.ready(...) causes problems for some.
    // The old addOnloadHook did something similar to the below
    if (document.readyState === 'complete') {
      autoEdit();
    } else {
      // will setup popups
      $(window).on('load', autoEdit);
    }

    // Support for MediaWiki's live preview, VisualEditor's saves and Echo's flyout.
    (function () {
      var once = true;
      var dynamicContentHandler = function dynamicContentHandler($content) {
        // Try to detect the hook fired on initial page load and disregard
        // it, we already hook to onload (possibly to different parts of
        // page - it's configurable) and running twice might be bad. Ugly…
        if ($content.attr('id') === 'mw-content-text' && once) {
          once = false;
          return;
        }
        var registerHooksForVisibleNavpops = function registerHooksForVisibleNavpops() {
          for (var i = 0; pg.current.links && i < pg.current.links.length; ++i) {
            var navpop = pg.current.links[i].navpopup;
            if (!navpop || !navpop.isVisible()) {
              continue;
            }
            Navpopup.tracker.addHook(posCheckerHook(navpop));
          }
        };
        var doIt = function doIt() {
          registerHooksForVisibleNavpops();
          $content.each(function () {
            this.ranSetupTooltipsAlready = false;
            setupTooltips(this);
          });
        };
        setupPopups(doIt);
      };

      // This hook is also fired after page load.
      mw.hook('wikipage.content').add(dynamicContentHandler);
      mw.hook('ext.echo.overlay.beforeShowingOverlay').add(function ($overlay) {
        dynamicContentHandler($overlay.find('.mw-echo-state'));
      });
    })();
  });
  // ENDFILE: run.js
})(jQuery, mediaWiki);

/* </nowiki> */