Чемпион Южной Америки 1916 Чемпион Южной Америки 1917 Чемпион Южной Америки 1920 Чемпион Южной Америки 1923 Олимпийский чемпион и чемпион мира среди любителей 1924 Чемпион Южной Америки 1924 Чемпион Южной Америки 1926 Олимпийский чемпион и чемпион мира среди любителей 1928 Чемпион мира 1930 Чемпион Южной Америки 1935 Чемпион Южной Америки 1942 Чемпион мира 1950 Чемпион Южной Америки 1956 Чемпион Южной Америки 1959 (Эквадор) Чемпион Южной Америки 1967 Победитель Мундиалито 1980-81 Победитель Кубка Америки 1983 Победитель Кубка Америки 1987 Победитель Кубка Америки 1995 Победитель Кубка Америки 2011

MediaWiki:Gadget-HotCat.js — различия между версиями

Материал из CelestePedia
Перейти к: навигация, поиск
Строка 1: Строка 1:
//<source lang="javascript">
 
 
 
/*
 
/*
  HotCat V2.10
+
This imports the latest version of HotCat from Commons.
 
+
HotCat is a gadget to make changes to categories much easier.
  Ajax-based simple Category manager. Allows adding/removing/changing categories on a page view.
+
Full documentation can be found at https://commons.wikimedia.org/wiki/Help:Gadget-HotCat
  Supports multiple category changes, as well as redirect and disambiguation resolution. Also
 
  plugs into the upload form. Search engines to use for the suggestion list are configurable, and
 
  can be selected interactively.
 
 
 
  Authors:
 
    V0.0: July 2007 - 2010-05-26: original version by [[User:Magnus Manske]], with lots of
 
          additions by many editors, notably [[User:Dschwen]], [[User:TheDJ]], [[User:Superm401]],
 
          and [[User:Lupo]]. No explicit license, assumed multi-licensed GFDL and CC-BY-SA-3.0 per
 
          normal wiki submissions.
 
    V2.0: April-May 2010: [[User:Lupo]]. Complete rewrite reusing only a little code from V0.0.
 
    V2.1: May 2010: [[User:Merlissimo]] (added features: namespace case insensitive, subcategory
 
          engine, category template mapping for removing; developed at de-Wikipedia.)
 
    V2.2: May 2010: [[User:Lupo]] (porting additions from de-WP to the Commons, auto-localization
 
          of template namespace name, cleanup, various other improvements. New features:
 
          highlighting of changed categories, enabling/disabling save button, search engine name
 
          localization, parent category engine).
 
    V2.3: Aug 2010: [[User:Lupo]]: page up/down for scrolling in suggestion lists; suggestion list
 
          size configurable; minor bug fixes.
 
    V2.4: Oct 2010: [[User:TheDJ]] & [[User:Lupo]]: switch to not enable on upload form, ability to
 
          load translations from the Commons. Major/minor edit flag configurable; and fix a minor
 
          bug with the "Warn upon empty edit summary" user preference.
 
    V2.5: Jan 2011: [[User:Lupo]]: blacklist
 
    V2.6: Feb 2011: [[User:Ciencia Al Poder]] & [[User:Lupo]]: get rid of old URI-param based edit
 
          interface, preventing unwanted single-category changes following malicious links
 
    V2.7: March 2011: [[User:Lupo]]: fixes for being loaded as a gadget through the resource loader.
 
    V2.8: July 2011: [[User:Lupo]]: don't remove "uncategorized" template if only hidden categories
 
          are added.
 
    V2.9: July 2011: [[User:Lupo]]: properly delay start-up of HotCat until localizations are loaded.
 
          Also fix skipping interwikis and newline before new category.
 
    V2.10:Sep 2011: [[User:Lupo]]: make compatible with new UL-LI DOM for categories, and adapt for
 
          protocol-relative URLs.
 
 
 
  License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0)
 
 
  Choose whichever license of these you like best :-)
 
 
*/
 
*/
 
+
mw.loader.load( '//commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-HotCat.js&action=raw&ctype=text/javascript' );
// Globals:
 
//  (inline script on the page):
 
//    wgNamespaceNumber, wgCanonicalSpecialPageName, wgNamespaceIds (optional), wgFormattedNamespaces (optional)
 
//    wgScript, wgServer, wgArticlePath, wgScriptPath, wgAction, wgPageName, wgTitle, wgUserName, wgIsArticle,
 
//    wgArticleId
 
//  ajax.js
 
//    sajax_init_object
 
//  wikibits.js
 
//    window.ie6_bugs, window.is_safari, window.is_opera, addOnloadHook (or alternatively jQuery)
 
 
 
if (typeof (window.HotCat) == 'undefined' && wgAction != 'edit') { // Guard against double inclusions, and inactivate on edit pages
 
   
 
// Configuration stuff.
 
var HotCat = {
 
  isCommonsVersion : false
 
    // If you copy HotCat to your wiki, you should set this to false!
 
 
 
  // Localize these messages to the main language of your wiki.
 
  ,messages :
 
    { cat_removed  : 'Удaлeнa [[Категория:$1]]'
 
    ,template_removed  : 'Удаление {{[[Категория:$1|$1]]}}'
 
    ,cat_added    : 'Дoбaвлeнa [[Категория:$1]]'
 
    ,cat_keychange: 'нoвый ключ copтиpoвки для [[Категория:$1]]: '
 
    ,cat_notFound : 'Категория "$1" не найдена'
 
    ,cat_exists  : 'Категория "$1" уже существует, не добавлена'
 
    ,cat_resolved : ' (Перенаправление [[Категория:$1]] разрешено)'
 
    ,uncat_removed: 'удалён шаблон {{нет категорий}}' 
 
    ,separator    : '; '
 
    ,prefix      : ''
 
        // Some text to prefix to the edit summary.
 
    ,using        : ' с помощью [[CelestePedia:HC|HotCat]]'
 
        // Some text to append to the edit summary. Named 'using' for historical reasons. If you prefer
 
        // to have a marker at the front, use prefix and set this to the empty string.
 
    ,multi_change : '$1 категорий'
 
        // $1 is replaced by a number
 
    ,commit      : 'Сохранить'
 
        // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
 
        // see localization hook below.
 
    ,ok          : 'OK'
 
        // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
 
        // see localization hook below.
 
    ,cancel      : 'Отмена'
 
        // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
 
        // see localization hook below.
 
    ,multi_error  : 'Неизвестные ошибки. Ваши изменения не были сохранены'
 
        // Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
 
        // see localization hook below.
 
    }
 
,category_regexp    : '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]|[Кк][Аа][Тт][Ее][Гг][Оо][Рр][Ии][Яя]'
 
  // Regular sub-expression matching all possible names for the category namespace. Is automatically localized
 
  // correctly if you're running MediaWiki 1.16 or later. Otherwise, set it appropriately, e.g. at the German
 
  // Wikipedia, use '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]|[Kk][Aa][Tt][Ee][Gg][Oo][Rr][Ii][Ee]', or at the
 
  // Chinese Wikipedia, use '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]|分类|分類'. Note that namespaces are case-
 
  // insensitive!
 
,category_canonical : 'Категория'
 
  // The standard category name on your wiki. Is automatically localized correctly if you're running
 
  // MediaWiki 1.16 or later; otherwise, set it to the preferred category name (e.g., "Kategorie").
 
,categories        : 'Категории'
 
  // Plural of category_canonical
 
,disambig_category  : null
 
  // Any category in this category is deemed a disambiguation category; i.e., a category that should not contain
 
  // any items, but that contains links to other categories where stuff should be categorized. If you don't have
 
  // that concept on your wiki, set it to null.
 
,redir_category    : null
 
  // Any category in this category is deemed a (soft) redirect to some other category defined by the first link
 
  // to another category. If your wiki doesn't have soft category redirects, set this to null.
 
,links : {change: '(±)', remove: '(−)', add: '(+)', restore: '(×)', undo: '(×)', down: '(↓)', up: '(↑)'}
 
  // The little modification links displayed after category names.
 
,tooltips : {
 
    change:  'Изменить категорию'
 
  ,remove:  'Удалить категорию'
 
  ,add:    'Добавить новую категорию'
 
  ,restore: 'Восстановить'
 
  ,undo:    'Отменить'
 
  ,down:    'Заменить на подкатегорию'
 
  ,up:      'Заменить надкатегорией'
 
  }
 
  // The tooltips for the above links
 
,addmulti          : '<span>+<sup>+</sup></span>'
 
  // The HTML content of the "enter multi-mode" link at the front.
 
,multi_tooltip      : 'Изменить другие категории'
 
  // Tooltip for the "enter multi-mode" link
 
,disable            :
 
    function () { // Return true to disable HotCat
 
      return (  wgNamespaceNumber < 0  // Special pages; Special:Upload is handled differently
 
              || wgNamespaceNumber == 8  // MediaWiki
 
              || wgNamespaceNumber == 2
 
                && wgTitle && wgTitle.length >= 3 && wgTitle.lastIndexOf ('.js') + 3 == wgTitle.length
 
                // User scripts
 
              || typeof (wgNamespaceIds) != 'undefined'
 
                && (  wgNamespaceNumber == wgNamespaceIds['creator']
 
                    || wgNamespaceNumber == wgNamespaceIds['timedtext']
 
                    || wgNamespaceNumber == wgNamespaceIds['institution']
 
                    )
 
            );
 
    }
 
,uncat_regexp : /\{\{\s*(uncategorized|nocat|нет категорий|шаблон без категорий|rq\s*\|\s*cat)\s*\}\}\n?/gi
 
  // A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
 
  // If not, set it to null.
 
,existsYes    : (wgServer.substring(0, 2) != '//' ? document.location.protocol : "") + '//upload.wikimedia.org/wikipedia/commons/thumb/b/be/P_yes.svg/20px-P_yes.svg.png'
 
,existsNo    : (wgServer.substring(0, 2) != '//' ? document.location.protocol : "") + '//upload.wikimedia.org/wikipedia/commons/thumb/4/42/P_no.svg/20px-P_no.svg.png'
 
  // The images used for the little indication icon. Should not need changing.
 
,template_regexp    : '[Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee]|[Шш][Аа][Бб][Лл][Оо][Нн]'
 
  // Regexp to recognize templates. Like "category" above; autolocalized for MW 1.16+, otherwise set manually here.
 
,template_categories : {}
 
  // a list of categories which can be removed by removing a template
 
  // key: the category without namespace
 
  // value: A regexp matching the template name, again without namespace
 
  // If you don't have this at your wiki, or don't want this, set it to an empty object {}.
 
,engine_names : {
 
    searchindex : 'Поисковый индекс'
 
  ,pagelist    : 'Список страниц'
 
  ,combined    : 'Расширенный поиск'
 
  ,subcat      : 'Подкатегории'
 
  ,parentcat  : 'Надкатегории'
 
  }
 
  // Names for the search engines
 
,capitalizePageNames : true
 
  // Set to false if your wiki has case-sensitive page names. MediaWiki has two modes: either the first letter
 
  // of a page is automatically capitalized ("first-letter"; Category:aa == Category:Aa), or it isn't
 
  // ("case-sensitive"; Category:aa != Category:Aa). It doesn't currently have a fully case-insensitive mode
 
  // (which would mean Category:aa == Category:Aa == Category:AA == Category:aA)
 
  // HotCat tries to set this corretcly automatically using an API query. It's still a good idea to manually
 
  // configure it correctly; either directly here if you copied HotCat, or in the local configuration file
 
  // MediaWiki:Gadget-HotCat.js/local_defaults if you hotlink to the Commons-version, to ensure it is set even
 
  // if that API query should fail for some strange reason.
 
,upload_disabled : false
 
  // If upload_disabled is true, HotCat will not be used on the Upload form.
 
,blacklist : null
 
  // Single regular expression matching blacklisted categories that cannot be changed or
 
  // added using HotCat. For instance /\bstubs?$/ (any category ending with the word "stub"
 
  // or "stubs"), or /(\bstubs?$)|\bmaintenance\b/ (stub categories and any category with the
 
  // word "maintenance" in its title.
 
 
 
  // Stuff changeable by users:
 
,bg_changed : '#F8CCB0'
 
  // Background for changed categories in multi-edit mode. Default is a very light salmon pink.
 
,no_autocommit : false
 
  // If true, HotCat will never automatically submit changes. HotCat will only open an edit page with
 
  // the changes; users must always save explicitly.
 
,suggest_delay : 100
 
  // Time, in milliseconds, that HotCat waits after a keystroke before making a request to the
 
  // server to get suggestions.
 
,editbox_width : 40
 
  // Default width, in characters, of the text input field.
 
,suggestions : 'combined'
 
  // One of the engine_names above, to be used as the default suggestion engine.
 
,fixed_search : false
 
  // If true, always use the default engine, and never display a selector.
 
,use_up_down : true
 
  // If false, do not display the "up" and "down" links
 
,list_size : 5
 
  // Default list size
 
,single_minor : true
 
  // If true, single category changes are marked as minor edits. If false, they're not.
 
,dont_add_to_watchlist : false
 
  // If true, never add a page to the user's watchlist. If false, pages get added to the watchlist if
 
  // the user has the "Add pages I edit to my watchlist" or the "Add pages I create to my watchlist"
 
  // options in his or her preferences set.
 
};
 
 
 
// Make sure this is exported, so that localizations *can* actually modify parts of it, and the
 
// guard at the top actually works. (If we're loaded as an extension module through the resource
 
// loader, this outer scope may actually be a closure, not the global "window" scope.)
 
if (typeof (window.HotCat) == 'undefined') window.HotCat = HotCat;
 
 
 
(function () { // Local scope to avoid polluting global namespace with declarations
 
 
 
  function armorUri (uri) {
 
    // Avoid protocol-relative URIs, IE7 has a bug with them in Ajax calls
 
    if (uri.length >= 2 && uri.substring(0, 2) == '//') return document.location.protocol + uri;
 
    return uri;
 
  }
 
 
 
  function LoadTrigger () { this.initialize.apply (this, arguments); };
 
  LoadTrigger.prototype = {
 
    initialize : function (needed) {
 
      this.queue = [];
 
      this.toLoad = needed;
 
    },
 
 
 
    register : function (callback) {
 
      if (this.toLoad <= 0) {
 
        callback (); // Execute directly
 
      } else {
 
        this.queue[this.queue.length] = callback;
 
      }
 
    },
 
 
 
    loaded : function () {
 
      if (this.toLoad > 0) {
 
        this.toLoad--;
 
        if (this.toLoad === 0) {
 
          // Run queued callbacks once
 
          for (var i = 0; i < this.queue.length; i++) this.queue[i]();
 
          this.queue = [];
 
        }
 
      }
 
    }
 
 
 
  };
 
 
 
  var loadTrigger = new LoadTrigger(2);
 
 
 
  function load (uri) {
 
    var head = document.getElementsByTagName ('head')[0];
 
    var s = document.createElement ('script');
 
    s.setAttribute ('src', armorUri(uri));
 
    s.setAttribute ('type', 'text/javascript');
 
    var done = false;
 
    s.onload = s.onreadystatechange = function () { // onreadystatechange for IE, onload for all others
 
      if (done) return;
 
      if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
 
        done = true;
 
        s.onload = s.onreadystatechange = null; // Properly clean up to avoid memory leaks in IE
 
        if (head && s.parentNode) head.removeChild (s);
 
        loadTrigger.loaded();
 
      }
 
    };
 
    head.insertBefore (s, head.firstChild); // appendChild may trigger bugs in IE6 here
 
  }
 
 
 
  function loadJS (page) {
 
    load (wgServer + wgScript + '?title=' + encodeURIComponent (page) + '&action=raw&ctype=text/javascript');
 
  }
 
 
 
  function loadURI (href) {
 
    var url = href;
 
    if (url.substring (0, 2) == '//') {
 
      url = window.location.protocol + url;
 
    } else if (url.substring (0, 1) == '/') {
 
      url = wgServer + url;
 
    }
 
    load (url);
 
  }
 
 
 
  if (HotCat.isCommonsVersion && wgServer.indexOf ('/commons') < 0) {
 
    // We're running in some other wiki, which hotlinks to the Commons version. The other wiki can put local settings
 
    // in this file to override the Commons settings for all user languages. For instance, if on your wiki people do
 
    // not like automatic saving, you'd add in that file the line HotCat.no_autocommit = true; If you hotlink, you
 
    // *must* adapt HotCat.categories in this file to the local translation in wgContentLanguage of your wiki of the
 
    // English plural "Categories", and you should provide translations in wgContentLanguage of your wiki of all messages,
 
    // tooltips, and of the engine names.
 
    loadJS ('MediaWiki:Gadget-HotCat.js/local_defaults');
 
  } else {
 
    loadTrigger.loaded();
 
  }
 
 
 
  if (wgUserLanguage != 'ru') {
 
    // Localization hook to localize HotCat messages, tooltips, and engine names for wgUserLanguage.
 
    if (window.hotcat_translations_from_commons && wgServer.indexOf ('/commons') < 0) {
 
      loadURI (
 
        ((wgServer.indexOf( "https://secure.wikimedia.org") === 0)
 
          ? '/wikipedia/commons/w/index.php?title='
 
          : '//commons.wikimedia.org/w/index.php?title='
 
        )
 
        + 'MediaWiki:Gadget-HotCat.js/' + wgUserLanguage
 
        + '&action=raw&ctype=text/javascript&smaxage=21600&maxage=86400'
 
      );
 
    } else {
 
      // Load translations locally
 
      loadJS ('MediaWiki:Gadget-HotCat.js/' + wgUserLanguage);
 
    }
 
  } else {
 
    loadTrigger.loaded();
 
  }
 
 
 
  // No further changes should be necessary here.
 
 
  // First auto-localize the regexps for the category and the template namespaces.
 
  if (typeof (wgFormattedNamespaces) != 'undefined') {
 
    function autoLocalize (namespaceNumber, fallback) {
 
      function create_regexp_str (name)
 
      {
 
        if (!name || name.length === 0) return "";
 
        var regex_name = "";
 
        for (var i = 0; i < name.length; i++){
 
          var initial = name.substr (i, 1);
 
          var ll = initial.toLowerCase ();
 
          var ul = initial.toUpperCase ();
 
          if (ll == ul){
 
            regex_name += initial;
 
          } else {
 
            regex_name += '[' + ll + ul + ']';
 
          }
 
        }
 
        return regex_name.replace (/[ _]/g, '[ _]').replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1');
 
      }
 
 
 
      fallback = fallback.toLowerCase();
 
      var canonical  = wgFormattedNamespaces["" + namespaceNumber].toLowerCase();
 
      var regexp    = create_regexp_str (canonical);
 
      if (fallback && canonical != fallback) regexp += '|' + create_regexp_str (fallback);
 
      for (var cat_name in wgNamespaceIds) {
 
        if (  typeof (cat_name) == 'string'
 
            && cat_name.toLowerCase () != canonical
 
            && cat_name.toLowerCase () != fallback
 
            && wgNamespaceIds[cat_name] == namespaceNumber)
 
        {
 
          regexp += '|' + create_regexp_str (cat_name);
 
        }
 
      }
 
      return regexp;
 
    }
 
 
 
    if (wgFormattedNamespaces['14']) {
 
      HotCat.category_canonical = wgFormattedNamespaces['14'];
 
      HotCat.category_regexp = autoLocalize (14, 'category');
 
    }
 
    if (wgFormattedNamespaces['10']) {
 
      HotCat.template_regexp = autoLocalize (10, 'template');
 
    }
 
  }
 
 
 
  // Utility functions. Yes, this duplicates some functionality that also exists in other places, but
 
  // to keep this whole stuff in a single file not depending on any other on-wiki Javascripts, we re-do
 
  // these few operations here.
 
  function bind (func, target) {
 
    var f = func, tgt = target;
 
    return function () { return f.apply (tgt, arguments); };
 
  }
 
  function make (arg, literal) {
 
    if (!arg) return null;
 
    return literal ? document.createTextNode (arg) : document.createElement (arg);
 
  }
 
  function param (name, uri) {
 
    if (typeof (uri) == 'undefined' || uri === null) uri = document.location.href;
 
    var re = new RegExp ('[&?]' + name + '=([^&#]*)');
 
    var m = re.exec (uri);
 
    if (m && m.length > 1) return decodeURIComponent(m[1]);
 
    return null;
 
  }
 
  function title (href) {
 
    if (!href) return null;
 
    var script = wgScript + '?';
 
    if (href.indexOf (script) === 0 || href.indexOf (wgServer + script) === 0 || wgServer.substring(0, 2) == '//' && href.indexOf (document.location.protocol + wgServer + script) === 0) {
 
      // href="/w/index.php?title=..."
 
      return param ('title', href);
 
    } else {
 
      // href="/wiki/..."
 
      var prefix = wgArticlePath.replace ('$1', "");
 
      if (href.indexOf (prefix) != 0) prefix = wgServer + prefix; // Fully expanded URL?
 
      if (href.indexOf (prefix) != 0 && prefix.substring(0, 2) == '//') prefix = document.location.protocol + prefix; // Protocol-relative wgServer?
 
      if (href.indexOf (prefix) === 0)
 
        return decodeURIComponent (href.substring (prefix.length));
 
    }
 
    return null;
 
  }
 
  function hasClass (elem, name) {
 
    return (' ' + elem.className + ' ').indexOf (' ' + name + ' ') >= 0;
 
  }
 
  function capitalize (str) {
 
    if (!str || str.length === 0) return str;
 
    return str.substr(0, 1).toUpperCase() + str.substr (1);
 
  }
 
  function wikiPagePath (pageName) {
 
    // Note: do not simply use encodeURI, it doesn't encode '&', which might break if wgArticlePath actually has the $1 in
 
    // a query parameter.
 
    return wgArticlePath.replace('$1', encodeURIComponent (pageName).replace(/%3A/g, ':').replace(/%2F/g, '/'));
 
  }
 
 
 
  // Text modification
 
 
 
  var findCatsRE =
 
    new RegExp ('\\[\\[\\s*(?:' + HotCat.category_regexp + ')\\s*:[^\\]]+\\]\\]', 'g');
 
 
 
  function replaceByBlanks (match) {
 
    return match.replace(/(\s|\S)/g, ' '); // /./ doesn't match linebreaks. /(\s|\S)/ does.
 
  }
 
 
 
  function find_category (wikitext, category, once) {
 
    var cat_regex = null;
 
    if(HotCat.template_categories[category]){
 
      cat_regex = new RegExp ('\\{\\{\\s*(' + HotCat.template_regexp + '(?=\\s*:))?\\s*'
 
                              + '(?:' + HotCat.template_categories[category] + ')'
 
                              + '\\s*(\\|.*?)?\\}\\}', 'g'
 
                            );
 
    } else {
 
      var cat_name  = category.replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1');
 
      var initial  = cat_name.substr (0, 1);
 
      cat_regex = new RegExp ('\\[\\[\\s*(' + HotCat.category_regexp + ')\\s*:\\s*'
 
                              + (initial == '\\' || !HotCat.capitalizePageNames
 
                                ? initial
 
                                : '[' + initial.toUpperCase() + initial.toLowerCase() + ']')
 
                              + cat_name.substring (1).replace (/[ _]/g, '[ _]')
 
                              + '\\s*(\\|.*?)?\\]\\]', 'g'
 
                            );
 
    }
 
    if (once) return cat_regex.exec (wikitext);
 
    var copiedtext = wikitext.replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks)
 
                            .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks);
 
    var result = [];
 
    var curr_match = null;
 
    while ((curr_match = cat_regex.exec (copiedtext)) != null) {
 
      result.push ({match : curr_match});
 
    }
 
    result.re = cat_regex;
 
    return result; // An array containing all matches, with positions, in result[i].match
 
  }
 
 
 
  var interlanguageRE = null;
 
 
 
  function change_category (wikitext, toRemove, toAdd, key, is_hidden) {
 
 
 
    function find_insertionpoint (wikitext) {       
 
      var copiedtext = wikitext.replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks)
 
                              .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks);
 
      // Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element".
 
  var noinc = /<\/noinclude>/ig
 
      var index = -1;
 
      findCatsRE.lastIndex = 0;
 
      while (findCatsRE.exec(copiedtext) != null) index = findCatsRE.lastIndex;
 
  if (wgNamespaceNumber == 10)
 
    {
 
    while( noinc.exec(wikitext) != null ) index = noinc.lastIndex;
 
    return index;
 
    }
 
      if (index < 0) {
 
        // Find the index of the first interlanguage link...
 
        var match = null;
 
        if (!interlanguageRE) {
 
          // Approximation without API: interlanguage links start with 2 to 3 lower case letters, optionally followed by
 
          // a sequence of groups consisting of a dash followed by one or more lower case letters. Exceptions are "simple"
 
          // and "tokipona".
 
          match = /((^|\n\r?)(\[\[\s*(([a-z]{2,3}(-[a-z]+)*)|simple|tokipona)\s*:[^\]]+\]\]\s*))+$/.exec (copiedtext);
 
        } else {
 
          match = interlanguageRE.exec(copiedtext);
 
        }
 
        if (match) index = match.index;
 
        return {idx : index, onCat : false};
 
      }
 
      return {idx : index, onCat : index >= 0};
 
    }
 
 
 
    var summary  = [];
 
    var nameSpace = HotCat.category_canonical;
 
    var cat_point = -1; // Position of removed category;
 
 
 
    if (key) key = '|' + key;
 
    var keyChange = (toRemove && toAdd && toRemove == toAdd && toAdd.length > 0);
 
    if (toRemove && toRemove.length > 0) {
 
      var matches = find_category (wikitext, toRemove);
 
      if (!matches || matches.length === 0) {
 
        return {text: wikitext, 'summary': summary, error: HotCat.messages.cat_notFound.replace (/\$1/g, toRemove)};
 
      } else {
 
        var before = wikitext.substring (0, matches[0].match.index);
 
        var after  = wikitext.substring (matches[0].match.index + matches[0].match[0].length);
 
        if (matches.length > 1) {
 
          // Remove all occurrences in after
 
          matches.re.lastIndex = 0;
 
          after = after.replace (matches.re, "");
 
        }
 
        if (toAdd) {
 
          nameSpace = matches[0].match[1] || nameSpace;
 
          if (key == null) key = matches[0].match[2]; // Remember the category key, if any.
 
        }
 
        // Remove whitespace (properly): strip whitespace, but only up to the next line feed.
 
        // If we then have two linefeeds in a row, remove one. Otherwise, if we have two non-
 
        // whitespace characters, insert a blank.
 
        var i = before.length - 1;
 
        while (i >= 0 && before.charAt (i) != '\n' && before.substr (i, 1).search (/\s/) >= 0) i--;
 
        var j = 0;
 
        while (j < after.length && after.charAt (j) != '\n' && after.substr (j, 1).search (/\s/) >= 0)
 
          j++;
 
        if (i >= 0 && before.charAt (i) == '\n' && (after.length === 0 || j < after.length && after.charAt (j) == '\n'))
 
          i--;
 
        if (i >= 0) before = before.substring (0, i+1); else before = "";
 
        if (j < after.length) after = after.substring (j); else after = "";
 
        if (before.length > 0 && before.substring (before.length - 1).search (/\S/) >= 0
 
            && after.length > 0 && after.substr (0, 1).search (/\S/) >= 0)
 
          before += ' ';
 
        cat_point = before.length;
 
        wikitext = before + after;
 
        if (!keyChange) {
 
          if(HotCat.template_categories[toRemove]) {
 
            summary.push (HotCat.messages.template_removed.replace (/\$1/g, toRemove));
 
          } else {   
 
            summary.push (HotCat.messages.cat_removed.replace (/\$1/g, toRemove));
 
          }
 
        }
 
      }
 
    }
 
    if (toAdd && toAdd.length > 0) {
 
      var matches = find_category (wikitext, toAdd);
 
      if (matches && matches.length > 0) {
 
        return {text: wikitext, 'summary': summary, error : HotCat.messages.cat_exists.replace (/\$1/g, toAdd)};
 
      } else {
 
        var onCat = false;
 
        if (cat_point < 0) {
 
          var point = find_insertionpoint (wikitext);
 
          cat_point = point.idx;
 
          onCat = point.onCat;
 
        } else {
 
          onCat = true;
 
        }
 
      if (wgNamespaceNumber == 10 && (key=="" || key == null)) key = '|{{PAGENAME}}';
 
        var newcatstring = '[[' + nameSpace + ':' + toAdd + (key || "") + ']]';
 
      if (wgNamespaceNumber == 10) newcatstring = '<noinclude>' + newcatstring + '\n</noinclude>';
 
        if (cat_point >= 0) {
 
          var suffix = wikitext.substring (cat_point);
 
          wikitext = wikitext.substring (0, cat_point) + (cat_point > 0 ? '\n' : "") + newcatstring + (!onCat ? '\n' : "");
 
          if (suffix.length > 0 && suffix.substr(0, 1) != '\n') {
 
            wikitext += '\n' + suffix;
 
          } else {
 
            wikitext += suffix;
 
          }
 
        } else {
 
          if (wikitext.length > 0 && wikitext.substr (wikitext.length - 1, 1) != '\n')
 
            wikitext += '\n';
 
          wikitext += '\n' + newcatstring;
 
        }
 
    if (wgNamespaceNumber == 10)
 
        {
 
//          wikitext = wikitext.replace(/<noinclude>(\s|\n)*<\/noinclude>/gi, '');                    // исключение пустых пар
 
          wikitext = wikitext.replace(/<\/noinclude>(\s|\n)*<noinclude>(\s|\n)*/gi, '\n');            // объединение смежных пар
 
          wikitext = wikitext.replace(/\s*<noinclude>((\n)\s+)?/gi, '<noinclude>$2');
 
          wikitext = wikitext.replace(/(\]\]|<noinclude>|^)(\s|\n)+\[\[(Category|Категория)/gi, '$1\n[[Категория');
 
          wikitext = wikitext.replace(/<noinclude>\s*([^\<]*?)$/i, '<noinclude>\n$1</noinclude>');  // закрытие последних непарных
 
          }
 
        if (keyChange) {
 
          var k = key || "";
 
          if (k.length > 0) k = k.substr (1);
 
          summary.push (HotCat.messages.cat_keychange.replace (/\$1/g, toAdd) + '"' + k + '"');
 
        } else {
 
          summary.push (HotCat.messages.cat_added.replace (/\$1/g, toAdd));
 
        }
 
        if (HotCat.uncat_regexp && !is_hidden) {
 
          var txt = wikitext.replace (HotCat.uncat_regexp, ""); // Remove "uncat" templates
 
          txt = txt.replace(/\{\{rq\s*(\|.*?)\}\}/ig, function(str, par){
 
                return '{{rq'+par.replace(/\|\s*cat(?!\w)/ig,'')+'}}' }
 
                ) // Remove "cat" option in rq
 
          txt = txt.replace(/\{\{rq(\|topic=[^\|\}]*)?\}\}\n?/gi,'') // Remove empty rq
 
          if (txt.length != wikitext.length) {
 
            wikitext = txt;
 
            summary.push (HotCat.messages.uncat_removed);
 
          }
 
        }
 
      }
 
    }
 
  if (wgNamespaceNumber == 10)
 
          wikitext = wikitext.replace(/<noinclude>\s*([^\<]*?)\s*<\/noinclude>\s*([^\<]*?)\s*<\/noinclude>/gi, '\n$1\n$2</noinclude>').replace(/<noinclude>\s*([^\<]*?)\s*<noinclude>\s*([^\<]*?)\s*<\/noinclude>/gi, '<noinclude>\n$1\n$2</noinclude>').replace(/\s\s+<\/noinclude>/gi,'\n</noinclude>');    // очистка вложенных пар после добавления и лишних переводов строк
 
    return {text: wikitext, 'summary': summary, error: null};
 
  }
 
 
 
  // The real HotCat UI
 
 
 
  function evtKeys (e) {
 
    e = e || window.event || window.Event; // W3C, IE, Netscape
 
    var code = 0;
 
    if (typeof (e.ctrlKey) != 'undefined') { // All modern browsers
 
      // Ctrl-click seems to be overloaded in FF/Mac (it opens a pop-up menu), so treat cmd-click
 
      // as a ctrl-click, too.
 
      if (e.ctrlKey || e.metaKey)  code |= 1;
 
      if (e.shiftKey) code |= 2;
 
    } else if (typeof (e.modifiers) != 'undefined') { // Netscape...
 
      if (e.modifiers & (Event.CONTROL_MASK | Event.META_MASK)) code |= 1;
 
      if (e.modifiers & Event.SHIFT_MASK) code |= 2;
 
    }
 
    return code;
 
  }
 
  function evtKill (e) {
 
    e = e || window.event || window.Event; // W3C, IE, Netscape
 
    if (typeof (e.preventDefault) != 'undefined') {
 
      e.preventDefault ();
 
      e.stopPropagation ();
 
    } else
 
      e.cancelBubble = true;
 
    return false;
 
  }
 
 
 
  var catLine      = null;
 
  var onUpload    = false;   
 
  var editors      = [];
 
 
 
  var commitButton = null;
 
  var commitForm  = null;
 
  var multiSpan    = null;
 
 
 
  var pageText    = null;
 
  var pageTime    = null;
 
  var pageWatched  = false;
 
  var watchCreate  = false;
 
  var watchEdit    = false;
 
  var minorEdits  = false;
 
  var editToken    = null;
 
 
 
  var is_rtl      = false;
 
  var serverTime  = null;
 
 
 
  var newDOM      = false; // true if MediaWiki serves the new UL-LI DOM for categories
 
 
 
  function setMultiInput () {
 
    if (commitButton || onUpload) return;
 
    commitButton = make ('input');
 
    commitButton.type  = 'button';
 
    commitButton.value = HotCat.messages.commit;
 
    commitButton.onclick = multiSubmit;
 
    if (multiSpan) {
 
      multiSpan.parentNode.replaceChild (commitButton, multiSpan);
 
    } else {
 
      catLine.appendChild (commitButton);
 
    }
 
  }
 
 
 
  function checkMultiInput () {
 
    if (!commitButton) return;
 
    var has_changes = false;
 
    for (var i = 0; i < editors.length; i++) {
 
      if (editors[i].state != CategoryEditor.UNCHANGED) {
 
        has_changes = true;
 
        break;
 
      }
 
    }
 
    commitButton.disabled = !has_changes;
 
  }
 
 
 
  function currentTimestamp () {
 
    var now = new Date();
 
    var ts  = "" + now.getUTCFullYear();
 
    function two (s) { return s.substr (s.length - 2); }
 
    ts = ts
 
      + two ('0' + (now.getUTCMonth() + 1))
 
      + two ('0' + now.getUTCDate())
 
      + two ('00' + now.getUTCHours())
 
      + two ('00' + now.getUTCMinutes())
 
      + two ('00' + now.getUTCSeconds());
 
    return ts;
 
  }
 
 
 
  function initiateEdit (doEdit, failure) {
 
    // Must use Ajax here to get the user options and the edit token.
 
    var request = sajax_init_object ();
 
    var uri    = wgServer + wgScriptPath
 
                + '/api.php?format=json&action=query&titles=' + encodeURIComponent (wgPageName)
 
                + '&prop=info%7Crevisions%7Clanglinks&inprop=watched&intoken=edit&rvprop=content%7Ctimestamp&lllimit=500'
 
                + '&rvlimit=1&rvstartid=' + wgCurRevisionId
 
                + '&meta=siteinfo%7Cuserinfo&uiprop=options';
 
    request.open ('GET', armorUri(uri), true);
 
    request.onreadystatechange = function () {
 
      if (request.readyState != 4) return;
 
      if (request.status != 200 || !request.responseText || !(/^\s*\{/.test(request.responseText))) {
 
        failure(request.status + ' ' + request.statusText);
 
        return;
 
      }
 
      setPage (eval ('(' + request.responseText + ')'));
 
      doEdit (failure);
 
    };
 
    request.setRequestHeader ('Pragma', 'cache=yes');
 
    request.setRequestHeader ('Cache-Control', 'no-transform');
 
    request.send (null);
 
  }
 
 
 
  function performChanges (failure, singleEditor) {
 
    if (pageText === null) {
 
      failure (HotCat.messages.multi_error);
 
      return;
 
    }
 
    // Create a form and submit it. We don't use the edit API (api.php?action=edit) because
 
    // (a) sensibly reporting back errors like edit conflicts is always a hassle, and
 
    // (b) we want to show a diff for multi-edits anyway.
 
    // Using the form, we can do (b) and we get (a) for free. And, of course, using the form
 
    // automatically reloads the page with the updated categories on a successful submit, which
 
    // we would have to do explicitly if we used the edit API.
 
    if (!commitForm) {
 
      var formContainer = make ('div');
 
      formContainer.style.display = 'none';
 
      document.body.appendChild (formContainer);
 
      var formText =
 
          '<form method="post" enctype="multipart/form-data" action="'
 
        + wgScript + '?title=' + encodeURIComponent (wgPageName)
 
        + '&action=edit">'
 
        + '<input type="hidden" name="wpTextbox1" />'
 
        + '<input type="hidden" name="wpSummary" value="" />'
 
        + '<input type="checkbox" name="wpMinoredit" value="1" />'
 
        + '<input type="checkbox" name="wpWatchthis" value="1" />'
 
        + '<input type="hidden" name="wpAutoSummary" value="" />'
 
        + '<input type="hidden" name="wpEdittime" />'
 
        + '<input type="hidden" name="wpStarttime" />';
 
      if (singleEditor && !singleEditor.noCommit && !HotCat.no_autocommit && editToken) {
 
        formText += '<input type="hidden" name="wpEditToken" value="' + editToken + '" />';
 
        formText += '<input type="hidden" name="wpSave" value="wpSave" />';
 
      } else {
 
        formText += '<input type="hidden" name="wpDiff" value="wpDiff" />';
 
      }
 
      formText += '</form>';
 
      formContainer.innerHTML = formText;
 
      commitForm = formContainer.firstChild;
 
    }
 
    var result = { text : pageText };
 
    var changed = [], added = [], deleted = [], changes = 0;
 
    var toEdit = !!singleEditor ? [singleEditor] : editors;
 
    var error = null;
 
    for (var i=0; i < toEdit.length; i++) {
 
      if (toEdit[i].state == CategoryEditor.CHANGED) {
 
        result = change_category (
 
            result.text
 
          , toEdit[i].originalCategory
 
          , toEdit[i].currentCategory
 
          , toEdit[i].currentKey
 
          , toEdit[i].currentHidden
 
        );
 
        if (!result.error) {
 
          changes++;
 
          if (!toEdit[i].originalCategory || toEdit[i].originalCategory.length === 0) {
 
            added.push (toEdit[i].currentCategory);
 
          } else {
 
            changed.push ({from : toEdit[i].originalCategory, to : toEdit[i].currentCategory});
 
          }
 
        } else if (error === null) {
 
          error = result.error;
 
        }
 
      } else if (  toEdit[i].state == CategoryEditor.DELETED
 
                && toEdit[i].originalCategory
 
                && toEdit[i].originalCategory.length > 0)
 
      {
 
        result = change_category (result.text, toEdit[i].originalCategory, null, null, false);
 
        if (!result.error) {
 
          changes++;
 
          deleted.push (toEdit[i].originalCategory);
 
        } else if (error === null) {
 
          error = result.error;
 
        }
 
      }
 
    }
 
    if (error !== null) { // Do not commit if there were errors
 
      if (commitForm.wpSave) {
 
        var action = commitForm.wpSave;
 
        action.name = 'wpDiff';
 
        action.value = 'wpDiff';
 
      }
 
    }
 
    if (changes === 0 && !singleEditor) return;
 
    // Fill in the form and submit it
 
    commitForm.wpAutoSummary.value = 'd41d8cd98f00b204e9800998ecf8427e'; // MD5 hash of the empty string
 
    commitForm.wpMinoredit.checked = minorEdits;
 
    commitForm.wpWatchthis.checked = wgArticleId == 0 && watchCreate || watchEdit || pageWatched;
 
    if (wgArticleId > 0 || !!singleEditor) {
 
      if (changes == 1) {
 
        if (result.summary && result.summary.length > 0)
 
          commitForm.wpSummary.value = HotCat.messages.prefix + result.summary.join (HotCat.messages.separator) + HotCat.messages.using;
 
        commitForm.wpMinoredit.checked = HotCat.single_minor || minorEdits;
 
      } else if (changes > 1) {
 
        var summary = [];
 
        var shortSummary = [];
 
        // Deleted
 
        for (var i=0; i < deleted.length; i++) {
 
          summary.push ('−[[' + HotCat.category_canonical + ':' + deleted[i] + ']]');
 
        }
 
        if (deleted.length == 1)
 
          shortSummary.push ('−[[' + HotCat.category_canonical + ':' + deleted[0] + ']]');
 
        else if (deleted.length > 1)
 
          shortSummary.push ('− ' + HotCat.messages.multi_change.replace ('$1', "" + deleted.length));
 
        // Added
 
        for (var i=0; i < added.length; i++) {
 
          summary.push ('+[[' + HotCat.category_canonical + ':' + added[i] + ']]');
 
        }
 
        if (added.length == 1)
 
          shortSummary.push ('+[[' + HotCat.category_canonical + ':' + added[0] + ']]');
 
        else if (added.length > 1)
 
          shortSummary.push ('+ ' + HotCat.messages.multi_change.replace ('$1', "" + added.length));
 
        // Changed
 
        var arrow = "]]→[[";
 
        if (is_rtl) arrow = "]]←[[";
 
        for (var i=0; i < changed.length; i++) {
 
          if (changed[i].from != changed[i].to) {
 
            summary.push ('±[[' + HotCat.category_canonical + ':' + changed[i].from + arrow
 
                        + HotCat.category_canonical + ':' + changed[i].to + ']]');
 
          } else {
 
            summary.push ('±[[' + HotCat.category_canonical + ':' + changed[i].from + ']]');
 
          }
 
        }
 
        if (changed.length == 1) {
 
          if (changed[0].from != changed[0].to) {
 
            shortSummary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + arrow
 
                        + HotCat.category_canonical + ':' + changed[0].to + ']]');
 
          } else {
 
            shortSummary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + ']]');
 
          }
 
        } else if (changed.length > 1) {
 
          shortSummary.push ('± ' + HotCat.messages.multi_change.replace ('$1', "" + changed.length));
 
        }
 
        if (summary.length > 0) {
 
          summary = summary.join (HotCat.messages.separator);
 
          if (summary.length > 200 - HotCat.messages.prefix.length - HotCat.messages.using.length) {
 
            summary = shortSummary.join (HotCat.messages.separator);
 
          }
 
          commitForm.wpSummary.value = HotCat.messages.prefix + summary + HotCat.messages.using;
 
        }
 
      }
 
    }
 
    commitForm.wpTextbox1.value = result.text;
 
    commitForm.wpStarttime.value = serverTime || currentTimestamp ();
 
    commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value;
 
    commitForm.submit();
 
  }
 
 
 
  function resolveMulti (toResolve, callback) {
 
    for (var i = 0; i < toResolve.length; i++) {
 
      toResolve[i].dab = null;
 
      toResolve[i].dabInput = toResolve[i].lastInput;
 
    }
 
    if (noSuggestions) {
 
      callback (toResolve);
 
      return;
 
    }
 
    var request = sajax_init_object ();
 
    if (!request) {
 
      noSuggestions = true;
 
      callback (toResolve);
 
      return;
 
    }
 
    var url = armorUri(wgServer + wgScriptPath + '/api.php');
 
    // Use %7C instead of |, otherwise Konqueror insists on re-encoding the arguments, resulting in doubly encoded
 
    // category names. (That is a bug in Konqueror. Other browsers don't have this problem.)
 
    var args = 'action=query&prop=info%7Clinks%7Ccategories%7Ccategoryinfo&plnamespace=14'
 
            + '&pllimit=' + (toResolve.length * 10)
 
            + '&cllimit=' + (toResolve.length * 10)
 
            + '&format=json&titles=';
 
    for (var i = 0; i < toResolve.length; i++) {
 
      args += encodeURIComponent ('Category:' + toResolve[i].dabInput);
 
      if (i+1 < toResolve.length) args += '%7C';
 
    }
 
    if (url.length + args.length + 1 > 2000) { // Lowest common denominator: IE has a URI length limit of 2083
 
      request.open ('POST', url, true);
 
      request.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded');
 
    } else {
 
      url += '?' + args; args = null;
 
      request.open ('GET', url, true);
 
    }
 
    request.onreadystatechange =
 
      function () {
 
        if (request.readyState != 4) return;
 
        if (request.status != 200 || !request.responseText || !/^\s*\{/.test (request.responseText)) {
 
          callback (toResolve);
 
          return;
 
        }
 
        resolveRedirects (toResolve, eval ('(' + request.responseText + ')'));
 
        callback (toResolve);
 
      };
 
    request.setRequestHeader ('Pragma', 'cache=yes');
 
    request.setRequestHeader ('Cache-Control', 'no-transform');
 
    request.send (args);       
 
  }
 
   
 
  function resolveOne (page, toResolve) {
 
    var cats    = page.categories;
 
    var lks      = page.links;
 
    var is_dab  = false;
 
    var is_redir = typeof (page.redirect) == 'string'; // Hard redirect?
 
    var is_hidden = page.categoryinfo && typeof (page.categoryinfo.hidden) == 'string';
 
    for (var j = 0; j < toResolve.length; j++) {
 
      if (toResolve[j].dabInput != page.title.substring (page.title.indexOf (':') + 1)) continue;
 
      toResolve[j].currentHidden = is_hidden;
 
    }
 
    if (!is_redir && cats && (HotCat.disambig_category || HotCat.redir_category)) {
 
      for (var c = 0; c < cats.length; c++) {
 
        var cat = cats[c]['title'];
 
        // Strip namespace prefix
 
        if (cat) {
 
          cat = cat.substring (cat.indexOf (':') + 1).replace(/_/g, ' ');
 
          if (cat == HotCat.disambig_category) {
 
            is_dab = true; break;
 
          } else if (cat == HotCat.redir_category) {
 
            is_redir = true; break;
 
          }
 
        }
 
      }
 
    }
 
    if (!is_redir && !is_dab) return;
 
    if (!lks || lks.length === 0) return;
 
    var titles = [];
 
    for (var i = 0; i < lks.length; i++) {
 
      if (  lks[i]['ns'] == 14                            // Category namespace
 
          && lks[i]['title'] && lks[i]['title'].length > 0) // Name not empty
 
      {
 
        // Internal link to existing thingy. Extract the page name and remove the namespace.
 
        var match = lks[i]['title'];
 
        titles.push (match.substring (match.indexOf (':') + 1));
 
        if (is_redir) break;
 
      }
 
    }
 
    for (var j = 0; j < toResolve.length; j++) {
 
      if (toResolve[j].dabInput != page.title.substring (page.title.indexOf (':') + 1)) continue;
 
      if (titles.length > 1) {
 
        toResolve[j].dab = titles;
 
      } else {
 
        toResolve[j].inputExists = true; // Might actually be wrong...
 
        toResolve[j].icon.src = HotCat.existsYes;
 
        toResolve[j].text.value =
 
          titles[0] + (toResolve[j].currentKey != null ? '|' + toResolve[j].currentKey : "");
 
      }
 
    }
 
  }
 
 
 
  function resolveRedirects (toResolve, params) {
 
    if (!params || !params.query || !params.query.pages) return;     
 
    for (var p in params.query.pages) resolveOne (params.query.pages[p], toResolve);
 
  }
 
 
 
  function multiSubmit () {
 
    var toResolve = [];
 
    for (var i = 0; i < editors.length; i++) {
 
      if (editors[i].state == CategoryEditor.CHANGE_PENDING || editors[i].state == CategoryEditor.OPEN)
 
        toResolve.push (editors[i]);
 
    }
 
    if (toResolve.length === 0) {
 
      initiateEdit (function (failure) {performChanges (failure);}, function (msg) {alert (msg);});
 
      return;
 
    }
 
    resolveMulti (
 
        toResolve
 
      , function (resolved) {
 
          var firstDab = null;
 
          var dontChange = false;
 
          for (var i = 0; i < resolved.length; i++) {
 
            if (resolved[i].lastInput != resolved[i].dabInput) {
 
              // We didn't disable all the open editors, but we did asynchronous calls. It is
 
              // theoretically possible that the user changed something...
 
              dontChange = true;
 
            } else {
 
              if (resolved[i].dab) {
 
                if (!firstDab) firstDab = resolved[i];
 
              } else {
 
                if (resolved[i].acceptCheck(true)) resolved[i].commit();
 
              }
 
            }
 
          }
 
          if (firstDab) {
 
            CategoryEditor.makeActive (firstDab);
 
          } else if (!dontChange) {
 
            initiateEdit (function (failure) {performChanges (failure);}, function (msg) {alert (msg);});
 
          }
 
        }
 
    );
 
  }
 
 
 
  var cat_prefix = null;
 
  var noSuggestions = false;
 
  var suggestionEngines = {
 
    opensearch :
 
      { uri    : '/api.php?format=json&action=opensearch&namespace=14&limit=30&search=Category:$1' // $1 = search term
 
      ,handler : // Function to convert result of uri into an array of category names
 
          function (responseText, queryKey) {
 
            if (!/^\s*\[/.test (responseText)) return null;
 
            var queryResult = eval ('(' + responseText + ')');
 
            if (  queryResult != null && queryResult.length == 2
 
                && queryResult[0].toLowerCase() == 'category:' + queryKey.toLowerCase()
 
              )
 
            {
 
              var titles = queryResult[1];
 
              if (!cat_prefix) cat_prefix = new RegExp ('^(' + HotCat.category_regexp + ':)');
 
              for (var i = 0; i < titles.length; i++) {
 
                cat_prefix.lastIndex = 0;
 
                var m = cat_prefix.exec (titles[i]);
 
                if (m && m.length > 1) {
 
                  titles[i] = titles[i].substring (titles[i].indexOf (':') + 1); // rm namespace
 
                } else {
 
                  titles.splice (i, 1); // Nope, it's not a category after all.
 
                  i--;
 
                }
 
              }
 
              return titles;
 
            }
 
            return null;     
 
          }
 
      }
 
    ,internalsearch :
 
      { uri    : '/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1'
 
      ,handler :
 
          function (responseText, queryKey) {
 
            if (!/^\s*\{/.test (responseText)) return null;
 
            var queryResult = eval ('(' + responseText + ')');
 
            if (queryResult && queryResult.query && queryResult.query.allpages) {
 
              var titles = queryResult.query.allpages;
 
              var key    = queryKey.toLowerCase();
 
              for (var i = 0; i < titles.length; i++) {
 
                titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace
 
                if (titles[i].toLowerCase().indexOf (key) != 0) {
 
                  titles.splice (i, 1); // Doesn't start with the query key
 
                  i--;
 
                }
 
              }
 
              return titles;
 
            }
 
            return null;
 
          }
 
      }
 
    ,subcategories :
 
      { uri    : '/api.php?format=json&action=query&list=categorymembers&cmnamespace=14&cmlimit=max&cmtitle=Category:$1'
 
      ,handler :
 
          function (responseText, queryKey) {
 
            if (!/^\s*\{/.test (responseText)) return null;
 
            var queryResult = eval ('(' + responseText + ')');
 
            if (queryResult && queryResult.query && queryResult.query.categorymembers) {
 
              var titles = queryResult.query.categorymembers;
 
              for (var i = 0; i < titles.length; i++) {
 
                titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace
 
              }
 
              return titles;
 
            }
 
            return null;
 
          }
 
      }
 
  ,parentcategories :
 
      { uri    : '/api.php?format=json&action=query&prop=categories&titles=Category:$1&cllimit=max'
 
      ,handler :
 
          function (responseText, queryKey) {
 
            if (!/^\s*\{/.test (responseText)) return null;
 
            var queryResult = eval ('(' + responseText + ')');
 
            if (queryResult && queryResult.query && queryResult.query.pages) {
 
              for (var p in queryResult.query.pages) {
 
                if (queryResult.query.pages[p].categories) {
 
                  var titles = queryResult.query.pages[p].categories;
 
                  for (var i = 0; i < titles.length; i++) {
 
                    titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace
 
                  }
 
                  return titles;
 
                }
 
              }
 
            }
 
            return null;
 
          }
 
      }
 
  };
 
 
 
  var suggestionConfigs = {
 
    searchindex : {name: 'Search index', engines: ['opensearch'], cache: {}, show: true, temp: false, noCompletion : false}
 
  ,pagelist    : {name: 'Page list', engines: ['internalsearch'], cache: {}, show: true, temp: false, noCompletion : false}
 
  ,combined    : {name: 'Combined search', engines: ['opensearch', 'internalsearch'], cache: {}, show: true, temp: false, noCompletion : false}
 
  ,subcat      : {name: 'Subcategories', engines: ['subcategories'], cache: {}, show: true, temp: true, noCompletion : true}
 
  ,parentcat  : {name: 'Parent categories', engines: ['parentcategories'], cache: {}, show: true, temp: true, noCompletion : true}
 
  };
 
 
 
  function CategoryEditor () { this.initialize.apply (this, arguments); };
 
  CategoryEditor.UNCHANGED      = 0;
 
  CategoryEditor.OPEN          = 1; // Open, but no input yet
 
  CategoryEditor.CHANGE_PENDING = 2; // Open, some input made
 
  CategoryEditor.CHANGED        = 3;
 
  CategoryEditor.DELETED        = 4;
 
 
 
  CategoryEditor.makeActive = function (toActivate) {
 
    for (var i = 0; i < editors.length; i++) {
 
      if (editors[i] != toActivate) editors[i].inactivate ();
 
    }
 
    toActivate.is_active = true;
 
    if (toActivate.dab) {
 
      toActivate.showSuggestions (toActivate.dab, false, null, null); // do autocompletion, no key, no engine selector
 
      toActivate.dab = null;
 
    }
 
  };
 
 
 
  CategoryEditor.prototype = {
 
   
 
    initialize : function (line, span, after, key, is_hidden) {
 
      // If a span is given, 'after' is the category title, otherwise it may be an element after which to
 
      // insert the new span. 'key' is likewise overloaded; if a span is given, it is the category key (if
 
      // known), otherwise it is a boolean indicating whether a bar shall be prepended.
 
      if (!span) {
 
        this.isAddCategory = true;
 
        // Create add span and append to catLinks
 
        this.originalCategory = "";
 
        this.originalKey = null;
 
        this.originalExists  = false;
 
        if (!newDOM) {
 
          span = make ('span');
 
          span.className = 'noprint';
 
          if (key) {
 
            span.appendChild (make (' | ', true));
 
            if (after) {
 
              after.parentNode.insertBefore (span, after.nextSibling);
 
              after = after.nextSibling;
 
            } else {
 
              line.appendChild (span);
 
            }
 
          } else if (line.firstChild) {
 
            span.appendChild (make (' ', true));
 
            line.appendChild (span);
 
          }
 
        }
 
        this.linkSpan = make ('span');
 
        this.linkSpan.className = 'noprint nopopups hotcatlink';
 
        var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this);
 
        lk.appendChild (make (HotCat.links.add, true)); lk.title = HotCat.tooltips.add;     
 
        this.linkSpan.appendChild (lk);
 
        span = make (newDOM ? 'li' : 'span');
 
        span.className = 'noprint';
 
        if (is_rtl) span.dir = 'rtl';
 
        span.appendChild (this.linkSpan);
 
        if (after)
 
          after.parentNode.insertBefore (span, after.nextSibling);
 
        else
 
          line.appendChild (span);
 
        this.normalLinks = null;
 
        this.undelLink = null;
 
        this.catLink = null;
 
      } else {
 
        if (is_rtl) span.dir = 'rtl';
 
        this.isAddCategory = false;
 
        this.catLink = span.firstChild;
 
        this.originalCategory = after;
 
        this.originalKey = (key && key.length > 1) ? key.substr(1) : null; // > 1 because it includes the leading bar
 
        this.originalExists  = !hasClass (this.catLink, 'new');
 
        // Create change and del links
 
        this.makeLinkSpan ();
 
        if (!this.originalExists && this.upDownLinks) this.upDownLinks.style.display = 'none';
 
        span.appendChild (this.linkSpan);
 
      }
 
      this.originalHidden    = is_hidden;
 
      this.line              = line;
 
      this.engine            = HotCat.suggestions;
 
      this.span              = span;
 
      this.currentCategory    = this.originalCategory;
 
      this.currentExists      = this.originalExists;
 
      this.currentHidden      = this.originalHidden;
 
      this.currentKey        = this.originalKey;
 
      this.state              = CategoryEditor.UNCHANGED;
 
      this.lastSavedState    = CategoryEditor.UNCHANGED;
 
      this.lastSavedCategory  = this.originalCategory;
 
      this.lastSavedKey      = this.originalKey;
 
      this.lastSavedExists    = this.originalExists;
 
      this.lastSavedHidden    = this.originalHidden;
 
      if (this.catLink && this.currentKey) {
 
        this.catLink.title = this.currentKey;
 
      }
 
      editors[editors.length] = this;
 
    },
 
   
 
    makeLinkSpan : function () {
 
      this.normalLinks = make ('span');
 
      var lk = null;
 
      if (this.originalCategory && this.originalCategory.length > 0) {
 
        lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.remove, this);
 
        lk.appendChild (make (HotCat.links.remove, true)); lk.title = HotCat.tooltips.remove;
 
        this.normalLinks.appendChild (make (' ', true));
 
        this.normalLinks.appendChild (lk);
 
      }
 
      if (!HotCat.template_categories[this.originalCategory]) {
 
        lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this);
 
        lk.appendChild (make (HotCat.links.change, true)); lk.title = HotCat.tooltips.change;
 
        this.normalLinks.appendChild (make (' ', true));
 
        this.normalLinks.appendChild (lk);
 
        if (!noSuggestions && HotCat.use_up_down) {
 
          this.upDownLinks = make ('span');
 
          lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.down, this);
 
          lk.appendChild (make (HotCat.links.down, true)); lk.title = HotCat.tooltips.down;
 
          this.upDownLinks.appendChild (make (' ', true));
 
          this.upDownLinks.appendChild (lk);
 
          lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.up, this);
 
          lk.appendChild (make (HotCat.links.up, true)); lk.title = HotCat.tooltips.up;
 
          this.upDownLinks.appendChild (make (' ', true));
 
          this.upDownLinks.appendChild (lk);
 
          this.normalLinks.appendChild (this.upDownLinks);
 
        }
 
      }
 
      this.linkSpan = make ('span');
 
      this.linkSpan.className = 'noprint nopopups hotcatlink';
 
      this.linkSpan.appendChild (this.normalLinks);
 
      this.undelLink = make ('span');
 
      this.undelLink.className = 'nopopups hotcatlink';
 
      this.undelLink.style.display = 'none';
 
      lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.restore, this);
 
      lk.appendChild (make (HotCat.links.restore, true)); lk.title = HotCat.tooltips.restore;
 
      this.undelLink.appendChild (make (' ', true));
 
      this.undelLink.appendChild (lk);
 
      this.linkSpan.appendChild (this.undelLink);
 
    },
 
   
 
    makeForm : function () {
 
      var form = make ('form');
 
      form.method = 'POST'; form.onsubmit = bind (this.accept, this);
 
      this.form = form;
 
     
 
      var text = make ('input'); text.type = 'text'; text.size = HotCat.editbox_width;
 
      if (!noSuggestions) {
 
        text.onkeyup =
 
          bind (
 
            function (evt) {
 
              evt = evt || window.event || window.Event; // W3C, IE, Netscape
 
              var key = evt.keyCode || 0;
 
              if (key == 38 || key == 40 || key == 33 || key == 34) { // Up and down arrows, page up/down
 
                // In case a browser doesn't generate keypress events for arrow keys...
 
                if (this.keyCount === 0) return this.processKey (evt);
 
              } else {
 
                if (key == 27) { // ESC
 
                  if (!this.resetKeySelection ()) {
 
                    // No undo of key selection: treat ESC as "cancel".
 
                    this.cancel ();
 
                    return;
 
                  }
 
                }
 
                // Also do this for ESC as a workaround for Firefox bug 524360
 
                // https://bugzilla.mozilla.org/show_bug.cgi?id=524360
 
                var dont_autocomplete = (key == 8 || key == 46 || key == 27); // BS, DEL, ESC
 
                if (this.engine && suggestionConfigs[this.engine] && suggestionConfigs[this.engine].temp && !dont_autocomplete) {
 
                  this.engine = HotCat.suggestions; // Reset to a search upon input
 
                }
 
                this.state = CategoryEditor.CHANGE_PENDING;
 
                var self = this;
 
                window.setTimeout (function () {self.textchange (dont_autocomplete);}, HotCat.suggest_delay);
 
              }
 
              return true;
 
            }
 
          ,this
 
          );
 
        text.onkeydown =
 
          bind (
 
            function (evt) {
 
              evt = evt || window.event || window.Event; // W3C, IE, Netscape
 
              this.lastKey = evt.keyCode || 0;
 
              this.keyCount = 0;
 
              // Handle return explicitly, to override the default form submission to be able to check for ctrl
 
              if (this.lastKey == 13) this.accept (evt);
 
              // Inhibit default behavior of ESC (revert to last real input in FF: we do that ourselves)
 
              if (this.lastKey == 27) return evtKill (evt);
 
              return true;
 
            }
 
          ,this
 
          );
 
        // And handle continued pressing of arrow keys
 
        text.onkeypress = bind (function (evt) {this.keyCount++; return this.processKey (evt);}, this);
 
      }
 
      text.onfocus = bind (function () { CategoryEditor.makeActive (this); }, this);
 
      this.text = text;
 
     
 
      this.icon = make ('img');
 
     
 
      var list = null;
 
      if (!noSuggestions) {
 
        list = make ('select');
 
        list.onclick    = bind ( function (e) { if (this.highlightSuggestion (0)) this.textchange (false, true); }, this);
 
        list.ondblclick = bind (function (e) { if (this.highlightSuggestion (0)) this.accept (e); }, this);
 
        list.onchange = bind (function (e) { this.highlightSuggestion (0); this.text.focus(); }, this);
 
        list.onkeyup =
 
          bind (
 
            function (evt) {
 
              evt = evt || window.event || window.Event; // W3C, IE, Netscape
 
              if (evt.keyCode == 27) {
 
                this.resetKeySelection ();
 
                this.text.focus();
 
                var self = this;
 
                window.setTimeout (function () {self.textchange (true);}, HotCat.suggest_delay);
 
              } else if (evt.keyCode == 13) {
 
                this.accept (evt);
 
              }
 
            }
 
          ,this
 
          );
 
        if (!HotCat.fixed_search) {
 
          var engineSelector = make ('select');
 
          for (var key in suggestionConfigs) {
 
            if (suggestionConfigs[key].show) {
 
              var opt = make ('option');
 
              opt.value = key;
 
              if (key == this.engine) opt.selected = true;
 
              opt.appendChild (make (suggestionConfigs[key].name, true));
 
              engineSelector.appendChild (opt);
 
            }
 
          }
 
          engineSelector.onchange = bind (
 
            function () {
 
              this.engine = this.engineSelector.options[this.engineSelector.selectedIndex].value;
 
              this.textchange (true, true); // Don't autocomplete, force re-display of list
 
            }
 
          ,this
 
          );
 
          this.engineSelector = engineSelector;
 
        }
 
      }
 
      this.list = list;
 
     
 
      function button_label (id, defaultText) {
 
        var label = null;
 
        if (  onUpload
 
            && typeof (UFUI) != 'undefined'
 
            && typeof (UIElements) != 'undefined'
 
            && typeof (UFUI.getLabel) == 'function') {
 
          try {
 
            label = UFUI.getLabel (id, true);
 
            // Extract the plain text. IE doesn't know that Node.TEXT_NODE == 3
 
            while (label && label.nodeType != 3) label = label.firstChild;
 
          } catch (ex) {
 
            label = null;
 
          }
 
        }
 
        if (!label || !label.data) return defaultText;
 
        return label.data;   
 
      }
 
 
 
      // Do not use type 'submit'; we cannot detect modifier keys if we do
 
      var OK = make ('input'); OK.type = 'button';
 
      OK.value = button_label ('wpOkUploadLbl', HotCat.messages.ok);
 
      OK.onclick = bind (this.accept, this);
 
      this.ok = OK;
 
       
 
      var cancel = make ('input'); cancel.type = 'button';
 
      cancel.value = button_label ('wpCancelUploadLbl', HotCat.messages.cancel);
 
      cancel.onclick = bind (this.cancel, this);
 
      this.cancelButton = cancel;
 
       
 
      if (list) form.appendChild (list);
 
      if (this.engineSelector) form.appendChild (this.engineSelector);
 
      form.appendChild (text);
 
      if (!noSuggestions) form.appendChild (this.icon);
 
      form.appendChild (OK);
 
      form.appendChild (cancel);
 
      form.style.display = 'none';
 
      this.span.appendChild (form);
 
    },
 
 
 
    display : function (evt) {
 
      if (this.isAddCategory && !onUpload) {
 
        var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one
 
      }
 
      if (!commitButton && !onUpload) {
 
        for (var i = 0; i < editors.length; i++) {
 
          if (editors[i].state != CategoryEditor.UNCHANGED) {
 
            setMultiInput();
 
            break;
 
          }
 
        }
 
      }
 
      if (!this.form) {
 
        this.makeForm ();
 
      }
 
      if (this.list) this.list.style.display = 'none';
 
      if (this.engineSelector) this.engineSelector.style.display = 'none';
 
      this.currentCategory = this.lastSavedCategory;
 
      this.currentExists  = this.lastSavedExists;
 
      this.currentHidden  = this.lastSavedHidden;
 
      this.currentKey      = this.lastSavedKey;
 
      this.icon.src = this.currentExists ? HotCat.existsYes : HotCat.existsNo;
 
      this.text.value = this.currentCategory + (this.currentKey != null ? '|' + this.currentKey : "");
 
      this.originalState = this.state;
 
      this.lastInput    = this.currentCategory;
 
      this.inputExists  = this.currentExists;
 
      this.state        = this.state == CategoryEditor.UNCHANGED ? CategoryEditor.OPEN : CategoryEditor.CHANGE_PENDING;
 
      // Display the form
 
      if (this.catLink) this.catLink.style.display = 'none';
 
      this.linkSpan.style.display = 'none';
 
      this.form.style.display = 'inline';
 
      this.ok.disabled = false;
 
      CategoryEditor.makeActive (this);
 
      // Kill the event before focussing, otherwise IE will kill the onfocus event!
 
      var result = evtKill (evt);
 
      this.text.focus();
 
      this.text.readOnly = false;
 
      checkMultiInput ();
 
      return result;
 
    },
 
 
 
    open : function (evt) {
 
      var result = this.display (evt);
 
      var v = this.lastSavedCategory;
 
      if (v.length === 0) return result;
 
 
 
      if (this.engine && suggestionConfigs[this.engine].temp) this.engine = HotCat.suggestions;
 
      this.textchange (false, true); // do autocompletion, force display of suggestions
 
      return result;
 
    },
 
 
 
    down : function (evt) {
 
      var result = this.display (evt);
 
      var v = this.lastSavedCategory;
 
      if (v.length === 0) return result;
 
 
 
      this.text.readOnly = true; // This request may be very slow!
 
      this.engine = 'subcat';
 
      this.textchange (false, true);
 
 
 
      return result;
 
    },
 
 
 
    up : function (evt) {
 
      var result = this.display (evt);
 
      var v = this.lastSavedCategory;
 
      if (v.length === 0) return result;
 
 
 
      this.engine = 'parentcat';
 
      this.textchange (false, true);
 
 
 
      return result;
 
    },
 
 
 
    cancel : function () {
 
      if (this.isAddCategory && !onUpload) {
 
        this.removeEditor(); // We added a new adder when opening
 
        return;
 
      }
 
      // Close, re-display link
 
      if (this.list) this.list.style.display = 'none';
 
      if (this.engineSelector) this.engineSelector.style.display = 'none';
 
      this.form.style.display = 'none';
 
      if (this.catLink) this.catLink.style.display = "";
 
      this.linkSpan.style.display = "";
 
      this.state = this.originalState;
 
      this.currentCategory = this.lastSavedCategory;
 
      this.currentKey      = this.lastSavedKey;
 
      this.currentExists  = this.lastSavedExists;
 
      this.currentHidden  = this.lastSavedHidden;
 
      if (this.catLink) {
 
        if (this.currentkey && this.currentKey.length > 0) {
 
          this.catLink.title = this.currentKey;
 
        } else {
 
          this.catLink.title = null;
 
        }
 
      }
 
      if (this.state == CategoryEditor.UNCHANGED) {
 
        if (this.catLink) this.catLink.style.backgroundColor = 'transparent';
 
      } else {
 
        if (!onUpload) {
 
          try {
 
            this.catLink.style.backgroundColor = HotCat.bg_changed;
 
          } catch (ex) {}
 
        }
 
      }
 
      checkMultiInput ();
 
    },
 
 
 
    removeEditor : function () {
 
      if (!newDOM) {
 
        var next = this.span.nextSibling;
 
        if (next) next.parentNode.removeChild (next);
 
      }
 
      this.span.parentNode.removeChild (this.span);
 
      for (var i = 0; i < editors.length; i++) {
 
        if (editors[i] == this) {
 
          editors.splice (i, 1);
 
          break;
 
        }
 
      }
 
      checkMultiInput ();
 
      var self = this;
 
      window.setTimeout (function () {delete self;}, 10);
 
    },
 
 
 
    rollback : function (evt) {
 
      this.undoLink.parentNode.removeChild (this.undoLink);
 
      this.undoLink = null;
 
      this.currentCategory = this.originalCategory;
 
      this.currentKey = this.originalKey;
 
      this.currentExists = this.originalExists;
 
      this.currentHidden = this.originalHidden;
 
      this.lastSavedCategory = this.originalCategory;
 
      this.lastSavedKey = this.originalKey;
 
      this.lastSavedExists = this.originalExists;
 
      this.lastSavedHidden = this.originalHidden;
 
      this.state = CategoryEditor.UNCHANGED;
 
      if (!this.currentCategory || this.currentCategory.length === 0) {
 
        // It was a newly added category. Remove the whole editor.
 
        this.removeEditor();
 
      } else {
 
        // Redisplay the link...
 
        this.catLink.removeChild (this.catLink.firstChild);
 
        this.catLink.appendChild (make (this.currentCategory, true));
 
        this.catLink.href = wikiPagePath (HotCat.category_canonical + ':' + this.currentCategory);
 
        this.catLink.title = this.currentKey;
 
        this.catLink.className = this.currentExists ? "" : 'new';
 
        this.catLink.style.backgroundColor = 'transparent';
 
        if (this.upDownLinks) this.upDownLinks.style.display = this.currentExists ? "" : 'none';
 
        checkMultiInput ();
 
      }
 
      return evtKill (evt);
 
    },
 
 
 
    inactivate : function () {
 
      if (this.list) this.list.style.display = 'none';
 
      if (this.engineSelector) this.engineSelector.style.display = 'none';
 
      this.is_active = false;
 
    },
 
 
 
    acceptCheck : function (dontCheck) {
 
      this.sanitizeInput ();
 
      var value = this.text.value.split('|');
 
      var key  = null;
 
      if (value.length > 1) key = value[1];
 
      var v = value[0].replace(/_/g, ' ').replace(/^\s+|\s+$/g, "");
 
      if (HotCat.capitalizePageNames) v = capitalize (v);
 
      this.lastInput = v;
 
      if (v.length === 0) {
 
        this.cancel ();
 
        return false;
 
      }
 
      if (!dontCheck
 
          && (  wgNamespaceNumber == 14 && v == wgTitle
 
              || HotCat.blacklist != null && HotCat.blacklist.test(v))
 
        ) {
 
        this.cancel ();
 
        return false;
 
      }
 
      this.currentCategory = v;
 
      this.currentKey = key;
 
      this.currentExists = this.inputExists;
 
      return true;
 
    },
 
   
 
    accept : function (evt) {
 
      this.noCommit = (evtKeys (evt) & 1) != 0;
 
      var result = evtKill (evt);
 
      if (this.acceptCheck ()) {
 
        var toResolve = [this];
 
        var original  = this.currentCategory;
 
        resolveMulti (
 
            toResolve
 
          , function (resolved) {
 
              if (resolved[0].dab) {
 
                CategoryEditor.makeActive (resolved[0]);
 
              } else {
 
                if (resolved[0].acceptCheck(true)) {
 
                  resolved[0].commit (
 
                    (resolved[0].currentCategory != original)
 
                      ? HotCat.messages.cat_resolved.replace (/\$1/g, original)
 
                      : null
 
                  );
 
                }
 
              }
 
            }
 
        );
 
      }
 
      return result;
 
    },
 
 
 
    close : function () {
 
      if (!this.catLink) {
 
        // Create a catLink
 
        this.catLink = make ('a');
 
        this.catLink.appendChild (make ('foo', true));
 
        this.catLink.style.display = 'none';
 
        this.span.insertBefore (this.catLink, this.span.firstChild.nextSibling);
 
      }
 
      this.catLink.removeChild (this.catLink.firstChild);
 
      this.catLink.appendChild (make (this.currentCategory, true));
 
      this.catLink.href = wikiPagePath (HotCat.category_canonical + ':' + this.currentCategory);
 
      this.catLink.title = "";
 
      this.catLink.className = this.currentExists ? "" : 'new';
 
      this.lastSavedCategory = this.currentCategory;
 
      this.lastSavedKey      = this.currentKey;
 
      this.lastSavedExists  = this.currentExists;
 
      this.lastSavedHidden  = this.currentHidden;
 
      // Close form and redisplay category
 
      if (this.list) this.list.style.display = 'none';
 
      if (this.engineSelector) this.engineSelector.style.display = 'none';
 
      this.form.style.display = 'none';
 
      this.catLink.title = this.currentKey;
 
      this.catLink.style.display = "";
 
      if (this.isAddCategory) {
 
        if (onUpload) {
 
          var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one
 
        }
 
        this.isAddCategory = false;
 
        this.linkSpan.parentNode.removeChild (this.linkSpan);
 
        this.makeLinkSpan ();
 
        this.span.appendChild (this.linkSpan);
 
      }
 
      if (!this.undoLink) {
 
        // Append an undo link.
 
        var span = make ('span');
 
        var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.rollback, this);
 
        lk.appendChild (make (HotCat.links.undo, true)); lk.title = HotCat.tooltips.undo;
 
        span.appendChild (make (' ', true));
 
        span.appendChild (lk);
 
        this.normalLinks.appendChild (span);
 
        this.undoLink = span;
 
        if (!onUpload) {
 
          try {
 
            this.catLink.style.backgroundColor = HotCat.bg_changed;
 
          } catch (ex) {}
 
        }
 
      }
 
      if (this.upDownLinks) this.upDownLinks.style.display = this.lastSavedExists ? "" : 'none';
 
      this.linkSpan.style.display = "";
 
      this.state = CategoryEditor.CHANGED;
 
      checkMultiInput ();
 
    },
 
   
 
    commit : function (comment) {
 
      // Check again to catch problem cases after redirect resolution
 
      if (  (  this.currentCategory == this.originalCategory
 
              && (this.currentKey == this.originalKey
 
                  || this.currentKey === null && this.originalKey.length === 0
 
                )
 
            )
 
          || wgNamespaceNumber == 14 && this.currentCategory == wgTitle
 
          || HotCat.blacklist != null && HotCat.blacklist.test (this.currentCategory)
 
        )
 
      {
 
        this.cancel ();
 
        return;
 
      }
 
      if (commitButton || onUpload) {
 
        this.close ();
 
      } else {
 
        this.close ();
 
        var self = this;
 
        initiateEdit (function (failure) {performChanges (failure, self);}, function (msg) {alert (msg);});
 
      }
 
    },
 
   
 
    remove : function (evt) {
 
      this.doRemove (evtKeys (evt) & 1);
 
      return evtKill (evt);
 
    },
 
   
 
    doRemove : function (noCommit) {
 
      if (this.isAddCategory) { // Empty input on adding a new category
 
        this.cancel ();
 
        return;
 
      }
 
      if (!commitButton && !onUpload) {
 
        for (var i = 0; i < editors.length; i++) {
 
          if (editors[i].state != CategoryEditor.UNCHANGED) {
 
            setMultiInput();
 
            break;
 
          }
 
        }
 
      }
 
      if (commitButton) {
 
        this.catLink.title = "";
 
        this.catLink.style.textDecoration = 'line-through';
 
        try {
 
          this.catLink.style.backgroundColor = HotCat.bg_changed;
 
        } catch (ex) {}
 
        this.originalState = this.state;
 
        this.state = CategoryEditor.DELETED;
 
        this.normalLinks.style.display = 'none';
 
        this.undelLink.style.display = "";
 
        checkMultiInput ();
 
      } else {
 
        if (onUpload) {
 
          // Remove this editor completely
 
          this.removeEditor ();
 
        } else {
 
          this.originalState = this.state;
 
          this.state = CategoryEditor.DELETED;
 
          this.noCommit = noCommit;
 
          var self = this;
 
          initiateEdit (function (failure) {performChanges (failure, self);}, function (msg) {self.state = self.originalState; alert (msg);});
 
        }
 
      }
 
    },
 
   
 
    restore : function (evt) {
 
      // Can occur only if we do have a commit button and are not on the upload form
 
      this.catLink.title = this.currentKey;
 
      this.catLink.style.textDecoration = "";
 
      this.state = this.originalState;
 
      if (this.state == CategoryEditor.UNCHANGED) {
 
        this.catLink.style.backgroundColor = 'transparent';
 
      } else {
 
        try {
 
          this.catLink.style.backgroundColor = HotCat.bg_changed;
 
        } catch (ex) {}
 
      }
 
      this.normalLinks.style.display = "";
 
      this.undelLink.style.display = 'none';
 
      checkMultiInput ();
 
      return evtKill (evt);
 
    },
 
   
 
    // Internal operations
 
       
 
    selectEngine : function (engineName) {
 
      if (!this.engineSelector) return;
 
      for (var i = 0; i < this.engineSelector.options.length; i++) {
 
        this.engineSelector.options[i].selected = this.engineSelector.options[i].value == engineName;
 
      }
 
    },
 
 
 
    sanitizeInput : function () {
 
      var v = this.text.value || "";
 
      v = v.replace(/^(\s|_)+/, ""); // Trim leading blanks and underscores
 
      var re = new RegExp ('^(' + HotCat.category_regexp + '):');
 
      if (re.test (v)) v = v.substring (v.indexOf (':') + 1);
 
      if (HotCat.capitalizePageNames) v = capitalize (v);
 
      // Only update the input field if there is a difference. IE8 appears to reset the selection
 
      // and place the cursor at the front upon reset, which makes our autocompletetion become a
 
      // nuisance. FF and IE6 don't seem to have this problem.
 
      if (this.text.value != null && this.text.value != v)
 
        this.text.value = v;
 
    },
 
 
 
    makeCall : function (url, callbackObj, engine, queryKey) {
 
      var cb = callbackObj;
 
      var e  = engine;
 
      var v  = queryKey;
 
      var r  = sajax_init_object ();
 
      cb.requests.push (r);
 
      r.open('GET', armorUri(url), true);
 
      r.onreadystatechange =
 
        bind (
 
          function () {
 
            if (r.readyState == 4) {
 
              if (r.status != 200) cb.dontCache = true;
 
              if (r.status == 200 && r.responseText != null) {
 
                var titles = e.handler (r.responseText, v);
 
                if (titles && titles.length > 0) {
 
                  if (cb.allTitles == null) {
 
                    cb.allTitles = titles;
 
                  } else {
 
                    cb.allTitles = cb.allTitles.concat (titles);
 
                  }
 
                }
 
              }
 
              cb.callsMade++;
 
            }
 
            if (cb.callsMade == cb.nofCalls) {
 
              if (!cb.dontCache && !suggestionConfigs[cb.engineName].cache[v]) {
 
                suggestionConfigs[cb.engineName].cache[v] = cb.allTitles;
 
              }
 
              this.text.readOnly = false;
 
              if (!cb.cancelled) this.showSuggestions (cb.allTitles, cb.noCompletion, v, cb.engineName);
 
              if (cb === this.callbackObj) this.callbackObj = null;
 
              delete cb;
 
            }
 
          }
 
        ,this
 
        );
 
      r.setRequestHeader ('Pragma', 'cache=yes');
 
      r.setRequestHeader ('Cache-Control', 'no-transform');
 
      r.send (null);
 
    },
 
 
 
    callbackObj : null,
 
 
 
    textchange : function (dont_autocomplete, force) {
 
      // Hide all other lists
 
      CategoryEditor.makeActive (this);
 
      // Get input value, omit sort key, if any
 
      this.sanitizeInput ();
 
      var v = this.text.value;
 
      // Disregard anything after a pipe.
 
      var pipe = v.indexOf ('|');
 
      if (pipe >= 0) v = v.substring (0, pipe);
 
      if (this.lastInput == v && !force) return; // No change
 
      if (this.lastInput != v) checkMultiInput ();
 
      this.lastInput = v;
 
      this.lastRealInput = v;
 
 
 
      // Mark blacklisted inputs.
 
      this.ok.disabled = v.length > 0 && HotCat.blacklist != null && HotCat.blacklist.test (v);
 
 
 
      if (noSuggestions) {
 
        // No Ajax: just make sure the list is hidden
 
        if (this.list) this.list.style.display = 'none';
 
        if (this.engineSelector) this.engineSelector.style.display = 'none';
 
        if (this.icon) this.icon.style.display = 'none';
 
        return;
 
      }
 
     
 
      if (v.length === 0) { this.showSuggestions([]); return; }
 
      if (!sajax_init_object ()) {
 
        noSuggestions = true;
 
        if (this.list) this.list.style.display = 'none';
 
        if (this.engineSelector) this.engineSelector.style.display = 'none';
 
        if (this.icon) this.icon.style.display = 'none';
 
        return;
 
      }
 
      if (this.callbackObj) this.callbackObj.cancelled = true;
 
      var engineName  = suggestionConfigs[this.engine] ? this.engine : 'combined';
 
 
 
      dont_autocomplete = dont_autocomplete || suggestionConfigs[engineName].noCompletion;
 
      if (suggestionConfigs[engineName].cache[v]) {
 
        this.showSuggestions (suggestionConfigs[engineName].cache[v], dont_autocomplete, v, engineName);
 
        return;
 
      }
 
 
 
      var engines = suggestionConfigs[engineName].engines;
 
      this.callbackObj =
 
        {allTitles: null, requests: [], callsMade: 0, nofCalls: engines.length, noCompletion: dont_autocomplete, engineName: engineName};
 
      for (var j = 0; j < engines.length; j++) {
 
        engine = suggestionEngines[engines[j]];
 
        var url = wgServer + wgScriptPath + engine.uri.replace (/\$1/g, encodeURIComponent (v));
 
        this.makeCall (url, this.callbackObj, engine, v);
 
      }   
 
    },
 
   
 
    showSuggestions : function (titles, dontAutocomplete, queryKey, engineName) {
 
      this.text.readOnly = false;
 
      this.dab = null;
 
      if (!this.list) return;
 
      if (noSuggestions) {
 
        if (this.list) this.list.style.display = 'none';
 
        if (this.engineSelector) this.engineSelector.style.display = 'none';
 
        if (this.icon) this.icon.style.display = 'none';
 
        this.inputExists = true; // Default...
 
        return;
 
      }
 
      var haveEngine = !!engineName;
 
      if (haveEngine) {
 
        haveEngine = this.engineSelector != null;
 
      } else {
 
        if (this.engineSelector) this.engineSelector.style.display = 'none';
 
      }
 
      if (queryKey) {
 
        if (this.lastInput.indexOf (queryKey) != 0) return;
 
        if (this.lastQuery && this.lastInput.indexOf (this.lastQuery) === 0 && this.lastQuery.length > queryKey.length)
 
          return;
 
      }
 
      this.lastQuery = queryKey;
 
     
 
      // Get current input text
 
      var v = this.text.value.split('|');
 
      var key = v.length > 1 ? '|' + v[1] : "";
 
      v = (HotCat.capitalizePageNames ? capitalize (v[0]) : v[0]);
 
 
 
      if (titles) {
 
        var vLow = v.toLowerCase ();
 
        // Strip blacklisted categories
 
        if (HotCat.blacklist != null) {
 
          for (var i = 0; i < titles.length; i++) {
 
            if (HotCat.blacklist.test (titles[i])) {
 
              titles.splice(i, 1);
 
              i--;
 
            }
 
          }
 
        }
 
        titles.sort (
 
          function (a, b) {
 
            if (a.indexOf (b) === 0) return 1; // a begins with b: a > b
 
            if (b.indexOf (a) === 0) return -1; // b begins with a: a < b
 
            // Opensearch may return stuff not beginning with the search prefix!
 
            var prefixMatchA = (a.indexOf (v) === 0 ? 1 : 0);
 
            var prefixMatchB = (b.indexOf (v) === 0 ? 1 : 0);
 
            if (prefixMatchA != prefixMatchB) return prefixMatchB - prefixMatchA;
 
            // Case-insensitive prefix match!
 
            var aLow = a.toLowerCase(), bLow = b.toLowerCase();
 
            prefixMatchA = (aLow.indexOf (vLow) === 0 ? 1 : 0);
 
            prefixMatchB = (bLow.indexOf (vLow) === 0 ? 1 : 0);
 
            if (prefixMatchA != prefixMatchB) return prefixMatchB - prefixMatchA;
 
            if (a < b) return -1;
 
            if (b < a) return 1;
 
            return 0;
 
          }
 
        );
 
        // Remove duplicates and self-references
 
        for (var i = 0; i < titles.length; i++) {
 
          if (  i+1 < titles.length && titles[i] == titles[i+1]
 
              || wgNamespaceNumber == 14 && titles[i] == wgTitle
 
            )
 
          {
 
            titles.splice (i, 1);
 
            i--;
 
          }
 
        }
 
      }
 
      if (!titles || titles.length === 0) {
 
        if (this.list) this.list.style.display = 'none';
 
        if (this.engineSelector) this.engineSelector.style.display = 'none';
 
        if (engineName && suggestionConfigs[engineName] && !suggestionConfigs[engineName].temp) {
 
          if (this.icon) this.icon.src = HotCat.existsNo;
 
          this.inputExists = false;
 
        }
 
        return;
 
      }
 
                   
 
      var firstTitle = titles[0];
 
      var completed = this.autoComplete (firstTitle, v, key, dontAutocomplete);
 
      if (engineName && suggestionConfigs[engineName] && !suggestionConfigs[engineName].temp) {
 
        this.icon.src = completed ? HotCat.existsYes : HotCat.existsNo;
 
        this.inputExists = completed;
 
      }
 
      if (completed) {
 
        this.lastInput = firstTitle;
 
        if (titles.length == 1) {
 
          this.list.style.display = 'none';
 
          if (this.engineSelector) this.engineSelector.style.display = 'none';
 
          return;
 
        }
 
      }
 
      if (!this.is_active) {
 
        this.list.style.display = 'none';
 
        if (this.engineSelector) this.engineSelector.style.display = 'none';
 
        return;
 
      }
 
      var nofItems = (titles.length > HotCat.list_size ? HotCat.list_size : titles.length);
 
      if (nofItems <= 1) nofItems = 2;
 
      this.list.size = nofItems;
 
      this.list.style.align    = is_rtl ? 'right' : 'left';
 
      this.list.style.zIndex  = 5;
 
      this.list.style.position = 'absolute';
 
      // Compute initial list position. First the height.
 
      var listh = 0;
 
      if (this.list.style.display == 'none') {
 
        // Off-screen display to get the height
 
        this.list.style.top = this.text.offsetTop + 'px';
 
        this.list.style.left = '-10000px';
 
        this.list.style.display = "";
 
        listh = this.list.offsetHeight;
 
        this.list.style.display = 'none';
 
      } else {
 
        listh = this.list.offsetHeight;
 
      }
 
      // Approximate calculation of maximum list size
 
      var maxListHeight = listh;
 
      if (nofItems < HotCat.list_size) maxListHeight = (listh / nofItems) * HotCat.list_size;
 
 
 
      function scroll_offset (what) {
 
        var s = 'scroll' + what;
 
        return (document.documentElement ? document.documentElement[s] : 0)
 
              || document.body[s] || 0;
 
      } 
 
      function viewport (what) {
 
        if (typeof (is_safari) != 'undefined' && is_safari && !document.evaluate)
 
          return window['inner' + what];
 
        var s = 'client' + what;
 
        if (typeof (is_opera) != 'undefined' && is_opera) return document.body[s];
 
        return (document.documentElement ? document.documentElement[s] : 0)
 
              || document.body[s] || 0;
 
      }
 
      function position (node) {
 
        // Stripped-down simplified position function. It's good enough for our purposes.
 
        if (node.getBoundingClientRect) {
 
          var box    = node.getBoundingClientRect ();
 
          return { x : Math.round (box.left + scroll_offset ('Left'))
 
                  ,y : Math.round (box.top + scroll_offset ('Top'))
 
                };
 
        }
 
        var t = 0, l = 0;
 
        do {
 
          t = t + (node.offsetTop  || 0);
 
          l = l + (node.offsetLeft || 0);
 
          node = node.offsetParent;
 
        } while (node);
 
        return {x : l, y : t};
 
      }
 
 
 
      // IE6 seems to report in this.text.offsetTop and this.text.offsetLeft global offsets??
 
      // Possibly this has something to do with the special status of input elements in IE as
 
      // "windowed controls". Calculate the relative offsets manually.
 
      var textPos = position (this.text);
 
      var catLinePos = position (this.line);
 
      var textTop = textPos.y - catLinePos.y;
 
      var textLeft = textPos.x - catLinePos.x;
 
      if (window.ie6_bugs) {
 
        // IE6 somehow has a problem with inline-displayed forms (to which our list belongs), and will add the
 
        // offset of the beginning of the text to the offsets we'd normally calculate, which in particular with
 
        // right-aligned category lines as they occur in some older skins completely misplaces the lists, sometimes
 
        // even off-screen. This appears to affect only the horizontal positioning of the list and of the
 
        // engineSelector. Try to account for this bizarre behavior. Notes: dunno if that also occurs on IE7.
 
        var textStartPos = position (this.line.firstChild);
 
        textStartPos.x -= catLinePos.x;
 
        textLeft -= textStartPos.x;
 
      }
 
      var nl = textLeft;
 
      var nt = 0;
 
      var offset = 0;
 
      if (haveEngine) {
 
        this.engineSelector.style.zIndex = 5;
 
        this.engineSelector.style.position = 'absolute';
 
        this.engineSelector.style.width = this.text.offsetWidth + 'px';
 
        // Figure out the height of this selector: display it off-screen, then hide it again.
 
        if (this.engineSelector.style.display == 'none') {
 
          this.engineSelector.style.left  = '-1000px';
 
          this.engineSelector.style.top  = textTop + 'px';
 
          this.engineSelector.style.display = "";
 
          offset = this.engineSelector.offsetHeight;
 
          this.engineSelector.style.display = 'none';
 
        } else {
 
          offset = this.engineSelector.offsetHeight;
 
        }
 
        this.engineSelector.style.left  = nl + 'px';
 
      }
 
      if (textPos.y < maxListHeight + offset) {
 
        // The list might extend beyond the upper border of the page. Let's avoid that by placing it
 
        // below the input text field.
 
        nt = textTop + this.text.offsetHeight + offset + 1;
 
        if (haveEngine) this.engineSelector.style.top = textTop + this.text.offsetHeight + 'px';
 
      } else {
 
        nt = textTop - listh - offset;
 
        if (haveEngine) this.engineSelector.style.top = textTop - offset + 'px';
 
      }
 
      this.list.style.top = nt + 'px';
 
      this.list.style.width = ""; // No fixed width (yet)
 
      this.list.style.left = nl + 'px';
 
      // (Re-)fill the list
 
      while (this.list.firstChild) this.list.removeChild (this.list.firstChild);
 
      for (var i = 0 ; i < titles.length ; i++) {
 
        var opt = make ('option') ;
 
        opt.appendChild (make (titles[i], true));
 
        opt.selected = completed && (i === 0);
 
        this.list.appendChild (opt);
 
      }
 
      if (haveEngine) {
 
        this.selectEngine (engineName);
 
        this.engineSelector.style.display = "";
 
      }
 
      this.list.style.display = 'block';
 
      // Set the width of the list       
 
      var scroll = scroll_offset ('Left');
 
      var view_w = viewport ('Width');
 
      var l_pos  = position (this.list);
 
      if (this.list.offsetWidth < this.text.offsetWidth) {
 
        this.list.style.width = this.text.offsetWidth + 'px';
 
        return;
 
      }
 
      // Make sure that the list fits horizontally into the browser window     
 
      var w      = this.list.offsetWidth;
 
      if (l_pos.x + w > scroll + view_w) {
 
        if (w > view_w) w = view_w;
 
        this.list.style.width = w + 'px';
 
        this.list.style.left = nl - (l_pos.x + w - scroll - view_w) + 'px';
 
      }
 
    },
 
 
 
    autoComplete : function (newVal, actVal, key, dontModify) {
 
      if (newVal == actVal) return true;
 
      if (dontModify || newVal.indexOf (actVal) != 0) return false;
 
      // Actual input is a prefix of the new text. Fill in new text, selecting the newly added suffix
 
      // such that it can be easily removed by typing backspace if the suggestion is unwanted.
 
      if (!(  this.text.setSelectionRange
 
            || this.text.createTextRange
 
            ||    typeof (this.text.selectionStart) != 'undefined'
 
              && typeof (this.text.selectionEnd) != 'undefined'
 
          )
 
        )
 
        return false;
 
      // Here we know that we can indeed select properly. If we can't doing this would be a major
 
      // annoyance.
 
      this.text.focus();
 
      var start  = actVal.length;       
 
      this.text.value = newVal + key;         
 
      if (this.text.setSelectionRange)      // e.g. khtml
 
        this.text.setSelectionRange (start, newVal.length);
 
      else if (this.text.createTextRange) { // IE
 
        var new_selection = this.text.createTextRange();
 
        new_selection.move ('character', start);
 
        new_selection.moveEnd ('character', newVal.length - start);
 
        new_selection.select();
 
      } else {
 
        this.text.selectionStart = start;
 
        this.text.selectionEnd  = newVal.length;
 
      }
 
      return true;
 
    },
 
 
 
    processKey : function (evt) {
 
      var dir = 0;
 
      switch (this.lastKey) {
 
        case 38: dir = -1; // Up arrow
 
        case 40: if (dir === 0) dir = 1; // Down arrow
 
        case 33: if (dir === 0) dir = -HotCat.list_size; // Page up
 
        case 34: if (dir === 0) dir = HotCat.list_size; // Page down
 
          if (this.list.style.display != 'none') {
 
            // List is visible, so there are suggestions
 
            this.highlightSuggestion (dir);
 
            // Kill the event, otherwise some browsers (e.g., Firefox) may additionally treat an up-arrow
 
            // as "place the text cursor at the front", which we don't want here.
 
            return evtKill (evt);
 
          } else if (  this.keyCount <= 1
 
                    && (!this.callbackObj || this.callbackObj.callsMade == this.callbackObj.nofCalls)
 
                    )
 
          {
 
            // If no suggestions displayed, get them, unless we're already getting them.
 
            this.textchange ();
 
          }
 
          break;
 
        case 27: // ESC: inhibit default behavior (revert to last real input in FF: we do that ourselves)
 
          return evtKill (evt);
 
      }
 
      return true;
 
    },
 
 
 
    highlightSuggestion : function (dir) {
 
      if (noSuggestions || !this.list || this.list.style.display == 'none') return false;
 
      var curr = this.list.selectedIndex;
 
      var tgt  = -1;
 
      if (dir === 0) {
 
        if (curr < 0 || curr >= this.list.options.length) return false;
 
        tgt = curr;
 
      } else {
 
        tgt = curr < 0 ? 0 : curr + dir;
 
        tgt = tgt < 0 ? 0 : tgt;
 
        if (tgt >= this.list.options.length) tgt = this.list.options.length - 1;
 
      }
 
      if (tgt != curr || dir === 0) {
 
        if (curr >= 0 && curr < this.list.options.length && dir != 0) this.list.options[curr].selected = false;
 
        this.list.options[tgt].selected = true;
 
        // Get current input text
 
        var v = this.text.value.split('|');
 
        var key = v.length > 1 ? '|' + v[1] : "";
 
        var completed = this.autoComplete (this.list.options[tgt].text, this.lastRealInput, key, false);
 
        if (!completed) {
 
          this.text.value = this.list.options[tgt].text + key;
 
        }
 
        this.lastInput = this.list.options[tgt].text;
 
        this.inputExists = true; // Might be wrong if from a dab list...
 
        if (this.icon) this.icon.src = HotCat.existsYes;
 
        this.state = CategoryEditor.CHANGE_PENDING;
 
      }
 
      return true;
 
    },
 
 
 
    resetKeySelection : function () {
 
      if (noSuggestions || !this.list || this.list.style.display == 'none') return false;
 
      var curr = this.list.selectedIndex;
 
      if (curr >= 0 && curr < this.list.options.length) {
 
        this.list.options[curr].selected = false;
 
        // Get current input text
 
        var v = this.text.value.split('|');
 
        var key = v.length > 1 ? '|' + v[1] : "";
 
        // ESC is handled strangely by some browsers (e.g., FF); somehow it resets the input value before
 
        // our event handlers ever get a chance to run.
 
        var result = v[0] != this.lastInput;
 
        if (v[0] != this.lastRealInput) {
 
          this.text.value = this.lastRealInput + key;
 
          result = true;
 
        }
 
        this.lastInput = this.lastRealInput;
 
        return result;
 
      }
 
      return false;
 
    }
 
 
 
  }; // end CategoryEditor.prototype
 
 
 
  function initialize () {
 
    // User configurations. Do this here, called from the onload handler, so that users can
 
    // override it easily in their own user script files by just declaring variables. JSconfig
 
    // is some feature used at Wikimedia Commons.
 
    HotCat.dont_add_to_watchlist =
 
      (typeof (window.hotcat_dont_add_to_watchlist) != 'undefined'
 
        ? !!window.hotcat_dont_add_to_watchlist
 
        : (typeof (JSconfig) != 'undefined' && typeof (JSconfig.keys['HotCatDontAddToWatchlist']) != 'undefined'
 
            ? JSconfig.keys['HotCatDontAddToWatchlist']
 
            : HotCat.dont_add_to_watchlist
 
          )
 
      );
 
    HotCat.no_autocommit =
 
      (typeof (window.hotcat_no_autocommit) != 'undefined'
 
        ? !!window.hotcat_no_autocommit
 
        : (typeof (JSconfig) != 'undefined' && typeof (JSconfig.keys['HotCatNoAutoCommit']) != 'undefined'
 
            ? JSconfig.keys['HotCatNoAutoCommit']
 
            : HotCat.no_autocommit
 
          )
 
      );
 
    HotCat.suggest_delay =  window.hotcat_suggestion_delay
 
                          || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatSuggestionDelay']
 
                          || HotCat.suggest_delay;
 
    HotCat.editbox_width =  window.hotcat_editbox_width
 
                          || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatEditBoxWidth']
 
                          || HotCat.editbox_width;
 
    HotCat.suggestions  =  window.hotcat_suggestions
 
                          || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatSuggestions']
 
                          || HotCat.suggestions;
 
    if (typeof (HotCat.suggestions) != 'string' || !suggestionConfigs[HotCat.suggestions])
 
      HotCat.suggestions = 'combined';
 
    HotCat.fixed_search  =
 
      (typeof (window.hotcat_suggestions_fixed) != 'undefined'
 
        ? !!window.hotcat_suggestions_fixed
 
        : (typeof (JSconfig) != 'undefined' && typeof (JSconfig.keys['HotCatFixedSuggestions']) != 'undefined'
 
            ? JSconfig.keys['HotCatFixedSuggestions']
 
            : HotCat.fixed_search
 
          )
 
      );
 
    HotCat.single_minor  =
 
      (typeof (window.hotcat_single_changes_are_minor) != 'undefined'
 
        ? !!window.hotcat_single_changes_are_minor
 
        : (typeof (JSconfig) != 'undefined' && typeof (JSconfig.keys['HotCatMinorSingleChanges']) != 'undefined'
 
            ? JSconfig.keys['HotCatMinorSingleChanges']
 
            : HotCat.single_minor
 
          )
 
      );
 
    HotCat.bg_changed    =  window.hotcat_changed_background
 
                          || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatChangedBackground']
 
                          || HotCat.bg_changed;
 
    HotCat.use_up_down  =
 
      (typeof (window.hotcat_use_category_links) != 'undefined'
 
        ? !!window.hotcat_use_category_links
 
        : (typeof (JSconfig) != 'undefined' && typeof (JSconfig.keys['HotCatUseCategoryLinks']) != 'undefined'
 
            ? JSconfig.keys['HotCatUseCategoryLinks']
 
            : HotCat.use_up_down
 
          )
 
      );
 
    HotCat.list_size =    window.hotcat_list_size
 
                      || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatListSize']
 
                      || HotCat.list_size;
 
    // Numeric input, make sure we have a numeric value
 
    HotCat.list_size = parseInt (HotCat.list_size, 10);
 
    if (isNaN (HotCat.list_size) || HotCat.list_size < 5) HotCat.list_size = 5;
 
    if (HotCat.list_size > 15) HotCat.list_size = 15;
 
    // Localize search engine names
 
    if (HotCat.engine_names) {
 
      for (var key in HotCat.engine_names) {
 
        if (suggestionConfigs[key] && HotCat.engine_names[key]) {
 
          suggestionConfigs[key].name = HotCat.engine_names[key];
 
        }
 
      }
 
    }
 
    // Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]]
 
    is_rtl = hasClass (document.body, 'rtl');
 
    if (!is_rtl) {
 
      if (document.defaultView && document.defaultView.getComputedStyle) { // Gecko etc.
 
        is_rtl = document.defaultView.getComputedStyle (document.body, null).getPropertyValue ('direction');
 
      } else if (document.body.currentStyle) { // IE, has subtle differences to getComputedStyle
 
        is_rtl = document.body.currentStyle['direction'];
 
      } else { // Not exactly right, but best effort
 
        is_rtl = document.body.style['direction'];
 
      }
 
      is_rtl = (is_rtl == 'rtl');
 
    }
 
  }
 
       
 
  function can_edit () {
 
    var container = null;
 
    switch (skin) {
 
      case 'cologneblue':
 
        container = document.getElementById ('quickbar');
 
        // Fall through
 
      case 'standard':
 
      case 'nostalgia':
 
        if (!container) container = document.getElementById ('topbar');
 
        var lks = container.getElementsByTagName ('a');
 
        for (var i = 0; i < lks.length; i++) {
 
          if (  param ('title', lks[i].href) == wgPageName
 
              && param ('action', lks[i].href) == 'edit')
 
            return true;
 
        }
 
        return false;
 
      default:
 
        // all modern skins:
 
        return document.getElementById ('ca-edit') != null;
 
    }
 
    return false;
 
  }   
 
 
 
  function setup_upload () {
 
    onUpload = true;
 
    // Add an empty category bar above the "watch this" box, and change the onsubmit handler.
 
    var ip = document.getElementById ('wpWatchthis');
 
    if (!ip) return;
 
    var reupload = document.getElementById ('wpForReUpload');
 
    var destFile = document.getElementById ('wpDestFile');
 
    if (  (reupload && !!reupload.value)
 
        || (destFile && (destFile.disabled || destFile.readOnly)))
 
      return; // re-upload form...
 
    // Insert a table row with two fields (label and empty category bar)
 
    ip = ip.parentNode.parentNode; // The containing <tr>
 
    var newRow = make ('tr');
 
    var labelCell = make ('td');
 
    var lineCell  = make ('td');
 
    newRow.appendChild (labelCell);
 
    newRow.appendChild (lineCell);
 
    // Create the category line
 
    catLine = make ('div');
 
    catLine.className = 'catlinks';
 
    catLine.id = 'catlinks';
 
    catLine.style.textAlign = is_rtl ? 'right' : 'left';
 
    lineCell.appendChild (catLine);
 
    // Create the label
 
    var label = null;
 
    if (  typeof (UFUI) != 'undefined'
 
        && typeof (UIElements) != 'undefined'
 
        && typeof (UFUI.getLabel) == 'function') {
 
      try {
 
        label = UFUI.getLabel ('wpCategoriesUploadLbl');
 
      } catch (ex) {
 
        label = null;
 
      }
 
    }
 
    if (!label) {
 
      labelCell.id = 'hotcatLabel';
 
      labelCell.appendChild (make (HotCat.categories), true);
 
    } else {
 
      labelCell.id = 'hotcatLabelTranslated';
 
      labelCell.appendChild (label);
 
    }
 
    labelCell.className          = 'mw-label';
 
    labelCell.style.textAlign    = 'right';
 
    labelCell.style.verticalAlign = 'middle';
 
    // Change the onsubmit handler
 
    var form = document.getElementById ('upload') || document.getElementById ('mw-upload-form');
 
    if (form) {
 
      var optionsTable = document.getElementById ('mw-htmlform-options');
 
      if (optionsTable) optionsTable.width = '100%';
 
      ip.parentNode.insertBefore (newRow, ip);
 
      form.onsubmit = (function (oldSubmit) {
 
        return function () {         
 
          var do_submit = true;
 
          if (oldSubmit) {
 
            if (typeof (oldSubmit) == 'string')
 
              do_submit = eval (oldSubmit);
 
            else if (typeof (oldSubmit) == 'function')
 
              do_submit = oldSubmit.apply (form, arguments);
 
          }
 
          if (!do_submit) return false;
 
          closeForm ();
 
          // Copy the categories
 
          var eb =    document.getElementById ('wpUploadDescription')
 
                  || document.getElementById ('wpDesc');
 
          var addedOne = false;
 
          for (var i = 0; i < editors.length; i++) {
 
            var t = editors[i].currentCategory;
 
            if (!t) continue ;
 
            var key = editors[i].currentKey;
 
            var new_cat = '[[' + HotCat.category_canonical + ':' + t + (key ? '|' + key : "") + ']]';
 
            // Only add if not already present
 
            var cleanedText = eb.value.replace(/<\!--(\s|\S)*?--\>/g, "")
 
                                      .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, "");
 
            if (!find_category (cleanedText, t, true)) {
 
              eb.value += '\n' + new_cat;
 
              addedOne = true;
 
            }
 
          }
 
          if (addedOne) {
 
            // Remove "subst:unc" added by Flinfo if it didn't find categories
 
            eb.value = eb.value.replace(/\{\{subst:unc\}\}/g, "");
 
          }
 
          return true;           
 
        };
 
      }) (form.onsubmit);
 
    }
 
  }
 
 
 
  var cleanedText = null;
 
 
 
  function isOnPage (span) {
 
    var catTitle = title (span.firstChild.getAttribute ('href', 2));
 
    if (!catTitle) return null;
 
    catTitle = catTitle.substr (catTitle.indexOf (':') + 1).replace (/_/g, ' ');
 
    if (HotCat.blacklist != null && HotCat.blacklist.test (catTitle)) return null;
 
    var result = { title : catTitle, match : ["", "", ""] };
 
    if (pageText === null) return result;
 
    if (cleanedText === null) {
 
      cleanedText = pageText.replace(/<\!--(\s|\S)*?--\>/g, "")
 
                            .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, "");
 
    }
 
    result.match = find_category (cleanedText, catTitle, true);
 
    return result;
 
  }
 
 
 
  var initialized = false;
 
  var setupTimeout = null;
 
 
 
  function setup () {
 
    if (initialized) return;
 
    initialized = true;
 
    if (setupTimeout) {
 
      window.clearTimeout (setupTimeout);
 
      setupTimeout = null;
 
    }
 
    // Find the category bar, or create an empty one if there isn't one. Then add -/+- links after
 
    // each category, and add the + link.
 
    catLine =  catLine                                                  // Special:Upload
 
            || document.getElementById ('mw-normal-catlinks')          // MW >= 1.13alpha
 
            || getElementsByClassName (document , 'p' , 'catlinks')[0]; // MW < 1.13
 
    var hiddenCats = document.getElementById ('mw-hidden-catlinks');
 
    if (!catLine) {
 
      var footer = null;
 
      if (!hiddenCats) {
 
        footer = getElementsByClassName (document , 'div' , 'printfooter')[0];
 
        if (!footer) return; // Don't know where to insert the category line
 
      }
 
      catLine = make ('div');
 
      catLine.id = 'mw-normal-catlinks';
 
      catLine.style.textAlign = is_rtl ? 'right' : 'left';
 
      // Add a label
 
      var label = make ('a');
 
      label.href  = wgArticlePath.replace ('$1', 'Special:Categories');
 
      label.title = HotCat.categories;
 
      label.appendChild (make (HotCat.categories, true));
 
      catLine.appendChild (label);
 
      catLine.appendChild (make (':', true));
 
      // Insert the new category line
 
      var container = (hiddenCats ? hiddenCats.parentNode : document.getElementById ('catlinks'));
 
      if (!container) {
 
        container = make ('div');
 
        container.id = 'catlinks';
 
        footer.parentNode.insertBefore (container, footer.nextSibling);
 
      }
 
      container.className = 'catlinks noprint';
 
      container.style.display = "";
 
      if (!hiddenCats) {
 
        container.appendChild (catLine);
 
      } else {
 
        container.insertBefore (catLine, hiddenCats);
 
      }
 
    } // end if catLine exists
 
    catLine.style.position = 'relative';
 
    if (is_rtl) catLine.dir = 'rtl';
 
 
 
    // Create editors for all existing categories
 
 
 
    function createEditors (line, is_hidden) {
 
      var cats = line.getElementsByTagName ('li');
 
      if (cats.length > 0) {
 
        newDOM = true; line = cats[0].parentNode;
 
      } else {
 
        cats = line.getElementsByTagName ('span');
 
      }
 
      // Copy cats, otherwise it'll also magically contain our added spans as it is a live collection!
 
      var copyCats = new Array (cats.length);
 
      for (var i = 0; i < cats.length; i++) copyCats[i] = cats[i];
 
      var editor = null;
 
      for (var i = 0; i < copyCats.length; i++) {
 
        var test = isOnPage (copyCats[i]);
 
        if (test !== null && test.match !== null) {
 
          editor = new CategoryEditor (line, copyCats[i], test.title, test.match[2], is_hidden);
 
        }
 
      }
 
      return copyCats.length > 0 ? copyCats[copyCats.length-1] : null;
 
    }
 
 
 
    var lastSpan = createEditors (catLine, false);
 
    // Create one to add a new category
 
    var editor = new CategoryEditor(newDOM ? catLine.getElementsByTagName('ul')[0] : catLine, null, null, lastSpan != null, false);
 
    if (!onUpload) {
 
      if (pageText !== null && hiddenCats) {
 
        hiddenCats.style.position = 'relative';
 
        if (is_rtl) hiddenCats.dir = 'rtl';
 
        createEditors (hiddenCats, true);
 
      }
 
      // And finally add the "multi-mode" span. (Do this at the end, otherwise it ends up in the list above.)
 
      var enableMulti = make ('span');
 
      enableMulti.className = 'noprint';
 
      if (is_rtl) enableMulti.dir = 'rtl';
 
      catLine.insertBefore (enableMulti, catLine.firstChild.nextSibling);
 
      enableMulti.appendChild (make ('\xa0', true)); // nbsp
 
      multiSpan = make ('span');
 
      enableMulti.appendChild (multiSpan);
 
      multiSpan.innerHTML = '(<a>' + HotCat.addmulti + '</a>)';
 
      var lk = multiSpan.getElementsByTagName ('a')[0];
 
      lk.onclick = function (evt) {setMultiInput (); checkMultiInput (); return evtKill (evt);};
 
      lk.title = HotCat.multi_tooltip;
 
      lk.style.cursor = 'pointer';
 
    }
 
    cleanedText = null;
 
  }
 
 
 
  function setPage (json) {
 
    if (json && json.query) {
 
      if (json.query.pages) {
 
        var page = json.query.pages[wgArticleId == 0 ? "-1" : "" + wgArticleId];
 
        if (page) {
 
          if (page.revisions && page.revisions.length > 0) {
 
            pageText = page.revisions[0]['*'];
 
            pageTime = page.revisions[0].timestamp.replace (/\D/g, "");
 
          }
 
          pageWatched = typeof (page.watched) == 'string';
 
          editToken = page.edittoken;
 
          if (page.langlinks && (!json['query-continue'] || !json['query-continue'].langlinks)) {
 
            // We have interlanguage links, and we got them all.
 
            var re = "";
 
            for (var i = 0; i < page.langlinks.length; i++) {
 
              re += (i > 0 ? '|' : "") + page.langlinks[i].lang.replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1');
 
            }
 
            if (re.length > 0) {
 
              interlanguageRE = new RegExp ('((^|\\n\\r?)(\\[\\[\\s*(' + re + ')\\s*:[^\\]]+\\]\\]\\s*))+$');
 
            }
 
          }
 
         
 
        }
 
      }
 
      // Siteinfo
 
      if (json.query.general) {
 
        HotCat.capitalizePageNames = (json.query.general['case'] == 'first-letter');
 
        if (json.query.general.time) serverTime = json.query.general.time.replace (/\D/g, "");
 
      }
 
      // Userinfo
 
      if (json.query.userinfo && json.query.userinfo.options) {
 
        watchCreate = !HotCat.dont_add_to_watchlist && json.query.userinfo.options.watchcreations == '1';
 
        watchEdit  = !HotCat.dont_add_to_watchlist && json.query.userinfo.options.watchdefault == '1';
 
        minorEdits  = json.query.userinfo.options.minordefault == 1;
 
        // If the user has the "All edits are minor" preference enabled, we should honor that
 
        // for single category changes, no matter what the site configuration is.
 
        if (minorEdits) HotCat.single_minor = true;
 
      }
 
    }
 
  }
 
 
 
  function getPage () {
 
    // We know we have an article here.
 
    if (wgArticleId == 0) {
 
      // Doesn't exist yet.
 
      pageText = "";
 
      pageTime = null;
 
      setup ();
 
    } else {
 
      var url = wgServer + wgScriptPath + '/api.php?format=json&callback=HotCat.start&action=query&titles='
 
              + encodeURIComponent (wgPageName)
 
              + '&prop=info%7Crevisions&rvprop=content%7Ctimestamp&meta=siteinfo&rvlimit=1&rvstartid='
 
              + wgCurRevisionId;
 
      var s = make ('script');
 
      s.src = armorUri(url);
 
      s.type = 'text/javascript';
 
      HotCat.start = function (json) { setPage (json); setup (); };
 
      document.getElementsByTagName ('head')[0].appendChild (s);
 
      setupTimeout = window.setTimeout (setup, 4000); // 4 sec, just in case getting the wikitext takes longer.
 
    }
 
  }
 
 
 
  function run () {
 
    if (HotCat.started) return;
 
    HotCat.started = true;
 
    loadTrigger.register(really_run);
 
  }
 
 
 
  function really_run () {
 
    initialize ();
 
 
 
    if (is_rtl && window.ie6_bugs) return; // Disabled! IE6 with RTL is just too broken...
 
 
 
    if (!HotCat.upload_disabled && wgNamespaceNumber == -1 && wgCanonicalSpecialPageName == 'Upload' && wgUserName) {
 
      setup_upload ();
 
      setup ();
 
      // Check for state restoration
 
      if (  typeof (UploadForm) != 'undefined'
 
          && typeof (UploadForm.previous_hotcat_state) != 'undefined'
 
          && UploadForm.previous_hotcat_state != null)
 
        UploadForm.previous_hotcat_state = setState (UploadForm.previous_hotcat_state);     
 
    } else {
 
      if (!wgIsArticle || wgAction != 'view' || param('diff') != null || !can_edit() || HotCat.disable()) return;
 
      if ( wgNamespaceNumber == 6 && ! wgArticleId ) return; //ruwiki: disable on Commons files
 
      getPage ();
 
    }
 
  }
 
 
 
  // Legacy stuff
 
 
 
  function closeForm () {
 
    // Close all open editors without redirect resolution and other asynchronous stuff.
 
    for (var i = 0; i < editors.length; i++) {
 
      if (editors[i].state == CategoryEditor.OPEN) {
 
        editors[i].cancel();
 
      } else if (editors[i].state == CategoryEditor.CHANGE_PENDING) {
 
        editors[i].sanitizeInput ();
 
        var value = editors[i].text.value.split('|');
 
        var key  = null;
 
        if (value.length > 1) key = value[1];
 
        var v = value[0].replace(/_/g, ' ').replace(/^\s+|\s+$/g, "");
 
        if (v.length === 0) {
 
          editors[i].cancel ();
 
        } else {
 
          editors[i].currentCategory = v;
 
          editors[i].currentKey = key;
 
          editors[i].currentExists = this.inputExists;
 
          editors[i].close ();
 
        }
 
      }
 
    }
 
  }
 
 
 
  function getState () {
 
    var result = null;
 
    for (var i = 0; i < editors.length; i++) {
 
      var text = editors[i].currentCategory;
 
      var key  = editors[i].currentKey;
 
      if (text && text.length > 0) {
 
        if (key != null) text += '|' + key;
 
        if (result == null)
 
          result = text;
 
        else
 
          result = result + '\n' + text;
 
      }
 
    }
 
    return result;
 
  }
 
 
 
  function setState (state) {
 
    var cats = state.split ('\n');
 
    if (cats.length === 0) return null;
 
    if (initialized && editors.length == 1 && editors[0].isAddCategory) {
 
      // Insert new spans and create new editors for them.
 
      var newSpans = [];
 
      var before = editors.length == 1 ? editors[0].span : null;
 
      for (var i = 0; i < cats.length; i++) {
 
        if (cats[i].length === 0) continue;
 
        var cat = cats[i].split ('|');
 
        var key = cat.length > 1 ? cat[1] : null;
 
        cat = cat[0];
 
        var lk = make ('a'); lk.href = wikiPagePath (HotCat.category_canonical + ':' + cat);
 
        lk.appendChild (make (cat, true));
 
        lk.title = cat;
 
        var span = make ('span');
 
        span.appendChild (lk);
 
        if (i === 0) catLine.insertBefore (make (' ', true), before);
 
        catLine.insertBefore (span, before);
 
        if (before && i+1 < cats.length) parent.insertBefore (make (' | ', true), before);
 
        newSpans.push ({element: span, title: cat, 'key': key});
 
      }
 
      // And change the last one...
 
      if (before) {
 
        before.parentNode.insertBefore (make (' | ', true), before);
 
      }
 
      var editor = null;
 
      for (var i = 0; i < newSpans.length; i++) {
 
        editor = new CategoryEditor (catLine, newSpans[i].element, newSpans[i].title, newSpans[i].key);
 
      }
 
    }
 
    return null;
 
  }
 
 
 
  // Now export these legacy functions
 
  window.hotcat_get_state  = function () { return getState(); };
 
  window.hotcat_set_state  = function (state) { return setState (state); };
 
  window.hotcat_close_form = function () { closeForm (); };
 
 
 
  if (window.mediaWiki && window.mediaWiki.config) {
 
    // Make sure we don't get conflicts with AjaxCategories (core development that should one day
 
    // replace HotCat).
 
    window.mediaWiki.config.set('disableAJAXCategories', true);
 
  }
 
  if (window.jQuery) {
 
    window.jQuery(document).ready(run);
 
  } else {
 
    addOnloadHook (run);
 
  }
 
})();
 
 
 
} // end if (guard)
 
//</source>
 

Версия 17:14, 10 сентября 2021

/*
 This imports the latest version of HotCat from Commons.
 HotCat is a gadget to make changes to categories much easier.
 Full documentation can be found at https://commons.wikimedia.org/wiki/Help:Gadget-HotCat
*/
mw.loader.load( '//commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-HotCat.js&action=raw&ctype=text/javascript' );