10,163
edits
(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:
/**
HotCat V2.43
This code should run on any MediaWiki installation >= MW 1.27.
*/
// <nowiki>
/* eslint-disable vars-on-top, one-var, camelcase, no-alert, curly */
/* global jQuery, mediaWiki, UFUI, JSconfig, UploadForm */
/* jslint strict:false, nonew:false, bitwise:true */
( function ( $, mw ) {
// 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
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 ( ( window.HotCat && !window.HotCat.nodeName ) ||
conf.wgAction === 'edit' ) // Not on edit mode
return;
// Configuration stuff.
var HC = window.HotCat = {
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 ' +
// 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: {
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
},
// 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*
// 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'
},
// Override the decision of whether HotCat should help users by automatically
// capitalising the title in the user input text if the wiki has case-sensitive page names.
// Basically, this will make an API query to check the MediaWiki configuration and HotCat then sets
// this to true for most wikis, and to false on Wiktionary.
//
// You can set this directly if there is a problem with it. For example, Georgian Wikipedia (kawiki),
// is known to have different capitalisation logic between MediaWiki PHP and JavaScript. As such, automatic
// case changes in JavaScript by HotCat would be wrong.
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: '#
// 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
// 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 )
window.HotCat.shortcuts = window.HotCat.shortcuts || {};
for ( var k in map ) {
if ( !map.hasOwnProperty( k ) || typeof k !== 'string' )
var v = map[ k ];
if ( typeof v !== 'string' )
k = k.replace( /^\s+|\s+$/g, '' );
v = v.replace( /^\s+|\s+$/g, '' );
if ( !k.length
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;
var noSuggestions = false;
// Define methods in a closure so that self reference is available,
// also allows method calls to be detached.
var self = this;
self.queue = [];
self.needed = needed;
self.register = function ( callback ) {
if ( self.needed <= 0 ) callback(); // Execute directly
else self.queue.push( callback );
};
self.loaded = function () {
self.needed--;
// Run queued callbacks once
}
};
}
// Used to delay running the HotCat setup until /local_defaults and localizations have been loaded.
var loadTrigger = new LoadTrigger( 2 );
function load( uri, callback ) {
var s = document.createElement( 'script' );
s.
var called = false;
s.onload = s.onerror = function
if (
callback();
}
if ( s.parentNode ) {
s.parentNode.removeChild( s );
}
};
document.head.appendChild( s );
}
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 ) === '//' )
}
// 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
// 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
if (
// 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=' +
} 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
if ( !name || !name.length
var regex_name = '';
for ( var i = 0; i < name.length; i++ ) {
var initial = name.
if ( ll === ul )
}
return regex_name
Line 420 ⟶ 344:
fallback = fallback.toLowerCase();
var canonical = formattedNamespaces[ String( namespaceNumber ) ].toLowerCase();
var regexp =
if ( fallback && canonical !== fallback )
if ( namespaceIds ) {
for ( var cat_name in namespaceIds ) {
Line 430 ⟶ 355:
namespaceIds[ cat_name ] === namespaceNumber
) {
regexp += '|' +
}
}
Line 437 ⟶ 362:
}
if ( formattedNamespaces[ '10' ] )
// 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
// these few operations here.
function make( arg, literal ) {
if ( !arg )
return literal ? document.createTextNode( arg ) : document.createElement( arg );
}
function param( name, uri ) {
var re = new RegExp( '[&?]' + name + '=([^&#]*)' );
var m = re.exec( uri );
if ( m && m.length > 1 )
return null;
}
function title( href ) {
if ( !href )
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 )
if ( href.indexOf( prefix ) && prefix.substring( 0, 2 ) === '//' ) prefix = document.location.protocol + prefix; // Protocol-relative wgServer?
if ( href.indexOf( prefix ) === 0 ) return decodeURIComponent( href.substring( prefix.length ) );
}
return null;
Line 482 ⟶ 403:
}
function capitalize( str ) {
if ( !str || !str.length
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.replace( re, function ( match, prefix, idx, key, alpha ) {
if ( prefix === 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: ']'
} );
return function ( str, map ) {
var s = replaceHash( str, map );
return
};
}() );
Line 539 ⟶ 465:
var findCatsRE =
function replaceByBlanks( match ) {
Line 547 ⟶ 473:
function find_category( wikitext, category, once ) {
var cat_regex = null;
if (
cat_regex = new RegExp(
'\\{\\{' + wikiTextBlankOrBidi + '(' +
'(?:' +
wikiTextBlankOrBidi + '(\\|.*?)?\\}\\}',
'g'
Line 558 ⟶ 484:
var initial = cat_name.substr( 0, 1 );
cat_regex = new RegExp(
'\\[\\[' + wikiTextBlankOrBidi + '(' +
( initial === '\\' || !
initial :
'[' + initial.toUpperCase() + initial.toLowerCase() + ']' ) +
cat_name.substring( 1 ).replace( wikiTextBlankRE, wikiTextBlank ) +
wikiTextBlankOrBidi + '(\\|.*?)?\\]\\]',
Line 568 ⟶ 493:
);
}
if ( once )
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
} );
}
result.re = cat_regex;
Line 592 ⟶ 520:
var index = -1;
findCatsRE.lastIndex = 0;
while ( findCatsRE.exec( copiedtext ) !== null )
if ( index < 0 ) {
// Find the index of the first interlanguage link...
var match = null;
if ( !interlanguageRE ) {
match = /((^|\n\r?)(\[\[\s*(([a-z]{2,3}(-[a-z]+)*)|simple|tokipona)\s*:[^\]]+\]\]\s*))+$/.exec( copiedtext );
} else {
match = interlanguageRE.exec( copiedtext );
}
if ( match )
return {
idx: index,
onCat: false
};
}
return {
idx: index,
onCat: index >= 0
};
}
var summary = []
keyChange = ( toRemove && toAdd && toRemove === toAdd && toAdd.length ),
matches;
if ( key ) key = '|' + key;
if ( toRemove && toRemove.length
matches = find_category( wikitext, toRemove );
if ( !matches || !matches.length
return {
text: wikitext,
summary: summary,
error: HC.messages.cat_notFound.replace( /\$1/g, toRemove )
};
} else {
var before = wikitext.substring( 0, matches[ 0 ].match.index )
if ( matches.length > 1 ) {
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 )
// 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 )
var j = 0;
while ( j < after.length && after.charAt( j ) !== '\n' && after.substr( j, 1 ).search( /\s/ ) >= 0 )
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 ); else before = '';
if ( j < after.length ) after = after.substring( j ); else after = '';
if (
before.length
after.length
) {
before += ' ';
}
cat_point = before.length;
if ( cat_point === 0 && after.length
wikitext = before + after;
if ( !keyChange ) {
if (
}
}
}
// Add
if ( toAdd && toAdd.length ) {
matches = find_category( wikitext, toAdd );
if ( matches && matches.length
// Already exists
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
} else {
if ( wikitext.length
wikitext += ( wikitext.length ? '\n' : '' ) + newcatstring;
}
if ( keyChange ) {
var k = key || '';
if ( k.length
summary.push( substitute( HC.messages.cat_keychange, [ null, toAdd, k ] ) );
} else {
summary.push(
}
if (
var txt = wikitext.replace(
if ( txt.length !== wikitext.length ) {
wikitext = txt;
summary.push(
}
}
}
}
return {
text: wikitext,
summary: summary,
error: null
};
}
Line 721 ⟶ 663:
function evtKeys( e ) {
/* eslint-disable no-bitwise */
var code = 0;
if (
if ( e.ctrlKey || e.metaKey )
if ( e.shiftKey ) code |= 2;
}
return code;
}
function evtKill( e ) {
if ( e.preventDefault ) {
e.preventDefault();
e.stopPropagation();
Line 746 ⟶ 683:
}
var catLine = null
function
this.initialize.apply( this, arguments );
}
function
var startTime = null;
if ( json && json.query ) {
var page = json.query.pages[ !conf.wgArticleId ? '-1' : String( conf.wgArticleId ) ];
if ( page.revisions && page.revisions.length ) {
// 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;
}
}
}
var saveInProgress = false;
function initiateEdit( doEdit, failure ) {
if ( saveInProgress )
saveInProgress = true;
var oldButtonState;
Line 820 ⟶ 772:
function fail() {
saveInProgress = false;
if ( commitButton )
failure.apply( this, arguments );
}
// Must use Ajax here to get the user options and the edit token.
$.getJSON(
conf.wgServer + conf.wgScriptPath + '/api.php?' +
'format=json&action=query&rawcontinue=&titles=' + encodeURIComponent( conf.wgPageName ) +
'&prop=info%7Crevisions%7Clanglinks&inprop=watched&intoken=edit&rvprop=content%7Ctimestamp%7Cids%7Cuser&lllimit=500' +
'&rvlimit=2&rvdir=newer&rvstartid=' + conf.wgCurRevisionId + '&meta=siteinfo%7Cuserinfo&uiprop=options',
function ( json ) {
setPage( json );
doEdit( fail );
}
).fail( function ( req ) {
fail( req.status + ' ' + req.statusText );
} );
}
function multiChangeMsg( count ) {
var msg =
if ( typeof msg !== 'string' && msg.length )
if ( mw.language && mw.language.convertPlural ) { 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(
return;
}
// Backwards compatibility after message change (added $2 to cat_keychange)
if (
// More backwards-compatibility with earlier HotCat versions:
if ( !
// 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
if ( singleEditor && !singleEditor.noCommit && !
// 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 )
} else {
action = commitForm.wpSave;
if ( action )
}
var result = {
text: pageText
},
deleted = [],
changes = 0,
toEdit = singleEditor ? [ singleEditor ] : editors,
error = null,
edit,
i;
for ( i = 0; i < toEdit.length; i++ ) {
if ( edit.state === CategoryEditor.CHANGED ) {
result = change_category(
result.text,
if ( !result.error ) {
changes++;
if ( !
added.push(
} else {
changed.push( {
from: edit.originalCategory,
to: edit.currentCategory
} );
}
} else if ( error === null ) {
Line 913 ⟶ 884:
}
} else if (
result = change_category(
result.text,
edit.originalCategory,
if ( !result.error ) {
changes++;
deleted.push(
} 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 )
}
// Fill in the form and submit it
commitForm.wpMinoredit.checked = minorEdits;
commitForm.wpWatchthis.checked = !conf.wgArticleId
if ( conf.wgArticleId
// Prepare change-tag save
if ( HC.changeTag ) {
commitForm.wpChangeTags.value = HC.changeTag;
HC.messages.using = '';
HC.messages.prefix = '';
}
} else {
commitForm.wpAutoSummary.value = HC.changeTag;
}
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++ )
if ( deleted.length === 1 ) shortSummary.push( '−' + substitute( HC.messages.short_catchange, [ null, deleted[ 0 ] ] ) ); else if ( deleted.length ) shortSummary.push( '− ' + multiChangeMsg( deleted.length ) );
// Added
for ( i = 0; i < added.length; i++ )
if ( added.length === 1 ) shortSummary.push( '+' + substitute( HC.messages.short_catchange, [ null, added[ 0 ] ] ) ); else if ( added.length ) 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(
substitute( HC.messages.short_catchange, [ null, changed[ i ].to ] )
);
} else {
summary.push( '±' + substitute(
}
}
if ( changed.length === 1 ) {
if ( changed[ 0 ].from !== changed[ 0 ].to ) {
shortSummary.push(
substitute( HC.messages.short_catchange, [ null, changed[ 0 ].to ] )
);
} else {
shortSummary.push( '±' + substitute(
}
} else if ( changed.length
shortSummary.push( '± ' + multiChangeMsg( changed.length ) );
}
if ( summary.length
summary = summary.join(
if ( summary.length > 200 -
commitForm.wpSummary.value = HC.messages.prefix + summary + HC.messages.using;
}
}
}
commitForm.wpTextbox1.value = result.text;
commitForm.wpStarttime.value = serverTime || currentTimestamp();
commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value;
if ( selfEditConflict )
// Submit the form in a way that triggers onsubmit events: commitForm.submit() doesn't.
commitForm.hcCommit.click();
}
function resolveOne( page, toResolve ) {
var cats = page.categories
for ( i = 0; i < toResolve.length; i++ ) {
if (
// 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 =
}
if ( is_missing )
if ( !is_redir && cats && (
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 ===
is_dab = true
break;
} else if ( cat === HC.redir_category ) {
is_redir = true;
break;
}
}
}
}
if ( !is_redir && !is_dab )
if ( !lks || !lks.length
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
) {
// 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 ( !
}
}
if ( !titles.length
for ( i = 0; i < toResolve.length; i++ ) {
if (
toResolve[ i ].inputExists = true; // Might actually be wrong if it's a redirect pointing to a non-existing category
toResolve[ i ].icon.src =
if ( titles.length > 1 ) {
toResolve[ i ].dab = titles;
Line 1,096 ⟶ 1,038:
function resolveRedirects( toResolve, params ) {
if ( !params || !params.query || !params.query.pages )
for ( var p in params.query.pages )
}
function
var
for (
toResolve[ i ].dab = null;
}
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 );
}
}
}
}
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 )
} else {
if ( resolved[ i ].acceptCheck( true ) )
}
}
Line 1,130 ⟶ 1,162:
showDab( firstDab );
} else if ( !dontChange ) {
initiateEdit( function ( failure ) {
performChanges( failure );
}, function ( msg ) {
alert( msg );
} );
}
} );
}
function setMultiInput() {
if ( commitButton || onUpload ) return;
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 )
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 ] )
} else {
titles.splice( i, 1 ); // Nope, it's not a category after all.
Line 1,159 ⟶ 1,215:
}
titles.exists = exists;
if ( queryKey !== key )
// 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++ )
return titles;
}
Line 1,188 ⟶ 1,244:
var titles = [ title ];
titles.exists = true;
if ( queryKey !== title )
// 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++ )
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++ )
return titles;
}
Line 1,228 ⟶ 1,283:
var suggestionConfigs = {
searchindex: {
name: 'Search index',
engines: [ 'opensearch' ],
cache: {},
show: 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
}
};
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 )
}
// Event keyCodes that we handle in the text input field/suggestion list.
var BS = 8,
TAB = 9,
RET = 13,
ESC = 27,
SPACE = 32,
PGUP = 33,
PGDOWN = 34,
UP = 38,
DOWN = 40,
DEL = 46,
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.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.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 )
this.isAddCategory = false;
this.catLink = span.firstChild;
Line 1,357 ⟶ 1,413:
// Create change and del links
this.makeLinkSpan();
if ( !this.originalExists && this.upDownLinks )
span.appendChild( this.linkSpan );
}
this.originalHidden = is_hidden;
this.line = line;
this.engine =
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 )
editors[ editors.length ] = this;
},
Line 1,383 ⟶ 1,439:
this.normalLinks = make( 'span' );
var lk = null;
if ( this.originalCategory && this.originalCategory.length
lk = make( 'a'
lk.href = '#catlinks';
lk.onclick = this.remove.bind( this );
lk.appendChild( make( HC.links.remove, true ) );
lk.title = HC.tooltips.remove;
this.normalLinks.appendChild( make( ' ', true ) );
this.normalLinks.appendChild( lk );
}
if ( !
lk = make( 'a'
lk.href = '#catlinks';
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 &&
this.upDownLinks = make( 'span' );
lk = make( 'a'
lk.href = '#catlinks';
lk.onclick = this.down.bind( this );
lk.appendChild( make( HC.links.down, true ) );
lk.title = HC.tooltips.down;
this.upDownLinks.appendChild( make( ' ', true ) );
this.upDownLinks.appendChild( lk );
lk = make( 'a'
lk.href = '#catlinks';
lk.onclick = this.up.bind( this );
lk.appendChild( make( HC.links.up, true ) );
lk.title = HC.tooltips.up;
this.upDownLinks.appendChild( make( ' ', true ) );
this.upDownLinks.appendChild( lk );
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.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.state = CategoryEditor.CHANGE_PENDING;
var self = this;
window.setTimeout( function () {
self.textchange( dont_autocomplete );
}, HC.suggest_delay );
},
makeForm: function () {
var form = make( 'form' );
form.method = 'POST'
form.onsubmit = this.accept.bind( this );
this.form = form;
var self = this;
var text = make( 'input' )
text.type = 'text';
text.size = HC.editbox_width;
if ( !noSuggestions ) {
// Be careful here to handle IME input. This is browser/OS/IME dependent, but basically there are two mechanisms:
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.
text.onkeyup = function ( evt ) {
if ( self.ime && self.lastKey === IME && !self.usesComposition && ( key === TAB || key === RET || key === ESC || key === SPACE ) ) self.ime = false;
if ( self.ime ) return true;
}
}
// Also do this for ESC as a workaround for Firefox bug 524360
// https://bugzilla.mozilla.org/show_bug.cgi?id=524360
self.invokeSuggestions( key === BS || key === DEL || key === ESC );
return true;
};
text.onkeydown = function ( evt ) {
self.lastKey = key;
self.keyCount = 0;
// DOM Level < 3 IME input
// self.usesComposition catches browsers that may emit spurious keydown IME after a composition has ended
self.ime = true;
} else if ( self.ime && key !== IME && !( key >= 16 && key <= 20 || key >= 91 && key <= 93 || key === 144 ) ) {
// Ignore control keys: ctrl, shift, alt, alt gr, caps lock, windows/apple cmd keys, num lock. Only the windows keys
// terminate IME (apple cmd doesn't), but they also cause a blur, so it's OK to ignore them here.
// Note: Safari 4 (530.17) propagates ESC out of an IME composition (observed at least on Win XP).
}
if ( key === RET ) return self.accept( evt );
// Inhibit default behavior of ESC (revert to last real input in FF: we do that ourselves)
return ( key === ESC ) ? evtKill( evt ) : true;
};
// And handle continued pressing of arrow keys
text.onkeypress = function ( evt ) {
self.keyCount++;
return self.processKey( evt );
};
$( text ).on( 'focus', function () {
makeActive( self );
} );
// On IE, blur events are asynchronous, and may thus arrive after the element has lost the focus. Since IE
// can get the selection only while the element is active (has the focus), we may not always get the selection.
// Therefore, use an IE-specific synchronous event on IE...
// Don't test for text.selectionStart being defined;
$( text ).on(
(
// DOM Level 3 IME handling
try {
// Setting lastKey = IME provides a fake keyDown for Gecko's single keyUp after a cmposition. If we didn't do this,
// cancelling a composition via ESC would also cancel and close the whole category input editor.
$( text ).on( 'compositionstart', function () {
self.lastKey = IME;
self.usesComposition = true;
self.ime = true;
} );
$( text ).on( 'compositionend', function () {
self.lastKey = IME;
self.usesComposition = true;
self.ime = false;
} );
$( text ).on( 'textInput', function () {
self.ime = false;
self.invokeSuggestions( false );
} );
} catch ( any ) {
// Just in case some browsers might produce exceptions with these DOM Level 3 events
}
$( text ).on( 'blur', function () {
self.usesComposition = false;
self.ime = false;
} );
}
this.text = text;
Line 1,522 ⟶ 1,614:
if ( !noSuggestions ) {
list = make( 'select' );
list.onclick = function () {
};
list.ondblclick = function ( e ) {
if ( self.highlightSuggestion( 0 ) ) self.accept( e );
};
list.onchange = function () {
self.highlightSuggestion( 0 );
self.text.focus();
};
list.onkeyup = function ( evt ) {
if ( evt.keyCode === ESC ) {
self.resetKeySelection();
self.text.focus();
window.setTimeout( function () {
self.textchange( true );
}, HC.suggest_delay );
} else if ( evt.keyCode === RET ) {
self.accept( evt );
}
};
if ( !
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.appendChild( make( suggestionConfigs[ key ].name, true ) );
engineSelector.appendChild( opt );
Line 1,560 ⟶ 1,661:
if (
onUpload &&
) {
try {
label = UFUI.getLabel( id, true );
// Extract the plain text. IE doesn't know that Node.TEXT_NODE === 3
while ( label && label.nodeType !== 3 )
} catch ( ex ) {
label = null;
}
}
if ( !label || !label.data )
return label.data;
}
// Do not use type 'submit'; we cannot detect modifier keys if we do
var OK = make( 'input' )
OK.type = 'button';
OK.value = button_label( 'wpOkUploadLbl', HC.messages.ok );
OK.onclick = this.accept.bind( this );
this.ok = OK;
var cancel = make( 'input' )
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';
span.appendChild( text );
// Support: IE8, IE9
// Put some text into this span (a0 is nbsp) and make sure it always stays on the same
// line as the input field, otherwise, IE8/9 miscalculates the height of the span and
// then the engine selector may overlap the input field.
span.appendChild( make( '\xa0', true ) );
span.style.whiteSpace = 'nowrap';
if ( list )
if (
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 )
if ( this.list ) this.list.style.display = 'none';
if ( this.engineSelector )
this.currentCategory = this.lastSavedCategory;
this.currentExists = this.lastSavedExists;
this.currentHidden = this.lastSavedHidden;
this.currentKey = this.lastSavedKey;
this.icon.src =
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
};
this.showsList = false;
// Display the form
if ( this.catLink )
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
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 ) ?
},
Line 1,685 ⟶ 1,796:
this.inactivate();
this.form.style.display = 'none';
if ( this.catLink )
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
if ( this.state === CategoryEditor.UNCHANGED ) {
if ( this.catLink )
} else {
if ( !onUpload ) {
try {
this.catLink.style.backgroundColor =
} catch ( ex ) {}
}
Line 1,715 ⟶ 1,823:
if ( !newDOM ) {
var next = this.span.nextSibling;
if ( next )
}
if (this.span && this.span.parentNode) {
this.span.parentNode.removeChild( this.span );
}
for ( var i = 0; i < editors.length; i++ ) {
if ( editors[ i ] === this ) {
Line 1,725 ⟶ 1,835:
}
checkMultiInput();
},
Line 1,742 ⟶ 1,849:
this.lastSavedHidden = this.originalHidden;
this.state = CategoryEditor.UNCHANGED;
if ( !this.currentCategory || !this.currentCategory.length
// It was a newly added category. Remove the whole editor.
this.removeEditor();
Line 1,749 ⟶ 1,856:
this.catLink.removeChild( this.catLink.firstChild );
this.catLink.appendChild( make( this.currentCategory, true ) );
this.catLink.href = wikiPagePath(
this.catLink.title = this.currentKey || '';
this.catLink.className = this.currentExists ? '' : 'new';
this.catLink.style.backgroundColor = 'transparent';
if ( this.upDownLinks )
checkMultiInput();
}
Line 1,760 ⟶ 1,868:
inactivate: function () {
if ( this.list )
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 )
var v = value[ 0 ].replace( /_/g, ' ' ).replace( /^\s+|\s+$/g, '' );
if (
this.lastInput = v;
v = replaceShortcuts( v,
if ( !v.length
this.cancel();
return false;
}
if ( !dontCheck && (
conf.wgNamespaceNumber === 14 && v === conf.wgTitle || HC.blacklist && HC.blacklist.test( v ) ) ) {
this.cancel();
return false;
Line 1,807 ⟶ 1,915:
resolved[ 0 ].commit(
( resolved[ 0 ].currentCategory !== original ) ?
null );
}
}
Line 1,827 ⟶ 1,934:
this.catLink.removeChild( this.catLink.firstChild );
this.catLink.appendChild( make( this.currentCategory, true ) );
this.catLink.href = wikiPagePath(
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.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 =
} catch ( ex ) {}
}
}
if ( this.upDownLinks )
this.linkSpan.style.display = '';
this.state = CategoryEditor.CHANGED;
Line 1,877 ⟶ 1,988:
(
this.currentKey === this.originalKey ||
this.currentKey === null && !this.originalKey.length
)
) ||
conf.wgNamespaceNumber === 14 && this.currentCategory === conf.wgTitle ||
) {
this.cancel();
return;
}
this.close();
if ( !commitButton && !onUpload ) {
var self = this;
initiateEdit( function ( failure ) {
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 =
} catch ( ex ) {}
this.originalState = this.state;
Line 1,932 ⟶ 2,045:
this.originalState = this.state;
this.state = CategoryEditor.DELETED;
this.noCommit = noCommit ||
var self = this;
initiateEdit(
function ( failure ) {
performChanges( failure, self );
function ( msg ) {
self.state = self.originalState;
alert( msg );
} );
}
}
Line 1,951 ⟶ 2,068:
} else {
try {
this.catLink.style.backgroundColor =
} catch ( ex ) {}
}
Line 1,963 ⟶ 2,080:
selectEngine: function ( engineName ) {
if ( !this.engineSelector )
for ( var i = 0; i < this.engineSelector.options.length; i++ )
},
Line 1,972 ⟶ 2,087:
var v = this.text.value || '';
v = v.replace( /^(\s|_)+/, '' ); // Trim leading blanks and underscores
var re = new RegExp( '^(' +
if ( re.test( v ) )
if ( HC.capitalizePageNames ) v = capitalize( v );
// Only update the input field if there is a difference.
// reset the selection and cursor position after each value re-assignment.
if ( this.text.value !== null && this.text.value !== v ) this.text.value = v;
},
makeCall: function ( url, callbackObj, engine, queryKey, cleanKey ) {
var cb = callbackObj
function done() {
cb.callsMade++;
if ( cb.callsMade === cb.nofCalls ) {
if ( cb.exists )
if (
if ( !cb.dontCache && !suggestionConfigs[ cb.engineName ].cache[ z ] ) suggestionConfigs[ cb.engineName ].cache[ z ] = cb.allTitles;
thisObj.text.readOnly = false;
if ( !cb.cancelled )
if ( cb === thisObj.callbackObj ) thisObj.callbackObj = null;
cb = undefined;
}
}
$.getJSON( url, function ( json ) {
var titles = e.handler( json, z );
if ( cb.allTitles === null ) cb.allTitles = titles; else cb.allTitles = cb.allTitles.concat( titles );
if ( titles.exists
}
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 )
if ( this.lastInput !== v )
this.lastInput = v;
this.lastRealInput = v;
// Mark blacklisted inputs.
this.ok.disabled = v.length
if ( noSuggestions ) {
if ( this.list )
if ( this.engineSelector )
if ( this.icon )
return;
}
if ( !v.length
this.showSuggestions( [] );
return;
}
var cleanKey = v.replace( /[\u200E\u200F\u202A-\u202E]/g, '' ).replace( wikiTextBlankRE, ' ' );
cleanKey = replaceShortcuts( cleanKey,
cleanKey = cleanKey.replace( /^\s+|\s+$/g, '' );
if ( !cleanKey.length
this.showSuggestions( [] );
return;
}
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,
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 )
if ( noSuggestions ) {
if ( this.list )
if ( this.
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 )
} else {
if ( this.engineSelector )
}
if ( queryKey ) {
if ( this.lastInput.indexOf( queryKey )
if ( this.lastQuery && this.lastInput.indexOf( this.lastQuery ) === 0 && this.lastQuery.length > queryKey.length )
}
this.lastQuery = queryKey;
Line 2,119 ⟶ 2,241:
var v = this.text.value.split( '|' );
var key = v.length > 1 ? '|' + v[ 1 ] : '';
v = (
var vNormalized = v;
var knownToExist = titles && titles.exists;
Line 2,125 ⟶ 2,247:
if ( titles ) {
if ( titles.normalized && v.indexOf( queryKey ) === 0 ) {
vNormalized = titles.normalized + v.substring( queryKey.length );
}
var vLow = vNormalized.toLowerCase();
// Strip blacklisted categories
if (
for ( i = 0; i < titles.length; i++ ) {
if (
titles.splice( i, 1 );
i--;
Line 2,140 ⟶ 2,262:
titles.sort(
function ( a, b ) {
if ( a === b )
if (
// 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 )
// Case-insensitive prefix match!
var aLow = a.toLowerCase(),
bLow = b.toLowerCase();
prefixMatchA = ( aLow.indexOf( vLow ) === 0 ? 1 : 0 );
prefixMatchB = ( bLow.indexOf( vLow ) === 0 ? 1 : 0 );
if ( prefixMatchA !== prefixMatchB )
if (
if ( b < a ) return 1;
return 0;
} );
// Remove duplicates and self-references
for ( i = 0; i < titles.length; i++ ) {
if (
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
if ( this.list )
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
if ( engineName && suggestionConfigs[ engineName ] && !suggestionConfigs[ engineName ].temp ) {
if ( this.icon )
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,
if ( engineName && suggestionConfigs[ engineName ] && !suggestionConfigs[ engineName ].temp ) {
this.icon.src =
this.inputExists = existing;
}
Line 2,188 ⟶ 2,321:
if ( titles.length === 1 ) {
this.list.style.display = 'none';
if ( this.engineSelector )
return;
}
}
// (Re-)fill the list
while ( 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 )
return;
}
var nofItems = ( this.list.options.length >
if ( nofItems <= 1 )
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 <
function viewport( what ) {
if ( is_webkit && !document.evaluate ) {
return window[ 'inner' + what ];
}
var s = 'client' + what;
if ( window.opera )
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.
// Opera: don't know...
if ( result < 0 )
// Now all have webkit behavior, i.e. zero if at the leftmost edge.
}
Line 2,271 ⟶ 2,406:
};
}
var t = 0,
l = 0;
do {
node = node.offsetParent;
} while ( node );
Line 2,283 ⟶ 2,419:
}
var textPos = position( this.text )
// Opera 9.5 somehow has offsetWidth = 0 here?? Use the next best value...
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 = '
this.engineSelector.style.display = '';
offset = this.engineSelector.offsetHeight;
Line 2,306 ⟶ 2,442:
}
if ( textPos.y < maxListHeight + offset + 1 ) {
nt = this.text.offsetHeight + offset + 1;
if ( this.engineName )
} else {
nt = -listh - offset - 1;
if ( this.engineName )
}
this.list.style.top = nt + 'px';
Line 2,338 ⟶ 2,474:
w = view_w;
this.list.style.width = w + 'px';
if ( is_rtl )
}
var relative_offset = 0;
if ( left < scroll ) relative_offset = scroll - left; else if ( right > scroll + view_w ) relative_offset = -( right - scroll - view_w );
if ( is_rtl ) relative_offset = -relative_offset;
if ( relative_offset ) this.list.style[ anchor ] = ( nl + relative_offset ) + 'px';
}
},
autoComplete: function ( newVal, actVal, normalizedActVal, key, dontModify ) {
if ( newVal === actVal )
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 )
// Maybe it'll work with the normalized value (NFC)?
if ( normalizedActVal && newVal.indexOf( normalizedActVal ) === 0 ) {
if ( this.lastRealInput === actVal )
actVal = normalizedActVal;
} else {
Line 2,380 ⟶ 2,511:
canSelect: function () {
return this.text.setSelectionRange ||
},
setSelection: function ( from, to ) {
// this.text must be focused (at least on IE)
if ( !this.text.value )
if ( this.text.setSelectionRange ) { // e.g. khtml
this.text.setSelectionRange( from, to );
} else if (
if ( from > this.text.selectionStart ) {
this.text.selectionEnd = to;
Line 2,407 ⟶ 2,538:
getSelection: function () {
var from = 0,
to = 0;
// this.text must be focused (at least on IE)
if ( !this.text.value ) {
// No text.
} else if (
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
}
}
}
return {
start: from,
end: to
};
},
Line 2,448 ⟶ 2,584:
break;
case PGUP:
dir = -
break;
case PGDOWN:
dir =
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' ) {
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' )
var curr = this.list.selectedIndex;
var tgt = -1;
if ( dir === 0 ) {
if ( curr < 0 || curr >= this.list.options.length )
tgt = curr;
} else {
tgt = curr < 0 ? 0 : curr + dir;
tgt = tgt < 0 ? 0 : tgt;
if ( tgt >= this.list.options.length )
}
if ( tgt !== curr || dir === 0 ) {
if ( curr >= 0 && curr < this.list.options.length && dir !== 0 )
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.lastInput = this.list.options[ tgt ].text;
this.inputExists = true; // Might be wrong if from a dab list...
if ( this.icon )
this.state = CategoryEditor.CHANGE_PENDING;
}
Line 2,509 ⟶ 2,646:
resetKeySelection: function () {
if ( noSuggestions || !this.list || this.list.style.display === 'none' )
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
// override it easily in their own user script files by just declaring variables. JSconfig
// is some feature used at Wikimedia Commons.
var config = (
( config.HotCatDontAddToWatchlist !== undefined ? config.HotCatDontAddToWatchlist :
HC.dont_add_to_watchlist ) );
HC.no_autocommit = ( window.hotcat_no_autocommit !== undefined ?
!!window.hotcat_no_autocommit : ( config.HotCatNoAutoCommit !== undefined ?
config.HotCatNoAutoCommit :
// On talk namespace default autocommit off
( conf.wgNamespaceNumber % 2 ?
true : HC.no_autocommit ) ) );
HC.del_needs_diff = ( window.hotcat_del_needs_diff !== undefined ?
!!window.hotcat_del_needs_diff :
config.HotCatDelNeedsDiff :
HC.del_needs_diff ) );
HC.suggest_delay = window.hotcat_suggestion_delay || config.HotCatSuggestionDelay || HC.suggest_delay;
HC.editbox_width = window.hotcat_editbox_width || config.HotCatEditBoxWidth || HC.editbox_width;
HC.suggestions = window.hotcat_suggestions || config.HotCatSuggestions || HC.suggestions;
if ( typeof HC.suggestions !== 'string' || !suggestionConfigs[ HC.suggestions ] ) HC.suggestions = 'combined';
HC.fixed_search = ( window.hotcat_suggestions_fixed !== undefined ?
!!window.hotcat_suggestions_fixed : ( config.HotCatFixedSuggestions !== undefined ?
config.HotCatFixedSuggestions : HC.fixed_search ) );
HC.single_minor = ( window.hotcat_single_changes_are_minor !== undefined ?
!!window.hotcat_single_changes_are_minor :
( config.HotCatMinorSingleChanges !== undefined ?
config.HotCatMinorSingleChanges :
HC.single_minor ) );
HC.bg_changed = window.hotcat_changed_background || config.HotCatChangedBackground || HC.bg_changed;
HC.use_up_down = ( window.hotcat_use_category_links !== undefined ?
!!window.hotcat_use_category_links :
config.HotCatUseCategoryLinks :
HC.use_up_down ) );
HC.listSize = window.hotcat_list_size || config.HotCatListSize || HC.listSize;
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();
}
}
}
// Numeric input, make sure we have a numeric value
if ( isNaN(
HC.listSize = Math.min( HC.listSize, 30 ); // Max size
// Localize search engine names
if (
for ( var key in
if ( suggestionConfigs[ key ] &&
}
// Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]]
Line 2,633 ⟶ 2,813:
case 'cologneblue':
container = document.getElementById( 'quickbar' );
case 'standard':
case 'nostalgia':
if ( !container )
var lks = container.getElementsByTagName( 'a' );
for ( var i = 0; i < lks.length; i++ ) {
if (
param( '
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' )
}
if ( !ip )
var reupload = document.getElementById( 'wpForReUpload' );
var destFile = document.getElementById( 'wpDestFile' );
if (
( reupload && !!reupload.value ) ||
( destFile && ( destFile.disabled || destFile.readOnly ) )
) {
return; // re-upload form...
}
// Insert a table row with two fields (label and empty category bar)
var labelCell = make( 'td' );
Line 2,676 ⟶ 2,889:
// Create the label
var label = null;
if ( window.UFUI && window.UIElements && UFUI.getLabel instanceof Function ) {
try {
label = UFUI.getLabel( 'wpCategoriesUploadLbl' );
Line 2,688 ⟶ 2,898:
if ( !label ) {
labelCell.id = 'hotcatLabel';
labelCell.appendChild( make(
} else {
labelCell.id = 'hotcatLabelTranslated';
Line 2,707 ⟶ 2,917:
if ( oldSubmit ) {
if ( typeof oldSubmit === 'string' ) {
do_submit = eval( oldSubmit );
} else if (
do_submit = oldSubmit.apply( form, arguments );
}
}
if ( !do_submit )
closeForm();
// Copy the categories
var eb = document.getElementById( 'wpUploadDescription' ) || document.getElementById( 'wpDesc' );
var addedOne = false;
for ( var i = 0; i < editors.length; i++ ) {
var t = editors[ i ].currentCategory;
if ( !t )
var key = editors[ i ].currentKey;
var new_cat = '[[' +
// Only add if not already present
var cleanedText = eb.value
Line 2,736 ⟶ 2,943:
}
if ( addedOne ) {
eb.value = eb.value.replace( /\{\{subst:unc\}\}/g, '' );
}
Line 2,748 ⟶ 2,955:
function isOnPage( span ) {
if ( span.firstChild.nodeType !== Node.ELEMENT_NODE )
if ( !catTitle ) return null;
catTitle = catTitle.substr( catTitle.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
if (
var 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 =
return ( result && result.length ) ? result[ 0 ] : null;
}
function setup( additionalWork ) {
if ( initialized )
initialized = true;
if ( setupTimeout ) {
Line 2,790 ⟶ 3,004:
if ( !hiddenCats ) {
footer = findByClass( document, 'div', 'printfooter' );
if ( !footer )
}
catLine = make( 'div' );
Line 2,798 ⟶ 3,012:
var label = make( 'a' );
label.href = conf.wgArticlePath.replace( '$1', 'Special:Categories' );
label.title =
label.appendChild( make(
catLine.appendChild( label );
catLine.appendChild( make( ':', true ) );
Line 2,811 ⟶ 3,025:
container.className = 'catlinks noprint';
container.style.display = '';
if ( !hiddenCats )
} // end if catLine exists
if ( is_rtl )
// Create editors for all existing categories
Line 2,824 ⟶ 3,034:
var i;
var cats = line.getElementsByTagName( 'li' );
if ( cats.length
newDOM = true
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++ )
for ( i = 0; i < copyCats.length; i++ ) {
var test = isOnPage( copyCats[ i ] );
if ( test !== null && test.match !== null && line ) {
new CategoryEditor( line, copyCats[ i ], test.title, test.match[ 2 ], is_hidden );
}
}
return copyCats.length
}
Line 2,848 ⟶ 3,059:
if ( !onUpload ) {
if ( pageText !== null && hiddenCats ) {
if ( is_rtl )
createEditors( hiddenCats, true );
}
Line 2,854 ⟶ 3,065:
var enableMulti = make( 'span' );
enableMulti.className = 'noprint';
if ( is_rtl )
catLine.insertBefore( enableMulti, catLine.firstChild.nextSibling );
enableMulti.appendChild( make( '\xa0', true ) ); // nbsp
multiSpan = make( 'span' );
enableMulti.appendChild( multiSpan );
multiSpan.innerHTML = '(<a>' +
var lk = multiSpan.getElementsByTagName( 'a' )[ 0 ];
lk.onclick = function ( evt ) {
setMultiInput();
checkMultiInput();
return evtKill( evt );
};
lk.title = HC.multi_tooltip;
lk.style.cursor = 'pointer';
}
cleanedText = null;
if (
$( 'body' ).trigger( 'hotcatSetupCompleted' );
}
function createCommitForm() {
if ( commitForm )
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">' +
'
'<input type="hidden" name="
'<input type="hidden" name="
'<input type="hidden" name="
'<input type="
'<input type="checkbox" name="
'<input type="
'<input type="hidden" name="
'<input type="hidden" name="
'<input type="hidden" name="
'<input type="hidden" name="
'<input type="
'<input type="
'<input type="hidden" name="
'<input type="hidden" name="
'<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
// Doesn't exist yet. Disable on non-existing User pages -- might be a global user page.
if ( conf.wgNamespaceNumber === 2 )
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=' +
var s = make( 'script' );
s.src =
setup( createCommitForm );
};
document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
setupTimeout = window.setTimeout( function () {
setup( createCommitForm );
}, 4000 ); // 4 sec, just in case getting the wikitext takes longer.
}
}
function setState( state ) {
var cats = state.split( '\n' );
if ( !cats.length
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
var cat = cats[ i ].split( '|' );
var key = cat.length > 1 ? cat[ 1 ] : null;
cat = cat[ 0 ];
var lk = make( 'a'
lk.href = wikiPagePath( HC.category_canonical + ':' + cat );
lk.appendChild( make( cat, true ) );
lk.title = cat;
var span = make( 'span' );
span.appendChild( lk );
if ( !i
catLine.insertBefore( span, before );
if ( before && i + 1 < cats.length )
newSpans.push( {
element: span,
title: cat,
key: key
} );
}
// And change the last one...
if ( before )
for ( i = 0; i < newSpans.length; i++ ) {
new CategoryEditor( catLine, newSpans[ i ].element, newSpans[ i ].title, newSpans[ i ].key );
}
}
return null;
}
function getState() {
var result = null;
for ( var i = 0; i < editors.length; i++ ) {
var text = editors[ i ].currentCategory;
var key = editors[ i ].currentKey;
if ( text && text.length ) {
if ( key !== null ) text += '|' + key;
if ( result === null ) result = text; else result += '\n' + text;
}
}
return result;
}
function really_run() {
initialize();
if ( !HC.upload_disabled && conf.wgNamespaceNumber === -1 && conf.wgCanonicalSpecialPageName === 'Upload' && conf.wgUserName ) {
setup_upload();
setup( function () {
// Check for state restoration once the setup is done otherwise, but before signalling setup completion
if ( window.UploadForm && UploadForm.previous_hotcat_state ) UploadForm.previous_hotcat_state = setState( UploadForm.previous_hotcat_state );
} );
} else {
if ( !conf.wgIsArticle || conf.wgAction !== 'view' || param( 'diff' ) !== null || param( 'oldid' ) !== null || !can_edit() || HC.disable() ) return;
getPage();
}
}
function run() {
if ( HC.started ) return;
HC.started = true;
loadTrigger.register( really_run );
}
// Export legacy functions
window.hotcat_get_state = function () {
return getState();
};
window.hotcat_set_state = function ( state ) {
return setState( state );
};
window.hotcat_close_form = function () {
closeForm();
};
HC.runWhenReady = function ( callback ) {
// run user-registered code once HotCat is fully set up and ready.
mw.hook( 'hotcat.ready' ).add( callback );
};
// Make sure we don't get conflicts with AjaxCategories (core development that should one day
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 (
// 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;
run();
} );
}
// We can safely trigger just after user configuration is loaded.
// Use always() instead of then() to also start HotCat if the user module has problems.
$.when( mw.loader.using( 'user' ), $.ready ).always( run );
}( jQuery, mediaWiki ) );
// </nowiki>
|