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)
Update auf Version 2.17
K HotCat v2.3
Zeile 1: Zeile 1:
//<source lang="javascript">
//<source lang="javascript">
 
/*
/*
  HotCat V2.17
HotCat V2.30
 
  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.
  Supports multiple category changes, as well as redirect and disambiguation resolution. Also
Supports multiple category changes, as well as redirect and disambiguation resolution. Also
  plugs into the upload form. Search engines to use for the suggestion list are configurable, and
plugs into the upload form. Search engines to use for the suggestion list are configurable, and
  can be selected interactively.
can be selected interactively.
 
  Documentation: https://commons.wikimedia.org/wiki/Help:Gadget-HotCat
Documentation: https://commons.wikimedia.org/wiki/Help:Gadget-HotCat
  List of main authors: https://commons.wikimedia.org/wiki/Help:Gadget-HotCat/Version_history
List of main authors: https://commons.wikimedia.org/wiki/Help:Gadget-HotCat/Version_history
 
  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 :-)
*/
*/
 
/*
/*
  This code is MW version safe. It should run on any MediaWiki installation >= MW 1.15. Note: if
This code is MW version safe. It should run on any MediaWiki installation >= MW 1.15. Note: if
  running on MW >= 1.17 configured with $wgLegacyJavaScriptGlobals != true, it will still force
running on MW >= 1.17 configured with $wgLegacyJavaScriptGlobals != true, it will still force
  publishing the wg* globals in the window object. Note that HotCat is supposed to run with or
publishing the wg* globals in the window object. Note that HotCat is supposed to run with or
  without jQuery, and also on older installations that do not yet have window.mediaWiki. If you
without jQuery, and also on older installations that do not yet have window.mediaWiki. If you
  use any of these newer features, make sure you qualify them by checking whether they exist at
use any of these newer features, make sure you qualify them by checking whether they exist at
  all, and by providing some meaningful fallback implementation if not. To start itself, HotCat
all, and by providing some meaningful fallback implementation if not. To start itself, HotCat
  uses either jQuery(document).ready(), if available (preferred), or the old addOnloadHook().
uses jQuery(document).ready(). If it doesn't exist, HotCat won't start.
  If neither exists, HotCat won't start.
*/
*/
if ((typeof wgAction == 'undefined') && window.mediaWiki && window.mediaWiki.config) { // Compatibility hack
/* jshint ignore:start */ // This old code uses too many coding conventions incompatible with jshint.
  window.wgAction = window.mediaWiki.config.get('wgAction');
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 window.HotCat == 'undefined' || window.HotCat.nodeName) && wgAction != 'edit') { // Guard against double inclusions, and inactivate on edit pages
 
// Configuration stuff.
// Configuration stuff.
var HotCat = {
window.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  : 'removed [[Category:$1]]'
    ,template_removed  : 'Entferne {{[[Kategorie:$1|$1]]}}'
,template_removed  : 'removed {{[[Category:$1]]}}'
    ,cat_added    : 'Ergänze [[Kategorie:$1]]'
,cat_added    : 'added [[Category:$1]]'
    ,cat_keychange: 'neuer Sortierschlüssel für [[Kategorie:$1]]: "$2"' // $2 is the new key
,cat_keychange: 'new key for [[Category:$1]]: "$2"' // $2 is the new key
    ,cat_notFound : 'Kategorie "$1" konnte nicht gefunden werden'
,cat_notFound : 'Category "$1" not found'
    ,cat_exists  : 'Kategorie "$1" bereits enthalten; nicht ergänzt'
,cat_exists  : 'Category "$1" already exists; not added.'
    ,cat_resolved : ' (Weiterleitung [[Kategorie:$1]] aufgelöst)'
,cat_resolved : ' (redirect [[Category:$1]] resolved)'
    ,uncat_removed: 'entferne {{uncategorized}}'
,uncat_removed: 'removed {{uncategorized}}'
    ,separator    : '; '
,separator    : '; '
    ,prefix      : '[[Hilfe:HotCat|HC]]: '
,prefix      : ""
        // Some text to prefix to the edit summary.
// Some text to prefix to the edit summary.
    ,using        : ""
,using        : ' using [[Help:Gadget-HotCat|HotCat]]'
        // 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 categories'
        // $1 is replaced by a number. If your language has several plural forms (c.f. [[:en:Dual (grammatical form)]]),
// $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().
// 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
// If that function doesn't exist, HotCat will simply fall back to using the last
        // entry in the array.
// entry in the array.
    ,commit      : 'Speichern'
,commit      : 'Save'
        // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
        // see localization hook below.
// see localization hook below.
    ,ok          : 'OK'
,ok          : 'OK'
        // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
        // see localization hook below.
// see localization hook below.
    ,cancel      : 'Abbrechen'
,cancel      : 'Cancel'
        // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
        // see localization hook below.
// see localization hook below.
    ,multi_error  : 'Quelltext konnte nicht abrufen werden. Deine Änderungen wurden deshalb nicht gespeichert.'
,multi_error  : 'Could not retrieve the page text from the server. Therefore, your category changes '
        // Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
+'cannot be saved. We apologize for the inconvenience.'
        // see localization hook below.
// Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
    }
// see localization hook below.
,short_catchange : null
// Defaults to '[[' + category_canonical + ':$1]]'. Can be overridden if in the short edit summaries
// not the standard category name should be used but, say, a shorter namespace alias. $1 is replaced
// by a category name.
}
  ,category_regexp    : '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]'
  ,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
  // Wikipedia, use '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]|[Kk][Aa][Tt][Ee][Gg][Oo][Rr][Ii][Ee]', or at the
// Wikipedia, use '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]|[Kk][Aa][Tt][Ee][Gg][Oo][Rr][Ii][Ee]', or at the
  // Chinese Wikipedia, use '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]|分类|分類'. Note that namespaces are case-
// Chinese Wikipedia, use '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]|分类|分類'. Note that namespaces are case-
  // insensitive!
// insensitive!
  ,category_canonical : 'Kategorie'
  ,category_canonical : 'Category'
  // The standard category name on your wiki. Is automatically localized correctly if you're running
// The standard category name on your wiki. Is automatically localized correctly if you're running
  // MediaWiki 1.16 or later; otherwise, set it to the preferred category name (e.g., "Kategorie").
// MediaWiki 1.16 or later; otherwise, set it to the preferred category name (e.g., "Kategorie").
  ,categories        : 'Kategorien'
  ,categories        : 'Categories'
  // Plural of category_canonical.
// Plural of category_canonical.
  ,disambig_category  : null
  ,disambig_category  : 'Disambiguation'
  // 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
  // any items, but that contains links to other categories where stuff should be categorized. If you don't have
// any items, but that contains links to other categories where stuff should be categorized. If you don't have
  // that concept on your wiki, set it to null.
// that concept on your wiki, set it to null. Use blanks, not underscores.
  ,redir_category    : null
  ,redir_category    : 'Category redirects'
  // Any category in this category is deemed a (soft) redirect to some other category defined by the first link
// Any category in this category is deemed a (soft) redirect to some other category defined by a link
  // to another category. If your wiki doesn't have soft category redirects, set this to null.
// to another non-blacklisted category. If your wiki doesn't have soft category redirects, set this to null.
  ,links : {change: '(±)', remove: '()', add: '(+)', restore: '(×)', undo: '(×)', down: '()', up: '()'}
    // If a soft-redirected category contains more than one link to another non-blacklisted category, it's considered
  // The little modification links displayed after category names.
    // a disambiguation category instead.
  ,links : {change: '(±)', remove: '(\u2212)', add: '(+)', restore: '(×)', undo: '(×)', down: '(\u2193)', up: '(\u2191)'}
// The little modification links displayed after category names. U+2212 is a minus sign; U+2193 and U+2191 are
// downward and upward pointing arrows. Do not use ↓ and ↑ in the code!
  ,tooltips : {
  ,tooltips : {
    change:  'Ändern'
change:  'Modify'
  ,remove:  'Entfernen'
,remove:  'Remove'
  ,add:    'Neue Kategorie hinzufügen'
,add:    'Add a new category'
  ,restore: 'Wiederherstellen'
,restore: 'Undo changes'
  ,undo:    'Zurücksetzen'
,undo:    'Undo changes'
  ,down:    'durch Unterkategorie ersetzen'
,down:    'Open for modifying and display subcategories'
  ,up:      'durch Überkategorie ersetzen'
,up:      'Open for modifying and display parent categories'
   }
   }
  // The tooltips for the above links
// The tooltips for the above links
  ,addmulti          : '<span>+<sup>+</sup></span>'
  ,addmulti          : '<span>+<sup>+</sup></span>'
  // The HTML content of the "enter multi-mode" link at the front.
// The HTML content of the "enter multi-mode" link at the front.
  ,multi_tooltip      : 'Mehrere Kategorien ändern'
  ,multi_tooltip      : 'Modify several categories'
  // Tooltip for the "enter multi-mode" link
// Tooltip for the "enter multi-mode" link
  ,disable            :
  ,disable            :
    function () { // Return true to disable HotCat. HotCat guarantees that the wg* globals exist here.
function () { // Return true to disable HotCat. HotCat guarantees that the wg* globals exist here.
      var ns = wgNamespaceNumber;
var ns = wgNamespaceNumber;
      return (  ns < 0   // Special pages; Special:Upload is handled differently
return (  ns < 0     // Special pages; Special:Upload is handled differently
              || ns === 10 // Templates
|| ns === 10 // Templates
              || ns === 8 // MediaWiki
|| ns === 828 // Module (Lua)
              || ns === 6 && wgArticleId === 0 // Non-existing file pages
|| ns === 8   // MediaWiki
              || ns === 2 && /\.(js|css)$/.test(wgTitle) // User scripts
|| ns === 6 && wgArticleId === 0 // Non-existing file pages
              || typeof (wgNamespaceIds) != 'undefined'
|| ns === 2 && /\.(js|css)$/.test(wgTitle) // User scripts
                && (  ns === wgNamespaceIds['creator']
|| typeof (wgNamespaceIds) != 'undefined'
                    || ns === wgNamespaceIds['timedtext']
&& (  ns === wgNamespaceIds['creator']
                    || ns === wgNamespaceIds['institution']
|| ns === wgNamespaceIds['timedtext']
                    )
|| ns === wgNamespaceIds['institution']
            );
  )
    }
  );
  ,uncat_regexp : null
}
  // A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
  ,uncat_regexp : /\{\{\s*([Uu]ncat(egori[sz]ed( image)?)?|[Nn]ocat|[Nn]eedscategory)[^}]*\}\}\s*(<\!--.*?--\>)?/g
  // If not, set it to null.
// A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
// If not, set it to null.
  ,existsYes    : '//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    : '//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]|[Vv][Oo][Rr][Ll][Aa][Gg][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
  // key: the category without namespace
// key: the category without namespace
  // value: A regexp matching the template name, again without namespace
// value: A regexp matching the template name, again without namespace
  // If you don't have this at your wiki, or don't want this, set it to an empty object {}.
// If you don't have this at your wiki, or don't want this, set it to an empty object {}.
  ,engine_names : {
  ,engine_names : {
    searchindex : 'Indexsuche'
searchindex : 'Search index'
  ,pagelist    : 'Seitenliste'
,pagelist    : 'Page list'
  ,combined    : 'Kombinierte Suche'
,combined    : 'Combined search'
  ,subcat      : 'Unterkategorien'
,subcat      : 'Subcategories'
  ,parentcat  : 'Überkategorien'
,parentcat  : 'Parent categories'
   }
   }
  // 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 correctly 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
  ,upload_disabled : false
  // If upload_disabled is true, HotCat will not be used on the Upload form.
// If upload_disabled is true, HotCat will not be used on the Upload form.
  ,blacklist : null
  ,blacklist : null
  // Single regular expression matching blacklisted categories that cannot be changed or
// Single regular expression matching blacklisted categories that cannot be changed or
  // added using HotCat. For instance /\bstubs?$/ (any category ending with the word "stub"
// 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
// or "stubs"), or /(\bstubs?$)|\bmaintenance\b/ (stub categories and any category with the
  // word "maintenance" in its title.
// word "maintenance" in its title.
 
  // Stuff changeable by users:
// Stuff changeable by users:
  ,bg_changed : '#F8CCB0'
  ,bg_changed : '#F8CCB0'
  // Background for changed categories in multi-edit mode. Default is a very light salmon pink.
// Background for changed categories in multi-edit mode. Default is a very light salmon pink.
  ,no_autocommit : false
  ,no_autocommit : false
  // If true, HotCat will never automatically submit changes. HotCat will only open an edit page with
// If true, HotCat will never automatically submit changes. HotCat will only open an edit page with
  // the changes; users must always save explicitly.
// the changes; users must always save explicitly.
,del_needs_diff : false
// If true, the "category deletion" link "(-)" will never save automatically but always show an
// edit page where the user has to save the edit manually. Is false by default because that's the
// traditional behavior. This setting overrides no_autocommit for "(-)" links.
  ,suggest_delay : 100
  ,suggest_delay : 100
  // Time, in milliseconds, that HotCat waits after a keystroke before making a request to the
// Time, in milliseconds, that HotCat waits after a keystroke before making a request to the
  // server to get suggestions.
// server to get suggestions.
  ,editbox_width : 40
  ,editbox_width : 40
  // Default width, in characters, of the text input field.
// Default width, in characters, of the text input field.
  ,suggestions : 'combined'
  ,suggestions : 'combined'
  // One of the engine_names above, to be used as the default suggestion engine.
// One of the engine_names above, to be used as the default suggestion engine.
  ,fixed_search : false
  ,fixed_search : false
  // If true, always use the default engine, and never display a selector.
// If true, always use the default engine, and never display a selector.
  ,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
  ,list_size : 5
  // Default list size
// Default list size
  ,single_minor : true
  ,single_minor : true
  // If true, single category changes are marked as minor edits. If false, they're not.
// If true, single category changes are marked as minor edits. If false, they're not.
  ,dont_add_to_watchlist : false
  ,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
// 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"
// 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.
// options in his or her preferences set.
,shortcuts : null
,addShortcuts :
function (map) {
if (!map) return;
window.HotCat.shortcuts = window.HotCat.shortcuts || {};
for (var k in map) {
if (!map.hasOwnProperty (k) || typeof k != 'string') continue;
var v = map[k];
if (typeof v != 'string') continue;
k = k.replace (/^\s+|\s+$/g, "");
v = v.replace (/^\s+|\s+$/g, "");
if (k.length === 0 || v.length === 0) continue;
window.HotCat.shortcuts[k] = v;
}
}
};
};
 
// Make sure this is exported, so that localizations *can* actually modify parts of it, and the
// guard at the top actually works. (If we're loaded as an extension module through the resource
// loader, this outer scope may actually be a closure, not the global "window" scope.)
if (typeof (window.HotCat) == 'undefined') window.HotCat = HotCat;
 
(function () { // Local scope to avoid polluting the global namespace with declarations
(function () { // Local scope to avoid polluting the global namespace with declarations
  // 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.
  if (window.mediaWiki && window.mediaWiki.config) {
    var globals = window.mediaWiki.config.get();
    if (globals && globals !== window) {
      for (var k in globals) window[k] = globals[k];
      window.mediWiki.config = new window.mediaWiki.Map(true); // Make config point to window again.
    }
    globals = null;
  }
  // More backwards compatibility. We have four places where we test for the browser: once for
  // Safari < 3.0, once for WebKit (Chrome or Safari, any versions), and twice for IE <= 6.
  var ua = navigator.userAgent.toLowerCase();
  var is_ie6 = /msie ([0-9]{1,}[\.0-9]{0,})/.exec(ua) != null && parseFloat(RegExp.$1) <= 6.0;
  var is_webkit = /applewebkit\/\d+/.test(ua) && ua.indexOf ('spoofer') < 0; 
  // And even more compatbility. HotCat was developed without jQuery, and anyway current jQuery
  // (1.7.1) doesn't seem to support in jquery.getJSON() or jQuery.ajax() the automatic
  // switching from GET to POST requests if the query arguments would make the uri too long.
  // (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
      } // end try-catch
      return request;
    }
    function makeRequest (settings) {
      var req = getRequest();
      if (!req && settings && settings.error) settings.error (req);
      if (!req || !settings || !settings.uri) return req;
      var uri = armorUri (settings.uri);
      var args = settings.data || null;
      var method;
      if (args && uri.length + args.length + 1 > 2000) {
        // We lose caching, but at least we can make the request
        method = 'POST';
        req.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded');
      } else {
        method = 'GET';
        if (args) uri += '?' + args;
        args = null;
      }
      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;
    }
    return makeRequest;
  })();
  function armorUri (uri) {
    // Avoid protocol-relative URIs, IE7 has a bug with them in Ajax calls
    if (uri.length >= 2 && uri.substring(0, 2) == '//') return document.location.protocol + uri;
    return uri;
  }
  function LoadTrigger () { this.initialize.apply (this, arguments); };
  LoadTrigger.prototype = {
    initialize : function (needed) {
      this.queue = [];
      this.toLoad = needed;
    },
    register : function (callback) {
      if (this.toLoad <= 0) {
        callback (); // Execute directly
      } else {
        this.queue[this.queue.length] = callback;
      }
    },
    loaded : function () {
      if (this.toLoad > 0) {
        this.toLoad--;
        if (this.toLoad === 0) {
          // Run queued callbacks once
          for (var i = 0; i < this.queue.length; i++) this.queue[i]();
          this.queue = [];
        }
      }
    }
  };
  var 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 loadJS (page) {
    load (wgServer + wgScript + '?title=' + encodeURIComponent (page) + '&action=raw&ctype=text/javascript');
  }
  function loadURI (href) {
    var url = href;
    if (url.substring (0, 2) == '//') {
      url = window.location.protocol + url;
    } else if (url.substring (0, 1) == '/') {
      url = wgServer + url;
    }
    load (url);
  }
  if (HotCat.isCommonsVersion && wgServer.indexOf ('/commons') < 0) {
    // We're running in some other wiki, which hotlinks to the Commons version. The other wiki can put local settings
    // in this file to override the Commons settings for all user languages. For instance, if on your wiki people do
    // not like automatic saving, you'd add in that file the line HotCat.no_autocommit = true; If you hotlink, you
    // *must* adapt HotCat.categories in this file to the local translation in wgContentLanguage of your wiki of the
    // English plural "Categories", and you should provide translations in wgContentLanguage of your wiki of all messages,
    // tooltips, and of the engine names.
    loadJS ('MediaWiki:Gadget-HotCat.js/local_defaults');
  } else {
    loadTrigger.loaded();
  }
  if (wgUserLanguage != '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 {
      // Load translations locally
      loadJS ('MediaWiki:Gadget-HotCat.js/' + wgUserLanguage);
    }
  } else {
    loadTrigger.loaded();
  }
  // No further changes should be necessary here.
  // The following regular expression strings are used when searching for categories in wikitext.
  var wikiTextBlank  = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+';
  var wikiTextBlankRE = new RegExp (wikiTextBlank, 'g');
  // Regexp for handling blanks inside a category title or namespace name.
  // See http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Title.php?revision=104051&view=markup#l2722
  // 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
  // number of special whitespace characters by simple blanks. And finally, blanks are treated as underscores.
  // Therefore, when looking for page titles in wikitext, we must handle all these cases.
  //  Note: we _do_ include the horizontal tab in the above list, even though the MediaWiki software for some reason
  // appears to not handle it. The zero-width space \u200B is _not_ handled as a space inside titles by MW.
  var wikiTextBlankOrBidi = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200B\\u200E\\u200F\\u2028-\\u202F\\u205F\\u3000]*';
  // Whitespace regexp for handling whitespace between link components. Including the horizontal tab, but not \n\r\f\v:
  // a link must be on one single line.
  //  MediaWiki also removes Unicode bidi override characters in page titles (and namespace names) completely.
  // 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,
  // or adjacent to and inside of "[[" and "]]").
  // First auto-localize the regexps for the category and the template namespaces.
  if (typeof (wgFormattedNamespaces) != 'undefined') {
    function autoLocalize (namespaceNumber, fallback) {
      function create_regexp_str (name)
      {
        if (!name || name.length === 0) return "";
        var regex_name = "";
        for (var i = 0; i < name.length; i++){
          var initial = name.substr (i, 1);
          var ll = initial.toLowerCase ();
          var ul = initial.toUpperCase ();
          if (ll == ul){
            regex_name += initial;
          } else {
            regex_name += '[' + ll + ul + ']';
          }
        }
        return regex_name.replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1')
                        .replace (wikiTextBlankRE, wikiTextBlank);
      }
      fallback = fallback.toLowerCase();
      var canonical  = wgFormattedNamespaces["" + namespaceNumber].toLowerCase();
      var regexp    = create_regexp_str (canonical);
      if (fallback && canonical != fallback) regexp += '|' + create_regexp_str (fallback);
      for (var cat_name in wgNamespaceIds) {
        if (  typeof (cat_name) == 'string'
            && cat_name.toLowerCase () != canonical
            && cat_name.toLowerCase () != fallback
            && wgNamespaceIds[cat_name] == namespaceNumber)
        {
          regexp += '|' + create_regexp_str (cat_name);
        }
      }
      return regexp;
    }
    if (wgFormattedNamespaces['14']) {
      HotCat.category_canonical = wgFormattedNamespaces['14'];
      HotCat.category_regexp = autoLocalize (14, 'category');
    }
    if (wgFormattedNamespaces['10']) {
      HotCat.template_regexp = autoLocalize (10, 'template');
    }
  }
  // Utility functions. Yes, this duplicates some functionality that also exists in other places, but
  // to keep this whole stuff in a single file not depending on any other on-wiki Javascripts, we re-do
  // these few operations here.
  function bind (func, target) {
    var f = func, tgt = target;
    return function () { return f.apply (tgt, arguments); };
  }
  function make (arg, literal) {
    if (!arg) return null;
    return literal ? document.createTextNode (arg) : document.createElement (arg);
  }
  function param (name, uri) {
    if (typeof (uri) == 'undefined' || uri === null) uri = document.location.href;
    var re = new RegExp ('[&?]' + name + '=([^&#]*)');
    var m = re.exec (uri);
    if (m && m.length > 1) return decodeURIComponent(m[1]);
    return null;
  }
  function title (href) {
    if (!href) return null;
    var script = wgScript + '?';
    if (href.indexOf (script) === 0 || href.indexOf (wgServer + script) === 0 || wgServer.substring(0, 2) == '//' && href.indexOf (document.location.protocol + wgServer + script) === 0) {
      // href="/w/index.php?title=..."
      return param ('title', href);
    } else {
      // href="/wiki/..."
      var prefix = wgArticlePath.replace ('$1', "");
      if (href.indexOf (prefix) != 0) prefix = wgServer + prefix; // Fully expanded URL?
      if (href.indexOf (prefix) != 0 && prefix.substring(0, 2) == '//') prefix = document.location.protocol + prefix; // Protocol-relative wgServer?
      if (href.indexOf (prefix) === 0)
        return decodeURIComponent (href.substring (prefix.length));
    }
    return null;
  }
  function hasClass (elem, name) {
    return (' ' + elem.className + ' ').indexOf (' ' + name + ' ') >= 0;
  }
  function capitalize (str) {
    if (!str || str.length === 0) return str;
    return str.substr(0, 1).toUpperCase() + str.substr (1);
  }
  function wikiPagePath (pageName) {
    // Note: do not simply use encodeURI, it doesn't encode '&', which might break if wgArticlePath actually has the $1 in
    // a query parameter.
    return wgArticlePath.replace('$1', encodeURIComponent (pageName).replace(/%3A/g, ':').replace(/%2F/g, '/'));
  }
  function substitute (str, map) {
    // Replace $1, $2, or ${key1}, ${key2} by values from map. $$ is replaced by a single $.
    return str.replace(
      /\$(\$|(\d+)|\{([^{}]+)\})/g
    ,function (match, dollar, idx, key) {
        if (dollar == '$') return '$';
        var k = key || idx;
        var replacement = typeof (map[k]) === 'function' ? map[k](match, k) : map[k];
        return typeof (replacement) === 'string' ? replacement : (replacement || match);
      }
    );
  }
  // Text modification
  var findCatsRE =
    new RegExp ('\\[\\[' + wikiTextBlankOrBidi + '(?:' + HotCat.category_regexp + ')' + wikiTextBlankOrBidi + ':[^\\]]+\\]\\]', 'g');
  function replaceByBlanks (match) {
    return match.replace(/(\s|\S)/g, ' '); // /./ doesn't match linebreaks. /(\s|\S)/ does.
  }
  function find_category (wikitext, category, once) {
    var cat_regex = null;
    if(HotCat.template_categories[category]){
      cat_regex = new RegExp ('\\{\\{' + wikiTextBlankOrBidi + '(' + HotCat.template_regexp + '(?=' + wikiTextBlankOrBidi + ':))?' + wikiTextBlankOrBidi
                              + '(?:' + HotCat.template_categories[category] + ')'
                              + wikiTextBlankOrBidi + '(\\|.*?)?\\}\\}', 'g'
                            );
    } else {
      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'
                            );
    }
    if (once) return cat_regex.exec (wikitext);
    var copiedtext = wikitext.replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks)
                            .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks);
    var result = [];
    var curr_match = null;
    while ((curr_match = cat_regex.exec (copiedtext)) != null) {
      result.push ({match : curr_match});
    }
    result.re = cat_regex;
    return result; // An array containing all matches, with positions, in result[i].match
  }
  var interlanguageRE = null;
  function change_category (wikitext, toRemove, toAdd, key, is_hidden) {
    function find_insertionpoint (wikitext) {
      var copiedtext = wikitext.replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks)
                              .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks);
      // Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element".
      var 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};
    }
    var summary  = [];
    var nameSpace = HotCat.category_canonical;
    var cat_point = -1; // Position of removed category;
    if (key) key = '|' + key;
    var keyChange = (toRemove && toAdd && toRemove == toAdd && toAdd.length > 0);
    if (toRemove && toRemove.length > 0) {
      var matches = find_category (wikitext, toRemove);
      if (!matches || matches.length === 0) {
        return {text: wikitext, 'summary': summary, error: HotCat.messages.cat_notFound.replace (/\$1/g, toRemove)};
      } else {
        var before = wikitext.substring (0, matches[0].match.index);
        var after  = wikitext.substring (matches[0].match.index + matches[0].match[0].length);
        if (matches.length > 1) {
          // Remove all occurrences in after
          matches.re.lastIndex = 0;
          after = after.replace (matches.re, "");
        }
        if (toAdd) {
          nameSpace = matches[0].match[1] || nameSpace;
          if (key == null) key = matches[0].match[2]; // Remember the category key, if any.
        }
        // Remove whitespace (properly): strip whitespace, but only up to the next line feed.
        // If we then have two linefeeds in a row, remove one. Otherwise, if we have two non-
        // whitespace characters, insert a blank.
        var i = before.length - 1;
        while (i >= 0 && before.charAt (i) != '\n' && before.substr (i, 1).search (/\s/) >= 0) i--;
        var j = 0;
        while (j < after.length && after.charAt (j) != '\n' && after.substr (j, 1).search (/\s/) >= 0)
          j++;
        if (i >= 0 && before.charAt (i) == '\n' && (after.length === 0 || j < after.length && after.charAt (j) == '\n'))
          i--;
        if (i >= 0) before = before.substring (0, i+1); else before = "";
        if (j < after.length) after = after.substring (j); else after = "";
        if (before.length > 0 && before.substring (before.length - 1).search (/\S/) >= 0
            && after.length > 0 && after.substr (0, 1).search (/\S/) >= 0)
          before += ' ';
        cat_point = before.length;
        wikitext = before + after;
        if (!keyChange) {
          if(HotCat.template_categories[toRemove]) {
            summary.push (HotCat.messages.template_removed.replace (/\$1/g, toRemove));
          } else {
            summary.push (HotCat.messages.cat_removed.replace (/\$1/g, toRemove));
          }
        }
      }
    }
    if (toAdd && toAdd.length > 0) {
      var matches = find_category (wikitext, toAdd);
      if (matches && matches.length > 0) {
        return {text: wikitext, 'summary': summary, error : HotCat.messages.cat_exists.replace (/\$1/g, toAdd)};
      } else {
        var onCat = false;
        if (cat_point < 0) {
          var point = find_insertionpoint (wikitext);
          cat_point = point.idx;
          onCat = point.onCat;
        } else {
          onCat = true;
        }
        var newcatstring = '[[' + nameSpace + ':' + toAdd + (key || "") + ']]';
        if (cat_point >= 0) {
          var suffix = wikitext.substring (cat_point);
          wikitext = wikitext.substring (0, cat_point) + (cat_point > 0 ? '\n' : "") + newcatstring + (!onCat ? '\n' : "");
          if (suffix.length > 0 && suffix.substr(0, 1) != '\n') {
            wikitext += '\n' + suffix;
          } else {
            wikitext += suffix;
          }
        } else {
          if (wikitext.length > 0 && wikitext.substr (wikitext.length - 1, 1) != '\n')
            wikitext += '\n';
          wikitext += '\n' + newcatstring;
        }
        if (keyChange) {
          var k = key || "";
          if (k.length > 0) k = k.substr (1);
          summary.push (substitute (HotCat.messages.cat_keychange, [null, toAdd, k]));
        } else {
          summary.push (HotCat.messages.cat_added.replace (/\$1/g, toAdd));
        }
        if (HotCat.uncat_regexp && !is_hidden) {
          var txt = wikitext.replace (HotCat.uncat_regexp, ""); // Remove "uncat" templates
          if (txt.length != wikitext.length) {
            wikitext = txt;
            summary.push (HotCat.messages.uncat_removed);
          }
        }
      }
    }
    return {text: wikitext, 'summary': summary, error: null};
  }
  // The real HotCat UI
  function evtKeys (e) {
    e = e || window.event || window.Event; // W3C, IE, Netscape
    var code = 0;
    if (typeof (e.ctrlKey) != 'undefined') { // All modern browsers
      // Ctrl-click seems to be overloaded in FF/Mac (it opens a pop-up menu), so treat cmd-click
      // as a ctrl-click, too.
      if (e.ctrlKey || e.metaKey)  code |= 1;
      if (e.shiftKey) code |= 2;
    } else if (typeof (e.modifiers) != 'undefined') { // Netscape...
      if (e.modifiers & (Event.CONTROL_MASK | Event.META_MASK)) code |= 1;
      if (e.modifiers & Event.SHIFT_MASK) code |= 2;
    }
    return code;
  }
  function evtKill (e) {
    e = e || window.event || window.Event; // W3C, IE, Netscape
    if (typeof (e.preventDefault) != 'undefined') {
      e.preventDefault ();
      e.stopPropagation ();
    } else
      e.cancelBubble = true;
    return false;
  }
  function addEvent (node, evt, f, capture)
  {
    if (window.jQuery && (!capture || !node.addEventListener)) window.jQuery (node).bind (evt, f);
    else if (node.attachEvent) node.attachEvent ('on' + evt, f);
    else if (node.addEventListener) node.addEventListener (evt, f, capture);
    else node['on' + evt] = f;
  }
  var catLine      = null;
  var onUpload    = false;
  var editors      = [];
  var commitButton = null;
  var commitForm  = null;
  var multiSpan    = null;
  var pageText    = null;
  var pageTime    = null;
  var pageWatched  = false;
  var watchCreate  = false;
  var watchEdit    = false;
  var minorEdits  = false;
  var editToken    = null;
  var is_rtl      = false;
  var serverTime  = null;
  var newDOM      = false; // true if MediaWiki serves the new UL-LI DOM for categories
  function setMultiInput () {
    if (commitButton || onUpload) return;
    commitButton = make ('input');
    commitButton.type  = 'button';
    commitButton.value = HotCat.messages.commit;
    commitButton.onclick = multiSubmit;
    if (multiSpan) {
      multiSpan.parentNode.replaceChild (commitButton, multiSpan);
    } else {
      catLine.appendChild (commitButton);
    }
  }
  function checkMultiInput () {
    if (!commitButton) return;
    var has_changes = false;
    for (var i = 0; i < editors.length; i++) {
      if (editors[i].state != CategoryEditor.UNCHANGED) {
        has_changes = true;
        break;
      }
    }
    commitButton.disabled = !has_changes;
  }
  function currentTimestamp () {
    var now = new Date();
    var ts  = "" + now.getUTCFullYear();
    function two (s) { return s.substr (s.length - 2); }
    ts = ts
      + two ('0' + (now.getUTCMonth() + 1))
      + two ('0' + now.getUTCDate())
      + two ('00' + now.getUTCHours())
      + two ('00' + now.getUTCMinutes())
      + two ('00' + now.getUTCSeconds());
    return ts;
  }
  function initiateEdit (doEdit, failure) {
    // Must use Ajax here to get the user options and the edit token.
    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'
        + '&rvlimit=1&rvstartid=' + wgCurRevisionId
        + '&meta=siteinfo%7Cuserinfo&uiprop=options'
      ,success : function (json) { setPage(json); doEdit(failure); }
      ,error : function (req) { failure(req.status + ' ' + req.statusText); }
    });
  }
  function multiChangeMsg (count) {
    var msg = HotCat.messages.multi_change;
    if (typeof (msg) != 'string' && msg.length) {
      if (window.mediaWiki && window.mediaWiki.language && window.mediaWiki.language.convertPlural) {
        msg = window.mediaWiki.language.convertPlural (count, msg);
      } else {
        msg = msg[msg.length-1];
      }
    }
    return substitute (msg, [null, "" + count]);   
  }
  function performChanges (failure, singleEditor) {
    if (pageText === null) {
      failure (HotCat.messages.multi_error);
      return;
    }
    // Backwards compatibility after message change (added $2 to cat_keychange)
    if (HotCat.messages.cat_keychange.indexOf ('$2') < 0) {
      HotCat.messages.cat_keychange += '"$2"';
    }
    // Create a form and submit it. We don't use the edit API (api.php?action=edit) because
    // (a) sensibly reporting back errors like edit conflicts is always a hassle, and
    // (b) we want to show a diff for multi-edits anyway.
    // Using the form, we can do (b) and we get (a) for free. And, of course, using the form
    // automatically reloads the page with the updated categories on a successful submit, which
    // we would have to do explicitly if we used the edit API.
    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;
        }
      }
    }
    if (error !== null) { // Do not commit if there were errors
      action = commitForm.wpSave;
      if (action) action.name = action.value = 'wpDiff';
    }
    if (changes === 0 && !singleEditor) return;
    // Fill in the form and submit it
    commitForm.wpAutoSummary.value = 'd41d8cd98f00b204e9800998ecf8427e'; // MD5 hash of the empty string
    commitForm.wpMinoredit.checked = minorEdits;
    commitForm.wpWatchthis.checked = wgArticleId == 0 && watchCreate || watchEdit || pageWatched;
    if (wgArticleId > 0 || !!singleEditor) {
      if (changes == 1) {
        if (result.summary && result.summary.length > 0)
          commitForm.wpSummary.value = HotCat.messages.prefix + result.summary.join (HotCat.messages.separator) + HotCat.messages.using;
        commitForm.wpMinoredit.checked = HotCat.single_minor || minorEdits;
      } else if (changes > 1) {
        var summary = [];
        var shortSummary = [];
        // Deleted
        for (var i=0; i < deleted.length; i++) {
          summary.push ('−[[' + HotCat.category_canonical + ':' + deleted[i] + ']]');
        }
        if (deleted.length == 1)
          shortSummary.push ('−[[' + HotCat.category_canonical + ':' + deleted[0] + ']]');
        else if (deleted.length > 1)
          shortSummary.push ('− ' + 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 + ']]');
          }
        }
        if (changed.length == 1) {
          if (changed[0].from != changed[0].to) {
            shortSummary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + arrow
                        + HotCat.category_canonical + ':' + changed[0].to + ']]');
          } else {
            shortSummary.push ('±[[' + HotCat.category_canonical + ':' + changed[0].from + ']]');
          }
        } else if (changed.length > 1) {
          shortSummary.push ('± ' + multiChangeMsg (changed.length));
        }
        if (summary.length > 0) {
          summary = summary.join (HotCat.messages.separator);
          if (summary.length > 200 - HotCat.messages.prefix.length - HotCat.messages.using.length) {
            summary = shortSummary.join (HotCat.messages.separator);
          }
          commitForm.wpSummary.value = HotCat.messages.prefix + summary + HotCat.messages.using;
        }
      }
    }
    commitForm.wpTextbox1.value = result.text;
    commitForm.wpStarttime.value = serverTime || currentTimestamp ();
    commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value;
    // Submit the form in a way that triggers onsubmit events: commitForm.submit() doesn't.
    commitForm.hcCommit.click();
  }
  function resolveMulti (toResolve, callback) {
    for (var i = 0; i < toResolve.length; i++) {
      toResolve[i].dab = null;
      toResolve[i].dabInput = toResolve[i].lastInput;
    }
    if (noSuggestions) {
      callback (toResolve);
      return;
    }
    // Use %7C instead of |, otherwise Konqueror insists on re-encoding the arguments, resulting in doubly encoded
    // category names. (That is a bug in Konqueror. Other browsers don't have this problem.)
    var args = 'action=query&prop=info%7Clinks%7Ccategories%7Ccategoryinfo&plnamespace=14'
            + '&pllimit=' + (toResolve.length * 10)
            + '&cllimit=' + (toResolve.length * 10)
            + '&format=json&titles=';
    for (var i = 0; i < toResolve.length; i++) {
      args += encodeURIComponent ('Category:' + toResolve[i].dabInput);
      if (i+1 < toResolve.length) args += '%7C';
    }
    getJSON({
      uri : wgServer + wgScriptPath + '/api.php'
    ,data : args
    ,success: function (json) { resolveRedirects (toResolve, json); callback (toResolve); }
    ,error: function (req) { if (!req) noSuggestions = true; callback (toResolve); }
    });
  }
  function resolveOne (page, toResolve) {
    var cats    = page.categories;
    var lks      = page.links;
    var is_dab  = false;
    var is_redir = typeof (page.redirect) == 'string'; // Hard redirect?
    var is_hidden = page.categoryinfo && typeof (page.categoryinfo.hidden) == 'string';
    for (var j = 0; j < toResolve.length; j++) {
      if (toResolve[j].dabInput != page.title.substring (page.title.indexOf (':') + 1)) continue;
      toResolve[j].currentHidden = is_hidden;
    }
    if (!is_redir && cats && (HotCat.disambig_category || HotCat.redir_category)) {
      for (var c = 0; c < cats.length; c++) {
        var cat = cats[c]['title'];
        // Strip namespace prefix
        if (cat) {
          cat = cat.substring (cat.indexOf (':') + 1).replace(/_/g, ' ');
          if (cat == HotCat.disambig_category) {
            is_dab = true; break;
          } else if (cat == HotCat.redir_category) {
            is_redir = true; break;
          }
        }
      }
    }
    if (!is_redir && !is_dab) return;
    if (!lks || lks.length === 0) return;
    var titles = [];
    for (var i = 0; i < lks.length; i++) {
      if (  lks[i]['ns'] == 14                            // Category namespace
          && lks[i]['title'] && lks[i]['title'].length > 0) // Name not empty
      {
        // Internal link to existing thingy. Extract the page name and remove the namespace.
        var match = lks[i]['title'];
        titles.push (match.substring (match.indexOf (':') + 1));
        if (is_redir) break;
      }
    }
    for (var j = 0; j < toResolve.length; j++) {
      if (toResolve[j].dabInput != page.title.substring (page.title.indexOf (':') + 1)) continue;
      if (titles.length > 1) {
        toResolve[j].dab = titles;
      } else {
        toResolve[j].inputExists = true; // Might actually be wrong...
        toResolve[j].icon.src = armorUri(HotCat.existsYes);
        toResolve[j].text.value =
          titles[0] + (toResolve[j].currentKey != null ? '|' + toResolve[j].currentKey : "");
      }
    }
  }
  function resolveRedirects (toResolve, params) {
    if (!params || !params.query || !params.query.pages) return;
    for (var p in params.query.pages) resolveOne (params.query.pages[p], toResolve);
  }
  function multiSubmit () {
    var toResolve = [];
    for (var i = 0; i < editors.length; i++) {
      if (editors[i].state == CategoryEditor.CHANGE_PENDING || editors[i].state == CategoryEditor.OPEN)
        toResolve.push (editors[i]);
    }
    if (toResolve.length === 0) {
      initiateEdit (function (failure) {performChanges (failure);}, function (msg) {alert (msg);});
      return;
    }
    resolveMulti (
        toResolve
      , function (resolved) {
          var firstDab = null;
          var dontChange = false;
          for (var i = 0; i < resolved.length; i++) {
            if (resolved[i].lastInput != resolved[i].dabInput) {
              // We didn't disable all the open editors, but we did asynchronous calls. It is
              // theoretically possible that the user changed something...
              dontChange = true;
            } else {
              if (resolved[i].dab) {
                if (!firstDab) firstDab = resolved[i];
              } else {
                if (resolved[i].acceptCheck(true)) resolved[i].commit();
              }
            }
          }
          if (firstDab) {
            CategoryEditor.makeActive (firstDab);
          } else if (!dontChange) {
            initiateEdit (function (failure) {performChanges (failure);}, function (msg) {alert (msg);});
          }
        }
    );
  }
  var cat_prefix = null;
  var noSuggestions = false;
  var suggestionEngines = {
    opensearch :
      { uri    : '/api.php?format=json&action=opensearch&namespace=14&limit=30&search=Category:$1' // $1 = search term
      ,handler : // Function to convert result of uri into an array of category names
          function (queryResult, queryKey) {
            if (  queryResult != null && queryResult.length == 2
                && queryResult[0].toLowerCase() == 'category:' + queryKey.toLowerCase()
              )
            {
              var titles = queryResult[1];
              if (!cat_prefix) cat_prefix = new RegExp ('^(' + HotCat.category_regexp + ':)');
              for (var i = 0; i < titles.length; i++) {
                cat_prefix.lastIndex = 0;
                var m = cat_prefix.exec (titles[i]);
                if (m && m.length > 1) {
                  titles[i] = titles[i].substring (titles[i].indexOf (':') + 1); // rm namespace
                } else {
                  titles.splice (i, 1); // Nope, it's not a category after all.
                  i--;
                }
              }
              return titles;
            }
            return null;
          }
      }
    ,internalsearch :
      { uri    : '/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1'
      ,handler :
          function (queryResult, queryKey) {
            if (queryResult && queryResult.query && queryResult.query.allpages) {
              var titles = queryResult.query.allpages;
              var key    = queryKey.toLowerCase();
              for (var i = 0; i < titles.length; i++) {
                titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace
                if (titles[i].toLowerCase().indexOf (key) != 0) {
                  titles.splice (i, 1); // Doesn't start with the query key
                  i--;
                }
              }
              return titles;
            }
            return null;
          }
      }
    ,subcategories :
      // I don't understand why they didn't map cmnamespace=14 automatically to cmtype=subcat,
      // which gives better results and is faster.
      { uri    : '/api.php?format=json&action=query&list=categorymembers'
                +(function (version) {
                    var m = version.match(/^(\d+)\.(\d+)/);
                    var major = 0, minor = 0;
                    if (m && m.length > 1) {
                      major = parseInt (m[1], 10);
                      minor = (m.length > 2 ? parseInt (m[2], 10) : 0);
                    }
                    if (major > 1 || major === 1 && minor > 17) return '&cmtype=subcat'; // Since MW1.18
                    return '&cmnamespace=14';
                  }
                  )(wgVersion)
                +'&cmlimit=max&cmtitle=Category:$1'
      ,handler :
          function (queryResult, queryKey) {
            if (queryResult && queryResult.query && queryResult.query.categorymembers) {
              var titles = queryResult.query.categorymembers;
              for (var i = 0; i < titles.length; i++) {
                titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace
              }
              return titles;
            }
            return null;
          }
      }
  ,parentcategories :
      { uri    : '/api.php?format=json&action=query&prop=categories&titles=Category:$1&cllimit=max'
      ,handler :
          function (queryResult, queryKey) {
            if (queryResult && queryResult.query && queryResult.query.pages) {
              for (var p in queryResult.query.pages) {
                if (queryResult.query.pages[p].categories) {
                  var titles = queryResult.query.pages[p].categories;
                  for (var i = 0; i < titles.length; i++) {
                    titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace
                  }
                  return titles;
                }
              }
            }
            return null;
          }
      }
  };
  var suggestionConfigs = {
    searchindex : {name: 'Search index', engines: ['opensearch'], cache: {}, show: true, temp: false, noCompletion : false}
  ,pagelist    : {name: 'Page list', engines: ['internalsearch'], cache: {}, show: true, temp: false, noCompletion : false}
  ,combined    : {name: 'Combined search', engines: ['opensearch', 'internalsearch'], cache: {}, show: true, temp: false, noCompletion : false}
  ,subcat      : {name: 'Subcategories', engines: ['subcategories'], cache: {}, show: true, temp: true, noCompletion : true}
  ,parentcat  : {name: 'Parent categories', engines: ['parentcategories'], cache: {}, show: true, temp: true, noCompletion : true}
  };
  function CategoryEditor () { this.initialize.apply (this, arguments); };
  CategoryEditor.UNCHANGED      = 0;
  CategoryEditor.OPEN          = 1; // Open, but no input yet
  CategoryEditor.CHANGE_PENDING = 2; // Open, some input made
  CategoryEditor.CHANGED        = 3;
  CategoryEditor.DELETED        = 4;
  // IE6 sometimes forgets to redraw the list when editors are opened or closed.
  // Adding/removing a dummy element helps, at least when opening editors.
  CategoryEditor.dummyElement  = make ('\xa0', true);
  CategoryEditor.forceRedraw = function () {
    if (!is_ie6) return;
    if (CategoryEditor.dummyElement.parentNode) {
      document.body.removeChild (CategoryEditor.dummyElement);
    } else {
      document.body.appendChild (CategoryEditor.dummyElement);
    }
  }
  CategoryEditor.makeActive = function (toActivate) {
    if (toActivate.is_active) return;
    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);
        }
      }
    }
  };
  CategoryEditor.prototype = {
    initialize : function (line, span, after, key, is_hidden) {
      // If a span is given, 'after' is the category title, otherwise it may be an element after which to
      // insert the new span. 'key' is likewise overloaded; if a span is given, it is the category key (if
      // known), otherwise it is a boolean indicating whether a bar shall be prepended.
      if (!span) {
        this.isAddCategory = true;
        // Create add span and append to catLinks
        this.originalCategory = "";
        this.originalKey = null;
        this.originalExists  = false;
        if (!newDOM) {
          span = make ('span');
          span.className = 'noprint';
          if (key) {
            span.appendChild (make (' | ', true));
            if (after) {
              after.parentNode.insertBefore (span, after.nextSibling);
              after = after.nextSibling;
            } else {
              line.appendChild (span);
            }
          } else if (line.firstChild) {
            span.appendChild (make (' ', true));
            line.appendChild (span);
          }
        }
        this.linkSpan = make ('span');
        this.linkSpan.className = 'noprint nopopups hotcatlink';
        var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this);
        lk.appendChild (make (HotCat.links.add, true)); lk.title = HotCat.tooltips.add;
        this.linkSpan.appendChild (lk);
        span = make (newDOM ? 'li' : 'span');
        span.className = 'noprint';
        if (is_rtl) span.dir = 'rtl';
        span.appendChild (this.linkSpan);
        if (after)
          after.parentNode.insertBefore (span, after.nextSibling);
        else
          line.appendChild (span);
        this.normalLinks = null;
        this.undelLink = null;
        this.catLink = null;
      } else {
        if (is_rtl) span.dir = 'rtl';
        this.isAddCategory = false;
        this.catLink = span.firstChild;
        this.originalCategory = after;
        this.originalKey = (key && key.length > 1) ? key.substr(1) : null; // > 1 because it includes the leading bar
        this.originalExists  = !hasClass (this.catLink, 'new');
        // Create change and del links
        this.makeLinkSpan ();
        if (!this.originalExists && this.upDownLinks) this.upDownLinks.style.display = 'none';
        span.appendChild (this.linkSpan);
      }
      this.originalHidden    = is_hidden;
      this.line              = line;
      this.engine            = HotCat.suggestions;
      this.span              = span;
      this.currentCategory    = this.originalCategory;
      this.currentExists      = this.originalExists;
      this.currentHidden      = this.originalHidden;
      this.currentKey        = this.originalKey;
      this.state              = CategoryEditor.UNCHANGED;
      this.lastSavedState    = CategoryEditor.UNCHANGED;
      this.lastSavedCategory  = this.originalCategory;
      this.lastSavedKey      = this.originalKey;
      this.lastSavedExists    = this.originalExists;
      this.lastSavedHidden    = this.originalHidden;
      if (this.catLink && this.currentKey) {
        this.catLink.title = this.currentKey;
      }
      editors[editors.length] = this;
    },
    makeLinkSpan : function () {
      this.normalLinks = make ('span');
      var lk = null;
      if (this.originalCategory && this.originalCategory.length > 0) {
        lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.remove, this);
        lk.appendChild (make (HotCat.links.remove, true)); lk.title = HotCat.tooltips.remove;
        this.normalLinks.appendChild (make (' ', true));
        this.normalLinks.appendChild (lk);
      }
      if (!HotCat.template_categories[this.originalCategory]) {
        lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this);
        lk.appendChild (make (HotCat.links.change, true)); lk.title = HotCat.tooltips.change;
        this.normalLinks.appendChild (make (' ', true));
        this.normalLinks.appendChild (lk);
        if (!noSuggestions && HotCat.use_up_down) {
          this.upDownLinks = make ('span');
          lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.down, this);
          lk.appendChild (make (HotCat.links.down, true)); lk.title = HotCat.tooltips.down;
          this.upDownLinks.appendChild (make (' ', true));
          this.upDownLinks.appendChild (lk);
          lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.up, this);
          lk.appendChild (make (HotCat.links.up, true)); lk.title = HotCat.tooltips.up;
          this.upDownLinks.appendChild (make (' ', true));
          this.upDownLinks.appendChild (lk);
          this.normalLinks.appendChild (this.upDownLinks);
        }
      }
      this.linkSpan = make ('span');
      this.linkSpan.className = 'noprint nopopups hotcatlink';
      this.linkSpan.appendChild (this.normalLinks);
      this.undelLink = make ('span');
      this.undelLink.className = 'nopopups hotcatlink';
      this.undelLink.style.display = 'none';
      lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.restore, this);
      lk.appendChild (make (HotCat.links.restore, true)); lk.title = HotCat.tooltips.restore;
      this.undelLink.appendChild (make (' ', true));
      this.undelLink.appendChild (lk);
      this.linkSpan.appendChild (this.undelLink);
    },
    makeForm : function () {
      var form = make ('form');
      form.method = 'POST'; form.onsubmit = bind (this.accept, this);
      this.form = form;
      var text = make ('input'); text.type = 'text'; text.size = HotCat.editbox_width;
      if (!noSuggestions) {
        text.onkeyup =
          bind (
            function (evt) {
              evt = evt || window.event || window.Event; // W3C, IE, Netscape
              var key = evt.keyCode || 0;
              if (key === 38 || key === 40 || key === 33 || key === 34) { // Up and down arrows, page up/down
                // In case a browser doesn't generate keypress events for arrow keys...
                if (this.keyCount === 0) return this.processKey (evt);
              } else {
                if (key == 27) { // ESC
                  if (!this.resetKeySelection ()) {
                    // No undo of key selection: treat ESC as "cancel".
                    this.cancel ();
                    return;
                  }
                }
                // Also do this for ESC as a workaround for Firefox bug 524360
                // https://bugzilla.mozilla.org/show_bug.cgi?id=524360
                var dont_autocomplete = (key == 8 || key == 46 || key == 27); // BS, DEL, ESC
                if (this.engine && suggestionConfigs[this.engine] && suggestionConfigs[this.engine].temp && !dont_autocomplete) {
                  this.engine = HotCat.suggestions; // Reset to a search upon input
                }
                this.state = CategoryEditor.CHANGE_PENDING;
                var self = this;
                window.setTimeout (function () {self.textchange (dont_autocomplete);}, HotCat.suggest_delay);
              }
              return true;
            }
          ,this
          );
        text.onkeydown =
          bind (
            function (evt) {
              evt = evt || window.event || window.Event; // W3C, IE, Netscape
              this.lastKey = evt.keyCode || 0;
              this.keyCount = 0;
              // Handle return explicitly, to override the default form submission to be able to check for ctrl
              if (this.lastKey == 13) 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);
      }
      this.text = text;
      this.icon = make ('img');
      var list = null;
      if (!noSuggestions) {
        list = make ('select');
        list.onclick    = bind ( function (e) { if (this.highlightSuggestion (0)) this.textchange (false, true); }, this);
        list.ondblclick = bind (function (e) { if (this.highlightSuggestion (0)) this.accept (e); }, this);
        list.onchange = bind (function (e) { this.highlightSuggestion (0); this.text.focus(); }, this);
        list.onkeyup =
          bind (
            function (evt) {
              evt = evt || window.event || window.Event; // W3C, IE, Netscape
              if (evt.keyCode == 27) {
                this.resetKeySelection ();
                this.text.focus();
                var self = this;
                window.setTimeout (function () {self.textchange (true);}, HotCat.suggest_delay);
              } else if (evt.keyCode == 13) {
                this.accept (evt);
              }
            }
          ,this
          );
        if (!HotCat.fixed_search) {
          var engineSelector = make ('select');
          for (var key in suggestionConfigs) {
            if (suggestionConfigs[key].show) {
              var opt = make ('option');
              opt.value = key;
              if (key == this.engine) opt.selected = true;
              opt.appendChild (make (suggestionConfigs[key].name, true));
              engineSelector.appendChild (opt);
            }
          }
          engineSelector.onchange = bind (
            function () {
              this.engine = this.engineSelector.options[this.engineSelector.selectedIndex].value;
              this.text.focus();
              this.textchange (true, true); // Don't autocomplete, force re-display of list
            }
          ,this
          );
          this.engineSelector = engineSelector;
        }
      }
      this.list = list;
      function button_label (id, defaultText) {
        var label = null;
        if (  onUpload
            && typeof (UFUI) != 'undefined'
            && typeof (UIElements) != 'undefined'
            && typeof (UFUI.getLabel) == 'function') {
          try {
            label = UFUI.getLabel (id, true);
            // Extract the plain text. IE doesn't know that Node.TEXT_NODE == 3
            while (label && label.nodeType != 3) label = label.firstChild;
          } catch (ex) {
            label = null;
          }
        }
        if (!label || !label.data) return defaultText;
        return label.data;
      }
      // Do not use type 'submit'; we cannot detect modifier keys if we do
      var OK = make ('input'); OK.type = 'button';
      OK.value = button_label ('wpOkUploadLbl', HotCat.messages.ok);
      OK.onclick = bind (this.accept, this);
      this.ok = OK;
      var cancel = make ('input'); cancel.type = 'button';
      cancel.value = button_label ('wpCancelUploadLbl', HotCat.messages.cancel);
      cancel.onclick = bind (this.cancel, this);
      this.cancelButton = cancel;
      var span = make ('span');
      span.className = 'hotcatinput';
      span.style.position = 'relative';
      // 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
      // moving the form to the front of the next line.
      span.appendChild (text);
      // 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 (!commitButton && !onUpload) {
        for (var i = 0; i < editors.length; i++) {
          if (editors[i].state != CategoryEditor.UNCHANGED) {
            setMultiInput();
            break;
          }
        }
      }
      if (!this.form) {
        this.makeForm ();
      }
      if (this.list) this.list.style.display = 'none';
      if (this.engineSelector) this.engineSelector.style.display = 'none';
      this.currentCategory = this.lastSavedCategory;
      this.currentExists  = this.lastSavedExists;
      this.currentHidden  = this.lastSavedHidden;
      this.currentKey      = this.lastSavedKey;
      this.icon.src = 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 ();
      return result;
    },
    show : function (evt, engine, readOnly) {
      var result = this.display (evt);
      var v = this.lastSavedCategory;
      if (v.length === 0) return result;
      this.text.readOnly = !!readOnly;
      this.engine = engine;
      this.textchange (false, true); // do autocompletion, force display of suggestions
      CategoryEditor.forceRedraw ();
      return result;
    },
   
   
    open : function (evt) {
// Backwards compatibility stuff. We want HotCat to work with either wg* globals, or with mw.config.get().
      return this.show (evt, (this.engine && suggestionConfigs[this.engine].temp) ? HotCat.suggestions : this.engine);
// Our "solution" is to publish the wg* globals if they're not already published.
    },
if (window.mediaWiki && window.mediaWiki.config) {
 
var globals = window.mediaWiki.config.get();
    down : function (evt) {
if (globals && globals !== window) {
      return this.show (evt, 'subcat', true);
for (var k in globals) window[k] = globals[k];
    },
window.mediaWiki.config = new window.mediaWiki.Map(true); // Make config point to window again.
 
}
    up : function (evt) {
globals = null;
      return this.show (evt, 'parentcat');
}
    },
// More backwards compatibility. We have a few places where we test for the browser: once for
 
// Safari < 3.0, twice for WebKit (Chrome or Safari, any versions), twice for IE <= 6, and
    cancel : function () {
// once for IE < 8.
      if (this.isAddCategory && !onUpload) {
var ua = navigator.userAgent.toLowerCase();
        this.removeEditor(); // We added a new adder when opening
var is_ie6 = /msie ([0-9]{1,}[\.0-9]{0,})/.exec(ua) !== null && parseFloat(RegExp.$1) <= 6.0;
        return;
var is_ie_lt8 = /msie ([0-9]{1,}[\.0-9]{0,})/.exec(ua) !== null && parseFloat(RegExp.$1) < 8.0;
      }
var is_webkit = /applewebkit\/\d+/.test(ua) && ua.indexOf ('spoofer') < 0;
      // Close, re-display link
// And even more compatbility. HotCat was developed without jQuery, and anyway current jQuery
      this.inactivate();
// (1.7.1) doesn't seem to support in jquery.getJSON() or jQuery.ajax() the automatic
      this.form.style.display = 'none';
// switching from GET to POST requests if the query arguments would make the uri too long.
      if (this.catLink) this.catLink.style.display = "";
// (IE has a hard limit of 2083 bytes, and the servers may have limits around 4 or 8kB.)
      this.linkSpan.style.display = "";
//    Anyway, HotCat is supposed to run on wikis without jQuery, so we'd have to supply some
      this.state = this.originalState;
// ajax routines ourselves in any case. We can't rely on the old sajax_init_object(), newer
      this.currentCategory = this.lastSavedCategory;
// MW versions (>= 1.19) might not have it.
      this.currentKey      = this.lastSavedKey;
var getJSON = (function () {
      this.currentExists  = this.lastSavedExists;
function getRequest () {
      this.currentHidden  = this.lastSavedHidden;
var request = null;
      if (this.catLink) {
try {
        if (this.currentkey && this.currentKey.length > 0) {
request = new window.XMLHttpRequest();
          this.catLink.title = this.currentKey;
} catch (anything) {
        } else {
if (window.ActiveXObject) {
          this.catLink.title = null;
try {
        }
request = new window.ActiveXObject('Microsoft.XMLHTTP');
      }
} catch (any) {
      if (this.state == CategoryEditor.UNCHANGED) {
}
        if (this.catLink) this.catLink.style.backgroundColor = 'transparent';
} // end if IE
      } else {
} // end try-catch
        if (!onUpload) {
return request;
          try {
}
            this.catLink.style.backgroundColor = HotCat.bg_changed;
          } catch (ex) {}
return function (settings) {
        }
var req = getRequest();
      }
if (!req && settings && settings.error) settings.error (req);
      checkMultiInput ();
if (!req || !settings || !settings.uri) return req;
      CategoryEditor.forceRedraw ();
var uri = armorUri (settings.uri);
    },
var args = settings.data || null;
 
var method;
    removeEditor : function () {
if (args && uri.length + args.length + 1 > 2000) {
      if (!newDOM) {
// We lose caching, but at least we can make the request
        var next = this.span.nextSibling;
method = 'POST';
        if (next) next.parentNode.removeChild (next);
req.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded');
      }
} else {
      this.span.parentNode.removeChild (this.span);
method = 'GET';
      for (var i = 0; i < editors.length; i++) {
if (args) uri += '?' + args;
        if (editors[i] == this) {
args = null;
          editors.splice (i, 1);
}
          break;
req.open (method, uri, true);
        }
req.onreadystatechange = function () {
      }
if (req.readyState != 4) return;
      checkMultiInput ();
if (req.status != 200 || !req.responseText || !(/^\s*[\{\[]/.test(req.responseText))) {
      var self = this;
if (settings.error) settings.error (req);
      window.setTimeout (function () {delete self;}, 10);
} else {
    },
if (settings.success) settings.success (eval ('(' + req.responseText + ')'));
 
}
    rollback : function (evt) {
};
      this.undoLink.parentNode.removeChild (this.undoLink);
req.setRequestHeader ('Pragma', 'cache=yes');
      this.undoLink = null;
req.setRequestHeader ('Cache-Control', 'no-transform');
      this.currentCategory = this.originalCategory;
req.send (args);
      this.currentKey = this.originalKey;
return req;
      this.currentExists = this.originalExists;
};
      this.currentHidden = this.originalHidden;
})();
      this.lastSavedCategory = this.originalCategory;
      this.lastSavedKey = this.originalKey;
function armorUri (uri) {
      this.lastSavedExists = this.originalExists;
// Avoid protocol-relative URIs, IE7 has a bug with them in Ajax calls
      this.lastSavedHidden = this.originalHidden;
if (uri.length >= 2 && uri.substring(0, 2) == '//') return document.location.protocol + uri;
      this.state = CategoryEditor.UNCHANGED;
return uri;
      if (!this.currentCategory || this.currentCategory.length === 0) {
}
        // It was a newly added category. Remove the whole editor.
        this.removeEditor();
function LoadTrigger (needed) {
      } else {
this.queue = [];
        // Redisplay the link...
this.toLoad = needed;
        this.catLink.removeChild (this.catLink.firstChild);
}
        this.catLink.appendChild (make (this.currentCategory, true));
LoadTrigger.prototype = {
        this.catLink.href = wikiPagePath (HotCat.category_canonical + ':' + this.currentCategory);
register : function (callback) {
        this.catLink.title = this.currentKey;
if (this.toLoad <= 0) {
        this.catLink.className = this.currentExists ? "" : 'new';
callback (); // Execute directly
        this.catLink.style.backgroundColor = 'transparent';
} else {
        if (this.upDownLinks) this.upDownLinks.style.display = this.currentExists ? "" : 'none';
this.queue[this.queue.length] = callback;
        checkMultiInput ();
}
      }
},
      return evtKill (evt);
    },
loaded : function () {
 
if (this.toLoad > 0) {
    inactivate : function () {
this.toLoad--;
      if (this.list) this.list.style.display = 'none';
if (this.toLoad === 0) {
      if (this.engineSelector) this.engineSelector.style.display = 'none';
// Run queued callbacks once
      this.is_active = false;
for (var i = 0; i < this.queue.length; i++) this.queue[i]();
    },
this.queue = [];
 
}
    acceptCheck : function (dontCheck) {
}
      this.sanitizeInput ();
}
      var value = this.text.value.split('|');
      var key  = null;
};
      if (value.length > 1) key = value[1];
      var v = value[0].replace(/_/g, ' ').replace(/^\s+|\s+$/g, "");
var setupCompleted = new LoadTrigger(1);
      if (HotCat.capitalizePageNames) v = capitalize (v);
// Used to run user-registered code once HotCat is fully set up and ready.
      this.lastInput = v;
HotCat.runWhenReady = function (callback) {setupCompleted.register(callback);};
      if (v.length === 0) {
        this.cancel ();
var loadTrigger = new LoadTrigger(2);
        return false;
// Used to delay running the HotCat setup until /local_defaults and localizations have been loaded.
      }
      if (!dontCheck
function load (uri) {
          && (  wgNamespaceNumber == 14 && v == wgTitle
var head = document.getElementsByTagName ('head')[0];
              || HotCat.blacklist != null && HotCat.blacklist.test(v))
var s = document.createElement ('script');
        ) {
s.setAttribute ('src', armorUri(uri));
        this.cancel ();
s.setAttribute ('type', 'text/javascript');
        return false;
var done = false;
      }
      this.currentCategory = v;
function afterLoad () {
      this.currentKey = key;
if (done) return;
      this.currentExists = this.inputExists;
done = true;
      return 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();
    accept : function (evt) {
}
      this.noCommit = (evtKeys (evt) & 1) != 0;
      var result = evtKill (evt);
s.onload = s.onreadystatechange = function () { // onreadystatechange for IE, onload for all others
      if (this.acceptCheck ()) {
if (done) return;
        var toResolve = [this];
if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
        var original  = this.currentCategory;
afterLoad ();
        resolveMulti (
}
            toResolve
};
          ,
s.onerror = afterLoad; // Clean up, but otherwise ignore errors
head.insertBefore (s, head.firstChild); // appendChild may trigger bugs in IE6 here
}
function loadJS (page) {
load (wgServer + wgScript + '?title=' + encodeURIComponent (page) + '&action=raw&ctype=text/javascript');
}
function loadURI (href) {
var url = href;
if (url.substring (0, 2) == '//') {
url = window.location.protocol + url;
} else if (url.substring (0, 1) == '/') {
url = wgServer + url;
}
load (url);
}
// Load local configurations, overriding the pre-set default values in the HotCat object above. This is always loaded
// from the wiki where this script is executing, even if this script itself is hotlinked from the Commons. This can
// be used to change the default settings, or to provide localized interface texts for edit summaries and so on.
loadJS ('MediaWiki:Gadget-HotCat.js/local_defaults');
// Load localized UI texts. These are the texts that HotCat displays on the page itself. Texts shown in edit summaries
// should be localized in /local_defaults above.
if (wgUserLanguage != 'en') {
// Lupo: somebody thought it would be a good idea to add this. So the default is true, and you have to set it to false
// explicitly if you're not on the Commons and don't want that.
if (typeof window.hotcat_translations_from_commons == 'undefined') {
window.hotcat_translations_from_commons = wgServer.indexOf('//commons') < 0;
}
// Localization hook to localize HotCat messages, tooltips, and engine names for wgUserLanguage.
if (window.hotcat_translations_from_commons && wgServer.indexOf('//commons') < 0) {
loadURI ('//commons.wikimedia.org/w/index.php?title='
+ 'MediaWiki:Gadget-HotCat.js/' + wgUserLanguage
+ '&action=raw&ctype=text/javascript&smaxage=21600&maxage=86400'
);
} else {
// Load translations locally
loadJS ('MediaWiki:Gadget-HotCat.js/' + wgUserLanguage);
}
} else {
loadTrigger.loaded();
}
// No further changes should be necessary here.
// The following regular expression strings are used when searching for categories in wikitext.
var wikiTextBlank  = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+';
var wikiTextBlankRE = new RegExp (wikiTextBlank, 'g');
// Regexp for handling blanks inside a category title or namespace name.
// See http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Title.php?revision=104051&view=markup#l2722
// 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
// number of special whitespace characters by simple blanks. And finally, blanks are treated as underscores.
// Therefore, when looking for page titles in wikitext, we must handle all these cases.
//  Note: we _do_ include the horizontal tab in the above list, even though the MediaWiki software for some reason
// appears to not handle it. The zero-width space \u200B is _not_ handled as a space inside titles by MW.
var wikiTextBlankOrBidi = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200B\\u200E\\u200F\\u2028-\\u202F\\u205F\\u3000]*';
// Whitespace regexp for handling whitespace between link components. Including the horizontal tab, but not \n\r\f\v:
// a link must be on one single line.
//  MediaWiki also removes Unicode bidi override characters in page titles (and namespace names) completely.
// 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,
// or adjacent to and inside of "[[" and "]]").
// First auto-localize the regexps for the category and the template namespaces.
if (typeof (wgFormattedNamespaces) != 'undefined') {
function autoLocalize (namespaceNumber, fallback) {
function create_regexp_str (name)
{
if (!name || name.length === 0) return "";
var regex_name = "";
for (var i = 0; i < name.length; i++){
var initial = name.substr (i, 1);
var ll = initial.toLowerCase ();
var ul = initial.toUpperCase ();
if (ll == ul
})();
})();
 
} // end if (guard)
} // end if (guard)
//</source>
//</source>