Jump to content

MediaWiki:Gadget-HotCat.js: Difference between revisions

updated from https://commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-HotCat.js&action=raw&ctype=text/javascript
(Upgraded to V2.36 to fix the "It appears your browser does not support Unicode" false-positive error.)
(updated from https://commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-HotCat.js&action=raw&ctype=text/javascript)
 
Line 1:
/**
// <nowiki>
HotCat V2.43
/*
HotCat V2.36
 
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
plugs into the upload form. Search engines to use for the suggestion list are configurable, and
can be selected interactively.
 
Documentation: https://commons.wikimedia.org/wiki/Help:Gadget-HotCat
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)
 
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:
 
<=1.26: https://commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-HotCat.js&oldid=211134664
*/
// <nowiki>
/* eslint-disable vars-on-top, one-var, camelcase, no-use-before-define, no-alert */
/* eslint-disable vars-on-top, one-var, camelcase, no-alert, curly */
/* global HotCat, mediaWiki, UFUI, JSconfig, UploadForm */
/* global jQuery, mediaWiki, UFUI, JSconfig, UploadForm */
/* jslint strict:false, nonew:false, bitwise:true */
( function ( $, mw ) {
// Don't use mw.config.get() as that takes a copy of the config, and so doesn't
// account for values changing, e.g. wgCurRevisionId after a VE edit
var
var conf = mw.config.values;
conf = $.extend( {}, mw.config.values, {
// when running on mobile domain - do not use wgServer.
wgServer: window.location.host.indexOf('.m.') > -1 ?
'//' + window.location.host : mw.config.get( 'wgServer' )
} );
 
// Guard against double inclusions (in old IE/Opera element ids become window properties)
if (
if ( ( window.HotCat && !window.HotCat.nodeName ) ||
// Guard against double inclusions (in old IE/Opera element ids become window properties)
conf.wgAction === 'edit' ) // Not on edit mode
( window.HotCat && !window.HotCat.nodeName ) ||
// Not on edit pages
conf.wgAction === 'edit'
 
) {
return;
}
 
// Configuration stuff.
var HC = window.HotCat = {
// Localize these messages to the main language of your wiki.
messages: {
cat_removed: 'removed [[Category:$1]]',
Line 75 ⟶ 74:
// see localization hook below.
multi_error: 'Could not retrieve the page text from the server. Therefore, your category changes ' +
'cannot be saved. We apologize for the inconvenience.',
// 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
Line 94 ⟶ 93:
// 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!
links: {
links: { change: '(±)', remove: '(\u2212)', add: '(+)', restore: '(×)', undo: '(×)', down: '(\u2193)', up: '(\u2191)' },
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
tooltips: {
Line 115 ⟶ 123:
return (
ns < 0 || // Special pages; Special:Upload is handled differently
ns === 10 || // Templates
ns === 828 || // Module (Lua)
ns === 8 || // MediaWiki
ns === 6 && !conf.wgArticleId === 0 || // Non-existing file pages
ns === 2 && /\.(js|css)$/.test( conf.wgTitle ) || // User scripts
nsIds &&
( ns === nsIds.creator ||
ns === nsIds.timedtext ||
ns === nsIds.institution ) );
)
);
},
// A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
// If not, set it to null.
uncat_regexp: /\{\{\s*([Uu]ncat(egori[sz]ed( image)?)?|[Nn]ocat|[Nn]eedscategory)ncategorized\s*[^}]*\}\}\s*(<!--.*?-->\s*)?/g,
// 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',
Line 146 ⟶ 152:
parentcat: 'Parent categories'
},
 
// Set to false if your wiki has case-sensitive page names. MediaWiki has two modes: either the first letter
// Override the decision of whether HotCat should help users by automatically
// of a page is automatically capitalized ("first-letter"; Category:aa === Category:Aa), or it isn't
// capitalising the title in the user input text if the wiki has case-sensitive page names.
// ("case-sensitive"; Category:aa !== Category:Aa). It doesn't currently have a fully case-insensitive mode
// Basically, this will make an API query to check the MediaWiki configuration and HotCat then sets
// (which would mean Category:aa === Category:Aa === Category:AA === Category:aA)
// this to true for most wikis, and to false on Wiktionary.
// HotCat tries to set this correctly automatically using an API query. It's still a good idea to manually
//
// configure it correctly; either directly here if you copied HotCat, or in the local configuration file
// You can set this directly if there is a problem with it. For example, Georgian Wikipedia (kawiki),
// MediaWiki:Gadget-HotCat.js/local_defaults if you hotlink to the Commons-version, to ensure it is set even
// is known to have different capitalisation logic between MediaWiki PHP and JavaScript. As such, automatic
// if that API query should fail for some strange reason.
// case changes in JavaScript by HotCat would be wrong.
capitalizePageNames: true,
capitalizePageNames: null,
// If upload_disabled is true, HotCat will not be used on the Upload form.
upload_disabled: false,
Line 165 ⟶ 172:
// Stuff changeable by users:
// Background for changed categories in multi-edit mode. Default is a very light salmon pink.
bg_changed: '#F8CCB0FCA',
// If true, HotCat will never automatically submit changes. HotCat will only open an edit page with
// the changes; users must always save explicitly.
Line 185 ⟶ 192:
use_up_down: true,
// Default list size
list_sizelistSize: 510,
// If true, single category changes are marked as minor edits. If false, they're not.
single_minor: true,
Line 194 ⟶ 201:
shortcuts: null,
addShortcuts: function ( map ) {
if ( !map ) { return; }
window.HotCat.shortcuts = window.HotCat.shortcuts || {};
for ( var k in map ) {
if ( !map.hasOwnProperty( k ) || typeof k !== 'string' ) { continue; }
 
var v = map[ k ];
if ( typeof v !== 'string' ) { continue; }
 
k = k.replace( /^\s+|\s+$/g, '' );
v = v.replace( /^\s+|\s+$/g, '' );
if ( !k.length === 0 || !v.length === 0 ) { continue; }
 
window.HotCat.shortcuts[ k ] = v;
}
Line 212 ⟶ 222:
var ua = navigator.userAgent.toLowerCase();
var is_webkit = /applewebkit\/\d+/.test( ua ) && ua.indexOf( 'spoofer' ) < 0;
var cat_prefix = null;
// And even more compatbility. HotCat was developed without jQuery, and anyway current jQuery
var noSuggestions = false;
// (1.7.1) doesn't seem to support in jquery.getJSON() or jQuery.ajax() the automatic
// switching from GET to POST requests if the query arguments would make the uri too long.
// (IE has a hard limit of 2083 bytes, and the servers may have limits around 4 or 8kB.)
// Anyway, HotCat is supposed to run on wikis without jQuery, so we'd have to supply some
// ajax routines ourselves in any case. We can't rely on the old sajax_init_object(), newer
// MW versions (>= 1.19) might not have it.
var getJSON = ( function () {
function getRequest() {
var request = null;
try {
request = new window.XMLHttpRequest();
} catch ( anything ) {
if ( window.ActiveXObject ) {
try {
request = new window.ActiveXObject( 'Microsoft.XMLHTTP' );
} catch ( any ) {
}
} // end if IE
} // end try-catch
return request;
}
 
return function LoadTrigger( settingsneeded ) {
// Define methods in a closure so that self reference is available,
var req = getRequest();
// also allows method calls to be detached.
if ( !req && settings && settings.error ) { settings.error( req ); }
var self = this;
if ( !req || !settings || !settings.uri ) { return req; }
self.queue = [];
// eslint-disable-next-line no-use-before-define
self.needed = needed;
var uri = armorUri( settings.uri );
self.register = function ( callback ) {
var args = settings.data || null;
if ( self.needed <= 0 ) callback(); // Execute directly
var method;
else self.queue.push( callback );
if ( args && uri.length + args.length + 1 > 2000 ) {
};
// We lose caching, but at least we can make the request
self.loaded = function () {
method = 'POST';
self.needed--;
req.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
}if else( self.needed === 0 ) {
// Run queued callbacks once
method = 'GET';
iffor ( argsvar )i {= uri0; +=i '?'< self.queue.length; i++ args;) }self.queue[ i ]();
argsself.queue = null[];
}
req.open( method, uri, true );
req.onreadystatechange = function () {
if ( req.readyState !== 4 ) { return; }
if ( req.status !== 200 || !req.responseText || !( /^\s*[{[]/.test( req.responseText ) ) ) {
if ( settings.error ) { settings.error( req ); }
} else {
// 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 ) {
// Avoid protocol-relative URIs, IE7 has a bug with them in Ajax calls
if ( uri.length >= 2 && uri.substring( 0, 2 ) === '//' ) { return document.location.protocol + uri; }
return uri;
}
 
// Used to delay running the HotCat setup until /local_defaults and localizations have been loaded.
function LoadTrigger( needed ) {
this.queue = [];
this.toLoad = needed;
}
LoadTrigger.prototype = {
register: function ( callback ) {
if ( this.toLoad <= 0 ) {
callback(); // Execute directly
} else {
this.queue[ this.queue.length ] = callback;
}
},
 
loaded: function () {
if ( this.toLoad > 0 ) {
this.toLoad--;
if ( this.toLoad === 0 ) {
// Run queued callbacks once
for ( var i = 0; i < this.queue.length; i++ ) { this.queue[ i ](); }
this.queue = [];
}
}
}
 
};
 
var setupCompleted = new LoadTrigger( 1 );
// Used to run user-registered code once HotCat is fully set up and ready.
HotCat.runWhenReady = function ( callback ) { setupCompleted.register( callback ); };
 
var loadTrigger = new LoadTrigger( 2 );
// Used to delay running the HotCat setup until /local_defaults and localizations have been loaded.
 
function load( uri, callback ) {
var head = document.getElementsByTagName( 'head' )[ 0 ];
var s = document.createElement( 'script' );
s.setAttribute( 'src', armorUri(= uri ) );
var called = false;
s.setAttribute( 'type', 'text/javascript' );
var done = false;
 
s.onload = s.onerror = function afterLoad() {
if ( done!called )&& {callback return;) }{
done called = true;
callback();
s.onload = s.onreadystatechange = s.onerror = null; // Properly clean up to avoid memory leaks in IE
}
if ( head && s.parentNode ) { head.removeChild( s ); }
if ( s.parentNode ) {
loadTrigger.loaded();
s.parentNode.removeChild( s );
}
 
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();
}
};
document.head.appendChild( s );
s.onerror = afterLoad; // Clean up, but otherwise ignore errors
head.insertBefore( s, head.firstChild ); // appendChild may trigger bugs in IE6 here
}
 
function loadJS( page, callback ) {
load( conf.wgServer + conf.wgScript + '?title=' + encodeURIComponent( page ) + '&action=raw&ctype=text/javascript', callback );
}
 
function loadURI( href, callback ) {
var url = href;
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 load( url.substring( 0, 1callback ) === '/' ) {;
url = conf.wgServer + url;
}
load( url );
}
 
// Load local configurations, overriding the pre-set default values in the HotCat object above. This is always loaded
// from the wiki where this script is executing, even if this script itself is hotlinked from the Commons. This can
// be used to change the default settings, or to provide localized interface texts for edit summaries and so on.
loadJS( 'MediaWiki:Gadget-HotCat.js/local_defaults', loadTrigger.loaded );
 
// Load localized UI texts. These are the texts that HotCat displays on the page itself. Texts shown in edit summaries
Line 356 ⟶ 285:
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
// explicitly if you're not on the Commons and don't want that.
if ( typeof window.hotcat_translations_from_commons === 'undefined' ) {window.hotcat_translations_from_commons = true;
 
window.hotcat_translations_from_commons = true;
}
// Localization hook to localize HotCat messages, tooltips, and engine names for wgUserLanguage.
if ( window.hotcat_translations_from_commons && conf.wgServer.indexOf( '//commons' ) < 0 ) {
loadURI( '//commons.wikimedia.org/w/index.php?title=' +
'MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage +
'&action=raw&ctype=text/javascript', loadTrigger.loaded );
);
} else {
// Load translations locally
loadJS( 'MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage, loadTrigger.loaded );
}
} else {
Line 400 ⟶ 327:
var namespaceIds = conf.wgNamespaceIds;
function autoLocalize( namespaceNumber, fallback ) {
function create_regexp_strcreateRegexpStr( name ) {
if ( !name || !name.length === 0 ) { return ''; }
 
var regex_name = '';
for ( var i = 0; i < name.length; i++ ) {
var initial = name.substrcharAt( i, 1 );,
var ll = initial.toLowerCase();,
var ul = initial.toUpperCase();
if ( ll === ul ) {regex_name += initial; else regex_name += '[' + ll + ul + ']';
regex_name += initial;
} else {
regex_name += '[' + ll + ul + ']';
}
}
return regex_name
Line 420 ⟶ 344:
fallback = fallback.toLowerCase();
var canonical = formattedNamespaces[ String( namespaceNumber ) ].toLowerCase();
var regexp = create_regexp_strcreateRegexpStr( canonical );
if ( fallback && canonical !== fallback ) { regexp += '|' + create_regexp_strcreateRegexpStr( fallback ); }
 
if ( namespaceIds ) {
for ( var cat_name in namespaceIds ) {
Line 430 ⟶ 355:
namespaceIds[ cat_name ] === namespaceNumber
) {
regexp += '|' + create_regexp_strcreateRegexpStr( cat_name );
}
}
Line 437 ⟶ 362:
}
 
HotCatHC.category_canonical = formattedNamespaces[ '14' ];
HotCatHC.category_regexp = autoLocalize( 14, 'category' );
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
// to keep this whole stuff in a single file not depending on any other on-wiki JavascriptsJavaScripts, we re-do
// these few operations here.
function make( arg, literal ) {
if ( !arg ) { return null; }
 
return literal ? document.createTextNode( arg ) : document.createElement( arg );
}
function param( name, uri ) {
if ( typeof uri === 'undefined'uri || uri === null ) { uri = document.location.href; }
var re = new RegExp( '[&?]' + name + '=([^&#]*)' );
var m = re.exec( uri );
if ( m && m.length > 1 ) { return decodeURIComponent( m[ 1 ] ); }
return null;
}
function title( href ) {
if ( !href ) { return null; }
 
var script = 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 ) {
Line 466 ⟶ 391:
// href="/wiki/..."
var prefix = conf.wgArticlePath.replace( '$1', '' );
if ( href.indexOf( prefix ) !=) prefix = 0conf.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 ) === '//' ) {
if ( href.indexOf( prefix ) === 0 ) return decodeURIComponent( href.substring( prefix.length ) );
prefix = document.location.protocol + prefix; // Protocol-relative wgServer?
}
if ( href.indexOf( prefix ) === 0 ) {
return decodeURIComponent( href.substring( prefix.length ) );
}
}
return null;
Line 482 ⟶ 403:
}
function capitalize( str ) {
if ( !str || !str.length === 0 ) { return str; }
 
return str.substr( 0, 1 ).toUpperCase() + str.substr( 1 );
}
Line 515 ⟶ 437:
// Replace $1, $2, or ${key1}, ${key2}, or $key1, $key2 by values from map. $$ is replaced by a single $.
return function ( str, map ) {
if ( !map ) { return str; }
 
return str.replace( re, function ( match, prefix, idx, key, alpha ) {
if ( prefix === lead ) {return lead;
 
return lead;
}
var k = alpha || key || idx;
var replacement = typeof map[ k ] === 'function' ? map[ k ]( match, k ) : map[ k ];
Line 529 ⟶ 451:
var substitute = substituteFactory();
var replaceShortcuts = ( function () {
var replaceHash = substituteFactory( { indicator: '#', lbrace: '[', rbrace: ']' } );
indicator: '#',
lbrace: '[',
rbrace: ']'
} );
return function ( str, map ) {
var s = replaceHash( str, map );
return HotCatHC.capitalizePageNames ? capitalize( s ) : s;
};
}() );
Line 539 ⟶ 465:
 
var findCatsRE =
new RegExp( '\\[\\[' + wikiTextBlankOrBidi + '(?:' + HotCatHC.category_regexp + ')' + wikiTextBlankOrBidi + ':[^\\]]+\\]\\]', 'g' );
 
function replaceByBlanks( match ) {
Line 547 ⟶ 473:
function find_category( wikitext, category, once ) {
var cat_regex = null;
if ( HotCatHC.template_categories[ category ] ) {
cat_regex = new RegExp(
'\\{\\{' + wikiTextBlankOrBidi + '(' + HotCatHC.template_regexp + '(?=' + wikiTextBlankOrBidi + ':))?' + wikiTextBlankOrBidi +
'(?:' + HotCatHC.template_categories[ category ] + ')' +
wikiTextBlankOrBidi + '(\\|.*?)?\\}\\}',
'g'
Line 558 ⟶ 484:
var initial = cat_name.substr( 0, 1 );
cat_regex = new RegExp(
'\\[\\[' + wikiTextBlankOrBidi + '(' + HotCatHC.category_regexp + ')' + wikiTextBlankOrBidi + ':' + wikiTextBlankOrBidi +
( initial === '\\' || !HotCatHC.capitalizePageNames ?
initial :
'[' + initial.toUpperCase() + initial.toLowerCase() + ']' ) +
) +
cat_name.substring( 1 ).replace( wikiTextBlankRE, wikiTextBlank ) +
wikiTextBlankOrBidi + '(\\|.*?)?\\]\\]',
Line 568 ⟶ 493:
);
}
if ( once ) { return cat_regex.exec( wikitext ); }
 
var copiedtext = wikitext
.replace( /<!--(\s|\S)*?-->/g, replaceByBlanks )
Line 575 ⟶ 501:
var curr_match = null;
while ( ( curr_match = cat_regex.exec( copiedtext ) ) !== null ) {
result.push( { match: curr_match } );
match: curr_match
} );
}
result.re = cat_regex;
Line 592 ⟶ 520:
var index = -1;
findCatsRE.lastIndex = 0;
while ( findCatsRE.exec( copiedtext ) !== null ) { index = findCatsRE.lastIndex; }
 
if ( index < 0 ) {
// Find the index of the first interlanguage link...
var match = null;
if ( !interlanguageRE ) {
// Approximation without API: interlanguage links start with 2 to 3 lower case letters, optionally followed by
// a sequence of groups consisting of a dash followed by one or more lower case letters. Exceptions are "simple"
// and "tokipona".
match = /((^|\n\r?)(\[\[\s*(([a-z]{2,3}(-[a-z]+)*)|simple|tokipona)\s*:[^\]]+\]\]\s*))+$/.exec( copiedtext );
} else {
match = interlanguageRE.exec( copiedtext );
}
if ( match ) { index = match.index; }
 
return { idx: index, onCat: false };
return {
idx: index,
onCat: false
};
}
return { idx: index, onCat: index >= 0 };
idx: index,
onCat: index >= 0
};
}
 
var summary = [];,
var nameSpace = HotCatHC.category_canonical;,
var cat_point = -1;, // Position of removed category;
keyChange = ( toRemove && toAdd && toRemove === toAdd && toAdd.length ),
 
matches;
if ( key ) { key = '|' + key; }
if ( key ) key = '|' + key;
var keyChange = ( toRemove && toAdd && toRemove === toAdd && toAdd.length > 0 );
var// matches;Remove
if ( toRemove && toRemove.length > 0 ) {
matches = find_category( wikitext, toRemove );
if ( !matches || !matches.length === 0 ) {
return {
return { text: wikitext, summary: summary, error: HotCat.messages.cat_notFound.replace( /\$1/g, toRemove ) };
text: wikitext,
summary: summary,
error: HC.messages.cat_notFound.replace( /\$1/g, toRemove )
};
} else {
var before = wikitext.substring( 0, matches[ 0 ].match.index );,
var after = wikitext.substring( matches[ 0 ].match.index + matches[ 0 ].match[ 0 ].length );
if ( matches.length > 1 ) {
// Remove all occurrences in after
matches.re.lastIndex = 0;
after = after.replace( matches.re, '' );
}
if ( toAdd ) {
// nameSpace = matches[ 0 ].match[ 1 ] || nameSpace; Canonical namespace should be always preferred
if ( key === null ) { key = matches[ 0 ].match[ 2 ]; } // Remember the category key, if any.
// Remember the category key, if any.
}
// Remove whitespace (properly): strip whitespace, but only up to the next line feed.
Line 637 ⟶ 578:
// whitespace characters, insert a blank.
var i = before.length - 1;
while ( i >= 0 && before.charAt( i ) !== '\n' && before.substr( i, 1 ).search( /\s/ ) >= 0 ) { i--; }
 
var j = 0;
while ( j < after.length && after.charAt( j ) !== '\n' && after.substr( j, 1 ).search( /\s/ ) >= 0 ) { j++; }
 
if ( i >= 0 && before.charAt( i ) === '\n' && ( after.length === 0 || j < after.length && after.charAt( j ) === '\n' ) ) { i--; }
if ( i >= 0 && before.charAt( i ) === '\n' && ( !after.length || j < after.length && after.charAt( j ) === '\n' ) ) i--;
if ( i >= 0 ) {
 
before = before.substring( 0, i + 1 );
if ( i >= 0 ) before = before.substring( 0, i + 1 ); else before = '';
} else {
 
before = '';
if ( j < after.length ) after = after.substring( j ); else after = '';
}
 
if ( j < after.length ) {
after = after.substring( j );
} else {
after = '';
}
if (
before.length > 0 && before.substring( before.length - 1 ).search( /\S/ ) >= 0 &&
after.length > 0 && after.substr( 0, 1 ).search( /\S/ ) >= 0 ) {
) {
before += ' ';
}
 
cat_point = before.length;
if ( cat_point === 0 && after.length > 0 && after.substr( 0, 1 ) === '\n' ) {after = after.substr( 1 );
 
after = after.substr( 1 );
}
wikitext = before + after;
if ( !keyChange ) {
if ( HotCatHC.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 ) );
}
}
 
}
}
// Add
if ( toAdd && toAdd.length > 0 ) {
if ( toAdd && toAdd.length ) {
matches = find_category( wikitext, toAdd );
if ( matches && matches.length > 0 ) {
// Already exists
return { text: wikitext, summary: summary, error: HotCat.messages.cat_exists.replace( /\$1/g, toAdd ) };
return {
text: wikitext,
summary: summary,
error: HC.messages.cat_exists.replace( /\$1/g, toAdd )
};
} else {
var onCat = false;
Line 687 ⟶ 629:
var suffix = wikitext.substring( cat_point );
wikitext = wikitext.substring( 0, cat_point ) + ( cat_point > 0 ? '\n' : '' ) + newcatstring + ( !onCat ? '\n' : '' );
if ( suffix.length > 0 && suffix.substr( 0, 1 ) !== '\n' ) {wikitext += '\n' + suffix; else wikitext += suffix;
wikitext += '\n' + suffix;
} else {
wikitext += suffix;
}
} else {
if ( wikitext.length > 0 && wikitext.substr( wikitext.length - 1, 1 ) !== '\n' ) {wikitext += '\n';
 
wikitext += '\n';
wikitext += ( wikitext.length ? '\n' : '' ) + newcatstring;
}
wikitext += ( wikitext.length > 0 ? '\n' : '' ) + newcatstring;
}
if ( keyChange ) {
var k = key || '';
if ( k.length > 0 ) { k = k.substr( 1 ); }
 
summary.push( substitute( HotCat.messages.cat_keychange, [ null, toAdd, k ] ) );
summary.push( substitute( HC.messages.cat_keychange, [ null, toAdd, k ] ) );
} else {
summary.push( HotCatHC.messages.cat_added.replace( /\$1/g, toAdd ) );
}
if ( HotCatHC.uncat_regexp && !is_hidden ) {
var txt = wikitext.replace( HotCatHC.uncat_regexp, '' ); // Remove "uncat" templates
if ( txt.length !== wikitext.length ) {
wikitext = txt;
summary.push( HotCatHC.messages.uncat_removed );
}
}
}
}
return {
return { text: wikitext, summary: summary, error: null };
text: wikitext,
summary: summary,
error: null
};
}
 
Line 721 ⟶ 663:
function evtKeys( e ) {
/* eslint-disable no-bitwise */
e = e || window.event || window.Event; // W3C, IE, Netscape
var code = 0;
if ( typeof e.ctrlKey !== 'undefined' ) { // All modern browsers
// Ctrl-click seems to be overloaded in FF/Mac (it opens a pop-up menu), so treat cmd-click
// as a ctrl-click, too.
if ( e.ctrlKey || e.metaKey ) { code |= 1; }
 
if ( e.shiftKey ) { code |= 2; }
if ( e.shiftKey ) code |= 2;
} else if ( typeof e.modifiers !== 'undefined' ) { // Netscape...
if ( e.modifiers & ( Event.CONTROL_MASK | Event.META_MASK ) ) { code |= 1; }
if ( e.modifiers & Event.SHIFT_MASK ) { code |= 2; }
}
/* eslint-enable no-bitwise */
return code;
}
function evtKill( e ) {
if ( e.preventDefault ) {
e = e || window.event || window.Event; // W3C, IE, Netscape
if ( typeof e.preventDefault !== 'undefined' ) {
e.preventDefault();
e.stopPropagation();
Line 746 ⟶ 683:
}
 
var catLine = null;,
var onUpload = false;,
var editors = [];,
 
var commitButton = null;,
var commitForm = null;,
var multiSpan = null;,
 
var pageText = null;,
var pageTime = null;,
var pageWatched = false;,
var watchCreate = false;,
var watchEdit = false;,
var minorEdits = false;,
var editToken = null;,
 
var is_rtl = false;,
var serverTime = null;,
var lastRevId = null;,
var pageTextRevId = null;,
var conflictingUser = null;,
 
var newDOM = false; // true if MediaWiki serves the new UL-LI DOM for categories
 
function setMultiInputCategoryEditor() {
this.initialize.apply( this, arguments );
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 checkMultiInputsetPage( json ) {
var startTime = null;
if ( !commitButton ) { return; }
if ( json && json.query ) {
var has_changes = false;
for if ( var i = 0; i < editorsjson.length; i++query.pages ) {
var page = json.query.pages[ !conf.wgArticleId ? '-1' : String( conf.wgArticleId ) ];
if ( editors[ i ].state !== CategoryEditor.UNCHANGED ) {
has_changesif =( true;page ) {
if ( page.revisions && page.revisions.length ) {
break;
// Revisions are sorted by revision ID, hence [ 0 ] is the one we asked for, and possibly there's a [ 1 ] if we're
// not on the latest revision (edit conflicts and such).
pageText = page.revisions[ 0 ][ '*' ];
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 ) {
if ( json.query.general.time && !startTime ) startTime = json.query.general.time.replace( /\D/g, '' );
 
if ( HC.capitalizePageNames === null ) {
// ResourceLoader's JSParser doesn't like .case, so override eslint.
// eslint-disable-next-line dot-notation
HC.capitalizePageNames = ( json.query.general[ 'case' ] === 'first-letter' );
}
}
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;
}
}
commitButton.disabled = !has_changes;
}
 
function currentTimestamp() {
var now = new Date();
var ts = String( now.getUTCFullYear() );
function two( s ) { return s.substr( s.length - 2 ); }
ts = ts +
two( '0' + ( now.getUTCMonth() + 1 ) ) +
two( '0' + now.getUTCDate() ) +
two( '00' + now.getUTCHours() ) +
two( '00' + now.getUTCMinutes() ) +
two( '00' + now.getUTCSeconds() );
return ts;
}
 
var saveInProgress = false;
function initiateEdit( doEdit, failure ) {
if ( saveInProgress ) { return; }
saveInProgress = true;
var oldButtonState;
Line 820 ⟶ 772:
function fail() {
saveInProgress = false;
if ( commitButton ) { commitButton.disabled = oldButtonState; }
failure.apply( this, arguments );
}
 
// Must use Ajax here to get the user options and the edit token.
$.getJSON(
 
conf.wgServer + conf.wgScriptPath + '/api.php?' +
getJSON( {
'format=json&action=query&rawcontinue=&titles=' + encodeURIComponent( conf.wgPageName ) +
uri: conf.wgServer + conf.wgScriptPath + '/api.php',
'&prop=info%7Crevisions%7Clanglinks&inprop=watched&intoken=edit&rvprop=content%7Ctimestamp%7Cids%7Cuser&lllimit=500' +
data: 'format=json&action=query&rawcontinue=&titles=' + encodeURIComponent( conf.wgPageName ) +
'&rvlimit=2&rvdir=newer&rvstartid=' + conf.wgCurRevisionId + '&meta=siteinfo%7Cuserinfo&uiprop=options',
'&prop=info%7Crevisions%7Clanglinks&inprop=watched&intoken=edit&rvprop=content%7Ctimestamp%7Cids%7Cuser&lllimit=500' +
function ( json ) {
'&rvlimit=2&rvdir=newer&rvstartid=' + conf.wgCurRevisionId +
'&meta=siteinfo%7Cuserinfo&uiprop=options',
success: function ( json ) {
setPage( json );
doEdit( fail );
},
error: function ( req ) {
fail( req.status + ' ' + req.statusText );
}
).fail( function ( req ) {
fail( req.status + ' ' + req.statusText );
} );
}
 
function multiChangeMsg( count ) {
var msg = HotCatHC.messages.multi_change;
if ( typeof msg !== 'string' && msg.length ) {
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 {
msg = msg[ msg.length - 1 ];
}
}
return substitute( msg, [ null, String( count ) ] );
}
 
function currentTimestamp() {
var now = new Date();
var ts = String( now.getUTCFullYear() );
function two( s ) {
return s.substr( s.length - 2 );
}
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 ) {
if ( pageText === null ) {
failure( HotCatHC.messages.multi_error );
return;
}
// Backwards compatibility after message change (added $2 to cat_keychange)
if ( HotCatHC.messages.cat_keychange.indexOf( '$2' ) < 0 ) { HotCatHC.messages.cat_keychange += '"$2"'; }
 
// More backwards-compatibility with earlier HotCat versions:
if ( !HotCatHC.messages.short_catchange ) { HotCatHC.messages.short_catchange = '[[' + HotCatHC.category_canonical + ':$1]]'; }
 
// 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
Line 876 ⟶ 838:
// 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.
var editingOldVersionselfEditConflict = ( lastRevId !== null && lastRevId !== conf.wgCurRevisionId || pageTextRevId !== null && pageTextRevId !== conf.wgCurRevisionId;
var selfEditConflict pageTextRevId !== conf.wgCurRevisionId editingOldVersion) && conflictingUser && conflictingUser === conf.wgUserName;
if ( singleEditor && !singleEditor.noCommit && !HotCatHC.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
// 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;
action = commitForm.wpDiff;
if ( action ) { action.name = action.value = 'wpSave'; }
} else {
action = commitForm.wpSave;
if ( action ) { action.name = action.value = 'wpDiff'; }
}
var result = { text: pageText };
text: pageText
var changed = [], added = [], deleted = [], changes = 0;
},
var toEdit = singleEditor ? [ singleEditor ] : editors;
var error changed = null;[],
var added i;= [],
deleted = [],
changes = 0,
toEdit = singleEditor ? [ singleEditor ] : editors,
error = null,
edit,
i;
for ( i = 0; i < toEdit.length; i++ ) {
ifedit (= toEdit[ i ].state === CategoryEditor.CHANGED ) {;
if ( edit.state === CategoryEditor.CHANGED ) {
result = change_category(
result.text,
toEdit[ i ]edit.originalCategory,
toEdit[ i ]edit.currentCategory,
toEdit[ i ]edit.currentKey,
toEdit[ i ]edit.currentHidden );
);
if ( !result.error ) {
changes++;
if ( !toEdit[ i ]edit.originalCategory || toEdit[ i ]!edit.originalCategory.length === 0 ) {
added.push( toEdit[ i ]edit.currentCategory );
} else {
changed.push( { from: toEdit[ i ].originalCategory, to: toEdit[ i ].currentCategory } );
from: edit.originalCategory,
to: edit.currentCategory
} );
}
} else if ( error === null ) {
Line 913 ⟶ 884:
}
} else if (
toEdit[ i ]edit.state === CategoryEditor.DELETED && edit.originalCategory && edit.originalCategory.length ) {
result = change_category(
toEdit[ i ].originalCategory &&
result.text,
toEdit[ i ].originalCategory.length > 0
edit.originalCategory,
) {
result = change_category( result.text, toEdit[ i ].originalCategory, null, null, false );
if ( !result.error ) {
changes++;
deleted.push( toEdit[ i ]edit.originalCategory );
} else if ( error === null ) {
error = result.error;
Line 928 ⟶ 899:
if ( error !== null ) { // Do not commit if there were errors
action = commitForm.wpSave;
if ( action ) { action.name = action.value = 'wpDiff'; }
}
// Fill in the form and submit it
commitForm.wpAutoSummary.value = 'd41d8cd98f00b204e9800998ecf8427e'; // MD5 hash of the empty string
commitForm.wpMinoredit.checked = minorEdits;
commitForm.wpWatchthis.checked = !conf.wgArticleId === 0 && watchCreate || watchEdit || pageWatched;
if ( conf.wgArticleId > 0 || !!singleEditor ) {
// Prepare change-tag save
if ( changes === 1 ) {
if ( result.summaryaction && result.summaryaction.lengthvalue >=== 0'wpSave' ) {
if ( HC.changeTag ) {
commitForm.wpSummary.value = HotCat.messages.prefix + result.summary.join( HotCat.messages.separator ) + HotCat.messages.using;
commitForm.wpChangeTags.value = HC.changeTag;
HC.messages.using = '';
HC.messages.prefix = '';
}
} else {
commitForm.wpMinoredit.checked = HotCat.single_minor || minorEdits;
commitForm.wpAutoSummary.value = HC.changeTag;
} else if ( changes > 1 ) {
}
if ( changes === 1 ) {
if ( result.summary && result.summary.length ) commitForm.wpSummary.value = HC.messages.prefix + result.summary.join( HC.messages.separator ) + HC.messages.using;
commitForm.wpMinoredit.checked = HC.single_minor || minorEdits;
} else if ( changes ) {
var summary = [];
var shortSummary = [];
// Deleted
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
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
var arrow = is_rtl ? '\u2190' : '\u2192'; // left and right arrows. Don't use ← and → in the code.
for ( i = 0; i < changed.length; i++ ) {
if ( changed[ i ].from !== changed[ i ].to ) {
summary.push(
summary.push( '±' + substitute( HotCat.messages.short_catchange, [ null, changed[ i ].from ] ) + arrow +
'±' + substitute( HotCatHC.messages.short_catchange, [ null, changed[ i ].tofrom ] ) );+ arrow +
substitute( HC.messages.short_catchange, [ null, changed[ i ].to ] )
);
} else {
summary.push( '±' + substitute( HotCatHC.messages.short_catchange, [ null, changed[ i ].from ] ) );
}
}
if ( changed.length === 1 ) {
if ( changed[ 0 ].from !== changed[ 0 ].to ) {
shortSummary.push( '±' + substitute( HotCat.messages.short_catchange, [ null, changed[ 0 ].from ] ) + arrow +
'±' + substitute( HotCatHC.messages.short_catchange, [ null, changed[ 0 ].tofrom ] ) );+ arrow +
substitute( HC.messages.short_catchange, [ null, changed[ 0 ].to ] )
);
} else {
shortSummary.push( '±' + substitute( HotCatHC.messages.short_catchange, [ null, changed[ 0 ].from ] ) );
}
} else if ( changed.length > 1 ) {
shortSummary.push( '± ' + multiChangeMsg( changed.length ) );
}
if ( summary.length > 0 ) {
summary = summary.join( HotCatHC.messages.separator );
if ( summary.length > 200 - HotCatHC.messages.prefix.length - HotCatHC.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.wpStarttime.value = serverTime || currentTimestamp();
commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value;
if ( selfEditConflict ) { commitForm.oldid.value = String( pageTextRevId || conf.wgCurRevisionId ); }
 
// Submit the form in a way that triggers onsubmit events: commitForm.submit() doesn't.
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 ) {
var cats = page.categories;,
var lks = page.links;,
var is_dab = false;,
var is_redir = typeof page.redirect === 'string';, // Hard redirect?
var is_hidden = page.categoryinfo && typeof page.categoryinfo.hidden === 'string';,
var is_missing = typeof page.missing === 'string';,
var i;
for ( i = 0; i < toResolve.length; i++ ) {
if ( toResolve.length > 1i && 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
// 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 ].inputExists = !is_missing;
toResolve[ i ].icon.src = armorUri( is_missing ? HotCatHC.existsNo : HotCatHC.existsYes );
}
if ( is_missing ) { return; }
if ( !is_redir && cats && ( HotCatHC.disambig_category || HotCatHC.redir_category ) ) {
for ( var c = 0; c < cats.length; c++ ) {
var cat = cats[ c ].title;
Line 1,052 ⟶ 996:
if ( cat ) {
cat = cat.substring( cat.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
if ( cat === HotCatHC.disambig_category ) {
is_dab = true; break;
break;
} else if ( cat === HotCat.redir_category ) {
} else if ( cat === HC.redir_category ) {
is_redir = true; break;
is_redir = true;
break;
}
}
}
}
if ( !is_redir && !is_dab ) { return; }
if ( !lks || !lks.length === 0 ) { return; }
var titles = [];
for ( i = 0; i < lks.length; i++ ) {
Line 1,068 ⟶ 1,014:
lks[ i ].ns === 14 &&
// Name not empty
lks[ i ].title && lks[ i ].title.length > 0
) {
// Internal link to existing thingy. Extract the page name and remove the namespace.
Line 1,074 ⟶ 1,020:
match = match.substring( match.indexOf( ':' ) + 1 );
// Exclude blacklisted categories.
if ( !HotCatHC.blacklist || !HotCatHC.blacklist.test( match ) ) {titles.push( match );
titles.push( match );
}
}
}
if ( !titles.length === 0 ) {return;
return;
}
for ( i = 0; i < toResolve.length; i++ ) {
if ( toResolve.length > 1i && 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 ].icon.src = armorUri( HotCatHC.existsYes );
if ( titles.length > 1 ) {
toResolve[ i ].dab = titles;
Line 1,096 ⟶ 1,038:
 
function resolveRedirects( toResolve, params ) {
if ( !params || !params.query || !params.query.pages ) { return; }
for ( var p in params.query.pages ) { resolveOne( params.query.pages[ p ], toResolve ); }
}
 
function multiSubmitresolveMulti( toResolve, callback ) {
var toResolve = []i;
for ( var i = 0; i < editorstoResolve.length; i++ ) {
toResolve[ i ].dab = null;
if ( editors[ i ].state === CategoryEditor.CHANGE_PENDING || editors[ i ].state === CategoryEditor.OPEN ) {
toResolve[ i ].push(dabInput editors= 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, HC.shortcuts );
toResolve[ i ].dabInputCleaned = v;
args += encodeURIComponent( 'Category:' + v );
if ( i + 1 < toResolve.length ) args += '%7C';
}
$.getJSON( conf.wgServer + conf.wgScriptPath + '/api.php?' + args,
function ( json ) {
resolveRedirects( toResolve, json );
callback( toResolve );
} ).fail( function ( req ) {
if ( !req ) noSuggestions = true;
callback( toResolve );
} );
}
 
function makeActive( which ) {
if ( which.is_active ) return;
for ( var i = 0; i < editors.length; i++ )
if ( editors[ i ] !== which ) editors[ i ].inactivate();
 
which.is_active = true;
if ( which.dab ) {
// eslint-disable-next-line no-use-before-define
showDab( which );
} else {
// Check for programmatic value changes.
var expectedInput = which.lastRealInput || which.lastInput || '';
var actualValue = which.text.value || '';
if ( !expectedInput.length && actualValue.length || expectedInput.length && actualValue.indexOf( expectedInput ) ) {
// Somehow the field's value appears to have changed, and which.lastSelection therefore is no longer valid. Try to set the
// cursor at the end of the category, and do not display the old suggestion list.
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 );
}
}
}
}
if ( toResolve.length === 0 ) {
 
initiateEdit( function ( failure ) { performChanges( failure ); }, function ( msg ) { alert( msg ); } );
function showDab( which ) {
if ( !which.is_active ) {
makeActive( which );
} else {
which.showSuggestions( which.dab, false, null, null ); // do autocompletion, no key, no engine selector
which.dab = null;
}
}
 
function multiSubmit() {
var toResolve = [];
for ( var i = 0; i < editors.length; i++ )
if ( editors[ i ].state === CategoryEditor.CHANGE_PENDING || editors[ i ].state === CategoryEditor.OPEN ) toResolve.push( editors[ i ] );
 
if ( !toResolve.length ) {
initiateEdit( function ( failure ) {
performChanges( failure );
}, function ( msg ) {
alert( msg );
} );
return;
}
Line 1,121 ⟶ 1,153:
} else {
if ( resolved[ i ].dab ) {
if ( !firstDab ) { firstDab = resolved[ i ]; }
} else {
if ( resolved[ i ].acceptCheck( true ) ) { resolved[ i ].commit(); }
}
}
Line 1,130 ⟶ 1,162:
showDab( firstDab );
} else if ( !dontChange ) {
initiateEdit( function ( failure ) { performChanges( failure ); }, function ( msg ) { alert( msg ); } );
performChanges( failure );
}, function ( msg ) {
alert( msg );
} );
}
} );
}
 
function setMultiInput() {
var cat_prefix = null;
if ( commitButton || onUpload ) return;
var noSuggestions = false;
commitButton = make( 'input' );
commitButton.type = 'button';
commitButton.value = HC.messages.commit;
commitButton.onclick = multiSubmit;
if ( multiSpan ) multiSpan.parentNode.replaceChild( commitButton, multiSpan ); else catLine.appendChild( commitButton );
}
 
function checkMultiInput() {
if ( !commitButton ) return;
var hasChanges = false;
for ( var i = 0; i < editors.length; i++ ) {
if ( editors[ i ].state !== CategoryEditor.UNCHANGED ) {
hasChanges = true;
break;
}
}
commitButton.disabled = !hasChanges;
}
 
var suggestionEngines = {
opensearch: {
Line 1,146 ⟶ 1,201:
var titles = queryResult[ 1 ];
var exists = false;
if ( !cat_prefix ) { cat_prefix = new RegExp( '^(' + HotCatHC.category_regexp + ':):' ); }
 
for ( var i = 0; i < titles.length; i++ ) {
cat_prefix.lastIndex = 0;
Line 1,152 ⟶ 1,208:
if ( m && m.length > 1 ) {
titles[ i ] = titles[ i ].substring( titles[ i ].indexOf( ':' ) + 1 ); // rm namespace
if ( key === titles[ i ] ) { exists = true; }
} else {
titles.splice( i, 1 ); // Nope, it's not a category after all.
Line 1,159 ⟶ 1,215:
}
titles.exists = exists;
if ( queryKey !== key ) { titles.normalized = key; } // Remember the NFC normalized key we got back from the server
// Remember the NFC normalized key we got back from the server
return titles;
}
Line 1,170 ⟶ 1,227:
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
 
titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace
}
return titles;
}
Line 1,188 ⟶ 1,244:
var titles = [ title ];
titles.exists = true;
if ( queryKey !== title ) { titles.normalized = title; } // NFC
// NFC
return titles;
}
Line 1,200 ⟶ 1,257:
if ( queryResult && queryResult.query && queryResult.query.categorymembers ) {
var titles = queryResult.query.categorymembers;
for ( var i = 0; i < titles.length; i++ ) {titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace
 
titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace
}
return titles;
}
Line 1,215 ⟶ 1,271:
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
 
titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace
}
return titles;
}
Line 1,228 ⟶ 1,283:
 
var suggestionConfigs = {
searchindex: {
searchindex: { name: 'Search index', engines: [ 'opensearch' ], cache: {}, show: true, temp: false, noCompletion: false },
name: 'Search index',
pagelist: { name: 'Page list', engines: [ 'internalsearch', 'exists' ], cache: {}, show: true, temp: false, noCompletion: false },
engines: [ 'opensearch' ],
combined: { name: 'Combined search', engines: [ 'opensearch', 'internalsearch' ], cache: {}, show: true, temp: false, noCompletion: false },
cache: {},
subcat: { name: 'Subcategories', engines: [ 'subcategories' ], cache: {}, show: true, temp: true, noCompletion: true },
show: true,
parentcat: { name: 'Parent categories', engines: [ 'parentcategories' ], cache: {}, show: true, temp: true, noCompletion: true }
temp: false,
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
}
};
 
function CategoryEditor() { this.initialize.apply( this, arguments ); }
CategoryEditor.UNCHANGED = 0;
CategoryEditor.OPEN = 1; // Open, but no input yet
Line 1,242 ⟶ 1,331:
CategoryEditor.DELETED = 4;
 
// Support: IE6
// IE6 sometimes forgets to redraw the list when editors are opened or closed.
// Adding/removing a dummy element helps, at least when opening editors.
Line 1,247 ⟶ 1,337:
 
function forceRedraw() {
if ( dummyElement.parentNode ) {document.body.removeChild( dummyElement ); else document.body.appendChild( dummyElement );
document.body.removeChild( dummyElement );
} else {
document.body.appendChild( dummyElement );
}
}
 
// Event keyCodes that we handle in the text input field/suggestion list.
var BS = 8,
var BS = 8, TAB = 9, RET = 13, ESC = 27, SPACE = 32, PGUP = 33, PGDOWN = 34, UP = 38, DOWN = 40, DEL = 46, IME = 229;
TAB = 9,
 
RET = 13,
function makeActive( which ) {
ESC = 27,
if ( which.is_active ) { return; }
SPACE = 32,
for ( var i = 0; i < editors.length; i++ ) {
PGUP = 33,
if ( editors[ i ] !== which ) { editors[ i ].inactivate(); }
PGDOWN = 34,
}
UP = 38,
which.is_active = true;
DOWN = 40,
if ( which.dab ) {
DEL = 46,
showDab( which );
}IME else= {229;
// Check for programmatic value changes.
var expectedInput = which.lastRealInput || which.lastInput || '';
var actualValue = which.text.value || '';
if ( expectedInput.length === 0 && actualValue.length > 0 || expectedInput.length > 0 && actualValue.indexOf( expectedInput ) !== 0 ) {
// Somehow the field's value appears to have changed, and which.lastSelection therefore is no longer valid. Try to set the
// cursor at the end of the category, and do not display the old suggestion list.
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 );
}
}
}
}
 
function showDab( which ) {
if ( !which.is_active ) {
makeActive( which );
} else {
which.showSuggestions( which.dab, false, null, null ); // do autocompletion, no key, no engine selector
which.dab = null;
}
}
 
CategoryEditor.prototype = {
Line 1,323 ⟶ 1,373:
after.parentNode.insertBefore( span, after.nextSibling );
after = after.nextSibling;
} else if (line) {
line.appendChild( span );
}
} else if ( line && line.firstChild ) {
span.appendChild( make( ' ', true ) );
line.appendChild( span );
Line 1,333 ⟶ 1,383:
this.linkSpan = make( 'span' );
this.linkSpan.className = 'noprint nopopups hotcatlink';
var lk = make( 'a' ); lk.href = '#catlinks'; lk.onclick = this.open.bind( this );
lk.href = '#catlinks';
lk.appendChild( make( HotCat.links.add, true ) ); lk.title = HotCat.tooltips.add;
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'; }
 
span.appendChild( this.linkSpan );
if ( after ) {
after.parentNode.insertBefore( span, after.nextSibling );
} else if ( line ) {
line.appendChild( span );
}
 
this.normalLinks = null;
this.undelLink = null;
this.catLink = null;
} else {
if ( is_rtl ) { span.dir = 'rtl'; }
 
this.isAddCategory = false;
this.catLink = span.firstChild;
Line 1,357 ⟶ 1,413:
// 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 = HotCatHC.suggestions;
this.span = span;
this.currentCategory = this.originalCategory;
Line 1,374 ⟶ 1,431:
this.lastSavedExists = this.originalExists;
this.lastSavedHidden = this.originalHidden;
if ( this.catLink && this.currentKey ) {this.catLink.title = this.currentKey;
 
this.catLink.title = this.currentKey;
}
editors[ editors.length ] = this;
},
Line 1,383 ⟶ 1,439:
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.href = '#catlinks';
lk.appendChild( make( HotCat.links.remove, true ) ); lk.title = HotCat.tooltips.remove;
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 ( !HotCatHC.template_categories[ this.originalCategory ] ) {
lk = make( 'a' ); lk.href = '#catlinks'; lk.onclick = this.open.bind( this );
lk.href = '#catlinks';
lk.appendChild( make( HotCat.links.change, true ) ); lk.title = HotCat.tooltips.change;
lk.onclick = this.open.bind( this );
lk.appendChild( make( HC.links.change, true ) );
lk.title = HC.tooltips.change;
this.normalLinks.appendChild( make( ' ', true ) );
this.normalLinks.appendChild( lk );
if ( !noSuggestions && HotCatHC.use_up_down ) {
this.upDownLinks = make( 'span' );
lk = make( 'a' ); lk.href = '#catlinks'; lk.onclick = this.down.bind( this );
lk.href = '#catlinks';
lk.appendChild( make( HotCat.links.down, true ) ); lk.title = HotCat.tooltips.down;
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.href = '#catlinks';
lk.appendChild( make( HotCat.links.up, true ) ); lk.title = HotCat.tooltips.up;
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 );
Line 1,413 ⟶ 1,481:
this.undelLink.className = 'nopopups hotcatlink';
this.undelLink.style.display = 'none';
lk = make( 'a' ); lk.href = '#catlinks'; lk.onclick = this.restore.bind( this );
lk.href = '#catlinks';
lk.appendChild( make( HotCat.links.restore, true ) ); lk.title = HotCat.tooltips.restore;
lk.onclick = this.restore.bind( this );
lk.appendChild( make( HC.links.restore, true ) );
lk.title = HC.tooltips.restore;
this.undelLink.appendChild( make( ' ', true ) );
this.undelLink.appendChild( lk );
Line 1,421 ⟶ 1,492:
 
invokeSuggestions: function ( dont_autocomplete ) {
if ( this.engine && suggestionConfigs[ this.engine ] && suggestionConfigs[ this.engine ].temp && !dont_autocomplete ) {this.engine = HC.suggestions; // Reset to a search upon input
 
this.engine = HotCat.suggestions; // Reset to a search upon input
}
this.state = CategoryEditor.CHANGE_PENDING;
var self = this;
window.setTimeout( function () { self.textchange( dont_autocomplete ); }, HotCat.suggest_delay );
self.textchange( dont_autocomplete );
}, HC.suggest_delay );
},
 
makeForm: function () {
var form = make( 'form' );
form.method = 'POST'; form.onsubmit = this.accept.bind( this );
form.onsubmit = this.accept.bind( this );
this.form = form;
var self = this;
var text = make( 'input' ); text.type = 'text'; text.size = HotCat.editbox_width;
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:
Line 1,445 ⟶ 1,520:
// - 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 ) {
functionvar (key = evt.keyCode )|| {0;
if ( self.ime && self.lastKey === IME && !self.usesComposition && ( key === TAB || key === RET || key === ESC || key === SPACE ) ) self.ime = false;
evt = evt || window.event || window.Event; // W3C, IE, Netscape
 
var key = evt.keyCode || 0;
if ( self.ime ) return true;
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 );
}
// Also do this for ESC as a workaround for Firefox bug 524360
return true;
// https://bugzilla.mozilla.org/show_bug.cgi?id=524360
};
self.invokeSuggestions( key === BS || key === DEL || key === ESC );
text.onkeydown =
function ( evt ) {}
return true;
evt = evt || window.event || window.Event; // W3C, IE, Netscape
};
var key = evt.keyCode || 0;
text.onkeydown = function ( evt ) {
self.lastKey = key;
self.keyCountvar key = evt.keyCode || 0;
self.lastKey = key;
// DOM Level < 3 IME input
self.keyCount = 0;
if ( !self.ime && key === IME && !self.usesComposition ) {
// DOM Level < 3 IME input
// self.usesComposition catches browsers that may emit spurious keydown IME after a composition has ended
if ( !self.ime && key === IME && !self.usesComposition ) true;{
// self.usesComposition catches browsers that may emit spurious keydown IME after a composition has ended
} else if ( self.ime && key !== IME && !( key >= 16 && key <= 20 || key >= 91 && key <= 93 || key === 144 ) ) {
self.ime = true;
// Ignore control keys: ctrl, shift, alt, alt gr, caps lock, windows/apple cmd keys, num lock. Only the windows keys
} else if ( self.ime && key !== IME && !( key >= 16 && key <= 20 || key >= 91 && key <= 93 || key === 144 ) ) {
// terminate IME (apple cmd doesn't), but they also cause a blur, so it's OK to ignore them here.
// Ignore control keys: ctrl, shift, alt, alt gr, caps lock, windows/apple cmd keys, num lock. Only the windows keys
// Note: Safari 4 (530.17) propagates ESC out of an IME composition (observed at least on Win XP).
// terminate IME (apple cmd doesn't), but they also cause a blur, so it's OK to ignore them here.
self.ime = false;
// Note: Safari 4 (530.17) propagates ESC out of an IME composition (observed at least on Win XP).
}
if ( self.ime )= { return truefalse; }
}
// Handle return explicitly, to override the default form submission to be able to check for ctrl
if ( key === RETself.ime ) { return self.accept( evt )true; }
 
// Inhibit default behavior of ESC (revert to last real input in FF: we do that ourselves)
// Handle return (explicitly, to override the keydefault ===form ESCsubmission )to ?be evtKill(able evtto )check :for true;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 ); };
self.keyCount++;
$( text ).on( 'focus', function () { makeActive( self ); } );
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; } );
self.lastKey = IME;
$( text ).on( 'compositionend', function () { self.lastKey = IME; self.usesComposition = true; self.ime = false; } );
self.usesComposition = true;
$( text ).on( 'textInput', function () { self.ime = false; self.invokeSuggestions( false ); } );
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; } );
self.usesComposition = false;
self.ime = false;
} );
}
this.text = text;
Line 1,522 ⟶ 1,614:
if ( !noSuggestions ) {
list = make( 'select' );
list.onclick = function () { if ( self.highlightSuggestion( 0 ) ) { self.textchange( false, true ); } };
list.ondblclick = function ( e ) { if ( self.highlightSuggestion( 0 ) ) { self.accepttextchange( efalse, true ); } };
};
list.onchange = function () { self.highlightSuggestion( 0 ); self.text.focus(); };
list.ondblclick = function ( e ) {
if ( self.highlightSuggestion( 0 ) ) self.accept( e );
};
list.onchange = function () {
self.highlightSuggestion( 0 );
self.text.focus();
};
list.onkeyup = function ( evt ) {
evt = evt || window.event || window.Event; // W3C, IE, Netscape
if ( evt.keyCode === ESC ) {
self.resetKeySelection();
self.text.focus();
window.setTimeout( function () { self.textchange( true ); }, HotCat.suggest_delay );
self.textchange( true );
}, HC.suggest_delay );
} else if ( evt.keyCode === RET ) {
self.accept( evt );
}
};
if ( !HotCatHC.fixed_search ) {
var engineSelector = make( 'select' );
for ( var key in suggestionConfigs ) {
Line 1,541 ⟶ 1,641:
var opt = make( 'option' );
opt.value = key;
if ( key === this.engine ) { opt.selected = true; }
 
opt.appendChild( make( suggestionConfigs[ key ].name, true ) );
engineSelector.appendChild( opt );
Line 1,560 ⟶ 1,661:
if (
onUpload &&
typeof window.UFUI !== 'undefined' &&
typeof window.UIElements !== 'undefined' &&
typeof UFUI.getLabel ===instanceof 'function'Function
) {
try {
label = UFUI.getLabel( id, true );
// Extract the plain text. IE doesn't know that Node.TEXT_NODE === 3
while ( label && label.nodeType !== 3 ) { label = label.firstChild; }
} catch ( ex ) {
label = null;
}
}
if ( !label || !label.data ) { return defaultText; }
 
return label.data;
}
 
// Do not use type 'submit'; we cannot detect modifier keys if we do
var OK = make( 'input' ); OK.type = 'button';
OK.type = 'button';
OK.value = button_label( 'wpOkUploadLbl', HotCat.messages.ok );
OK.value = button_label( 'wpOkUploadLbl', HC.messages.ok );
OK.onclick = this.accept.bind( this );
this.ok = OK;
 
var cancel = make( 'input' ); cancel.type = 'button';
cancel.valuetype = button_label( 'wpCancelUploadLblbutton', HotCat.messages.cancel );
cancel.value = button_label( 'wpCancelUploadLbl', HC.messages.cancel );
cancel.onclick = this.cancel.bind( this );
this.cancelButton = cancel;
Line 1,590 ⟶ 1,694:
span.className = 'hotcatinput';
span.style.position = 'relative';
// FF3.6: add the input field first, then the two absolutely positioned elements. Otherwise, FF3.6 may leave the
// suggestions and the selector at the right edge of the screen if display of the input field causes a re-layout
// moving the form to the front of the next line.
span.appendChild( text );
 
// Support: IE8, IE9
// IE8/IE9: put some text into this span (a0 is nbsp) and make sure it always stays on the
// Put some text into this span (a0 is nbsp) and make sure it always stays on the same
// same line as the input field, otherwise, IE8/9 miscalculates the height of the span and
// line as the input field, otherwise, IE8/9 miscalculates the height of the span and
// then the engine selector may overlap the input field.
span.appendChild( make( '\xa0', true ) );
span.style.whiteSpace = 'nowrap';
 
if ( list ) { span.appendChild( list ); }
 
if ( this.engineSelector ) { span.appendChild( this.engineSelector ); }
if ( !noSuggestionsthis.engineSelector ) { span.appendChild( this.iconengineSelector ); }
 
if ( !noSuggestions ) span.appendChild( this.icon );
 
span.appendChild( OK );
span.appendChild( cancel );
Line 1,612 ⟶ 1,717:
 
display: function ( evt ) {
if ( this.isAddCategory && !onUpload && this.line ) {
// eslint-disable-next-line no-new
new CategoryEditor( this.line, null, this.span, true ); // Create a new one
Line 1,624 ⟶ 1,729:
}
}
if ( !this.form ) {this.makeForm();
 
this.makeForm();
if ( this.list ) this.list.style.display = 'none';
}
 
if ( this.list ) { this.list.style.display = 'none'; }
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
 
this.currentCategory = this.lastSavedCategory;
this.currentExists = this.lastSavedExists;
this.currentHidden = this.lastSavedHidden;
this.currentKey = this.lastSavedKey;
this.icon.src = armorUri( this.currentExists ? HotCatHC.existsYes : HotCatHC.existsNo );
this.text.value = this.currentCategory + ( this.currentKey !== null ? '|' + this.currentKey : '' );
this.originalState = this.state;
Line 1,639 ⟶ 1,745:
this.inputExists = this.currentExists;
this.state = this.state === CategoryEditor.UNCHANGED ? CategoryEditor.OPEN : CategoryEditor.CHANGE_PENDING;
this.lastSelection = { start: this.currentCategory.length, end: this.currentCategory.length };
start: this.currentCategory.length,
end: this.currentCategory.length
};
this.showsList = false;
// Display the form
if ( this.catLink ) { this.catLink.style.display = 'none'; }
 
this.linkSpan.style.display = 'none';
this.form.style.display = 'inline';
Line 1,657 ⟶ 1,767:
var result = this.display( evt );
var v = this.lastSavedCategory;
if ( !v.length === 0 ) { return result; }
 
this.text.readOnly = !!readOnly;
this.engine = engine;
Line 1,666 ⟶ 1,777:
 
open: function ( evt ) {
return this.show( evt, ( this.engine && suggestionConfigs[ this.engine ].temp ) ? HotCatHC.suggestions : this.engine );
},
 
Line 1,685 ⟶ 1,796:
this.inactivate();
this.form.style.display = 'none';
if ( this.catLink ) { this.catLink.style.display = ''; }
 
this.linkSpan.style.display = '';
this.state = this.originalState;
Line 1,692 ⟶ 1,804:
this.currentExists = this.lastSavedExists;
this.currentHidden = this.lastSavedHidden;
if ( this.catLink ) {
if ( this.currentKey && this.currentKey.length >) 0{ )this.catLink.title = this.currentKey; } else { this.catLink.title = ''; }
 
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 = HotCatHC.bg_changed;
} catch ( ex ) {}
}
Line 1,715 ⟶ 1,823:
if ( !newDOM ) {
var next = this.span.nextSibling;
if ( next ) { next.parentNode.removeChild( next ); }
}
if (this.span && this.span.parentNode) {
this.span.parentNode.removeChild( this.span );
}
this.span.parentNode.removeChild( this.span );
for ( var i = 0; i < editors.length; i++ ) {
if ( editors[ i ] === this ) {
Line 1,725 ⟶ 1,835:
}
checkMultiInput();
var self = this;
// eslint-disable-next-line no-delete-var
window.setTimeout( function () { delete self; }, 10 );
},
 
Line 1,742 ⟶ 1,849:
this.lastSavedHidden = this.originalHidden;
this.state = CategoryEditor.UNCHANGED;
if ( !this.currentCategory || !this.currentCategory.length === 0 ) {
// It was a newly added category. Remove the whole editor.
this.removeEditor();
Line 1,749 ⟶ 1,856:
this.catLink.removeChild( this.catLink.firstChild );
this.catLink.appendChild( make( this.currentCategory, true ) );
this.catLink.href = wikiPagePath( HotCatHC.category_canonical + ':' + this.currentCategory );
this.catLink.title = this.currentKey || '';
this.catLink.className = this.currentExists ? '' : 'new';
this.catLink.style.backgroundColor = 'transparent';
if ( this.upDownLinks ) { this.upDownLinks.style.display = this.currentExists ? '' : 'none'; }
 
checkMultiInput();
}
Line 1,760 ⟶ 1,868:
 
inactivate: function () {
if ( this.list ) { this.list.style.display = 'none'; }
 
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
 
this.is_active = false;
},
Line 1,769 ⟶ 1,879:
var value = this.text.value.split( '|' );
var key = null;
if ( value.length > 1 ) { key = value[ 1 ]; }
 
var v = value[ 0 ].replace( /_/g, ' ' ).replace( /^\s+|\s+$/g, '' );
if ( HotCatHC.capitalizePageNames ) { v = capitalize( v ); }
 
this.lastInput = v;
v = replaceShortcuts( v, HotCatHC.shortcuts );
if ( !v.length === 0 ) {
this.cancel();
return false;
}
if ( !dontCheck && (
conf.wgNamespaceNumber === 14 && v === conf.wgTitle || HC.blacklist && HC.blacklist.test( v ) ) ) {
!dontCheck && (
conf.wgNamespaceNumber === 14 && v === conf.wgTitle ||
HotCat.blacklist && HotCat.blacklist.test( v )
)
) {
this.cancel();
return false;
Line 1,807 ⟶ 1,915:
resolved[ 0 ].commit(
( resolved[ 0 ].currentCategory !== original ) ?
HotCatHC.messages.cat_resolved.replace( /\$1/g, original ) :
null );
);
}
}
Line 1,827 ⟶ 1,934:
this.catLink.removeChild( this.catLink.firstChild );
this.catLink.appendChild( make( this.currentCategory, true ) );
this.catLink.href = wikiPagePath( HotCatHC.category_canonical + ':' + this.currentCategory );
this.catLink.className = this.currentExists ? '' : 'new';
this.lastSavedCategory = this.currentCategory;
Line 1,839 ⟶ 1,946:
this.catLink.style.display = '';
if ( this.isAddCategory ) {
if ( onUpload && this.line ) {
// eslint-disable-next-line no-new
new CategoryEditor( this.line, null, this.span, true ); // Create a new one
Line 1,851 ⟶ 1,958:
// Append an undo link.
var span = make( 'span' );
var lk = make( 'a' ); lk.href = '#catlinks'; lk.onclick = this.rollback.bind( this );
lk.href = '#catlinks';
lk.appendChild( make( HotCat.links.undo, true ) ); lk.title = HotCat.tooltips.undo;
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 );
Line 1,859 ⟶ 1,969:
if ( !onUpload ) {
try {
this.catLink.style.backgroundColor = HotCatHC.bg_changed;
} catch ( ex ) {}
}
}
if ( this.upDownLinks ) { this.upDownLinks.style.display = this.lastSavedExists ? '' : 'none'; }
 
this.linkSpan.style.display = '';
this.state = CategoryEditor.CHANGED;
Line 1,877 ⟶ 1,988:
(
this.currentKey === this.originalKey ||
this.currentKey === null && !this.originalKey.length === 0
)
) ||
conf.wgNamespaceNumber === 14 && this.currentCategory === conf.wgTitle ||
HotCatHC.blacklist && HotCatHC.blacklist.test( this.currentCategory )
) {
this.cancel();
return;
}
this.close();
if ( commitButton || onUpload ) {
if ( !commitButton && !onUpload ) {
this.close();
} else {
this.close();
var self = this;
initiateEdit( function ( failure ) { performChanges( failure, self ); }, function ( msg ) { alert( msg ); } );
performChanges( failure, self );
}, function ( msg ) {
alert( msg );
} );
}
},
Line 1,918 ⟶ 2,031:
this.catLink.style.cssText += '; text-decoration : line-through !important;';
try {
this.catLink.style.backgroundColor = HotCatHC.bg_changed;
} catch ( ex ) {}
this.originalState = this.state;
Line 1,932 ⟶ 2,045:
this.originalState = this.state;
this.state = CategoryEditor.DELETED;
this.noCommit = noCommit || HotCatHC.del_needs_diff;
var self = this;
initiateEdit(
function ( failure ) { performChanges( failure, self ); },
performChanges( failure, self );
function ( msg ) { self.state = self.originalState; alert( msg ); }
); },
function ( msg ) {
self.state = self.originalState;
alert( msg );
} );
}
}
Line 1,951 ⟶ 2,068:
} else {
try {
this.catLink.style.backgroundColor = HotCatHC.bg_changed;
} catch ( ex ) {}
}
Line 1,963 ⟶ 2,080:
 
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;
this.engineSelector.options[ i ].selected = this.engineSelector.options[ i ].value === engineName;
}
},
 
Line 1,972 ⟶ 2,087:
var v = this.text.value || '';
v = v.replace( /^(\s|_)+/, '' ); // Trim leading blanks and underscores
var re = new RegExp( '^(' + HotCatHC.category_regexp + '):' );
if ( re.test( v ) ) {v = v.substring( v.indexOf( ':' ) + 1 ).replace( /^(\s|_)+/, '' );
v = v.substring( v.indexOf( ':' ) + 1 ).replace( /^(\s|_)+u200E$/, '' ); // Trim ending left-to-right mark
if ( HC.capitalizePageNames ) v = capitalize( v );
}
 
if ( HotCat.capitalizePageNames ) { v = capitalize( v ); }
// Only update the input field if there is a difference. IE8Various appearsbrowsers to reset the selectionotherwise
// reset the selection and cursor position after each value re-assignment.
// and place the cursor at the front upon reset, which makes our autocompletetion become a
if ( this.text.value !== null && this.text.value !== v ) this.text.value = v;
// 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 ) {
var cb = callbackObj;,
var e = engine;,
var v = queryKey;,
var z = cleanKey;,
var thisObj = this;
 
function done() {
cb.callsMade++;
if ( cb.callsMade === cb.nofCalls ) {
if ( cb.exists ) { cb.allTitles.exists = true; }
 
if ( cb.normalized ) { cb.allTitles.normalized = cb.normalized; }
if ( !cb.dontCachenormalized && !suggestionConfigs[) cb.engineName ]allTitles.cache[normalized z= ] ) {cb.normalized;
 
suggestionConfigs[ cb.engineName ].cache[ z ] = cb.allTitles;
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; }
if ( cb === thisObj.callbackObj ) thisObj.callbackObj = null;
// eslint-disable-next-line no-delete-var
 
delete cb;
cb = undefined;
}
}
 
$.getJSON( url, function ( json ) {
var titles = e.handler( json, z );
uri: url,
success: functionif ( jsontitles && titles.length ) {
if ( cb.allTitles === null ) cb.allTitles = titles; else cb.allTitles = cb.allTitles.concat( titles );
var titles = e.handler( json, z );
if ( titles.exists &&) titlescb.lengthexists > 0 )= {true;
if ( titles.normalized ) cb.allTitlesnormalized === null ) {titles.normalized;
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();
}
done();
} ).fail( function ( req ) {
if ( !req ) noSuggestions = true;
cb.dontCache = true;
done();
} );
},
Line 2,047 ⟶ 2,153:
this.currentKey = null;
}
if ( this.lastInput === v && !force ) { return; } // No change
if ( this.lastInput !== v ) { checkMultiInput(); }
 
this.lastInput = v;
this.lastRealInput = v;
 
// Mark blacklisted inputs.
this.ok.disabled = v.length > 0 && HotCatHC.blacklist && HotCatHC.blacklist.test( v );
 
if ( noSuggestions ) {
// No Ajax: just make sure the list is hidden
if ( this.list ) { this.list.style.display = 'none'; }
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
if ( this.icon ) { this.icon.style.display = 'none'; }
return;
}
 
if ( !v.length === 0 ) { this.showSuggestions( [] ); return; }
this.showSuggestions( [] );
return;
}
var cleanKey = v.replace( /[\u200E\u200F\u202A-\u202E]/g, '' ).replace( wikiTextBlankRE, ' ' );
cleanKey = replaceShortcuts( cleanKey, HotCatHC.shortcuts );
cleanKey = cleanKey.replace( /^\s+|\s+$/g, '' );
if ( !cleanKey.length === 0 ) { this.showSuggestions( [] ); return; }
this.showSuggestions( [] );
return;
}
 
if ( this.callbackObj ) this.callbackObj.cancelled = true;
 
if ( this.callbackObj ) { this.callbackObj.cancelled = true; }
var engineName = suggestionConfigs[ this.engine ] ? this.engine : 'combined';
 
Line 2,079 ⟶ 2,193:
 
var engines = suggestionConfigs[ engineName ].engines;
this.callbackObj = {
allTitles: null,
{ allTitles: null, callsMade: 0, nofCalls: engines.length, noCompletion: dont_autocomplete, engineName: engineName };
callsMade: 0,
nofCalls: engines.length,
noCompletion: dont_autocomplete,
engineName: engineName
};
this.makeCalls( engines, this.callbackObj, v, cleanKey );
},
Line 2,096 ⟶ 2,215:
this.dab = null;
this.showsList = false;
if ( !this.list ) { return; }
if ( noSuggestions ) {
if ( this.list ) { this.list.style.display = 'none'; }
 
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
if ( this.iconengineSelector ) { this.iconengineSelector.style.display = 'none'; }
 
if ( this.icon ) this.icon.style.display = 'none';
 
this.inputExists = true; // Default...
return;
Line 2,106 ⟶ 2,228:
this.engineName = engineName;
if ( engineName ) {
if ( !this.engineSelector ) { this.engineName = null; }
} else {
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
}
if ( queryKey ) {
if ( this.lastInput.indexOf( queryKey ) !== 0 ) { return; }
if ( this.lastQuery && this.lastInput.indexOf( this.lastQuery ) === 0 && this.lastQuery.length > queryKey.length ) { return; }
}
this.lastQuery = queryKey;
Line 2,119 ⟶ 2,241:
var v = this.text.value.split( '|' );
var key = v.length > 1 ? '|' + v[ 1 ] : '';
v = ( HotCatHC.capitalizePageNames ? capitalize( v[ 0 ] ) : v[ 0 ] );
var vNormalized = v;
var knownToExist = titles && titles.exists;
Line 2,125 ⟶ 2,247:
if ( titles ) {
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 );
}
var vLow = vNormalized.toLowerCase();
// Strip blacklisted categories
if ( HotCatHC.blacklist ) {
for ( i = 0; i < titles.length; i++ ) {
if ( HotCatHC.blacklist.test( titles[ i ] ) ) {
titles.splice( i, 1 );
i--;
Line 2,140 ⟶ 2,262:
titles.sort(
function ( a, b ) {
if ( a === b ) { return 0; }
 
if ( a.indexOf( b ) === 0 ) { return 1; } // a begins with b: a > b
if ( ba.indexOf( ab ) === 0 ) { return -1; } // b begins with a: a < b
// 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();
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 ( ba < ab ) { return -1; }
 
if ( b < a ) return 1;
 
return 0;
} );
);
// Remove duplicates and self-references
for ( i = 0; i < titles.length; i++ ) {
if (
if ( i + 1 < titles.length && titles[ i ] === titles[ i + 1 ] ||
i + 1 < titles.length && titles[ i ] === titles[ i + 1 ] ||
conf.wgNamespaceNumber === 14 && titles[ i ] === conf.wgTitle
) {
Line 2,167 ⟶ 2,297:
}
}
if ( !titles || !titles.length === 0 ) {
if ( this.list ) { this.list.style.display = 'none'; }
 
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
 
if ( engineName && suggestionConfigs[ engineName ] && !suggestionConfigs[ engineName ].temp ) {
if ( this.icon ) { this.icon.src = armorUri( HotCatHC.existsNo ); }
 
this.inputExists = false;
}
Line 2,179 ⟶ 2,312:
var firstTitle = titles[ 0 ];
var completed = this.autoComplete( firstTitle, v, vNormalized, key, dontAutocomplete );
var existing = completed || knownToExist || firstTitle === replaceShortcuts( v, HotCatHC.shortcuts );
if ( engineName && suggestionConfigs[ engineName ] && !suggestionConfigs[ engineName ].temp ) {
this.icon.src = armorUri( existing ? HotCatHC.existsYes : HotCatHC.existsNo );
this.inputExists = existing;
}
Line 2,188 ⟶ 2,321:
if ( titles.length === 1 ) {
this.list.style.display = 'none';
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
 
return;
}
}
// (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' );
Line 2,207 ⟶ 2,342:
if ( !this.is_active ) {
this.list.style.display = 'none';
if ( this.engineSelector ) { this.engineSelector.style.display = 'none'; }
 
return;
}
var nofItems = ( this.list.options.length > HotCatHC.list_sizelistSize ? HotCatHC.list_sizelistSize : this.list.options.length );
if ( nofItems <= 1 ) { nofItems = 2; }
 
this.list.size = nofItems;
this.list.style.align = is_rtl ? 'right' : 'left';
Line 2,231 ⟶ 2,368:
// Approximate calculation of maximum list size
var maxListHeight = listh;
if ( nofItems < HotCatHC.list_sizelistSize ) { maxListHeight = ( listh / nofItems ) * HotCatHC.list_sizelistSize; }
 
function viewport( what ) {
if ( is_webkit && !document.evaluate ) {
// Safari < 3.0
return window[ 'inner' + what ];
}
var s = 'client' + what;
if ( window.opera ) {return document.body[ s ];
 
return document.body[ s ];
}
return ( document.documentElement ? document.documentElement[ s ] : 0 ) || document.body[ s ] || 0;
}
Line 2,252 ⟶ 2,388:
// IE >= 8: 0 at the far right, then increasingly positive values.
// 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 ) {
if ( !is_webkit ) result = scroll_offset( 'Width' ) - viewport( 'Width' ) - result;
 
}
// Now all have webkit behavior, i.e. zero if at the leftmost edge.
}
Line 2,271 ⟶ 2,406:
};
}
var t = 0, l = 0;
l = 0;
do {
t = t += ( node.offsetTop || 0 );
l = l += ( node.offsetLeft || 0 );
node = node.offsetParent;
} while ( node );
Line 2,283 ⟶ 2,419:
}
 
var textPos = position( this.text );,
var nl = 0;,
var nt = 0;,
var offset = 0;,
// Opera 9.5 somehow has offsetWidth = 0 here?? Use the next best value...
var textBoxWidth = this.text.offsetWidth || this.text.clientWidth;
if ( this.engineName ) {
this.engineSelector.style.zIndex = 5;
Line 2,296 ⟶ 2,432:
if ( this.engineSelector.style.display === 'none' ) {
this.engineSelector.style[ anchor ] = '-10000px';
this.engineSelector.style.top = '0px0';
this.engineSelector.style.display = '';
offset = this.engineSelector.offsetHeight;
Line 2,306 ⟶ 2,442:
}
if ( textPos.y < maxListHeight + offset + 1 ) {
// The list might extend beyond the upper border of the page. Let's avoid that by placing it
// below the input text field.
nt = this.text.offsetHeight + offset + 1;
if ( this.engineName ) { this.engineSelector.style.top = this.text.offsetHeight + 'px'; }
} else {
nt = -listh - offset - 1;
if ( this.engineName ) { this.engineSelector.style.top = -( offset + 1 ) + 'px'; }
}
this.list.style.top = nt + 'px';
Line 2,338 ⟶ 2,474:
w = view_w;
this.list.style.width = w + 'px';
if ( is_rtl ) {left = right - w; else right = left + w;
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 ( left < scroll ) {
 
relative_offset = scroll - left;
if ( is_rtl ) relative_offset = -relative_offset;
} else if ( right > scroll + view_w ) {
 
relative_offset = -( right - scroll - view_w );
if ( relative_offset ) this.list.style[ anchor ] = ( nl + relative_offset ) + 'px';
}
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 ) {
if ( newVal === actVal ) { return true; }
 
if ( dontModify || this.ime || !this.canSelect() ) { return false; }
if ( dontModify || this.ime || !this.canSelect() ) return false;
 
// 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 ) !== 0 ) {
// 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 {
Line 2,380 ⟶ 2,511:
canSelect: function () {
return this.text.setSelectionRange ||
this.text.createTextRange ||
typeof this.text.selectionStart !== 'undefined' &&
typeof this.text.selectionEnd !== 'undefined';
},
 
setSelection: function ( from, to ) {
// this.text must be focused (at least on IE)
if ( !this.text.value ) { return; }
if ( this.text.setSelectionRange ) { // e.g. khtml
this.text.setSelectionRange( from, to );
} else if ( typeof this.text.selectionStart !== 'undefined' ) {
if ( from > this.text.selectionStart ) {
this.text.selectionEnd = to;
Line 2,407 ⟶ 2,538:
 
getSelection: function () {
var from = 0, to = 0;
to = 0;
// this.text must be focused (at least on IE)
if ( !this.text.value ) {
// No text.
} else if ( typeof this.text.selectionStart !== 'undefined' ) {
from = this.text.selectionStart;
to = this.text.selectionEnd;
Line 2,427 ⟶ 2,559:
from = textRng.text.length;
} catch ( notFocused ) {
from = this.text.value.length; to = from; // At end of text
to = from; // At end of text
}
}
}
return { start: from, end: to };
start: from,
end: to
};
},
 
Line 2,448 ⟶ 2,584:
break;
case PGUP:
dir = -HotCatHC.list_sizelistSize;
break;
case PGDOWN:
dir = HotCatHC.list_sizelistSize;
break;
case ESC: // Inhibit default behavior (revert to last real input in FF: we do that ourselves)
Line 2,458 ⟶ 2,594:
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
Line 2,465 ⟶ 2,601:
} 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();
Line 2,474 ⟶ 2,611:
 
highlightSuggestion: function ( dir ) {
if ( noSuggestions || !this.list || this.list.style.display === 'none' ) { return false; }
 
var curr = this.list.selectedIndex;
var tgt = -1;
if ( dir === 0 ) {
if ( curr < 0 || curr >= this.list.options.length ) { return false; }
 
tgt = curr;
} else {
tgt = curr < 0 ? 0 : curr + dir;
tgt = tgt < 0 ? 0 : tgt;
if ( tgt >= this.list.options.length ) { tgt = this.list.options.length - 1; }
}
if ( tgt !== curr || dir === 0 ) {
if ( curr >= 0 && curr < this.list.options.length && dir !== 0 ) {this.list.options[ curr ].selected = false;
 
this.list.options[ curr ].selected = false;
}
this.list.options[ tgt ].selected = true;
// Get current input text
Line 2,496 ⟶ 2,634:
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.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( HotCatHC.existsYes ); }
 
this.state = CategoryEditor.CHANGE_PENDING;
}
Line 2,509 ⟶ 2,646:
 
resetKeySelection: function () {
if ( noSuggestions || !this.list || this.list.style.display === 'none' ) { return false; }
 
var curr = this.list.selectedIndex;
if ( curr >= 0 && curr < this.list.options.length ) {
Line 2,528 ⟶ 2,666:
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 = ( typeof window.JSconfig !== 'undefined' && JSconfig.keys ) ? JSconfig.keys : {};
HotCatHC.dont_add_to_watchlist = ( window.hotcat_dont_add_to_watchlist !== undefined ?
( typeof !!window.hotcat_dont_add_to_watchlist !== 'undefined' ?:
( config.HotCatDontAddToWatchlist !== undefined ? config.HotCatDontAddToWatchlist :
!!window.hotcat_dont_add_to_watchlist :
HC.dont_add_to_watchlist ) );
( typeof config.HotCatDontAddToWatchlist !== 'undefined' ?
HC.no_autocommit = ( window.hotcat_no_autocommit !== undefined ?
config.HotCatDontAddToWatchlist :
!!window.hotcat_no_autocommit : ( config.HotCatNoAutoCommit !== undefined ?
HotCat.dont_add_to_watchlist
config.HotCatNoAutoCommit :
)
// On talk namespace default autocommit off
);
( conf.wgNamespaceNumber % 2 ?
HotCat.no_autocommit =
true : HC.no_autocommit ) ) );
( typeof window.hotcat_no_autocommit !== 'undefined' ?
HC.del_needs_diff = ( window.hotcat_del_needs_diff !== undefined ?
!!window.hotcat_no_autocommit :
!!window.hotcat_del_needs_diff :
( typeof config.HotCatNoAutoCommit !== 'undefined' ?
( config.HotCatNoAutoCommitHotCatDelNeedsDiff !== undefined :?
config.HotCatDelNeedsDiff :
HotCat.no_autocommit
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;
HotCat.del_needs_diff =
HC.suggestions = window.hotcat_suggestions || config.HotCatSuggestions || HC.suggestions;
( typeof window.hotcat_del_needs_diff !== 'undefined' ?
if ( typeof HC.suggestions !== 'string' || !suggestionConfigs[ HC.suggestions ] ) HC.suggestions = 'combined';
!!window.hotcat_del_needs_diff :
 
( typeof config.HotCatDelNeedsDiff !== 'undefined' ?
HC.fixed_search = ( window.hotcat_suggestions_fixed !== undefined ?
config.HotCatDelNeedsDiff :
!!window.hotcat_suggestions_fixed : ( config.HotCatFixedSuggestions !== undefined ?
HotCat.del_needs_diff
config.HotCatFixedSuggestions : HC.fixed_search ) );
)
HC.single_minor = ( window.hotcat_single_changes_are_minor !== undefined ?
);
!!window.hotcat_single_changes_are_minor :
HotCat.suggest_delay = window.hotcat_suggestion_delay ||
( config.HotCatMinorSingleChanges !== undefined ?
config.HotCatSuggestionDelay ||
config.HotCatMinorSingleChanges :
HotCat.suggest_delay;
HC.single_minor ) );
HotCat.editbox_width = window.hotcat_editbox_width ||
HC.bg_changed = window.hotcat_changed_background || config.HotCatChangedBackground || HC.bg_changed;
config.HotCatEditBoxWidth ||
HC.use_up_down = ( window.hotcat_use_category_links !== undefined ?
HotCat.editbox_width;
!!window.hotcat_use_category_links :
HotCat.suggestions = window.hotcat_suggestions ||
( config.HotCatSuggestionsHotCatUseCategoryLinks !== undefined ||?
config.HotCatUseCategoryLinks :
HotCat.suggestions;
HC.use_up_down ) );
if ( typeof HotCat.suggestions !== 'string' || !suggestionConfigs[ HotCat.suggestions ] ) {
HC.listSize = window.hotcat_list_size || config.HotCatListSize || HC.listSize;
HotCat.suggestions = 'combined';
if ( conf.wgDBname !== 'commonswiki' ) HC.changeTag = config.HotCatChangeTag || '';
 
// The next whole shebang is needed, because manual tags get not submitted except of save
if ( HC.changeTag ) {
var eForm = document.editform,
catRegExp = new RegExp( '^\\[\\[(' + HC.category_regexp + '):' ),
oldTxt;
// Returns true if minor change
var isMinorChange = function () {
var newTxt = eForm.wpTextbox1;
if ( !newTxt ) return;
newTxt = newTxt.value;
var oldLines = oldTxt.match( /^.*$/gm ),
newLines = newTxt.match( /^.*$/gm ),
cArr; // changes
var except = function ( aArr, bArr ) {
var result = [],
lArr, // larger
sArr; // smaller
if ( aArr.length < bArr.length ) {
lArr = bArr;
sArr = aArr;
} else {
lArr = aArr;
sArr = bArr;
}
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 ), '' );
 
} );
var removeChangeTag = function () {
$( eForm.wpTextbox1 ).add( sum ).one( 'input', function () {
window.setTimeout( function () {
if ( !isMinorChange() ) $ct.val( '' );
else removeChangeTag();
}, 500 );
} );
};
removeChangeTag();
}
}
}
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
HotCatHC.list_sizelistSize = parseInt( HotCatHC.list_sizelistSize, 10 );
if ( isNaN( HotCatHC.list_sizelistSize ) || HotCatHC.list_sizelistSize < 5 ) { HotCatHC.list_sizelistSize = 5; }
 
if ( HotCat.list_size > 15 ) { HotCat.list_size = 15; }
HC.listSize = Math.min( HC.listSize, 30 ); // Max size
 
// Localize search engine names
if ( HotCatHC.engine_names ) {
for ( var key in HotCatHC.engine_names ) {
if ( suggestionConfigs[ key ] && HotCatHC.engine_names[ key ] ) {suggestionConfigs[ key ].name = HC.engine_names[ key ];
 
suggestionConfigs[ key ].name = HotCat.engine_names[ key ];
}
}
}
// Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]]
Line 2,633 ⟶ 2,813:
case 'cologneblue':
container = document.getElementById( 'quickbar' );
//* Fallfall 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 (
if ( param( 'title', lks[ i ].href ) === conf.wgPageName &&
param( 'actiontitle', lks[ i ].href ) === 'edit' ) { return true;conf.wgPageName }&&
param( 'action', lks[ i ].href ) === 'edit'
) {
return true;
}
}
return false;
Line 2,646 ⟶ 2,830:
// 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++ ) {
var edit = editors[ i ];
if ( edit.state === CategoryEditor.OPEN ) {
edit.cancel();
} else if ( edit.state === CategoryEditor.CHANGE_PENDING ) {
edit.sanitizeInput();
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 {
edit.currentCategory = v;
edit.currentKey = key;
edit.currentExists = this.inputExists;
edit.close();
}
}
}
}
Line 2,655 ⟶ 2,864:
if ( !ip ) {
ip = document.getElementById( 'wpDestFile' );
while ( ip && ip.nodeName.toLowerCase() !== 'table' ) { ip = ip.parentNode; }
}
if ( !ip ) { return; }
var reupload = document.getElementById( 'wpForReUpload' );
var destFile = document.getElementById( 'wpDestFile' );
if (
if ( ( reupload && !!reupload.value ) ||
( reupload && !!reupload.value ) ||
( destFile && ( destFile.disabled || destFile.readOnly ) ) ) { return; } // re-upload form...
( 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' );
Line 2,676 ⟶ 2,889:
// Create the label
var label = null;
if ( window.UFUI && window.UIElements && UFUI.getLabel instanceof Function ) {
if ( typeof UFUI !== 'undefined' &&
typeof UIElements !== 'undefined' &&
typeof UFUI.getLabel === 'function'
) {
try {
label = UFUI.getLabel( 'wpCategoriesUploadLbl' );
Line 2,688 ⟶ 2,898:
if ( !label ) {
labelCell.id = 'hotcatLabel';
labelCell.appendChild( make( HotCatHC.categories, true ) );
} else {
labelCell.id = 'hotcatLabelTranslated';
Line 2,707 ⟶ 2,917:
if ( oldSubmit ) {
if ( typeof oldSubmit === 'string' ) {
// eslint-disable-next-line no-eval
do_submit = eval( oldSubmit );
} else if ( typeof oldSubmit ===instanceof 'function'Function ) {
do_submit = oldSubmit.apply( form, arguments );
}
}
if ( !do_submit ) {return false;
return false;
}
closeForm();
// Copy the categories
var eb = document.getElementById( 'wpUploadDescription' ) || document.getElementById( 'wpDesc' );
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 = '[[' + HotCatHC.category_canonical + ':' + t + ( key ? '|' + key : '' ) + ']]';
// Only add if not already present
var cleanedText = eb.value
Line 2,736 ⟶ 2,943:
}
if ( addedOne ) {
// Remove "subst:unc" added by Flinfo if it didn't find categories
eb.value = eb.value.replace( /\{\{subst:unc\}\}/g, '' );
}
Line 2,748 ⟶ 2,955:
 
function isOnPage( span ) {
if ( span.firstChild.nodeType !== Node.ELEMENT_NODE ) { return null; }
 
var catTitle = title( span.firstChild.getAttribute( 'href', 2 ) );
ifvar ( !catTitle )= {title( returnspan.firstChild.getAttribute( null;'href' }) );
if ( !catTitle ) return null;
 
catTitle = catTitle.substr( catTitle.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
if ( HotCatHC.blacklist && HotCatHC.blacklist.test( catTitle ) ) { return null; }
 
var result = { title: catTitle, match: [ '', '', '' ] };
var result = {
if ( pageText === null ) { return result; }
title: catTitle,
match: [ '', '', '' ]
};
if ( pageText === null ) return result;
 
if ( cleanedText === null ) {
cleanedText = pageText
Line 2,768 ⟶ 2,982:
 
function findByClass( scope, tag, className ) {
var result = window.jQuery$( scope ).find( tag + '.' + className );
return ( result && result.length ) ? result[ 0 ] : null;
}
 
function setup( additionalWork ) {
if ( initialized ) { return; }
initialized = true;
if ( setupTimeout ) {
Line 2,790 ⟶ 3,004:
if ( !hiddenCats ) {
footer = findByClass( document, 'div', 'printfooter' );
if ( !footer ) { return; } // Don't know where to insert the category line
}
catLine = make( 'div' );
Line 2,798 ⟶ 3,012:
var label = make( 'a' );
label.href = conf.wgArticlePath.replace( '$1', 'Special:Categories' );
label.title = HotCatHC.categories;
label.appendChild( make( HotCatHC.categories, true ) );
catLine.appendChild( label );
catLine.appendChild( make( ':', true ) );
Line 2,811 ⟶ 3,025:
container.className = 'catlinks noprint';
container.style.display = '';
if ( !hiddenCats ) {container.appendChild( catLine ); else container.insertBefore( catLine, 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
Line 2,824 ⟶ 3,034:
var i;
var cats = line.getElementsByTagName( 'li' );
if ( cats.length > 0 ) {
newDOM = true; line = cats[ 0 ].parentNode;
line = cats[ 0 ].parentNode;
} else {
cats = line.getElementsByTagName( 'span' );
Line 2,831 ⟶ 3,042:
// 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 && line ) {
// eslint-disable-next-line no-new
new CategoryEditor( line, copyCats[ i ], test.title, test.match[ 2 ], is_hidden );
}
}
return copyCats.length > 0 ? copyCats[ copyCats.length - 1 ] : null;
}
 
Line 2,848 ⟶ 3,059:
if ( !onUpload ) {
if ( pageText !== null && hiddenCats ) {
if ( is_rtl ) { hiddenCats.dir = 'rtl'; }
createEditors( hiddenCats, true );
}
Line 2,854 ⟶ 3,065:
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>' + HotCatHC.addmulti + '</a>)';
var lk = multiSpan.getElementsByTagName( 'a' )[ 0 ];
lk.onclick = function ( evt ) { setMultiInput(); checkMultiInput(); return evtKill( evt ); };
setMultiInput();
lk.title = HotCat.multi_tooltip;
checkMultiInput();
return evtKill( evt );
};
lk.title = HC.multi_tooltip;
lk.style.cursor = 'pointer';
}
cleanedText = null;
if ( typeof additionalWork ===instanceof 'function'Function ) { additionalWork(); }
setupCompletedmw.loadedhook( 'hotcat.ready' ).fire(); // Trigger signal; executeExecute registered callback functions
$( 'body' ).trigger( 'hotcatSetupCompleted' );
}
 
function setPage( json ) {
var startTime = null;
if ( json && json.query ) {
if ( json.query.pages ) {
var page = json.query.pages[ conf.wgArticleId === 0 ? '-1' : String( conf.wgArticleId ) ];
if ( page ) {
if ( page.revisions && page.revisions.length > 0 ) {
// Revisions are sorted by revision ID, hence [ 0 ] is the one we asked for, and possibly there's a [ 1 ] if we're
// not on the latest revision (edit conflicts and such).
pageText = page.revisions[ 0 ][ '*' ];
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 > 0 ) {
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
HotCat.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 = !HotCat.dont_add_to_watchlist && json.query.userinfo.options.watchcreations === '1';
watchEdit = !HotCat.dont_add_to_watchlist && json.query.userinfo.options.watchdefault === '1';
minorEdits = json.query.userinfo.options.minordefault === 1;
// If the user has the "All edits are minor" preference enabled, we should honor that
// for single category changes, no matter what the site configuration is.
if ( minorEdits ) { HotCat.single_minor = true; }
}
}
}
 
function createCommitForm() {
if ( commitForm ) { return; }
var formContainer = make( 'div' );
formContainer.style.display = 'none';
Line 2,929 ⟶ 3,093:
formContainer.innerHTML =
'<form id="hotcatCommitForm" method="post" enctype="multipart/form-data" action="' +
conf.wgScript + '?title=' + encodeURIComponent( conf.wgPageName ) + '&action=submit">' +
'&action<input type=submit"hidden" name="wpTextbox1">' +
'<input type="hidden" name="wpTextbox1model" /value="' + conf.wgPageContentModel + '">' +
'<input type="hidden" name="modelformat" value="wikitexttext/x-wiki" />' +
'<input type="hidden" name="formatwpSummary" value="text/x-wiki" />' +
'<input type="hiddencheckbox" name="wpSummarywpMinoredit" value="1" />' +
'<input type="checkbox" name="wpMinoreditwpWatchthis" value="1" />' +
'<input type="checkboxhidden" name="wpWatchthiswpAutoSummary" value="1d41d8cd98f00b204e9800998ecf8427e" />' +
'<input type="hidden" name="wpAutoSummarywpEdittime" value="" />' +
'<input type="hidden" name="wpEdittimewpStarttime" />' +
'<input type="hidden" name="wpStarttimewpDiff" /value="wpDiff">' +
'<input type="hidden" name="wpDiffoldid" value="wpDiff0" />' +
'<input type="hiddensubmit" name="oldidhcCommit" value="0hcCommit" />' +
'<input type="submithidden" name="hcCommitwpEditToken" value="hcCommit" />' +
'<input type="hidden" name="wpEditTokenwpUltimateParam" /value="1">' +
'<input type="hidden" name="wpUltimateParamwpChangeTags" value="1" />' +
'<input type="hidden" value="ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ" name="wpUnicodeCheck" />' +
'</form>';
commitForm = document.getElementById( 'hotcatCommitForm' );
Line 2,952 ⟶ 3,116:
function getPage() {
// We know we have an article here.
if ( !conf.wgArticleId === 0 ) {
// Doesn't exist yet. Disable on non-existing User pages -- might be a global user page.
if ( conf.wgNamespaceNumber === 2 ) {return;
// Disable on non-existing User pages -- might be a global user page.
return;
}
pageText = '';
pageTime = null;
Line 2,963 ⟶ 3,124:
} 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 );
sHC.typestart = 'text/javascript';function ( json ) {
HotCat.start = function ( json ) { setPage( json ); setup( createCommitForm ); };
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.
setup( createCommitForm );
}, 4000 ); // 4 sec, just in case getting the wikitext takes longer.
}
}
 
function run() {
if ( HotCat.started ) { return; }
HotCat.started = true;
loadTrigger.register( really_run );
}
 
function really_run() {
initialize();
 
if ( !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();
}
}
 
// Legacy stuff
 
function closeForm() {
// Close all open editors without redirect resolution and other asynchronous stuff.
for ( var i = 0; i < editors.length; i++ ) {
if ( editors[ i ].state === CategoryEditor.OPEN ) {
editors[ i ].cancel();
} else if ( editors[ i ].state === CategoryEditor.CHANGE_PENDING ) {
editors[ i ].sanitizeInput();
var value = editors[ i ].text.value.split( '|' );
var key = null;
if ( value.length > 1 ) { key = value[ 1 ]; }
var v = value[ 0 ].replace( /_/g, ' ' ).replace( /^\s+|\s+$/g, '' );
if ( v.length === 0 ) {
editors[ i ].cancel();
} else {
editors[ i ].currentCategory = v;
editors[ i ].currentKey = key;
editors[ i ].currentExists = this.inputExists;
editors[ i ].close();
}
}
}
}
 
function getState() {
var result = null;
for ( var i = 0; i < editors.length; i++ ) {
var text = editors[ i ].currentCategory;
var key = editors[ i ].currentKey;
if ( text && text.length > 0 ) {
if ( key !== null ) { text += '|' + key; }
if ( result === null ) {
result = text;
} else {
result = result + '\n' + text;
}
}
}
return result;
}
 
function setState( state ) {
var cats = state.split( '\n' );
if ( !cats.length === 0 ) { return null; }
 
if ( initialized && editors.length === 1 && editors[ 0 ].isAddCategory ) {
// Insert new spans and create new editors for them.
Line 3,051 ⟶ 3,150:
var i;
for ( i = 0; i < cats.length; i++ ) {
if ( !cats[ i ].length === 0 ) { continue; }
var cat = cats[ i ].split( '|' );
var key = cat.length > 1 ? cat[ 1 ] : null;
cat = cat[ 0 ];
var lk = make( 'a' ); lk.href = wikiPagePath( HotCat.category_canonical + ':' + cat );
lk.href = wikiPagePath( HC.category_canonical + ':' + cat );
lk.appendChild( make( cat, true ) );
lk.title = cat;
var span = make( 'span' );
span.appendChild( lk );
if ( !i === 0 ) { catLine.insertBefore( make( ' ', true ), before ); }
 
catLine.insertBefore( span, before );
if ( before && i + 1 < cats.length ) { parent.insertBefore( make( ' | ', true ), before ); }
 
newSpans.push( { element: span, title: cat, key: key } );
newSpans.push( {
element: span,
title: cat,
key: key
} );
}
// And change the last one...
if ( before ) {before.parentNode.insertBefore( make( ' | ', true ), before );
 
before.parentNode.insertBefore( make( ' | ', true ), before );
}
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(); };
return getState();
window.hotcat_set_state = function ( state ) { return setState( state ); };
};
window.hotcat_close_form = function () { closeForm(); };
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
Line 3,092 ⟶ 3,241:
// Reload HotCat after (VE) edits (bug T103285)
mw.hook( 'postEdit' ).add( function () {
// Reset HotCat in case this is a soft reload (VEe.g. VisualEditor edit), unless the categories
// were not re-rendered and our interface is still there (e.g. DiscussionTools edit)
if ( document.querySelector( '#catlinks .hotcatlink' ) ) {
return;
}
catLine = null;
editors = [];
initialized = false;
HotCatHC.started = false;
run();
} );
}
 
// We can safely trigger just after user configuration is loaded.
var startHotCat = function () {
// Use always() instead of then() to also start HotCat if the user module has problems.
$( run );
$.when( mw.loader.using( 'user' ), $.ready ).always( 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 ) );
 
// </nowiki>
Cookies help us deliver our services. By using our services, you agree to our use of cookies.