Menü aufrufen
Toggle preferences menu
Persönliches Menü aufrufen
Nicht angemeldet
Ihre IP-Adresse wird öffentlich sichtbar sein, wenn Sie Änderungen vornehmen.

MediaWiki:Gadget-HotCat.js: Unterschied zwischen den Versionen

MediaWiki-Schnittstellenseite
CF (Diskussion | Beiträge)
K verschob „MediaWiki:HotCat.js“ nach „MediaWiki:Gadget-HotCat.js“: Wieder als Helferlein eingebunden
CF (Diskussion | Beiträge)
Update auf Version 2.17
Zeile 2: Zeile 2:


/*
/*
   HotCat V2.2b
   HotCat V2.17


   Ajax-based simple Category manager. Allows adding/removing/changing categories on a page view.
   Ajax-based simple Category manager. Allows adding/removing/changing categories on a page view.
Zeile 9: Zeile 9:
   can be selected interactively.
   can be selected interactively.


   Authors:
   Documentation: https://commons.wikimedia.org/wiki/Help:Gadget-HotCat
    V0.0: July 2007 - 2010-05-26: original version by [[User:Magnus Manske]], with lots of
  List of main authors: https://commons.wikimedia.org/wiki/Help:Gadget-HotCat/Version_history
          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).


   License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0)
   License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0)
 
   Choose whichever license of these you like best :-)
   Choose whichever license of these you like best :-)
*/
*/


// Globals:
/*
//  (inline script on the page):
  This code is MW version safe. It should run on any MediaWiki installation >= MW 1.15. Note: if
//    wgNamespaceNumber, wgCanonicalSpecialPageName, wgNamespaceIds (optional), wgFormattedNamespaces (optional)
  running on MW >= 1.17 configured with $wgLegacyJavaScriptGlobals != true, it will still force
//    wgScript, wgServer, wgArticlePath, wgScriptPath, wgAction, wgPageName, wgTitle, wgUserName, wgIsArticle,
  publishing the wg* globals in the window object. Note that HotCat is supposed to run with or
//    wgArticleId
  without jQuery, and also on older installations that do not yet have window.mediaWiki. If you
// ajax.js
  use any of these newer features, make sure you qualify them by checking whether they exist at
//    sajax_init_object
  all, and by providing some meaningful fallback implementation if not. To start itself, HotCat
//  wikibits.js
  uses either jQuery(document).ready(), if available (preferred), or the old addOnloadHook().
//   addOnloadHook, window.ie6_bugs, importScript
  If neither exists, HotCat won't start.
*/
if ((typeof wgAction == 'undefined') && window.mediaWiki && window.mediaWiki.config) { // Compatibility hack
  window.wgAction = window.mediaWiki.config.get('wgAction');
}
if (typeof (window.HotCat) == 'undefined' && wgAction != 'edit') { // Guard against double inclusions, and inactivate on edit pages


if (typeof (HotCat) == 'undefined') { // Guard against double inclusions
   
// Configuration stuff.
// Configuration stuff.
var HotCat = {
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.
   // Localize these messages to the main language of your wiki.
   messages :
   ,messages :
     { cat_removed  : 'Entferne [[Kategorie:$1]]'
     { cat_removed  : 'Entferne [[Kategorie:$1]]'
     ,template_removed  : 'Entferne {{[[Kategorie:$1|$1]]}}'
     ,template_removed  : 'Entferne {{[[Kategorie:$1|$1]]}}'
     ,cat_added    : 'Ergänze [[Kategorie:$1]]'
     ,cat_added    : 'Ergänze [[Kategorie:$1]]'
     ,cat_keychange: 'neuer Sortierschlüssel für [[Kategorie:$1]]: '
     ,cat_keychange: 'neuer Sortierschlüssel für [[Kategorie:$1]]: "$2"' // $2 is the new key
     ,cat_notFound : 'Kategorie "$1" konnte nicht gefunden werden'
     ,cat_notFound : 'Kategorie "$1" konnte nicht gefunden werden'
     ,cat_exists  : 'Kategorie "$1" bereits enthalten; nicht ergänzt'
     ,cat_exists  : 'Kategorie "$1" bereits enthalten; nicht ergänzt'
     ,cat_resolved : ' (Weiterleitung [[Kategorie:$1]] aufgelöst)' //wird nicht für dewiki benötigt
     ,cat_resolved : ' (Weiterleitung [[Kategorie:$1]] aufgelöst)'
     ,uncat_removed: 'entferne {{uncategorized}}' //wird nicht für dewiki benötigt
     ,uncat_removed: 'entferne {{uncategorized}}'
    ,separator    : '; '
     ,prefix      : '[[Hilfe:HotCat|HC]]: '
     ,prefix      : '[[Hilfe:HotCat|HC]]: '
         // Some text to prefix to the edit summary.
         // Some text to prefix to the edit summary.
     ,using        : ''
     ,using        : ""
         // Some text to append to the edit summary. Named 'using' for historical reasons. If you prefer
         // 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.
         // to have a marker at the front, use prefix and set this to the empty string.
     ,multi_change : '$1 Kategorien'
     ,multi_change : '$1 Kategorien'
         // $1 is replaced by a number
         // $1 is replaced by a number. If your language has several plural forms (c.f. [[:en:Dual (grammatical form)]]),
        // you can set this to an array of strings suitable for passing to mw.language.configPlural().
        // If that function doesn't exist, HotCat will simply fall back to using the last
        // entry in the array.
     ,commit      : 'Speichern'
     ,commit      : 'Speichern'
         // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
         // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
Zeile 71: Zeile 71:
         // see localization hook below.
         // see localization hook below.
     }
     }
  ,category_regexp    : '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]|[Kk][Aa][Tt][Ee][Gg][Oo][Rr][Ii][Ee]'
  ,category_regexp    : '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]'
   // Regular sub-expression matching all possible names for the category namespace. Is automatically localized
   // 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
   // correctly if you're running MediaWiki 1.16 or later. Otherwise, set it appropriately, e.g. at the German
Zeile 81: Zeile 81:
   // MediaWiki 1.16 or later; otherwise, set it to the preferred category name (e.g., "Kategorie").
   // MediaWiki 1.16 or later; otherwise, set it to the preferred category name (e.g., "Kategorie").
  ,categories        : 'Kategorien'
  ,categories        : 'Kategorien'
   // Plural of category_canonical
   // Plural of category_canonical.
  ,disambig_category  : null
  ,disambig_category  : null
   // Any category in this category is deemed a disambiguation category; i.e., a category that should not contain
   // Any category in this category is deemed a disambiguation category; i.e., a category that should not contain
Zeile 106: Zeile 106:
   // Tooltip for the "enter multi-mode" link
   // Tooltip for the "enter multi-mode" link
  ,disable            :
  ,disable            :
     function () { // Return true to disable HotCat
     function () { // Return true to disable HotCat. HotCat guarantees that the wg* globals exist here.
       return (  wgNamespaceNumber < 0  // Special pages; Special:Upload is handled differently
      var ns = wgNamespaceNumber;
               || wgNamespaceNumber == 10 // Templates
       return (  ns < 0  // Special pages; Special:Upload is handled differently
               || wgNamespaceNumber == 8  // MediaWiki
               || ns === 10 // Templates
               || wgNamespaceNumber == 2
               || ns === 8  // MediaWiki
                && wgTitle && wgTitle.length >= 3 && wgTitle.lastIndexOf ('.js') + 3 == wgTitle.length
               || ns === 6 && wgArticleId === 0 // Non-existing file pages
                // User scripts
              || ns === 2 && /\.(js|css)$/.test(wgTitle) // User scripts
               || typeof (wgNamespaceIds) != 'unknown'
               || typeof (wgNamespaceIds) != 'undefined'
                 && (  wgNamespaceNumber == wgNamespaceIds['creator']
                 && (  ns === wgNamespaceIds['creator']
                     || wgNamespaceNumber == wgNamespaceIds['timedtext']
                     || ns === wgNamespaceIds['timedtext']
                    || ns === wgNamespaceIds['institution']
                     )
                     )
             );
             );
Zeile 122: Zeile 123:
   // A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
   // A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
   // If not, set it to null.
   // If not, set it to null.
  ,existsYes    : 'http://upload.wikimedia.org/wikipedia/commons/thumb/b/be/P_yes.svg/20px-P_yes.svg.png'
  ,existsYes    : '//upload.wikimedia.org/wikipedia/commons/thumb/b/be/P_yes.svg/20px-P_yes.svg.png'
  ,existsNo    : 'http://upload.wikimedia.org/wikipedia/commons/thumb/4/42/P_no.svg/20px-P_no.svg.png'
  ,existsNo    : '//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.
   // The images used for the little indication icon. Should not need changing.
  ,template_regexp : '[Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee]'  
  ,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.
   // Regexp to recognize templates. Like "category" above; autolocalized for MW 1.16+, otherwise set manually here.
   // On the German Wikipedia, you might use '[Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee]'.
   // On the German Wikipedia, you might use '[Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee]|[Vv][Oo][Rr][Ll][Aa][Gg][Ee]'.
  ,template_categories : {}
  ,template_categories : {}
   // a list of categories which can be removed by removing a template
   // a list of categories which can be removed by removing a template
Zeile 140: Zeile 141:
   ,parentcat  : 'Überkategorien'
   ,parentcat  : 'Überkategorien'
   }
   }
  // Names for the search engines
  // Names for the search engines
  ,capitalizePageNames : true
,capitalizePageNames : true
   // Set to false if your wiki has case-sensitive page names. MediaWiki has two modes: either the first letter
   // 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
   // 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
   // ("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)
   // (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
   // HotCat tries to set this correctly 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
   // 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
   // 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.
   // 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:
   // Stuff changeable by users:
  ,bg_changed : '#F8CCB0'
  ,bg_changed : '#F8CCB0'
Zeile 167: Zeile 176:
  ,use_up_down : true
  ,use_up_down : true
   // If false, do not display the "up" and "down" links
   // 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.
};
};


importScript ('MediaWiki:Gadget-HotCat.js/' + wgUserLanguage);
// Make sure this is exported, so that localizations *can* actually modify parts of it, and the
// Localization hook to localize HotCat.messages.commit and HotCat.messages.multi_error. For German, the
// guard at the top actually works. (If we're loaded as an extension module through the resource
// file would be "MediaWiki:Gadget-HotCat.js/de", and its contents could be for instance
// loader, this outer scope may actually be a closure, not the global "window" scope.)
//
if (typeof (window.HotCat) == 'undefined') window.HotCat = HotCat;
// HotCat.messages.commit      = 'Speichern';
 
// HotCat.messages.ok          = 'OK';
(function () { // Local scope to avoid polluting the global namespace with declarations
// HotCat.messages.cancel      = 'Abbrechen';
// HotCat.messages.multi_error = 'Seitentext konnte nicht vom Server geladen werden. Die Änderungen können '
//                             +'leider nicht gespeichert werden.';


// No further changes should be necessary here.
  // Backwards compatibility stuff. We want HotCat to work with either wg* globals, or with mw.config.get().
   // Our "solution" is to publish the wg* globals if they're not already published.
(function () {
   if (window.mediaWiki && window.mediaWiki.config) {
 
    var globals = window.mediaWiki.config.get();
   // First auto-localize the regexps for the category and the template namespaces.
     if (globals && globals !== window) {
   if (typeof (wgFormattedNamespaces) != 'undefined') {
       for (var k in globals) window[k] = globals[k];
     function autoLocalize (namespaceNumber, fallback) {
       window.mediWiki.config = new window.mediaWiki.Map(true); // Make config point to window again.
       function create_regexp_str (name)
    }
       {
    globals = null;
        if (!name || name.length == 0) return "";
  }
        var regex_name = "";
  // More backwards compatibility. We have four places where we test for the browser: once for
        for (var i = 0; i < name.length; i++){
  // Safari < 3.0, once for WebKit (Chrome or Safari, any versions), and twice for IE <= 6.
          var initial = name.substr (i, 1);
  var ua = navigator.userAgent.toLowerCase();
          var ll = initial.toLowerCase ();
  var is_ie6 = /msie ([0-9]{1,}[\.0-9]{0,})/.exec(ua) != null && parseFloat(RegExp.$1) <= 6.0;
          var ul = initial.toUpperCase ();
  var is_webkit = /applewebkit\/\d+/.test(ua) && ua.indexOf ('spoofer') < 0;
          if (ll == ul){
  // And even more compatbility. HotCat was developed without jQuery, and anyway current jQuery
            regex_name += initial;
  // (1.7.1) doesn't seem to support in jquery.getJSON() or jQuery.ajax() the automatic
           } else {
  // switching from GET to POST requests if the query arguments would make the uri too long.
             regex_name += '[' + ll + ul + ']';
  // (IE has a hard limit of 2083 bytes, and the servers may have limits around 4 or 8kB.)
  //    Anyway, HotCat is supposed to run on wikis without jQuery, so we'd have to supply some
  // ajax routines ourselves in any case. We can't rely on the old sajax_init_object(), newer
  // MW versions (>= 1.19) might not have it.
  var getJSON = (function () {
    function getRequest () {
      var request = null;
      try {
        request = new window.XMLHttpRequest();
      } catch (anything) {
        if (window.ActiveXObject) {
           try {
             request = new window.ActiveXObject('Microsoft.XMLHTTP');
          } catch (any) {
           }
           }
         }
         } // end if IE
        return regex_name.replace (/[ _]/g, '[ _]').replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1');
      } // end try-catch
      }
      return request;
    }


       fallback = fallback.toLowerCase();
    function makeRequest (settings) {
       var canonical  = wgFormattedNamespaces["" + namespaceNumber].toLowerCase();
       var req = getRequest();
       var regexp    = create_regexp_str (canonical);
      if (!req && settings && settings.error) settings.error (req);
       if (fallback && canonical != fallback) regexp += '|' + create_regexp_str (fallback)
      if (!req || !settings || !settings.uri) return req;
      for (var cat_name in wgNamespaceIds) {
       var uri = armorUri (settings.uri);
         if (  typeof (cat_name) == 'string'
       var args = settings.data || null;
            && cat_name.toLowerCase () != canonical
      var method;
            && cat_name.toLowerCase () != fallback
       if (args && uri.length + args.length + 1 > 2000) {
            && wgNamespaceIds[cat_name] == namespaceNumber)
        // We lose caching, but at least we can make the request
         {
         method = 'POST';
          regexp += '|' + create_regexp_str (cat_name);
        req.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded');
         }
      } else {
        method = 'GET';
         if (args) uri += '?' + args;
         args = null;
       }
       }
       return regexp;
       req.open (method, uri, true);
      req.onreadystatechange = function () {
        if (req.readyState != 4) return;
        if (req.status != 200 || !req.responseText || !(/^\s*[\{\[]/.test(req.responseText))) {
          if (settings.error) settings.error (req);
        } else {
          if (settings.success) settings.success (eval ('(' + req.responseText + ')'));
        }         
      };
      req.setRequestHeader ('Pragma', 'cache=yes');
      req.setRequestHeader ('Cache-Control', 'no-transform');
      req.send (args);
      return req;
     }
     }


     if (wgFormattedNamespaces['14']) {
     return makeRequest;
      HotCat.category_canonical = wgFormattedNamespaces['14'];
  })();
      HotCat.category_regexp = autoLocalize (14, 'category');
 
     }
  function armorUri (uri) {
     if (wgFormattedNamespaces['10']) {
     // Avoid protocol-relative URIs, IE7 has a bug with them in Ajax calls
      HotCat.template_regexp = autoLocalize (10, 'template');
     if (uri.length >= 2 && uri.substring(0, 2) == '//') return document.location.protocol + uri;
     }
     return uri;
   }
   }
    
 
   // Utility functions. Yes, this duplicates some functionality that also exists in other places, but
   function LoadTrigger () { this.initialize.apply (this, arguments); };
   // to keep this whole stuff in a single file not depending on any other on-wiki Javascripts, we re-do
   LoadTrigger.prototype = {
   // these few operations here.
    initialize : function (needed) {
   function bind (func, target) {
      this.queue = [];
     var f = func, tgt = target;
      this.toLoad = needed;
     return function () { return f.apply (tgt, arguments); };
    },
 
    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 setupCompleted = new LoadTrigger(1);
   // Used to run user-registered code once HotCat is fully set up and ready.
  HotCat.runWhenReady = function (callback) {setupCompleted.register(callback);};
 
  var loadTrigger = new LoadTrigger(2);
   // Used to delay running the HotCat setup until /local_defaults and localizations have been loaded.
 
   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;
 
     function afterLoad () {
      if (done) return;
      done = true;
      s.onload = s.onreadystatechange = s.onerror = null; // Properly clean up to avoid memory leaks in IE
      if (head && s.parentNode) head.removeChild (s);
      loadTrigger.loaded();
    }
 
    s.onload = s.onreadystatechange = function () { // onreadystatechange for IE, onload for all others
      if (done) return;
      if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
        afterLoad ();
      }
    };
    s.onerror = afterLoad; // Clean up, but otherwise ignore errors
    head.insertBefore (s, head.firstChild); // appendChild may trigger bugs in IE6 here
   }
   }
   function make (arg, literal) {
 
     if (!arg) return null;
   function loadJS (page) {
    return literal ? document.createTextNode (arg) : document.createElement (arg);
     load (wgServer + wgScript + '?title=' + encodeURIComponent (page) + '&action=raw&ctype=text/javascript');
   }
   }
   function param (name, uri) {
 
     if (typeof (uri) == 'undefined' || uri === null) uri = document.location.href;
   function loadURI (href) {
     var re = RegExp ('[&?]' + name + '=([^&#]*)');
    var url = href;
     var m = re.exec (uri);
     if (url.substring (0, 2) == '//') {
     if (m && m.length > 1) return decodeURIComponent(m[1]);
      url = window.location.protocol + url;
    return null;
     } else if (url.substring (0, 1) == '/') {
      url = wgServer + url;
     }
     load (url);
   }
   }
   function title (href) {
 
     if (!href) return null;
   if (HotCat.isCommonsVersion && wgServer.indexOf ('/commons') < 0) {
     var script = wgScript + '?';
     // We're running in some other wiki, which hotlinks to the Commons version. The other wiki can put local settings
     if (href.indexOf (script) == 0 || href.indexOf (wgServer + script) == 0) {
    // in this file to override the Commons settings for all user languages. For instance, if on your wiki people do
      // href="/w/index.php?title=..."
    // not like automatic saving, you'd add in that file the line HotCat.no_autocommit = true; If you hotlink, you
      return param ('title', href);
    // *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 != 'en') {
    // 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 {
     } else {
       // href="/wiki/..."
       // Load translations locally
       var prefix = wgArticlePath.replace ('$1', "");
       loadJS ('MediaWiki:Gadget-HotCat.js/' + wgUserLanguage);
      if (href.indexOf (prefix) != 0) prefix = wgServer + prefix; // Fully expanded URL?
      if (href.indexOf (prefix) == 0)
        return decodeURIComponent (href.substring (prefix.length));
     }
     }
    return null;
   } else {
  }
     loadTrigger.loaded();
  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 catually has the $1 in
    // a query parameter.
    return wgArticlePath.replace('$1', encodeURIComponent (pageName).replace(/%3A/g, ':').replace(/%2F/g, '/'));
   }
   }


   // Text modification
   // No further changes should be necessary here.
 
  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) {
   // The following regular expression strings are used when searching for categories in wikitext.
    var cat_regex = null;
  var wikiTextBlank  = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+';
    if(HotCat.template_categories[category]){
  var wikiTextBlankRE = new RegExp (wikiTextBlank, 'g');
      cat_regex = new RegExp ('\\{\\{\\s*(' + HotCat.template_regexp + '(?=\\s*:))?\\s*'
  // Regexp for handling blanks inside a category title or namespace name.
                              + '(?:' + HotCat.template_categories[category] + ')'
  // See http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Title.php?revision=104051&view=markup#l2722
                              + '\\s*(\\|.*?)?\\}\\}', 'g'
  // See also http://www.fileformat.info/info/unicode/category/Zs/list.htm
                            );
  //  MediaWiki collapses several contiguous blanks inside a page title to one single blank. It also replace a
    } else {
  // number of special whitespace characters by simple blanks. And finally, blanks are treated as underscores.
      var cat_name  = category.replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1');
  // Therefore, when looking for page titles in wikitext, we must handle all these cases.
      var initial  = cat_name.substr (0, 1);
  //  Note: we _do_ include the horizontal tab in the above list, even though the MediaWiki software for some reason
      cat_regex = new RegExp ('\\[\\[\\s*(' + HotCat.category_regexp + ')\\s*:\\s*'
  // appears to not handle it. The zero-width space \u200B is _not_ handled as a space inside titles by MW.
                              + (initial == '\\' || !HotCat.capitalizePageNames
  var wikiTextBlankOrBidi = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200B\\u200E\\u200F\\u2028-\\u202F\\u205F\\u3000]*';
                                ? initial
  // Whitespace regexp for handling whitespace between link components. Including the horizontal tab, but not \n\r\f\v:
                                : '[' + initial.toUpperCase() + initial.toLowerCase() + ']')
  // a link must be on one single line.
                              + cat_name.substring (1).replace (/[ _]/g, '[ _]')
  //  MediaWiki also removes Unicode bidi override characters in page titles (and namespace names) completely.
                              + '\\s*(\\|.*?)?\\]\\]', 'g'
  // This is *not* handled, as it would require us to allow any of [\u200E\u200F\u202A-\u202E] between any two
                            );
  // characters inside a category link. It _could_ be done though... We _do_ handle strange spaces, including the
    }
  // zero-width space \u200B, and bidi overrides between the components of a category link (adjacent to the colon,
    if (once) return cat_regex.exec (wikitext);
  // or adjacent to and inside of "[[" and "]]").
    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
  }


   function change_category (wikitext, toRemove, toAdd, key) {
   // First auto-localize the regexps for the category and the template namespaces.
 
  if (typeof (wgFormattedNamespaces) != 'undefined') {
     function find_insertionpoint (wikitext) {      
     function autoLocalize (namespaceNumber, fallback) {
       var copiedtext = wikitext.replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks)
       function create_regexp_str (name)
                              .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks);
      {
      // Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element".
        if (!name || name.length === 0) return "";
      var index = -1;
        var regex_name = "";
      findCatsRE.lastIndex = 0;
        for (var i = 0; i < name.length; i++){
      while (findCatsRE.exec(copiedtext) != null) index = findCatsRE.lastIndex;  
          var initial = name.substr (i, 1);
      // We should try to find interwiki links here, but that's for later.
          var ll = initial.toLowerCase ();
       return index;
          var ul = initial.toUpperCase ();
    }
          if (ll == ul){
            regex_name += initial;
          } else {
            regex_name += '[' + ll + ul + ']';
          }
        }
        return regex_name.replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1')
                        .replace (wikiTextBlankRE, wikiTextBlank);
       }


    var summary  = [];
      fallback = fallback.toLowerCase();
    var nameSpace = HotCat.category_canonical;
      var canonical  = wgFormattedNamespaces["" + namespaceNumber].toLowerCase();
     var cat_point = -1; // Position of removed category;
      var regexp     = create_regexp_str (canonical);
 
      if (fallback && canonical != fallback) regexp += '|' + create_regexp_str (fallback);
    if (key) key = '|' + key;
      for (var cat_name in wgNamespaceIds) {
    var keyChange = (toRemove && toAdd && toRemove == toAdd && toAdd.length > 0);
        if (  typeof (cat_name) == 'string'
    if (toRemove && toRemove.length > 0) {
            && cat_name.toLowerCase () != canonical
      var matches = find_category (wikitext, toRemove);
            && cat_name.toLowerCase () != fallback
      if (!matches || matches.length == 0) {
            && wgNamespaceIds[cat_name] == namespaceNumber)
        return {text: wikitext, 'summary': summary, error: HotCat.messages.cat_notFound.replace ('$1', toRemove)};
         {
      } else {
           regexp += '|' + create_regexp_str (cat_name);
        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;
      return regexp;
          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 (wgFormattedNamespaces['14']) {
        // If we then have two linefeeds in a row, remove one. Otherwise, if we have two non-
      HotCat.category_canonical = wgFormattedNamespaces['14'];
        // whitespace characters, insert a blank.
      HotCat.category_regexp = autoLocalize (14, 'category');
        var i = before.length - 1;
    }
        while (i >= 0 && before.charAt (i) != '\n' && before.substr (i, 1).search (/\s/) >= 0) i--;
    if (wgFormattedNamespaces['10']) {
        var j = 0;
      HotCat.template_regexp = autoLocalize (10, 'template');
        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', 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', toAdd)};
      } else {
        if (cat_point < 0)
          cat_point = find_insertionpoint (wikitext);
        var newcatstring = '[[' + nameSpace + ':' + toAdd + (key || "") + ']]';
        if (cat_point >= 0) {
          wikitext = wikitext.substring (0, cat_point) + '\n' + newcatstring + wikitext.substring (cat_point);
        } else {
          if (wikitext.length > 0 && wikitext.substr (wikitext.length - 1, 1) != '\n')
            wikitext += '\n';
          if (wikitext.length > 0 && wikitext.substr (wikitext.length - 2, 1) != '\n')
            wikitext += '\n';
          wikitext += newcatstring;
        }
        if (keyChange) {
          var k = key || "";
          if (k.length > 0) k = k.substr (1);
          summary.push (HotCat.messages.cat_keychange.replace ('$1', toAdd) + '"' + k + '"');
        } else {
          summary.push (HotCat.messages.cat_added.replace ('$1', toAdd));
        }
        if (HotCat.uncat_regexp) {
          var txt = wikitext.replace (HotCat.uncat_regexp, ""); // Remove "uncat" templates
          if (txt.length != wikitext.length) {
            wikitext = txt;
            summary.push (HotCat.messages.uncat_removed);
          }
        }
      }
    }
    return {text: wikitext, 'summary': summary, error: null};
   }
   }
 
 
   if (wgAction == 'edit') {
   // Utility functions. Yes, this duplicates some functionality that also exists in other places, but
    // Legacy code based on URI parameters, can add/remove/change only one single category. Still
  // to keep this whole stuff in a single file not depending on any other on-wiki Javascripts, we re-do
    // used for single-category changes.
  // these few operations here.
     var toRemove = param ('hotcat_removecat');
  function bind (func, target) {
     var toAdd    = param ('hotcat_newcat');
     var f = func, tgt = target;
     if (toAdd) {
    return function () { return f.apply (tgt, arguments); };
      toAdd = toAdd.replace (/_/g, ' ').replace (/^\s+|\s+$/g, "");
  }
      if (toAdd.length == 0) {
  function make (arg, literal) {
        toAdd = null;
     if (!arg) return null;
      } else if (HotCat.capitalizePageNames) {
     return literal ? document.createTextNode (arg) : document.createElement (arg);
        toAdd = capitalize (toAdd);
  }
      }
  function param (name, uri) {
     }
    if (typeof (uri) == 'undefined' || uri === null) uri = document.location.href;
     if (toRemove) {
    var re = new RegExp ('[&?]' + name + '=([^&#]*)');
       toRemove = toRemove.replace (/_/g, ' ').replace (/^\s+|\s+$/g, "");
    var m = re.exec (uri);
       if (toRemove.length == 0) {
    if (m && m.length > 1) return decodeURIComponent(m[1]);
        toRemove = null;
    return null;
       } else if (HotCat.capitalizePageNames) {
  }
         toRemove = capitalize (toRemove);
  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));
     }
     }
    if (toAdd || toRemove) {
     return null;
      addOnloadHook (function () {
        if (!document.editform || !document.editform.wpTextbox1) return;
        var comment = param ('hotcat_comment') || "";
        var cat_key = param ('hotcat_sortkey');
        var result = change_category (document.editform.wpTextbox1.value, toRemove, toAdd, cat_key);
        var do_commit = !HotCat.noCommit && !result.error && param ('hotcat_nocommit') != '1';
        document.editform.wpTextbox1.value    = result.text;
        if (result.summary && result.summary.length > 0)
          document.editform.wpSummary.value    = HotCat.messages.prefix + result.summary.join ('; ') + comment + HotCat.messages.using;
        document.editform.wpMinoredit.checked = true ;
        if (result.error) alert (result.error);
        if (do_commit) {
          // Hide the entire edit section so as not to tempt the user into editing...
          var content =    document.getElementById ('bodyContent')      // monobook & vector skin
                        || document.getElementById ('mw_contentholder')  // modern skin
                        || document.getElementById ('article');          // classic skins
          if (content) content.style.display = 'none';
          document.editform.submit();
        }   
      });
    }
     return;
   }
   }
 
   function hasClass (elem, name) {
  // The real HotCat UI
     return (' ' + elem.className + ' ').indexOf (' ' + name + ' ') >= 0;
 
  }
   function evtKeys (e) {
  function capitalize (str) {
     e = e || window.event || window.Event; // W3C, IE, Netscape
    if (!str || str.length === 0) return str;
    var code = 0;
     return str.substr(0, 1).toUpperCase() + str.substr (1);
    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) {
   function wikiPagePath (pageName) {
     e = e || window.event || window.Event; // W3C, IE, Netscape
     // Note: do not simply use encodeURI, it doesn't encode '&', which might break if wgArticlePath actually has the $1 in
     if (typeof (e.preventDefault) != 'undefined') {
     // a query parameter.
      e.preventDefault ();
    return wgArticlePath.replace('$1', encodeURIComponent (pageName).replace(/%3A/g, ':').replace(/%2F/g, '/'));
      e.stopPropagation ();
    } else
      e.cancelBubble = true;
    return false;
   }
   }
    
   function substitute (str, map) {
  var catLine     = null;
    // Replace $1, $2, or ${key1}, ${key2} by values from map. $$ is replaced by a single $.
  var onUpload    = false;  
    return str.replace(
  var editors      = [];
      /\$(\$|(\d+)|\{([^{}]+)\})/g
 
     ,function (match, dollar, idx, key) {
  var commitButton = null;
        if (dollar == '$') return '$';
   var commitForm  = null;
        var k = key || idx;
   var multiSpan    = null;
        var replacement = typeof (map[k]) === 'function' ? map[k](match, k) : map[k];
        return typeof (replacement) === 'string' ? replacement : (replacement || match);
      }
    );
   }
 
   // Text modification


   var pageText    = null;
   var findCatsRE =
  var pageTime     = null;
     new RegExp ('\\[\\[' + wikiTextBlankOrBidi + '(?:' + HotCat.category_regexp + ')' + wikiTextBlankOrBidi + ':[^\\]]+\\]\\]', 'g');
  var pageWatched  = false;
  var watchCreate  = false;
  var watchEdit    = false;
  var minorEdits  = false;


   var is_rtl      = false;
   function replaceByBlanks (match) {
   var serverTime  = null;
    return match.replace(/(\s|\S)/g, ' '); // /./ doesn't match linebreaks. /(\s|\S)/ does.
   }


   function setMultiInput () {
   function find_category (wikitext, category, once) {
     if (commitButton || onUpload) return;
    var cat_regex = null;
    commitButton = make ('input');
     if(HotCat.template_categories[category]){
    commitButton.type  = 'button';
      cat_regex = new RegExp ('\\{\\{' + wikiTextBlankOrBidi + '(' + HotCat.template_regexp + '(?=' + wikiTextBlankOrBidi + ':))?' + wikiTextBlankOrBidi
    commitButton.value = HotCat.messages.commit;
                              + '(?:' + HotCat.template_categories[category] + ')'
    commitButton.onclick = multiSubmit;
                              + wikiTextBlankOrBidi + '(\\|.*?)?\\}\\}', 'g'
    if (multiSpan) {
                            );
      multiSpan.parentNode.replaceChild (commitButton, multiSpan);
     } else {
     } else {
       catLine.appendChild (commitButton);
       var cat_name  = category.replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1');
      var initial  = cat_name.substr (0, 1);
      cat_regex = new RegExp ('\\[\\[' + wikiTextBlankOrBidi + '(' + HotCat.category_regexp + ')' + wikiTextBlankOrBidi + ':' + wikiTextBlankOrBidi
                              + (initial == '\\' || !HotCat.capitalizePageNames
                                ? initial
                                : '[' + initial.toUpperCase() + initial.toLowerCase() + ']')
                              + cat_name.substring (1).replace (wikiTextBlankRE, wikiTextBlank)
                              + wikiTextBlankOrBidi + '(\\|.*?)?\\]\\]', 'g'
                            );
     }
     }
    // Get the preferences, so that we can set wpWatchthis correctly later on. Also get information
     if (once) return cat_regex.exec (wikitext);
    // about whether the current user watches the page. Must use Ajax here.
    var copiedtext = wikitext.replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks)
     if (wgUserName) {
                            .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks);
      var request = sajax_init_object ();    
    var result = [];
      request.open
    var curr_match = null;
        ('GET', wgServer + wgScriptPath + '/api.php?format=json&action=query&meta=userinfo&uiprop=options&prop=info&inprop=watched&titles=' + encodeURIComponent (wgPageName), true);
    while ((curr_match = cat_regex.exec (copiedtext)) != null) {
      request.onreadystatechange =
       result.push ({match : curr_match});
        function () {
          if (request.readyState != 4) return;
          if (request.status == 200 && request.responseText && request.responseText.charAt(0) == '{') {
            var json = eval ('(' + request.responseText + ')');
            if (json && json.query) {
              if (json.query.userinfo && json.query.userinfo.options) {
                watchCreate = json.query.userinfo.options.watchcreations == '1';
                watchEdit  = json.query.userinfo.options.watchdefault == '1';
                minorEdits  = json.query.userinfo.options.minordefault == 1;
              }
              if (json.query.pages) {
                for (var p in json.query.pages) {
                  pageWatched = typeof (json.query.pages[p].watched) == 'string';
                  break;
                }
              }
            }           
          }
        };
       request.setRequestHeader ('Pragma', 'cache=yes');
      request.setRequestHeader ('Cache-Control', 'no-transform');
      request.send (null);      
     }
     }
    result.re = cat_regex;
    return result; // An array containing all matches, with positions, in result[i].match
   }
   }
    
 
   function checkMultiInput () {
   var interlanguageRE = null;
     if (!commitButton) return;
 
    var has_changes = false;
   function change_category (wikitext, toRemove, toAdd, key, is_hidden) {
    for (var i = 0; i < editors.length; i++) {
 
      if (editors[i].state != CategoryEditor.UNCHANGED) {
     function find_insertionpoint (wikitext) {
         has_changes = true;
      var copiedtext = wikitext.replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks)
         break;
                              .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks);
      // Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element".
      var index = -1;
      findCatsRE.lastIndex = 0;
      while (findCatsRE.exec(copiedtext) != null) index = findCatsRE.lastIndex;
      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};
     }
     }
    commitButton.disabled = !has_changes;
  }


  function currentTimestamp () {
     var summary  = [];
     var now = new Date();
     var nameSpace = HotCat.category_canonical;
     var ts  = "" + now.getUTCFullYear();
     var cat_point = -1; // Position of removed category;
     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 performChanges () {
     if (key) key = '|' + key;
     // Don't use the edit API or LAPI, it's always bothersome to report back errors like edit
     var keyChange = (toRemove && toAdd && toRemove == toAdd && toAdd.length > 0);
    // conflicts. Instead, make one remote call (blocking, because we can't continue anyway if
    if (toRemove && toRemove.length > 0) {
    // it doesn't succeed), getting the page text. Perform the changes on the text, then construct
      var matches = find_category (wikitext, toRemove);
    // a form to submit all this as a diff.
       if (!matches || matches.length === 0) {
    // Note: we have to do this even if we already got the page text. Other scripts may have already 
        return {text: wikitext, 'summary': summary, error: HotCat.messages.cat_notFound.replace (/\$1/g, toRemove)};
    // edited the text, and we don't necessarily get an edit conflict with ourself. Use case: open
       } else {
    // a file page, add an image note through ImageAnnotator, then change the categories. If HotCat
        var before = wikitext.substring (0, matches[0].match.index);
    // still operates on the page text loaded initially, it'll delete the just added note again, and
         var after  = wikitext.substring (matches[0].match.index + matches[0].match[0].length);
    // somehow the MediaWiki software does not produce an edit conflict.
         if (matches.length > 1) {
     if (wgArticleId != 0) {
           // Remove all occurrences in after
      var request = sajax_init_object ();
           matches.re.lastIndex = 0;
      var uri    = wgServer + wgScriptPath
           after = after.replace (matches.re, "");
                  + '/api.php?format=json&action=query&titles=' + encodeURIComponent (wgPageName)
                  + '&prop=info%7Crevisions&inprop=watched&rvprop=content%7Ctimestamp&meta=siteinfo';
      request.open ('GET', uri, false); // Yes, synchronous
      request.send (null);
      if (request.status == 200 && request.responseText && request.responseText.charAt(0) == '{') {
        setPage (eval ('(' + request.responseText + ')'));
       }
    } else {
      pageText = null;
    }
    if (pageText === null) {
      alert (HotCat.messages.multi_error);
      return;
    }
    // Create a form and submit it
    if (!commitForm) {
      var formContainer = make ('div');
      formContainer.style.display = 'none';
      document.body.appendChild (formContainer);
      formContainer.innerHTML =
          '<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="wpEdittime" />'
        + '<input type="hidden" name="wpStarttime" />'
        + '<input type="hidden" name="wpDiff" value="wpDiff" />'
        + '</form>';
       commitForm = formContainer.firstChild;
    }
    var result = { text : pageText };
    var changed = [], added = [], deleted = [], changes = 0;
    for (var i=0; i < editors.length; i++) {
      if (editors[i].state == CategoryEditor.CHANGED) {
         result = change_category (
            result.text
          , editors[i].originalCategory
          , editors[i].currentCategory
          , editors[i].currentKey
        );
         if (!result.error) {
           changes++;
           if (!editors[i].originalCategory || editors[i].originalCategory.length == 0) {
            added.push (editors[i].currentCategory);
           } else {
            changed.push ({from : editors[i].originalCategory, to : editors[i].currentCategory});
          }
         }
         }
      } else if (   editors[i].state == CategoryEditor.DELETED
        if (toAdd) {
                && editors[i].originalCategory
          nameSpace = matches[0].match[1] || nameSpace;
                && editors[i].originalCategory.length > 0)
          if (key == null) key = matches[0].match[2]; // Remember the category key, if any.
      {
        }
         result = change_category (result.text, editors[i].originalCategory, null, null);
        // Remove whitespace (properly): strip whitespace, but only up to the next line feed.
         if (!result.error) {
        // If we then have two linefeeds in a row, remove one. Otherwise, if we have two non-
          changes++;
        // whitespace characters, insert a blank.
           deleted.push (editors[i].originalCategory);
        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));
          }
         }
         }
       }
       }
     }
     }
     // Fill in the form and submit it
     if (toAdd && toAdd.length > 0) {
    commitForm.wpMinoredit.checked = minorEdits;
       var matches = find_category (wikitext, toAdd);
    commitForm.wpWatchthis.checked = wgArticleId == 0 && watchCreate || watchEdit || pageWatched;
      if (matches && matches.length > 0) {
    if (wgArticleId > 0) {
        return {text: wikitext, 'summary': summary, error : HotCat.messages.cat_exists.replace (/\$1/g, toAdd)};
       if (changes == 1) {
      } else {
        if (result.summary && result.summary.length > 0)
         var onCat = false;
          commitForm.wpSummary.value = HotCat.messages.prefix + result.summary.join ('; ') + HotCat.messages.using;
        if (cat_point < 0) {
         commitForm.wpMinoredit.checked = true;
          var point = find_insertionpoint (wikitext);
      } else if (changes > 1) {
          cat_point = point.idx;
        var summary = [];
          onCat = point.onCat;
        var shortSummary = [];
        } else {
        // Deleted
           onCat = true;
        for (var i=0; i < deleted.length; i++) {
           summary.push ('−[[' + HotCat.category_canonical + ':' + deleted[i] + ']]');
         }
         }
         if (deleted.length == 1)
         var newcatstring = '[[' + nameSpace + ':' + toAdd + (key || "") + ']]';
          shortSummary.push ('[[' + HotCat.category_canonical + ':' + deleted[0] + ']]');
         if (cat_point >= 0) {
         else if (deleted.length > 1)
           var suffix = wikitext.substring (cat_point);
          shortSummary.push ('− ' + HotCat.messages.multi_change.replace ('$1', "" + deleted.length));
          wikitext = wikitext.substring (0, cat_point) + (cat_point > 0 ? '\n' : "") + newcatstring + (!onCat ? '\n' : "");
        // Added
          if (suffix.length > 0 && suffix.substr(0, 1) != '\n') {
        for (var i=0; i < added.length; i++) {
             wikitext += '\n' + suffix;
           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
        for (var i=0; i < changed.length; i++) {
          if (changed[i].from != changed[i].to) {
             summary.push ('±[[' + HotCat.category_canonical + ':' + changed[i].from + ']]→[['
                        + HotCat.category_canonical + ':' + changed[i].to + ']]');
           } else {
           } else {
             summary.push ('±[[' + HotCat.category_canonical + ':' + changed[i].from + ']]');
             wikitext += suffix;
           }
           }
        } else {
          if (wikitext.length > 0 && wikitext.substr (wikitext.length - 1, 1) != '\n')
            wikitext += '\n';
          wikitext += '\n' + newcatstring;
         }
         }
         if (changed.length == 1) {
         if (keyChange) {
           if (changed[0].from != changed[0].to) {
          var k = key || "";
            shortSummary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + ']]→[['
           if (k.length > 0) k = k.substr (1);
                        + HotCat.category_canonical + ':' + changed[0].to + ']]');
           summary.push (substitute (HotCat.messages.cat_keychange, [null, toAdd, k]));
           } else {
         } else {
            shortSummary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + ']]');
           summary.push (HotCat.messages.cat_added.replace (/\$1/g, toAdd));
          }
         } else if (changed.length > 1) {
           shortSummary.push ('± ' + HotCat.messages.multi_change.replace ('$1', "" + changed.length));
         }
         }
         if (summary.length > 0) {
         if (HotCat.uncat_regexp && !is_hidden) {
           summary = summary.join ('; ');
           var txt = wikitext.replace (HotCat.uncat_regexp, ""); // Remove "uncat" templates
           if (summary.length > 200 - HotCat.messages.prefix.length - HotCat.messages.using.length) {
           if (txt.length != wikitext.length) {
             summary = shortSummary.join ('; ');
            wikitext = txt;
             summary.push (HotCat.messages.uncat_removed);
           }
           }
          commitForm.wpSummary.value = HotCat.messages.prefix + summary + HotCat.messages.using;
         }
         }
       }
       }
     }
     }
     commitForm.wpTextbox1.value = result.text;
     return {text: wikitext, 'summary': summary, error: null};
    commitForm.wpStarttime.value = serverTime || currentTimestamp ();
    commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value;
    commitForm.submit();
   }
   }


   function resolveMulti (toResolve, callback) {
  // The real HotCat UI
     for (var i = 0; i < toResolve.length; i++) {
 
       toResolve[i].dab = null;
   function evtKeys (e) {
       toResolve[i].dabInput = toResolve[i].lastInput;
     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;
     }
     }
     if (noSuggestions) {
     return code;
      callback (toResolve);
  }
      return;
  function evtKill (e) {
    }
     e = e || window.event || window.Event; // W3C, IE, Netscape
    var request = sajax_init_object ();
     if (typeof (e.preventDefault) != 'undefined') {
    if (!request) {
       e.preventDefault ();
      noSuggestions = true;
       e.stopPropagation ();
      callback (toResolve);
     } else
      return;
       e.cancelBubble = true;
    }
    return false;
     var url = wgServer + wgScriptPath + '/api.php';
  }
    // Use %7C instead of |, otherwise Konqueror insists on re-encoding the arguments, resulting in doubly encoded
  function addEvent (node, evt, f, capture)
     // category names. (That is a bug in Konqueror. Other browsers don't have this problem.)
  {
    var args = 'action=query&prop=info%7Clinks%7Ccategories&plnamespace=14&pllimit=50'
    if (window.jQuery && (!capture || !node.addEventListener)) window.jQuery (node).bind (evt, f);
            + '&cllimit=' + (toResolve.length * 10) // Category limit is global, link limit is per page
    else if (node.attachEvent) node.attachEvent ('on' + evt, f);
            + '&format=json&titles=';
     else if (node.addEventListener) node.addEventListener (evt, f, capture);
    for (var i = 0; i < toResolve.length; i++) {
     else node['on' + evt] = f;
      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) {
          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 catLine      = null;
    var cats     = page.categories;
  var onUpload     = false;
    var lks     = page.links;
  var editors     = [];
    var is_dab   = false;
 
    var is_redir = typeof (page.redirect) == 'string'; // Hard redirect?
  var commitButton = null;
     if (!is_redir && cats && (HotCat.disambig_category || HotCat.redir_category)) {
  var commitForm   = null;
      for (var c = 0; c < cats.length; c++) {
  var multiSpan    = null;
        var cat = cats[c]['title'];
 
        // Strip namespace prefix
  var pageText    = null;
        if (cat) {
  var pageTime     = null;
          cat = cat.substring (cat.indexOf (':') + 1).replace(/_/g, ' ');
  var pageWatched  = false;
          if (cat == HotCat.disambig_category) {
  var watchCreate  = false;
            is_dab = true; break;
  var watchEdit    = false;
          } else if (cat == HotCat.redir_category) {
  var minorEdits  = false;
            is_redir = true; break;
  var editToken    = null;
          }
 
        }
  var is_rtl      = false;
       }
  var serverTime  = null;
    }
 
    if (!is_redir && !is_dab) return;
  var newDOM       = false; // true if MediaWiki serves the new UL-LI DOM for categories
     if (!lks || lks.length == 0) return;
 
     var titles = [];
  function setMultiInput () {
    for (var i = 0; i < lks.length; i++) {
     if (commitButton || onUpload) return;
      if (  lks[i]['ns'] == 14                            // Category namespace
     commitButton = make ('input');
          && lks[i]['title'] && lks[i]['title'].length > 0) // Name not empty
    commitButton.type  = 'button';
      {
    commitButton.value = HotCat.messages.commit;
        // Internal link to existing thingy. Extract the page name and remove the namespace.
    commitButton.onclick = multiSubmit;
        var match = lks[i]['title'];
     if (multiSpan) {
        titles.push (match.substring (match.indexOf (':') + 1));
       multiSpan.parentNode.replaceChild (commitButton, multiSpan);
        if (is_redir) break;
    } else {
      }
      catLine.appendChild (commitButton);
     }
    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) {
   function checkMultiInput () {
     if (!params || !params.query || !params.query.pages) return;    
     if (!commitButton) return;
    for (var p in params.query.pages) resolveOne (params.query.pages[p], toResolve);
     var has_changes = false;
  }
 
  function multiSubmit () {
     var toResolve = [];
     for (var i = 0; i < editors.length; i++) {
     for (var i = 0; i < editors.length; i++) {
       if (editors[i].state == CategoryEditor.CHANGE_PENDING || editors[i].state == CategoryEditor.OPEN)
       if (editors[i].state != CategoryEditor.UNCHANGED) {
         toResolve.push (editors[i]);
        has_changes = true;
         break;
      }
     }
     }
     if (toResolve.length == 0) {
     commitButton.disabled = !has_changes;
      performChanges ();
  }
      return;
 
    }
  function currentTimestamp () {
    resolveMulti (
    var now = new Date();
        toResolve
    var ts  = "" + now.getUTCFullYear();
      , function (resolved) {
    function two (s) { return s.substr (s.length - 2); }
          var firstDab = null;
    ts = ts
          var dontChange = false;
      + two ('0' + (now.getUTCMonth() + 1))
          for (var i = 0; i < resolved.length; i++) {
      + two ('0' + now.getUTCDate())
            if (resolved[i].lastInput != resolved[i].dabInput) {
      + two ('00' + now.getUTCHours())
              // We didn't disable all the open editors, but we did asynchronous calls. It is
      + two ('00' + now.getUTCMinutes())
              // theoretically possible that the user changed something...
      + two ('00' + now.getUTCSeconds());
              dontChange = true;
    return ts;
            } else {
  }
              if (resolved[i].dab) {
 
                if (!firstDab) firstDab = resolved[i];
  function initiateEdit (doEdit, failure) {
              } else {
    // Must use Ajax here to get the user options and the edit token.
                if (resolved[i].acceptCheck(true)) resolved[i].commit();
    getJSON ({
              }
      uri : wgServer + wgScriptPath + '/api.php'
            }
      ,data : 'format=json&action=query&titles=' + encodeURIComponent (wgPageName)
          }
        + '&prop=info%7Crevisions%7Clanglinks&inprop=watched&intoken=edit&rvprop=content%7Ctimestamp&lllimit=500'
          if (firstDab) {
        + '&rvlimit=1&rvstartid=' + wgCurRevisionId
            CategoryEditor.makeActive (firstDab);
        + '&meta=siteinfo%7Cuserinfo&uiprop=options'
          } else if (!dontChange) {
      ,success : function (json) { setPage(json); doEdit(failure); }
            performChanges ();
      ,error : function (req) { failure(req.status + ' ' + req.statusText); }
          }
    });
        }
    );
   }
   }


   var cat_prefix = null;
   function multiChangeMsg (count) {
  var noSuggestions = false;
    var msg = HotCat.messages.multi_change;
  var suggestionEngines = {
    if (typeof (msg) != 'string' && msg.length) {
    opensearch :
       if (window.mediaWiki && window.mediaWiki.language && window.mediaWiki.language.convertPlural) {
       { uri    : '/api.php?format=json&action=opensearch&namespace=14&limit=30&search=Category:$1' // $1 = search term
        msg = window.mediaWiki.language.convertPlural (count, msg);
      ,handler : // Function to convert result of uri into an array of category names
      } else {
          function (responseText, queryKey) {
        msg = msg[msg.length-1];
            if (responseText.charAt (0) != '[') return null;
      }
            var queryResult = eval ('(' + responseText + ')');
    }
            if (   queryResult != null && queryResult.length == 2
    return substitute (msg, [null, "" + count]);   
                && queryResult[0].toLowerCase() == 'category:' + queryKey.toLowerCase()
  }
              )
 
            {
  function performChanges (failure, singleEditor) {
              var titles = queryResult[1];
    if (pageText === null) {
              if (!cat_prefix) cat_prefix = new RegExp ('^(' + HotCat.category_regexp + ':)');
      failure (HotCat.messages.multi_error);
              for (var i = 0; i < titles.length; i++) {
      return;
                cat_prefix.lastIndex = 0;
    }
                var m = cat_prefix.exec (titles[i]);
    // Backwards compatibility after message change (added $2 to cat_keychange)
                if (m && m.length > 1) {
    if (HotCat.messages.cat_keychange.indexOf ('$2') < 0) {
                  titles[i] = titles[i].substring (titles[i].indexOf (':') + 1); // rm namespace
      HotCat.messages.cat_keychange += '"$2"';
                } else {
    }
                  titles.splice (i, 1); // Nope, it's not a category after all.
    // Create a form and submit it. We don't use the edit API (api.php?action=edit) because
                  i--;
    // (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
              return titles;
    // 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.
            return null;    
    var action;
    if (singleEditor && !singleEditor.noCommit && !HotCat.no_autocommit && editToken) {
      commitForm.wpEditToken.value = editToken;
      action = commitForm.wpDiff;
      if (action) action.name = action.value = 'wpSave';
    } else {
      action = commitForm.wpSave;
      if (action) action.name = action.value = 'wpDiff';
    }
    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;
        }
       }
       }
     ,internalsearch :
     }
      { uri    : '/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1'
    if (error !== null) { // Do not commit if there were errors
      ,handler :
      action = commitForm.wpSave;
          function (responseText, queryKey) {
      if (action) action.name = action.value = 'wpDiff';
            if (responseText.charAt (0) != '{') return null;
    }
            var queryResult = eval ('(' + responseText + ')');
    if (changes === 0 && !singleEditor) return;
            if (queryResult && queryResult.query && queryResult.query.allpages) {
    // Fill in the form and submit it
              var titles = queryResult.query.allpages;
    commitForm.wpAutoSummary.value = 'd41d8cd98f00b204e9800998ecf8427e'; // MD5 hash of the empty string
              var key    = queryKey.toLowerCase();
    commitForm.wpMinoredit.checked = minorEdits;
              for (var i = 0; i < titles.length; i++) {
    commitForm.wpWatchthis.checked = wgArticleId == 0 && watchCreate || watchEdit || pageWatched;
                titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace
    if (wgArticleId > 0 || !!singleEditor) {
                if (titles[i].toLowerCase().indexOf (key) != 0) {
      if (changes == 1) {
                  titles.splice (i, 1); // Doesn't start with the query key
        if (result.summary && result.summary.length > 0)
                  i--;
          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) {
              return titles;
        var summary = [];
             }
        var shortSummary = [];
            return null;
        // 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 ('− ' + multiChangeMsg (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 ('+ ' + multiChangeMsg (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 + ']]');
           }
           }
      }
        }
    ,subcategories :
        if (changed.length == 1) {
      { uri    : '/api.php?format=json&action=query&list=categorymembers&cmnamespace=14&cmlimit=max&cmtitle=Category:$1'
           if (changed[0].from != changed[0].to) {
      ,handler :
             shortSummary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + arrow
           function (responseText, queryKey) {
                        + HotCat.category_canonical + ':' + changed[0].to + ']]');
             if (responseText.charAt (0) != '{') return null;
          } else {
             var queryResult = eval ('(' + responseText + ')');
             shortSummary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + ']]');
            if (queryResult && queryResult.query && queryResult.query.categorymembers) {
          }
              var titles = queryResult.query.categorymembers;
        } else if (changed.length > 1) {
              for (var i = 0; i < titles.length; i++) {
          shortSummary.push ('± ' + multiChangeMsg (changed.length));
                titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace
        }
              }
        if (summary.length > 0) {
              return titles;
          summary = summary.join (HotCat.messages.separator);
             }
          if (summary.length > 200 - HotCat.messages.prefix.length - HotCat.messages.using.length) {
            return null;
             summary = shortSummary.join (HotCat.messages.separator);
           }
           }
          commitForm.wpSummary.value = HotCat.messages.prefix + summary + HotCat.messages.using;
        }
       }
       }
  ,parentcategories :
    }
      { uri     : '/api.php?format=json&action=query&prop=categories&titles=Category:$1&cllimit=max'
     commitForm.wpTextbox1.value = result.text;
      ,handler :
    commitForm.wpStarttime.value = serverTime || currentTimestamp ();
          function (responseText, queryKey) {
    commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value;
            if (responseText.charAt (0) != '{') return null;
    // Submit the form in a way that triggers onsubmit events: commitForm.submit() doesn't.
            var queryResult = eval ('(' + responseText + ')');
    commitForm.hcCommit.click();
            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 = {
   function resolveMulti (toResolve, callback) {
    searchindex : {name: 'Search index', engines: ['opensearch'], cache: {}, show: true, temp: false}
     for (var i = 0; i < toResolve.length; i++) {
  ,pagelist    : {name: 'Page list', engines: ['internalsearch'], cache: {}, show: true, temp: false}
       toResolve[i].dab = null;
  ,combined    : {name: 'Combined search', engines: ['opensearch', 'internalsearch'], cache: {}, show: true, temp: false}
      toResolve[i].dabInput = toResolve[i].lastInput;
  ,subcat      : {name: 'Subcategories', engines: ['subcategories'], cache: {}, show: true, temp: true}
    }
  ,parentcat  : {name: 'Parent categories', engines: ['parentcategories'], cache: {}, show: true, temp: true}
    if (noSuggestions) {
  };
      callback (toResolve);
 
      return;
   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;
     // Use %7C instead of |, otherwise Konqueror insists on re-encoding the arguments, resulting in doubly encoded
     if (toActivate.dab) {
    // category names. (That is a bug in Konqueror. Other browsers don't have this problem.)
       toActivate.showSuggestions (toActivate.dab, false, null, null); // do autocompletion, no key, no engine selector
    var args = 'action=query&prop=info%7Clinks%7Ccategories%7Ccategoryinfo&plnamespace=14'
       toActivate.dab = null;
            + '&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';
     }
     }
  };
    getJSON({
    
      uri : wgServer + wgScriptPath + '/api.php'
   CategoryEditor.prototype = {
    ,data : args
      
    ,success: function (json) { resolveRedirects (toResolve, json); callback (toResolve); }
     initialize : function (line, span, after, key) {
    ,error: function (req) { if (!req) noSuggestions = true; callback (toResolve); }
      // 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) {
   function resolveOne (page, toResolve) {
        this.isAddCategory = true;
    var cats    = page.categories;
        // Create add span and append to catLinks
    var lks      = page.links;
        this.originalCategory = "";
     var is_dab  = false;
        this.originalKey = null;
     var is_redir = typeof (page.redirect) == 'string'; // Hard redirect?
        this.originalExists  = false;
    var is_hidden = page.categoryinfo && typeof (page.categoryinfo.hidden) == 'string';
         span = make ('span');
    for (var j = 0; j < toResolve.length; j++) {
         span.className = 'noprint';
       if (toResolve[j].dabInput != page.title.substring (page.title.indexOf (':') + 1)) continue;
         if (key) {
      toResolve[j].currentHidden = is_hidden;
           span.appendChild (make (' | ', true));
    }
           if (after) {
    if (!is_redir && cats && (HotCat.disambig_category || HotCat.redir_category)) {
             after.parentNode.insertBefore (span, after.nextSibling);
      for (var c = 0; c < cats.length; c++) {
            after = after.nextSibling;
         var cat = cats[c]['title'];
           } else {
         // Strip namespace prefix
             line.appendChild (span);
         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;
           }
           }
        } else if (line.firstChild) {
          span.appendChild (make (' ', true));
          line.appendChild (span);
         }
         }
        this.linkSpan = make ('span');
      }
        this.linkSpan.className = 'noprint';
    }
        var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this);
    if (!is_redir && !is_dab) return;
        lk.appendChild (make (HotCat.links.add, true)); lk.title = HotCat.tooltips.add;     
    if (!lks || lks.length === 0) return;
         this.linkSpan.appendChild (lk);
    var titles = [];
         span = make ('span');
    for (var i = 0; i < lks.length; i++) {
         span.className = 'noprint';
      if (   lks[i]['ns'] == 14                            // Category namespace
         if (is_rtl) span.dir = 'rtl';
          && lks[i]['title'] && lks[i]['title'].length > 0) // Name not empty
        span.appendChild (this.linkSpan);
      {
        if (after)
         // Internal link to existing thingy. Extract the page name and remove the namespace.
          after.parentNode.insertBefore (span, after.nextSibling);
         var match = lks[i]['title'];
        else
         titles.push (match.substring (match.indexOf (':') + 1));
          line.appendChild (span);
         if (is_redir) break;
        this.normalLinks = null;
      }
        this.undelLink = null;
    }
         this.catLink = null;
    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 {
       } else {
         if (is_rtl) span.dir = 'rtl';
         toResolve[j].inputExists = true; // Might actually be wrong...
        this.isAddCategory = false;
         toResolve[j].icon.src = armorUri(HotCat.existsYes);
        this.catLink = span.firstChild;
         toResolve[j].text.value =
         this.originalCategory = after;
          titles[0] + (toResolve[j].currentKey != null ? '|' + toResolve[j].currentKey : "");
        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.line              = line;
    }
      this.engine            = HotCat.suggestions;
  }
      this.span              = span;
 
      this.currentCategory    = this.originalCategory;
  function resolveRedirects (toResolve, params) {
      this.currentExists      = this.originalExists;
    if (!params || !params.query || !params.query.pages) return;
      this.currentKey        = this.originalKey;
     for (var p in params.query.pages) resolveOne (params.query.pages[p], toResolve);
      this.state              = CategoryEditor.UNCHANGED;
  }
      this.lastSavedState     = CategoryEditor.UNCHANGED;
 
      this.lastSavedCategory  = this.originalCategory;
  function multiSubmit () {
      this.lastSavedKey      = this.originalKey;
    var toResolve = [];
      this.lastSavedExists    = this.originalExists;
    for (var i = 0; i < editors.length; i++) {
      editors[editors.length] = this;
      if (editors[i].state == CategoryEditor.CHANGE_PENDING || editors[i].state == CategoryEditor.OPEN)
    },
         toResolve.push (editors[i]);
   
    }
    makeLinkSpan : function () {
    if (toResolve.length === 0) {
      this.normalLinks = make ('span');
      initiateEdit (function (failure) {performChanges (failure);}, function (msg) {alert (msg);});
      var lk = null;
      return;
      if (this.originalCategory && this.originalCategory.length > 0) {
    }
        lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.remove, this);
    resolveMulti (
        lk.appendChild (make (HotCat.links.remove, true)); lk.title = HotCat.tooltips.remove;
         toResolve
        this.normalLinks.appendChild (make (' ', true));
      , function (resolved) {
         this.normalLinks.appendChild (lk);
           var firstDab = null;
      }
           var dontChange = false;
      if (!HotCat.template_categories[this.originalCategory]) {
          for (var i = 0; i < resolved.length; i++) {
        lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this);
            if (resolved[i].lastInput != resolved[i].dabInput) {
        lk.appendChild (make (HotCat.links.change, true)); lk.title = HotCat.tooltips.change;
              // We didn't disable all the open editors, but we did asynchronous calls. It is
        this.normalLinks.appendChild (make (' ', true));
              // theoretically possible that the user changed something...
         this.normalLinks.appendChild (lk);
              dontChange = true;
        if (!noSuggestions && HotCat.use_up_down) {
            } else {
           this.upDownLinks = make ('span');
              if (resolved[i].dab) {
           lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.down, this);
                if (!firstDab) firstDab = resolved[i];
          lk.appendChild (make (HotCat.links.down, true)); lk.title = HotCat.tooltips.down;
              } else {
          this.upDownLinks.appendChild (make (' ', true));
                if (resolved[i].acceptCheck(true)) resolved[i].commit();
          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));
           if (firstDab) {
          this.upDownLinks.appendChild (lk);
            CategoryEditor.makeActive (firstDab);
           this.normalLinks.appendChild (this.upDownLinks);
           } else if (!dontChange) {
            initiateEdit (function (failure) {performChanges (failure);}, function (msg) {alert (msg);});
           }
         }
         }
      }
    );
      this.linkSpan = make ('span');
  }
      this.linkSpan.className = 'noprint';
 
      this.linkSpan.appendChild (this.normalLinks);
  var cat_prefix = null;
       this.undelLink = make ('span');
  var noSuggestions = false;
      this.undelLink.style.display = 'none';
  var suggestionEngines = {
      lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.restore, this);
    opensearch :
      lk.appendChild (make (HotCat.links.restore, true)); lk.title = HotCat.tooltips.restore;
       { uri    : '/api.php?format=json&action=opensearch&namespace=14&limit=30&search=Category:$1' // $1 = search term
      this.undelLink.appendChild (make (' ', true));
      ,handler : // Function to convert result of uri into an array of category names
      this.undelLink.appendChild (lk);
          function (queryResult, queryKey) {
      this.linkSpan.appendChild (this.undelLink);
            if (  queryResult != null && queryResult.length == 2
    },
                && queryResult[0].toLowerCase() == 'category:' + queryKey.toLowerCase()
   
              )
    makeForm : function () {
             {
      var form = make ('form');
               var titles = queryResult[1];
      form.method = 'POST'; form.onsubmit = bind (this.accept, this);
               if (!cat_prefix) cat_prefix = new RegExp ('^(' + HotCat.category_regexp + ':)');
      this.form = form;
               for (var i = 0; i < titles.length; i++) {
     
                 cat_prefix.lastIndex = 0;
      var text = make ('input'); text.type = 'text'; text.size = HotCat.editbox_width;
                 var m = cat_prefix.exec (titles[i]);
      if (!noSuggestions) {
                 if (m && m.length > 1) {
        text.onkeyup =
                  titles[i] = titles[i].substring (titles[i].indexOf (':') + 1); // rm namespace
          bind (
                } else {
             function (evt) {
                   titles.splice (i, 1); // Nope, it's not a category after all.
              evt = evt || window.event || window.Event; // W3C, IE, Netscape
                  i--;
               var key = evt.keyCode || 0;
               if (key == 38 || key == 40) { // Up and down arrows
                // In case a browser doesn't generate keypress events for arrow keys...
                if (this.keyCount == 0) return this.processKey (evt);
               } else {
                if (key == 27) this.resetKeySelection (); // ESC
                 // 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 titles;
             }
             }
          ,this
            return null;
           );
           }
        text.onkeydown =
      }
           bind (
    ,internalsearch :
             function (evt) {
      { uri    : '/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1'
               evt = evt || window.event || window.Event; // W3C, IE, Netscape
      ,handler :
               this.lastKey = evt.keyCode || 0;
           function (queryResult, queryKey) {
               this.keyCount = 0;
             if (queryResult && queryResult.query && queryResult.query.allpages) {
              // Handle return explicitly, to override the default form submission to be able to check for ctrl
               var titles = queryResult.query.allpages;
              if (evt.keyCode == 13) this.accept (evt);
               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;
             }
             }
          ,this
            return null;
           );
           }
        // 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);
    ,subcategories :
       this.text = text;
       // I don't understand why they didn't map cmnamespace=14 automatically to cmtype=subcat,
        
       // which gives better results and is faster.
      this.icon = make ('img');
       { uri    : '/api.php?format=json&action=query&list=categorymembers'
     
                +(function (version) {
      var list = null;
                    var m = version.match(/^(\d+)\.(\d+)/);
      if (!noSuggestions) {
                    var major = 0, minor = 0;
        list = make ('select');
                    if (m && m.length > 1) {
        list.onclick    = bind (function () { if (this.setValueFromList ()) this.textchange (); }, this);
                      major = parseInt (m[1], 10);
        list.ondblclick = bind (function (e) { if (this.setValueFromList ()) this.accept (e); }, this);
                      minor = (m.length > 2 ? parseInt (m[2], 10) : 0);
        list.onchange = bind (function (e) { this.setValueFromList (); this.text.focus(); }, this);
                    }
        list.onkeyup =
                    if (major > 1 || major === 1 && minor > 17) return '&cmtype=subcat'; // Since MW1.18
          bind (
                    return '&cmnamespace=14';
            function (evt) {
                  }
              evt = evt || window.event || window.Event; // W3C, IE, Netscape
                  )(wgVersion)
              if (evt.keyCode == 27) {
                +'&cmlimit=max&cmtitle=Category:$1'
                this.resetKeySelection ();
      ,handler :
                this.text.focus();
          function (queryResult, queryKey) {
                var self = this;
            if (queryResult && queryResult.query && queryResult.query.categorymembers) {
                window.setTimeout (function () {self.textchange (true);}, HotCat.suggest_delay);
              var titles = queryResult.query.categorymembers;
               } else if (evt.keyCode == 13) {
               for (var i = 0; i < titles.length; i++) {
                 this.accept (evt);
                 titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace
               }
               }
              return titles;
             }
             }
          ,this
            return null;
           );
          }
        if (!HotCat.fixed_search) {
      }
          var engineSelector = make ('select');
  ,parentcategories :
          for (var key in suggestionConfigs) {
      { uri    : '/api.php?format=json&action=query&prop=categories&titles=Category:$1&cllimit=max'
            if (suggestionConfigs[key].show) {
      ,handler :
              var opt = make ('option');
           function (queryResult, queryKey) {
              opt.value = key;
            if (queryResult && queryResult.query && queryResult.query.pages) {
              if (key == this.engine) opt.selected = true;
              for (var p in queryResult.query.pages) {
              opt.appendChild (make (suggestionConfigs[key].name, true));
                if (queryResult.query.pages[p].categories) {
               engineSelector.appendChild (opt);
                  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;
           }
           }
          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;   
       }
       }
  };
  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;


      // Do not use type 'submit'; we cannot detect modifier keys if we do 
  // IE6 sometimes forgets to redraw the list when editors are opened or closed.
      var OK = make ('input'); OK.type = 'button';
  // Adding/removing a dummy element helps, at least when opening editors.
      OK.value = button_label ('wpOkUploadLbl', HotCat.messages.ok);
  CategoryEditor.dummyElement  = make ('\xa0', true);
      OK.onclick = bind (this.accept, this);
 
      this.ok = OK;
  CategoryEditor.forceRedraw = function () {
       
    if (!is_ie6) return;
      var cancel = make ('input'); cancel.type = 'button';
    if (CategoryEditor.dummyElement.parentNode) {
      cancel.value = button_label ('wpCancelUploadLbl', HotCat.messages.cancel);
       document.body.removeChild (CategoryEditor.dummyElement);
      cancel.onclick = bind (this.cancel, this);
    } else {
      this.cancelButton = cancel;
       document.body.appendChild (CategoryEditor.dummyElement);
       
     }
      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) {
  CategoryEditor.makeActive = function (toActivate) {
       if (this.isAddCategory && !onUpload) {
    if (toActivate.is_active) return;
         var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one
     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;
    } else {
      if (toActivate.showsList) toActivate.displayList();
      if (toActivate.lastSelection) {
         if (is_webkit) {
          // WebKit (Safari, Chrome) has problems selecting inside focus()
          // See http://code.google.com/p/chromium/issues/detail?id=32865#c6
          window.setTimeout (
            function () { toActivate.setSelection (toActivate.lastSelection.start, toActivate.lastSelection.end); }
            ,1
          );
        } else {
          toActivate.setSelection (toActivate.lastSelection.start, toActivate.lastSelection.end);
        }
       }
       }
       if (!commitButton && !onUpload) {
    }
         for (var i = 0; i < editors.length; i++) {
  };
          if (editors[i].state != CategoryEditor.UNCHANGED) {
 
             setMultiInput();
  CategoryEditor.prototype = {
             break;
 
    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');
      if (!this.form) {
         this.linkSpan.className = 'noprint nopopups hotcatlink';
         this.makeForm ();
        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;
      if (this.list) this.list.style.display = 'none';
        this.linkSpan.appendChild (lk);
      if (this.engineSelector) this.engineSelector.style.display = 'none';
        span = make (newDOM ? 'li' : 'span');
      this.currentCategroy = this.lastSavedCategory;
        span.className = 'noprint';
       this.currentExists  = this.lastSavedExists;
        if (is_rtl) span.dir = 'rtl';
      this.currentKey      = this.lastSavedKey;
        span.appendChild (this.linkSpan);
      this.icon.src = this.currentExists ? HotCat.existsYes : HotCat.existsNo;
        if (after)
      this.text.value = this.currentCategory + (this.currentKey != null ? '|' + this.currentKey : "");
          after.parentNode.insertBefore (span, after.nextSibling);
       this.originalState = this.state;
        else
       this.lastInput    = this.currentCategory;
          line.appendChild (span);
       this.inputExists  = this.currentExists;
        this.normalLinks = null;
       this.state         = this.state == CategoryEditor.UNCHANGED ? CategoryEditor.OPEN : CategoryEditor.CHANGE_PENDING;
        this.undelLink = null;
       // Display the form
        this.catLink = null;
      if (this.catLink) this.catLink.style.display = 'none';
       } else {
       this.linkSpan.style.display = 'none';
        if (is_rtl) span.dir = 'rtl';
       this.form.style.display = 'inline';
        this.isAddCategory = false;
       CategoryEditor.makeActive (this);
        this.catLink = span.firstChild;
       // Kill the event before focussing, otherwise IE will kill the onfocus event!
        this.originalCategory = after;
      var result = evtKill (evt);
        this.originalKey = (key && key.length > 1) ? key.substr(1) : null; // > 1 because it includes the leading bar
      this.text.focus();
        this.originalExists  = !hasClass (this.catLink, 'new');
      this.text.readOnly = false;
        // Create change and del links
       checkMultiInput ();
        this.makeLinkSpan ();
       return result;
        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;
     },
     },


     open : function (evt) {
     makeLinkSpan : function () {
       var result = this.display (evt);
       this.normalLinks = make ('span');
       var v = this.lastSavedCategory;
       var lk = null;
      if (v.length == 0) return result;
       if (this.originalCategory && this.originalCategory.length > 0) {
 
        lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.remove, this);
       if (this.engine && suggestionConfigs[this.engine].temp) this.engine = HotCat.suggestions;
        lk.appendChild (make (HotCat.links.remove, true)); lk.title = HotCat.tooltips.remove;
      this.textchange (false, true); // do autocompletion, force display of suggestions
        this.normalLinks.appendChild (make (' ', true));
      return result;
        this.normalLinks.appendChild (lk);
    },
 
    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 (!HotCat.template_categories[this.originalCategory]) {
       if (this.list) this.list.style.display = 'none';
        lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this);
      if (this.engineSelector) this.engineSelector.style.display = 'none';
        lk.appendChild (make (HotCat.links.change, true)); lk.title = HotCat.tooltips.change;
      this.form.style.display = 'none';
        this.normalLinks.appendChild (make (' ', true));
      if (this.catLink) this.catLink.style.display = "";
        this.normalLinks.appendChild (lk);
      this.linkSpan.style.display = "";
        if (!noSuggestions && HotCat.use_up_down) {
      this.state = this.originalState;
          this.upDownLinks = make ('span');
      this.currentCategory = this.lastSavedCategory;
          lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.down, this);
      this.currentKey      = this.lastSavedKey;
          lk.appendChild (make (HotCat.links.down, true)); lk.title = HotCat.tooltips.down;
      this.currentExists  = this.lastSavedExists;
          this.upDownLinks.appendChild (make (' ', true));
      if (this.state == CategoryEditor.UNCHANGED) {
          this.upDownLinks.appendChild (lk);
        if (this.catLink) this.catLink.style.backgroundColor = 'transparent';
          lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.up, this);
      } else {
          lk.appendChild (make (HotCat.links.up, true)); lk.title = HotCat.tooltips.up;
        if (!onUpload) {
          this.upDownLinks.appendChild (make (' ', true));
           try {
           this.upDownLinks.appendChild (lk);
            this.catLink.style.backgroundColor = HotCat.bg_changed;
           this.normalLinks.appendChild (this.upDownLinks);
           } catch (ex) {}
         }
         }
       }
       }
       checkMultiInput ();
       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);
     },
     },


     removeEditor : function () {
     makeForm : function () {
       var next = this.span.nextSibling;
       var form = make ('form');
      if (next) next.parentNode.removeChild (next);
       form.method = 'POST'; form.onsubmit = bind (this.accept, this);
       this.span.parentNode.removeChild (this.span);
       this.form = form;
      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) {
       var text = make ('input'); text.type = 'text'; text.size = HotCat.editbox_width;
       this.undoLink.parentNode.removeChild (this.undoLink);
       if (!noSuggestions) {
      this.undoLink = null;
        text.onkeyup =
      this.currentCategory = this.originalCategory;
          bind (
       this.currentKey = this.originalKey;
            function (evt) {
      this.currentExists = this.originalExists;
              evt = evt || window.event || window.Event; // W3C, IE, Netscape
      this.lastSavedCategory = this.originalCategory;
              var key = evt.keyCode || 0;
      this.lastSavedKey = this.originalKey;
              if (key === 38 || key === 40 || key === 33 || key === 34) { // Up and down arrows, page up/down
      this.lastSavedExists = this.originalExists;
                // In case a browser doesn't generate keypress events for arrow keys...
      this.state = CategoryEditor.UNCHANGED;
                if (this.keyCount === 0) return this.processKey (evt);
      if (!this.currentCategory || this.currentCategory.length == 0) {
              } else {
        // It was a newly added category. Remove the whole editor.
                if (key == 27) { // ESC
        this.removeEditor();
                  if (!this.resetKeySelection ()) {
      } else {
                    // No undo of key selection: treat ESC as "cancel".
        // Redisplay the link...
                    this.cancel ();
        this.catLink.removeChild (this.catLink.firstChild);
                    return;
        this.catLink.appendChild (make (this.currentCategory, true));
                  }
         this.catLink.href = wikiPagePath (HotCat.category_canonical + ':' + this.currentCategory);
                }
        this.catLink.title = "";
                // Also do this for ESC as a workaround for Firefox bug 524360
        this.catLink.className = this.currentExists ? "" : 'new';
                // https://bugzilla.mozilla.org/show_bug.cgi?id=524360
        this.catLink.style.backgroundColor = 'transparent';
                var dont_autocomplete = (key == 8 || key == 46 || key == 27); // BS, DEL, ESC
        if (this.upDownLinks) this.upDownLinks.style.display = this.currentExists ? "" : 'none';
                if (this.engine && suggestionConfigs[this.engine] && suggestionConfigs[this.engine].temp && !dont_autocomplete) {
        checkMultiInput ();
                  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) return 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);
       }
       }
       return evtKill (evt);
       this.text = text;
    },


    inactivate : function () {
       this.icon = make ('img');
       if (this.list) this.list.style.display = 'none';
      if (this.engineSelector) this.engineSelector.style.display = 'none';
      this.is_active = false;
    },


    acceptCheck : function (dontCheck) {
      var list = null;
       this.sanitizeInput ();
       if (!noSuggestions) {
      var value = this.text.value.split('|');
        list = make ('select');
      var key  = null;
        list.onclick    = bind ( function (e) { if (this.highlightSuggestion (0)) this.textchange (false, true); }, this);
      if (value.length > 1) key = value[1];
        list.ondblclick = bind (function (e) { if (this.highlightSuggestion (0)) this.accept (e); }, this);
      var v = value[0].replace(/_/g, ' ').replace(/^\s+|\s+$/g, "");
        list.onchange = bind (function (e) { this.highlightSuggestion (0); this.text.focus(); }, this);
      if (HotCat.capitalizePageNames) v = capitalize (v);
         list.onkeyup =
      this.lastInput = v;
          bind (
      if (v.length == 0) {
            function (evt) {
        this.cancel ();
              evt = evt || window.event || window.Event; // W3C, IE, Netscape
         return false;
               if (evt.keyCode == 27) {
      }
                this.resetKeySelection ();
      if (   !dontCheck
                this.text.focus();
          && (  v == this.lastSavedCategory && key == this.lastSavedKey
                var self = this;
               || wgNamespaceNumber == 14 && v == wgTitle
                window.setTimeout (function () {self.textchange (true);}, HotCat.suggest_delay);
            )
              } else if (evt.keyCode == 13) {
        )
                this.accept (evt);
      {
              }
        this.cancel ();
            }
        return false;
          ,this
      }
          );
      this.currentCategory = v;
        if (!HotCat.fixed_search) {
      this.currentKey = key;
          var engineSelector = make ('select');
      this.currentExists = this.inputExists;
           for (var key in suggestionConfigs) {
      return true;
            if (suggestionConfigs[key].show) {
    },
              var opt = make ('option');
   
               opt.value = key;
    accept : function (evt) {
              if (key == this.engine) opt.selected = true;
      this.noCommit = (evtKeys (evt) & 1) != 0;
              opt.appendChild (make (suggestionConfigs[key].name, true));
      var result = evtKill (evt);
              engineSelector.appendChild (opt);
      if (this.acceptCheck ()) {
            }
        var toResolve = [this];
          }
        var original  = this.currentCategory;
          engineSelector.onchange = bind (
        resolveMulti (
            function () {
            toResolve
              this.engine = this.engineSelector.options[this.engineSelector.selectedIndex].value;
           , function (resolved) {
              this.text.focus();
              if (resolved[0].dab) {
              this.textchange (true, true); // Don't autocomplete, force re-display of list
                CategoryEditor.makeActive (resolved[0]);
               } else {
                if (resolved[0].acceptCheck(true)) {
                  resolved[0].commit (
                    (resolved[0].currentCategory != original)
                      ? HotCat.messages.cat_resolved.replace ('$1', original)
                      : null
                  );
                }
              }
             }
             }
        );
          ,this
          );
          this.engineSelector = engineSelector;
        }
       }
       }
       return result;
       this.list = list;
    },


    close : function () {
      function button_label (id, defaultText) {
      if (!this.catLink) {
        var label = null;
        // Create a catLink
        if (   onUpload
        this.catLink = make ('a');
            && typeof (UFUI) != 'undefined'
        this.catLink.appendChild (make ('foo', true));
            && typeof (UIElements) != 'undefined'
        this.catLink.style.display = 'none';
            && typeof (UFUI.getLabel) == 'function') {
         this.span.insertBefore (this.catLink, this.span.firstChild.nextSibling);
          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;
       }
       }
       this.catLink.removeChild (this.catLink.firstChild);
 
       this.catLink.appendChild (make (this.currentCategory, true));
       // Do not use type 'submit'; we cannot detect modifier keys if we do
       this.catLink.href = wikiPagePath (HotCat.category_canonical + ':' + this.currentCategory);
      var OK = make ('input'); OK.type = 'button';
       this.catLink.title = "";
      OK.value = button_label ('wpOkUploadLbl', HotCat.messages.ok);
       this.catLink.className = this.currentExists ? "" : 'new';
       OK.onclick = bind (this.accept, this);
       this.lastSavedCategory = this.currentCategory;
       this.ok = OK;
       this.lastSavedKey      = this.currentKey;
 
       this.lastSavedExists  = this.currentExists;
      var cancel = make ('input'); cancel.type = 'button';
       // Close form and redisplay category
      cancel.value = button_label ('wpCancelUploadLbl', HotCat.messages.cancel);
       if (this.list) this.list.style.display = 'none';
      cancel.onclick = bind (this.cancel, this);
       if (this.engineSelector) this.engineSelector.style.display = 'none';
       this.cancelButton = cancel;
       this.form.style.display = 'none';
 
       this.catLink.style.display = "";
      var span = make ('span');
       if (this.isAddCategory) {
       span.className = 'hotcatinput';
        if (onUpload) {
       span.style.position = 'relative';
          var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one
       // FF3.6: add the input field first, then the two absolutely positioned elements. Otherwise, FF3.6 may leave the
        }
      // suggestions and the selector at the right edge of the screen if display of the input field causes a re-layout
        this.isAddCategory = false;
       // moving the form to the front of the next line.
        this.linkSpan.parentNode.removeChild (this.linkSpan);
      span.appendChild (text);
        this.makeLinkSpan ();
 
        this.span.appendChild (this.linkSpan);
       // IE8/IE9: put some text into this span (a0 is nbsp) and make sure it always stays on the
      // same line as the input field, otherwise, IE8/9 miscalculates the height of the span and
       // then the engine selector may overlap the input field.
      span.appendChild (make ('\xa0', true));
      span.style.whiteSpace = 'nowrap';
 
      if (list) span.appendChild (list);
       if (this.engineSelector) span.appendChild (this.engineSelector);
      if (!noSuggestions) span.appendChild (this.icon);
      span.appendChild (OK);
      span.appendChild (cancel);
      form.appendChild(span);
      form.style.display = 'none';
       this.span.appendChild (form);
      addEvent (text, 'focus', bind (function () { CategoryEditor.makeActive (this); }, this));
       // On IE, blur events are asynchronous, and may thus arrive after the element has lost the focus. Since IE
      // can get the selection only while the element is active (has the focus), we may not always get the selection.
      // Therefore, use an IE-specific synchronous event on IE...
      // Don't test for text.selectionStart being defined; FF3.6.4 raises an exception when trying to access that
      // property while the element is not being displayed.
       addEvent (text, (typeof text.onbeforedeactivate != 'undefined' && text.createTextRange) ? 'beforedeactivate' : 'blur', bind (this.saveView, this));
    },
 
    display : function (evt) {
      if (this.isAddCategory && !onUpload) {
        var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one
       }
       }
       if (!this.undoLink) {
       if (!commitButton && !onUpload) {
         // Append an undo link.
         for (var i = 0; i < editors.length; i++) {
        var span = make ('span');
          if (editors[i].state != CategoryEditor.UNCHANGED) {
        var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.rollback, this);
            setMultiInput();
        lk.appendChild (make (HotCat.links.undo, true)); lk.title = HotCat.tooltips.undo;
             break;
        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';
       if (!this.form) {
       this.linkSpan.style.display = "";
        this.makeForm ();
       this.state = CategoryEditor.CHANGED;
      }
      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 = armorUri(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;
      this.lastSelection = {start: this.currentCategory.length, end: this.currentCategory.length};
      this.showsList = false;
      // 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;
      // Kill the event before focussing, otherwise IE will kill the onfocus event!
      var result = evtKill (evt);
      CategoryEditor.makeActive (this);
      this.text.focus();
      this.text.readOnly = false;
       checkMultiInput ();
       checkMultiInput ();
      return result;
     },
     },
   
 
     commit : function (comment) {
     show : function (evt, engine, readOnly) {
       // Check again to catch problem cases after redirect resolution
       var result = this.display (evt);
      if (  (  this.currentCategory == this.originalCategory
      var v = this.lastSavedCategory;
              && (this.currentKey == this.originalKey
      if (v.length === 0) return result;
                  || this.currentKey === null && this.originalKey.length == 0
      this.text.readOnly = !!readOnly;
                )
      this.engine = engine;
            )
      this.textchange (false, true); // do autocompletion, force display of suggestions
          || wgNamespaceNumber == 14 && this.currentCategory == wgTitle
       CategoryEditor.forceRedraw ();
        )
      return result;
       {
    },
        this.cancel ();
        return;
    open : function (evt) {
      }
      return this.show (evt, (this.engine && suggestionConfigs[this.engine].temp) ? HotCat.suggestions : this.engine);
      if (commitButton || onUpload) {
    },
        this.close ();
 
      } else {
    down : function (evt) {
        if (this.list) this.list.style.display = 'none';
      return this.show (evt, 'subcat', true);
        if (this.engineSelector) this.engineSelector.style.display = 'none';
        // Execute change from this.originalCategory to this.currentCategory|this.currentKey,
        var editlk = wgServer + wgScript + '?title=' + encodeURIComponent (wgPageName)
                  + '&action=edit';
        var url = editlk + '&hotcat_newcat=' + encodeURIComponent (this.currentCategory)
        if (this.currentKey != null) url += '&hotcat_sortkey=' + encodeURIComponent (this.currentKey);
        if (this.originalCategory.length > 0)
          url += '&hotcat_removecat=' + encodeURIComponent (this.originalCategory);
        if (comment) url = url + '&hotcat_comment=' + encodeURIComponent (comment);
        if (this.noCommit || HotCat.no_autocommit) url = url + '&hotcat_nocommit=1';
        document.location = url;
      }
     },
     },
   
 
     remove : function (evt) {
     up : function (evt) {
       this.doRemove (evtKeys (evt) & 1);
       return this.show (evt, 'parentcat');
      return evtKill (evt);
     },
     },
   
 
     doRemove : function (noCommit) {
     cancel : function () {
       if (this.isAddCategory) { // Empty input on adding a new category
       if (this.isAddCategory && !onUpload) {
         this.cancel ();
         this.removeEditor(); // We added a new adder when opening
         return;
         return;
       }
       }
       if (!commitButton && !onUpload) {
      // Close, re-display link
        for (var i = 0; i < editors.length; i++) {
      this.inactivate();
          if (editors[i].state != CategoryEditor.UNCHANGED) {
      this.form.style.display = 'none';
            setMultiInput();
       if (this.catLink) this.catLink.style.display = "";
            break;
      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 (commitButton) {
       if (this.state == CategoryEditor.UNCHANGED) {
        this.catLink.style.textDecoration = 'line-through';
         if (this.catLink) this.catLink.style.backgroundColor = 'transparent';
        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 {
       } else {
         if (onUpload) {
         if (!onUpload) {
           // Remove this editor completely
           try {
          this.removeEditor ();
            this.catLink.style.backgroundColor = HotCat.bg_changed;
        } else {
           } catch (ex) {}
          // Execute single category deletion.
          var editlk = wgServer + wgScript + '?title=' + encodeURIComponent (wgPageName)
                    + '&action=edit';
          if (noCommit || HotCat.no_autocommit) editlk += '&hotcat_nocommit=1';
           document.location =
            editlk + '&hotcat_removecat=' + encodeURIComponent (this.originalCategory);
         }
         }
       }
       }
    },
   
    restore : function (evt) {
      // Can occur only if we do have a commit button and are not on the upload form
      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 ();
       checkMultiInput ();
       return evtKill (evt);
       CategoryEditor.forceRedraw ();
     },
     },
   
 
    // Internal operations
     removeEditor : function () {
   
       if (!newDOM) {
     setValueFromList : function (idx) {
         var next = this.span.nextSibling;
       if (typeof (idx) == 'undefined') idx = this.list.selectedIndex;
         if (next) next.parentNode.removeChild (next);
      if (idx >= 0 && idx < this.list.options.length) {
         var v = this.text.value.split ('|');
         this.text.value = this.list.options[idx].text + (v.length > 1 ? '|' + v[1] : "");
        this.inputExists = true; // Might be wrong if from a dab list...
        if (this.icon) this.icon.src = HotCat.existsYes;
        return true;
       }
       }
       return false;
       this.span.parentNode.removeChild (this.span);
    },
       for (var i = 0; i < editors.length; i++) {
   
         if (editors[i] == this) {
    selectEngine : function (engineName) {
          editors.splice (i, 1);
      if (!this.engineSelector) return;
          break;
       for (var i = 0; i < this.engineSelector.options.length; i++) {
        }
         this.engineSelector.options[i].selected = this.engineSelector.options[i].value == engineName;
       }
       }
      checkMultiInput ();
      var self = this;
      window.setTimeout (function () {delete self;}, 10);
     },
     },


     sanitizeInput : function () {
     rollback : function (evt) {
       var v = this.text.value || "";
       this.undoLink.parentNode.removeChild (this.undoLink);
       v = v.replace(/^(\s|_)+/, ""); // Trim leading blanks and underscores
      this.undoLink = null;
      var re = new RegExp ('^(' + HotCat.category_regexp + '):');
      this.currentCategory = this.originalCategory;
      if (re.test (v)) v = v.substring (v.indexOf (':') + 1);
      this.currentKey = this.originalKey;
      if (HotCat.capitalizePageNames) v = capitalize (v);
      this.currentExists = this.originalExists;
       // Only update the input field if there is a difference. IE8 appears to reset the selection
      this.currentHidden = this.originalHidden;
       // and place the cursor at the front upon reset, which makes our autocompletetion become a
      this.lastSavedCategory = this.originalCategory;
       // nuisance. FF and IE6 don't seem to have this problem.
      this.lastSavedKey = this.originalKey;
       if (this.text.value != null && this.text.value != v)
      this.lastSavedExists = this.originalExists;
        this.text.value = v;
      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;
     },
     },


     makeCall : function (url, callbackObj, engine, queryKey) {
     acceptCheck : function (dontCheck) {
       var cb = callbackObj;
       this.sanitizeInput ();
       var = engine;
       var value = this.text.value.split('|');
       var = queryKey;
       var key  = null;
       var r  = sajax_init_object ();
       if (value.length > 1) key = value[1];
       cb.requests.push (r);
       var v = value[0].replace(/_/g, ' ').replace(/^\s+|\s+$/g, "");
      r.open('GET', url, true);
       if (HotCat.capitalizePageNames) v = capitalize (v);
       r.onreadystatechange =
      this.lastInput = v;
        bind (
      if (v.length === 0) {
          function () {
        this.cancel ();
            if (r.readyState == 4) {
        return false;
              if (r.status != 200) cb.dontCache = true;
      }
              if (r.status == 200 && r.responseText != null) {
      if (!dontCheck
                var titles = e.handler (r.responseText, v);
          && (  wgNamespaceNumber == 14 && v == wgTitle
                if (titles && titles.length > 0) {
               || HotCat.blacklist != null && HotCat.blacklist.test(v))
                  if (cb.allTitles == null) {
        ) {
                    cb.allTitles = titles;
        this.cancel ();
                  } else {
        return false;
                    cb.allTitles = cb.allTitles.concat (titles);
      }
                  }
      this.currentCategory = v;
                }
       this.currentKey = key;
              }
       this.currentExists = this.inputExists;
              cb.callsMade++;
       return true;
            }
            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,
     accept : function (evt) {
 
       this.noCommit = (evtKeys (evt) & 1) != 0;
    textchange : function (dont_autocomplete, force) {
       var result = evtKill (evt);
       // Hide all other lists
       if (this.acceptCheck ()) {
       CategoryEditor.makeActive (this);
         var toResolve = [this];
       if (noSuggestions) {
        var original  = this.currentCategory;
         // No Ajax: just make sure the list is hidden
         resolveMulti (
         if (this.list) this.list.style.display = 'none';
            toResolve
        if (this.engineSelector) this.engineSelector.style.display = 'none';
          , function (resolved) {
        if (this.icon) this.icon.style.display = 'none';
              if (resolved[0].dab) {
         return;
                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;
      // 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;
      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';


       if (suggestionConfigs[engineName].cache[v]) {
    close : function () {
         this.showSuggestions (suggestionConfigs[engineName].cache[v], dont_autocomplete, v, engineName);
       if (!this.catLink) {
         return;
        // 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);
       var engines = suggestionConfigs[engineName].engines;
       this.catLink.appendChild (make (this.currentCategory, true));
       this.callbackObj =
       this.catLink.href = wikiPagePath (HotCat.category_canonical + ':' + this.currentCategory);
        {allTitles: null, requests: [], callsMade: 0, nofCalls: engines.length, noCompletion: dont_autocomplete, engineName: engineName};  
      this.catLink.title = "";
       for (var j = 0; j < engines.length; j++) {
      this.catLink.className = this.currentExists ? "" : 'new';
        engine = suggestionEngines[engines[j]];
      this.lastSavedCategory = this.currentCategory;
        var url = wgServer + wgScriptPath + engine.uri.replace (/\$1/g, encodeURIComponent (v));
       this.lastSavedKey      = this.currentKey;
        this.makeCall (url, this.callbackObj, engine, v);
       this.lastSavedExists  = this.currentExists;
      }   
       this.lastSavedHidden  = this.currentHidden;
    },
       // Close form and redisplay category
   
      this.inactivate();
    showSuggestions : function (titles, dontAutocomplete, queryKey, engineName) {
      this.form.style.display = 'none';
       this.text.readOnly = false;
      this.catLink.title = this.currentKey;
       this.dab = null;
      this.catLink.style.display = "";
       if (!this.list) return;
      if (this.isAddCategory) {
       if (noSuggestions) {
        if (onUpload) {
        if (this.list) this.list.style.display = 'none';
          var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one
        if (this.engineSelector) this.engineSelector.style.display = 'none';
        }
        if (this.icon) this.icon.style.display = 'none';
        this.isAddCategory = false;
         this.inputExists = true; // Default...
         this.linkSpan.parentNode.removeChild (this.linkSpan);
         return;
        this.makeLinkSpan ();
         this.span.appendChild (this.linkSpan);
       }
       }
       var haveEngine = !!engineName;
       if (!this.undoLink) {
      if (haveEngine) {
        // Append an undo link.
         haveEngine = this.engineSelector != null;
        var span = make ('span');
      } else {
        var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.rollback, this);
         if (this.engineSelector) this.engineSelector.style.display = 'none';
         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 (queryKey) {
       if (this.upDownLinks) this.upDownLinks.style.display = this.lastSavedExists ? "" : 'none';
        if (this.lastInput.indexOf (queryKey) != 0) return;
      this.linkSpan.style.display = "";
        if (this.lastQuery && this.lastInput.indexOf (this.lastQuery) == 0 && this.lastQuery.length > queryKey.length)
       this.state = CategoryEditor.CHANGED;
          return;
       checkMultiInput ();
      }
       CategoryEditor.forceRedraw ();
       this.lastQuery = queryKey;
    },
        
      // Get current input text
      var v = this.text.value.split('|');
       var key = v.length > 1 ? '|' + v[1] : "";
      v = capitalize (v[0]);


      if (titles) {
    commit : function (comment) {
        var vLow = v.toLowerCase ();
      // Check again to catch problem cases after redirect resolution
        titles.sort (
      if (   (  this.currentCategory == this.originalCategory
          function (a, b) {
              && (this.currentKey == this.originalKey
            if (a.indexOf (b) == 0) return 1; // a begins with b: a > b
                  || this.currentKey === null && this.originalKey.length === 0
            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
             )
             )
           {
           || wgNamespaceNumber == 14 && this.currentCategory == wgTitle
            titles.splice (i, 1);
           || HotCat.blacklist != null && HotCat.blacklist.test (this.currentCategory)
            i--;
        )
           }
      {
        }
        this.cancel ();
      }
      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;
         return;
       }
       }
                   
       if (commitButton || onUpload) {
       var firstTitle = titles[0];
        this.close ();
      var completed = this.autoComplete (firstTitle, v, key, dontAutocomplete);
       } else {
       if (engineName && suggestionConfigs[engineName] && !suggestionConfigs[engineName].temp) {
         this.close ();
         this.icon.src = completed ? HotCat.existsYes : HotCat.existsNo;
        var self = this;
         this.inputExists = completed;
         initiateEdit (function (failure) {performChanges (failure, self);}, function (msg) {alert (msg);});
       }
       }
       if (completed) {
    },
         this.lastInput = firstTitle;
 
         if (titles.length == 1) {
    remove : function (evt) {
           this.list.style.display = 'none';
      this.doRemove (evtKeys (evt) & 1);
          if (this.engineSelector) this.engineSelector.style.display = 'none';
      return evtKill (evt);
           return;
    },
 
    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 (!this.is_active) {
       if (commitButton) {
         this.list.style.display = 'none';
         this.catLink.title = "";
         if (this.engineSelector) this.engineSelector.style.display = 'none';
         this.catLink.style.cssText += '; text-decoration : line-through !important;';
        return;
        try {
      }
          this.catLink.style.backgroundColor = HotCat.bg_changed;
      var nofItems = (titles.length > 5 ? 5 : titles.length);
        } catch (ex) {}
      if (nofItems <= 1) nofItems = 2;
         this.originalState = this.state;
      this.list.size = nofItems;
         this.state = CategoryEditor.DELETED;
      this.list.style.align    = 'left';
         this.normalLinks.style.display = 'none';
      this.list.style.zIndex  = 5;
         this.undelLink.style.display = "";
      this.list.style.position = 'absolute';
         checkMultiInput ();
      // 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 {
       } else {
         listh = this.list.offsetHeight;
         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);});
        }
       }
       }
      // Approximate calculation of maximum list size
    },
      var maxListHeight = listh;
      if (nofItems < 5) maxListHeight = (listh / nofItems) * 5;


      function scroll_offset (what) {
    restore : function (evt) {
        var s = 'scroll' + what;
       // Can occur only if we do have a commit button and are not on the upload form
        return (document.documentElement ? document.documentElement[s] : 0)
       this.catLink.title = this.currentKey;
              || document.body[s] || 0;
      this.catLink.style.textDecoration = "";
      
       this.state = this.originalState;
       function viewport (what) {
      if (this.state == CategoryEditor.UNCHANGED) {
        if (typeof (is_safari) != 'undefined' && is_safari && !document.evaluate)
        this.catLink.style.backgroundColor = 'transparent';
          return window['inner' + what];
      } else {
        var s = 'client' + what;
         try {
        if (typeof (is_opera) != 'undefined' && is_opera) return document.body[s];
           this.catLink.style.backgroundColor = HotCat.bg_changed;
        return (document.documentElement ? document.documentElement[s] : 0)
         } catch (ex) {}
              || 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};
       }
       }
      this.normalLinks.style.display = "";
      this.undelLink.style.display = 'none';
      checkMultiInput ();
      return evtKill (evt);
    },


      // IE6 seems to report in this.text.offsetTop and this.text.offsetLeft global offsets??
    // Internal operations
      // Possibly this has something to do with the special status of input elements in IE as
 
      // "windowed controls". Calculate the relative offsets manually.
    selectEngine : function (engineName) {
      var textPos = position (this.text);
       if (!this.engineSelector) return;
       var catLinePos = position (this.line);
       for (var i = 0; i < this.engineSelector.options.length; i++) {
       var textTop = textPos.y - catLinePos.y;
         this.engineSelector.options[i].selected = this.engineSelector.options[i].value == engineName;
      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;
    sanitizeInput : function () {
       if (haveEngine) {
       var v = this.text.value || "";
        this.engineSelector.style.zIndex = 5;
       v = v.replace(/^(\s|_)+/, ""); // Trim leading blanks and underscores
        this.engineSelector.style.position = 'absolute';
       var re = new RegExp ('^(' + HotCat.category_regexp + '):');
        this.engineSelector.style.width = this.text.offsetWidth + 'px';
       if (re.test (v)) v = v.substring (v.indexOf (':') + 1);
        // Figure out the height of this selector: display it off-screen, then hide it again.
      if (HotCat.capitalizePageNames) v = capitalize (v);
         if (this.engineSelector.style.display == 'none') {
      // Only update the input field if there is a difference. IE8 appears to reset the selection
           this.engineSelector.style.left  = '-1000px';
      // and place the cursor at the front upon reset, which makes our autocompletetion become a
           this.engineSelector.style.top  = textTop + 'px';
      // nuisance. FF and IE6 don't seem to have this problem.
           this.engineSelector.style.display = "";
      if (this.text.value != null && this.text.value != v)
          offset = this.engineSelector.offsetHeight;
        this.text.value = v;
           this.engineSelector.style.display = 'none';
    },
        } else {
 
           offset = this.engineSelector.offsetHeight;
    makeCall : function (url, callbackObj, engine, queryKey, cleanKey) {
      var cb = callbackObj;
      var e  = engine;
      var v  = queryKey;
      var z  = cleanKey;
      var thisObj = this;
 
      function done () {
        cb.callsMade++;
         if (cb.callsMade === cb.nofCalls) {
           if (!cb.dontCache && !suggestionConfigs[cb.engineName].cache[z]) {
            suggestionConfigs[cb.engineName].cache[z] = cb.allTitles;
           }
          thisObj.text.readOnly = false;
           if (!cb.cancelled) thisObj.showSuggestions (cb.allTitles, cb.noCompletion, v, cb.engineName);
           if (cb === thisObj.callbackObj) thisObj.callbackObj = null;
           delete cb;
         }
         }
        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)
       getJSON ({
       this.list.style.left = nl + 'px';
        uri : url
       // (Re-)fill the list
      ,success : function (json) {
       while (this.list.firstChild) this.list.removeChild (this.list.firstChild);
          var titles = e.handler (json, z);
       for (var i = 0 ; i < titles.length ; i++) {
          if (titles && titles.length > 0) {
         var opt = make ('option') ;
            if (cb.allTitles === null) {
         opt.appendChild (make (titles[i], true));
              cb.allTitles = titles;
         this.list.appendChild (opt);
            } else {
              cb.allTitles = cb.allTitles.concat (titles);
            }
          }
          done();
        }
      ,error : function (req) {if (!req) noSuggestions = true; cb.dontCache = true; done(); }
       });          
    },
 
    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) {
         this.currentKey = v.substring (pipe+1);
         v = v.substring (0, pipe);
      } else {
         this.currentKey = null;
       }
       }
       if (haveEngine) {
       if (this.lastInput == v && !force) return; // No change
        this.selectEngine (engineName);
      if (this.lastInput != v) checkMultiInput ();
        this.engineSelector.style.display = "";
      this.lastInput = v;
      }
       this.lastRealInput = v;
       this.list.style.display = 'block';
 
       // Set the width of the list       
       // Mark blacklisted inputs.
       var scroll = scroll_offset ('Left');
       this.ok.disabled = v.length > 0 && HotCat.blacklist != null && HotCat.blacklist.test (v);
       var view_w = viewport ('Width');
 
      var l_pos  = position (this.list);
       if (noSuggestions) {
      if (this.list.offsetWidth < this.text.offsetWidth) {
        // No Ajax: just make sure the list is hidden
         this.list.style.width = this.text.offsetWidth + 'px';
        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;
         return;
       }
       }
      // Make sure that the list fits horizontally into the browser window     
 
       var w      = this.list.offsetWidth;
       if (v.length === 0) { this.showSuggestions([]); return; }
       if (l_pos.x + w > scroll + view_w) {
       if (this.callbackObj) this.callbackObj.cancelled = true;
        if (w > view_w) w = view_w;
      var engineName  = suggestionConfigs[this.engine] ? this.engine : 'combined';
        this.list.style.width = w + 'px';
 
         this.list.style.left = nl - (l_pos.x + w - scroll - view_w) + 'px';
      var cleanKey = v.replace(/[\u200E\u200F\u202A-\u202E]/g, "")
                      .replace(wikiTextBlankRE, ' ');
      dont_autocomplete = dont_autocomplete || suggestionConfigs[engineName].noCompletion;
      if (suggestionConfigs[engineName].cache[cleanKey]) {
         this.showSuggestions (suggestionConfigs[engineName].cache[cleanKey], dont_autocomplete, v, engineName);
        return;
       }
       }
      var engines = suggestionConfigs[engineName].engines;
      this.callbackObj =
        {allTitles: null, callsMade: 0, nofCalls: engines.length, noCompletion: dont_autocomplete, engineName: engineName};
      this.makeCalls (engines, this.callbackObj, v, cleanKey);
     },
     },


     autoComplete : function (newVal, actVal, key, dontModify) {
     makeCalls : function (engines, cb, v, cleanKey) {
       if (newVal == actVal) return true;
       for (var j = 0; j < engines.length; j++) {
      if (dontModify || newVal.indexOf (actVal) != 0) return false;
         var engine = suggestionEngines[engines[j]];
      // Actual input is a prefix of the new text. Fill in new text, selecting the newly added suffix
        var url = wgServer + wgScriptPath + engine.uri.replace (/\$1/g, encodeURIComponent (cleanKey));
      // such that it can be easily removed by typing backspace if the suggestion is unwanted.
         this.makeCall (url, cb, engine, v, cleanKey);
      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) {
     showSuggestions : function (titles, dontAutocomplete, queryKey, engineName) {
       if (this.lastKey == 38 || this.lastKey == 40) { // Up and down arrows
       this.text.readOnly = false;
         if (this.list.style.display != 'none') {
      this.dab = null;
          // List is visible, so there are suggestions
      this.showsList = false;
          this.highlightSuggestion (this.lastKey == 38 ? -1 : 1);
      if (!this.list) return;
          // Kill the event, otherwise some browsers (e.g., Firefox) may additionally treat an up-arrow as
      if (noSuggestions) {
          // "place the text cursor at the front", which we don't want here.
         if (this.list) this.list.style.display = 'none';
          return evtKill (evt);
        if (this.engineSelector) this.engineSelector.style.display = 'none';
        } else if (  this.keyCount <= 1
        if (this.icon) this.icon.style.display = 'none';
                  && (!this.callbackObj || this.callbackObj.callsMade == this.callbackObj.nofCalls)
        this.inputExists = true; // Default...
                  )
        return;
         {
      }    
          // If no suggestions displayed, get them, unless we're already getting them.
      this.engineName = engineName;
          this.textchange ();
      if (engineName) {
        }
        if (!this.engineSelector) this.engineName = null;
      } else {
         if (this.engineSelector) this.engineSelector.style.display = 'none';
       }
       }
       return true;
       if (queryKey) {
    },
        if (this.lastInput.indexOf (queryKey) != 0) return;
 
        if (this.lastQuery && this.lastInput.indexOf (this.lastQuery) === 0 && this.lastQuery.length > queryKey.length)
    highlightSuggestion : function (dir) {
           return;
      if (noSuggestions || !this.list || this.list.style.display == 'none') return;
      var curr = this.list.selectedIndex;
      var tgt = curr < 0 ? 0 : curr + dir;
      tgt = tgt < 0 ? 0 : tgt;
      if (tgt != curr && tgt < this.list.options.length) {
        if (curr >= 0 && curr < this.list.options.length) 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.lastInput, key, false);
        if (!completed) {
           this.text.value = this.list.options[tgt].text + key;
        }
        this.lastInput = this.list.options[tgt].text;
       }
       }
    },
      this.lastQuery = queryKey;


    resetKeySelection : function () {
       // Get current input text
      if (noSuggestions || !this.list || this.list.style.display == 'none') return;
      var v = this.text.value.split('|');
       var curr = this.list.selectedIndex;
      var key = v.length > 1 ? '|' + v[1] : "";
      if (curr >= 0 && curr < this.list.options.length) {
      v = (HotCat.capitalizePageNames ? capitalize (v[0]) : v[0]);
        this.list.options[curr].selected = false;
        // Get current input text
        var v = this.text.value.split('|');
        var key = v.length > 1 ? '|' + v[1] : "";
        this.text.value = this.lastRealInput + key;
        this.lastInput = this.lastRealInput;
      }
    }


  }; // end CategoryEditor.prototype
      if (titles) {
 
        var vLow = v.toLowerCase ();
  function initialize () {
        // Strip blacklisted categories
    // User configurations. Do this here, called from the onload handler, so that users can
        if (HotCat.blacklist != null) {
    // override it easily in their own user script files by just declaring variables. JSconfig
          for (var i = 0; i < titles.length; i++) {
    // is some feature used at Wikimedia Commons.
             if (HotCat.blacklist.test (titles[i])) {
    HotCat.no_autocommit =  
              titles.splice(i, 1);
      (typeof (hotcat_no_autocommit) != 'undefined'
              i--;
        ? !!hotcat_no_autocommit
            }
        : (typeof (JSconfig) != 'undefined' && typeof (JSconfig.keys['HotCatNoAutoCommit']) != 'undefined'
          }
             ? JSconfig.keys['HotCatNoAutoCommit']
        }
            : HotCat.no_autocommit
        titles.sort (
          )
          function (a, b) {
      );
            if (a.indexOf (b) === 0) return 1; // a begins with b: a > b
    HotCat.suggest_delay =  window.hotcat_suggestion_delay
            if (b.indexOf (a) === 0) return -1; // b begins with a: a < b
                          || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatSuggestionDelay']
            // Opensearch may return stuff not beginning with the search prefix!
                          || HotCat.suggest_delay;
            var prefixMatchA = (a.indexOf (v) === 0 ? 1 : 0);
    HotCat.editbox_width =  window.hotcat_editbox_width
            var prefixMatchB = (b.indexOf (v) === 0 ? 1 : 0);
                          || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatEditBoxWidth']
            if (prefixMatchA != prefixMatchB) return prefixMatchB - prefixMatchA;
                          || HotCat.editbox_width;
             // Case-insensitive prefix match!
    HotCat.suggestions  =  window.hotcat_suggestions
             var aLow = a.toLowerCase(), bLow = b.toLowerCase();
                          || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatSuggestions']
            prefixMatchA = (aLow.indexOf (vLow) === 0 ? 1 : 0);
                          || HotCat.suggestions;
            prefixMatchB = (bLow.indexOf (vLow) === 0 ? 1 : 0);
    if (typeof (HotCat.suggestions) != 'string' || !suggestionConfigs[HotCat.suggestions])
            if (prefixMatchA != prefixMatchB) return prefixMatchB - prefixMatchA;
      HotCat.suggestions = 'combined';
            if (a < b) return -1;
    HotCat.fixed_search  =
             if (b < a) return 1;
      (typeof (hotcat_suggestions_fixed) != 'undefined'
             return 0;
        ? !!hotcat_suggestions_fixed
           }
        : (typeof (JSconfig) != 'undefined' && typeof (JSconfig.keys['HotCatFixedSuggestions']) != 'undefined'
        );
             ? JSconfig.keys['HotCatFixedSuggestions']
        // Remove duplicates and self-references
             : HotCat.fixed_search
        for (var i = 0; i < titles.length; i++) {
          )
          if (   i+1 < titles.length && titles[i] == titles[i+1]
      );
              || wgNamespaceNumber == 14 && titles[i] == wgTitle
    HotCat.bg_changed    =   window.hotcat_changed_background
            )
                          || typeof (JSconfig) != 'undefined' && JSconfig.keys['HotCatChangedBackground']
          {
                          || HotCat.bg_changed;
            titles.splice (i, 1);
    HotCat.use_up_down  =
            i--;
      (typeof (hotcat_use_category_links) != 'undefined'
          }
        ? !!hotcat_use_category_links
        : (typeof (JSconfig) != 'undefined' && typeof (JSconfig.keys['HotCatUseCategoryLinks']) != 'undefined'
             ? JSconfig.keys['HotCatUseCategoryLinks']
             : HotCat.use_up_down
           )
      );
    // 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];
         }
         }
       }
       }
    }
      if (!titles || titles.length === 0) {
    // Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]]
        if (this.list) this.list.style.display = 'none';
    is_rtl = hasClass (document.body, 'rtl');
         if (this.engineSelector) this.engineSelector.style.display = 'none';
    if (!is_rtl) {
        if (engineName && suggestionConfigs[engineName] && !suggestionConfigs[engineName].temp) {
      if (document.defaultView && document.defaultView.getComputedStyle) { // Gecko etc.
          if (this.icon) this.icon.src = armorUri(HotCat.existsNo);
         is_rtl = document.defaultView.getComputedStyle (document.body, null).getPropertyValue ('direction');
          this.inputExists = false;
      } else if (document.body.currentStyle) { // IE, has subtle differences to getComputedStyle
        }
        is_rtl = document.body.currentStyle['direction'];
         return;
      } else { // Not exactly right, but best effort
         is_rtl = document.body.style['direction'];
       }
       }
       is_rtl = (is_rtl == 'rtl');
 
    }
       var firstTitle = titles[0];
  }
      var completed = this.autoComplete (firstTitle, v, key, dontAutocomplete);
       
      if (engineName && suggestionConfigs[engineName] && !suggestionConfigs[engineName].temp) {
  function can_edit () {
         this.icon.src = armorUri(completed ? HotCat.existsYes : HotCat.existsNo);
    var container = null;
         this.inputExists = completed;
    switch (skin) {
       }
      case 'cologneblue':
       if (completed) {
         container = document.getElementById ('quickbar');
         this.lastInput = firstTitle;
         // Fall through
         if (titles.length === 1) {
       case 'standard':
           this.list.style.display = 'none';
       case 'nostalgia':
          if (this.engineSelector) this.engineSelector.style.display = 'none';
        if (!container) container = document.getElementById ('topbar');
          return;
         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 = '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;
       }
       }
    }
      // (Re-)fill the list
    if (!label) {
      while (this.list.firstChild) this.list.removeChild (this.list.firstChild);
       labelCell.id = 'hotcatLabel';
       for (var i = 0 ; i < titles.length ; i++) {
      labelCell.appendChild (make (HotCat.categories), true);
        var opt = make ('option') ;
     } else {
        opt.appendChild (make (titles[i], true));
       labelCell.id = 'hotcatLabelTranslated';
        opt.selected = completed && (i === 0);
       labelCell.appendChild (label);
        this.list.appendChild (opt);
    }
      }
    labelCell.className          = 'mw-label';
      this.displayList();
    labelCell.style.textAlign    = 'right';
     },
    labelCell.style.verticalAlign = 'middle';
 
    // Change the onsubmit handler
    displayList : function () {
    var form = document.getElementById ('upload') || document.getElementById ('mw-upload-form');
       this.showsList = true;
    if (form) {
      if (!this.is_active) {
      var optionsTable = document.getElementById ('mw-htmlform-options');
        this.list.style.display = 'none';
       if (optionsTable) optionsTable.width = '100%';
        if (this.engineSelector) this.engineSelector.style.display = 'none';
      ip.parentNode.insertBefore (newRow, ip);
        return;
       form.onsubmit = (function (oldSubmit) {
      }
         return function () {        
       var nofItems = (this.list.options.length > HotCat.list_size ? HotCat.list_size : this.list.options.length);
           var do_submit = true;
      if (nofItems <= 1) nofItems = 2;
           if (oldSubmit) {
      this.list.size = nofItems;
            if (typeof (oldSubmit) == 'string')
      this.list.style.align    = is_rtl ? 'right' : 'left';
              do_submit = eval (oldSubmit);
      this.list.style.zIndex  = 5;
            else if (typeof (oldSubmit) == 'function')
      this.list.style.position = 'absolute';
              do_submit = oldSubmit.apply (form, arguments);
      // Compute initial list position. First the height.
          }
      var listh = 0;
          if (!do_submit) return false;
      if (this.list.style.display == 'none') {
          closeForm ();
        // Off-screen display to get the height
          // Copy the categories
        this.list.style.top = this.text.offsetTop + 'px';
          var eb =   document.getElementById ('wpUploadDescription')
        this.list.style.left = '-10000px';
                  || document.getElementById ('wpDesc');
        this.list.style.display = "";
           for (var i = 0; i < editors.length; i++) {
        listh = this.list.offsetHeight;
            var t = editors[i].currentCategory;
        this.list.style.display = 'none';
            if (!t) continue ;
      } else {
            var key = editors[i].currentKey;
        listh = this.list.offsetHeight;
            var new_cat = '[[' + HotCat.category_canonical + ':' + t + (key ? '|' + key : "") + ']]';
      }
            // Only add if not already present
      // Approximate calculation of maximum list size
            var cleanedText = eb.value.replace(/<\!--(\s|\S)*?--\>/g, "")
      var maxListHeight = listh;
                                      .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, "");
      if (nofItems < HotCat.list_size) maxListHeight = (listh / nofItems) * HotCat.list_size;
            if (!find_category (cleanedText, t, true)) {
 
              eb.value += '\n' + new_cat;
      function scroll_offset (what) {
            }
        var s = 'scroll' + what;
          }
        return (document.documentElement ? document.documentElement[s] : 0)
          return true;          
              || document.body[s] || 0;
         };
       }
      }) (form.onsubmit);
      function viewport (what) {
    }
        if (is_webkit && !document.evaluate)
  }
          return window['inner' + what]; // Safari < 3.0
 
        var s = 'client' + what;
  var cleanedText = null;
        if (window.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};
      }
 
      var textPos = position (this.text);
      var nl = 0;
      var nt = 0;
      var offset = 0;
      // Opera 9.5 somehow has offsetWidth = 0 here?? Use the next best value...
      var textBoxWidth = this.text.offsetWidth || this.text.clientWidth;
      if (this.engineName) {
        this.engineSelector.style.zIndex = 5;
        this.engineSelector.style.position = 'absolute';
        this.engineSelector.style.width = textBoxWidth + '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  = '-10000px';
          this.engineSelector.style.top  = '0px';
          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 + 1) {
        // The list might extend beyond the upper border of the page. Let's avoid that by placing it
        // below the input text field.
        nt = this.text.offsetHeight + offset + 1;
        if (this.engineName) this.engineSelector.style.top = this.text.offsetHeight + 'px';
      } else {
        nt = - listh - offset - 1;
        if (this.engineName) this.engineSelector.style.top = - (offset + 1) + 'px';
      }
      this.list.style.top = nt + 'px';
      this.list.style.width = ""; // No fixed width (yet)
      this.list.style.left = nl + 'px';
      if (this.engineName) {
        this.selectEngine (this.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 < textBoxWidth ) {
        this.list.style.width = textBoxWidth + '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';
      }
    },


  function isOnPage (span) {
    autoComplete : function (newVal, actVal, key, dontModify) {
    var catTitle = title (span.firstChild.getAttribute ('href', 2));
      if (newVal == actVal) return true;
    if (!catTitle) return null;
      if (dontModify || newVal.indexOf (actVal) != 0) return false;
    catTitle = catTitle.substr (catTitle.indexOf (':') + 1).replace (/_/g, ' ');
      // Actual input is a prefix of the new text. Fill in new text, selecting the newly added suffix
    var result = { title : catTitle, match : ["", "", ""] };
      // such that it can be easily removed by typing backspace if the suggestion is unwanted.
    if (pageText === null) return result;
      if (!this.canSelect()) return false;
    if (cleanedText === null) {
       // If we can't select properly, autocompletion would be a major annoyance to the user.
       cleanedText = pageText.replace(/<\!--(\s|\S)*?--\>/g, "")
      this.text.focus();
                            .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, "");
      this.text.value = newVal + key;
    }
      this.setSelection (actVal.length, newVal.length);
    result.match = find_category (cleanedText, catTitle, true);
      return true;
    return result;
    },
  }


  var initialized = false;
    canSelect : function () {
      return    this.text.setSelectionRange
              || this.text.createTextRange
              ||    typeof (this.text.selectionStart) != 'undefined'
                  && typeof (this.text.selectionEnd) != 'undefined';
    },


  function setup () {
    setSelection : function (from, to) {
    if (initialized) return;
      // this.text must be focused (at least on IE)
    initialized = true;
      if (!this.text.value) return;
    // Find the category bar, or create an empty one if there isn't one. Then add -/+- links after
      if (this.text.setSelectionRange) {    // e.g. khtml
    // each category, and add the + link.
        this.text.setSelectionRange (from, to);
    catLine catLine                                                  // Special:Upload
      } else if (typeof (this.text.selectionStart) != 'undefined') {
            || document.getElementById ('mw-normal-catlinks')           // MW >= 1.13alpha
        if (from > this.text.selectionStart) {
            || getElementsByClassName (document , 'p' , 'catlinks')[0]; // MW < 1.13
          this.text.selectionEnd  = to;
    var hiddenCats = document.getElementById ('mw-hidden-catlinks');
          this.text.selectionStart = from;
    if (!catLine) {
        } else {
      var footer = null;
          this.text.selectionStart = from;
      if (!hiddenCats) {
          this.text.selectionEnd   = to;
        footer = getElementsByClassName (document , 'div' , 'printfooter')[0];
        }
        if (!footer) return; // Don't know where to insert the category line
      } else if (this.text.createTextRange) { // IE
        var new_selection = this.text.createTextRange();
        new_selection.move ('character', from);
        new_selection.moveEnd ('character', to - from);
        new_selection.select();
      }
    },
 
    getSelection : function () {
      var from = 0, to = 0;
      // this.text must be focused (at least on IE)
      if (!this.text.value) {
        // No text.
      } else if (typeof (this.text.selectionStart) != 'undefined') {
        from = this.text.selectionStart;
        to  = this.text.selectionEnd;
      } else if (document.selection && document.selection.createRange) { // IE
        var rng = document.selection.createRange().duplicate();
        if (rng.parentElement() === this.text) {
          try {
            var textRng = this.text.createTextRange();
            textRng.move('character', 0);
            textRng.setEndPoint('EndToEnd', rng);
            // We're in a single-line input box: no need to care about IE's strange
            // handling of line ends
            to = textRng.text.length;
            textRng.setEndPoint('EndToStart', rng);
            from = textRng.text.length;
          } catch (notFocused) {
            from = this.text.value.length; to = from; // At end of text
          }
        }
       }
       }
       catLine = make ('div');
       return {start: from, end: to};
       catLine.id = 'mw-normal-catlinks';
    },
      catLine.style.textAlign = 'left';
 
      // Add a label
    saveView : function (evt) {
       var label = make ('a');
       this.lastSelection = this.getSelection ();
       label.href  = wgArticlePath.replace ('$1', 'Special:Categories');
    },
      label.title = HotCat.categories;
 
      label.appendChild (make (HotCat.categories, true));
    processKey : function (evt) {
      catLine.appendChild (label);
       var dir = 0;
      catLine.appendChild (make (':', true));
       switch (this.lastKey) {
      // Insert the new category line
        case 38: dir = -1; // Up arrow
      var container = (hiddenCats ? hiddenCats.parentNode : document.getElementById ('catlinks'));
        case 40: if (dir === 0) dir = 1; // Down arrow
      if (!container) {
        case 33: if (dir === 0) dir = -HotCat.list_size; // Page up
        container = make ('div');
        case 34: if (dir === 0) dir = HotCat.list_size; // Page down
        container.id = 'catlinks';
          if (this.list.style.display != 'none') {
        footer.parentNode.insertBefore (container, footer.nextSibling);
            // 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);
       }
       }
       container.className = 'catlinks noprint';
       return true;
       container.style.display = "";
    },
       if (!hiddenCats) {
 
         container.appendChild (catLine);
    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 {
       } else {
         container.insertBefore (catLine, hiddenCats);
         tgt = curr < 0 ? 0 : curr + dir;
        tgt = tgt < 0 ? 0 : tgt;
        if (tgt >= this.list.options.length) tgt = this.list.options.length - 1;
       }
       }
    } // end if catLine exists
      if (tgt != curr || dir === 0) {
    catLine.style.position = 'relative';
        if (curr >= 0 && curr < this.list.options.length && dir != 0) this.list.options[curr].selected = false;
    if (is_rtl) catLine.dir = 'rtl';
        this.list.options[tgt].selected = true;
 
        // Get current input text
    // Create editors for all existing categories
        var v = this.text.value.split('|');
 
        var key = v.length > 1 ? '|' + v[1] : "";
     function createEditors (line) {
        var completed = this.autoComplete (this.list.options[tgt].text, this.lastRealInput, key, false);
       var cats = line.getElementsByTagName ('span');
        if (!completed) {
      // Copy cats, otherwise it'll also magically contain our added spans as it is a live collection!
          this.text.value = this.list.options[tgt].text + key;
       var copyCats = new Array (cats.length);
        }
       for (var i = 0; i < cats.length; i++) copyCats[i] = cats[i];
        this.lastInput = this.list.options[tgt].text;
      var editor = null;
        this.inputExists = true; // Might be wrong if from a dab list...
      for (var i = 0; i < copyCats.length; i++) {
        if (this.icon) this.icon.src = armorUri(HotCat.existsYes);
         var test = isOnPage (copyCats[i]);
        this.state = CategoryEditor.CHANGE_PENDING;
         if (test !== null && test.match !== null) {
      }
           editor = new CategoryEditor (line, copyCats[i], test.title, test.match[2]);
      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 copyCats.length > 0 ? copyCats[copyCats.length-1] : null;
       return false;
     }
     }


    var lastSpan = createEditors (catLine);
  }; // end CategoryEditor.prototype
     // Create one to add a new category
 
     var editor = new CategoryEditor(catLine, null, null, lastSpan != null);
  function initialize () {
     if (!onUpload) {
    // User configurations. Do this here, called from the onload handler, so that users can
      if (pageText !== null && hiddenCats) {
    // override it easily in their own user script files by just declaring variables. JSconfig
        hiddenCats.style.position = 'relative';
     // is some feature used at Wikimedia Commons.
         if (is_rtl) hiddenCats.dir = 'rtl';
     var config = (typeof (JSconfig) != 'undefined' && JSconfig.keys) ? JSconfig.keys : {};
        createEditors (hiddenCats);
     HotCat.dont_add_to_watchlist =
       }
      (typeof (window.hotcat_dont_add_to_watchlist) != 'undefined'
      // And finally add the "multi-mode" span. (Do this at the end, otherwise it ends up in the list above.)
        ? !!window.hotcat_dont_add_to_watchlist
      var enableMulti = make ('span');
        : (typeof (config['HotCatDontAddToWatchlist']) != 'undefined'
      enableMulti.className = 'noprint';
            ? config['HotCatDontAddToWatchlist']
      if (is_rtl) enableMulti.dir = 'rtl';
            : HotCat.dont_add_to_watchlist
       catLine.insertBefore (enableMulti, catLine.firstChild.nextSibling);
          )
      enableMulti.appendChild (make ('\xa0', true)); // nbsp
      );
      multiSpan = make ('span');
    HotCat.no_autocommit =
       enableMulti.appendChild (multiSpan);
      (typeof (window.hotcat_no_autocommit) != 'undefined'
       multiSpan.innerHTML = '(<a>' + HotCat.addmulti + '</a>)';
         ? !!window.hotcat_no_autocommit
      var lk = multiSpan.getElementsByTagName ('a')[0];
        : (typeof (config['HotCatNoAutoCommit']) != 'undefined'
      lk.onclick = function (evt) {setMultiInput (); checkMultiInput (); return evtKill (evt);};
            ? config['HotCatNoAutoCommit']
      lk.title = HotCat.multi_tooltip;
            : HotCat.no_autocommit
      lk.style.cursor = 'pointer';
          )
     }
       );
     cleanedText = null;
    HotCat.suggest_delay =  window.hotcat_suggestion_delay
  }
                          || config['HotCatSuggestionDelay']
 
                          || HotCat.suggest_delay;
  function setPage (json) {
    HotCat.editbox_width =   window.hotcat_editbox_width
     if (json && json.query) {
                          || config['HotCatEditBoxWidth']
      if (json.query.pages) {
                          || HotCat.editbox_width;
        for (var p in json.query.pages) {
    HotCat.suggestions  =   window.hotcat_suggestions
          var page = json.query.pages[p];
                          || config['HotCatSuggestions']
          if (!page.revisions || page.revisions.length == 0) break;
                          || HotCat.suggestions;
          pageText = page.revisions[0]['*'];
    if (typeof (HotCat.suggestions) != 'string' || !suggestionConfigs[HotCat.suggestions])
           pageTime = page.revisions[0].timestamp.replace (/\D/g, "");
      HotCat.suggestions = 'combined';
          pageWatched = typeof (page.watched) == 'string';
    HotCat.fixed_search  =
          break;
       (typeof (window.hotcat_suggestions_fixed) != 'undefined'
        ? !!window.hotcat_suggestions_fixed
        : (typeof (config['HotCatFixedSuggestions']) != 'undefined'
            ? config['HotCatFixedSuggestions']
            : HotCat.fixed_search
          )
       );
    HotCat.single_minor  =
       (typeof (window.hotcat_single_changes_are_minor) != 'undefined'
        ? !!window.hotcat_single_changes_are_minor
        : (typeof (config['HotCatMinorSingleChanges']) != 'undefined'
            ? config['HotCatMinorSingleChanges']
            : HotCat.single_minor
          )
      );
    HotCat.bg_changed    =   window.hotcat_changed_background
                          || config['HotCatChangedBackground']
                          || HotCat.bg_changed;
    HotCat.use_up_down  =
      (typeof (window.hotcat_use_category_links) != 'undefined'
        ? !!window.hotcat_use_category_links
        : (typeof (config['HotCatUseCategoryLinks']) != 'undefined'
            ? config['HotCatUseCategoryLinks']
            : HotCat.use_up_down
          )
      );
    HotCat.list_size =    window.hotcat_list_size
                      || config['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];
         }
         }
       }
       }
      // Siteinfo
    }
       if (json.query.general) {
    // Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]]
         HotCat.capitalizePageNames = (json.query.general['case'] == 'first-letter');
    is_rtl = hasClass (document.body, 'rtl');
        if (json.query.general.time) serverTime = json.query.general.time.replace (/\D/g, "");
    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 getPage () {
   function can_edit () {
     // We know we have an article here.
     var container = null;
     if (wgArticleId == 0) {
     switch (skin) {
       // Doesn't exist yet.
       case 'cologneblue':
      pageText = "";
        container = document.getElementById ('quickbar');
      pageTime = null;
        // Fall through
       setup ();
       case 'standard':
    } else {
       case 'nostalgia':
       var url = wgServer + wgScriptPath + '/api.php?format=json&callback=HotCat.start&action=query&titles='
        if (!container) container = document.getElementById ('topbar');
              + encodeURIComponent (wgPageName) + '&prop=info%7Crevisions&rvprop=content%7Ctimestamp&meta=siteinfo';
        var lks = container.getElementsByTagName ('a');
      var s = make ('script');
        for (var i = 0; i < lks.length; i++) {
      s.src = url;
          if (   param ('title', lks[i].href) == wgPageName
      s.type = 'text/javascript';
              && param ('action', lks[i].href) == 'edit')
      HotCat.start = function (json) { setPage (json); setup (); };
            return true;
      document.getElementsByTagName ('head')[0].appendChild (s);
        }
       window.setTimeout (setup, 4000); // 4 seconds. Just in case getting the wikitext takes longer.
        return false;
       default:
        // all modern skins:
        return document.getElementById ('ca-edit') != null;
     }
     }
    return false;
   }
   }


   function run () {
   function setup_upload () {
     if (HotCat.started) return;
     onUpload = true;
    HotCat.started = true;
     // Add an empty category bar at the end of the table containing the description, and change the onsubmit handler.
     initialize ();
     var ip = document.getElementById ('mw-htmlform-description') || document.getElementById ('wpDestFile');
 
    if (!ip) {
    if (is_rtl && window.ie6_bugs) return; // Disabled! IE6 with RTL is just too broken...
       ip = document.getElementById ('wpDestFile');
 
      while (ip && ip.nodeName.toLowerCase() != 'table') ip = ip.parentNode;
     if (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' || !can_edit() || HotCat.disable()) return;
      getPage ();
     }
     }
   }
    if (!ip) return;
 
    var reupload = document.getElementById ('wpForReUpload');
   // Legacy stuff
    var destFile = document.getElementById ('wpDestFile');
 
    if (  (reupload && !!reupload.value)
   function closeForm () {
        || (destFile && (destFile.disabled || destFile.readOnly)))
     // Close all open editors without redirect resolution and other asynchronous stuff.
      return; // re-upload form...
     for (var i = 0; i < editors.length; i++) {
    // Insert a table row with two fields (label and empty category bar)
       if (editors[i].state == CategoryEditor.OPEN) {
    var labelCell = make ('td');
         editors[i].cancel();
    var lineCell  = make ('td');
       } else if (editors[i].state == CategoryEditor.CHANGE_PENDING) {
    // Create the category line
         editors[i].sanitizeInput ();
    catLine = make ('div');
         var value = editors[i].text.value.split('|');
    catLine.className = 'catlinks';
         var key  = null;
    catLine.id = 'catlinks';
         if (value.length > 1) key = value[1];
    catLine.style.textAlign = is_rtl ? 'right' : 'left';
         var v = value[0].replace(/_/g, ' ').replace(/^\s+|\s+$/g, "");
    // We'll be inside a table row. Make sure that we don't have margins or strange borders.
         if (v.length == 0) {
    catLine.style.margin = '0';
           editors[i].cancel ();
    catLine.style.border = 'none';
    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 newRow = ip.insertRow (-1);
      newRow.appendChild (labelCell);
      newRow.appendChild (lineCell);
      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 (additionalWork) {
    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
    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) {
        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;
    if (typeof (additionalWork) == 'function') additionalWork();
    setupCompleted.loaded(); // Trigger signal; execute registered functions
    if (window.jQuery) window.jQuery('body').trigger ('hotcatSetupCompleted');
  }
 
  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 createCommitForm () {
    if (commitForm) return;
    var formContainer = make ('div');
    formContainer.style.display = 'none';
    document.body.appendChild (formContainer);
    var formText =
        '<form id="hotcatCommitForm" 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" />'
      + '<input type="hidden" name="wpEditToken" />'
      + '<input type="hidden" name="wpDiff" value="wpDiff" />'
      + '<input type="submit" name="hcCommit" value="hcCommit" />'
      + '</form>';
    formContainer.innerHTML = formText;
    commitForm = document.getElementById ('hotcatCommitForm');
  }
 
  function getPage () {
    // We know we have an article here.
    if (wgArticleId === 0) {
      // Doesn't exist yet.
      pageText = "";
      pageTime = null;
      setup (createCommitForm);
    } 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 (createCommitForm); };
      document.getElementsByTagName ('head')[0].appendChild (s);
      setupTimeout = window.setTimeout (function () {setup (createCommitForm);}, 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 && is_ie6) return; // Disabled! IE6 with RTL is just too broken...
    if (!HotCat.upload_disabled && wgNamespaceNumber === -1 && wgCanonicalSpecialPageName == 'Upload' && wgUserName) {
      setup_upload ();
      setup (function () {
        // Check for state restoration once the setup is done otherwise, but before signalling setup completion
        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;
      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 {
         } else {
           editors[i].currentCategory = v;
           editors[i].currentCategory = v;
           editors[i].currentKey = key;
           editors[i].currentKey = key;
           editors[i].currentExists = this.inputExists;
           editors[i].currentExists = this.inputExists;
           editors[i].close ();
           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 (); };


   function getState () {
   if (window.mediaWiki && window.mediaWiki.config) {
     var result = null;
     // Make sure we don't get conflicts with AjaxCategories (core development that should one day
     for (var i = 0; i < editors.length; i++) {
     // replace HotCat).
      var text = editors[i].currentCategory;
    window.mediaWiki.config.set('disableAJAXCategories', true);
      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;
   }
   }
 
   if (window.jQuery) {
   function setState (state) {
     window.jQuery(document).ready(run);
    var cats = state.split ('\n');
  } else {
    if (cats.length == 0) return null;
    addOnloadHook (run);
     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 (); };
  addOnloadHook (run);
})();
})();


} // end if (guard)
} // end if (guard)
//</source>
//</source>