پرش به محتوا

مدیاویکی:Gadget-HotCat.js: تفاوت میان نسخه‌ها

بدون خلاصۀ ویرایش
بدون خلاصۀ ویرایش
بدون خلاصۀ ویرایش
خط ۱: خط ۱:
// <nowiki>
/**
/*
HotCat V2.41
HotCat V2.35


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 should run on any MediaWiki installation >= MW 1.27.
This code should run on any MediaWiki installation >= MW 1.27.


For use with older versions of MediaWiki, use the archived versions below:
For use with older versions of MediaWiki, use the archived versions below:


<=1.26: https://commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-HotCat.js&oldid=211134664
<=1.26: https://commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-HotCat.js&oldid=211134664
*/
*/
/* eslint-disable vars-on-top, one-var, camelcase, no-use-before-define, no-alert */
// <nowiki>
/* global HotCat, mediaWiki, UFUI, JSconfig, UploadForm */
/* eslint-disable vars-on-top, one-var, camelcase, no-alert, curly */
/* global jQuery, mediaWiki, UFUI, JSconfig, UploadForm */
/* jslint strict:false, nonew:false, bitwise:true */
( function ( $, mw ) {
( function ( $, mw ) {
// Don't use mw.config.get() as that takes a copy of the config, and so doesn't
// Don't use mw.config.get() as that takes a copy of the config, and so doesn't
خط ۳۰: خط ۲۹:
var conf = mw.config.values;
var conf = mw.config.values;


if (
// Guard against double inclusions (in old IE/Opera element ids become window properties)
// Guard against double inclusions (in old IE/Opera element ids become window properties)
if ( ( window.HotCat && !window.HotCat.nodeName ) ||
( window.HotCat && !window.HotCat.nodeName ) ||
conf.wgAction === 'edit' ) // Not on edit mode
// Not on edit pages
conf.wgAction === 'edit'
 
) {
return;
return;
}


// Configuration stuff.
// Configuration stuff.
window.HotCat = {
var HC = window.HotCat = {
// Localize these messages to the main language of your wiki.
// Localize these messages to the main language of your wiki.
messages: {
messages: {
cat_removed: 'removed [[Category:$1]]',
cat_removed: 'removed [[Category:$1]]',
خط ۷۵: خط ۶۹:
// see localization hook below.
// see localization hook below.
multi_error: 'Could not retrieve the page text from the server. Therefore, your category changes ' +
multi_error: 'Could not retrieve the page text from the server. Therefore, your category changes ' +
'cannot be saved. We apologize for the inconvenience.',
'cannot be saved. We apologize for the inconvenience.',
// Defaults to '[[' + category_canonical + ':$1]]'. Can be overridden if in the short edit summaries
// 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
// not the standard category name should be used but, say, a shorter namespace alias. $1 is replaced
خط ۹۴: خط ۸۸:
// The little modification links displayed after category names. U+2212 is a minus sign; U+2193 and U+2191 are
// 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!
// downward and upward pointing arrows. Do not use ↓ and ↑ in the code!
links: { change: '(±)', remove: '(\u2212)', add: '(+)', restore: '(×)', undo: '(×)', down: '(\u2193)', up: '(\u2191)' },
links: {
change: '(±)',
remove: '(\u2212)',
add: '(+)',
restore: '(×)',
undo: '(×)',
down: '(\u2193)',
up: '(\u2191)'
},
changeTag: conf.wgUserName ? 'HotCat' : '', // if tag is missing, edit is rejected
// The tooltips for the above links
// The tooltips for the above links
tooltips: {
tooltips: {
خط ۱۱۵: خط ۱۱۸:
return (
return (
ns < 0 || // Special pages; Special:Upload is handled differently
ns < 0 || // Special pages; Special:Upload is handled differently
ns === 10 || // Templates
ns === 10 || // Templates
ns === 828 || // Module (Lua)
ns === 828 || // Module (Lua)
ns === 8 || // MediaWiki
ns === 8 || // MediaWiki
ns === 6 && conf.wgArticleId === 0 || // Non-existing file pages
ns === 6 && !conf.wgArticleId || // Non-existing file pages
ns === 2 && /\.(js|css)$/.test( conf.wgTitle ) || // User scripts
ns === 2 && /\.(js|css)$/.test( conf.wgTitle ) || // User scripts
nsIds &&
nsIds &&
( ns === nsIds.creator ||
( ns === nsIds.creator ||
ns === nsIds.timedtext ||
ns === nsIds.timedtext ||
ns === nsIds.institution
ns === nsIds.institution ) );
)
);
},
},
// A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
// A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
// If not, set it to null.
// If not, set it to null.
uncat_regexp: /\{\{\s*([Uu]ncat(egori[sz]ed( image)?)?|[Nn]ocat|[Nn]eedscategory)[^}]*\}\}\s*(<!--.*?-->)?/g,
uncat_regexp: /\{\{\s*[Uu]ncategorized\s*[^}]*\}\}\s*(<!--.*?-->\s*)?/g,
// The images used for the little indication icon. Should not need changing.
// The images used for the little indication icon. Should not need changing.
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',
خط ۱۴۶: خط ۱۴۷:
parentcat: 'Parent categories'
parentcat: 'Parent categories'
},
},
// 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 // any items, but that contains links to other categories where stuff should be categorized. If you don't have
// 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)
خط ۱۶۵: خط ۱۶۶:
// Stuff changeable by users:
// Stuff changeable by users:
// 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.
bg_changed: '#F8CCB0',
bg_changed: '#FCA',
// 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.
خط ۱۸۵: خط ۱۸۶:
use_up_down: true,
use_up_down: true,
// Default list size
// Default list size
list_size: 5,
listSize: 5,
// 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.
single_minor: true,
single_minor: true,
خط ۱۹۴: خط ۱۹۵:
shortcuts: null,
shortcuts: null,
addShortcuts: function ( map ) {
addShortcuts: function ( map ) {
if ( !map ) { return; }
if ( !map ) return;
window.HotCat.shortcuts = window.HotCat.shortcuts || {};
window.HotCat.shortcuts = window.HotCat.shortcuts || {};
for ( var k in map ) {
for ( var k in map ) {
if ( !map.hasOwnProperty( k ) || typeof k !== 'string' ) { continue; }
if ( !map.hasOwnProperty( k ) || typeof k !== 'string' ) continue;
 
var v = map[ k ];
var v = map[ k ];
if ( typeof v !== 'string' ) { continue; }
if ( typeof v !== 'string' ) continue;
 
k = k.replace( /^\s+|\s+$/g, '' );
k = k.replace( /^\s+|\s+$/g, '' );
v = v.replace( /^\s+|\s+$/g, '' );
v = v.replace( /^\s+|\s+$/g, '' );
if ( k.length === 0 || v.length === 0 ) { continue; }
if ( !k.length || !v.length ) continue;
 
window.HotCat.shortcuts[ k ] = v;
window.HotCat.shortcuts[ k ] = v;
}
}
خط ۲۱۲: خط ۲۱۶:
var ua = navigator.userAgent.toLowerCase();
var ua = navigator.userAgent.toLowerCase();
var is_webkit = /applewebkit\/\d+/.test( ua ) && ua.indexOf( 'spoofer' ) < 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
var cat_prefix = null;
// (1.7.1) doesn't seem to support in jquery.getJSON() or jQuery.ajax() the automatic
var noSuggestions = false;
// 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;
}


return function ( settings ) {
function LoadTrigger( needed ) {
var req = getRequest();
// Define methods in a closure so that self reference is available,
if ( !req && settings && settings.error ) { settings.error( req ); }
// also allows method calls to be detached.
if ( !req || !settings || !settings.uri ) { return req; }
var self = this;
// eslint-disable-next-line no-use-before-define
self.queue = [];
var uri = armorUri( settings.uri );
self.needed = needed;
var args = settings.data || null;
self.register = function ( callback ) {
var method;
if ( self.needed <= 0 ) callback(); // Execute directly
if ( args && uri.length + args.length + 1 > 2000 ) {
else self.queue.push( callback );
// We lose caching, but at least we can make the request
};
method = 'POST';
self.loaded = function () {
req.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
self.needed--;
} else {
if ( self.needed === 0 ) {
method = 'GET';
// Run queued callbacks once
if ( args ) { uri += '?' + args; }
for ( var i = 0; i < self.queue.length; i++ ) self.queue[ i ]();
args = null;
self.queue = [];
}
}
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 {
// eslint-disable-next-line no-eval
if ( settings.success ) { settings.success( eval( '(' + req.responseText + ')' ) ); }
}
};
req.setRequestHeader( 'Pragma', 'cache=yes' );
req.setRequestHeader( 'Cache-Control', 'no-transform' );
req.send( args );
return req;
};
};
}() );
}


function armorUri( uri ) {
// Used to delay running the HotCat setup until /local_defaults and localizations have been loaded.
// Avoid protocol-relative URIs, IE7 has a bug with them in Ajax calls
var loadTrigger = new LoadTrigger( 2 );
if ( uri.length >= 2 && uri.substring( 0, 2 ) === '//' ) { return document.location.protocol + uri; }
return uri;
}


function LoadTrigger( needed ) {
function load( uri, callback ) {
this.queue = [];
var s = document.createElement( 'script' );
this.toLoad = needed;
s.src = uri;
}
var called = false;
LoadTrigger.prototype = {
register: function ( callback ) {
if ( this.toLoad <= 0 ) {
callback(); // Execute directly
} else {
this.queue[ this.queue.length ] = callback;
}
},


loaded: function () {
s.onload = s.onerror = function () {
if ( this.toLoad > 0 ) {
if ( !called && callback ) {
this.toLoad--;
called = true;
if ( this.toLoad === 0 ) {
callback();
// Run queued callbacks once
for ( var i = 0; i < this.queue.length; i++ ) { this.queue[ i ](); }
this.queue = [];
}
}
}
}
if ( s.parentNode ) {
 
s.parentNode.removeChild( s );
};
 
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
document.head.appendChild( s );
head.insertBefore( s, head.firstChild ); // appendChild may trigger bugs in IE6 here
}
}


function loadJS( page ) {
function loadJS( page, callback ) {
load( conf.wgServer + conf.wgScript + '?title=' + encodeURIComponent( page ) + '&action=raw&ctype=text/javascript' );
load( conf.wgServer + conf.wgScript + '?title=' + encodeURIComponent( page ) + '&action=raw&ctype=text/javascript', callback );
}
}


function loadURI( href ) {
function loadURI( href, callback ) {
var url = href;
var url = href;
if ( url.substring( 0, 2 ) === '//' ) {
if ( url.substring( 0, 2 ) === '//' ) url = window.location.protocol + url; else if ( url.substring( 0, 1 ) === '/' ) url = conf.wgServer + url;
url = window.location.protocol + url;
 
} else if ( url.substring( 0, 1 ) === '/' ) {
load( url, callback );
url = conf.wgServer + url;
}
load( url );
}
}


// Load local configurations, overriding the pre-set default values in the HotCat object above. This is always loaded
// 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
// from the wiki where this script is executing, even if this script itself is hotlinked from Commons. This can
// be used to change the default settings, or to provide localized interface texts for edit summaries and so on.
// 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' );
loadJS( 'MediaWiki:Gadget-HotCat.js/local_defaults', loadTrigger.loaded );


// Load localized UI texts. These are the texts that HotCat displays on the page itself. Texts shown in edit summaries
// Load localized UI texts. These are the texts that HotCat displays on the page itself. Texts shown in edit summaries
خط ۳۵۶: خط ۲۷۹:
if ( conf.wgUserLanguage !== 'en' ) {
if ( conf.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
// 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.
// explicitly if you're not on Commons and don't want that.
if ( typeof window.hotcat_translations_from_commons === 'undefined' ) {
if ( window.hotcat_translations_from_commons === undefined ) window.hotcat_translations_from_commons = true;
window.hotcat_translations_from_commons = true;
 
}
// Localization hook to localize HotCat messages, tooltips, and engine names for wgUserLanguage.
// Localization hook to localize HotCat messages, tooltips, and engine names for wgUserLanguage.
if ( window.hotcat_translations_from_commons && conf.wgServer.indexOf( '//commons' ) < 0 ) {
if ( window.hotcat_translations_from_commons && conf.wgServer.indexOf( '//commons' ) < 0 ) {
loadURI( '//commons.wikimedia.org/w/index.php?title=' +
loadURI( '//commons.wikimedia.org/w/index.php?title=' +
'MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage +
'MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage +
'&action=raw&ctype=text/javascript'
'&action=raw&ctype=text/javascript', loadTrigger.loaded );
);
} else {
} else {
// Load translations locally
// Load translations locally
loadJS( 'MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage );
loadJS( 'MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage, loadTrigger.loaded );
}
}
} else {
} else {
خط ۴۰۰: خط ۳۲۱:
var namespaceIds = conf.wgNamespaceIds;
var namespaceIds = conf.wgNamespaceIds;
function autoLocalize( namespaceNumber, fallback ) {
function autoLocalize( namespaceNumber, fallback ) {
function create_regexp_str( name ) {
function createRegexpStr( name ) {
if ( !name || name.length === 0 ) { return ''; }
if ( !name || !name.length ) return '';
 
var regex_name = '';
var regex_name = '';
for ( var i = 0; i < name.length; i++ ) {
for ( var i = 0; i < name.length; i++ ) {
var initial = name.substr( i, 1 );
var initial = name.charAt( i ),
var ll = initial.toLowerCase();
ll = initial.toLowerCase(),
var ul = initial.toUpperCase();
ul = initial.toUpperCase();
if ( ll === ul ) {
if ( ll === ul ) regex_name += initial; else regex_name += '[' + ll + ul + ']';
regex_name += initial;
} else {
regex_name += '[' + ll + ul + ']';
}
}
}
return regex_name
return regex_name
خط ۴۲۰: خط ۳۳۸:
fallback = fallback.toLowerCase();
fallback = fallback.toLowerCase();
var canonical = formattedNamespaces[ String( namespaceNumber ) ].toLowerCase();
var canonical = formattedNamespaces[ String( namespaceNumber ) ].toLowerCase();
var regexp = create_regexp_str( canonical );
var regexp = createRegexpStr( canonical );
if ( fallback && canonical !== fallback ) { regexp += '|' + create_regexp_str( fallback ); }
if ( fallback && canonical !== fallback ) regexp += '|' + createRegexpStr( fallback );
 
if ( namespaceIds ) {
if ( namespaceIds ) {
for ( var cat_name in namespaceIds ) {
for ( var cat_name in namespaceIds ) {
خط ۴۳۰: خط ۳۴۹:
namespaceIds[ cat_name ] === namespaceNumber
namespaceIds[ cat_name ] === namespaceNumber
) {
) {
regexp += '|' + create_regexp_str( cat_name );
regexp += '|' + createRegexpStr( cat_name );
}
}
}
}
خط ۴۳۷: خط ۳۵۶:
}
}


HotCat.category_canonical = formattedNamespaces[ '14' ];
HC.category_canonical = formattedNamespaces[ '14' ];
HotCat.category_regexp = autoLocalize( 14, 'category' );
HC.category_regexp = autoLocalize( 14, 'category' );
if ( formattedNamespaces[ '10' ] ) {
if ( formattedNamespaces[ '10' ] ) HC.template_regexp = autoLocalize( 10, 'template' );
HotCat.template_regexp = autoLocalize( 10, 'template' );
}


// Utility functions. Yes, this duplicates some functionality that also exists in other places, but
// 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
// to keep this whole stuff in a single file not depending on any other on-wiki JavaScripts, we re-do
// these few operations here.
// these few operations here.
function make( arg, literal ) {
function make( arg, literal ) {
if ( !arg ) { return null; }
if ( !arg ) return null;
 
return literal ? document.createTextNode( arg ) : document.createElement( arg );
return literal ? document.createTextNode( arg ) : document.createElement( arg );
}
}
function param( name, uri ) {
function param( name, uri ) {
if ( typeof uri === 'undefined' || uri === null ) { uri = document.location.href; }
uri = uri || document.location.href;
var re = new RegExp( '[&?]' + name + '=([^&#]*)' );
var re = new RegExp( '[&?]' + name + '=([^&#]*)' );
var m = re.exec( uri );
var m = re.exec( uri );
if ( m && m.length > 1 ) { return decodeURIComponent( m[ 1 ] ); }
if ( m && m.length > 1 ) return decodeURIComponent( m[ 1 ] );
return null;
return null;
}
}
function title( href ) {
function title( href ) {
if ( !href ) { return null; }
if ( !href ) return null;
 
var script = conf.wgScript + '?';
var script = conf.wgScript + '?';
if ( href.indexOf( script ) === 0 || href.indexOf( conf.wgServer + script ) === 0 || conf.wgServer.substring( 0, 2 ) === '//' && href.indexOf( document.location.protocol + conf.wgServer + script ) === 0 ) {
if ( href.indexOf( script ) === 0 || href.indexOf( conf.wgServer + script ) === 0 || conf.wgServer.substring( 0, 2 ) === '//' && href.indexOf( document.location.protocol + conf.wgServer + script ) === 0 ) {
خط ۴۶۶: خط ۳۸۵:
// href="/wiki/..."
// href="/wiki/..."
var prefix = conf.wgArticlePath.replace( '$1', '' );
var prefix = conf.wgArticlePath.replace( '$1', '' );
if ( href.indexOf( prefix ) !== 0 ) {
if ( href.indexOf( prefix ) ) prefix = conf.wgServer + prefix; // Fully expanded URL?
prefix = conf.wgServer + prefix; // Fully expanded URL?
 
}
if ( href.indexOf( prefix ) && prefix.substring( 0, 2 ) === '//' ) prefix = document.location.protocol + prefix; // Protocol-relative wgServer?
if ( href.indexOf( prefix ) !== 0 && prefix.substring( 0, 2 ) === '//' ) {
 
prefix = document.location.protocol + prefix; // Protocol-relative wgServer?
if ( href.indexOf( prefix ) === 0 ) return decodeURIComponent( href.substring( prefix.length ) );
}
if ( href.indexOf( prefix ) === 0 ) {
return decodeURIComponent( href.substring( prefix.length ) );
}
}
}
return null;
return null;
خط ۴۸۲: خط ۳۹۷:
}
}
function capitalize( str ) {
function capitalize( str ) {
if ( !str || str.length === 0 ) { return str; }
if ( !str || !str.length ) return str;
 
return str.substr( 0, 1 ).toUpperCase() + str.substr( 1 );
return str.substr( 0, 1 ).toUpperCase() + str.substr( 1 );
}
}
خط ۵۱۵: خط ۴۳۱:
// Replace $1, $2, or ${key1}, ${key2}, or $key1, $key2 by values from map. $$ is replaced by a single $.
// Replace $1, $2, or ${key1}, ${key2}, or $key1, $key2 by values from map. $$ is replaced by a single $.
return function ( str, map ) {
return function ( str, map ) {
if ( !map ) { return str; }
if ( !map ) return str;
 
return str.replace( re, function ( match, prefix, idx, key, alpha ) {
return str.replace( re, function ( match, prefix, idx, key, alpha ) {
if ( prefix === lead ) {
if ( prefix === lead ) return lead;
return lead;
 
}
var k = alpha || key || idx;
var k = alpha || key || idx;
var replacement = typeof map[ k ] === 'function' ? map[ k ]( match, k ) : map[ k ];
var replacement = typeof map[ k ] === 'function' ? map[ k ]( match, k ) : map[ k ];
خط ۵۲۹: خط ۴۴۵:
var substitute = substituteFactory();
var substitute = substituteFactory();
var replaceShortcuts = ( function () {
var replaceShortcuts = ( function () {
var replaceHash = substituteFactory( { indicator: '#', lbrace: '[', rbrace: ']' } );
var replaceHash = substituteFactory( {
indicator: '#',
lbrace: '[',
rbrace: ']'
} );
return function ( str, map ) {
return function ( str, map ) {
var s = replaceHash( str, map );
var s = replaceHash( str, map );
return HotCat.capitalizePageNames ? capitalize( s ) : s;
return HC.capitalizePageNames ? capitalize( s ) : s;
};
};
}() );
}() );
خط ۵۳۹: خط ۴۵۹:


var findCatsRE =
var findCatsRE =
new RegExp( '\\[\\[' + wikiTextBlankOrBidi + '(?:' + HotCat.category_regexp + ')' + wikiTextBlankOrBidi + ':[^\\]]+\\]\\]', 'g' );
new RegExp( '\\[\\[' + wikiTextBlankOrBidi + '(?:' + HC.category_regexp + ')' + wikiTextBlankOrBidi + ':[^\\]]+\\]\\]', 'g' );


function replaceByBlanks( match ) {
function replaceByBlanks( match ) {
خط ۵۴۷: خط ۴۶۷:
function find_category( wikitext, category, once ) {
function find_category( wikitext, category, once ) {
var cat_regex = null;
var cat_regex = null;
if ( HotCat.template_categories[ category ] ) {
if ( HC.template_categories[ category ] ) {
cat_regex = new RegExp(
cat_regex = new RegExp(
'\\{\\{' + wikiTextBlankOrBidi + '(' + HotCat.template_regexp + '(?=' + wikiTextBlankOrBidi + ':))?' + wikiTextBlankOrBidi +
'\\{\\{' + wikiTextBlankOrBidi + '(' + HC.template_regexp + '(?=' + wikiTextBlankOrBidi + ':))?' + wikiTextBlankOrBidi +
'(?:' + HotCat.template_categories[ category ] + ')' +
'(?:' + HC.template_categories[ category ] + ')' +
wikiTextBlankOrBidi + '(\\|.*?)?\\}\\}',
wikiTextBlankOrBidi + '(\\|.*?)?\\}\\}',
'g'
'g'
خط ۵۵۸: خط ۴۷۸:
var initial = cat_name.substr( 0, 1 );
var initial = cat_name.substr( 0, 1 );
cat_regex = new RegExp(
cat_regex = new RegExp(
'\\[\\[' + wikiTextBlankOrBidi + '(' + HotCat.category_regexp + ')' + wikiTextBlankOrBidi + ':' + wikiTextBlankOrBidi +
'\\[\\[' + wikiTextBlankOrBidi + '(' + HC.category_regexp + ')' + wikiTextBlankOrBidi + ':' + wikiTextBlankOrBidi +
( initial === '\\' || !HotCat.capitalizePageNames ?
( initial === '\\' || !HC.capitalizePageNames ?
initial :
initial :
'[' + initial.toUpperCase() + initial.toLowerCase() + ']'
'[' + initial.toUpperCase() + initial.toLowerCase() + ']' ) +
) +
cat_name.substring( 1 ).replace( wikiTextBlankRE, wikiTextBlank ) +
cat_name.substring( 1 ).replace( wikiTextBlankRE, wikiTextBlank ) +
wikiTextBlankOrBidi + '(\\|.*?)?\\]\\]',
wikiTextBlankOrBidi + '(\\|.*?)?\\]\\]',
خط ۵۶۸: خط ۴۸۷:
);
);
}
}
if ( once ) { return cat_regex.exec( wikitext ); }
if ( once ) return cat_regex.exec( wikitext );
 
var copiedtext = wikitext
var copiedtext = wikitext
.replace( /<!--(\s|\S)*?-->/g, replaceByBlanks )
.replace( /<!--(\s|\S)*?-->/g, replaceByBlanks )
خط ۵۷۵: خط ۴۹۵:
var curr_match = null;
var curr_match = null;
while ( ( curr_match = cat_regex.exec( copiedtext ) ) !== null ) {
while ( ( curr_match = cat_regex.exec( copiedtext ) ) !== null ) {
result.push( { match: curr_match } );
result.push( {
match: curr_match
} );
}
}
result.re = cat_regex;
result.re = cat_regex;
خط ۵۹۲: خط ۵۱۴:
var index = -1;
var index = -1;
findCatsRE.lastIndex = 0;
findCatsRE.lastIndex = 0;
while ( findCatsRE.exec( copiedtext ) !== null ) { index = findCatsRE.lastIndex; }
while ( findCatsRE.exec( copiedtext ) !== null ) index = findCatsRE.lastIndex;
 
if ( index < 0 ) {
if ( index < 0 ) {
// Find the index of the first interlanguage link...
// Find the index of the first interlanguage link...
var match = null;
var match = null;
if ( !interlanguageRE ) {
if ( !interlanguageRE ) {
// Approximation without API: interlanguage links start with 2 to 3 lower case letters, optionally followed by
// 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"
// a sequence of groups consisting of a dash followed by one or more lower case letters. Exceptions are "simple"
// and "tokipona".
// and "tokipona".
match = /((^|\n\r?)(\[\[\s*(([a-z]{2,3}(-[a-z]+)*)|simple|tokipona)\s*:[^\]]+\]\]\s*))+$/.exec( copiedtext );
match = /((^|\n\r?)(\[\[\s*(([a-z]{2,3}(-[a-z]+)*)|simple|tokipona)\s*:[^\]]+\]\]\s*))+$/.exec( copiedtext );
} else {
} else {
match = interlanguageRE.exec( copiedtext );
match = interlanguageRE.exec( copiedtext );
}
}
if ( match ) { index = match.index; }
if ( match ) index = match.index;
return { idx: index, onCat: false };
 
return {
idx: index,
onCat: false
};
}
}
return { idx: index, onCat: index >= 0 };
return {
idx: index,
onCat: index >= 0
};
}
}


var summary = [];
var summary = [],
var nameSpace = HotCat.category_canonical;
nameSpace = HC.category_canonical,
var cat_point = -1; // Position of removed category;
cat_point = -1, // Position of removed category;
 
keyChange = ( toRemove && toAdd && toRemove === toAdd && toAdd.length ),
if ( key ) { key = '|' + key; }
matches;
var keyChange = ( toRemove && toAdd && toRemove === toAdd && toAdd.length > 0 );
if ( key ) key = '|' + key;
var matches;
// Remove
if ( toRemove && toRemove.length > 0 ) {
if ( toRemove && toRemove.length ) {
matches = find_category( wikitext, toRemove );
matches = find_category( wikitext, toRemove );
if ( !matches || matches.length === 0 ) {
if ( !matches || !matches.length ) {
return { text: wikitext, summary: summary, error: HotCat.messages.cat_notFound.replace( /\$1/g, toRemove ) };
return {
text: wikitext,
summary: summary,
error: HC.messages.cat_notFound.replace( /\$1/g, toRemove )
};
} else {
} else {
var before = wikitext.substring( 0, matches[ 0 ].match.index );
var before = wikitext.substring( 0, matches[ 0 ].match.index ),
var after = wikitext.substring( matches[ 0 ].match.index + matches[ 0 ].match[ 0 ].length );
after = wikitext.substring( matches[ 0 ].match.index + matches[ 0 ].match[ 0 ].length );
if ( matches.length > 1 ) {
if ( matches.length > 1 ) {
// Remove all occurrences in after
// Remove all occurrences in after
matches.re.lastIndex = 0;
matches.re.lastIndex = 0;
after = after.replace( matches.re, '' );
after = after.replace( matches.re, '' );
}
}
if ( toAdd ) {
if ( toAdd ) {
nameSpace = matches[ 0 ].match[ 1 ] || nameSpace;
// nameSpace = matches[ 0 ].match[ 1 ] || nameSpace; Canonical namespace should be always preferred
if ( key === null ) { key = matches[ 0 ].match[ 2 ]; } // Remember the category key, if any.
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.
// Remove whitespace (properly): strip whitespace, but only up to the next line feed.
خط ۶۳۷: خط ۵۷۲:
// whitespace characters, insert a blank.
// whitespace characters, insert a blank.
var i = before.length - 1;
var i = before.length - 1;
while ( i >= 0 && before.charAt( i ) !== '\n' && before.substr( i, 1 ).search( /\s/ ) >= 0 ) { i--; }
while ( i >= 0 && before.charAt( i ) !== '\n' && before.substr( i, 1 ).search( /\s/ ) >= 0 ) i--;
 
var j = 0;
var j = 0;
while ( j < after.length && after.charAt( j ) !== '\n' && after.substr( j, 1 ).search( /\s/ ) >= 0 ) { j++; }
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 ) {
if ( i >= 0 && before.charAt( i ) === '\n' && ( !after.length || j < after.length && after.charAt( j ) === '\n' ) ) i--;
before = before.substring( 0, i + 1 );
 
} else {
if ( i >= 0 ) before = before.substring( 0, i + 1 ); else before = '';
before = '';
 
}
if ( j < after.length ) after = after.substring( j ); else after = '';
if ( j < after.length ) {
 
after = after.substring( j );
} else {
after = '';
}
if (
if (
before.length > 0 && before.substring( before.length - 1 ).search( /\S/ ) >= 0 &&
before.length && before.substring( before.length - 1 ).search( /\S/ ) >= 0 &&
after.length > 0 && after.substr( 0, 1 ).search( /\S/ ) >= 0 ) {
after.length && after.substr( 0, 1 ).search( /\S/ ) >= 0
) {
before += ' ';
before += ' ';
}
}
cat_point = before.length;
cat_point = before.length;
if ( cat_point === 0 && after.length > 0 && after.substr( 0, 1 ) === '\n' ) {
if ( cat_point === 0 && after.length && after.substr( 0, 1 ) === '\n' ) after = after.substr( 1 );
after = after.substr( 1 );
 
}
wikitext = before + after;
wikitext = before + after;
if ( !keyChange ) {
if ( !keyChange ) {
if ( HotCat.template_categories[ toRemove ] ) {
if ( HC.template_categories[ toRemove ] ) { summary.push( HC.messages.template_removed.replace( /\$1/g, toRemove ) ); } else { summary.push( HC.messages.cat_removed.replace( /\$1/g, 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 ) {
// Add
if ( toAdd && toAdd.length ) {
matches = find_category( wikitext, toAdd );
matches = find_category( wikitext, toAdd );
if ( matches && matches.length > 0 ) {
if ( matches && matches.length ) {
return { text: wikitext, summary: summary, error: HotCat.messages.cat_exists.replace( /\$1/g, toAdd ) };
// Already exists
return {
text: wikitext,
summary: summary,
error: HC.messages.cat_exists.replace( /\$1/g, toAdd )
};
} else {
} else {
var onCat = false;
var onCat = false;
خط ۶۸۷: خط ۶۲۳:
var suffix = wikitext.substring( cat_point );
var suffix = wikitext.substring( cat_point );
wikitext = wikitext.substring( 0, cat_point ) + ( cat_point > 0 ? '\n' : '' ) + newcatstring + ( !onCat ? '\n' : '' );
wikitext = wikitext.substring( 0, cat_point ) + ( cat_point > 0 ? '\n' : '' ) + newcatstring + ( !onCat ? '\n' : '' );
if ( suffix.length > 0 && suffix.substr( 0, 1 ) !== '\n' ) {
if ( suffix.length && suffix.substr( 0, 1 ) !== '\n' ) wikitext += '\n' + suffix; else wikitext += suffix;
wikitext += '\n' + suffix;
} else {
wikitext += suffix;
}
} else {
} else {
if ( wikitext.length > 0 && wikitext.substr( wikitext.length - 1, 1 ) !== '\n' ) {
if ( wikitext.length && wikitext.substr( wikitext.length - 1, 1 ) !== '\n' ) wikitext += '\n';
wikitext += '\n';
 
}
wikitext += ( wikitext.length ? '\n' : '' ) + newcatstring;
wikitext += ( wikitext.length > 0 ? '\n' : '' ) + newcatstring;
}
}
if ( keyChange ) {
if ( keyChange ) {
var k = key || '';
var k = key || '';
if ( k.length > 0 ) { k = k.substr( 1 ); }
if ( k.length ) k = k.substr( 1 );
summary.push( substitute( HotCat.messages.cat_keychange, [ null, toAdd, k ] ) );
 
summary.push( substitute( HC.messages.cat_keychange, [ null, toAdd, k ] ) );
} else {
} else {
summary.push( HotCat.messages.cat_added.replace( /\$1/g, toAdd ) );
summary.push( HC.messages.cat_added.replace( /\$1/g, toAdd ) );
}
}
if ( HotCat.uncat_regexp && !is_hidden ) {
if ( HC.uncat_regexp && !is_hidden ) {
var txt = wikitext.replace( HotCat.uncat_regexp, '' ); // Remove "uncat" templates
var txt = wikitext.replace( HC.uncat_regexp, '' ); // Remove "uncat" templates
if ( txt.length !== wikitext.length ) {
if ( txt.length !== wikitext.length ) {
wikitext = txt;
wikitext = txt;
summary.push( HotCat.messages.uncat_removed );
summary.push( HC.messages.uncat_removed );
}
}
}
}
}
}
}
}
return { text: wikitext, summary: summary, error: null };
return {
text: wikitext,
summary: summary,
error: null
};
}
}


خط ۷۲۱: خط ۶۵۷:
function evtKeys( e ) {
function evtKeys( e ) {
/* eslint-disable no-bitwise */
/* eslint-disable no-bitwise */
e = e || window.event || window.Event; // W3C, IE, Netscape
var code = 0;
var code = 0;
if ( typeof e.ctrlKey !== 'undefined' ) { // All modern browsers
if ( e.ctrlKey ) { // All modern browsers
// Ctrl-click seems to be overloaded in FF/Mac (it opens a pop-up menu), so treat cmd-click
// Ctrl-click seems to be overloaded in FF/Mac (it opens a pop-up menu), so treat cmd-click
// as a ctrl-click, too.
// as a ctrl-click, too.
if ( e.ctrlKey || e.metaKey ) { code |= 1; }
if ( e.ctrlKey || e.metaKey ) code |= 1;
if ( e.shiftKey ) { code |= 2; }
 
} else if ( typeof e.modifiers !== 'undefined' ) { // Netscape...
if ( e.shiftKey ) code |= 2;
if ( e.modifiers & ( Event.CONTROL_MASK | Event.META_MASK ) ) { code |= 1; }
if ( e.modifiers & Event.SHIFT_MASK ) { code |= 2; }
}
}
/* eslint-enable no-bitwise */
return code;
return code;
}
}
function evtKill( e ) {
function evtKill( e ) {
e = e || window.event || window.Event; // W3C, IE, Netscape
if ( e.preventDefault ) {
if ( typeof e.preventDefault !== 'undefined' ) {
e.preventDefault();
e.preventDefault();
e.stopPropagation();
e.stopPropagation();
خط ۷۴۶: خط ۶۷۷:
}
}


var catLine = null;
var catLine = null,
var onUpload = false;
onUpload = false,
var editors = [];
editors = [],
 
var commitButton = null;
var commitForm = null;
var multiSpan = null;


var pageText = null;
commitButton = null,
var pageTime = null;
commitForm = null,
var pageWatched = false;
multiSpan = null,
var watchCreate = false;
var watchEdit = false;
var minorEdits = false;
var editToken = null;


var is_rtl = false;
pageText = null,
var serverTime = null;
pageTime = null,
var lastRevId = null;
pageWatched = false,
var pageTextRevId = null;
watchCreate = false,
var conflictingUser = null;
watchEdit = false,
minorEdits = false,
editToken = null,


var newDOM = false; // true if MediaWiki serves the new UL-LI DOM for categories
is_rtl = false,
serverTime = null,
lastRevId = null,
pageTextRevId = null,
conflictingUser = null,


function setMultiInput() {
newDOM = false; // true if MediaWiki serves the new UL-LI DOM for categories
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() {
function CategoryEditor() {
if ( !commitButton ) { return; }
this.initialize.apply( this, arguments );
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() {
function setPage( json ) {
var now = new Date();
var startTime = null;
var ts = String( now.getUTCFullYear() );
if ( json && json.query ) {
function two( s ) { return s.substr( s.length - 2 ); }
if ( json.query.pages ) {
ts = ts +
var page = json.query.pages[ !conf.wgArticleId ? '-1' : String( conf.wgArticleId ) ];
two( '0' + ( now.getUTCMonth() + 1 ) ) +
if ( page ) {
two( '0' + now.getUTCDate() ) +
if ( page.revisions && page.revisions.length ) {
two( '00' + now.getUTCHours() ) +
// Revisions are sorted by revision ID, hence [ 0 ] is the one we asked for, and possibly there's a [ 1 ] if we're
two( '00' + now.getUTCMinutes() ) +
// not on the latest revision (edit conflicts and such).
two( '00' + now.getUTCSeconds() );
pageText = page.revisions[ 0 ][ '*' ];
return ts;
if ( page.revisions[ 0 ].timestamp ) pageTime = page.revisions[ 0 ].timestamp.replace( /\D/g, '' );
if ( page.revisions[ 0 ].revid ) pageTextRevId = page.revisions[ 0 ].revid;
if ( page.revisions.length > 1 ) conflictingUser = page.revisions[ 1 ].user;
}
if ( page.lastrevid ) lastRevId = page.lastrevid;
if ( page.starttimestamp ) startTime = page.starttimestamp.replace( /\D/g, '' );
pageWatched = typeof page.watched === 'string';
editToken = page.edittoken;
if ( page.langlinks && ( !json[ 'query-continue' ] || !json[ 'query-continue' ].langlinks ) ) {
// We have interlanguage links, and we got them all.
var re = '';
for ( var i = 0; i < page.langlinks.length; i++ ) re += ( i > 0 ? '|' : '' ) + page.langlinks[ i ].lang.replace( /([\\^$.?*+()])/g, '\\$1' );
if ( re.length ) interlanguageRE = new RegExp( '((^|\\n\\r?)(\\[\\[\\s*(' + re + ')\\s*:[^\\]]+\\]\\]\\s*))+$' );
}
}
}
// Siteinfo
if ( json.query.general ) {
// ResourceLoader's JSParser doesn't like .case, so override eslint.
// eslint-disable-next-line dot-notation
HC.capitalizePageNames = ( json.query.general[ 'case' ] === 'first-letter' );
if ( json.query.general.time && !startTime ) startTime = json.query.general.time.replace( /\D/g, '' );
}
serverTime = startTime;
// Userinfo
if ( json.query.userinfo && json.query.userinfo.options ) {
watchCreate = !HC.dont_add_to_watchlist && json.query.userinfo.options.watchcreations === '1';
watchEdit = !HC.dont_add_to_watchlist && json.query.userinfo.options.watchdefault === '1';
minorEdits = json.query.userinfo.options.minordefault === 1;
// If the user has the "All edits are minor" preference enabled, we should honor that
// for single category changes, no matter what the site configuration is.
if ( minorEdits ) HC.single_minor = true;
}
}
}
}


var saveInProgress = false;
var saveInProgress = false;
function initiateEdit( doEdit, failure ) {
function initiateEdit( doEdit, failure ) {
if ( saveInProgress ) { return; }
if ( saveInProgress ) return;
saveInProgress = true;
saveInProgress = true;
var oldButtonState;
var oldButtonState;
خط ۸۲۰: خط ۷۶۳:
function fail() {
function fail() {
saveInProgress = false;
saveInProgress = false;
if ( commitButton ) { commitButton.disabled = oldButtonState; }
if ( commitButton ) commitButton.disabled = oldButtonState;
failure.apply( this, arguments );
failure.apply( this, arguments );
}
}


// Must use Ajax here to get the user options and the edit token.
// Must use Ajax here to get the user options and the edit token.
 
$.getJSON(
getJSON( {
conf.wgServer + conf.wgScriptPath + '/api.php?' +
uri: conf.wgServer + conf.wgScriptPath + '/api.php',
'format=json&action=query&rawcontinue=&titles=' + encodeURIComponent( conf.wgPageName ) +
data: 'format=json&action=query&rawcontinue=&titles=' + encodeURIComponent( conf.wgPageName ) +
'&prop=info%7Crevisions%7Clanglinks&inprop=watched&intoken=edit&rvprop=content%7Ctimestamp%7Cids%7Cuser&lllimit=500' +
'&prop=info%7Crevisions%7Clanglinks&inprop=watched&intoken=edit&rvprop=content%7Ctimestamp%7Cids%7Cuser&lllimit=500' +
'&rvlimit=2&rvdir=newer&rvstartid=' + conf.wgCurRevisionId + '&meta=siteinfo%7Cuserinfo&uiprop=options',
'&rvlimit=2&rvdir=newer&rvstartid=' + conf.wgCurRevisionId +
function ( json ) {
'&meta=siteinfo%7Cuserinfo&uiprop=options',
success: function ( json ) {
setPage( json );
setPage( json );
doEdit( fail );
doEdit( fail );
},
error: function ( req ) {
fail( req.status + ' ' + req.statusText );
}
}
).fail( function ( req ) {
fail( req.status + ' ' + req.statusText );
} );
} );
}
}


function multiChangeMsg( count ) {
function multiChangeMsg( count ) {
var msg = HotCat.messages.multi_change;
var msg = HC.messages.multi_change;
if ( typeof msg !== 'string' && msg.length ) {
if ( typeof msg !== 'string' && msg.length )
if ( mw.language && mw.language.convertPlural ) {
if ( mw.language && mw.language.convertPlural ) { msg = mw.language.convertPlural( count, msg ); } else { msg = msg[ msg.length - 1 ]; }
msg = mw.language.convertPlural( count, msg );
 
} else {
return substitute( msg, [ null, String( count ) ] );
msg = msg[ msg.length - 1 ];
}
}
 
function currentTimestamp() {
var now = new Date();
var ts = String( now.getUTCFullYear() );
function two( s ) {
return s.substr( s.length - 2 );
}
}
return substitute( msg, [ null, String( count ) ] );
ts +=
two( '0' + ( now.getUTCMonth() + 1 ) ) +
two( '0' + now.getUTCDate() ) +
two( '00' + now.getUTCHours() ) +
two( '00' + now.getUTCMinutes() ) +
two( '00' + now.getUTCSeconds() );
return ts;
}
}


function performChanges( failure, singleEditor ) {
function performChanges( failure, singleEditor ) {
if ( pageText === null ) {
if ( pageText === null ) {
failure( HotCat.messages.multi_error );
failure( HC.messages.multi_error );
return;
return;
}
}
// Backwards compatibility after message change (added $2 to cat_keychange)
// Backwards compatibility after message change (added $2 to cat_keychange)
if ( HotCat.messages.cat_keychange.indexOf( '$2' ) < 0 ) { HotCat.messages.cat_keychange += '"$2"'; }
if ( HC.messages.cat_keychange.indexOf( '$2' ) < 0 ) HC.messages.cat_keychange += '"$2"';
 
// More backwards-compatibility with earlier HotCat versions:
// More backwards-compatibility with earlier HotCat versions:
if ( !HotCat.messages.short_catchange ) { HotCat.messages.short_catchange = '[[' + HotCat.category_canonical + ':$1]]'; }
if ( !HC.messages.short_catchange ) HC.messages.short_catchange = '[[' + HC.category_canonical + ':$1]]';
 
// Create a form and submit it. We don't use the edit API (api.php?action=edit) because
// 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
// (a) sensibly reporting back errors like edit conflicts is always a hassle, and
خط ۸۷۶: خط ۸۲۹:
// current user, then we set the "oldid" value and switch to diff, which gives the "you are editing an old version;
// current user, then we set the "oldid" value and switch to diff, which gives the "you are editing an old version;
// if you save, any more recent changes will be lost" screen.
// if you save, any more recent changes will be lost" screen.
var editingOldVersion = lastRevId !== null && lastRevId !== conf.wgCurRevisionId || pageTextRevId !== null && pageTextRevId !== conf.wgCurRevisionId;
var selfEditConflict = ( lastRevId !== null && lastRevId !== conf.wgCurRevisionId || pageTextRevId !== null &&
var selfEditConflict = editingOldVersion && conflictingUser && conflictingUser === conf.wgUserName;
pageTextRevId !== conf.wgCurRevisionId ) && conflictingUser && conflictingUser === conf.wgUserName;
if ( singleEditor && !singleEditor.noCommit && !HotCat.no_autocommit && editToken && !selfEditConflict ) {
if ( singleEditor && !singleEditor.noCommit && !HC.no_autocommit && editToken && !selfEditConflict ) {
// If we do have an edit conflict, but not with ourself, that's no reason not to attempt to save: the server side may actually be able to
// If we do have an edit conflict, but not with ourself, that's no reason not to attempt to save: the server side may actually be able to
// merge the changes. We just need to make sure that we do present a diff view if it's a self edit conflict.
// merge the changes. We just need to make sure that we do present a diff view if it's a self edit conflict.
commitForm.wpEditToken.value = editToken;
commitForm.wpEditToken.value = editToken;
action = commitForm.wpDiff;
action = commitForm.wpDiff;
if ( action ) { action.name = action.value = 'wpSave'; }
if ( action ) action.name = action.value = 'wpSave';
} else {
} else {
action = commitForm.wpSave;
action = commitForm.wpSave;
if ( action ) { action.name = action.value = 'wpDiff'; }
if ( action ) action.name = action.value = 'wpDiff';
}
}
var result = { text: pageText };
var result = {
var changed = [], added = [], deleted = [], changes = 0;
text: pageText
var toEdit = singleEditor ? [ singleEditor ] : editors;
},
var error = null;
changed = [],
var i;
added = [],
deleted = [],
changes = 0,
toEdit = singleEditor ? [ singleEditor ] : editors,
error = null,
edit,
i;
for ( i = 0; i < toEdit.length; i++ ) {
for ( i = 0; i < toEdit.length; i++ ) {
if ( toEdit[ i ].state === CategoryEditor.CHANGED ) {
edit = toEdit[ i ];
if ( edit.state === CategoryEditor.CHANGED ) {
result = change_category(
result = change_category(
result.text,
result.text,
toEdit[ i ].originalCategory,
edit.originalCategory,
toEdit[ i ].currentCategory,
edit.currentCategory,
toEdit[ i ].currentKey,
edit.currentKey,
toEdit[ i ].currentHidden
edit.currentHidden );
);
if ( !result.error ) {
if ( !result.error ) {
changes++;
changes++;
if ( !toEdit[ i ].originalCategory || toEdit[ i ].originalCategory.length === 0 ) {
if ( !edit.originalCategory || !edit.originalCategory.length ) {
added.push( toEdit[ i ].currentCategory );
added.push( edit.currentCategory );
} else {
} else {
changed.push( { from: toEdit[ i ].originalCategory, to: toEdit[ i ].currentCategory } );
changed.push( {
from: edit.originalCategory,
to: edit.currentCategory
} );
}
}
} else if ( error === null ) {
} else if ( error === null ) {
خط ۹۱۳: خط ۸۷۵:
}
}
} else if (
} else if (
toEdit[ i ].state === CategoryEditor.DELETED &&
edit.state === CategoryEditor.DELETED && edit.originalCategory && edit.originalCategory.length ) {
toEdit[ i ].originalCategory &&
result = change_category(
toEdit[ i ].originalCategory.length > 0
result.text,
) {
edit.originalCategory,
result = change_category( result.text, toEdit[ i ].originalCategory, null, null, false );
null, null, false );
if ( !result.error ) {
if ( !result.error ) {
changes++;
changes++;
deleted.push( toEdit[ i ].originalCategory );
deleted.push( edit.originalCategory );
} else if ( error === null ) {
} else if ( error === null ) {
error = result.error;
error = result.error;
خط ۹۲۸: خط ۸۹۰:
if ( error !== null ) { // Do not commit if there were errors
if ( error !== null ) { // Do not commit if there were errors
action = commitForm.wpSave;
action = commitForm.wpSave;
if ( action ) { action.name = action.value = 'wpDiff'; }
if ( action ) action.name = action.value = 'wpDiff';
}
}
// Fill in the form and submit it
// Fill in the form and submit it
commitForm.wpAutoSummary.value = 'd41d8cd98f00b204e9800998ecf8427e'; // MD5 hash of the empty string
commitForm.wpMinoredit.checked = minorEdits;
commitForm.wpMinoredit.checked = minorEdits;
commitForm.wpWatchthis.checked = conf.wgArticleId === 0 && watchCreate || watchEdit || pageWatched;
commitForm.wpWatchthis.checked = !conf.wgArticleId && watchCreate || watchEdit || pageWatched;
if ( conf.wgArticleId > 0 || !!singleEditor ) {
if ( conf.wgArticleId || !!singleEditor ) {
// Prepare change-tag save
if ( action && action.value === 'wpSave' ) {
if ( HC.changeTag ) {
commitForm.wpChangeTags.value = HC.changeTag;
HC.messages.using = '';
HC.messages.prefix = '';
}
} else {
commitForm.wpAutoSummary.value = HC.changeTag;
}
if ( changes === 1 ) {
if ( changes === 1 ) {
if ( result.summary && result.summary.length > 0 ) {
if ( result.summary && result.summary.length ) commitForm.wpSummary.value = HC.messages.prefix + result.summary.join( HC.messages.separator ) + HC.messages.using;
commitForm.wpSummary.value = HotCat.messages.prefix + result.summary.join( HotCat.messages.separator ) + HotCat.messages.using;
commitForm.wpMinoredit.checked = HC.single_minor || minorEdits;
}
} else if ( changes ) {
commitForm.wpMinoredit.checked = HotCat.single_minor || minorEdits;
} else if ( changes > 1 ) {
var summary = [];
var summary = [];
var shortSummary = [];
var shortSummary = [];
// Deleted
// Deleted
for ( i = 0; i < deleted.length; i++ ) {
for ( i = 0; i < deleted.length; i++ ) summary.push( '-' + substitute( HC.messages.short_catchange, [ null, deleted[ i ] ] ) );
summary.push( '-' + substitute( HotCat.messages.short_catchange, [ null, deleted[ i ] ] ) );
 
}
if ( deleted.length === 1 ) shortSummary.push( '-' + substitute( HC.messages.short_catchange, [ null, deleted[ 0 ] ] ) ); else if ( deleted.length ) shortSummary.push( '- ' + multiChangeMsg( deleted.length ) );
if ( deleted.length === 1 ) {
 
shortSummary.push( '-' + substitute( HotCat.messages.short_catchange, [ null, deleted[ 0 ] ] ) );
} else if ( deleted.length > 1 ) {
shortSummary.push( '- ' + multiChangeMsg( deleted.length ) );
}
// Added
// Added
for ( i = 0; i < added.length; i++ ) {
for ( i = 0; i < added.length; i++ ) summary.push( '+' + substitute( HC.messages.short_catchange, [ null, added[ i ] ] ) );
summary.push( '+' + substitute( HotCat.messages.short_catchange, [ null, added[ i ] ] ) );
 
}
if ( added.length === 1 ) shortSummary.push( '+' + substitute( HC.messages.short_catchange, [ null, added[ 0 ] ] ) ); else if ( added.length ) shortSummary.push( '+ ' + multiChangeMsg( added.length ) );
if ( added.length === 1 ) {
 
shortSummary.push( '+' + substitute( HotCat.messages.short_catchange, [ null, added[ 0 ] ] ) );
} else if ( added.length > 1 ) {
shortSummary.push( '+ ' + multiChangeMsg( added.length ) );
}
// Changed
// Changed
var arrow = is_rtl ? '\u2190' : '\u2192'; // left and right arrows. Don't use ← and → in the code.
var arrow = is_rtl ? '\u2190' : '\u2192'; // left and right arrows. Don't use ← and → in the code.
for ( i = 0; i < changed.length; i++ ) {
for ( i = 0; i < changed.length; i++ ) {
if ( changed[ i ].from !== changed[ i ].to ) {
if ( changed[ i ].from !== changed[ i ].to ) {
summary.push( '±' + substitute( HotCat.messages.short_catchange, [ null, changed[ i ].from ] ) + arrow +
summary.push(
substitute( HotCat.messages.short_catchange, [ null, changed[ i ].to ] ) );
'±' + substitute( HC.messages.short_catchange, [ null, changed[ i ].from ] ) + arrow +
substitute( HC.messages.short_catchange, [ null, changed[ i ].to ] )
);
} else {
} else {
summary.push( '±' + substitute( HotCat.messages.short_catchange, [ null, changed[ i ].from ] ) );
summary.push( '±' + substitute( HC.messages.short_catchange, [ null, changed[ i ].from ] ) );
}
}
}
}
if ( changed.length === 1 ) {
if ( changed.length === 1 ) {
if ( changed[ 0 ].from !== changed[ 0 ].to ) {
if ( changed[ 0 ].from !== changed[ 0 ].to ) {
shortSummary.push( '±' + substitute( HotCat.messages.short_catchange, [ null, changed[ 0 ].from ] ) + arrow +
shortSummary.push(
substitute( HotCat.messages.short_catchange, [ null, changed[ 0 ].to ] ) );
'±' + substitute( HC.messages.short_catchange, [ null, changed[ 0 ].from ] ) + arrow +
substitute( HC.messages.short_catchange, [ null, changed[ 0 ].to ] )
);
} else {
} else {
shortSummary.push( '±' + substitute( HotCat.messages.short_catchange, [ null, changed[ 0 ].from ] ) );
shortSummary.push( '±' + substitute( HC.messages.short_catchange, [ null, changed[ 0 ].from ] ) );
}
}
} else if ( changed.length > 1 ) {
} else if ( changed.length ) {
shortSummary.push( '± ' + multiChangeMsg( changed.length ) );
shortSummary.push( '± ' + multiChangeMsg( changed.length ) );
}
}
if ( summary.length > 0 ) {
if ( summary.length ) {
summary = summary.join( HotCat.messages.separator );
summary = summary.join( HC.messages.separator );
if ( summary.length > 200 - HotCat.messages.prefix.length - HotCat.messages.using.length ) {
if ( summary.length > 200 - HC.messages.prefix.length - HC.messages.using.length ) summary = shortSummary.join( HC.messages.separator );
summary = shortSummary.join( HotCat.messages.separator );
 
}
commitForm.wpSummary.value = HC.messages.prefix + summary + HC.messages.using;
commitForm.wpSummary.value = HotCat.messages.prefix + summary + HotCat.messages.using;
}
}
}
}
}
}
commitForm.wpTextbox1.value = result.text;
commitForm.wpTextbox1.value = result.text;
commitForm.wpStarttime.value = serverTime || currentTimestamp();
commitForm.wpStarttime.value = serverTime || currentTimestamp();
commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value;
commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value;
if ( selfEditConflict ) { commitForm.oldid.value = String( pageTextRevId || conf.wgCurRevisionId ); }
if ( selfEditConflict ) commitForm.oldid.value = String( pageTextRevId || conf.wgCurRevisionId );
 
// Submit the form in a way that triggers onsubmit events: commitForm.submit() doesn't.
// Submit the form in a way that triggers onsubmit events: commitForm.submit() doesn't.
commitForm.hcCommit.click();
commitForm.hcCommit.click();
}
function resolveMulti( toResolve, callback ) {
var i;
for ( 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 ( i = 0; i < toResolve.length; i++ ) {
var v = toResolve[ i ].dabInput;
v = replaceShortcuts( v, HotCat.shortcuts );
toResolve[ i ].dabInputCleaned = v;
args += encodeURIComponent( 'Category:' + v );
if ( i + 1 < toResolve.length ) { args += '%7C'; }
}
getJSON( {
uri: conf.wgServer + conf.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 ) {
function resolveOne( page, toResolve ) {
var cats = page.categories;
var cats = page.categories,
var lks = page.links;
lks = page.links,
var is_dab = false;
is_dab = false,
var is_redir = typeof page.redirect === 'string'; // Hard redirect?
is_redir = typeof page.redirect === 'string', // Hard redirect?
var is_hidden = page.categoryinfo && typeof page.categoryinfo.hidden === 'string';
is_hidden = page.categoryinfo && typeof page.categoryinfo.hidden === 'string',
var is_missing = typeof page.missing === 'string';
is_missing = typeof page.missing === 'string',
var i;
i;
for ( i = 0; i < toResolve.length; i++ ) {
for ( i = 0; i < toResolve.length; i++ ) {
if ( toResolve.length > 1 && toResolve[ i ].dabInputCleaned !== page.title.substring( page.title.indexOf( ':' ) + 1 ) ) { continue; }
if ( i && toResolve[ i ].dabInputCleaned !== page.title.substring( page.title.indexOf( ':' ) + 1 ) ) continue;
// Note: the server returns in page an NFC normalized Unicode title. If our input was not NFC normalized, we may not find
// Note: the server returns in page an NFC normalized Unicode title. If our input was not NFC normalized, we may not find
// any entry here. If we have only one editor to resolve (the most common case, I presume), we may simply skip the check.
// any entry here. If we have only one editor to resolve (the most common case, I presume), we may simply skip the check.
toResolve[ i ].currentHidden = is_hidden;
toResolve[ i ].currentHidden = is_hidden;
toResolve[ i ].inputExists = !is_missing;
toResolve[ i ].inputExists = !is_missing;
toResolve[ i ].icon.src = armorUri( is_missing ? HotCat.existsNo : HotCat.existsYes );
toResolve[ i ].icon.src = ( is_missing ? HC.existsNo : HC.existsYes );
}
}
if ( is_missing ) { return; }
if ( is_missing ) return;
if ( !is_redir && cats && ( HotCat.disambig_category || HotCat.redir_category ) ) {
if ( !is_redir && cats && ( HC.disambig_category || HC.redir_category ) ) {
for ( var c = 0; c < cats.length; c++ ) {
for ( var c = 0; c < cats.length; c++ ) {
var cat = cats[ c ].title;
var cat = cats[ c ].title;
خط ۱٬۰۵۲: خط ۹۸۷:
if ( cat ) {
if ( cat ) {
cat = cat.substring( cat.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
cat = cat.substring( cat.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
if ( cat === HotCat.disambig_category ) {
if ( cat === HC.disambig_category ) {
is_dab = true; break;
is_dab = true;
} else if ( cat === HotCat.redir_category ) {
break;
is_redir = true; break;
} else if ( cat === HC.redir_category ) {
is_redir = true;
break;
}
}
}
}
}
}
}
}
if ( !is_redir && !is_dab ) { return; }
if ( !is_redir && !is_dab ) return;
if ( !lks || lks.length === 0 ) { return; }
if ( !lks || !lks.length ) return;
var titles = [];
var titles = [];
for ( i = 0; i < lks.length; i++ ) {
for ( i = 0; i < lks.length; i++ ) {
خط ۱٬۰۶۸: خط ۱٬۰۰۵:
lks[ i ].ns === 14 &&
lks[ i ].ns === 14 &&
// Name not empty
// Name not empty
lks[ i ].title && lks[ i ].title.length > 0
lks[ i ].title && lks[ i ].title.length
) {
) {
// Internal link to existing thingy. Extract the page name and remove the namespace.
// Internal link to existing thingy. Extract the page name and remove the namespace.
خط ۱٬۰۷۴: خط ۱٬۰۱۱:
match = match.substring( match.indexOf( ':' ) + 1 );
match = match.substring( match.indexOf( ':' ) + 1 );
// Exclude blacklisted categories.
// Exclude blacklisted categories.
if ( !HotCat.blacklist || !HotCat.blacklist.test( match ) ) {
if ( !HC.blacklist || !HC.blacklist.test( match ) ) titles.push( match );
titles.push( match );
}
}
}
}
}
if ( titles.length === 0 ) {
if ( !titles.length ) return;
return;
for ( i = 0; i < toResolve.length; i++ ) {
}
if ( i && toResolve[ i ].dabInputCleaned !== page.title.substring( page.title.indexOf( ':' ) + 1 ) ) continue;
for ( i = 0; i < toResolve.length; i++ ) {
if ( toResolve.length > 1 && toResolve[ i ].dabInputCleaned !== page.title.substring( page.title.indexOf( ':' ) + 1 ) ) { continue; }
toResolve[ i ].inputExists = true; // Might actually be wrong if it's a redirect pointing to a non-existing category
toResolve[ i ].inputExists = true; // Might actually be wrong if it's a redirect pointing to a non-existing category
toResolve[ i ].icon.src = armorUri( HotCat.existsYes );
toResolve[ i ].icon.src = HC.existsYes;
if ( titles.length > 1 ) {
if ( titles.length > 1 ) {
toResolve[ i ].dab = titles;
toResolve[ i ].dab = titles;
خط ۱٬۰۹۶: خط ۱٬۰۲۹:


function resolveRedirects( toResolve, params ) {
function resolveRedirects( toResolve, params ) {
if ( !params || !params.query || !params.query.pages ) { return; }
if ( !params || !params.query || !params.query.pages ) return;
for ( var p in params.query.pages ) { resolveOne( params.query.pages[ p ], toResolve ); }
for ( var p in params.query.pages ) resolveOne( params.query.pages[ p ], toResolve );
}
}


function multiSubmit() {
function resolveMulti( toResolve, callback ) {
var toResolve = [];
var i;
for ( var i = 0; i < editors.length; i++ ) {
for ( i = 0; i < toResolve.length; i++ ) {
if ( editors[ i ].state === CategoryEditor.CHANGE_PENDING || editors[ i ].state === CategoryEditor.OPEN ) {
toResolve[ i ].dab = null;
toResolve.push( editors[ i ] );
toResolve[ i ].dabInput = toResolve[ i ].lastInput;
}
}
}
if ( toResolve.length === 0 ) {
if ( noSuggestions ) {
initiateEdit( function ( failure ) { performChanges( failure ); }, function ( msg ) { alert( msg ); } );
callback( toResolve );
return;
return;
}
}
resolveMulti( toResolve, function ( resolved ) {
// Use %7C instead of |, otherwise Konqueror insists on re-encoding the arguments, resulting in doubly encoded
var firstDab = null;
// category names. (That is a bug in Konqueror. Other browsers don't have this problem.)
var dontChange = false;
var args = 'action=query&prop=info%7Clinks%7Ccategories%7Ccategoryinfo&plnamespace=14' +
for ( var i = 0; i < resolved.length; i++ ) {
'&pllimit=' + ( toResolve.length * 10 ) +
if ( resolved[ i ].lastInput !== resolved[ i ].dabInput ) {
'&cllimit=' + ( toResolve.length * 10 ) +
// We didn't disable all the open editors, but we did asynchronous calls. It is
'&format=json&titles=';
// theoretically possible that the user changed something...
for ( i = 0; i < toResolve.length; i++ ) {
dontChange = true;
var v = toResolve[ i ].dabInput;
} else {
v = replaceShortcuts( v, HC.shortcuts );
if ( resolved[ i ].dab ) {
toResolve[ i ].dabInputCleaned = v;
if ( !firstDab ) { firstDab = resolved[ i ]; }
args += encodeURIComponent( 'Category:' + v );
} else {
if ( i + 1 < toResolve.length ) args += '%7C';
if ( resolved[ i ].acceptCheck( true ) ) { resolved[ i ].commit(); }
}
}
$.getJSON( conf.wgServer + conf.wgScriptPath + '/api.php?' + args,
}
function ( json ) {
}
resolveRedirects( toResolve, json );
if ( firstDab ) {
callback( toResolve );
showDab( firstDab );
} ).fail( function ( req ) {
} else if ( !dontChange ) {
if ( !req ) noSuggestions = true;
initiateEdit( function ( failure ) { performChanges( failure ); }, function ( msg ) { alert( msg ); } );
callback( toResolve );
}
} );
} );
}
}


var cat_prefix = null;
function makeActive( which ) {
var noSuggestions = false;
if ( which.is_active ) return;
var suggestionEngines = {
for ( var i = 0; i < editors.length; i++ )
opensearch: {
if ( editors[ i ] !== which ) editors[ i ].inactivate();
uri: '/api.php?format=json&action=opensearch&namespace=14&limit=30&search=Category:$1', // $1 = search term
 
// Function to convert result of uri into an array of category names
which.is_active = true;
handler: function ( queryResult, queryKey ) {
if ( which.dab ) {
if ( queryResult && queryResult.length >= 2 ) {
// eslint-disable-next-line no-use-before-define
var key = queryResult[ 0 ].substring( queryResult[ 0 ].indexOf( ':' ) + 1 );
showDab( which );
var titles = queryResult[ 1 ];
} else {
var exists = false;
// Check for programmatic value changes.
if ( !cat_prefix ) { cat_prefix = new RegExp( '^(' + HotCat.category_regexp + ':)' ); }
var expectedInput = which.lastRealInput || which.lastInput || '';
for ( var i = 0; i < titles.length; i++ ) {
var actualValue = which.text.value || '';
cat_prefix.lastIndex = 0;
if ( !expectedInput.length && actualValue.length || expectedInput.length && actualValue.indexOf( expectedInput ) ) {
var m = cat_prefix.exec( titles[ i ] );
// Somehow the field's value appears to have changed, and which.lastSelection therefore is no longer valid. Try to set the
if ( m && m.length > 1 ) {
// cursor at the end of the category, and do not display the old suggestion list.
titles[ i ] = titles[ i ].substring( titles[ i ].indexOf( ':' ) + 1 ); // rm namespace
which.showsList = false;
if ( key === titles[ i ] ) { exists = true; }
var v = actualValue.split( '|' );
} else {
which.lastRealInput = which.lastInput = v[ 0 ];
titles.splice( i, 1 ); // Nope, it's not a category after all.
if ( v.length > 1 ) which.currentKey = v[ 1 ];
i--;
 
}
if ( which.lastSelection ) {
}
which.lastSelection = {
titles.exists = exists;
start: v[ 0 ].length,
if ( queryKey !== key ) { titles.normalized = key; } // Remember the NFC normalized key we got back from the server
end: v[ 0 ].length
return titles;
};
}
}
return null;
}
}
},
if ( which.showsList ) which.displayList();
internalsearch: {
 
uri: '/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1&apprefix=$1',
if ( which.lastSelection ) {
handler: function ( queryResult ) {
if ( is_webkit ) {
if ( queryResult && queryResult.query && queryResult.query.allpages ) {
// WebKit (Safari, Chrome) has problems selecting inside focus()
var titles = queryResult.query.allpages;
// See http://code.google.com/p/chromium/issues/detail?id=32865#c6
for ( var i = 0; i < titles.length; i++ ) {
window.setTimeout(
titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace
function () {
}
which.setSelection( which.lastSelection.start, which.lastSelection.end );
return titles;
},
1 );
} else {
which.setSelection( which.lastSelection.start, which.lastSelection.end );
}
}
return null;
}
}
},
}
exists: {
}
uri: '/api.php?format=json&action=query&prop=info&titles=Category:$1',
 
handler: function ( queryResult, queryKey ) {
function showDab( which ) {
if ( queryResult && queryResult.query && queryResult.query.pages && !queryResult.query.pages[ -1 ] ) {
if ( !which.is_active ) {
// Should have exactly 1
makeActive( which );
for ( var p in queryResult.query.pages ) {
} else {
var title = queryResult.query.pages[ p ].title;
which.showSuggestions( which.dab, false, null, null ); // do autocompletion, no key, no engine selector
title = title.substring( title.indexOf( ':' ) + 1 );
which.dab = null;
var titles = [ title ];
}
titles.exists = true;
}
if ( queryKey !== title ) { titles.normalized = title; } // NFC
 
return titles;
function multiSubmit() {
}
var toResolve = [];
}
for ( var i = 0; i < editors.length; i++ )
return null;
if ( editors[ i ].state === CategoryEditor.CHANGE_PENDING || editors[ i ].state === CategoryEditor.OPEN ) toResolve.push( editors[ i ] );
}
 
},
if ( !toResolve.length ) {
subcategories: {
initiateEdit( function ( failure ) {
uri: '/api.php?format=json&action=query&list=categorymembers&cmtype=subcat&cmlimit=max&cmtitle=Category:$1',
performChanges( failure );
handler: function ( queryResult ) {
}, function ( msg ) {
if ( queryResult && queryResult.query && queryResult.query.categorymembers ) {
alert( msg );
var titles = queryResult.query.categorymembers;
} );
for ( var i = 0; i < titles.length; i++ ) {
return;
titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace
}
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();
}
}
return titles;
}
}
return null;
}
}
},
if ( firstDab ) {
parentcategories: {
showDab( firstDab );
uri: '/api.php?format=json&action=query&prop=categories&titles=Category:$1&cllimit=max',
} else if ( !dontChange ) {
handler: function ( queryResult ) {
initiateEdit( function ( failure ) {
if ( queryResult && queryResult.query && queryResult.query.pages ) {
performChanges( failure );
for ( var p in queryResult.query.pages ) {
}, function ( msg ) {
if ( queryResult.query.pages[ p ].categories ) {
alert( msg );
var titles = queryResult.query.pages[ p ].categories;
} );
for ( var i = 0; i < titles.length; i++ ) {
titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace
}
return titles;
}
}
}
return null;
}
}
}
} );
};
}


var suggestionConfigs = {
function setMultiInput() {
searchindex: { name: 'Search index', engines: [ 'opensearch' ], cache: {}, show: true, temp: false, noCompletion: false },
if ( commitButton || onUpload ) return;
pagelist: { name: 'Page list', engines: [ 'internalsearch', 'exists' ], cache: {}, show: true, temp: false, noCompletion: false },
commitButton = make( 'input' );
combined: { name: 'Combined search', engines: [ 'opensearch', 'internalsearch' ], cache: {}, show: true, temp: false, noCompletion: false },
commitButton.type = 'button';
subcat: { name: 'Subcategories', engines: [ 'subcategories' ], cache: {}, show: true, temp: true, noCompletion: true },
commitButton.value = HC.messages.commit;
parentcat: { name: 'Parent categories', engines: [ 'parentcategories' ], cache: {}, show: true, temp: true, noCompletion: true }
commitButton.onclick = multiSubmit;
};
if ( multiSpan ) multiSpan.parentNode.replaceChild( commitButton, multiSpan ); else catLine.appendChild( commitButton );
}


function CategoryEditor() { this.initialize.apply( this, arguments ); }
function checkMultiInput() {
CategoryEditor.UNCHANGED = 0;
if ( !commitButton ) return;
CategoryEditor.OPEN = 1; // Open, but no input yet
var hasChanges = false;
CategoryEditor.CHANGE_PENDING = 2; // Open, some input made
for ( var i = 0; i < editors.length; i++ ) {
CategoryEditor.CHANGED = 3;
if ( editors[ i ].state !== CategoryEditor.UNCHANGED ) {
CategoryEditor.DELETED = 4;
hasChanges = true;
 
break;
// IE6 sometimes forgets to redraw the list when editors are opened or closed.
}
// Adding/removing a dummy element helps, at least when opening editors.
}
var dummyElement = make( '\xa0', true );
commitButton.disabled = !hasChanges;
 
function forceRedraw() {
if ( dummyElement.parentNode ) {
document.body.removeChild( dummyElement );
} else {
document.body.appendChild( dummyElement );
}
}
}


// Event keyCodes that we handle in the text input field/suggestion list.
var suggestionEngines = {
var BS = 8, TAB = 9, RET = 13, ESC = 27, SPACE = 32, PGUP = 33, PGDOWN = 34, UP = 38, DOWN = 40, DEL = 46, IME = 229;
opensearch: {
uri: '/api.php?format=json&action=opensearch&namespace=14&limit=30&search=Category:$1', // $1 = search term
// Function to convert result of uri into an array of category names
handler: function ( queryResult, queryKey ) {
if ( queryResult && queryResult.length >= 2 ) {
var key = queryResult[ 0 ].substring( queryResult[ 0 ].indexOf( ':' ) + 1 );
var titles = queryResult[ 1 ];
var exists = false;
if ( !cat_prefix ) cat_prefix = new RegExp( '^(' + HC.category_regexp + '):' );


function makeActive( which ) {
for ( var i = 0; i < titles.length; i++ ) {
if ( which.is_active ) { return; }
cat_prefix.lastIndex = 0;
for ( var i = 0; i < editors.length; i++ ) {
var m = cat_prefix.exec( titles[ i ] );
if ( editors[ i ] !== which ) { editors[ i ].inactivate(); }
if ( m && m.length > 1 ) {
}
titles[ i ] = titles[ i ].substring( titles[ i ].indexOf( ':' ) + 1 ); // rm namespace
which.is_active = true;
if ( key === titles[ i ] ) exists = true;
if ( which.dab ) {
} else {
showDab( which );
titles.splice( i, 1 ); // Nope, it's not a category after all.
} else {
i--;
// Check for programmatic value changes.
}
var expectedInput = which.lastRealInput || which.lastInput || '';
}
var actualValue = which.text.value || '';
titles.exists = exists;
if ( expectedInput.length === 0 && actualValue.length > 0 || expectedInput.length > 0 && actualValue.indexOf( expectedInput ) !== 0 ) {
if ( queryKey !== key ) titles.normalized = key;
// Somehow the field's value appears to have changed, and which.lastSelection therefore is no longer valid. Try to set the
// Remember the NFC normalized key we got back from the server
// cursor at the end of the category, and do not display the old suggestion list.
return titles;
which.showsList = false;
var v = actualValue.split( '|' );
which.lastRealInput = which.lastInput = v[ 0 ];
if ( v.length > 1 ) { which.currentKey = v[ 1 ]; }
if ( which.lastSelection ) { which.lastSelection = { start: v[ 0 ].length, end: v[ 0 ].length }; }
}
if ( which.showsList ) { which.displayList(); }
if ( which.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 () { which.setSelection( which.lastSelection.start, which.lastSelection.end ); },
1
);
} else {
which.setSelection( which.lastSelection.start, which.lastSelection.end );
}
}
return null;
}
}
}
},
}
internalsearch: {
uri: '/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1&apprefix=$1',
handler: function ( queryResult ) {
if ( queryResult && queryResult.query && queryResult.query.allpages ) {
var titles = queryResult.query.allpages;
for ( var i = 0; i < titles.length; i++ ) titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace


function showDab( which ) {
return titles;
if ( !which.is_active ) {
}
makeActive( which );
return null;
} else {
}
which.showSuggestions( which.dab, false, null, null ); // do autocompletion, no key, no engine selector
},
which.dab = null;
exists: {
}
uri: '/api.php?format=json&action=query&prop=info&titles=Category:$1',
}
handler: function ( queryResult, queryKey ) {
 
if ( queryResult && queryResult.query && queryResult.query.pages && !queryResult.query.pages[ -1 ] ) {
CategoryEditor.prototype = {
// Should have exactly 1
 
for ( var p in queryResult.query.pages ) {
initialize: function ( line, span, after, key, is_hidden ) {
var title = queryResult.query.pages[ p ].title;
// If a span is given, 'after' is the category title, otherwise it may be an element after which to
title = title.substring( title.indexOf( ':' ) + 1 );
// insert the new span. 'key' is likewise overloaded; if a span is given, it is the category key (if
var titles = [ title ];
// known), otherwise it is a boolean indicating whether a bar shall be prepended.
titles.exists = true;
if ( !span ) {
if ( queryKey !== title ) titles.normalized = title;
this.isAddCategory = true;
// NFC
// Create add span and append to catLinks
return titles;
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' );
return null;
this.linkSpan.className = 'noprint nopopups hotcatlink';
}
var lk = make( 'a' ); lk.href = '#catlinks'; lk.onclick = this.open.bind( this );
},
lk.appendChild( make( HotCat.links.add, true ) ); lk.title = HotCat.tooltips.add;
subcategories: {
this.linkSpan.appendChild( lk );
uri: '/api.php?format=json&action=query&list=categorymembers&cmtype=subcat&cmlimit=max&cmtitle=Category:$1',
span = make( newDOM ? 'li' : 'span' );
handler: function ( queryResult ) {
span.className = 'noprint';
if ( queryResult && queryResult.query && queryResult.query.categorymembers ) {
if ( is_rtl ) { span.dir = 'rtl'; }
var titles = queryResult.query.categorymembers;
span.appendChild( this.linkSpan );
for ( var i = 0; i < titles.length; i++ ) titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace
if ( after ) {
 
after.parentNode.insertBefore( span, after.nextSibling );
return titles;
} else {
line.appendChild( span );
}
}
this.normalLinks = null;
return 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;
},
},
parentcategories: {
uri: '/api.php?format=json&action=query&prop=categories&titles=Category:$1&cllimit=max',
handler: function ( queryResult ) {
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


makeLinkSpan: function () {
return titles;
this.normalLinks = make( 'span' );
}
var lk = null;
}
if ( this.originalCategory && this.originalCategory.length > 0 ) {
lk = make( 'a' ); lk.href = '#catlinks'; lk.onclick = this.remove.bind( 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 = this.open.bind( 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 = this.down.bind( 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 = this.up.bind( 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 );
}
}
return null;
}
}
this.linkSpan = make( 'span' );
}
this.linkSpan.className = 'noprint nopopups hotcatlink';
};
this.linkSpan.appendChild( this.normalLinks );
 
this.undelLink = make( 'span' );
var suggestionConfigs = {
this.undelLink.className = 'nopopups hotcatlink';
searchindex: {
this.undelLink.style.display = 'none';
name: 'Search index',
lk = make( 'a' ); lk.href = '#catlinks'; lk.onclick = this.restore.bind( this );
engines: [ 'opensearch' ],
lk.appendChild( make( HotCat.links.restore, true ) ); lk.title = HotCat.tooltips.restore;
cache: {},
this.undelLink.appendChild( make( ' ', true ) );
show: true,
this.undelLink.appendChild( lk );
temp: false,
this.linkSpan.appendChild( this.undelLink );
noCompletion: false
},
pagelist: {
name: 'Page list',
engines: [ 'internalsearch', 'exists' ],
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
}
};


invokeSuggestions: function ( dont_autocomplete ) {
CategoryEditor.UNCHANGED = 0;
if ( this.engine && suggestionConfigs[ this.engine ] && suggestionConfigs[ this.engine ].temp && !dont_autocomplete ) {
CategoryEditor.OPEN = 1; // Open, but no input yet
this.engine = HotCat.suggestions; // Reset to a search upon input
CategoryEditor.CHANGE_PENDING = 2; // Open, some input made
}
CategoryEditor.CHANGED = 3;
this.state = CategoryEditor.CHANGE_PENDING;
CategoryEditor.DELETED = 4;
var self = this;
window.setTimeout( function () { self.textchange( dont_autocomplete ); }, HotCat.suggest_delay );
},


makeForm: function () {
// Support: IE6
var form = make( 'form' );
// IE6 sometimes forgets to redraw the list when editors are opened or closed.
form.method = 'POST'; form.onsubmit = this.accept.bind( this );
// Adding/removing a dummy element helps, at least when opening editors.
this.form = form;
var dummyElement = make( '\xa0', true );
var self = this;
var text = make( 'input' ); text.type = 'text'; text.size = HotCat.editbox_width;
if ( !noSuggestions ) {
// Be careful here to handle IME input. This is browser/OS/IME dependent, but basically there are two mechanisms:
// - Modern (DOM Level 3) browsers use compositionstart/compositionend events to signal composition; if the
//  composition is not canceled, there'll be a textInput event following. During a composition key events are
//  either all suppressed (FF/Gecko), or otherwise have keyDown === IME for all keys (Webkit).
//   - Webkit sends a textInput followed by keyDown === IME and a keyUp with the key that ended composition.
//  - Gecko doesn't send textInput but just a keyUp with the key that ended composition, without sending keyDown
//    first. Gecko doesn't send any keydown while IME is active.
// - Older browsers signal composition by keyDown === IME for the first and subsequent keys for a composition. The
//  first keyDown !== IME is certainly after the end of the composition. Typically, composition end can also be
//  detected by a keyDown IME with a keyUp of space, tab, escape, or return. (Example: IE8)
text.onkeyup =
function ( evt ) {
evt = evt || window.event || window.Event; // W3C, IE, Netscape
var key = evt.keyCode || 0;
if ( self.ime && self.lastKey === IME && !self.usesComposition && ( key === TAB || key === RET || key === ESC || key === SPACE ) ) { self.ime = false; }
if ( self.ime ) { return true; }
if ( key === UP || key === DOWN || key === PGUP || key === PGDOWN ) {
// In case a browser doesn't generate keypress events for arrow keys...
if ( self.keyCount === 0 ) { return self.processKey( evt ); }
} else {
if ( key === ESC && self.lastKey !== IME ) {
if ( !self.resetKeySelection() ) {
// No undo of key selection: treat ESC as "cancel".
self.cancel();
return;
}
}
// Also do this for ESC as a workaround for Firefox bug 524360
// https://bugzilla.mozilla.org/show_bug.cgi?id=524360
self.invokeSuggestions( key === BS || key === DEL || key === ESC );
}
return true;
};
text.onkeydown =
function ( evt ) {
evt = evt || window.event || window.Event; // W3C, IE, Netscape
var key = evt.keyCode || 0;
self.lastKey = key;
self.keyCount = 0;
// DOM Level < 3 IME input
if ( !self.ime && key === IME && !self.usesComposition ) {
// self.usesComposition catches browsers that may emit spurious keydown IME after a composition has ended
self.ime = true;
} else if ( self.ime && key !== IME && !( key >= 16 && key <= 20 || key >= 91 && key <= 93 || key === 144 ) ) {
// Ignore control keys: ctrl, shift, alt, alt gr, caps lock, windows/apple cmd keys, num lock. Only the windows keys
// terminate IME (apple cmd doesn't), but they also cause a blur, so it's OK to ignore them here.
// Note: Safari 4 (530.17) propagates ESC out of an IME composition (observed at least on Win XP).
self.ime = false;
}
if ( self.ime ) { return true; }
// Handle return explicitly, to override the default form submission to be able to check for ctrl
if ( key === RET ) { return self.accept( evt ); }
// Inhibit default behavior of ESC (revert to last real input in FF: we do that ourselves)
return ( key === ESC ) ? evtKill( evt ) : true;
};
// And handle continued pressing of arrow keys
text.onkeypress = function ( evt ) { self.keyCount++; return self.processKey( evt ); };
$( text ).on( 'focus', function () { makeActive( self ); } );
// 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.
$( text ).on(
( typeof text.onbeforedeactivate !== 'undefined' && text.createTextRange ) ? 'beforedeactivate' : 'blur'
, this.saveView.bind( this )
);
// DOM Level 3 IME handling
try {
// Setting lastKey = IME provides a fake keyDown for Gecko's single keyUp after a cmposition. If we didn't do this,
// cancelling a composition via ESC would also cancel and close the whole category input editor.
$( text ).on( 'compositionstart', function () { self.lastKey = IME; self.usesComposition = true; self.ime = true; } );
$( text ).on( 'compositionend', function () { self.lastKey = IME; self.usesComposition = true; self.ime = false; } );
$( text ).on( 'textInput', function () { self.ime = false; self.invokeSuggestions( false ); } );
} catch ( any ) {
// Just in case some browsers might produce exceptions with these DOM Level 3 events
}
$( text ).on( 'blur', function () { self.usesComposition = false; self.ime = false; } );
}
this.text = text;


this.icon = make( 'img' );
function forceRedraw() {
if ( dummyElement.parentNode ) document.body.removeChild( dummyElement ); else document.body.appendChild( dummyElement );
}


var list = null;
// Event keyCodes that we handle in the text input field/suggestion list.
if ( !noSuggestions ) {
var BS = 8,
list = make( 'select' );
TAB = 9,
list.onclick = function () { if ( self.highlightSuggestion( 0 ) ) { self.textchange( false, true ); } };
RET = 13,
list.ondblclick = function ( e ) { if ( self.highlightSuggestion( 0 ) ) { self.accept( e ); } };
ESC = 27,
list.onchange = function () { self.highlightSuggestion( 0 ); self.text.focus(); };
SPACE = 32,
list.onkeyup = function ( evt ) {
PGUP = 33,
evt = evt || window.event || window.Event; // W3C, IE, Netscape
PGDOWN = 34,
if ( evt.keyCode === ESC ) {
UP = 38,
self.resetKeySelection();
DOWN = 40,
self.text.focus();
DEL = 46,
window.setTimeout( function () { self.textchange( true ); }, HotCat.suggest_delay );
IME = 229;
} else if ( evt.keyCode === RET ) {
 
self.accept( evt );
CategoryEditor.prototype = {
}
};
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 = function () {
self.engine = self.engineSelector.options[ self.engineSelector.selectedIndex ].value;
self.text.focus();
self.textchange( true, true ); // Don't autocomplete, force re-display of list
};
this.engineSelector = engineSelector;
}
}
this.list = list;


function button_label( id, defaultText ) {
initialize: function ( line, span, after, key, is_hidden ) {
var label = null;
// If a span is given, 'after' is the category title, otherwise it may be an element after which to
if (
// insert the new span. 'key' is likewise overloaded; if a span is given, it is the category key (if
onUpload &&
// known), otherwise it is a boolean indicating whether a bar shall be prepended.
typeof UFUI !== 'undefined' &&
if ( !span ) {
typeof UIElements !== 'undefined' &&
this.isAddCategory = true;
typeof UFUI.getLabel === 'function'
// Create add span and append to catLinks
) {
this.originalCategory = '';
try {
this.originalKey = null;
label = UFUI.getLabel( id, true );
this.originalExists = false;
// Extract the plain text. IE doesn't know that Node.TEXT_NODE === 3
if ( !newDOM ) {
while ( label && label.nodeType !== 3 ) { label = label.firstChild; }
span = make( 'span' );
} catch ( ex ) {
span.className = 'noprint';
label = null;
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 );
}
}
}
}
if ( !label || !label.data ) { return defaultText; }
this.linkSpan = make( 'span' );
return label.data;
this.linkSpan.className = 'noprint nopopups hotcatlink';
}
var lk = make( 'a' );
lk.href = '#catlinks';
lk.onclick = this.open.bind( this );
lk.appendChild( make( HC.links.add, true ) );
lk.title = HC.tooltips.add;
this.linkSpan.appendChild( lk );
span = make( newDOM ? 'li' : 'span' );
span.className = 'noprint';
if ( is_rtl ) span.dir = 'rtl';


// Do not use type 'submit'; we cannot detect modifier keys if we do
span.appendChild( this.linkSpan );
var OK = make( 'input' ); OK.type = 'button';
if ( after ) after.parentNode.insertBefore( span, after.nextSibling ); else line.appendChild( span );
OK.value = button_label( 'wpOkUploadLbl', HotCat.messages.ok );
OK.onclick = this.accept.bind( this );
this.ok = OK;


var cancel = make( 'input' ); cancel.type = 'button';
this.normalLinks = null;
cancel.value = button_label( 'wpCancelUploadLbl', HotCat.messages.cancel );
this.undelLink = null;
cancel.onclick = this.cancel.bind( this );
this.catLink = null;
this.cancelButton = cancel;
} else {
if ( is_rtl ) span.dir = 'rtl';


var span = make( 'span' );
this.isAddCategory = false;
span.className = 'hotcatinput';
this.catLink = span.firstChild;
span.style.position = 'relative';
this.originalCategory = after;
// FF3.6: add the input field first, then the two absolutely positioned elements. Otherwise, FF3.6 may leave the
this.originalKey = ( key && key.length > 1 ) ? key.substr( 1 ) : null; // > 1 because it includes the leading bar
// suggestions and the selector at the right edge of the screen if display of the input field causes a re-layout
this.originalExists = !hasClass( this.catLink, 'new' );
// moving the form to the front of the next line.
// Create change and del links
span.appendChild( text );
this.makeLinkSpan();
if ( !this.originalExists && this.upDownLinks ) this.upDownLinks.style.display = 'none';


// IE8/IE9: put some text into this span (a0 is nbsp) and make sure it always stays on the
span.appendChild( this.linkSpan );
// 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.
this.originalHidden = is_hidden;
span.appendChild( make( '\xa0', true ) );
this.line = line;
span.style.whiteSpace = 'nowrap';
this.engine = HC.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;


if ( list ) { span.appendChild( list ); }
editors[ editors.length ] = this;
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 );
},
},


display: function ( evt ) {
makeLinkSpan: function () {
if ( this.isAddCategory && !onUpload ) {
this.normalLinks = make( 'span' );
// eslint-disable-next-line no-new
var lk = null;
new CategoryEditor( this.line, null, this.span, true ); // Create a new one
if ( this.originalCategory && this.originalCategory.length ) {
lk = make( 'a' );
lk.href = '#catlinks';
lk.onclick = this.remove.bind( this );
lk.appendChild( make( HC.links.remove, true ) );
lk.title = HC.tooltips.remove;
this.normalLinks.appendChild( make( ' ', true ) );
this.normalLinks.appendChild( lk );
}
}
if ( !commitButton && !onUpload ) {
if ( !HC.template_categories[ this.originalCategory ] ) {
for ( var i = 0; i < editors.length; i++ ) {
lk = make( 'a' );
if ( editors[ i ].state !== CategoryEditor.UNCHANGED ) {
lk.href = '#catlinks';
setMultiInput();
lk.onclick = this.open.bind( this );
break;
lk.appendChild( make( HC.links.change, true ) );
}
lk.title = HC.tooltips.change;
this.normalLinks.appendChild( make( ' ', true ) );
this.normalLinks.appendChild( lk );
if ( !noSuggestions && HC.use_up_down ) {
this.upDownLinks = make( 'span' );
lk = make( 'a' );
lk.href = '#catlinks';
lk.onclick = this.down.bind( this );
lk.appendChild( make( HC.links.down, true ) );
lk.title = HC.tooltips.down;
this.upDownLinks.appendChild( make( ' ', true ) );
this.upDownLinks.appendChild( lk );
lk = make( 'a' );
lk.href = '#catlinks';
lk.onclick = this.up.bind( this );
lk.appendChild( make( HC.links.up, true ) );
lk.title = HC.tooltips.up;
this.upDownLinks.appendChild( make( ' ', true ) );
this.upDownLinks.appendChild( lk );
this.normalLinks.appendChild( this.upDownLinks );
}
}
}
}
if ( !this.form ) {
this.linkSpan = make( 'span' );
this.makeForm();
this.linkSpan.className = 'noprint nopopups hotcatlink';
}
this.linkSpan.appendChild( this.normalLinks );
if ( this.list ) { this.list.style.display = 'none'; }
this.undelLink = make( 'span' );
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
this.undelLink.className = 'nopopups hotcatlink';
this.currentCategory = this.lastSavedCategory;
this.undelLink.style.display = 'none';
this.currentExists = this.lastSavedExists;
lk = make( 'a' );
this.currentHidden = this.lastSavedHidden;
lk.href = '#catlinks';
this.currentKey = this.lastSavedKey;
lk.onclick = this.restore.bind( this );
this.icon.src = armorUri( this.currentExists ? HotCat.existsYes : HotCat.existsNo );
lk.appendChild( make( HC.links.restore, true ) );
this.text.value = this.currentCategory + ( this.currentKey !== null ? '|' + this.currentKey : '' );
lk.title = HC.tooltips.restore;
this.originalState = this.state;
this.undelLink.appendChild( make( ' ', true ) );
this.lastInput = this.currentCategory;
this.undelLink.appendChild( lk );
this.inputExists = this.currentExists;
this.linkSpan.appendChild( this.undelLink );
this.state = this.state === CategoryEditor.UNCHANGED ? CategoryEditor.OPEN : CategoryEditor.CHANGE_PENDING;
},
this.lastSelection = { start: this.currentCategory.length, end: this.currentCategory.length };
 
this.showsList = false;
invokeSuggestions: function ( dont_autocomplete ) {
// Display the form
if ( this.engine && suggestionConfigs[ this.engine ] && suggestionConfigs[ this.engine ].temp && !dont_autocomplete ) this.engine = HC.suggestions; // Reset to a search upon input
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 );
this.text.focus();
this.text.readOnly = false;
checkMultiInput();
return result;
},


show: function ( evt, engine, readOnly ) {
this.state = CategoryEditor.CHANGE_PENDING;
var result = this.display( evt );
var self = this;
var v = this.lastSavedCategory;
window.setTimeout( function () {
if ( v.length === 0 ) { return result; }
self.textchange( dont_autocomplete );
this.text.readOnly = !!readOnly;
}, HC.suggest_delay );
this.engine = engine;
this.textchange( false, true ); // do autocompletion, force display of suggestions
forceRedraw();
return result;
},
},


open: function ( evt ) {
makeForm: function () {
return this.show( evt, ( this.engine && suggestionConfigs[ this.engine ].temp ) ? HotCat.suggestions : this.engine );
var form = make( 'form' );
},
form.method = 'POST';
 
form.onsubmit = this.accept.bind( this );
down: function ( evt ) {
this.form = form;
return this.show( evt, 'subcat', true );
var self = this;
},
var text = make( 'input' );
text.type = 'text';
text.size = HC.editbox_width;
if ( !noSuggestions ) {
// Be careful here to handle IME input. This is browser/OS/IME dependent, but basically there are two mechanisms:
// - Modern (DOM Level 3) browsers use compositionstart/compositionend events to signal composition; if the
//  composition is not canceled, there'll be a textInput event following. During a composition key events are
//  either all suppressed (FF/Gecko), or otherwise have keyDown === IME for all keys (Webkit).
//  - Webkit sends a textInput followed by keyDown === IME and a keyUp with the key that ended composition.
//  - Gecko doesn't send textInput but just a keyUp with the key that ended composition, without sending keyDown
//    first. Gecko doesn't send any keydown while IME is active.
// - Older browsers signal composition by keyDown === IME for the first and subsequent keys for a composition. The
//  first keyDown !== IME is certainly after the end of the composition. Typically, composition end can also be
//  detected by a keyDown IME with a keyUp of space, tab, escape, or return.
text.onkeyup = function ( evt ) {
var key = evt.keyCode || 0;
if ( self.ime && self.lastKey === IME && !self.usesComposition && ( key === TAB || key === RET || key === ESC || key === SPACE ) ) self.ime = false;
 
if ( self.ime ) return true;
 
if ( key === UP || key === DOWN || key === PGUP || key === PGDOWN ) {
// In case a browser doesn't generate keypress events for arrow keys...
if ( self.keyCount === 0 ) return self.processKey( evt );
} else {
if ( key === ESC && self.lastKey !== IME ) {
if ( !self.resetKeySelection() ) {
// No undo of key selection: treat ESC as "cancel".
self.cancel();
return;
}
}
// Also do this for ESC as a workaround for Firefox bug 524360
// https://bugzilla.mozilla.org/show_bug.cgi?id=524360
self.invokeSuggestions( key === BS || key === DEL || key === ESC );
}
return true;
};
text.onkeydown = function ( evt ) {
var key = evt.keyCode || 0;
self.lastKey = key;
self.keyCount = 0;
// DOM Level < 3 IME input
if ( !self.ime && key === IME && !self.usesComposition ) {
// self.usesComposition catches browsers that may emit spurious keydown IME after a composition has ended
self.ime = true;
} else if ( self.ime && key !== IME && !( key >= 16 && key <= 20 || key >= 91 && key <= 93 || key === 144 ) ) {
// Ignore control keys: ctrl, shift, alt, alt gr, caps lock, windows/apple cmd keys, num lock. Only the windows keys
// terminate IME (apple cmd doesn't), but they also cause a blur, so it's OK to ignore them here.
// Note: Safari 4 (530.17) propagates ESC out of an IME composition (observed at least on Win XP).
self.ime = false;
}
if ( self.ime ) return true;


up: function ( evt ) {
// Handle return explicitly, to override the default form submission to be able to check for ctrl
return this.show( evt, 'parentcat' );
if ( key === RET ) return self.accept( evt );
},


cancel: function () {
// Inhibit default behavior of ESC (revert to last real input in FF: we do that ourselves)
if ( this.isAddCategory && !onUpload ) {
return ( key === ESC ) ? evtKill( evt ) : true;
this.removeEditor(); // We added a new adder when opening
};
return;
// And handle continued pressing of arrow keys
}
text.onkeypress = function ( evt ) {
// Close, re-display link
self.keyCount++;
this.inactivate();
return self.processKey( evt );
this.form.style.display = 'none';
};
if ( this.catLink ) { this.catLink.style.display = ''; }
$( text ).on( 'focus', function () {
this.linkSpan.style.display = '';
makeActive( self );
this.state = this.originalState;
} );
this.currentCategory = this.lastSavedCategory;
// On IE, blur events are asynchronous, and may thus arrive after the element has lost the focus. Since IE
this.currentKey = this.lastSavedKey;
// can get the selection only while the element is active (has the focus), we may not always get the selection.
this.currentExists = this.lastSavedExists;
// Therefore, use an IE-specific synchronous event on IE...
this.currentHidden = this.lastSavedHidden;
// Don't test for text.selectionStart being defined;
if ( this.catLink ) {
$( text ).on(
if ( this.currentKey && this.currentKey.length > 0 ) {
( text.onbeforedeactivate !== undefined && text.createTextRange ) ? 'beforedeactivate' : 'blur',
this.catLink.title = this.currentKey;
this.saveView.bind( this ) );
} else {
// DOM Level 3 IME handling
this.catLink.title = '';
try {
// Setting lastKey = IME provides a fake keyDown for Gecko's single keyUp after a cmposition. If we didn't do this,
// cancelling a composition via ESC would also cancel and close the whole category input editor.
$( text ).on( 'compositionstart', function () {
self.lastKey = IME;
self.usesComposition = true;
self.ime = true;
} );
$( text ).on( 'compositionend', function () {
self.lastKey = IME;
self.usesComposition = true;
self.ime = false;
} );
$( text ).on( 'textInput', function () {
self.ime = false;
self.invokeSuggestions( false );
} );
} catch ( any ) {
// Just in case some browsers might produce exceptions with these DOM Level 3 events
}
}
$( text ).on( 'blur', function () {
self.usesComposition = false;
self.ime = false;
} );
}
}
if ( this.state === CategoryEditor.UNCHANGED ) {
this.text = text;
if ( this.catLink ) { this.catLink.style.backgroundColor = 'transparent'; }
} else {
if ( !onUpload ) {
try {
this.catLink.style.backgroundColor = HotCat.bg_changed;
} catch ( ex ) {}
}
}
checkMultiInput();
forceRedraw();
},


removeEditor: function () {
this.icon = make( 'img' );
if ( !newDOM ) {
var next = this.span.nextSibling;
if ( next ) { next.parentNode.removeChild( next ); }
}
this.span.parentNode.removeChild( this.span );
for ( var i = 0; i < editors.length; i++ ) {
if ( editors[ i ] === this ) {
editors.splice( i, 1 );
break;
}
}
checkMultiInput();
var self = this;
// eslint-disable-next-line no-delete-var
window.setTimeout( function () { delete self; }, 10 );
},


rollback: function ( evt ) {
var list = null;
this.undoLink.parentNode.removeChild( this.undoLink );
if ( !noSuggestions ) {
this.undoLink = null;
list = make( 'select' );
this.currentCategory = this.originalCategory;
list.onclick = function () {
this.currentKey = this.originalKey;
if ( self.highlightSuggestion( 0 ) ) self.textchange( false, true );
this.currentExists = this.originalExists;
};
this.currentHidden = this.originalHidden;
list.ondblclick = function ( e ) {
this.lastSavedCategory = this.originalCategory;
if ( self.highlightSuggestion( 0 ) ) self.accept( e );
this.lastSavedKey = this.originalKey;
};
this.lastSavedExists = this.originalExists;
list.onchange = function () {
this.lastSavedHidden = this.originalHidden;
self.highlightSuggestion( 0 );
this.state = CategoryEditor.UNCHANGED;
self.text.focus();
if ( !this.currentCategory || this.currentCategory.length === 0 ) {
};
// It was a newly added category. Remove the whole editor.
list.onkeyup = function ( evt ) {
this.removeEditor();
if ( evt.keyCode === ESC ) {
} else {
self.resetKeySelection();
// Redisplay the link...
self.text.focus();
this.catLink.removeChild( this.catLink.firstChild );
window.setTimeout( function () {
this.catLink.appendChild( make( this.currentCategory, true ) );
self.textchange( true );
this.catLink.href = wikiPagePath( HotCat.category_canonical + ':' + this.currentCategory );
}, HC.suggest_delay );
this.catLink.title = this.currentKey || '';
} else if ( evt.keyCode === RET ) {
this.catLink.className = this.currentExists ? '' : 'new';
self.accept( evt );
this.catLink.style.backgroundColor = 'transparent';
}
if ( this.upDownLinks ) { this.upDownLinks.style.display = this.currentExists ? '' : 'none'; }
};
checkMultiInput();
if ( !HC.fixed_search ) {
}
var engineSelector = make( 'select' );
return evtKill( evt );
for ( var key in suggestionConfigs ) {
},
if ( suggestionConfigs[ key ].show ) {
var opt = make( 'option' );
opt.value = key;
if ( key === this.engine ) opt.selected = true;


inactivate: function () {
opt.appendChild( make( suggestionConfigs[ key ].name, true ) );
if ( this.list ) { this.list.style.display = 'none'; }
engineSelector.appendChild( opt );
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
}
this.is_active = false;
}
},
engineSelector.onchange = function () {
 
self.engine = self.engineSelector.options[ self.engineSelector.selectedIndex ].value;
acceptCheck: function ( dontCheck ) {
self.text.focus();
this.sanitizeInput();
self.textchange( true, true ); // Don't autocomplete, force re-display of list
var value = this.text.value.split( '|' );
};
var key = null;
this.engineSelector = engineSelector;
if ( value.length > 1 ) { key = value[ 1 ]; }
}
var v = value[ 0 ].replace( /_/g, ' ' ).replace( /^\s+|\s+$/g, '' );
if ( HotCat.capitalizePageNames ) { v = capitalize( v ); }
this.lastInput = v;
v = replaceShortcuts( v, HotCat.shortcuts );
if ( v.length === 0 ) {
this.cancel();
return false;
}
}
if (
this.list = list;
!dontCheck && (
conf.wgNamespaceNumber === 14 && v === conf.wgTitle ||
HotCat.blacklist && HotCat.blacklist.test( v )
)
) {
this.cancel();
return false;
}
this.currentCategory = v;
this.currentKey = key;
this.currentExists = this.inputExists;
return true;
},


accept: function ( evt ) {
function button_label( id, defaultText ) {
// eslint-disable-next-line no-bitwise
var label = null;
this.noCommit = ( evtKeys( evt ) & 1 ) !== 0;
if (
var result = evtKill( evt );
onUpload &&
if ( this.acceptCheck() ) {
window.UFUI !== undefined &&
var toResolve = [ this ];
window.UIElements !== undefined &&
var original = this.currentCategory;
UFUI.getLabel instanceof Function
resolveMulti( toResolve, function ( resolved ) {
) {
if ( resolved[ 0 ].dab ) {
try {
showDab( resolved[ 0 ] );
label = UFUI.getLabel( id, true );
} else {
// Extract the plain text. IE doesn't know that Node.TEXT_NODE === 3
if ( resolved[ 0 ].acceptCheck( true ) ) {
while ( label && label.nodeType !== 3 ) label = label.firstChild;
resolved[ 0 ].commit(
} catch ( ex ) {
( resolved[ 0 ].currentCategory !== original ) ?
label = null;
HotCat.messages.cat_resolved.replace( /\$1/g, original ) :
null
);
}
}
}
} );
}
if ( !label || !label.data ) return defaultText;
 
return label.data;
}
}
return result;
},


close: function () {
// Do not use type 'submit'; we cannot detect modifier keys if we do
if ( !this.catLink ) {
var OK = make( 'input' );
// Create a catLink
OK.type = 'button';
this.catLink = make( 'a' );
OK.value = button_label( 'wpOkUploadLbl', HC.messages.ok );
this.catLink.appendChild( make( 'foo', true ) );
OK.onclick = this.accept.bind( this );
this.catLink.style.display = 'none';
this.ok = OK;
this.span.insertBefore( this.catLink, this.span.firstChild.nextSibling );
 
}
var cancel = make( 'input' );
this.catLink.removeChild( this.catLink.firstChild );
cancel.type = 'button';
this.catLink.appendChild( make( this.currentCategory, true ) );
cancel.value = button_label( 'wpCancelUploadLbl', HC.messages.cancel );
this.catLink.href = wikiPagePath( HotCat.category_canonical + ':' + this.currentCategory );
cancel.onclick = this.cancel.bind( this );
this.catLink.className = this.currentExists ? '' : 'new';
this.cancelButton = cancel;
this.lastSavedCategory = this.currentCategory;
 
this.lastSavedKey = this.currentKey;
var span = make( 'span' );
this.lastSavedExists = this.currentExists;
span.className = 'hotcatinput';
this.lastSavedHidden = this.currentHidden;
span.style.position = 'relative';
// Close form and redisplay category
span.appendChild( text );
this.inactivate();
 
this.form.style.display = 'none';
// Support: IE8, IE9
this.catLink.title = this.currentKey || '';
// Put some text into this span (a0 is nbsp) and make sure it always stays on the same
this.catLink.style.display = '';
// line as the input field, otherwise, IE8/9 miscalculates the height of the span and
if ( this.isAddCategory ) {
// then the engine selector may overlap the input field.
if ( onUpload ) {
span.appendChild( make( '\xa0', true ) );
// eslint-disable-next-line no-new
span.style.whiteSpace = 'nowrap';
new CategoryEditor( this.line, null, this.span, true ); // Create a new one
 
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 );
},
 
display: function ( evt ) {
if ( this.isAddCategory && !onUpload ) {
// eslint-disable-next-line no-new
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;
}
}
}
this.isAddCategory = false;
this.linkSpan.parentNode.removeChild( this.linkSpan );
this.makeLinkSpan();
this.span.appendChild( this.linkSpan );
}
}
if ( !this.undoLink ) {
if ( !this.form ) this.makeForm();
// Append an undo link.
 
var span = make( 'span' );
if ( this.list ) this.list.style.display = 'none';
var lk = make( 'a' ); lk.href = '#catlinks'; lk.onclick = this.rollback.bind( this );
 
lk.appendChild( make( HotCat.links.undo, true ) ); lk.title = HotCat.tooltips.undo;
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
span.appendChild( make( ' ', true ) );
span.appendChild( lk );
this.normalLinks.appendChild( span );
this.undoLink = span;
if ( !onUpload ) {
try {
this.catLink.style.backgroundColor = HotCat.bg_changed;
} catch ( ex ) {}
}
}
if ( this.upDownLinks ) { this.upDownLinks.style.display = this.lastSavedExists ? '' : 'none'; }
this.linkSpan.style.display = '';
this.state = CategoryEditor.CHANGED;
checkMultiInput();
forceRedraw();
},


commit: function () {
this.currentCategory = this.lastSavedCategory;
// Check again to catch problem cases after redirect resolution
this.currentExists = this.lastSavedExists;
if (
this.currentHidden = this.lastSavedHidden;
(
this.currentKey = this.lastSavedKey;
this.currentCategory === this.originalCategory &&
this.icon.src = ( this.currentExists ? HC.existsYes : HC.existsNo );
(
this.text.value = this.currentCategory + ( this.currentKey !== null ? '|' + this.currentKey : '' );
this.currentKey === this.originalKey ||
this.originalState = this.state;
this.currentKey === null && this.originalKey.length === 0
this.lastInput = this.currentCategory;
)
this.inputExists = this.currentExists;
) ||
this.state = this.state === CategoryEditor.UNCHANGED ? CategoryEditor.OPEN : CategoryEditor.CHANGE_PENDING;
conf.wgNamespaceNumber === 14 && this.currentCategory === conf.wgTitle ||
this.lastSelection = {
HotCat.blacklist && HotCat.blacklist.test( this.currentCategory )
start: this.currentCategory.length,
) {
end: this.currentCategory.length
this.cancel();
};
return;
this.showsList = false;
}
// Display the form
if ( commitButton || onUpload ) {
if ( this.catLink ) this.catLink.style.display = 'none';
this.close();
} else {
this.close();
var self = this;
initiateEdit( function ( failure ) { performChanges( failure, self ); }, function ( msg ) { alert( msg ); } );
}
},


remove: function ( evt ) {
this.linkSpan.style.display = 'none';
// eslint-disable-next-line no-bitwise
this.form.style.display = 'inline';
this.doRemove( evtKeys( evt ) & 1 );
this.ok.disabled = false;
return evtKill( evt );
// Kill the event before focussing, otherwise IE will kill the onfocus event!
var result = evtKill( evt );
this.text.focus();
this.text.readOnly = false;
checkMultiInput();
return result;
},
},


doRemove: function ( noCommit ) {
show: function ( evt, engine, readOnly ) {
if ( this.isAddCategory ) { // Empty input on adding a new category
var result = this.display( evt );
this.cancel();
var v = this.lastSavedCategory;
if ( !v.length ) return result;
 
this.text.readOnly = !!readOnly;
this.engine = engine;
this.textchange( false, true ); // do autocompletion, force display of suggestions
forceRedraw();
return result;
},
 
open: function ( evt ) {
return this.show( evt, ( this.engine && suggestionConfigs[ this.engine ].temp ) ? HC.suggestions : this.engine );
},
 
down: function ( evt ) {
return this.show( evt, 'subcat', true );
},
 
up: function ( evt ) {
return this.show( evt, 'parentcat' );
},
 
cancel: function () {
if ( this.isAddCategory && !onUpload ) {
this.removeEditor(); // We added a new adder when opening
return;
return;
}
}
if ( !commitButton && !onUpload ) {
// Close, re-display link
for ( var i = 0; i < editors.length; i++ ) {
this.inactivate();
if ( editors[ i ].state !== CategoryEditor.UNCHANGED ) {
this.form.style.display = 'none';
setMultiInput();
if ( this.catLink ) this.catLink.style.display = '';
break;
 
}
this.linkSpan.style.display = '';
this.state = this.originalState;
this.currentCategory = this.lastSavedCategory;
this.currentKey = this.lastSavedKey;
this.currentExists = this.lastSavedExists;
this.currentHidden = this.lastSavedHidden;
if ( this.catLink )
if ( this.currentKey && this.currentKey.length ) { this.catLink.title = this.currentKey; } else { this.catLink.title = ''; }
 
if ( this.state === CategoryEditor.UNCHANGED ) {
if ( this.catLink ) this.catLink.style.backgroundColor = 'transparent';
} else {
if ( !onUpload ) {
try {
this.catLink.style.backgroundColor = HC.bg_changed;
} catch ( ex ) {}
}
}
}
}
if ( commitButton ) {
checkMultiInput();
this.catLink.title = '';
forceRedraw();
this.catLink.style.cssText += '; text-decoration : line-through !important;';
},
try {
 
this.catLink.style.backgroundColor = HotCat.bg_changed;
removeEditor: function () {
} catch ( ex ) {}
if ( !newDOM ) {
this.originalState = this.state;
var next = this.span.nextSibling;
this.state = CategoryEditor.DELETED;
if ( next ) next.parentNode.removeChild( next );
this.normalLinks.style.display = 'none';
}
this.undelLink.style.display = '';
this.span.parentNode.removeChild( this.span );
checkMultiInput();
for ( var i = 0; i < editors.length; i++ ) {
} else {
if ( editors[ i ] === this ) {
if ( onUpload ) {
editors.splice( i, 1 );
// Remove this editor completely
break;
this.removeEditor();
} else {
this.originalState = this.state;
this.state = CategoryEditor.DELETED;
this.noCommit = noCommit || HotCat.del_needs_diff;
var self = this;
initiateEdit(
function ( failure ) { performChanges( failure, self ); },
function ( msg ) { self.state = self.originalState; alert( msg ); }
);
}
}
}
}
},
restore: function ( evt ) {
// Can occur only if we do have a commit button and are not on the upload form
this.catLink.title = this.currentKey || '';
this.catLink.style.textDecoration = '';
this.state = this.originalState;
if ( this.state === CategoryEditor.UNCHANGED ) {
this.catLink.style.backgroundColor = 'transparent';
} else {
try {
this.catLink.style.backgroundColor = HotCat.bg_changed;
} catch ( ex ) {}
}
this.normalLinks.style.display = '';
this.undelLink.style.display = 'none';
checkMultiInput();
checkMultiInput();
return evtKill( evt );
},
},


// Internal operations
rollback: function ( evt ) {
this.undoLink.parentNode.removeChild( this.undoLink );
this.undoLink = null;
this.currentCategory = this.originalCategory;
this.currentKey = this.originalKey;
this.currentExists = this.originalExists;
this.currentHidden = this.originalHidden;
this.lastSavedCategory = this.originalCategory;
this.lastSavedKey = this.originalKey;
this.lastSavedExists = this.originalExists;
this.lastSavedHidden = this.originalHidden;
this.state = CategoryEditor.UNCHANGED;
if ( !this.currentCategory || !this.currentCategory.length ) {
// It was a newly added category. Remove the whole editor.
this.removeEditor();
} else {
// Redisplay the link...
this.catLink.removeChild( this.catLink.firstChild );
this.catLink.appendChild( make( this.currentCategory, true ) );
this.catLink.href = wikiPagePath( HC.category_canonical + ':' + this.currentCategory );
this.catLink.title = this.currentKey || '';
this.catLink.className = this.currentExists ? '' : 'new';
this.catLink.style.backgroundColor = 'transparent';
if ( this.upDownLinks ) this.upDownLinks.style.display = this.currentExists ? '' : 'none';


selectEngine: function ( engineName ) {
checkMultiInput();
if ( !this.engineSelector ) { return; }
for ( var i = 0; i < this.engineSelector.options.length; i++ ) {
this.engineSelector.options[ i ].selected = this.engineSelector.options[ i ].value === engineName;
}
}
return evtKill( evt );
},
},


sanitizeInput: function () {
inactivate: function () {
var v = this.text.value || '';
if ( this.list ) this.list.style.display = 'none';
v = v.replace( /^(\s|_)+/, '' ); // Trim leading blanks and underscores
var re = new RegExp( '^(' + HotCat.category_regexp + '):' );
if ( re.test( v ) ) {
v = v.substring( v.indexOf( ':' ) + 1 ).replace( /^(\s|_)+/, '' );
}
if ( HotCat.capitalizePageNames ) { v = capitalize( v ); }
// Only update the input field if there is a difference. IE8 appears to reset the selection
// and place the cursor at the front upon reset, which makes our autocompletetion become a
// nuisance. FF and IE6 don't seem to have this problem.
if ( this.text.value !== null && this.text.value !== v ) { this.text.value = v; }
},


makeCall: function ( url, callbackObj, engine, queryKey, cleanKey ) {
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
var cb = callbackObj;
var e = engine;
var v = queryKey;
var z = cleanKey;
var thisObj = this;


function done() {
this.is_active = false;
cb.callsMade++;
},
if ( cb.callsMade === cb.nofCalls ) {
if ( cb.exists ) { cb.allTitles.exists = true; }
if ( cb.normalized ) { cb.allTitles.normalized = cb.normalized; }
if ( !cb.dontCache && !suggestionConfigs[ cb.engineName ].cache[ z ] ) {
suggestionConfigs[ cb.engineName ].cache[ z ] = cb.allTitles;
}
thisObj.text.readOnly = false;
if ( !cb.cancelled ) { thisObj.showSuggestions( cb.allTitles, cb.noCompletion, v, cb.engineName ); }
if ( cb === thisObj.callbackObj ) { thisObj.callbackObj = null; }
// eslint-disable-next-line no-delete-var
delete cb;
}
}


getJSON( {
acceptCheck: function ( dontCheck ) {
uri: url,
this.sanitizeInput();
success: function ( json ) {
var value = this.text.value.split( '|' );
var titles = e.handler( json, z );
var key = null;
if ( titles && titles.length > 0 ) {
if ( value.length > 1 ) key = value[ 1 ];
if ( cb.allTitles === null ) {
cb.allTitles = titles;
} else {
cb.allTitles = cb.allTitles.concat( titles );
}
if ( titles.exists ) { cb.exists = true; }
if ( titles.normalized ) { cb.normalized = titles.normalized; }
}
done();
},
error: function ( req ) {
if ( !req ) {
noSuggestions = true;
}
cb.dontCache = true;
done();
}
} );
},


callbackObj: null,
var v = value[ 0 ].replace( /_/g, ' ' ).replace( /^\s+|\s+$/g, '' );
if ( HC.capitalizePageNames ) v = capitalize( v );


textchange: function ( dont_autocomplete, force ) {
this.lastInput = v;
// Hide all other lists
v = replaceShortcuts( v, HC.shortcuts );
makeActive( this );
if ( !v.length ) {
// Get input value, omit sort key, if any
this.cancel();
this.sanitizeInput();
return false;
var v = this.text.value;
// Disregard anything after a pipe.
var pipe = v.indexOf( '|' );
if ( pipe >= 0 ) {
this.currentKey = v.substring( pipe + 1 );
v = v.substring( 0, pipe );
} else {
this.currentKey = null;
}
}
if ( this.lastInput === v && !force ) { return; } // No change
if ( !dontCheck && (
if ( this.lastInput !== v ) { checkMultiInput(); }
conf.wgNamespaceNumber === 14 && v === conf.wgTitle || HC.blacklist && HC.blacklist.test( v ) ) ) {
this.lastInput = v;
this.cancel();
this.lastRealInput = v;
return false;
 
// Mark blacklisted inputs.
this.ok.disabled = v.length > 0 && HotCat.blacklist && HotCat.blacklist.test( v );
 
if ( noSuggestions ) {
// No Ajax: just make sure the list is hidden
if ( this.list ) { this.list.style.display = 'none'; }
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
if ( this.icon ) { this.icon.style.display = 'none'; }
return;
}
}
this.currentCategory = v;
this.currentKey = key;
this.currentExists = this.inputExists;
return true;
},


if ( v.length === 0 ) { this.showSuggestions( [] ); return; }
accept: function ( evt ) {
var cleanKey = v.replace( /[\u200E\u200F\u202A-\u202E]/g, '' ).replace( wikiTextBlankRE, ' ' );
// eslint-disable-next-line no-bitwise
cleanKey = replaceShortcuts( cleanKey, HotCat.shortcuts );
this.noCommit = ( evtKeys( evt ) & 1 ) !== 0;
cleanKey = cleanKey.replace( /^\s+|\s+$/g, '' );
var result = evtKill( evt );
if ( cleanKey.length === 0 ) { this.showSuggestions( [] ); return; }
if ( this.acceptCheck() ) {
 
var toResolve = [ this ];
if ( this.callbackObj ) { this.callbackObj.cancelled = true; }
var original = this.currentCategory;
var engineName = suggestionConfigs[ this.engine ] ? this.engine : 'combined';
resolveMulti( toResolve, function ( resolved ) {
 
if ( resolved[ 0 ].dab ) {
dont_autocomplete = dont_autocomplete || suggestionConfigs[ engineName ].noCompletion;
showDab( resolved[ 0 ] );
if ( suggestionConfigs[ engineName ].cache[ cleanKey ] ) {
} else {
this.showSuggestions( suggestionConfigs[ engineName ].cache[ cleanKey ], dont_autocomplete, v, engineName );
if ( resolved[ 0 ].acceptCheck( true ) ) {
return;
resolved[ 0 ].commit(
( resolved[ 0 ].currentCategory !== original ) ?
HC.messages.cat_resolved.replace( /\$1/g, original ) :
null );
}
}
} );
}
}
 
return result;
var engines = suggestionConfigs[ engineName ].engines;
this.callbackObj =
{ allTitles: null, callsMade: 0, nofCalls: engines.length, noCompletion: dont_autocomplete, engineName: engineName };
this.makeCalls( engines, this.callbackObj, v, cleanKey );
},
},


makeCalls: function ( engines, cb, v, cleanKey ) {
close: function () {
for ( var j = 0; j < engines.length; j++ ) {
if ( !this.catLink ) {
var engine = suggestionEngines[ engines[ j ] ];
// Create a catLink
var url = conf.wgServer + conf.wgScriptPath + engine.uri.replace( /\$1/g, encodeURIComponent( cleanKey ) );
this.catLink = make( 'a' );
this.makeCall( url, cb, engine, v, cleanKey );
this.catLink.appendChild( make( 'foo', true ) );
this.catLink.style.display = 'none';
this.span.insertBefore( this.catLink, this.span.firstChild.nextSibling );
}
}
},
this.catLink.removeChild( this.catLink.firstChild );
 
this.catLink.appendChild( make( this.currentCategory, true ) );
showSuggestions: function ( titles, dontAutocomplete, queryKey, engineName ) {
this.catLink.href = wikiPagePath( HC.category_canonical + ':' + this.currentCategory );
this.text.readOnly = false;
this.catLink.className = this.currentExists ? '' : 'new';
this.dab = null;
this.lastSavedCategory = this.currentCategory;
this.showsList = false;
this.lastSavedKey = this.currentKey;
if ( !this.list ) { return; }
this.lastSavedExists = this.currentExists;
if ( noSuggestions ) {
this.lastSavedHidden = this.currentHidden;
if ( this.list ) { this.list.style.display = 'none'; }
// Close form and redisplay category
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
this.inactivate();
if ( this.icon ) { this.icon.style.display = 'none'; }
this.form.style.display = 'none';
this.inputExists = true; // Default...
this.catLink.title = this.currentKey || '';
return;
this.catLink.style.display = '';
if ( this.isAddCategory ) {
if ( onUpload ) {
// eslint-disable-next-line no-new
new CategoryEditor( this.line, null, this.span, true ); // Create a new one
}
this.isAddCategory = false;
this.linkSpan.parentNode.removeChild( this.linkSpan );
this.makeLinkSpan();
this.span.appendChild( this.linkSpan );
}
}
this.engineName = engineName;
if ( !this.undoLink ) {
if ( engineName ) {
// Append an undo link.
if ( !this.engineSelector ) { this.engineName = null; }
var span = make( 'span' );
} else {
var lk = make( 'a' );
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
lk.href = '#catlinks';
lk.onclick = this.rollback.bind( this );
lk.appendChild( make( HC.links.undo, true ) );
lk.title = HC.tooltips.undo;
span.appendChild( make( ' ', true ) );
span.appendChild( lk );
this.normalLinks.appendChild( span );
this.undoLink = span;
if ( !onUpload ) {
try {
this.catLink.style.backgroundColor = HC.bg_changed;
} catch ( ex ) {}
}
}
}
if ( queryKey ) {
if ( this.upDownLinks ) this.upDownLinks.style.display = this.lastSavedExists ? '' : 'none';
if ( this.lastInput.indexOf( queryKey ) !== 0 ) { return; }
 
if ( this.lastQuery && this.lastInput.indexOf( this.lastQuery ) === 0 && this.lastQuery.length > queryKey.length ) { return; }
this.linkSpan.style.display = '';
}
this.state = CategoryEditor.CHANGED;
this.lastQuery = queryKey;
checkMultiInput();
forceRedraw();
},


// Get current input text
commit: function () {
var v = this.text.value.split( '|' );
// Check again to catch problem cases after redirect resolution
var key = v.length > 1 ? '|' + v[ 1 ] : '';
if (
v = ( HotCat.capitalizePageNames ? capitalize( v[ 0 ] ) : v[ 0 ] );
(
var vNormalized = v;
this.currentCategory === this.originalCategory &&
var knownToExist = titles && titles.exists;
(
var i;
this.currentKey === this.originalKey ||
if ( titles ) {
this.currentKey === null && !this.originalKey.length
if ( titles.normalized && v.indexOf( queryKey ) === 0 ) {
)
// We got back a different normalization than what is in the input field
) ||
vNormalized = titles.normalized + v.substring( queryKey.length );
conf.wgNamespaceNumber === 14 && this.currentCategory === conf.wgTitle ||
}
HC.blacklist && HC.blacklist.test( this.currentCategory )
var vLow = vNormalized.toLowerCase();
) {
// Strip blacklisted categories
this.cancel();
if ( HotCat.blacklist ) {
return;
for ( i = 0; i < titles.length; i++ ) {
}
if ( HotCat.blacklist.test( titles[ i ] ) ) {
if ( commitButton || onUpload ) {
titles.splice( i, 1 );
this.close();
i--;
} else {
}
this.close();
}
var self = this;
}
initiateEdit( function ( failure ) {
titles.sort(
performChanges( failure, self );
function ( a, b ) {
}, function ( msg ) {
if ( a === b ) { return 0; }
alert( msg );
if ( a.indexOf( b ) === 0 ) { return 1; } // a begins with b: a > b
} );
if ( b.indexOf( a ) === 0 ) { return -1; } // b begins with a: a < b
}
// Opensearch may return stuff not beginning with the search prefix!
},
var prefixMatchA = ( a.indexOf( vNormalized ) === 0 ? 1 : 0 );
 
var prefixMatchB = ( b.indexOf( vNormalized ) === 0 ? 1 : 0 );
remove: function ( evt ) {
if ( prefixMatchA !== prefixMatchB ) { return prefixMatchB - prefixMatchA; }
// eslint-disable-next-line no-bitwise
// Case-insensitive prefix match!
this.doRemove( evtKeys( evt ) & 1 );
var aLow = a.toLowerCase(), bLow = b.toLowerCase();
return evtKill( evt );
prefixMatchA = ( aLow.indexOf( vLow ) === 0 ? 1 : 0 );
},
prefixMatchB = ( bLow.indexOf( vLow ) === 0 ? 1 : 0 );
 
if ( prefixMatchA !== prefixMatchB ) { return prefixMatchB - prefixMatchA; }
doRemove: function ( noCommit ) {
if ( a < b ) { return -1; }
if ( this.isAddCategory ) { // Empty input on adding a new category
if ( b < a ) { return 1; }
this.cancel();
return 0;
return;
}
}
);
if ( !commitButton && !onUpload ) {
// Remove duplicates and self-references
for ( var i = 0; i < editors.length; i++ ) {
for ( i = 0; i < titles.length; i++ ) {
if ( editors[ i ].state !== CategoryEditor.UNCHANGED ) {
if ( i + 1 < titles.length && titles[ i ] === titles[ i + 1 ] ||
setMultiInput();
conf.wgNamespaceNumber === 14 && titles[ i ] === conf.wgTitle
break;
) {
titles.splice( i, 1 );
i--;
}
}
}
}
}
}
if ( !titles || titles.length === 0 ) {
if ( commitButton ) {
if ( this.list ) { this.list.style.display = 'none'; }
this.catLink.title = '';
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
this.catLink.style.cssText += '; text-decoration : line-through !important;';
if ( engineName && suggestionConfigs[ engineName ] && !suggestionConfigs[ engineName ].temp ) {
try {
if ( this.icon ) { this.icon.src = armorUri( HotCat.existsNo ); }
this.catLink.style.backgroundColor = HC.bg_changed;
this.inputExists = false;
} catch ( ex ) {}
this.originalState = this.state;
this.state = CategoryEditor.DELETED;
this.normalLinks.style.display = 'none';
this.undelLink.style.display = '';
checkMultiInput();
} else {
if ( onUpload ) {
// Remove this editor completely
this.removeEditor();
} else {
this.originalState = this.state;
this.state = CategoryEditor.DELETED;
this.noCommit = noCommit || HC.del_needs_diff;
var self = this;
initiateEdit(
function ( failure ) {
performChanges( failure, self );
},
function ( msg ) {
self.state = self.originalState;
alert( msg );
} );
}
}
return;
}
}
},


var firstTitle = titles[ 0 ];
restore: function ( evt ) {
var completed = this.autoComplete( firstTitle, v, vNormalized, key, dontAutocomplete );
// Can occur only if we do have a commit button and are not on the upload form
var existing = completed || knownToExist || firstTitle === replaceShortcuts( v, HotCat.shortcuts );
this.catLink.title = this.currentKey || '';
if ( engineName && suggestionConfigs[ engineName ] && !suggestionConfigs[ engineName ].temp ) {
this.catLink.style.textDecoration = '';
this.icon.src = armorUri( existing ? HotCat.existsYes : HotCat.existsNo );
this.state = this.originalState;
this.inputExists = existing;
if ( this.state === CategoryEditor.UNCHANGED ) {
}
this.catLink.style.backgroundColor = 'transparent';
if ( completed ) {
} else {
this.lastInput = firstTitle;
try {
if ( titles.length === 1 ) {
this.catLink.style.backgroundColor = HC.bg_changed;
this.list.style.display = 'none';
} catch ( ex ) {}
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
return;
}
}
}
// (Re-)fill the list
this.normalLinks.style.display = '';
while ( this.list.firstChild ) { this.list.removeChild( this.list.firstChild ); }
this.undelLink.style.display = 'none';
for ( i = 0; i < titles.length; i++ ) {
checkMultiInput();
var opt = make( 'option' );
return evtKill( evt );
opt.appendChild( make( titles[ i ], true ) );
},
opt.selected = completed && ( i === 0 );
 
this.list.appendChild( opt );
// Internal operations
}
 
this.displayList();
selectEngine: function ( engineName ) {
if ( !this.engineSelector ) return;
for ( var i = 0; i < this.engineSelector.options.length; i++ ) this.engineSelector.options[ i ].selected = this.engineSelector.options[ i ].value === engineName;
},
},


displayList: function () {
sanitizeInput: function () {
this.showsList = true;
var v = this.text.value || '';
if ( !this.is_active ) {
v = v.replace( /^(\s|_)+/, '' ); // Trim leading blanks and underscores
this.list.style.display = 'none';
var re = new RegExp( '^(' + HC.category_regexp + '):' );
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
if ( re.test( v ) ) v = v.substring( v.indexOf( ':' ) + 1 ).replace( /^(\s|_)+/, '' );
return;
 
}
if ( HC.capitalizePageNames ) v = capitalize( v );
var nofItems = ( this.list.options.length > HotCat.list_size ? HotCat.list_size : this.list.options.length );
 
if ( nofItems <= 1 ) { nofItems = 2; }
// Only update the input field if there is a difference. Various browsers otherwise
this.list.size = nofItems;
// reset the selection and cursor position after each value re-assignment.
this.list.style.align = is_rtl ? 'right' : 'left';
if ( this.text.value !== null && this.text.value !== v ) this.text.value = v;
this.list.style.zIndex = 5;
},
this.list.style.position = 'absolute';
 
// Compute initial list position. First the height.
makeCall: function ( url, callbackObj, engine, queryKey, cleanKey ) {
var anchor = is_rtl ? 'right' : 'left';
var cb = callbackObj,
var listh = 0;
e = engine,
if ( this.list.style.display === 'none' ) {
v = queryKey,
// Off-screen display to get the height
z = cleanKey,
this.list.style.top = this.text.offsetTop + 'px';
thisObj = this;
this.list.style[ anchor ] = '-10000px';
this.list.style.display = '';
listh = this.list.offsetHeight;
this.list.style.display = 'none';
} else {
listh = this.list.offsetHeight;
}
// Approximate calculation of maximum list size
var maxListHeight = listh;
if ( nofItems < HotCat.list_size ) { maxListHeight = ( listh / nofItems ) * HotCat.list_size; }


function viewport( what ) {
function done() {
if ( is_webkit && !document.evaluate ) {
cb.callsMade++;
// Safari < 3.0
if ( cb.callsMade === cb.nofCalls ) {
return window[ 'inner' + what ];
if ( cb.exists ) cb.allTitles.exists = true;
}
 
var s = 'client' + what;
if ( cb.normalized ) cb.allTitles.normalized = cb.normalized;
if ( window.opera ) {
 
return document.body[ s ];
if ( !cb.dontCache && !suggestionConfigs[ cb.engineName ].cache[ z ] ) suggestionConfigs[ cb.engineName ].cache[ z ] = cb.allTitles;
 
thisObj.text.readOnly = false;
if ( !cb.cancelled ) thisObj.showSuggestions( cb.allTitles, cb.noCompletion, v, cb.engineName );
 
if ( cb === thisObj.callbackObj ) thisObj.callbackObj = null;
 
cb = undefined;
}
}
return ( document.documentElement ? document.documentElement[ s ] : 0 ) || document.body[ s ] || 0;
}
}
function scroll_offset( what ) {
 
var s = 'scroll' + what;
$.getJSON( url, function ( json ) {
var result = ( document.documentElement ? document.documentElement[ s ] : 0 ) || document.body[ s ] || 0;
var titles = e.handler( json, z );
if ( is_rtl && what === 'Left' ) {
if ( titles && titles.length ) {
// RTL inconsistencies.
if ( cb.allTitles === null ) cb.allTitles = titles; else cb.allTitles = cb.allTitles.concat( titles );
// FF: 0 at the far right, then increasingly negative values.
if ( titles.exists ) cb.exists = true;
// IE >= 8: 0 at the far right, then increasingly positive values.
if ( titles.normalized ) cb.normalized = titles.normalized;
// Webkit: scrollWidth - clientWidth at the far right, then down to zero.
// IE 7: like webkit; IE6: disabled in RTL anyway since too many problems.
// Opera: don't know...
if ( result < 0 ) { result = -result; }
if ( !is_webkit ) {
result = scroll_offset( 'Width' ) - viewport( 'Width' ) - result;
}
// Now all have webkit behavior, i.e. zero if at the leftmost edge.
}
}
return result;
done();
}
} ).fail( function ( req ) {
function position( node ) {
if ( !req ) noSuggestions = true;
// Stripped-down simplified position function. It's good enough for our purposes.
cb.dontCache = true;
if ( node.getBoundingClientRect ) {
done();
var box = node.getBoundingClientRect();
} );
return {
},
x: Math.round( box.left + scroll_offset( 'Left' ) ),
 
y: Math.round( box.top + scroll_offset( 'Top' ) )
callbackObj: null,
};
 
}
textchange: function ( dont_autocomplete, force ) {
var t = 0, l = 0;
// Hide all other lists
do {
makeActive( this );
t = t + ( node.offsetTop || 0 );
// Get input value, omit sort key, if any
l = l + ( node.offsetLeft || 0 );
this.sanitizeInput();
node = node.offsetParent;
var v = this.text.value;
} while ( node );
// Disregard anything after a pipe.
return {
var pipe = v.indexOf( '|' );
x: l,
if ( pipe >= 0 ) {
y: t
this.currentKey = v.substring( pipe + 1 );
};
v = v.substring( 0, pipe );
} else {
this.currentKey = null;
}
}
if ( this.lastInput === v && !force ) return; // No change
if ( this.lastInput !== v ) checkMultiInput();
this.lastInput = v;
this.lastRealInput = v;


var textPos = position( this.text );
// Mark blacklisted inputs.
var nl = 0;
this.ok.disabled = v.length && HC.blacklist && HC.blacklist.test( v );
var nt = 0;
 
var offset = 0;
if ( noSuggestions ) {
// Opera 9.5 somehow has offsetWidth = 0 here?? Use the next best value...
// No Ajax: just make sure the list is hidden
var textBoxWidth = this.text.offsetWidth || this.text.clientWidth;
if ( this.list ) this.list.style.display = 'none';
if ( this.engineName ) {
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
this.engineSelector.style.zIndex = 5;
if ( this.icon ) this.icon.style.display = 'none';
this.engineSelector.style.position = 'absolute';
return;
this.engineSelector.style.width = textBoxWidth + 'px';
// Figure out the height of this selector: display it off-screen, then hide it again.
if ( this.engineSelector.style.display === 'none' ) {
this.engineSelector.style[ anchor ] = '-10000px';
this.engineSelector.style.top = '0px';
this.engineSelector.style.display = '';
offset = this.engineSelector.offsetHeight;
this.engineSelector.style.display = 'none';
} else {
offset = this.engineSelector.offsetHeight;
}
this.engineSelector.style[ anchor ] = nl + 'px';
}
}
if ( textPos.y < maxListHeight + offset + 1 ) {
 
// The list might extend beyond the upper border of the page. Let's avoid that by placing it
if ( !v.length ) {
// below the input text field.
this.showSuggestions( [] );
nt = this.text.offsetHeight + offset + 1;
return;
if ( this.engineName ) { this.engineSelector.style.top = this.text.offsetHeight + 'px'; }
} else {
nt = -listh - offset - 1;
if ( this.engineName ) { this.engineSelector.style.top = -( offset + 1 ) + 'px'; }
}
}
this.list.style.top = nt + 'px';
var cleanKey = v.replace( /[\u200E\u200F\u202A-\u202E]/g, '' ).replace( wikiTextBlankRE, ' ' );
this.list.style.width = ''; // No fixed width (yet)
cleanKey = replaceShortcuts( cleanKey, HC.shortcuts );
this.list.style[ anchor ] = nl + 'px';
cleanKey = cleanKey.replace( /^\s+|\s+$/g, '' );
if ( this.engineName ) {
if ( !cleanKey.length ) {
this.selectEngine( this.engineName );
this.showSuggestions( [] );
this.engineSelector.style.display = '';
return;
}
}
this.list.style.display = 'block';
 
// Set the width of the list
if ( this.callbackObj ) this.callbackObj.cancelled = true;
if ( this.list.offsetWidth < textBoxWidth ) {
 
this.list.style.width = textBoxWidth + 'px';
var engineName = suggestionConfigs[ this.engine ] ? this.engine : 'combined';
 
dont_autocomplete = dont_autocomplete || suggestionConfigs[ engineName ].noCompletion;
if ( suggestionConfigs[ engineName ].cache[ cleanKey ] ) {
this.showSuggestions( suggestionConfigs[ engineName ].cache[ cleanKey ], dont_autocomplete, v, engineName );
return;
return;
}
}
// If the list is wider than the textbox: make sure it fits horizontally into the browser window
 
var scroll = scroll_offset( 'Left' );
var engines = suggestionConfigs[ engineName ].engines;
var view_w = viewport( 'Width' );
this.callbackObj = {
var w = this.list.offsetWidth;
allTitles: null,
var l_pos = position( this.list );
callsMade: 0,
var left = l_pos.x;
nofCalls: engines.length,
var right = left + w;
noCompletion: dont_autocomplete,
if ( left < scroll || right > scroll + view_w ) {
engineName: engineName
if ( w > view_w ) {
};
w = view_w;
this.makeCalls( engines, this.callbackObj, v, cleanKey );
this.list.style.width = w + 'px';
if ( is_rtl ) {
left = right - w;
} else {
right = left + w;
}
}
var relative_offset = 0;
if ( left < scroll ) {
relative_offset = scroll - left;
} else if ( right > scroll + view_w ) {
relative_offset = -( right - scroll - view_w );
}
if ( is_rtl ) { relative_offset = -relative_offset; }
if ( relative_offset !== 0 ) {
this.list.style[ anchor ] = ( nl + relative_offset ) + 'px';
}
}
},
},


autoComplete: function ( newVal, actVal, normalizedActVal, key, dontModify ) {
makeCalls: function ( engines, cb, v, cleanKey ) {
if ( newVal === actVal ) { return true; }
for ( var j = 0; j < engines.length; j++ ) {
if ( dontModify || this.ime || !this.canSelect() ) { return false; }
var engine = suggestionEngines[ engines[ j ] ];
// If we can't select properly or an IME composition is ongoing, autocompletion would be a major annoyance to the user.
var url = conf.wgServer + conf.wgScriptPath + engine.uri.replace( /\$1/g, encodeURIComponent( cleanKey ) );
if ( newVal.indexOf( actVal ) !== 0 ) {
this.makeCall( url, cb, engine, v, cleanKey );
// Maybe it'll work with the normalized value (NFC)?
if ( normalizedActVal && newVal.indexOf( normalizedActVal ) === 0 ) {
if ( this.lastRealInput === actVal ) { this.lastRealInput = normalizedActVal; }
actVal = normalizedActVal;
} else {
return false;
}
}
}
// Actual input is a prefix of the new text. Fill in new text, selecting the newly added suffix
// such that it can be easily removed by typing backspace if the suggestion is unwanted.
this.text.focus();
this.text.value = newVal + key;
this.setSelection( actVal.length, newVal.length );
return true;
},
},


canSelect: function () {
showSuggestions: function ( titles, dontAutocomplete, queryKey, engineName ) {
return this.text.setSelectionRange ||
this.text.readOnly = false;
this.text.createTextRange ||
this.dab = null;
typeof this.text.selectionStart !== 'undefined' &&
this.showsList = false;
typeof this.text.selectionEnd !== 'undefined';
if ( !this.list ) return;
},
if ( noSuggestions ) {
if ( this.list ) this.list.style.display = 'none';
 
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
 
if ( this.icon ) this.icon.style.display = 'none';


setSelection: function ( from, to ) {
this.inputExists = true; // Default...
// this.text must be focused (at least on IE)
return;
if ( !this.text.value ) { return; }
}
if ( this.text.setSelectionRange ) { // e.g. khtml
this.engineName = engineName;
this.text.setSelectionRange( from, to );
if ( engineName ) {
} else if ( typeof this.text.selectionStart !== 'undefined' ) {
if ( !this.engineSelector ) this.engineName = null;
if ( from > this.text.selectionStart ) {
} else {
this.text.selectionEnd = to;
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
this.text.selectionStart = from;
}
} else {
if ( queryKey ) {
this.text.selectionStart = from;
if ( this.lastInput.indexOf( queryKey ) ) return;
this.text.selectionEnd = to;
if ( this.lastQuery && this.lastInput.indexOf( this.lastQuery ) === 0 && this.lastQuery.length > queryKey.length ) return;
}
} else if ( this.text.createTextRange ) { // IE
var new_selection = this.text.createTextRange();
new_selection.move( 'character', from );
new_selection.moveEnd( 'character', to - from );
new_selection.select();
}
}
},
this.lastQuery = queryKey;


getSelection: function () {
// Get current input text
var from = 0, to = 0;
var v = this.text.value.split( '|' );
// this.text must be focused (at least on IE)
var key = v.length > 1 ? '|' + v[ 1 ] : '';
if ( !this.text.value ) {
v = ( HC.capitalizePageNames ? capitalize( v[ 0 ] ) : v[ 0 ] );
// No text.
var vNormalized = v;
} else if ( typeof this.text.selectionStart !== 'undefined' ) {
var knownToExist = titles && titles.exists;
from = this.text.selectionStart;
var i;
to = this.text.selectionEnd;
if ( titles ) {
} else if ( document.selection && document.selection.createRange ) { // IE
if ( titles.normalized && v.indexOf( queryKey ) === 0 ) {
var rng = document.selection.createRange().duplicate();
// We got back a different normalization than what is in the input field
if ( rng.parentElement() === this.text ) {
vNormalized = titles.normalized + v.substring( queryKey.length );
try {
}
var textRng = this.text.createTextRange();
var vLow = vNormalized.toLowerCase();
textRng.move( 'character', 0 );
// Strip blacklisted categories
textRng.setEndPoint( 'EndToEnd', rng );
if ( HC.blacklist ) {
// We're in a single-line input box: no need to care about IE's strange
for ( i = 0; i < titles.length; i++ ) {
// handling of line ends
if ( HC.blacklist.test( titles[ i ] ) ) {
to = textRng.text.length;
titles.splice( i, 1 );
textRng.setEndPoint( 'EndToStart', rng );
i--;
from = textRng.text.length;
}
} catch ( notFocused ) {
from = this.text.value.length; to = from; // At end of text
}
}
}
}
}
titles.sort(
return { start: from, end: to };
function ( a, b ) {
},
if ( a === b ) return 0;


saveView: function () {
if ( a.indexOf( b ) === 0 ) return 1;
this.lastSelection = this.getSelection();
// a begins with b: a > b
},
if ( b.indexOf( a ) === 0 ) return -1;
// b begins with a: a < b
// Opensearch may return stuff not beginning with the search prefix!
var prefixMatchA = ( a.indexOf( vNormalized ) === 0 ? 1 : 0 );
var prefixMatchB = ( b.indexOf( vNormalized ) === 0 ? 1 : 0 );
if ( prefixMatchA !== prefixMatchB ) return prefixMatchB - prefixMatchA;
 
// Case-insensitive prefix match!
var aLow = a.toLowerCase(),
bLow = b.toLowerCase();
prefixMatchA = ( aLow.indexOf( vLow ) === 0 ? 1 : 0 );
prefixMatchB = ( bLow.indexOf( vLow ) === 0 ? 1 : 0 );
if ( prefixMatchA !== prefixMatchB ) return prefixMatchB - prefixMatchA;
 
if ( a < b ) return -1;
 
if ( b < a ) return 1;


processKey: function ( evt ) {
return 0;
var dir = 0;
} );
switch ( this.lastKey ) {
// Remove duplicates and self-references
case UP:
for ( i = 0; i < titles.length; i++ ) {
dir = -1;
if (
break;
i + 1 < titles.length && titles[ i ] === titles[ i + 1 ] ||
case DOWN:
conf.wgNamespaceNumber === 14 && titles[ i ] === conf.wgTitle
dir = 1;
) {
break;
titles.splice( i, 1 );
case PGUP:
i--;
dir = -HotCat.list_size;
}
break;
}
case PGDOWN:
dir = HotCat.list_size;
break;
case ESC: // Inhibit default behavior (revert to last real input in FF: we do that ourselves)
return evtKill( evt );
}
}
if ( dir ) {
if ( !titles || !titles.length ) {
if ( this.list.style.display !== 'none' ) {
if ( this.list ) this.list.style.display = 'none';
// List is visible, so there are suggestions
 
this.highlightSuggestion( dir );
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
// Kill the event, otherwise some browsers (e.g., Firefox) may additionally treat an up-arrow
 
// as "place the text cursor at the front", which we don't want here.
if ( engineName && suggestionConfigs[ engineName ] && !suggestionConfigs[ engineName ].temp ) {
return evtKill( evt );
if ( this.icon ) this.icon.src = HC.existsNo;
} else if (
 
this.keyCount <= 1 &&
this.inputExists = false;
( !this.callbackObj || this.callbackObj.callsMade === this.callbackObj.nofCalls ) ) {
// If no suggestions displayed, get them, unless we're already getting them.
this.textchange();
}
}
return;
}
}
return true;
},


highlightSuggestion: function ( dir ) {
var firstTitle = titles[ 0 ];
if ( noSuggestions || !this.list || this.list.style.display === 'none' ) { return false; }
var completed = this.autoComplete( firstTitle, v, vNormalized, key, dontAutocomplete );
var curr = this.list.selectedIndex;
var existing = completed || knownToExist || firstTitle === replaceShortcuts( v, HC.shortcuts );
var tgt = -1;
if ( engineName && suggestionConfigs[ engineName ] && !suggestionConfigs[ engineName ].temp ) {
if ( dir === 0 ) {
this.icon.src = ( existing ? HC.existsYes : HC.existsNo );
if ( curr < 0 || curr >= this.list.options.length ) { return false; }
this.inputExists = existing;
tgt = curr;
} else {
tgt = curr < 0 ? 0 : curr + dir;
tgt = tgt < 0 ? 0 : tgt;
if ( tgt >= this.list.options.length ) { tgt = this.list.options.length - 1; }
}
}
if ( tgt !== curr || dir === 0 ) {
if ( completed ) {
if ( curr >= 0 && curr < this.list.options.length && dir !== 0 ) {
this.lastInput = firstTitle;
this.list.options[ curr ].selected = false;
if ( titles.length === 1 ) {
this.list.style.display = 'none';
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
 
return;
}
}
this.list.options[ tgt ].selected = true;
// Get current input text
var v = this.text.value.split( '|' );
var key = v.length > 1 ? '|' + v[ 1 ] : '';
var completed = this.autoComplete( this.list.options[ tgt ].text, this.lastRealInput, null, key, false );
if ( !completed || this.list.options[ tgt ].text === this.lastRealInput ) {
this.text.value = this.list.options[ tgt ].text + key;
if ( this.canSelect() ) {
this.setSelection( this.list.options[ tgt ].text.length, this.list.options[ tgt ].text.length );
}
}
this.lastInput = this.list.options[ tgt ].text;
this.inputExists = true; // Might be wrong if from a dab list...
if ( this.icon ) { this.icon.src = armorUri( HotCat.existsYes ); }
this.state = CategoryEditor.CHANGE_PENDING;
}
}
return true;
// (Re-)fill the list
while ( this.list.firstChild ) this.list.removeChild( this.list.firstChild );
 
for ( i = 0; i < titles.length; i++ ) {
var opt = make( 'option' );
opt.appendChild( make( titles[ i ], true ) );
opt.selected = completed && ( i === 0 );
this.list.appendChild( opt );
}
this.displayList();
},
},


resetKeySelection: function () {
displayList: function () {
if ( noSuggestions || !this.list || this.list.style.display === 'none' ) { return false; }
this.showsList = true;
var curr = this.list.selectedIndex;
if ( !this.is_active ) {
if ( curr >= 0 && curr < this.list.options.length ) {
this.list.style.display = 'none';
this.list.options[ curr ].selected = false;
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
// Get current input text
 
var v = this.text.value.split( '|' );
return;
var key = v.length > 1 ? '|' + v[ 1 ] : '';
// ESC is handled strangely by some browsers (e.g., FF); somehow it resets the input value before
// our event handlers ever get a chance to run.
var result = v[ 0 ] !== this.lastInput;
if ( v[ 0 ] !== this.lastRealInput ) {
this.text.value = this.lastRealInput + key;
result = true;
}
this.lastInput = this.lastRealInput;
return result;
}
}
return false;
var nofItems = ( this.list.options.length > HC.listSize ? HC.listSize : this.list.options.length );
}
if ( nofItems <= 1 ) nofItems = 2;


}; // end CategoryEditor.prototype
this.list.size = nofItems;
 
this.list.style.align = is_rtl ? 'right' : 'left';
function initialize() {
this.list.style.zIndex = 5;
// User configurations. Do this here, called from the onload handler, so that users can
this.list.style.position = 'absolute';
// override it easily in their own user script files by just declaring variables. JSconfig
// Compute initial list position. First the height.
// is some feature used at Wikimedia Commons.
var anchor = is_rtl ? 'right' : 'left';
var config = ( typeof JSconfig !== 'undefined' && JSconfig.keys ) ? JSconfig.keys : {};
var listh = 0;
HotCat.dont_add_to_watchlist =
if ( this.list.style.display === 'none' ) {
( typeof window.hotcat_dont_add_to_watchlist !== 'undefined' ?
// Off-screen display to get the height
!!window.hotcat_dont_add_to_watchlist :
this.list.style.top = this.text.offsetTop + 'px';
( typeof config.HotCatDontAddToWatchlist !== 'undefined' ?
this.list.style[ anchor ] = '-10000px';
config.HotCatDontAddToWatchlist :
this.list.style.display = '';
HotCat.dont_add_to_watchlist
listh = this.list.offsetHeight;
)
this.list.style.display = 'none';
);
} else {
HotCat.no_autocommit =
listh = this.list.offsetHeight;
( typeof window.hotcat_no_autocommit !== 'undefined' ?
}
!!window.hotcat_no_autocommit :
// Approximate calculation of maximum list size
( typeof config.HotCatNoAutoCommit !== 'undefined' ?
var maxListHeight = listh;
config.HotCatNoAutoCommit :
if ( nofItems < HC.listSize ) maxListHeight = ( listh / nofItems ) * HC.listSize;
HotCat.no_autocommit
 
)
function viewport( what ) {
);
if ( is_webkit && !document.evaluate ) {
HotCat.del_needs_diff =
// Safari < 3.0
( typeof window.hotcat_del_needs_diff !== 'undefined' ?
return window[ 'inner' + what ];
!!window.hotcat_del_needs_diff :
( typeof config.HotCatDelNeedsDiff !== 'undefined' ?
config.HotCatDelNeedsDiff :
HotCat.del_needs_diff
)
);
HotCat.suggest_delay = window.hotcat_suggestion_delay ||
config.HotCatSuggestionDelay ||
HotCat.suggest_delay;
HotCat.editbox_width = window.hotcat_editbox_width ||
config.HotCatEditBoxWidth ||
HotCat.editbox_width;
HotCat.suggestions = window.hotcat_suggestions ||
config.HotCatSuggestions ||
HotCat.suggestions;
if ( typeof HotCat.suggestions !== 'string' || !suggestionConfigs[ HotCat.suggestions ] ) {
HotCat.suggestions = 'combined';
}
HotCat.fixed_search =
( typeof window.hotcat_suggestions_fixed !== 'undefined' ?
!!window.hotcat_suggestions_fixed :
( typeof config.HotCatFixedSuggestions !== 'undefined' ?
config.HotCatFixedSuggestions :
HotCat.fixed_search
)
);
HotCat.single_minor =
( typeof window.hotcat_single_changes_are_minor !== 'undefined' ?
!!window.hotcat_single_changes_are_minor :
( typeof config.HotCatMinorSingleChanges !== 'undefined' ?
config.HotCatMinorSingleChanges :
HotCat.single_minor
)
);
HotCat.bg_changed = window.hotcat_changed_background ||
config.HotCatChangedBackground ||
HotCat.bg_changed;
HotCat.use_up_down =
( typeof window.hotcat_use_category_links !== 'undefined' ?
!!window.hotcat_use_category_links :
( typeof config.HotCatUseCategoryLinks !== 'undefined' ?
config.HotCatUseCategoryLinks :
HotCat.use_up_down
)
);
HotCat.list_size = window.hotcat_list_size ||
config.HotCatListSize ||
HotCat.list_size;
// Numeric input, make sure we have a numeric value
HotCat.list_size = parseInt( HotCat.list_size, 10 );
if ( isNaN( HotCat.list_size ) || HotCat.list_size < 5 ) { HotCat.list_size = 5; }
if ( HotCat.list_size > 15 ) { HotCat.list_size = 15; }
// Localize search engine names
if ( HotCat.engine_names ) {
for ( var key in HotCat.engine_names ) {
if ( suggestionConfigs[ key ] && HotCat.engine_names[ key ] ) {
suggestionConfigs[ key ].name = HotCat.engine_names[ key ];
}
}
var s = 'client' + what;
if ( window.opera ) return document.body[ s ];
return ( document.documentElement ? document.documentElement[ s ] : 0 ) || document.body[ s ] || 0;
}
}
}
function scroll_offset( what ) {
// Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]]
var s = 'scroll' + what;
is_rtl = hasClass( document.body, 'rtl' );
var result = ( document.documentElement ? document.documentElement[ s ] : 0 ) || document.body[ s ] || 0;
if ( !is_rtl ) {
if ( is_rtl && what === 'Left' ) {
if ( document.defaultView && document.defaultView.getComputedStyle ) { // Gecko etc.
// RTL inconsistencies.
is_rtl = document.defaultView.getComputedStyle( document.body, null ).getPropertyValue( 'direction' );
// FF: 0 at the far right, then increasingly negative values.
} else if ( document.body.currentStyle ) { // IE, has subtle differences to getComputedStyle
// IE >= 8: 0 at the far right, then increasingly positive values.
is_rtl = document.body.currentStyle.direction;
// Webkit: scrollWidth - clientWidth at the far right, then down to zero.
} else { // Not exactly right, but best effort
// Opera: don't know...
is_rtl = document.body.style.direction;
if ( result < 0 ) result = -result;
}
 
is_rtl = ( is_rtl === 'rtl' );
if ( !is_webkit ) result = scroll_offset( 'Width' ) - viewport( 'Width' ) - result;
}
}


function can_edit() {
// Now all have webkit behavior, i.e. zero if at the leftmost edge.
var container = null;
switch ( mw.config.get( 'skin' ) ) {
case 'cologneblue':
container = document.getElementById( 'quickbar' );
// Fall through
case 'standard':
case 'nostalgia':
if ( !container ) { container = document.getElementById( 'topbar' ); }
var lks = container.getElementsByTagName( 'a' );
for ( var i = 0; i < lks.length; i++ ) {
if ( param( 'title', lks[ i ].href ) === conf.wgPageName &&
param( 'action', lks[ i ].href ) === 'edit' ) { return true; }
}
}
return false;
return result;
default:
}
// all modern skins:
function position( node ) {
return document.getElementById( 'ca-edit' ) !== null;
// Stripped-down simplified position function. It's good enough for our purposes.
}
if ( node.getBoundingClientRect ) {
}
var box = node.getBoundingClientRect();
return {
x: Math.round( box.left + scroll_offset( 'Left' ) ),
y: Math.round( box.top + scroll_offset( 'Top' ) )
};
}
var t = 0,
l = 0;
do {
t += ( node.offsetTop || 0 );
l += ( node.offsetLeft || 0 );
node = node.offsetParent;
} while ( node );
return {
x: l,
y: t
};
}


function setup_upload() {
var textPos = position( this.text ),
onUpload = true;
nl = 0,
// Add an empty category bar at the end of the table containing the description, and change the onsubmit handler.
nt = 0,
var ip = document.getElementById( 'mw-htmlform-description' ) || document.getElementById( 'wpDestFile' );
offset = 0,
if ( !ip ) {
// Opera 9.5 somehow has offsetWidth = 0 here?? Use the next best value...
ip = document.getElementById( 'wpDestFile' );
textBoxWidth = this.text.offsetWidth || this.text.clientWidth;
while ( ip && ip.nodeName.toLowerCase() !== 'table' ) { ip = ip.parentNode; }
if ( this.engineName ) {
}
this.engineSelector.style.zIndex = 5;
if ( !ip ) { return; }
this.engineSelector.style.position = 'absolute';
var reupload = document.getElementById( 'wpForReUpload' );
this.engineSelector.style.width = textBoxWidth + 'px';
var destFile = document.getElementById( 'wpDestFile' );
// Figure out the height of this selector: display it off-screen, then hide it again.
if ( ( reupload && !!reupload.value ) ||
if ( this.engineSelector.style.display === 'none' ) {
( destFile && ( destFile.disabled || destFile.readOnly ) ) ) { return; } // re-upload form...
this.engineSelector.style[ anchor ] = '-10000px';
// Insert a table row with two fields (label and empty category bar)
this.engineSelector.style.top = '0';
var labelCell = make( 'td' );
this.engineSelector.style.display = '';
var lineCell = make( 'td' );
offset = this.engineSelector.offsetHeight;
// Create the category line
this.engineSelector.style.display = 'none';
catLine = make( 'div' );
} else {
catLine.className = 'catlinks';
offset = this.engineSelector.offsetHeight;
catLine.id = 'catlinks';
}
catLine.style.textAlign = is_rtl ? 'right' : 'left';
this.engineSelector.style[ anchor ] = nl + 'px';
// We'll be inside a table row. Make sure that we don't have margins or strange borders.
}
catLine.style.margin = '0';
if ( textPos.y < maxListHeight + offset + 1 ) {
catLine.style.border = 'none';
// The list might extend beyond the upper border of the page. Let's avoid that by placing it
lineCell.appendChild( catLine );
// below the input text field.
// Create the label
nt = this.text.offsetHeight + offset + 1;
var label = null;
if ( this.engineName ) this.engineSelector.style.top = this.text.offsetHeight + 'px';
if ( typeof UFUI !== 'undefined' &&
} else {
typeof UIElements !== 'undefined' &&
nt = -listh - offset - 1;
typeof UFUI.getLabel === 'function'
if ( this.engineName ) this.engineSelector.style.top = -( offset + 1 ) + 'px';
) {
}
try {
this.list.style.top = nt + 'px';
label = UFUI.getLabel( 'wpCategoriesUploadLbl' );
this.list.style.width = ''; // No fixed width (yet)
} catch ( ex ) {
this.list.style[ anchor ] = nl + 'px';
label = null;
if ( this.engineName ) {
this.selectEngine( this.engineName );
this.engineSelector.style.display = '';
}
this.list.style.display = 'block';
// Set the width of the list
if ( this.list.offsetWidth < textBoxWidth ) {
this.list.style.width = textBoxWidth + 'px';
return;
}
}
}
// If the list is wider than the textbox: make sure it fits horizontally into the browser window
if ( !label ) {
var scroll = scroll_offset( 'Left' );
labelCell.id = 'hotcatLabel';
var view_w = viewport( 'Width' );
labelCell.appendChild( make( HotCat.categories, true ) );
var w = this.list.offsetWidth;
} else {
var l_pos = position( this.list );
labelCell.id = 'hotcatLabelTranslated';
var left = l_pos.x;
labelCell.appendChild( label );
var right = left + w;
}
if ( left < scroll || right > scroll + view_w ) {
labelCell.className = 'mw-label';
if ( w > view_w ) {
labelCell.style.textAlign = 'right';
w = view_w;
labelCell.style.verticalAlign = 'middle';
this.list.style.width = w + 'px';
// Change the onsubmit handler
if ( is_rtl ) left = right - w; else right = left + w;
var form = document.getElementById( 'upload' ) || document.getElementById( 'mw-upload-form' );
}
if ( form ) {
var relative_offset = 0;
var newRow = ip.insertRow( -1 );
if ( left < scroll ) relative_offset = scroll - left; else if ( right > scroll + view_w ) relative_offset = -( right - scroll - view_w );
newRow.appendChild( labelCell );
 
newRow.appendChild( lineCell );
if ( is_rtl ) relative_offset = -relative_offset;
form.onsubmit = ( function ( oldSubmit ) {
 
return function () {
if ( relative_offset ) this.list.style[ anchor ] = ( nl + relative_offset ) + 'px';
var do_submit = true;
}
if ( oldSubmit ) {
},
if ( typeof oldSubmit === 'string' ) {
 
// eslint-disable-next-line no-eval
autoComplete: function ( newVal, actVal, normalizedActVal, key, dontModify ) {
do_submit = eval( oldSubmit );
if ( newVal === actVal ) return true;
} else if ( typeof oldSubmit === 'function' ) {
 
do_submit = oldSubmit.apply( form, arguments );
if ( dontModify || this.ime || !this.canSelect() ) return false;
}
}
if ( !do_submit ) {
return false;
}
closeForm();
// Copy the categories
var eb = document.getElementById( 'wpUploadDescription' ) ||
document.getElementById( 'wpDesc' );
var addedOne = false;
for ( var i = 0; i < editors.length; i++ ) {
var t = editors[ i ].currentCategory;
if ( !t ) { continue; }
var key = editors[ i ].currentKey;
var new_cat = '[[' + HotCat.category_canonical + ':' + t + ( key ? '|' + key : '' ) + ']]';
// Only add if not already present
var cleanedText = eb.value
.replace( /<!--(\s|\S)*?-->/g, '' )
.replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, '' );
if ( !find_category( cleanedText, t, true ) ) {
eb.value += '\n' + new_cat;
addedOne = true;
}
}
if ( addedOne ) {
// Remove "subst:unc" added by Flinfo if it didn't find categories
eb.value = eb.value.replace( /\{\{subst:unc\}\}/g, '' );
}
return true;
};
}( form.onsubmit ) );
}
}


var cleanedText = null;
// If we can't select properly or an IME composition is ongoing, autocompletion would be a major annoyance to the user.
if ( newVal.indexOf( actVal ) ) {
// Maybe it'll work with the normalized value (NFC)?
if ( normalizedActVal && newVal.indexOf( normalizedActVal ) === 0 ) {
if ( this.lastRealInput === actVal ) this.lastRealInput = normalizedActVal;


function isOnPage( span ) {
actVal = normalizedActVal;
if ( span.firstChild.nodeType !== Node.ELEMENT_NODE ) { return null; }
} else {
var catTitle = title( span.firstChild.getAttribute( 'href', 2 ) );
return false;
if ( !catTitle ) { return null; }
}
catTitle = catTitle.substr( catTitle.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
}
if ( HotCat.blacklist && HotCat.blacklist.test( catTitle ) ) { return null; }
// Actual input is a prefix of the new text. Fill in new text, selecting the newly added suffix
var result = { title: catTitle, match: [ '', '', '' ] };
// such that it can be easily removed by typing backspace if the suggestion is unwanted.
if ( pageText === null ) { return result; }
this.text.focus();
if ( cleanedText === null ) {
this.text.value = newVal + key;
cleanedText = pageText
this.setSelection( actVal.length, newVal.length );
.replace( /<!--(\s|\S)*?-->/g, '' )
return true;
.replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, '' );
},
}
result.match = find_category( cleanedText, catTitle, true );
return result;
}


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


function findByClass( scope, tag, className ) {
setSelection: function ( from, to ) {
var result = window.jQuery( scope ).find( tag + '.' + className );
// this.text must be focused (at least on IE)
return ( result && result.length ) ? result[ 0 ] : null;
if ( !this.text.value ) return;
}
if ( this.text.setSelectionRange ) { // e.g. khtml
 
this.text.setSelectionRange( from, to );
function setup( additionalWork ) {
} else if ( this.text.selectionStart !== undefined ) {
if ( initialized ) { return; }
if ( from > this.text.selectionStart ) {
initialized = true;
this.text.selectionEnd = to;
if ( setupTimeout ) {
this.text.selectionStart = from;
window.clearTimeout( setupTimeout );
} else {
setupTimeout = null;
this.text.selectionStart = from;
}
this.text.selectionEnd = to;
// Find the category bar, or create an empty one if there isn't one. Then add -/+- links after
}
// each category, and add the + link.
} else if ( this.text.createTextRange ) { // IE
catLine =
var new_selection = this.text.createTextRange();
// Special:Upload
new_selection.move( 'character', from );
catLine ||
new_selection.moveEnd( 'character', to - from );
document.getElementById( 'mw-normal-catlinks' );
new_selection.select();
var hiddenCats = document.getElementById( 'mw-hidden-catlinks' );
if ( !catLine ) {
var footer = null;
if ( !hiddenCats ) {
footer = findByClass( document, 'div', 'printfooter' );
if ( !footer ) { return; } // Don't know where to insert the category line
}
}
catLine = make( 'div' );
},
catLine.id = 'mw-normal-catlinks';
 
catLine.style.textAlign = is_rtl ? 'right' : 'left';
getSelection: function () {
// Add a label
var from = 0,
var label = make( 'a' );
to = 0;
label.href = conf.wgArticlePath.replace( '$1', 'Special:Categories' );
// this.text must be focused (at least on IE)
label.title = HotCat.categories;
if ( !this.text.value ) {
label.appendChild( make( HotCat.categories, true ) );
// No text.
catLine.appendChild( label );
} else if ( this.text.selectionStart !== undefined ) {
catLine.appendChild( make( ':', true ) );
from = this.text.selectionStart;
// Insert the new category line
to = this.text.selectionEnd;
var container = ( hiddenCats ? hiddenCats.parentNode : document.getElementById( 'catlinks' ) );
} else if ( document.selection && document.selection.createRange ) { // IE
if ( !container ) {
var rng = document.selection.createRange().duplicate();
container = make( 'div' );
if ( rng.parentElement() === this.text ) {
container.id = 'catlinks';
try {
footer.parentNode.insertBefore( container, footer.nextSibling );
var textRng = this.text.createTextRange();
}
textRng.move( 'character', 0 );
container.className = 'catlinks noprint';
textRng.setEndPoint( 'EndToEnd', rng );
container.style.display = '';
// We're in a single-line input box: no need to care about IE's strange
if ( !hiddenCats ) {
// handling of line ends
container.appendChild( catLine );
to = textRng.text.length;
} else {
textRng.setEndPoint( 'EndToStart', rng );
container.insertBefore( catLine, hiddenCats );
from = textRng.text.length;
} catch ( notFocused ) {
from = this.text.value.length;
to = from; // At end of text
}
}
}
}
} // end if catLine exists
return {
if ( is_rtl ) { catLine.dir = 'rtl'; }
start: from,
end: to
};
},


// Create editors for all existing categories
saveView: function () {
this.lastSelection = this.getSelection();
},


function createEditors( line, is_hidden ) {
processKey: function ( evt ) {
var i;
var dir = 0;
var cats = line.getElementsByTagName( 'li' );
switch ( this.lastKey ) {
if ( cats.length > 0 ) {
case UP:
newDOM = true; line = cats[ 0 ].parentNode;
dir = -1;
} else {
break;
cats = line.getElementsByTagName( 'span' );
case DOWN:
}
dir = 1;
// Copy cats, otherwise it'll also magically contain our added spans as it is a live collection!
break;
var copyCats = new Array( cats.length );
case PGUP:
for ( i = 0; i < cats.length; i++ ) { copyCats[ i ] = cats[ i ]; }
dir = -HC.listSize;
for ( i = 0; i < copyCats.length; i++ ) {
break;
var test = isOnPage( copyCats[ i ] );
case PGDOWN:
if ( test !== null && test.match !== null ) {
dir = HC.listSize;
// eslint-disable-next-line no-new
break;
new CategoryEditor( line, copyCats[ i ], test.title, test.match[ 2 ], is_hidden );
case ESC: // Inhibit default behavior (revert to last real input in FF: we do that ourselves)
return evtKill( evt );
}
if ( dir ) {
if ( this.list.style.display !== 'none' ) {
// List is visible, so there are suggestions
this.highlightSuggestion( dir );
// Kill the event, otherwise some browsers (e.g., Firefox) may additionally treat an up-arrow
// as "place the text cursor at the front", which we don't want here.
return evtKill( evt );
} else if (
this.keyCount <= 1 &&
( !this.callbackObj || this.callbackObj.callsMade === this.callbackObj.nofCalls )
) {
// If no suggestions displayed, get them, unless we're already getting them.
this.textchange();
}
}
}
}
return copyCats.length > 0 ? copyCats[ copyCats.length - 1 ] : null;
return true;
}
},
 
highlightSuggestion: function ( dir ) {
if ( noSuggestions || !this.list || this.list.style.display === 'none' ) return false;


var lastSpan = createEditors( catLine, false );
var curr = this.list.selectedIndex;
// Create one to add a new category
var tgt = -1;
// eslint-disable-next-line no-new
if ( dir === 0 ) {
new CategoryEditor( newDOM ? catLine.getElementsByTagName( 'ul' )[ 0 ] : catLine, null, null, lastSpan !== null, false );
if ( curr < 0 || curr >= this.list.options.length ) return false;
if ( !onUpload ) {
 
if ( pageText !== null && hiddenCats ) {
tgt = curr;
if ( is_rtl ) { hiddenCats.dir = 'rtl'; }
} else {
createEditors( hiddenCats, true );
tgt = curr < 0 ? 0 : curr + dir;
tgt = tgt < 0 ? 0 : tgt;
if ( tgt >= this.list.options.length ) tgt = this.list.options.length - 1;
}
}
// And finally add the "multi-mode" span. (Do this at the end, otherwise it ends up in the list above.)
if ( tgt !== curr || dir === 0 ) {
var enableMulti = make( 'span' );
if ( curr >= 0 && curr < this.list.options.length && dir !== 0 ) this.list.options[ curr ].selected = false;
enableMulti.className = 'noprint';
if ( is_rtl ) { enableMulti.dir = 'rtl'; }
catLine.insertBefore( enableMulti, catLine.firstChild.nextSibling );
enableMulti.appendChild( make( '\xa0', true ) ); // nbsp
multiSpan = make( 'span' );
enableMulti.appendChild( multiSpan );
multiSpan.innerHTML = '(<a>' + HotCat.addmulti + '</a>)';
var lk = multiSpan.getElementsByTagName( 'a' )[ 0 ];
lk.onclick = function ( evt ) { setMultiInput(); checkMultiInput(); return evtKill( evt ); };
lk.title = HotCat.multi_tooltip;
lk.style.cursor = 'pointer';
}
cleanedText = null;
if ( typeof additionalWork === 'function' ) { additionalWork(); }
setupCompleted.loaded(); // Trigger signal; execute registered functions
$( 'body' ).trigger( 'hotcatSetupCompleted' );
}


function setPage( json ) {
this.list.options[ tgt ].selected = true;
var startTime = null;
// Get current input text
if ( json && json.query ) {
var v = this.text.value.split( '|' );
if ( json.query.pages ) {
var key = v.length > 1 ? '|' + v[ 1 ] : '';
var page = json.query.pages[ conf.wgArticleId === 0 ? '-1' : String( conf.wgArticleId ) ];
var completed = this.autoComplete( this.list.options[ tgt ].text, this.lastRealInput, null, key, false );
if ( page ) {
if ( !completed || this.list.options[ tgt ].text === this.lastRealInput ) {
if ( page.revisions && page.revisions.length > 0 ) {
this.text.value = this.list.options[ tgt ].text + key;
// Revisions are sorted by revision ID, hence [ 0 ] is the one we asked for, and possibly there's a [ 1 ] if we're
if ( this.canSelect() ) this.setSelection( this.list.options[ tgt ].text.length, this.list.options[ tgt ].text.length );
// not on the latest revision (edit conflicts and such).
}
pageText = page.revisions[ 0 ][ '*' ];
this.lastInput = this.list.options[ tgt ].text;
if ( page.revisions[ 0 ].timestamp ) { pageTime = page.revisions[ 0 ].timestamp.replace( /\D/g, '' ); }
this.inputExists = true; // Might be wrong if from a dab list...
if ( page.revisions[ 0 ].revid ) { pageTextRevId = page.revisions[ 0 ].revid; }
if ( this.icon ) this.icon.src = HC.existsYes;
if ( page.revisions.length > 1 ) { conflictingUser = page.revisions[ 1 ].user; }
}
if ( page.lastrevid ) { lastRevId = page.lastrevid; }
if ( page.starttimestamp ) { startTime = page.starttimestamp.replace( /\D/g, '' ); }
pageWatched = typeof page.watched === 'string';
editToken = page.edittoken;
if ( page.langlinks && ( !json[ 'query-continue' ] || !json[ 'query-continue' ].langlinks ) ) {
// We have interlanguage links, and we got them all.
var re = '';
for ( var i = 0; i < page.langlinks.length; i++ ) {
re += ( i > 0 ? '|' : '' ) + page.langlinks[ i ].lang.replace( /([\\^$.?*+()])/g, '\\$1' );
}
if ( re.length > 0 ) {
interlanguageRE = new RegExp( '((^|\\n\\r?)(\\[\\[\\s*(' + re + ')\\s*:[^\\]]+\\]\\]\\s*))+$' );
}
}


}
this.state = CategoryEditor.CHANGE_PENDING;
}
}
// Siteinfo
return true;
if ( json.query.general ) {
},
// ResourceLoader's JSParser doesn't like .case, so override eslint.
 
// eslint-disable-next-line dot-notation
resetKeySelection: function () {
HotCat.capitalizePageNames = ( json.query.general[ 'case' ] === 'first-letter' );
if ( noSuggestions || !this.list || this.list.style.display === 'none' ) return false;
if ( json.query.general.time && !startTime ) { startTime = json.query.general.time.replace( /\D/g, '' ); }
 
}
var curr = this.list.selectedIndex;
serverTime = startTime;
if ( curr >= 0 && curr < this.list.options.length ) {
// Userinfo
this.list.options[ curr ].selected = false;
if ( json.query.userinfo && json.query.userinfo.options ) {
// Get current input text
watchCreate = !HotCat.dont_add_to_watchlist && json.query.userinfo.options.watchcreations === '1';
var v = this.text.value.split( '|' );
watchEdit = !HotCat.dont_add_to_watchlist && json.query.userinfo.options.watchdefault === '1';
var key = v.length > 1 ? '|' + v[ 1 ] : '';
minorEdits = json.query.userinfo.options.minordefault === 1;
// ESC is handled strangely by some browsers (e.g., FF); somehow it resets the input value before
// If the user has the "All edits are minor" preference enabled, we should honor that
// our event handlers ever get a chance to run.
// for single category changes, no matter what the site configuration is.
var result = v[ 0 ] !== this.lastInput;
if ( minorEdits ) { HotCat.single_minor = true; }
if ( v[ 0 ] !== this.lastRealInput ) {
this.text.value = this.lastRealInput + key;
result = true;
}
this.lastInput = this.lastRealInput;
return result;
}
}
return false;
}
}
}
}; // end CategoryEditor.prototype
 
function initialize() {
// User configurations. Do this here, called from the onload handler, so that users can
// override it easily in their own user script files by just declaring variables. JSconfig
// is some feature used at Wikimedia Commons.
var config = ( window.JSconfig !== undefined && JSconfig.keys ) ? JSconfig.keys : {};
HC.dont_add_to_watchlist = ( window.hotcat_dont_add_to_watchlist !== undefined ?
!!window.hotcat_dont_add_to_watchlist :
( config.HotCatDontAddToWatchlist !== undefined ? config.HotCatDontAddToWatchlist :
HC.dont_add_to_watchlist ) );
HC.no_autocommit = ( window.hotcat_no_autocommit !== undefined ?
!!window.hotcat_no_autocommit : ( config.HotCatNoAutoCommit !== undefined ?
config.HotCatNoAutoCommit :
HC.no_autocommit ) );
HC.del_needs_diff = ( window.hotcat_del_needs_diff !== undefined ?
!!window.hotcat_del_needs_diff :
( config.HotCatDelNeedsDiff !== undefined ?
config.HotCatDelNeedsDiff :
HC.del_needs_diff ) );
HC.suggest_delay = window.hotcat_suggestion_delay || config.HotCatSuggestionDelay || HC.suggest_delay;
HC.editbox_width = window.hotcat_editbox_width || config.HotCatEditBoxWidth || HC.editbox_width;
HC.suggestions = window.hotcat_suggestions || config.HotCatSuggestions || HC.suggestions;
if ( typeof HC.suggestions !== 'string' || !suggestionConfigs[ HC.suggestions ] ) HC.suggestions = 'combined';
 
HC.fixed_search = ( window.hotcat_suggestions_fixed !== undefined ?
!!window.hotcat_suggestions_fixed : ( config.HotCatFixedSuggestions !== undefined ?
config.HotCatFixedSuggestions : HC.fixed_search ) );
HC.single_minor = ( window.hotcat_single_changes_are_minor !== undefined ?
!!window.hotcat_single_changes_are_minor :
( config.HotCatMinorSingleChanges !== undefined ?
config.HotCatMinorSingleChanges :
HC.single_minor ) );
HC.bg_changed = window.hotcat_changed_background || config.HotCatChangedBackground || HC.bg_changed;
HC.use_up_down = ( window.hotcat_use_category_links !== undefined ?
!!window.hotcat_use_category_links :
( config.HotCatUseCategoryLinks !== undefined ?
config.HotCatUseCategoryLinks :
HC.use_up_down ) );
HC.listSize = window.hotcat_list_size || config.HotCatListSize || HC.listSize;
if ( conf.wgDBname !== 'commonswiki' ) HC.changeTag = config.HotCatChangeTag || '';


function createCommitForm() {
// The next whole shebang is needed, because manual tags get not submitted except of save
if ( commitForm ) { return; }
if ( HC.changeTag ) {
var formContainer = make( 'div' );
var eForm = document.editform,
formContainer.style.display = 'none';
catRegExp = new RegExp( '^\\[\\[(' + HC.category_regexp + '):' ),
document.body.appendChild( formContainer );
oldTxt;
formContainer.innerHTML =
// Returns true if minor change
'<form id="hotcatCommitForm" method="post" enctype="multipart/form-data" action="' +
var isMinorChange = function () {
conf.wgScript + '?title=' + encodeURIComponent( conf.wgPageName ) +
var newTxt = eForm.wpTextbox1;
'&action=submit">' +
if ( !newTxt ) return;
'<input type="hidden" name="wpTextbox1" />' +
newTxt = newTxt.value;
'<input type="hidden" name="model" value="wikitext" />' +
var oldLines = oldTxt.match( /^.*$/gm ),
'<input type="hidden" name="format" value="text/x-wiki" />' +
newLines = newTxt.match( /^.*$/gm ),
'<input type="hidden" name="wpSummary" value="" />' +
cArr; // changes
'<input type="checkbox" name="wpMinoredit" value="1" />' +
var except = function ( aArr, bArr ) {
'<input type="checkbox" name="wpWatchthis" value="1" />' +
var result = [],
'<input type="hidden" name="wpAutoSummary" value="" />' +
lArr, // larger
'<input type="hidden" name="wpEdittime" />' +
sArr; // smaller
'<input type="hidden" name="wpStarttime" />' +
if ( aArr.length < bArr.length ) {
'<input type="hidden" name="wpDiff" value="wpDiff" />' +
lArr = bArr;
'<input type="hidden" name="oldid" value="0" />' +
sArr = aArr;
'<input type="submit" name="hcCommit" value="hcCommit" />' +
} else {
'<input type="hidden" name="wpEditToken" />' +
lArr = aArr;
'<input type="hidden" name="wpUltimateParam" value="1" />' +
sArr = bArr;
'</form>';
}
commitForm = document.getElementById( 'hotcatCommitForm' );
for ( var i = 0; i < lArr.length; i++ ) {
}
var item = lArr[ i ];
var ind = $.inArray( item, sArr );
if ( ind === -1 ) result.push( item );
else sArr.splice( ind, 1 ); // don't check this item again
}
return result.concat( sArr );
};
cArr = except( oldLines, newLines );
if ( cArr.length ) {
cArr = $.grep( cArr, function ( c ) {
c = $.trim( c );
return ( c && !catRegExp.test( c ) );
} );
}
if ( !cArr.length ) {
oldTxt = newTxt;
return true;
}
};
 
if ( conf.wgAction === 'submit' && conf.wgArticleId && eForm && eForm.wpSummary && document.getElementById( 'wikiDiff' ) ) {
var sum = eForm.wpSummary,
sumA = eForm.wpAutoSummary;
if ( sum.value && sumA.value === HC.changeTag ) { // HotCat diff
// MD5 hash of the empty string, as HotCat edit is based on empty sum
sumA.value = sumA.value.replace( HC.changeTag, 'd41d8cd98f00b204e9800998ecf8427e' );
// Attr creation and event handling is not same in all (old) browsers so use $
var $ct = $( '<input type="hidden" name="wpChangeTags">' ).val( HC.changeTag );
$( eForm ).append( $ct );
oldTxt = eForm.wpTextbox1.value;
$( '#wpSave' ).one( 'click', function () {
if ( $ct.val() )
sum.value = sum.value.replace( ( HC.messages.using || HC.messages.prefix ), '' );


function getPage() {
} );
// We know we have an article here.
var removeChangeTag = function () {
if ( conf.wgArticleId === 0 ) {
$( eForm.wpTextbox1 ).add( sum ).one( 'input', function () {
// Doesn't exist yet.
window.setTimeout( function () {
if ( conf.wgNamespaceNumber === 2 ) {
if ( !isMinorChange() ) $ct.val( '' );
// Disable on non-existing User pages -- might be a global user page.
else removeChangeTag();
return;
}, 500 );
} );
};
removeChangeTag();
}
}
}
pageText = '';
pageTime = null;
setup( createCommitForm );
} else {
var url = conf.wgServer + conf.wgScriptPath + '/api.php?format=json&callback=HotCat.start&action=query&rawcontinue=&titles=' +
encodeURIComponent( conf.wgPageName ) +
'&prop=info%7Crevisions&rvprop=content%7Ctimestamp%7Cids&meta=siteinfo&rvlimit=1&rvstartid=' +
conf.wgCurRevisionId;
var s = make( 'script' );
s.src = armorUri( url );
s.type = 'text/javascript';
HotCat.start = function ( json ) { setPage( json ); setup( createCommitForm ); };
document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
setupTimeout = window.setTimeout( function () { setup( createCommitForm ); }, 4000 ); // 4 sec, just in case getting the wikitext takes longer.
}
}
}
// Numeric input, make sure we have a numeric value
HC.listSize = parseInt( HC.listSize, 10 );
if ( isNaN( HC.listSize ) || HC.listSize < 5 ) HC.listSize = 5;


function run() {
HC.listSize = Math.min( HC.listSize, 30 ); // Max size
if ( HotCat.started ) { return; }
HotCat.started = true;
loadTrigger.register( really_run );
}


function really_run() {
// Localize search engine names
initialize();
if ( HC.engine_names ) {
for ( var key in HC.engine_names )
if ( suggestionConfigs[ key ] && HC.engine_names[ key ] ) suggestionConfigs[ key ].name = HC.engine_names[ key ];


if ( !HotCat.upload_disabled && conf.wgNamespaceNumber === -1 && conf.wgCanonicalSpecialPageName === 'Upload' && conf.wgUserName ) {
setup_upload();
setup( function () {
// Check for state restoration once the setup is done otherwise, but before signalling setup completion
if ( typeof UploadForm !== 'undefined' &&
typeof UploadForm.previous_hotcat_state !== 'undefined' &&
UploadForm.previous_hotcat_state !== null ) {
UploadForm.previous_hotcat_state = setState( UploadForm.previous_hotcat_state );
}
} );
} else {
if ( !conf.wgIsArticle || conf.wgAction !== 'view' || param( 'diff' ) !== null || param( 'oldid' ) !== null || !can_edit() || HotCat.disable() ) { return; }
getPage();
}
}
}
// Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]]
 
is_rtl = hasClass( document.body, 'rtl' );
// Legacy stuff
if ( !is_rtl ) {
 
if ( document.defaultView && document.defaultView.getComputedStyle ) { // Gecko etc.
function closeForm() {
is_rtl = document.defaultView.getComputedStyle( document.body, null ).getPropertyValue( 'direction' );
// Close all open editors without redirect resolution and other asynchronous stuff.
} else if ( document.body.currentStyle ) { // IE, has subtle differences to getComputedStyle
for ( var i = 0; i < editors.length; i++ ) {
is_rtl = document.body.currentStyle.direction;
if ( editors[ i ].state === CategoryEditor.OPEN ) {
} else { // Not exactly right, but best effort
editors[ i ].cancel();
is_rtl = document.body.style.direction;
} else if ( editors[ i ].state === CategoryEditor.CHANGE_PENDING ) {
editors[ i ].sanitizeInput();
var value = editors[ i ].text.value.split( '|' );
var key = null;
if ( value.length > 1 ) { key = value[ 1 ]; }
var v = value[ 0 ].replace( /_/g, ' ' ).replace( /^\s+|\s+$/g, '' );
if ( v.length === 0 ) {
editors[ i ].cancel();
} else {
editors[ i ].currentCategory = v;
editors[ i ].currentKey = key;
editors[ i ].currentExists = this.inputExists;
editors[ i ].close();
}
}
}
is_rtl = ( is_rtl === 'rtl' );
}
}
}
}


function getState() {
function can_edit() {
var result = null;
var container = null;
switch ( mw.config.get( 'skin' ) ) {
case 'cologneblue':
container = document.getElementById( 'quickbar' );
/* fall through */
case 'standard':
case 'nostalgia':
if ( !container ) container = document.getElementById( 'topbar' );
var lks = container.getElementsByTagName( 'a' );
for ( var i = 0; i < lks.length; i++ ) {
if (
param( 'title', lks[ i ].href ) === conf.wgPageName &&
param( 'action', lks[ i ].href ) === 'edit'
) {
return true;
}
}
return false;
default:
// all modern skins:
return document.getElementById( 'ca-edit' ) !== null;
}
}
 
// Legacy stuff
function closeForm() {
// Close all open editors without redirect resolution and other asynchronous stuff.
for ( var i = 0; i < editors.length; i++ ) {
for ( var i = 0; i < editors.length; i++ ) {
var text = editors[ i ].currentCategory;
var edit = editors[ i ];
var key = editors[ i ].currentKey;
if ( edit.state === CategoryEditor.OPEN ) {
if ( text && text.length > 0 ) {
edit.cancel();
if ( key !== null ) { text += '|' + key; }
} else if ( edit.state === CategoryEditor.CHANGE_PENDING ) {
if ( result === null ) {
edit.sanitizeInput();
result = text;
var value = edit.text.value.split( '|' );
var key = null;
if ( value.length > 1 ) key = value[ 1 ];
var v = value[ 0 ].replace( /_/g, ' ' ).replace( /^\s+|\s+$/g, '' );
if ( !v.length ) {
edit.cancel();
} else {
} else {
result = result + '\n' + text;
edit.currentCategory = v;
edit.currentKey = key;
edit.currentExists = this.inputExists;
edit.close();
}
}
}
}
}
}
return result;
}
}


function setState( state ) {
function setup_upload() {
var cats = state.split( '\n' );
onUpload = true;
if ( cats.length === 0 ) { return null; }
// Add an empty category bar at the end of the table containing the description, and change the onsubmit handler.
if ( initialized && editors.length === 1 && editors[ 0 ].isAddCategory ) {
var ip = document.getElementById( 'mw-htmlform-description' ) || document.getElementById( 'wpDestFile' );
// Insert new spans and create new editors for them.
if ( !ip ) {
var newSpans = [];
ip = document.getElementById( 'wpDestFile' );
var before = editors.length === 1 ? editors[ 0 ].span : null;
while ( ip && ip.nodeName.toLowerCase() !== 'table' ) ip = ip.parentNode;
var i;
}
for ( i = 0; i < cats.length; i++ ) {
if ( !ip ) return;
if ( cats[ i ].length === 0 ) { continue; }
var reupload = document.getElementById( 'wpForReUpload' );
var destFile = document.getElementById( 'wpDestFile' );
if (
( reupload && !!reupload.value ) ||
( destFile && ( destFile.disabled || destFile.readOnly ) )
) {
return; // re-upload form...
}
// Insert a table row with two fields (label and empty category bar)
var labelCell = make( 'td' );
var lineCell = make( 'td' );
// Create the category line
catLine = make( 'div' );
catLine.className = 'catlinks';
catLine.id = 'catlinks';
catLine.style.textAlign = is_rtl ? 'right' : 'left';
// We'll be inside a table row. Make sure that we don't have margins or strange borders.
catLine.style.margin = '0';
catLine.style.border = 'none';
lineCell.appendChild( catLine );
// Create the label
var label = null;
if ( window.UFUI && window.UIElements && UFUI.getLabel instanceof Function ) {
try {
label = UFUI.getLabel( 'wpCategoriesUploadLbl' );
} catch ( ex ) {
label = null;
}
}
if ( !label ) {
labelCell.id = 'hotcatLabel';
labelCell.appendChild( make( HC.categories, true ) );
} else {
labelCell.id = 'hotcatLabelTranslated';
labelCell.appendChild( label );
}
labelCell.className = 'mw-label';
labelCell.style.textAlign = 'right';
labelCell.style.verticalAlign = 'middle';
// Change the onsubmit handler
var form = document.getElementById( 'upload' ) || document.getElementById( 'mw-upload-form' );
if ( form ) {
var newRow = ip.insertRow( -1 );
newRow.appendChild( labelCell );
newRow.appendChild( lineCell );
form.onsubmit = ( function ( oldSubmit ) {
return function () {
var do_submit = true;
if ( oldSubmit ) {
if ( typeof oldSubmit === 'string' ) {
// eslint-disable-next-line no-eval
do_submit = eval( oldSubmit );
} else if ( oldSubmit instanceof Function ) {
do_submit = oldSubmit.apply( form, arguments );
}
}
if ( !do_submit ) return false;
closeForm();
// Copy the categories
var eb = document.getElementById( 'wpUploadDescription' ) || document.getElementById( 'wpDesc' );
var addedOne = false;
for ( var i = 0; i < editors.length; i++ ) {
var t = editors[ i ].currentCategory;
if ( !t ) continue;
var key = editors[ i ].currentKey;
var new_cat = '[[' + HC.category_canonical + ':' + t + ( key ? '|' + key : '' ) + ']]';
// Only add if not already present
var cleanedText = eb.value
.replace( /<!--(\s|\S)*?-->/g, '' )
.replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, '' );
if ( !find_category( cleanedText, t, true ) ) {
eb.value += '\n' + new_cat;
addedOne = true;
}
}
if ( addedOne ) {
// Remove "subst:unc" added by Flinfo if it didn't find categories
eb.value = eb.value.replace( /\{\{subst:unc\}\}/g, '' );
}
return true;
};
}( form.onsubmit ) );
}
}
 
var cleanedText = null;
 
function isOnPage( span ) {
if ( span.firstChild.nodeType !== Node.ELEMENT_NODE ) return null;
 
var catTitle = title( span.firstChild.getAttribute( 'href' ) );
if ( !catTitle ) return null;
 
catTitle = catTitle.substr( catTitle.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
if ( HC.blacklist && HC.blacklist.test( catTitle ) ) return null;
 
var result = {
title: catTitle,
match: [ '', '', '' ]
};
if ( pageText === null ) return result;
 
if ( cleanedText === null ) {
cleanedText = pageText
.replace( /<!--(\s|\S)*?-->/g, '' )
.replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, '' );
}
result.match = find_category( cleanedText, catTitle, true );
return result;
}
 
var initialized = false;
var setupTimeout = null;
 
function findByClass( scope, tag, className ) {
var result = $( scope ).find( tag + '.' + className );
return ( result && result.length ) ? result[ 0 ] : null;
}
 
function setup( additionalWork ) {
if ( initialized ) return;
initialized = true;
if ( setupTimeout ) {
window.clearTimeout( setupTimeout );
setupTimeout = null;
}
// Find the category bar, or create an empty one if there isn't one. Then add -/+- links after
// each category, and add the + link.
catLine =
// Special:Upload
catLine ||
document.getElementById( 'mw-normal-catlinks' );
var hiddenCats = document.getElementById( 'mw-hidden-catlinks' );
if ( !catLine ) {
var footer = null;
if ( !hiddenCats ) {
footer = findByClass( document, 'div', 'printfooter' );
if ( !footer ) return; // Don't know where to insert the category line
}
catLine = make( 'div' );
catLine.id = 'mw-normal-catlinks';
catLine.style.textAlign = is_rtl ? 'right' : 'left';
// Add a label
var label = make( 'a' );
label.href = conf.wgArticlePath.replace( '$1', 'Special:Categories' );
label.title = HC.categories;
label.appendChild( make( HC.categories, true ) );
catLine.appendChild( label );
catLine.appendChild( make( ':', true ) );
// Insert the new category line
var container = ( hiddenCats ? hiddenCats.parentNode : document.getElementById( 'catlinks' ) );
if ( !container ) {
container = make( 'div' );
container.id = 'catlinks';
footer.parentNode.insertBefore( container, footer.nextSibling );
}
container.className = 'catlinks noprint';
container.style.display = '';
if ( !hiddenCats ) container.appendChild( catLine ); else container.insertBefore( catLine, hiddenCats );
} // end if catLine exists
if ( is_rtl ) catLine.dir = 'rtl';
 
// Create editors for all existing categories
 
function createEditors( line, is_hidden ) {
var i;
var cats = line.getElementsByTagName( 'li' );
if ( cats.length ) {
newDOM = true;
line = cats[ 0 ].parentNode;
} else {
cats = line.getElementsByTagName( 'span' );
}
// Copy cats, otherwise it'll also magically contain our added spans as it is a live collection!
var copyCats = new Array( cats.length );
for ( i = 0; i < cats.length; i++ ) copyCats[ i ] = cats[ i ];
for ( i = 0; i < copyCats.length; i++ ) {
var test = isOnPage( copyCats[ i ] );
if ( test !== null && test.match !== null ) {
// eslint-disable-next-line no-new
new CategoryEditor( line, copyCats[ i ], test.title, test.match[ 2 ], is_hidden );
}
}
return copyCats.length ? copyCats[ copyCats.length - 1 ] : null;
}
 
var lastSpan = createEditors( catLine, false );
// Create one to add a new category
// eslint-disable-next-line no-new
new CategoryEditor( newDOM ? catLine.getElementsByTagName( 'ul' )[ 0 ] : catLine, null, null, lastSpan !== null, false );
if ( !onUpload ) {
if ( pageText !== null && hiddenCats ) {
if ( is_rtl ) hiddenCats.dir = 'rtl';
createEditors( hiddenCats, true );
}
// And finally add the "multi-mode" span. (Do this at the end, otherwise it ends up in the list above.)
var enableMulti = make( 'span' );
enableMulti.className = 'noprint';
if ( is_rtl ) enableMulti.dir = 'rtl';
catLine.insertBefore( enableMulti, catLine.firstChild.nextSibling );
enableMulti.appendChild( make( '\xa0', true ) ); // nbsp
multiSpan = make( 'span' );
enableMulti.appendChild( multiSpan );
multiSpan.innerHTML = '(<a>' + HC.addmulti + '</a>)';
var lk = multiSpan.getElementsByTagName( 'a' )[ 0 ];
lk.onclick = function ( evt ) {
setMultiInput();
checkMultiInput();
return evtKill( evt );
};
lk.title = HC.multi_tooltip;
lk.style.cursor = 'pointer';
}
cleanedText = null;
if ( additionalWork instanceof Function ) additionalWork();
mw.hook( 'hotcat.ready' ).fire(); // Execute registered callback functions
$( 'body' ).trigger( 'hotcatSetupCompleted' );
}
 
function createCommitForm() {
if ( commitForm ) return;
var formContainer = make( 'div' );
formContainer.style.display = 'none';
document.body.appendChild( formContainer );
formContainer.innerHTML =
'<form id="hotcatCommitForm" method="post" enctype="multipart/form-data" action="' +
conf.wgScript + '?title=' + encodeURIComponent( conf.wgPageName ) + '&action=submit">' +
'<input type="hidden" name="wpTextbox1">' +
'<input type="hidden" name="model" value="wikitext">' +
'<input type="hidden" name="format" value="text/x-wiki">' +
'<input type="hidden" name="wpSummary" value="">' +
'<input type="checkbox" name="wpMinoredit" value="1">' +
'<input type="checkbox" name="wpWatchthis" value="1">' +
'<input type="hidden" name="wpAutoSummary" value="d41d8cd98f00b204e9800998ecf8427e">' +
'<input type="hidden" name="wpEdittime">' +
'<input type="hidden" name="wpStarttime">' +
'<input type="hidden" name="wpDiff" value="wpDiff">' +
'<input type="hidden" name="oldid" value="0">' +
'<input type="submit" name="hcCommit" value="hcCommit">' +
'<input type="hidden" name="wpEditToken">' +
'<input type="hidden" name="wpUltimateParam" value="1">' +
'<input type="hidden" name="wpChangeTags">' +
'<input type="hidden" value="ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ" name="wpUnicodeCheck">' +
'</form>';
commitForm = document.getElementById( 'hotcatCommitForm' );
}
 
function getPage() {
// We know we have an article here.
if ( !conf.wgArticleId ) {
// Doesn't exist yet. Disable on non-existing User pages -- might be a global user page.
if ( conf.wgNamespaceNumber === 2 ) return;
pageText = '';
pageTime = null;
setup( createCommitForm );
} else {
var url = conf.wgServer + conf.wgScriptPath + '/api.php?format=json&callback=HotCat.start&action=query&rawcontinue=&titles=' +
encodeURIComponent( conf.wgPageName ) +
'&prop=info%7Crevisions&rvprop=content%7Ctimestamp%7Cids&meta=siteinfo&rvlimit=1&rvstartid=' +
conf.wgCurRevisionId;
var s = make( 'script' );
s.src = url;
HC.start = function ( json ) {
setPage( json );
setup( createCommitForm );
};
document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
setupTimeout = window.setTimeout( function () {
setup( createCommitForm );
}, 4000 ); // 4 sec, just in case getting the wikitext takes longer.
}
}
 
function setState( state ) {
var cats = state.split( '\n' );
if ( !cats.length ) return null;
 
if ( initialized && editors.length === 1 && editors[ 0 ].isAddCategory ) {
// Insert new spans and create new editors for them.
var newSpans = [];
var before = editors.length === 1 ? editors[ 0 ].span : null;
var i;
for ( i = 0; i < cats.length; i++ ) {
if ( !cats[ i ].length ) continue;
var cat = cats[ i ].split( '|' );
var cat = cats[ i ].split( '|' );
var key = cat.length > 1 ? cat[ 1 ] : null;
var key = cat.length > 1 ? cat[ 1 ] : null;
cat = cat[ 0 ];
cat = cat[ 0 ];
var lk = make( 'a' ); lk.href = wikiPagePath( HotCat.category_canonical + ':' + cat );
var lk = make( 'a' );
lk.appendChild( make( cat, true ) );
lk.href = wikiPagePath( HC.category_canonical + ':' + cat );
lk.title = cat;
lk.appendChild( make( cat, true ) );
var span = make( 'span' );
lk.title = cat;
span.appendChild( lk );
var span = make( 'span' );
if ( i === 0 ) { catLine.insertBefore( make( ' ', true ), before ); }
span.appendChild( lk );
catLine.insertBefore( span, before );
if ( !i ) catLine.insertBefore( make( ' ', true ), before );
if ( before && i + 1 < cats.length ) { parent.insertBefore( make( ' | ', true ), before ); }
 
newSpans.push( { element: span, title: cat, key: key } );
catLine.insertBefore( span, before );
}
if ( before && i + 1 < cats.length ) parent.insertBefore( make( ' | ', true ), before );
// And change the last one...
 
if ( before ) {
newSpans.push( {
before.parentNode.insertBefore( make( ' | ', true ), before );
element: span,
}
title: cat,
for ( i = 0; i < newSpans.length; i++ ) {
key: key
// eslint-disable-next-line no-new
} );
new CategoryEditor( catLine, newSpans[ i ].element, newSpans[ i ].title, newSpans[ i ].key );
}
}
// And change the last one...
}
if ( before ) before.parentNode.insertBefore( make( ' | ', true ), before );
return null;
 
for ( i = 0; i < newSpans.length; i++ ) {
// eslint-disable-next-line no-new
new CategoryEditor( catLine, newSpans[ i ].element, newSpans[ i ].title, newSpans[ i ].key );
}
}
return null;
}
 
function getState() {
var result = null;
for ( var i = 0; i < editors.length; i++ ) {
var text = editors[ i ].currentCategory;
var key = editors[ i ].currentKey;
if ( text && text.length ) {
if ( key !== null ) text += '|' + key;
if ( result === null ) result = text; else result += '\n' + text;
}
}
return result;
}
 
function really_run() {
initialize();
 
if ( !HC.upload_disabled && conf.wgNamespaceNumber === -1 && conf.wgCanonicalSpecialPageName === 'Upload' && conf.wgUserName ) {
setup_upload();
setup( function () {
// Check for state restoration once the setup is done otherwise, but before signalling setup completion
if ( window.UploadForm && UploadForm.previous_hotcat_state ) UploadForm.previous_hotcat_state = setState( UploadForm.previous_hotcat_state );
} );
} else {
if ( !conf.wgIsArticle || conf.wgAction !== 'view' || param( 'diff' ) !== null || param( 'oldid' ) !== null || !can_edit() || HC.disable() ) return;
getPage();
}
}
 
function run() {
if ( HC.started ) return;
HC.started = true;
loadTrigger.register( really_run );
}
 
// Export legacy functions
window.hotcat_get_state = function () {
return getState();
};
window.hotcat_set_state = function ( state ) {
return setState( state );
};
window.hotcat_close_form = function () {
closeForm();
};
HC.runWhenReady = function ( callback ) {
// run user-registered code once HotCat is fully set up and ready.
mw.hook( 'hotcat.ready' ).add( callback );
};
 
// Make sure we don't get conflicts with AjaxCategories (core development that should one day
// replace HotCat).
mw.config.set( 'disableAJAXCategories', true );
 
// Run as soon as possible. This varies depending on MediaWiki version;
// window's 'load' event is always safe, but usually we can do better than that.
 
if ( conf.wgCanonicalSpecialPageName !== 'Upload' ) {
// Reload HotCat after (VE) edits (bug T103285)
mw.hook( 'postEdit' ).add( function () {
// Reset HotCat in case this is a soft reload (VE edit)
catLine = null;
editors = [];
initialized = false;
HC.started = false;
run();
} );
}
}


// Export legacy functions
// We can safely trigger just after user configuration is loaded.
window.hotcat_get_state = function () { return getState(); };
// Use always() instead of then() to also start HotCat if the user module has problems.
window.hotcat_set_state = function ( state ) { return setState( state ); };
$.when( mw.loader.using( 'user' ), $.ready ).always( run );
window.hotcat_close_form = function () { closeForm(); };
 
// Make sure we don't get conflicts with AjaxCategories (core development that should one day
// replace HotCat).
mw.config.set( 'disableAJAXCategories', true );
 
// Run as soon as possible. This varies depending on MediaWiki version;
// window's 'load' event is always safe, but usually we can do better than that.
 
var startHotCat;
if ( conf.wgCanonicalSpecialPageName !== 'Upload' ) {
// Use wikipage.content hook so that HotCat reloads after VE edits (bug T103285)
startHotCat = function () {
mw.hook( 'wikipage.content' ).add( function () {
// Reset HotCat in case this is a soft reload (VE edit)
catLine = null;
editors = [];
initialized = false;
HotCat.started = false;
run();
} );
};
} else {
// We're running on Special:Upload, where the 'wikipage.content' hook is fired for
// various previewed wikitext snippets, which shouldn't reload HotCat interface.
startHotCat = function () {
$( document ).ready( run );
};
}
// We can safely trigger just after user configuration is loaded. Also start HotCat if the user module fails to load.
// Avoid using Promise methods of mw.loader.using as those aren't supported in older
// MediaWiki versions.
mw.loader.using( 'user', startHotCat, startHotCat );
}( jQuery, mediaWiki ) );
}( jQuery, mediaWiki ) );
// </nowiki>
// </nowiki>