Line 1: |
Line 1: |
− | // <nowiki> | + | /** |
| + | HotCat V2.43 |
| + | |
| + | 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 |
− | HotCat V2.34
| + | List of main authors: https://commons.wikimedia.org/wiki/Help:Gadget-HotCat/Version_history |
| | | |
− | Ajax-based simple Category manager. Allows adding/removing/changing categories on a page view.
| + | License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0) |
− | 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
| + | Choose whichever license of these you like best :-) |
− | 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)
| + | This code should run on any MediaWiki installation >= MW 1.27. |
| | | |
− | Choose whichever license of these you like best :-)
| + | 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 |
− | This code is MW version safe. It should run on any MediaWiki installation >= MW 1.15. Note
| |
− | that HotCat is supposed to run with or without jQuery, and also on older installations that
| |
− | do not yet have window.mw. If you use any of these newer features, make sure you qualify them
| |
− | by checking whether they exist at all, and by providing some meaningful fallback implementation
| |
− | if not. To start itself, HotCat uses jQuery(document).ready(). If it doesn't exist, HotCat won't
| |
− | start.
| |
| */ | | */ |
− | /* jshint ignore:start */ // This old code uses too many coding conventions incompatible with jshint. | + | // <nowiki> |
− | (function () { | + | /* eslint-disable vars-on-top, one-var, camelcase, no-alert, curly */ |
− | // Support: MW 1.16 | + | /* global jQuery, mediaWiki, UFUI, JSconfig, UploadForm */ |
− | var conf = window.mw ? mw.config.values : window; | + | /* 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 = mw.config.values; |
| | | |
− | if ( | + | // Guard against double inclusions (in old IE/Opera element ids become window properties) |
− | // Guard against double inclusions (in old IE/Opera element ids become window properties)
| + | if ( ( window.HotCat && !window.HotCat.nodeName ) || |
− | (window.HotCat && !window.HotCat.nodeName)
| + | conf.wgAction === 'edit' ) // Not on edit mode |
− | // Not on edit pages | |
− | || conf.wgAction == 'edit'
| |
− | | |
− | ) {
| |
| return; | | return; |
− | }
| |
| | | |
− | // Configuration stuff. | + | // Configuration stuff. |
− | window.HotCat = { | + | var HC = window.HotCat = { |
− | // Localize these messages to the main language of your wiki.
| + | // Localize these messages to the main language of your wiki. |
− | messages :
| + | messages: { |
− | {cat_removed : 'removed [[Category:$1]]'
| + | cat_removed: 'removed [[Category:$1]]', |
− | ,template_removed : 'removed {{[[Category:$1]]}}'
| + | template_removed: 'removed {{[[Category:$1]]}}', |
− | ,cat_added : 'added [[Category:$1]]'
| + | cat_added: 'added [[Category:$1]]', |
− | ,cat_keychange: 'new key for [[Category:$1]]: "$2"' // $2 is the new key
| + | cat_keychange: 'new key for [[Category:$1]]: "$2"', // $2 is the new key |
− | ,cat_notFound : 'Category "$1" not found'
| + | cat_notFound: 'Category "$1" not found', |
− | ,cat_exists : 'Category "$1" already exists; not added.'
| + | cat_exists: 'Category "$1" already exists; not added.', |
− | ,cat_resolved : ' (redirect [[Category:$1]] resolved)'
| + | cat_resolved: ' (redirect [[Category:$1]] resolved)', |
− | ,uncat_removed: 'removed {{uncategorized}}'
| + | uncat_removed: 'removed {{uncategorized}}', |
− | ,separator : '; '
| + | separator: '; ', |
− | ,prefix : ""
| + | // Some text to prefix to the edit summary. |
− | // Some text to prefix to the edit summary.
| + | prefix: '', |
− | ,using : ' using [[Help:Gadget-HotCat|HotCat]]'
| + | // Some text to append to the edit summary. Named 'using' for historical reasons. If you prefer |
− | // Some text to append to the edit summary. Named 'using' for historical reasons. If you prefer
| + | // to have a marker at the front, use prefix and set this to the empty string. |
− | // to have a marker at the front, use prefix and set this to the empty string.
| + | using: ' using [[Help:Gadget-HotCat|HotCat]]', |
− | ,multi_change : '$1 categories'
| + | // $1 is replaced by a number. If your language has several plural forms (c.f. [[:en:Dual (grammatical form)]]), |
− | // $1 is replaced by a number. If your language has several plural forms (c.f. [[:en:Dual (grammatical form)]]),
| + | // you can set this to an array of strings suitable for passing to mw.language.configPlural(). |
− | // you can set this to an array of strings suitable for passing to mw.language.configPlural().
| + | // If that function doesn't exist, HotCat will simply fall back to using the last |
− | // If that function doesn't exist, HotCat will simply fall back to using the last
| + | // entry in the array. |
− | // entry in the array.
| + | multi_change: '$1 categories', |
− | ,commit : 'Save'
| + | // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, |
− | // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
| + | // see localization hook below. |
− | // see localization hook below.
| + | commit: 'Save', |
− | ,ok : 'OK'
| + | // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, |
− | // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
| + | // see localization hook below. |
− | // see localization hook below.
| + | ok: 'OK', |
− | ,cancel : 'Cancel'
| + | // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, |
− | // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
| + | // see localization hook below. |
− | // see localization hook below.
| + | cancel: 'Cancel', |
− | ,multi_error : 'Could not retrieve the page text from the server. Therefore, your category changes '
| + | // Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage, |
− | +'cannot be saved. We apologize for the inconvenience.'
| + | // see localization hook below. |
− | // Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
| + | multi_error: 'Could not retrieve the page text from the server. Therefore, your category changes ' + |
− | // see localization hook below.
| + | 'cannot be saved. We apologize for the inconvenience.', |
− | ,short_catchange : null
| + | // Defaults to '[[' + category_canonical + ':$1]]'. Can be overridden if in the short edit summaries |
− | // Defaults to '[[' + category_canonical + ':$1]]'. Can be overridden if in the short edit summaries
| + | // not the standard category name should be used but, say, a shorter namespace alias. $1 is replaced |
− | // not the standard category name should be used but, say, a shorter namespace alias. $1 is replaced
| + | // by a category name. |
− | // by a category name.
| + | short_catchange: null |
− | }
| + | }, |
− | ,category_regexp : '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]'
| + | // Plural of category_canonical. |
− | // Regular sub-expression matching all possible names for the category namespace. Is automatically localized
| + | categories: 'Categories', |
− | // correctly if you're running MediaWiki 1.16 or later. Otherwise, set it appropriately, e.g. at the German
| + | // Any category in this category is deemed a disambiguation category; i.e., a category that should not contain |
− | // Wikipedia, use '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]|[Kk][Aa][Tt][Ee][Gg][Oo][Rr][Ii][Ee]', or at the
| + | // any items, but that contains links to other categories where stuff should be categorized. If you don't have |
− | // Chinese Wikipedia, use '[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]|分类|分類'. Note that namespaces are case-
| + | // that concept on your wiki, set it to null. Use blanks, not underscores. |
− | // insensitive!
| + | disambig_category: 'Disambiguation', |
− | ,category_canonical : 'Category'
| + | // Any category in this category is deemed a (soft) redirect to some other category defined by a link |
− | // The standard category name on your wiki. Is automatically localized correctly if you're running
| + | // to another non-blacklisted category. If your wiki doesn't have soft category redirects, set this to null. |
− | // MediaWiki 1.16 or later; otherwise, set it to the preferred category name (e.g., "Kategorie").
| + | // If a soft-redirected category contains more than one link to another non-blacklisted category, it's considered |
− | ,categories : 'Categories'
| + | // a disambiguation category instead. |
− | // Plural of category_canonical.
| + | redir_category: 'Category redirects', |
− | ,disambig_category : 'Disambiguation'
| + | // The little modification links displayed after category names. U+2212 is a minus sign; U+2193 and U+2191 are |
− | // Any category in this category is deemed a disambiguation category; i.e., a category that should not contain
| + | // downward and upward pointing arrows. Do not use ↓ and ↑ in the code! |
− | // any items, but that contains links to other categories where stuff should be categorized. If you don't have
| + | links: { |
− | // that concept on your wiki, set it to null. Use blanks, not underscores.
| + | change: '(±)', |
− | ,redir_category : 'Category redirects'
| + | remove: '(\u2212)', |
− | // Any category in this category is deemed a (soft) redirect to some other category defined by a link
| + | add: '(+)', |
− | // to another non-blacklisted category. If your wiki doesn't have soft category redirects, set this to null.
| + | restore: '(×)', |
− | // If a soft-redirected category contains more than one link to another non-blacklisted category, it's considered
| + | undo: '(×)', |
− | // a disambiguation category instead.
| + | down: '(\u2193)', |
− | ,links : {change: '(±)', remove: '(\u2212)', add: '(+)', restore: '(×)', undo: '(×)', down: '(\u2193)', up: '(\u2191)'}
| + | up: '(\u2191)' |
− | // The little modification links displayed after category names. U+2212 is a minus sign; U+2193 and U+2191 are
| + | }, |
− | // downward and upward pointing arrows. Do not use ↓ and ↑ in the code!
| + | changeTag: conf.wgUserName ? 'HotCat' : '', // if tag is missing, edit is rejected |
− | ,tooltips : {
| + | // The tooltips for the above links |
− | change: 'Modify'
| + | tooltips: { |
− | ,remove: 'Remove'
| + | change: 'Modify', |
− | ,add: 'Add a new category'
| + | remove: 'Remove', |
− | ,restore: 'Undo changes'
| + | add: 'Add a new category', |
− | ,undo: 'Undo changes'
| + | restore: 'Undo changes', |
− | ,down: 'Open for modifying and display subcategories'
| + | undo: 'Undo changes', |
− | ,up: 'Open for modifying and display parent categories'
| + | down: 'Open for modifying and display subcategories', |
− | }
| + | up: 'Open for modifying and display parent categories' |
− | // The tooltips for the above links
| + | }, |
− | ,addmulti : '<span>+<sup>+</sup></span>'
| + | // The HTML content of the "enter multi-mode" link at the front. |
− | // The HTML content of the "enter multi-mode" link at the front.
| + | addmulti: '<span>+<sup>+</sup></span>', |
− | ,multi_tooltip : 'Modify several categories'
| + | // Tooltip for the "enter multi-mode" link |
− | // Tooltip for the "enter multi-mode" link
| + | multi_tooltip: 'Modify several categories', |
− | ,disable :
| + | // Return true to disable HotCat. |
− | function () { // Return true to disable HotCat. | + | disable: function () { |
| var ns = conf.wgNamespaceNumber; | | var ns = conf.wgNamespaceNumber; |
| var nsIds = conf.wgNamespaceIds; | | var nsIds = conf.wgNamespaceIds; |
− | return ( ns < 0 // Special pages; Special:Upload is handled differently | + | return ( |
− | || ns === 10 // Templates
| + | ns < 0 || // Special pages; Special:Upload is handled differently |
− | || ns === 828 // Module (Lua)
| + | ns === 10 || // Templates |
− | || ns === 8 // MediaWiki
| + | ns === 828 || // Module (Lua) |
− | || ns === 6 && conf.wgArticleId === 0 // Non-existing file pages
| + | ns === 8 || // MediaWiki |
− | || ns === 2 && /\.(js|css)$/.test(conf.wgTitle) // User scripts
| + | ns === 6 && !conf.wgArticleId || // Non-existing file pages |
− | || nsIds
| + | ns === 2 && /\.(js|css)$/.test( conf.wgTitle ) || // User scripts |
− | && ( ns === nsIds['creator']
| + | nsIds && |
− | || ns === nsIds['timedtext']
| + | ( ns === nsIds.creator || |
− | || ns === nsIds['institution']
| + | ns === nsIds.timedtext || |
− | )
| + | ns === nsIds.institution ) ); |
− | );
| + | }, |
− | } | + | // A regexp matching a templates used to mark uncategorized pages, if your wiki does have that. |
− | ,uncat_regexp : /\{\{\s*([Uu]ncat(egori[sz]ed( image)?)?|[Nn]ocat|[Nn]eedscategory)[^}]*\}\}\s*(<\!--.*?--\>)?/g
| + | // If not, set it to null. |
− | // A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
| + | uncat_regexp: /\{\{\s*[Uu]ncategorized\s*[^}]*\}\}\s*(<!--.*?-->\s*)?/g, |
− | // If not, set it to null.
| + | // The images used for the little indication icon. Should not need changing. |
− | ,existsYes : '//upload.wikimedia.org/wikipedia/commons/thumb/b/be/P_yes.svg/20px-P_yes.svg.png'
| + | existsYes: '//upload.wikimedia.org/wikipedia/commons/thumb/b/be/P_yes.svg/20px-P_yes.svg.png', |
− | ,existsNo : '//upload.wikimedia.org/wikipedia/commons/thumb/4/42/P_no.svg/20px-P_no.svg.png'
| + | existsNo: '//upload.wikimedia.org/wikipedia/commons/thumb/4/42/P_no.svg/20px-P_no.svg.png', |
− | // The images used for the little indication icon. Should not need changing.
| + | // a list of categories which can be removed by removing a template |
− | ,template_regexp : '[Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee]'
| + | // key: the category without namespace |
− | // Regexp to recognize templates. Like "category" above; autolocalized for MW 1.16+, otherwise set manually here.
| + | // value: A regexp matching the template name, again without namespace |
− | // On the German Wikipedia, you might use '[Tt][Ee][Mm][Pp][Ll][Aa][Tt][Ee]|[Vv][Oo][Rr][Ll][Aa][Gg][Ee]'.
| + | // If you don't have this at your wiki, or don't want this, set it to an empty object {}. |
− | ,template_categories : {}
| + | template_categories: {}, |
− | // a list of categories which can be removed by removing a template
| + | // Names for the search engines |
− | // key: the category without namespace
| + | engine_names: { |
− | // value: A regexp matching the template name, again without namespace
| + | searchindex: 'Search index', |
− | // If you don't have this at your wiki, or don't want this, set it to an empty object {}.
| + | pagelist: 'Page list', |
− | ,engine_names : {
| + | combined: 'Combined search', |
− | searchindex : 'Search index'
| + | subcat: 'Subcategories', |
− | ,pagelist : 'Page list'
| + | parentcat: 'Parent categories' |
− | ,combined : 'Combined search'
| + | }, |
− | ,subcat : 'Subcategories'
| + | |
− | ,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. |
− | // Names for the search engines
| + | // Basically, this will make an API query to check the MediaWiki configuration and HotCat then sets |
− | ,capitalizePageNames : true
| + | // this to true for most wikis, and to false on Wiktionary. |
− | // Set to false if your wiki has case-sensitive page names. MediaWiki has two modes: either the first letter
| + | // |
− | // of a page is automatically capitalized ("first-letter"; Category:aa == Category:Aa), or it isn't
| + | // You can set this directly if there is a problem with it. For example, Georgian Wikipedia (kawiki), |
− | // ("case-sensitive"; Category:aa != Category:Aa). It doesn't currently have a fully case-insensitive mode
| + | // is known to have different capitalisation logic between MediaWiki PHP and JavaScript. As such, automatic |
− | // (which would mean Category:aa == Category:Aa == Category:AA == Category:aA)
| + | // case changes in JavaScript by HotCat would be wrong. |
− | // HotCat tries to set this correctly automatically using an API query. It's still a good idea to manually
| + | capitalizePageNames: null, |
− | // configure it correctly; either directly here if you copied HotCat, or in the local configuration file
| + | // If upload_disabled is true, HotCat will not be used on the Upload form. |
− | // MediaWiki:Gadget-HotCat.js/local_defaults if you hotlink to the Commons-version, to ensure it is set even
| + | upload_disabled: false, |
− | // if that API query should fail for some strange reason.
| + | // Single regular expression matching blacklisted categories that cannot be changed or |
− | ,upload_disabled : false
| + | // added using HotCat. For instance /\bstubs?$/ (any category ending with the word "stub" |
− | // If upload_disabled is true, HotCat will not be used on the Upload form.
| + | // or "stubs"), or /(\bstubs?$)|\bmaintenance\b/ (stub categories and any category with the |
− | ,blacklist : null
| + | // word "maintenance" in its title. |
− | // Single regular expression matching blacklisted categories that cannot be changed or
| + | blacklist: null, |
− | // added using HotCat. For instance /\bstubs?$/ (any category ending with the word "stub"
| |
− | // or "stubs"), or /(\bstubs?$)|\bmaintenance\b/ (stub categories and any category with the
| |
− | // word "maintenance" in its title.
| |
| | | |
− | // Stuff changeable by users: | + | // Stuff changeable by users: |
− | ,bg_changed : '#F8CCB0'
| + | // Background for changed categories in multi-edit mode. Default is a very light salmon pink. |
− | // Background for changed categories in multi-edit mode. Default is a very light salmon pink.
| + | bg_changed: '#FCA', |
− | ,no_autocommit : false
| + | // If true, HotCat will never automatically submit changes. HotCat will only open an edit page with |
− | // If true, HotCat will never automatically submit changes. HotCat will only open an edit page with
| + | // the changes; users must always save explicitly. |
− | // the changes; users must always save explicitly.
| + | no_autocommit: false, |
− | ,del_needs_diff : false
| + | // If true, the "category deletion" link "(-)" will never save automatically but always show an |
− | // If true, the "category deletion" link "(-)" will never save automatically but always show an
| + | // edit page where the user has to save the edit manually. Is false by default because that's the |
− | // edit page where the user has to save the edit manually. Is false by default because that's the
| + | // traditional behavior. This setting overrides no_autocommit for "(-)" links. |
− | // traditional behavior. This setting overrides no_autocommit for "(-)" links.
| + | del_needs_diff: false, |
− | ,suggest_delay : 100
| + | // Time, in milliseconds, that HotCat waits after a keystroke before making a request to the |
− | // Time, in milliseconds, that HotCat waits after a keystroke before making a request to the
| + | // server to get suggestions. |
− | // server to get suggestions.
| + | suggest_delay: 100, |
− | ,editbox_width : 40
| + | // Default width, in characters, of the text input field. |
− | // Default width, in characters, of the text input field.
| + | editbox_width: 40, |
− | ,suggestions : 'combined'
| + | // One of the engine_names above, to be used as the default suggestion engine. |
− | // One of the engine_names above, to be used as the default suggestion engine.
| + | suggestions: 'combined', |
− | ,fixed_search : false
| + | // If true, always use the default engine, and never display a selector. |
− | // If true, always use the default engine, and never display a selector.
| + | fixed_search: false, |
− | ,use_up_down : true
| + | // If false, do not display the "up" and "down" links |
− | // If false, do not display the "up" and "down" links
| + | use_up_down: true, |
− | ,list_size : 5
| + | // Default list size |
− | // Default list size
| + | listSize: 10, |
− | ,single_minor : true
| + | // If true, single category changes are marked as minor edits. If false, they're not. |
− | // If true, single category changes are marked as minor edits. If false, they're not.
| + | single_minor: true, |
− | ,dont_add_to_watchlist : false
| + | // If true, never add a page to the user's watchlist. If false, pages get added to the watchlist if |
− | // If true, never add a page to the user's watchlist. If false, pages get added to the watchlist if
| + | // the user has the "Add pages I edit to my watchlist" or the "Add pages I create to my watchlist" |
− | // the user has the "Add pages I edit to my watchlist" or the "Add pages I create to my watchlist"
| + | // options in his or her preferences set. |
− | // options in his or her preferences set.
| + | dont_add_to_watchlist: false, |
− | ,shortcuts : null
| + | shortcuts: null, |
− | ,addShortcuts :
| + | addShortcuts: function ( map ) { |
− | function (map) {
| + | if ( !map ) return; |
− | if (!map) return; | |
| window.HotCat.shortcuts = window.HotCat.shortcuts || {}; | | window.HotCat.shortcuts = window.HotCat.shortcuts || {}; |
− | for (var k in map) { | + | for ( var k in map ) { |
− | if (!map.hasOwnProperty (k) || typeof k != 'string') continue; | + | if ( !map.hasOwnProperty( k ) || typeof k !== 'string' ) continue; |
− | var v = map[k]; | + | |
− | if (typeof v != 'string') continue; | + | var v = map[ k ]; |
− | k = k.replace (/^\s+|\s+$/g, ""); | + | if ( typeof v !== 'string' ) continue; |
− | v = v.replace (/^\s+|\s+$/g, ""); | + | |
− | if (k.length === 0 || v.length === 0) continue; | + | k = k.replace( /^\s+|\s+$/g, '' ); |
− | window.HotCat.shortcuts[k] = v; | + | v = v.replace( /^\s+|\s+$/g, '' ); |
| + | if ( !k.length || !v.length ) continue; |
| + | |
| + | window.HotCat.shortcuts[ k ] = v; |
| } | | } |
| } | | } |
− | }; | + | }; |
| | | |
| // More backwards compatibility. We have a few places where we test for the browser: once for | | // More backwards compatibility. We have a few places where we test for the browser: once for |
− | // Safari < 3.0, twice for WebKit (Chrome or Safari, any versions), twice for IE <= 6, and | + | // Safari < 3.0, and twice for WebKit (Chrome or Safari, any versions) |
− | // once for IE < 8.
| |
| var ua = navigator.userAgent.toLowerCase(); | | var ua = navigator.userAgent.toLowerCase(); |
− | var is_ie6 = /msie ([0-9]{1,}[\.0-9]{0,})/.exec(ua) !== null && parseFloat(RegExp.$1) <= 6.0;
| + | var is_webkit = /applewebkit\/\d+/.test( ua ) && ua.indexOf( 'spoofer' ) < 0; |
− | var is_ie_lt8 = /msie ([0-9]{1,}[\.0-9]{0,})/.exec(ua) !== null && parseFloat(RegExp.$1) < 8.0;
| + | var cat_prefix = null; |
− | var is_webkit = /applewebkit\/\d+/.test(ua) && ua.indexOf ('spoofer') < 0; | + | var noSuggestions = false; |
− | // And even more compatbility. HotCat was developed without jQuery, and anyway current jQuery
| |
− | // (1.7.1) doesn't seem to support in jquery.getJSON() or jQuery.ajax() the automatic
| |
− | // switching from GET to POST requests if the query arguments would make the uri too long.
| |
− | // (IE has a hard limit of 2083 bytes, and the servers may have limits around 4 or 8kB.)
| |
− | // Anyway, HotCat is supposed to run on wikis without jQuery, so we'd have to supply some
| |
− | // ajax routines ourselves in any case. We can't rely on the old sajax_init_object(), newer
| |
− | // MW versions (>= 1.19) might not have it.
| |
− | var getJSON = (function () { | |
− | function getRequest () {
| |
− | var request = null;
| |
− | try {
| |
− | request = new window.XMLHttpRequest();
| |
− | } catch (anything) {
| |
− | if (window.ActiveXObject) {
| |
− | try {
| |
− | request = new window.ActiveXObject('Microsoft.XMLHTTP');
| |
− | } catch (any) {
| |
− | }
| |
− | } // end if IE
| |
− | } // end try-catch
| |
− | return request;
| |
− | }
| |
| | | |
− | return function (settings) {
| + | function LoadTrigger( needed ) { |
− | var req = getRequest();
| + | // Define methods in a closure so that self reference is available, |
− | if (!req && settings && settings.error) settings.error (req); | + | // also allows method calls to be detached. |
− | if (!req || !settings || !settings.uri) return req; | + | var self = this; |
− | var uri = armorUri (settings.uri);
| + | self.queue = []; |
− | var args = settings.data || null; | + | self.needed = needed; |
− | var method;
| + | self.register = function ( callback ) { |
− | if (args && uri.length + args.length + 1 > 2000) { | + | if ( self.needed <= 0 ) callback(); // Execute directly |
− | // We lose caching, but at least we can make the request | + | else self.queue.push( callback ); |
− | method = 'POST'; | + | }; |
− | req.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded');
| + | self.loaded = function () { |
− | } else {
| + | self.needed--; |
− | method = 'GET';
| + | if ( self.needed === 0 ) { |
− | if (args) uri += '?' + args;
| + | // Run queued callbacks once |
− | args = null; | + | for ( var i = 0; i < self.queue.length; i++ ) self.queue[ i ](); |
| + | self.queue = []; |
| } | | } |
− | req.open (method, uri, true);
| |
− | req.onreadystatechange = function () {
| |
− | if (req.readyState != 4) return;
| |
− | if (req.status != 200 || !req.responseText || !(/^\s*[\{\[]/.test(req.responseText))) {
| |
− | if (settings.error) settings.error (req);
| |
− | } else {
| |
− | 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;
| |
| } | | } |
| | | |
− | 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. | | // Used to delay running the HotCat setup until /local_defaults and localizations have been loaded. |
| + | var loadTrigger = new LoadTrigger( 2 ); |
| | | |
− | function load (uri) { | + | function load( uri, callback ) { |
− | var head = document.getElementsByTagName ('head')[0];
| + | var s = document.createElement( 'script' ); |
− | var s = document.createElement ('script'); | + | s.src = uri; |
− | s.setAttribute ('src', armorUri(uri)); | + | var called = false; |
− | s.setAttribute ('type', 'text/javascript');
| |
− | var done = false; | |
| | | |
− | function afterLoad () { | + | s.onload = s.onerror = function () { |
− | if (done) return;
| + | if ( !called && callback ) { |
− | done = true;
| + | called = true; |
− | s.onload = s.onreadystatechange = s.onerror = null; // Properly clean up to avoid memory leaks in IE
| + | callback(); |
− | if (head && s.parentNode) head.removeChild (s); | + | } |
− | loadTrigger.loaded();
| + | if ( s.parentNode ) { |
− | }
| + | 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 (); | |
| } | | } |
| }; | | }; |
− | s.onerror = afterLoad; // Clean up, but otherwise ignore errors | + | document.head.appendChild( s ); |
− | head.insertBefore (s, head.firstChild); // appendChild may trigger bugs in IE6 here
| |
| } | | } |
| | | |
− | function loadJS (page) { | + | function loadJS( page, callback ) { |
− | load (conf.wgServer + conf.wgScript + '?title=' + encodeURIComponent (page) + '&action=raw&ctype=text/javascript'); | + | load( conf.wgServer + conf.wgScript + '?title=' + encodeURIComponent( page ) + '&action=raw&ctype=text/javascript', callback ); |
| } | | } |
| | | |
− | function loadURI (href) { | + | function loadURI( href, callback ) { |
| var url = href; | | var url = href; |
− | if (url.substring (0, 2) == '//') { | + | if ( url.substring( 0, 2 ) === '//' ) url = window.location.protocol + url; else if ( url.substring( 0, 1 ) === '/' ) url = conf.wgServer + url; |
− | url = window.location.protocol + url;
| + | |
− | } else if (url.substring (0, 1) == '/') {
| + | load( url, callback ); |
− | url = conf.wgServer + url;
| |
− | }
| |
− | load (url); | |
| } | | } |
| | | |
| // Load local configurations, overriding the pre-set default values in the HotCat object above. This is always loaded | | // Load local configurations, overriding the pre-set default values in the HotCat object above. This is always loaded |
− | // from the wiki where this script is executing, even if this script itself is hotlinked from the Commons. This can | + | // from the wiki where this script is executing, even if this script itself is hotlinked from Commons. This can |
| // be used to change the default settings, or to provide localized interface texts for edit summaries and so on. | | // be used to change the default settings, or to provide localized interface texts for edit summaries and so on. |
− | loadJS ('MediaWiki:Gadget-HotCat.js/local_defaults'); | + | loadJS( 'MediaWiki:Gadget-HotCat.js/local_defaults', loadTrigger.loaded ); |
| | | |
| // Load localized UI texts. These are the texts that HotCat displays on the page itself. Texts shown in edit summaries | | // Load localized UI texts. These are the texts that HotCat displays on the page itself. Texts shown in edit summaries |
| // should be localized in /local_defaults above. | | // should be localized in /local_defaults above. |
− | if (conf.wgUserLanguage != 'en') { | + | if ( conf.wgUserLanguage !== 'en' ) { |
| // Lupo: somebody thought it would be a good idea to add this. So the default is true, and you have to set it to false | | // Lupo: somebody thought it would be a good idea to add this. So the default is true, and you have to set it to false |
− | // explicitly if you're not on the Commons and don't want that. | + | // explicitly if you're not on Commons and don't want that. |
− | if (typeof window.hotcat_translations_from_commons == 'undefined') { | + | if ( window.hotcat_translations_from_commons === undefined ) window.hotcat_translations_from_commons = true; |
− | window.hotcat_translations_from_commons = true;
| + | |
− | }
| |
| // Localization hook to localize HotCat messages, tooltips, and engine names for wgUserLanguage. | | // Localization hook to localize HotCat messages, tooltips, and engine names for wgUserLanguage. |
− | if (window.hotcat_translations_from_commons && conf.wgServer.indexOf('//commons') < 0) { | + | if ( window.hotcat_translations_from_commons && conf.wgServer.indexOf( '//commons' ) < 0 ) { |
− | loadURI ('//commons.wikimedia.org/w/index.php?title=' | + | loadURI( '//commons.wikimedia.org/w/index.php?title=' + |
− | + 'MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage
| + | 'MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage + |
− | + '&action=raw&ctype=text/javascript'
| + | '&action=raw&ctype=text/javascript', loadTrigger.loaded ); |
− | );
| |
| } else { | | } else { |
| // Load translations locally | | // Load translations locally |
− | loadJS ('MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage); | + | loadJS( 'MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage, loadTrigger.loaded ); |
| } | | } |
| } else { | | } else { |
Line 390: |
Line 299: |
| | | |
| // The following regular expression strings are used when searching for categories in wikitext. | | // The following regular expression strings are used when searching for categories in wikitext. |
− | var wikiTextBlank = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+'; | + | var wikiTextBlank = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+'; |
− | var wikiTextBlankRE = new RegExp (wikiTextBlank, 'g'); | + | var wikiTextBlankRE = new RegExp( wikiTextBlank, 'g' ); |
| // Regexp for handling blanks inside a category title or namespace name. | | // Regexp for handling blanks inside a category title or namespace name. |
| // See http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Title.php?revision=104051&view=markup#l2722 | | // See http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Title.php?revision=104051&view=markup#l2722 |
Line 412: |
Line 321: |
| var formattedNamespaces = conf.wgFormattedNamespaces; | | var formattedNamespaces = conf.wgFormattedNamespaces; |
| var namespaceIds = conf.wgNamespaceIds; | | var namespaceIds = conf.wgNamespaceIds; |
− | if (formattedNamespaces) { | + | function autoLocalize( namespaceNumber, fallback ) { |
− | function autoLocalize (namespaceNumber, fallback) {
| + | function createRegexpStr( name ) { |
− | function create_regexp_str (name)
| + | if ( !name || !name.length ) return ''; |
− | { | + | |
− | if (!name || name.length === 0) return "";
| + | var regex_name = ''; |
− | var regex_name = "";
| + | for ( var i = 0; i < name.length; i++ ) { |
− | for (var i = 0; i < name.length; i++){
| + | var initial = name.charAt( i ), |
− | var initial = name.substr (i, 1);
| + | ll = initial.toLowerCase(), |
− | var ll = initial.toLowerCase (); | + | ul = initial.toUpperCase(); |
− | var ul = initial.toUpperCase (); | + | if ( ll === ul ) regex_name += initial; else regex_name += '[' + ll + ul + ']'; |
− | if (ll == ul){
| |
− | regex_name += initial;
| |
− | } else {
| |
− | regex_name += '[' + ll + ul + ']';
| |
− | }
| |
− | }
| |
− | return regex_name
| |
− | .replace(/([\\\^\$\.\?\*\+\(\)])/g, '\\$1')
| |
− | .replace (wikiTextBlankRE, wikiTextBlank);
| |
| } | | } |
| + | return regex_name |
| + | .replace( /([\\^$.?*+()])/g, '\\$1' ) |
| + | .replace( wikiTextBlankRE, wikiTextBlank ); |
| + | } |
| + | |
| + | fallback = fallback.toLowerCase(); |
| + | var canonical = formattedNamespaces[ String( namespaceNumber ) ].toLowerCase(); |
| + | var regexp = createRegexpStr( canonical ); |
| + | if ( fallback && canonical !== fallback ) regexp += '|' + createRegexpStr( fallback ); |
| | | |
− | fallback = fallback.toLowerCase();
| + | if ( namespaceIds ) { |
− | var canonical = formattedNamespaces["" + namespaceNumber].toLowerCase();
| + | for ( var cat_name in namespaceIds ) { |
− | var regexp = create_regexp_str (canonical);
| + | if ( |
− | if (fallback && canonical != fallback) regexp += '|' + create_regexp_str(fallback);
| + | typeof cat_name === 'string' && |
− | if (namespaceIds) {
| + | cat_name.toLowerCase() !== canonical && |
− | for (var cat_name in namespaceIds) {
| + | cat_name.toLowerCase() !== fallback && |
− | if ( typeof cat_name == 'string'
| + | namespaceIds[ cat_name ] === namespaceNumber |
− | && cat_name.toLowerCase() != canonical
| + | ) { |
− | && cat_name.toLowerCase() != fallback
| + | regexp += '|' + createRegexpStr( cat_name ); |
− | && namespaceIds[cat_name] == namespaceNumber)
| |
− | { | |
− | regexp += '|' + create_regexp_str(cat_name);
| |
− | }
| |
| } | | } |
| } | | } |
− | return regexp;
| |
| } | | } |
| + | return regexp; |
| + | } |
| | | |
− | if (formattedNamespaces['14']) {
| + | HC.category_canonical = formattedNamespaces[ '14' ]; |
− | HotCat.category_canonical = formattedNamespaces['14'];
| + | HC.category_regexp = autoLocalize( 14, 'category' ); |
− | HotCat.category_regexp = autoLocalize (14, 'category');
| + | if ( formattedNamespaces[ '10' ] ) HC.template_regexp = autoLocalize( 10, 'template' ); |
− | }
| |
− | if (formattedNamespaces['10']) {
| |
− | HotCat.template_regexp = autoLocalize (10, 'template');
| |
− | }
| |
− | }
| |
| | | |
| // Utility functions. Yes, this duplicates some functionality that also exists in other places, but | | // Utility functions. Yes, this duplicates some functionality that also exists in other places, but |
− | // to keep this whole stuff in a single file not depending on any other on-wiki Javascripts, we re-do | + | // to keep this whole stuff in a single file not depending on any other on-wiki JavaScripts, we re-do |
| // these few operations here. | | // these few operations here. |
− | function bind (func, target) {
| + | function make( arg, literal ) { |
− | var f = func, tgt = target;
| + | if ( !arg ) return null; |
− | return function () { return f.apply (tgt, arguments); };
| + | |
− | }
| + | return literal ? document.createTextNode( arg ) : document.createElement( arg ); |
− | function make (arg, literal) { | |
− | if (!arg) return null; | |
− | return literal ? document.createTextNode (arg) : document.createElement (arg); | |
| } | | } |
− | function param (name, uri) { | + | function param( name, uri ) { |
− | if (typeof uri == 'undefined' || uri === null) uri = document.location.href; | + | uri = uri || document.location.href; |
− | var re = new RegExp ('[&?]' + name + '=([^&#]*)'); | + | var re = new RegExp( '[&?]' + name + '=([^&#]*)' ); |
− | var m = re.exec (uri); | + | var m = re.exec( uri ); |
− | if (m && m.length > 1) return decodeURIComponent(m[1]); | + | if ( m && m.length > 1 ) return decodeURIComponent( m[ 1 ] ); |
| return null; | | return null; |
| } | | } |
− | function title (href) { | + | function title( href ) { |
− | if (!href) return null; | + | if ( !href ) return null; |
| + | |
| var script = conf.wgScript + '?'; | | var script = conf.wgScript + '?'; |
− | if (href.indexOf (script) === 0 || href.indexOf (conf.wgServer + script) === 0 || conf.wgServer.substring(0, 2) == '//' && href.indexOf (document.location.protocol + conf.wgServer + script) === 0) { | + | if ( href.indexOf( script ) === 0 || href.indexOf( conf.wgServer + script ) === 0 || conf.wgServer.substring( 0, 2 ) === '//' && href.indexOf( document.location.protocol + conf.wgServer + script ) === 0 ) { |
| // href="/w/index.php?title=..." | | // href="/w/index.php?title=..." |
− | return param ('title', href); | + | return param( 'title', href ); |
| } else { | | } else { |
| // href="/wiki/..." | | // href="/wiki/..." |
− | var prefix = conf.wgArticlePath.replace ('$1', ""); | + | var prefix = conf.wgArticlePath.replace( '$1', '' ); |
− | if (href.indexOf (prefix) !== 0) prefix = conf.wgServer + prefix; // Fully expanded URL? | + | if ( href.indexOf( prefix ) ) prefix = conf.wgServer + prefix; // Fully expanded URL? |
− | if (href.indexOf (prefix) !== 0 && prefix.substring(0, 2) == '//') prefix = document.location.protocol + prefix; // Protocol-relative wgServer? | + | |
− | if (href.indexOf (prefix) === 0) | + | if ( href.indexOf( prefix ) && prefix.substring( 0, 2 ) === '//' ) prefix = document.location.protocol + prefix; // Protocol-relative wgServer? |
− | return decodeURIComponent (href.substring (prefix.length));
| + | |
| + | if ( href.indexOf( prefix ) === 0 ) return decodeURIComponent( href.substring( prefix.length ) ); |
| } | | } |
| return null; | | return null; |
| } | | } |
− | function hasClass (elem, name) { | + | function hasClass( elem, name ) { |
− | return (' ' + elem.className + ' ').indexOf (' ' + name + ' ') >= 0; | + | return ( ' ' + elem.className + ' ' ).indexOf( ' ' + name + ' ' ) >= 0; |
| } | | } |
− | function capitalize (str) { | + | function capitalize( str ) { |
− | if (!str || str.length === 0) return str; | + | if ( !str || !str.length ) return str; |
− | return str.substr(0, 1).toUpperCase() + str.substr (1); | + | |
| + | return str.substr( 0, 1 ).toUpperCase() + str.substr( 1 ); |
| } | | } |
− | function wikiPagePath (pageName) { | + | function wikiPagePath( pageName ) { |
| // Note: do not simply use encodeURI, it doesn't encode '&', which might break if wgArticlePath actually has the $1 in | | // Note: do not simply use encodeURI, it doesn't encode '&', which might break if wgArticlePath actually has the $1 in |
| // a query parameter. | | // a query parameter. |
− | return conf.wgArticlePath.replace('$1', encodeURIComponent (pageName).replace(/%3A/g, ':').replace(/%2F/g, '/')); | + | return conf.wgArticlePath.replace( '$1', encodeURIComponent( pageName ).replace( /%3A/g, ':' ).replace( /%2F/g, '/' ) ); |
| } | | } |
− | function escapeRE(str) { | + | function escapeRE( str ) { |
− | return str.replace(/([\\\^\$\.\?\*\+\(\)\[\]])/g, '\\$1'); | + | return str.replace( /([\\^$.?*+()[\]])/g, '\\$1' ); |
| } | | } |
| | | |
− | function substituteFactory (options) { | + | function substituteFactory( options ) { |
| options = options || {}; | | options = options || {}; |
| var lead = options.indicator || '$'; | | var lead = options.indicator || '$'; |
− | var indicator = escapeRE (lead); | + | var indicator = escapeRE( lead ); |
− | var lbrace = escapeRE (options.lbrace || '{'); | + | var lbrace = escapeRE( options.lbrace || '{' ); |
− | var rbrace = escapeRE (options.rbrace || '}'); | + | var rbrace = escapeRE( options.rbrace || '}' ); |
| var re; | | var re; |
| | | |
| re = new RegExp( | | re = new RegExp( |
− | '(?:' + indicator + '(' + indicator + '))|' // $$
| + | // $$ |
− | +'(?:' + indicator + '(\\d+))|' // $0, $1 | + | '(?:' + indicator + '(' + indicator + '))|' + |
− | +'(?:' + indicator + '(?:' + lbrace + '([^' + lbrace + rbrace + ']+)' + rbrace + '))|' // ${key} | + | // $0, $1 |
− | +'(?:' + indicator + '(?!(?:[' + indicator + lbrace + ']|\\d))(\\S+?)\\b)' // $key (only if first char after $ is not $, digit, or { ) | + | '(?:' + indicator + '(\\d+))|' + |
− | ,'g'); | + | // ${key} |
| + | '(?:' + indicator + '(?:' + lbrace + '([^' + lbrace + rbrace + ']+)' + rbrace + '))|' + |
| + | // $key (only if first char after $ is not $, digit, or { ) |
| + | '(?:' + indicator + '(?!(?:[' + indicator + lbrace + ']|\\d))(\\S+?)\\b)', |
| + | 'g' |
| + | ); |
| // Replace $1, $2, or ${key1}, ${key2}, or $key1, $key2 by values from map. $$ is replaced by a single $. | | // Replace $1, $2, or ${key1}, ${key2}, or $key1, $key2 by values from map. $$ is replaced by a single $. |
− | return function (str, map) { | + | return function ( str, map ) { |
− | if (!map) return str; | + | if ( !map ) return str; |
− | return str.replace(re | + | |
− | ,function (match, prefix, idx, key, alpha) {
| + | return str.replace( re, function ( match, prefix, idx, key, alpha ) { |
− | if (prefix == lead) return lead;
| + | if ( prefix === lead ) return lead; |
− | var k = alpha || key || idx;
| + | |
− | var replacement = typeof map[k] === 'function' ? map[k](match, k) : map[k];
| + | var k = alpha || key || idx; |
− | return typeof replacement === 'string' ? replacement : (replacement || match);
| + | var replacement = typeof map[ k ] === 'function' ? map[ k ]( match, k ) : map[ k ]; |
− | }
| + | return typeof replacement === 'string' ? replacement : ( replacement || match ); |
− | );
| + | } ); |
| }; | | }; |
| } | | } |
| | | |
| var substitute = substituteFactory(); | | var substitute = substituteFactory(); |
− | var replaceShortcuts = (function () { | + | var replaceShortcuts = ( function () { |
− | var replaceHash = substituteFactory({indicator:'#',lbrace:'[',rbrace:']'}); | + | var replaceHash = substituteFactory( { |
− | return function (str, map) { | + | indicator: '#', |
− | var s = replaceHash (str, map); | + | lbrace: '[', |
− | return HotCat.capitalizePageNames ? capitalize(s) : s; | + | rbrace: ']' |
| + | } ); |
| + | return function ( str, map ) { |
| + | var s = replaceHash( str, map ); |
| + | return HC.capitalizePageNames ? capitalize( s ) : s; |
| }; | | }; |
− | })(); | + | }() ); |
| | | |
| // Text modification | | // Text modification |
| | | |
| var findCatsRE = | | var findCatsRE = |
− | new RegExp ('\\[\\[' + wikiTextBlankOrBidi + '(?:' + HotCat.category_regexp + ')' + wikiTextBlankOrBidi + ':[^\\]]+\\]\\]', 'g');
| + | new RegExp( '\\[\\[' + wikiTextBlankOrBidi + '(?:' + HC.category_regexp + ')' + wikiTextBlankOrBidi + ':[^\\]]+\\]\\]', 'g' ); |
| | | |
− | function replaceByBlanks (match) { | + | function replaceByBlanks( match ) { |
− | return match.replace(/(\s|\S)/g, ' '); // /./ doesn't match linebreaks. /(\s|\S)/ does. | + | return match.replace( /(\s|\S)/g, ' ' ); // /./ doesn't match linebreaks. /(\s|\S)/ does. |
| } | | } |
| | | |
− | function find_category (wikitext, category, once) { | + | function find_category( wikitext, category, once ) { |
| var cat_regex = null; | | var cat_regex = null; |
− | if(HotCat.template_categories[category]){ | + | if ( HC.template_categories[ category ] ) { |
− | cat_regex = new RegExp ('\\{\\{' + wikiTextBlankOrBidi + '(' + HotCat.template_regexp + '(?=' + wikiTextBlankOrBidi + ':))?' + wikiTextBlankOrBidi | + | cat_regex = new RegExp( |
− | + '(?:' + HotCat.template_categories[category] + ')'
| + | '\\{\\{' + wikiTextBlankOrBidi + '(' + HC.template_regexp + '(?=' + wikiTextBlankOrBidi + ':))?' + wikiTextBlankOrBidi + |
− | + wikiTextBlankOrBidi + '(\\|.*?)?\\}\\}', 'g'
| + | '(?:' + HC.template_categories[ category ] + ')' + |
| + | wikiTextBlankOrBidi + '(\\|.*?)?\\}\\}', |
| + | 'g' |
| ); | | ); |
| } else { | | } else { |
− | var cat_name = escapeRE (category); | + | var cat_name = escapeRE( category ); |
− | var initial = cat_name.substr (0, 1); | + | var initial = cat_name.substr( 0, 1 ); |
− | cat_regex = new RegExp ('\\[\\[' + wikiTextBlankOrBidi + '(' + HotCat.category_regexp + ')' + wikiTextBlankOrBidi + ':' + wikiTextBlankOrBidi | + | cat_regex = new RegExp( |
− | + (initial == '\\' || !HotCat.capitalizePageNames
| + | '\\[\\[' + wikiTextBlankOrBidi + '(' + HC.category_regexp + ')' + wikiTextBlankOrBidi + ':' + wikiTextBlankOrBidi + |
− | ? initial
| + | ( initial === '\\' || !HC.capitalizePageNames ? |
− | : '[' + initial.toUpperCase() + initial.toLowerCase() + ']')
| + | initial : |
− | + cat_name.substring (1).replace (wikiTextBlankRE, wikiTextBlank)
| + | '[' + initial.toUpperCase() + initial.toLowerCase() + ']' ) + |
− | + wikiTextBlankOrBidi + '(\\|.*?)?\\]\\]', 'g'
| + | cat_name.substring( 1 ).replace( wikiTextBlankRE, wikiTextBlank ) + |
| + | wikiTextBlankOrBidi + '(\\|.*?)?\\]\\]', |
| + | 'g' |
| ); | | ); |
| } | | } |
− | if (once) return cat_regex.exec (wikitext); | + | if ( once ) return cat_regex.exec( wikitext ); |
| + | |
| var copiedtext = wikitext | | var copiedtext = wikitext |
− | .replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks)
| + | .replace( /<!--(\s|\S)*?-->/g, replaceByBlanks ) |
− | .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks);
| + | .replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, replaceByBlanks ); |
| var result = []; | | var result = []; |
| var curr_match = null; | | var curr_match = null; |
− | while ((curr_match = cat_regex.exec (copiedtext)) !== null) { | + | while ( ( curr_match = cat_regex.exec( copiedtext ) ) !== null ) { |
− | result.push ({match : curr_match}); | + | result.push( { |
| + | match: curr_match |
| + | } ); |
| } | | } |
| result.re = cat_regex; | | result.re = cat_regex; |
− | return result; // An array containing all matches, with positions, in result[i].match | + | return result; // An array containing all matches, with positions, in result[ i ].match |
| } | | } |
| | | |
| var interlanguageRE = null; | | var interlanguageRE = null; |
| | | |
− | function change_category (wikitext, toRemove, toAdd, key, is_hidden) { | + | function change_category( wikitext, toRemove, toAdd, key, is_hidden ) { |
| | | |
− | function find_insertionpoint (wikitext) { | + | function find_insertionpoint( wikitext ) { |
| var copiedtext = wikitext | | var copiedtext = wikitext |
− | .replace(/<\!--(\s|\S)*?--\>/g, replaceByBlanks)
| + | .replace( /<!--(\s|\S)*?-->/g, replaceByBlanks ) |
− | .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, replaceByBlanks);
| + | .replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, replaceByBlanks ); |
| // Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element". | | // Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element". |
| var index = -1; | | var index = -1; |
| findCatsRE.lastIndex = 0; | | findCatsRE.lastIndex = 0; |
− | while (findCatsRE.exec(copiedtext) !== null) index = findCatsRE.lastIndex; | + | while ( findCatsRE.exec( copiedtext ) !== null ) index = findCatsRE.lastIndex; |
− | if (index < 0) { | + | |
| + | if ( index < 0 ) { |
| // Find the index of the first interlanguage link... | | // Find the index of the first interlanguage link... |
| var match = null; | | var match = null; |
− | if (!interlanguageRE) { | + | if ( !interlanguageRE ) { |
− | // Approximation without API: interlanguage links start with 2 to 3 lower case letters, optionally followed by
| + | // Approximation without API: interlanguage links start with 2 to 3 lower case letters, optionally followed by |
− | // a sequence of groups consisting of a dash followed by one or more lower case letters. Exceptions are "simple"
| + | // a sequence of groups consisting of a dash followed by one or more lower case letters. Exceptions are "simple" |
− | // and "tokipona".
| + | // and "tokipona". |
− | match = /((^|\n\r?)(\[\[\s*(([a-z]{2,3}(-[a-z]+)*)|simple|tokipona)\s*:[^\]]+\]\]\s*))+$/.exec (copiedtext); | + | match = /((^|\n\r?)(\[\[\s*(([a-z]{2,3}(-[a-z]+)*)|simple|tokipona)\s*:[^\]]+\]\]\s*))+$/.exec( copiedtext ); |
| } else { | | } else { |
− | match = interlanguageRE.exec(copiedtext); | + | match = interlanguageRE.exec( copiedtext ); |
| } | | } |
− | if (match) index = match.index; | + | if ( match ) index = match.index; |
− | return {idx : index, onCat : false}; | + | |
| + | return { |
| + | idx: index, |
| + | onCat: false |
| + | }; |
| } | | } |
− | return {idx : index, onCat : index >= 0}; | + | return { |
| + | idx: index, |
| + | onCat: index >= 0 |
| + | }; |
| } | | } |
| | | |
− | var summary = []; | + | var summary = [], |
− | var nameSpace = HotCat.category_canonical;
| + | nameSpace = HC.category_canonical, |
− | var cat_point = -1; // Position of removed category;
| + | cat_point = -1, // Position of removed category; |
− | | + | keyChange = ( toRemove && toAdd && toRemove === toAdd && toAdd.length ), |
− | if (key) key = '|' + key;
| + | matches; |
− | var keyChange = (toRemove && toAdd && toRemove == toAdd && toAdd.length > 0);
| + | if ( key ) key = '|' + key; |
− | var matches; | + | // Remove |
− | if (toRemove && toRemove.length > 0) { | + | if ( toRemove && toRemove.length ) { |
− | matches = find_category (wikitext, toRemove); | + | matches = find_category( wikitext, toRemove ); |
− | if (!matches || matches.length === 0) { | + | if ( !matches || !matches.length ) { |
− | return {text: wikitext, 'summary': summary, error: HotCat.messages.cat_notFound.replace (/\$1/g, toRemove)}; | + | return { |
| + | text: wikitext, |
| + | summary: summary, |
| + | error: HC.messages.cat_notFound.replace( /\$1/g, toRemove ) |
| + | }; |
| } else { | | } else { |
− | var before = wikitext.substring (0, matches[0].match.index); | + | var before = wikitext.substring( 0, matches[ 0 ].match.index ), |
− | var after = wikitext.substring (matches[0].match.index + matches[0].match[0].length);
| + | after = wikitext.substring( matches[ 0 ].match.index + matches[ 0 ].match[ 0 ].length ); |
− | if (matches.length > 1) { | + | if ( matches.length > 1 ) { |
− | // Remove all occurrences in after
| + | // Remove all occurrences in after |
| matches.re.lastIndex = 0; | | matches.re.lastIndex = 0; |
− | after = after.replace (matches.re, ""); | + | after = after.replace( matches.re, '' ); |
| } | | } |
− | if (toAdd) { | + | if ( toAdd ) { |
− | nameSpace = matches[0].match[1] || nameSpace; | + | // nameSpace = matches[ 0 ].match[ 1 ] || nameSpace; Canonical namespace should be always preferred |
− | if (key === null) key = matches[0].match[2]; // Remember the category key, if any. | + | if ( key === null ) key = matches[ 0 ].match[ 2 ]; |
| + | // Remember the category key, if any. |
| } | | } |
| // Remove whitespace (properly): strip whitespace, but only up to the next line feed. | | // Remove whitespace (properly): strip whitespace, but only up to the next line feed. |
Line 643: |
Line 573: |
| // whitespace characters, insert a blank. | | // whitespace characters, insert a blank. |
| var i = before.length - 1; | | var i = before.length - 1; |
− | while (i >= 0 && before.charAt (i) != '\n' && before.substr (i, 1).search (/\s/) >= 0) i--; | + | while ( i >= 0 && before.charAt( i ) !== '\n' && before.substr( i, 1 ).search( /\s/ ) >= 0 ) i--; |
| + | |
| var j = 0; | | var j = 0; |
− | while (j < after.length && after.charAt (j) != '\n' && after.substr (j, 1).search (/\s/) >= 0) | + | while ( j < after.length && after.charAt( j ) !== '\n' && after.substr( j, 1 ).search( /\s/ ) >= 0 ) j++; |
− | j++;
| + | |
− | if (i >= 0 && before.charAt (i) == '\n' && (after.length === 0 || j < after.length && after.charAt (j) == '\n')) | + | if ( i >= 0 && before.charAt( i ) === '\n' && ( !after.length || j < after.length && after.charAt( j ) === '\n' ) ) i--; |
− | i--;
| + | |
− | if (i >= 0) before = before.substring (0, i+1); else before = ""; | + | if ( i >= 0 ) before = before.substring( 0, i + 1 ); else before = ''; |
− | if (j < after.length) after = after.substring (j); else after = ""; | + | |
− | if (before.length > 0 && before.substring (before.length - 1).search (/\S/) >= 0 | + | if ( j < after.length ) after = after.substring( j ); else after = ''; |
− | && after.length > 0 && after.substr (0, 1).search (/\S/) >= 0)
| + | |
| + | if ( |
| + | before.length && before.substring( before.length - 1 ).search( /\S/ ) >= 0 && |
| + | after.length && after.substr( 0, 1 ).search( /\S/ ) >= 0 |
| + | ) { |
| before += ' '; | | before += ' '; |
| + | } |
| + | |
| cat_point = before.length; | | cat_point = before.length; |
− | if (cat_point === 0 && after.length > 0 && after.substr(0,1) == '\n') { | + | if ( cat_point === 0 && after.length && after.substr( 0, 1 ) === '\n' ) after = after.substr( 1 ); |
− | after = after.substr(1);
| + | |
− | }
| |
| wikitext = before + after; | | wikitext = before + after; |
− | if (!keyChange) { | + | if ( !keyChange ) { |
− | if(HotCat.template_categories[toRemove]) { | + | if ( HC.template_categories[ toRemove ] ) { summary.push( HC.messages.template_removed.replace( /\$1/g, toRemove ) ); } else { summary.push( HC.messages.cat_removed.replace( /\$1/g, toRemove ) ); } |
− | summary.push (HotCat.messages.template_removed.replace (/\$1/g, toRemove));
| |
− | } else {
| |
− | summary.push (HotCat.messages.cat_removed.replace (/\$1/g, toRemove));
| |
− | }
| |
| } | | } |
| + | |
| } | | } |
| } | | } |
− | if (toAdd && toAdd.length > 0) { | + | // Add |
− | matches = find_category (wikitext, toAdd); | + | if ( toAdd && toAdd.length ) { |
− | if (matches && matches.length > 0) { | + | matches = find_category( wikitext, toAdd ); |
− | return {text: wikitext, 'summary': summary, error : HotCat.messages.cat_exists.replace (/\$1/g, toAdd)}; | + | if ( matches && matches.length ) { |
| + | // Already exists |
| + | return { |
| + | text: wikitext, |
| + | summary: summary, |
| + | error: HC.messages.cat_exists.replace( /\$1/g, toAdd ) |
| + | }; |
| } else { | | } else { |
| var onCat = false; | | var onCat = false; |
− | if (cat_point < 0) { | + | if ( cat_point < 0 ) { |
− | var point = find_insertionpoint (wikitext); | + | var point = find_insertionpoint( wikitext ); |
| cat_point = point.idx; | | cat_point = point.idx; |
| onCat = point.onCat; | | onCat = point.onCat; |
Line 681: |
Line 620: |
| onCat = true; | | onCat = true; |
| } | | } |
− | var newcatstring = '[[' + nameSpace + ':' + toAdd + (key || "") + ']]'; | + | var newcatstring = '[[' + nameSpace + ':' + toAdd + ( key || '' ) + ']]'; |
− | if (cat_point >= 0) { | + | if ( cat_point >= 0 ) { |
− | var suffix = wikitext.substring (cat_point); | + | var suffix = wikitext.substring( cat_point ); |
− | wikitext = wikitext.substring (0, cat_point) + (cat_point > 0 ? '\n' : "") + newcatstring + (!onCat ? '\n' : ""); | + | wikitext = wikitext.substring( 0, cat_point ) + ( cat_point > 0 ? '\n' : '' ) + newcatstring + ( !onCat ? '\n' : '' ); |
− | if (suffix.length > 0 && suffix.substr(0, 1) != '\n') { | + | if ( suffix.length && suffix.substr( 0, 1 ) !== '\n' ) wikitext += '\n' + suffix; else wikitext += suffix; |
− | wikitext += '\n' + suffix;
| |
− | } else {
| |
− | wikitext += suffix;
| |
− | }
| |
| } else { | | } else { |
− | if (wikitext.length > 0 && wikitext.substr (wikitext.length - 1, 1) != '\n') | + | if ( wikitext.length && wikitext.substr( wikitext.length - 1, 1 ) !== '\n' ) wikitext += '\n'; |
− | wikitext += '\n';
| + | |
− | wikitext += (wikitext.length > 0 ? '\n' : "") + newcatstring; | + | wikitext += ( wikitext.length ? '\n' : '' ) + newcatstring; |
| } | | } |
− | if (keyChange) { | + | if ( keyChange ) { |
− | var k = key || ""; | + | var k = key || ''; |
− | if (k.length > 0) k = k.substr (1); | + | if ( k.length ) k = k.substr( 1 ); |
− | summary.push (substitute (HotCat.messages.cat_keychange, [null, toAdd, k])); | + | |
| + | summary.push( substitute( HC.messages.cat_keychange, [ null, toAdd, k ] ) ); |
| } else { | | } else { |
− | summary.push (HotCat.messages.cat_added.replace (/\$1/g, toAdd)); | + | summary.push( HC.messages.cat_added.replace( /\$1/g, toAdd ) ); |
| } | | } |
− | if (HotCat.uncat_regexp && !is_hidden) { | + | if ( HC.uncat_regexp && !is_hidden ) { |
− | var txt = wikitext.replace (HotCat.uncat_regexp, ""); // Remove "uncat" templates | + | var txt = wikitext.replace( HC.uncat_regexp, '' ); // Remove "uncat" templates |
− | if (txt.length != wikitext.length) { | + | if ( txt.length !== wikitext.length ) { |
| wikitext = txt; | | wikitext = txt; |
− | summary.push (HotCat.messages.uncat_removed); | + | summary.push( HC.messages.uncat_removed ); |
| } | | } |
| } | | } |
| } | | } |
| } | | } |
− | return {text: wikitext, 'summary': summary, error: null}; | + | return { |
| + | text: wikitext, |
| + | summary: summary, |
| + | error: null |
| + | }; |
| } | | } |
| | | |
| // The real HotCat UI | | // The real HotCat UI |
| | | |
− | function evtKeys (e) { | + | function evtKeys( e ) { |
− | e = e || window.event || window.Event; // W3C, IE, Netscape | + | /* eslint-disable no-bitwise */ |
| var code = 0; | | var code = 0; |
− | if (typeof e.ctrlKey != 'undefined') { // All modern browsers | + | if ( e.ctrlKey ) { // All modern browsers |
− | // Ctrl-click seems to be overloaded in FF/Mac (it opens a pop-up menu), so treat cmd-click
| + | // Ctrl-click seems to be overloaded in FF/Mac (it opens a pop-up menu), so treat cmd-click |
− | // as a ctrl-click, too.
| + | // as a ctrl-click, too. |
− | if (e.ctrlKey || e.metaKey) code |= 1; | + | if ( e.ctrlKey || e.metaKey ) code |= 1; |
− | if (e.shiftKey) code |= 2; | + | |
− | } else if (typeof e.modifiers != 'undefined') { // Netscape...
| + | if ( e.shiftKey ) code |= 2; |
− | if (e.modifiers & (Event.CONTROL_MASK | Event.META_MASK)) code |= 1;
| |
− | if (e.modifiers & Event.SHIFT_MASK) code |= 2;
| |
| } | | } |
| return code; | | return code; |
| } | | } |
− | function evtKill (e) { | + | function evtKill( e ) { |
− | e = e || window.event || window.Event; // W3C, IE, Netscape
| + | if ( e.preventDefault ) { |
− | if (typeof e.preventDefault != 'undefined') { | + | e.preventDefault(); |
− | e.preventDefault (); | + | e.stopPropagation(); |
− | e.stopPropagation (); | + | } else { |
− | } else | |
| e.cancelBubble = true; | | e.cancelBubble = true; |
| + | } |
| return false; | | return false; |
− | }
| |
− | function addEvent (node, evt, f, capture) {
| |
− | if (window.jQuery && (!capture || !node.addEventListener)) window.jQuery (node).bind (evt, f);
| |
− | else if (node.addEventListener) node.addEventListener (evt, f, capture); // FF etc; IE >= 9
| |
− | else if (node.attachEvent) node.attachEvent ('on' + evt, f); // Older IE; Opera
| |
− | else node['on' + evt] = f; // Very old!
| |
| } | | } |
| | | |
− | var catLine = null; | + | var catLine = null, |
− | var onUpload = false;
| + | onUpload = false, |
− | var editors = [];
| + | editors = [], |
| | | |
− | var commitButton = null;
| + | commitButton = null, |
− | var commitForm = null;
| + | commitForm = null, |
− | var multiSpan = null;
| + | multiSpan = null, |
| | | |
− | var pageText = null;
| + | pageText = null, |
− | var pageTime = null;
| + | pageTime = null, |
− | var pageWatched = false;
| + | pageWatched = false, |
− | var watchCreate = false;
| + | watchCreate = false, |
− | var watchEdit = false;
| + | watchEdit = false, |
− | var minorEdits = false;
| + | minorEdits = false, |
− | var editToken = null;
| + | editToken = null, |
| | | |
− | var is_rtl = false;
| + | is_rtl = false, |
− | var serverTime = null;
| + | serverTime = null, |
− | var lastRevId = null;
| + | lastRevId = null, |
− | var pageTextRevId = null;
| + | pageTextRevId = null, |
− | var conflictingUser = null;
| + | conflictingUser = null, |
| | | |
− | var newDOM = false; // true if MediaWiki serves the new UL-LI DOM for categories
| + | newDOM = false; // true if MediaWiki serves the new UL-LI DOM for categories |
| | | |
− | function setMultiInput () { | + | function CategoryEditor() { |
− | if (commitButton || onUpload) return; | + | this.initialize.apply( this, arguments ); |
− | commitButton = make ('input');
| |
− | commitButton.type = 'button';
| |
− | commitButton.value = HotCat.messages.commit;
| |
− | commitButton.onclick = multiSubmit;
| |
− | if (multiSpan) {
| |
− | multiSpan.parentNode.replaceChild (commitButton, multiSpan);
| |
− | } else {
| |
− | catLine.appendChild (commitButton);
| |
− | }
| |
| } | | } |
| | | |
− | function checkMultiInput () { | + | function setPage( json ) { |
− | if (!commitButton) return; | + | var startTime = null; |
− | var has_changes = false;
| + | if ( json && json.query ) { |
− | for (var i = 0; i < editors.length; i++) {
| + | if ( json.query.pages ) { |
− | if (editors[i].state != CategoryEditor.UNCHANGED) {
| + | var page = json.query.pages[ !conf.wgArticleId ? '-1' : String( conf.wgArticleId ) ]; |
− | has_changes = true; | + | if ( page ) { |
− | break; | + | 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; |
| } | | } |
| } | | } |
− | commitButton.disabled = !has_changes;
| |
− | }
| |
− |
| |
− | function currentTimestamp () {
| |
− | var now = new Date();
| |
− | var ts = "" + now.getUTCFullYear();
| |
− | function two (s) { return s.substr (s.length - 2); }
| |
− | ts = ts
| |
− | + two ('0' + (now.getUTCMonth() + 1))
| |
− | + two ('0' + now.getUTCDate())
| |
− | + two ('00' + now.getUTCHours())
| |
− | + two ('00' + now.getUTCMinutes())
| |
− | + two ('00' + now.getUTCSeconds());
| |
− | return ts;
| |
| } | | } |
| | | |
| var saveInProgress = false; | | var saveInProgress = false; |
− | function initiateEdit (doEdit, failure) { | + | function initiateEdit( doEdit, failure ) { |
− | if (saveInProgress) return; | + | if ( saveInProgress ) return; |
| saveInProgress = true; | | saveInProgress = true; |
| var oldButtonState; | | var oldButtonState; |
− | if (commitButton) { | + | if ( commitButton ) { |
| oldButtonState = commitButton.disabled; | | oldButtonState = commitButton.disabled; |
| commitButton.disabled = true; | | commitButton.disabled = true; |
Line 820: |
Line 767: |
| function fail() { | | function fail() { |
| saveInProgress = false; | | saveInProgress = false; |
− | if (commitButton) commitButton.disabled = oldButtonState; | + | if ( commitButton ) commitButton.disabled = oldButtonState; |
− | failure.apply(this, arguments); | + | failure.apply( this, arguments ); |
| } | | } |
| | | |
| // Must use Ajax here to get the user options and the edit token. | | // Must use Ajax here to get the user options and the edit token. |
| + | $.getJSON( |
| + | 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 = HC.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 ]; } |
| | | |
− | getJSON ({ | + | return substitute( msg, [ null, String( count ) ] ); |
− | uri : conf.wgServer + conf.wgScriptPath + '/api.php'
| |
− | ,data : 'format=json&action=query&rawcontinue=&titles=' + encodeURIComponent (conf.wgPageName)
| |
− | + '&prop=info%7Crevisions%7Clanglinks&inprop=watched&intoken=edit&rvprop=content%7Ctimestamp%7Cids%7Cuser&lllimit=500'
| |
− | + '&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); }
| |
− | });
| |
| } | | } |
| | | |
− | function multiChangeMsg (count) { | + | function currentTimestamp() { |
− | var msg = HotCat.messages.multi_change; | + | var now = new Date(); |
− | if (typeof msg != 'string' && msg.length) { | + | var ts = String( now.getUTCFullYear() ); |
− | if (window.mw && mw.language && mw.language.convertPlural) {
| + | function two( s ) { |
− | msg = mw.language.convertPlural (count, msg);
| + | return s.substr( s.length - 2 ); |
− | } else {
| |
− | msg = msg[msg.length-1];
| |
− | }
| |
| } | | } |
− | return substitute (msg, [null, "" + count]); | + | ts += |
| + | two( '0' + ( now.getUTCMonth() + 1 ) ) + |
| + | two( '0' + now.getUTCDate() ) + |
| + | two( '00' + now.getUTCHours() ) + |
| + | two( '00' + now.getUTCMinutes() ) + |
| + | two( '00' + now.getUTCSeconds() ); |
| + | return ts; |
| } | | } |
| | | |
− | function performChanges (failure, singleEditor) { | + | function performChanges( failure, singleEditor ) { |
− | if (pageText === null) { | + | if ( pageText === null ) { |
− | failure (HotCat.messages.multi_error); | + | failure( HC.messages.multi_error ); |
| return; | | return; |
| } | | } |
| // Backwards compatibility after message change (added $2 to cat_keychange) | | // Backwards compatibility after message change (added $2 to cat_keychange) |
− | if (HotCat.messages.cat_keychange.indexOf ('$2') < 0) HotCat.messages.cat_keychange += '"$2"'; | + | if ( HC.messages.cat_keychange.indexOf( '$2' ) < 0 ) HC.messages.cat_keychange += '"$2"'; |
| + | |
| // More backwards-compatibility with earlier HotCat versions: | | // More backwards-compatibility with earlier HotCat versions: |
− | if (!HotCat.messages.short_catchange) HotCat.messages.short_catchange = '[[' + HotCat.category_canonical + ':$1]]'; | + | if ( !HC.messages.short_catchange ) HC.messages.short_catchange = '[[' + HC.category_canonical + ':$1]]'; |
| + | |
| // Create a form and submit it. We don't use the edit API (api.php?action=edit) because | | // Create a form and submit it. We don't use the edit API (api.php?action=edit) because |
| // (a) sensibly reporting back errors like edit conflicts is always a hassle, and | | // (a) sensibly reporting back errors like edit conflicts is always a hassle, and |
Line 871: |
Line 833: |
| // current user, then we set the "oldid" value and switch to diff, which gives the "you are editing an old version; | | // current user, then we set the "oldid" value and switch to diff, which gives the "you are editing an old version; |
| // if you save, any more recent changes will be lost" screen. | | // if you save, any more recent changes will be lost" screen. |
− | var editingOldVersion = lastRevId !== null && lastRevId != conf.wgCurRevisionId || pageTextRevId !== null && pageTextRevId != conf.wgCurRevisionId; | + | var selfEditConflict = ( lastRevId !== null && lastRevId !== conf.wgCurRevisionId || pageTextRevId !== null && |
− | var selfEditConflict = editingOldVersion && conflictingUser && conflictingUser == conf.wgUserName;
| + | pageTextRevId !== conf.wgCurRevisionId ) && conflictingUser && conflictingUser === conf.wgUserName; |
− | if (singleEditor && !singleEditor.noCommit && !HotCat.no_autocommit && editToken && !selfEditConflict) { | + | if ( singleEditor && !singleEditor.noCommit && !HC.no_autocommit && editToken && !selfEditConflict ) { |
| // If we do have an edit conflict, but not with ourself, that's no reason not to attempt to save: the server side may actually be able to | | // If we do have an edit conflict, but not with ourself, that's no reason not to attempt to save: the server side may actually be able to |
| // merge the changes. We just need to make sure that we do present a diff view if it's a self edit conflict. | | // merge the changes. We just need to make sure that we do present a diff view if it's a self edit conflict. |
| commitForm.wpEditToken.value = editToken; | | commitForm.wpEditToken.value = editToken; |
| action = commitForm.wpDiff; | | action = commitForm.wpDiff; |
− | if (action) action.name = action.value = 'wpSave'; | + | if ( action ) action.name = action.value = 'wpSave'; |
| } else { | | } else { |
| action = commitForm.wpSave; | | action = commitForm.wpSave; |
− | if (action) action.name = action.value = 'wpDiff'; | + | if ( action ) action.name = action.value = 'wpDiff'; |
| } | | } |
− | var result = { text : pageText }; | + | var result = { |
− | var changed = [], added = [], deleted = [], changes = 0;
| + | text: pageText |
− | var toEdit = !!singleEditor ? [singleEditor] : editors;
| + | }, |
− | var error = null;
| + | changed = [], |
− | var i;
| + | added = [], |
− | for (i=0; i < toEdit.length; i++) { | + | deleted = [], |
− | if (toEdit[i].state == CategoryEditor.CHANGED) { | + | changes = 0, |
− | result = change_category ( | + | toEdit = singleEditor ? [ singleEditor ] : editors, |
− | result.text
| + | error = null, |
− | , toEdit[i].originalCategory | + | edit, |
− | , toEdit[i].currentCategory | + | i; |
− | , toEdit[i].currentKey | + | for ( i = 0; i < toEdit.length; i++ ) { |
− | , toEdit[i].currentHidden | + | edit = toEdit[ i ]; |
− | );
| + | if ( edit.state === CategoryEditor.CHANGED ) { |
− | if (!result.error) { | + | result = change_category( |
| + | result.text, |
| + | edit.originalCategory, |
| + | edit.currentCategory, |
| + | edit.currentKey, |
| + | edit.currentHidden ); |
| + | if ( !result.error ) { |
| changes++; | | changes++; |
− | if (!toEdit[i].originalCategory || toEdit[i].originalCategory.length === 0) { | + | if ( !edit.originalCategory || !edit.originalCategory.length ) { |
− | added.push (toEdit[i].currentCategory); | + | added.push( edit.currentCategory ); |
| } else { | | } else { |
− | changed.push ({from : toEdit[i].originalCategory, to : toEdit[i].currentCategory}); | + | changed.push( { |
| + | from: edit.originalCategory, |
| + | to: edit.currentCategory |
| + | } ); |
| } | | } |
− | } else if (error === null) { | + | } else if ( error === null ) { |
| error = result.error; | | error = result.error; |
| } | | } |
− | } else if ( toEdit[i].state == CategoryEditor.DELETED | + | } else if ( |
− | && toEdit[i].originalCategory
| + | edit.state === CategoryEditor.DELETED && edit.originalCategory && edit.originalCategory.length ) { |
− | && toEdit[i].originalCategory.length > 0)
| + | result = change_category( |
− | {
| + | result.text, |
− | result = change_category (result.text, toEdit[i].originalCategory, null, null, false); | + | edit.originalCategory, |
− | if (!result.error) { | + | null, null, false ); |
| + | if ( !result.error ) { |
| changes++; | | changes++; |
− | deleted.push (toEdit[i].originalCategory); | + | deleted.push( edit.originalCategory ); |
− | } else if (error === null) { | + | } else if ( error === null ) { |
| error = result.error; | | error = result.error; |
| } | | } |
| } | | } |
| } | | } |
− | if (error !== null) { // Do not commit if there were errors | + | if ( error !== null ) { // Do not commit if there were errors |
| action = commitForm.wpSave; | | action = commitForm.wpSave; |
− | if (action) action.name = action.value = 'wpDiff'; | + | if ( action ) action.name = action.value = 'wpDiff'; |
| } | | } |
| // Fill in the form and submit it | | // Fill in the form and submit it |
− | commitForm.wpAutoSummary.value = 'd41d8cd98f00b204e9800998ecf8427e'; // MD5 hash of the empty string
| |
| commitForm.wpMinoredit.checked = minorEdits; | | commitForm.wpMinoredit.checked = minorEdits; |
− | commitForm.wpWatchthis.checked = conf.wgArticleId === 0 && watchCreate || watchEdit || pageWatched; | + | commitForm.wpWatchthis.checked = !conf.wgArticleId && watchCreate || watchEdit || pageWatched; |
− | if (conf.wgArticleId > 0 || !!singleEditor) { | + | if ( conf.wgArticleId || !!singleEditor ) { |
− | if (changes == 1) { | + | // Prepare change-tag save |
− | if (result.summary && result.summary.length > 0) | + | if ( action && action.value === 'wpSave' ) { |
− | commitForm.wpSummary.value = HotCat.messages.prefix + result.summary.join (HotCat.messages.separator) + HotCat.messages.using;
| + | if ( HC.changeTag ) { |
− | commitForm.wpMinoredit.checked = HotCat.single_minor || minorEdits; | + | commitForm.wpChangeTags.value = HC.changeTag; |
− | } else if (changes > 1) { | + | 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 summary = []; |
| var shortSummary = []; | | var shortSummary = []; |
| // Deleted | | // Deleted |
− | for (i = 0; i < deleted.length; i++) { | + | for ( i = 0; i < deleted.length; i++ ) summary.push( '−' + substitute( HC.messages.short_catchange, [ null, deleted[ i ] ] ) ); |
− | summary.push ('-' + substitute (HotCat.messages.short_catchange, [null, deleted[i]]));
| + | |
− | }
| + | if ( deleted.length === 1 ) shortSummary.push( '−' + substitute( HC.messages.short_catchange, [ null, deleted[ 0 ] ] ) ); else if ( deleted.length ) shortSummary.push( '− ' + multiChangeMsg( deleted.length ) ); |
− | if (deleted.length == 1) | + | |
− | shortSummary.push ('-' + substitute (HotCat.messages.short_catchange, [null, deleted[0]]));
| |
− | else if (deleted.length > 1)
| |
− | shortSummary.push ('- ' + multiChangeMsg (deleted.length));
| |
| // Added | | // Added |
− | for (i = 0; i < added.length; i++) { | + | for ( i = 0; i < added.length; i++ ) summary.push( '+' + substitute( HC.messages.short_catchange, [ null, added[ i ] ] ) ); |
− | summary.push ('+' + substitute (HotCat.messages.short_catchange, [null, added[i]]));
| + | |
− | }
| + | if ( added.length === 1 ) shortSummary.push( '+' + substitute( HC.messages.short_catchange, [ null, added[ 0 ] ] ) ); else if ( added.length ) shortSummary.push( '+ ' + multiChangeMsg( added.length ) ); |
− | if (added.length == 1) | + | |
− | shortSummary.push ('+' + substitute (HotCat.messages.short_catchange, [null, added[0]]));
| |
− | else if (added.length > 1)
| |
− | shortSummary.push ('+ ' + multiChangeMsg (added.length));
| |
| // Changed | | // Changed |
| var arrow = is_rtl ? '\u2190' : '\u2192'; // left and right arrows. Don't use ← and → in the code. | | var arrow = is_rtl ? '\u2190' : '\u2192'; // left and right arrows. Don't use ← and → in the code. |
− | for (i = 0; i < changed.length; i++) { | + | for ( i = 0; i < changed.length; i++ ) { |
− | if (changed[i].from != changed[i].to) { | + | if ( changed[ i ].from !== changed[ i ].to ) { |
− | summary.push ('±' + substitute (HotCat.messages.short_catchange, [null, changed[i].from]) + arrow | + | summary.push( |
− | + substitute (HotCat.messages.short_catchange, [null, changed[i].to]));
| + | '±' + substitute( HC.messages.short_catchange, [ null, changed[ i ].from ] ) + arrow + |
| + | substitute( HC.messages.short_catchange, [ null, changed[ i ].to ] ) |
| + | ); |
| } else { | | } else { |
− | summary.push ('±' + substitute (HotCat.messages.short_catchange, [null, changed[i].from])); | + | summary.push( '±' + substitute( HC.messages.short_catchange, [ null, changed[ i ].from ] ) ); |
| } | | } |
| } | | } |
− | if (changed.length == 1) { | + | if ( changed.length === 1 ) { |
− | if (changed[0].from != changed[0].to) { | + | if ( changed[ 0 ].from !== changed[ 0 ].to ) { |
− | shortSummary.push ('±' + substitute (HotCat.messages.short_catchange, [null, changed[0].from]) + arrow | + | shortSummary.push( |
− | + substitute (HotCat.messages.short_catchange, [null, changed[0].to]));
| + | '±' + substitute( HC.messages.short_catchange, [ null, changed[ 0 ].from ] ) + arrow + |
| + | substitute( HC.messages.short_catchange, [ null, changed[ 0 ].to ] ) |
| + | ); |
| } else { | | } else { |
− | shortSummary.push ('±' + substitute (HotCat.messages.short_catchange, [null, changed[0].from])); | + | shortSummary.push( '±' + substitute( HC.messages.short_catchange, [ null, changed[ 0 ].from ] ) ); |
| } | | } |
− | } else if (changed.length > 1) { | + | } else if ( changed.length ) { |
− | shortSummary.push ('± ' + multiChangeMsg (changed.length)); | + | shortSummary.push( '± ' + multiChangeMsg( changed.length ) ); |
| } | | } |
− | if (summary.length > 0) { | + | if ( summary.length ) { |
− | summary = summary.join (HotCat.messages.separator); | + | summary = summary.join( HC.messages.separator ); |
− | if (summary.length > 200 - HotCat.messages.prefix.length - HotCat.messages.using.length) { | + | if ( summary.length > 200 - HC.messages.prefix.length - HC.messages.using.length ) summary = shortSummary.join( HC.messages.separator ); |
− | summary = shortSummary.join (HotCat.messages.separator);
| + | |
− | }
| + | commitForm.wpSummary.value = HC.messages.prefix + summary + HC.messages.using; |
− | commitForm.wpSummary.value = HotCat.messages.prefix + summary + HotCat.messages.using; | |
| } | | } |
| } | | } |
| } | | } |
| + | |
| commitForm.wpTextbox1.value = result.text; | | commitForm.wpTextbox1.value = result.text; |
− | commitForm.wpStarttime.value = serverTime || currentTimestamp (); | + | commitForm.wpStarttime.value = serverTime || currentTimestamp(); |
| commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value; | | commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value; |
− | if (selfEditConflict) commitForm.oldid.value = "" + (pageTextRevId || conf.wgCurRevisionId); | + | if ( selfEditConflict ) commitForm.oldid.value = String( pageTextRevId || conf.wgCurRevisionId ); |
| + | |
| // Submit the form in a way that triggers onsubmit events: commitForm.submit() doesn't. | | // Submit the form in a way that triggers onsubmit events: commitForm.submit() doesn't. |
| commitForm.hcCommit.click(); | | commitForm.hcCommit.click(); |
| } | | } |
| | | |
− | function resolveMulti (toResolve, callback) {
| + | function resolveOne( page, toResolve ) { |
− | var i;
| + | var cats = page.categories, |
− | for (i = 0; i < toResolve.length; i++) {
| + | lks = page.links, |
− | toResolve[i].dab = null;
| + | is_dab = false, |
− | toResolve[i].dabInput = toResolve[i].lastInput;
| + | is_redir = typeof page.redirect === 'string', // Hard redirect? |
− | }
| + | is_hidden = page.categoryinfo && typeof page.categoryinfo.hidden === 'string', |
− | if (noSuggestions) {
| + | is_missing = typeof page.missing === 'string', |
− | callback (toResolve);
| + | i; |
− | return;
| + | for ( i = 0; i < toResolve.length; i++ ) { |
− | }
| + | if ( i && toResolve[ i ].dabInputCleaned !== page.title.substring( page.title.indexOf( ':' ) + 1 ) ) continue; |
− | // 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 > 1 && toResolve[i].dabInputCleaned != page.title.substring (page.title.indexOf (':') + 1)) continue; | |
| // Note: the server returns in page an NFC normalized Unicode title. If our input was not NFC normalized, we may not find | | // Note: the server returns in page an NFC normalized Unicode title. If our input was not NFC normalized, we may not find |
| // any entry here. If we have only one editor to resolve (the most common case, I presume), we may simply skip the check. | | // any entry here. If we have only one editor to resolve (the most common case, I presume), we may simply skip the check. |
− | toResolve[i].currentHidden = is_hidden; | + | toResolve[ i ].currentHidden = is_hidden; |
− | toResolve[i].inputExists = !is_missing; | + | toResolve[ i ].inputExists = !is_missing; |
− | toResolve[i].icon.src = armorUri(is_missing ? HotCat.existsNo : HotCat.existsYes); | + | toResolve[ i ].icon.src = ( is_missing ? HC.existsNo : HC.existsYes ); |
| } | | } |
− | if (is_missing) return; | + | if ( is_missing ) return; |
− | if (!is_redir && cats && (HotCat.disambig_category || HotCat.redir_category)) { | + | if ( !is_redir && cats && ( HC.disambig_category || HC.redir_category ) ) { |
− | for (var c = 0; c < cats.length; c++) { | + | for ( var c = 0; c < cats.length; c++ ) { |
− | var cat = cats[c]['title']; | + | var cat = cats[ c ].title; |
| // Strip namespace prefix | | // Strip namespace prefix |
− | if (cat) { | + | if ( cat ) { |
− | cat = cat.substring (cat.indexOf (':') + 1).replace(/_/g, ' '); | + | cat = cat.substring( cat.indexOf( ':' ) + 1 ).replace( /_/g, ' ' ); |
− | if (cat == HotCat.disambig_category) { | + | if ( cat === HC.disambig_category ) { |
− | is_dab = true; break; | + | is_dab = true; |
− | } else if (cat == HotCat.redir_category) { | + | break; |
− | is_redir = true; break; | + | } else if ( cat === HC.redir_category ) { |
| + | is_redir = true; |
| + | break; |
| } | | } |
| } | | } |
| } | | } |
| } | | } |
− | if (!is_redir && !is_dab) return; | + | if ( !is_redir && !is_dab ) return; |
− | if (!lks || lks.length === 0) return; | + | if ( !lks || !lks.length ) return; |
| var titles = []; | | var titles = []; |
− | for (i = 0; i < lks.length; i++) { | + | for ( i = 0; i < lks.length; i++ ) { |
− | if ( lks[i]['ns'] == 14 // Category namespace -- always true since we ask only for the category links | + | if ( |
− | && lks[i]['title'] && lks[i]['title'].length > 0) // Name not empty | + | // Category namespace -- always true since we ask only for the category links |
− | { | + | 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. | | // Internal link to existing thingy. Extract the page name and remove the namespace. |
− | var match = lks[i]['title']; | + | var match = lks[ i ].title; |
− | match = match.substring (match.indexOf (':') + 1); | + | match = match.substring( match.indexOf( ':' ) + 1 ); |
| // Exclude blacklisted categories. | | // Exclude blacklisted categories. |
− | if (!HotCat.blacklist || !HotCat.blacklist.test (match)) { | + | if ( !HC.blacklist || !HC.blacklist.test( match ) ) titles.push( match ); |
− | titles.push (match);
| + | } |
− | } | + | } |
| + | if ( !titles.length ) return; |
| + | for ( i = 0; i < toResolve.length; i++ ) { |
| + | if ( i && 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 = HC.existsYes; |
| + | if ( titles.length > 1 ) { |
| + | toResolve[ i ].dab = titles; |
| + | } else { |
| + | toResolve[ i ].text.value = |
| + | titles[ 0 ] + ( toResolve[ i ].currentKey !== null ? '|' + toResolve[ i ].currentKey : '' ); |
| } | | } |
| } | | } |
− | if (titles.length === 0) { | + | } |
| + | |
| + | 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 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; | | return; |
| } | | } |
− | for (i = 0; i < toResolve.length; i++) { | + | // Use %7C instead of |, otherwise Konqueror insists on re-encoding the arguments, resulting in doubly encoded |
− | if (toResolve.length > 1 && toResolve[i].dabInputCleaned != page.title.substring (page.title.indexOf (':') + 1)) continue; | + | // category names. (That is a bug in Konqueror. Other browsers don't have this problem.) |
− | toResolve[i].inputExists = true; // Might actually be wrong if it's a redirect pointing to a non-existing category | + | var args = 'action=query&prop=info%7Clinks%7Ccategories%7Ccategoryinfo&plnamespace=14' + |
− | toResolve[i].icon.src = armorUri(HotCat.existsYes);
| + | '&pllimit=' + ( toResolve.length * 10 ) + |
− | if (titles.length > 1) {
| + | '&cllimit=' + ( toResolve.length * 10 ) + |
− | toResolve[i].dab = titles;
| + | '&format=json&titles='; |
− | } else { | + | for ( i = 0; i < toResolve.length; i++ ) { |
− | toResolve[i].text.value = | + | var v = toResolve[ i ].dabInput; |
− | titles[0] + (toResolve[i].currentKey !== null ? '|' + toResolve[i].currentKey : ""); | + | 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 resolveRedirects (toResolve, params) { | + | function showDab( which ) { |
− | if (!params || !params.query || !params.query.pages) return; | + | if ( !which.is_active ) { |
− | for (var p in params.query.pages) resolveOne (params.query.pages[p], toResolve); | + | makeActive( which ); |
| + | } else { |
| + | which.showSuggestions( which.dab, false, null, null ); // do autocompletion, no key, no engine selector |
| + | which.dab = null; |
| + | } |
| } | | } |
| | | |
− | function multiSubmit () { | + | function multiSubmit() { |
| var toResolve = []; | | var toResolve = []; |
− | for (var i = 0; i < editors.length; i++) { | + | for ( var i = 0; i < editors.length; i++ ) |
− | if (editors[i].state == CategoryEditor.CHANGE_PENDING || editors[i].state == CategoryEditor.OPEN) | + | if ( editors[ i ].state === CategoryEditor.CHANGE_PENDING || editors[ i ].state === CategoryEditor.OPEN ) toResolve.push( editors[ i ] ); |
− | toResolve.push (editors[i]);
| + | |
− | }
| + | if ( !toResolve.length ) { |
− | if (toResolve.length === 0) { | + | initiateEdit( function ( failure ) { |
− | initiateEdit (function (failure) {performChanges (failure);}, function (msg) {alert (msg);}); | + | performChanges( failure ); |
| + | }, function ( msg ) { |
| + | alert( msg ); |
| + | } ); |
| return; | | return; |
| } | | } |
− | resolveMulti ( | + | resolveMulti( toResolve, function ( resolved ) { |
− | toResolve
| + | var firstDab = null; |
− | , function (resolved) {
| + | var dontChange = false; |
− | var firstDab = null;
| + | for ( var i = 0; i < resolved.length; i++ ) { |
− | var dontChange = false;
| + | if ( resolved[ i ].lastInput !== resolved[ i ].dabInput ) { |
− | for (var i = 0; i < resolved.length; i++) {
| + | // We didn't disable all the open editors, but we did asynchronous calls. It is |
− | if (resolved[i].lastInput != resolved[i].dabInput) {
| + | // theoretically possible that the user changed something... |
− | // We didn't disable all the open editors, but we did asynchronous calls. It is
| + | dontChange = true; |
− | // theoretically possible that the user changed something...
| + | } else { |
− | dontChange = true;
| + | if ( resolved[ i ].dab ) { |
− | } else {
| + | if ( !firstDab ) firstDab = resolved[ i ]; |
− | if (resolved[i].dab) {
| + | } else { |
− | if (!firstDab) firstDab = resolved[i];
| + | if ( resolved[ i ].acceptCheck( true ) ) resolved[ i ].commit(); |
− | } else {
| |
− | if (resolved[i].acceptCheck(true)) resolved[i].commit();
| |
− | }
| |
− | }
| |
− | }
| |
− | if (firstDab) {
| |
− | showDab (firstDab);
| |
− | } else if (!dontChange) {
| |
− | initiateEdit (function (failure) {performChanges (failure);}, function (msg) {alert (msg);});
| |
| } | | } |
| } | | } |
− | ); | + | } |
| + | if ( firstDab ) { |
| + | 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 cat_prefix = null;
| |
− | var noSuggestions = false;
| |
| var suggestionEngines = { | | var suggestionEngines = { |
− | opensearch : | + | opensearch: { |
− | { uri : '/api.php?format=json&action=opensearch&namespace=14&limit=30&search=Category:$1' // $1 = search term | + | uri: '/api.php?format=json&action=opensearch&namespace=14&limit=30&search=Category:$1', // $1 = search term |
− | ,handler : // Function to convert result of uri into an array of category names
| + | // Function to convert result of uri into an array of category names |
− | function (queryResult, queryKey) {
| + | handler: function ( queryResult, queryKey ) { |
− | if (queryResult && queryResult.length >= 2) {
| + | if ( queryResult && queryResult.length >= 2 ) { |
− | var key = queryResult[0].substring(queryResult[0].indexOf(':') + 1);
| + | var key = queryResult[ 0 ].substring( queryResult[ 0 ].indexOf( ':' ) + 1 ); |
− | var titles = queryResult[1];
| + | var titles = queryResult[ 1 ]; |
− | var exists = false;
| + | var exists = false; |
− | if (!cat_prefix) cat_prefix = new RegExp ('^(' + HotCat.category_regexp + ':)');
| + | if ( !cat_prefix ) cat_prefix = new RegExp( '^(' + HC.category_regexp + '):' ); |
− | for (var i = 0; i < titles.length; i++) {
| + | |
− | cat_prefix.lastIndex = 0;
| + | for ( var i = 0; i < titles.length; i++ ) { |
− | var m = cat_prefix.exec (titles[i]);
| + | cat_prefix.lastIndex = 0; |
− | if (m && m.length > 1) {
| + | var m = cat_prefix.exec( titles[ i ] ); |
− | titles[i] = titles[i].substring (titles[i].indexOf (':') + 1); // rm namespace
| + | if ( m && m.length > 1 ) { |
− | if (key == titles[i]) exists = true;
| + | titles[ i ] = titles[ i ].substring( titles[ i ].indexOf( ':' ) + 1 ); // rm namespace |
− | } else {
| + | if ( key === titles[ i ] ) exists = true; |
− | titles.splice (i, 1); // Nope, it's not a category after all.
| + | } else { |
− | i--;
| + | titles.splice( i, 1 ); // Nope, it's not a category after all. |
− | }
| + | i--; |
| } | | } |
− | titles.exists = exists;
| |
− | if (queryKey != key) titles.normalized = key; // Remember the NFC normalized key we got back from the server
| |
− | return titles;
| |
| } | | } |
− | return null; | + | titles.exists = exists; |
| + | if ( queryKey !== key ) titles.normalized = key; |
| + | // Remember the NFC normalized key we got back from the server |
| + | return titles; |
| } | | } |
| + | return null; |
| } | | } |
− | ,internalsearch : | + | }, |
− | { uri : '/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1&apprefix=$1' | + | internalsearch: { |
− | ,handler :
| + | uri: '/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1&apprefix=$1', |
− | function (queryResult, queryKey) {
| + | handler: function ( queryResult ) { |
− | if (queryResult && queryResult.query && queryResult.query.allpages) {
| + | if ( queryResult && queryResult.query && queryResult.query.allpages ) { |
− | var titles = queryResult.query.allpages;
| + | var titles = queryResult.query.allpages; |
− | for (var i = 0; i < titles.length; i++) {
| + | 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; |
− | return titles;
| |
− | }
| |
− | return null;
| |
| } | | } |
| + | return null; |
| } | | } |
− | ,exists : | + | }, |
− | { uri : '/api.php?format=json&action=query&prop=info&titles=Category:$1' | + | exists: { |
− | ,handler :
| + | uri: '/api.php?format=json&action=query&prop=info&titles=Category:$1', |
− | function (queryResult, queryKey) {
| + | handler: function ( queryResult, queryKey ) { |
− | if (queryResult && queryResult.query && queryResult.query.pages && !queryResult.query.pages[-1]) {
| + | if ( queryResult && queryResult.query && queryResult.query.pages && !queryResult.query.pages[ -1 ] ) { |
− | // Should have exactly 1
| + | // Should have exactly 1 |
− | for (var p in queryResult.query.pages) {
| + | for ( var p in queryResult.query.pages ) { |
− | var title = queryResult.query.pages[p].title;
| + | var title = queryResult.query.pages[ p ].title; |
− | title = title.substring (title.indexOf (':') + 1);
| + | title = title.substring( title.indexOf( ':' ) + 1 ); |
− | var titles = [title];
| + | var titles = [ title ]; |
− | titles.exists = true;
| + | titles.exists = true; |
− | if (queryKey != title) titles.normalized = title; // NFC
| + | if ( queryKey !== title ) titles.normalized = title; |
− | return titles;
| + | // NFC |
− | }
| + | return titles; |
| } | | } |
− | return null;
| |
| } | | } |
| + | return null; |
| } | | } |
− | ,subcategories : | + | }, |
− | // I don't understand why they didn't map cmnamespace=14 automatically to cmtype=subcat, | + | subcategories: { |
− | // which gives better results and is faster.
| + | uri: '/api.php?format=json&action=query&list=categorymembers&cmtype=subcat&cmlimit=max&cmtitle=Category:$1', |
− | { uri : '/api.php?format=json&action=query&list=categorymembers'
| + | handler: function ( queryResult ) { |
− | +(function (version) {
| + | if ( queryResult && queryResult.query && queryResult.query.categorymembers ) { |
− | var m = version.match(/^(\d+)\.(\d+)/);
| + | var titles = queryResult.query.categorymembers; |
− | var major = 0, minor = 0;
| + | for ( var i = 0; i < titles.length; i++ ) titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace |
− | if (m && m.length > 1) {
| + | |
− | major = parseInt (m[1], 10);
| + | return titles; |
− | minor = (m.length > 2 ? parseInt (m[2], 10) : 0);
| |
− | }
| |
− | if (major > 1 || major === 1 && minor > 17) return '&cmtype=subcat'; // Since MW1.18
| |
− | return '&cmnamespace=14';
| |
− | }
| |
− | )(conf.wgVersion)
| |
− | +'&cmlimit=max&cmtitle=Category:$1'
| |
− | ,handler :
| |
− | function (queryResult, queryKey) {
| |
− | if (queryResult && queryResult.query && queryResult.query.categorymembers) {
| |
− | var titles = queryResult.query.categorymembers;
| |
− | for (var i = 0; i < titles.length; i++) {
| |
− | titles[i] = titles[i].title.substring (titles[i].title.indexOf (':') + 1); // rm namespace
| |
− | }
| |
− | return titles;
| |
− | }
| |
− | return null;
| |
| } | | } |
| + | return null; |
| } | | } |
− | ,parentcategories : | + | }, |
− | { uri : '/api.php?format=json&action=query&prop=categories&titles=Category:$1&cllimit=max' | + | parentcategories: { |
− | ,handler :
| + | uri: '/api.php?format=json&action=query&prop=categories&titles=Category:$1&cllimit=max', |
− | function (queryResult, queryKey) {
| + | handler: function ( queryResult ) { |
− | if (queryResult && queryResult.query && queryResult.query.pages) {
| + | if ( queryResult && queryResult.query && queryResult.query.pages ) { |
− | for (var p in queryResult.query.pages) {
| + | for ( var p in queryResult.query.pages ) { |
− | if (queryResult.query.pages[p].categories) {
| + | if ( queryResult.query.pages[ p ].categories ) { |
− | var titles = queryResult.query.pages[p].categories;
| + | var titles = queryResult.query.pages[ p ].categories; |
− | for (var i = 0; i < titles.length; i++) {
| + | 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; |
− | return titles;
| |
− | }
| |
| } | | } |
| } | | } |
− | return null;
| |
| } | | } |
| + | return null; |
| } | | } |
| + | } |
| }; | | }; |
| | | |
| var suggestionConfigs = { | | var suggestionConfigs = { |
− | searchindex : {name: 'Search index', engines: ['opensearch'], cache: {}, show: true, temp: false, noCompletion : false}
| + | searchindex: { |
− | ,pagelist : {name: 'Page list', engines: ['internalsearch', 'exists'], cache: {}, show: true, temp: false, noCompletion : false} | + | name: 'Search index', |
− | ,combined : {name: 'Combined search', engines: ['opensearch', 'internalsearch'], cache: {}, show: true, temp: false, noCompletion : false} | + | engines: [ 'opensearch' ], |
− | ,subcat : {name: 'Subcategories', engines: ['subcategories'], cache: {}, show: true, temp: true, noCompletion : true} | + | cache: {}, |
− | ,parentcat : {name: 'Parent categories', engines: ['parentcategories'], cache: {}, show: true, temp: true, noCompletion : true} | + | 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 |
| + | } |
| }; | | }; |
| | | |
− | function CategoryEditor () { this.initialize.apply (this, arguments); }
| + | CategoryEditor.UNCHANGED = 0; |
− | CategoryEditor.UNCHANGED = 0; | + | CategoryEditor.OPEN = 1; // Open, but no input yet |
− | CategoryEditor.OPEN = 1; // Open, but no input yet | |
| CategoryEditor.CHANGE_PENDING = 2; // Open, some input made | | CategoryEditor.CHANGE_PENDING = 2; // Open, some input made |
− | CategoryEditor.CHANGED = 3; | + | CategoryEditor.CHANGED = 3; |
− | CategoryEditor.DELETED = 4; | + | CategoryEditor.DELETED = 4; |
| | | |
| + | // Support: IE6 |
| // IE6 sometimes forgets to redraw the list when editors are opened or closed. | | // IE6 sometimes forgets to redraw the list when editors are opened or closed. |
| // Adding/removing a dummy element helps, at least when opening editors. | | // Adding/removing a dummy element helps, at least when opening editors. |
− | var dummyElement = make ('\xa0', true); | + | var dummyElement = make( '\xa0', true ); |
| | | |
− | function forceRedraw () { | + | function forceRedraw() { |
− | if (!is_ie6) return;
| + | if ( dummyElement.parentNode ) document.body.removeChild( dummyElement ); else document.body.appendChild( dummyElement ); |
− | if (dummyElement.parentNode) { | |
− | document.body.removeChild (dummyElement);
| |
− | } else {
| |
− | document.body.appendChild (dummyElement);
| |
− | }
| |
| } | | } |
| | | |
| // Event keyCodes that we handle in the text input field/suggestion list. | | // 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, IME = 229; | + | var BS = 8, |
− |
| + | TAB = 9, |
− | function makeActive (which) {
| + | RET = 13, |
− | if (which.is_active) return;
| + | ESC = 27, |
− | for (var i = 0; i < editors.length; i++) {
| + | SPACE = 32, |
− | if (editors[i] !== which) editors[i].inactivate ();
| + | PGUP = 33, |
− | }
| + | PGDOWN = 34, |
− | which.is_active = true;
| + | UP = 38, |
− | if (which.dab) {
| + | DOWN = 40, |
− | showDab (which);
| + | DEL = 46, |
− | } else {
| + | IME = 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 = { | | CategoryEditor.prototype = { |
| | | |
− | initialize : function (line, span, after, key, is_hidden) { | + | initialize: function ( line, span, after, key, is_hidden ) { |
| // If a span is given, 'after' is the category title, otherwise it may be an element after which to | | // If a span is given, 'after' is the category title, otherwise it may be an element after which to |
| // insert the new span. 'key' is likewise overloaded; if a span is given, it is the category key (if | | // insert the new span. 'key' is likewise overloaded; if a span is given, it is the category key (if |
| // known), otherwise it is a boolean indicating whether a bar shall be prepended. | | // known), otherwise it is a boolean indicating whether a bar shall be prepended. |
− | if (!span) { | + | if ( !span ) { |
| this.isAddCategory = true; | | this.isAddCategory = true; |
| // Create add span and append to catLinks | | // Create add span and append to catLinks |
− | this.originalCategory = ""; | + | this.originalCategory = ''; |
| this.originalKey = null; | | this.originalKey = null; |
− | this.originalExists = false; | + | this.originalExists = false; |
− | if (!newDOM) { | + | if ( !newDOM ) { |
− | span = make ('span'); | + | span = make( 'span' ); |
| span.className = 'noprint'; | | span.className = 'noprint'; |
− | if (key) { | + | if ( key ) { |
− | span.appendChild (make (' | ', true)); | + | span.appendChild( make( ' | ', true ) ); |
− | if (after) { | + | if ( after ) { |
− | after.parentNode.insertBefore (span, after.nextSibling); | + | after.parentNode.insertBefore( span, after.nextSibling ); |
| after = after.nextSibling; | | after = after.nextSibling; |
− | } else { | + | } else if (line) { |
− | line.appendChild (span); | + | line.appendChild( span ); |
| } | | } |
− | } else if (line.firstChild) { | + | } else if ( line && line.firstChild ) { |
− | span.appendChild (make (' ', true)); | + | span.appendChild( make( ' ', true ) ); |
− | line.appendChild (span); | + | line.appendChild( span ); |
| } | | } |
| } | | } |
− | this.linkSpan = make ('span'); | + | this.linkSpan = make( 'span' ); |
| this.linkSpan.className = 'noprint nopopups hotcatlink'; | | this.linkSpan.className = 'noprint nopopups hotcatlink'; |
− | var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this); | + | var lk = make( 'a' ); |
− | lk.appendChild (make (HotCat.links.add, true)); lk.title = HotCat.tooltips.add; | + | lk.href = '#catlinks'; |
− | this.linkSpan.appendChild (lk); | + | lk.onclick = this.open.bind( this ); |
− | span = make (newDOM ? 'li' : 'span'); | + | lk.appendChild( make( HC.links.add, true ) ); |
| + | lk.title = HC.tooltips.add; |
| + | this.linkSpan.appendChild( lk ); |
| + | span = make( newDOM ? 'li' : 'span' ); |
| span.className = 'noprint'; | | span.className = 'noprint'; |
− | if (is_rtl) span.dir = 'rtl'; | + | if ( is_rtl ) span.dir = 'rtl'; |
− | span.appendChild (this.linkSpan); | + | |
− | if (after) | + | span.appendChild( this.linkSpan ); |
− | after.parentNode.insertBefore (span, after.nextSibling); | + | if ( after ) { |
− | else | + | after.parentNode.insertBefore( span, after.nextSibling ); |
− | line.appendChild (span); | + | } else if ( line ) { |
| + | line.appendChild( span ); |
| + | } |
| + | |
| this.normalLinks = null; | | this.normalLinks = null; |
| this.undelLink = null; | | this.undelLink = null; |
| this.catLink = null; | | this.catLink = null; |
| } else { | | } else { |
− | if (is_rtl) span.dir = 'rtl'; | + | if ( is_rtl ) span.dir = 'rtl'; |
| + | |
| this.isAddCategory = false; | | this.isAddCategory = false; |
| this.catLink = span.firstChild; | | this.catLink = span.firstChild; |
| this.originalCategory = after; | | this.originalCategory = after; |
− | this.originalKey = (key && key.length > 1) ? key.substr(1) : null; // > 1 because it includes the leading bar | + | this.originalKey = ( key && key.length > 1 ) ? key.substr( 1 ) : null; // > 1 because it includes the leading bar |
− | this.originalExists = !hasClass (this.catLink, 'new'); | + | this.originalExists = !hasClass( this.catLink, 'new' ); |
| // Create change and del links | | // Create change and del links |
− | this.makeLinkSpan (); | + | this.makeLinkSpan(); |
− | if (!this.originalExists && this.upDownLinks) this.upDownLinks.style.display = 'none'; | + | if ( !this.originalExists && this.upDownLinks ) this.upDownLinks.style.display = 'none'; |
− | span.appendChild (this.linkSpan); | + | |
| + | span.appendChild( this.linkSpan ); |
| } | | } |
− | this.originalHidden = is_hidden; | + | this.originalHidden = is_hidden; |
− | this.line = line; | + | this.line = line; |
− | this.engine = HotCat.suggestions; | + | this.engine = HC.suggestions; |
− | this.span = span; | + | this.span = span; |
− | this.currentCategory = this.originalCategory; | + | this.currentCategory = this.originalCategory; |
− | this.currentExists = this.originalExists; | + | this.currentExists = this.originalExists; |
− | this.currentHidden = this.originalHidden; | + | this.currentHidden = this.originalHidden; |
− | this.currentKey = this.originalKey; | + | this.currentKey = this.originalKey; |
− | this.state = CategoryEditor.UNCHANGED; | + | this.state = CategoryEditor.UNCHANGED; |
− | this.lastSavedState = CategoryEditor.UNCHANGED; | + | this.lastSavedState = CategoryEditor.UNCHANGED; |
− | this.lastSavedCategory = this.originalCategory; | + | this.lastSavedCategory = this.originalCategory; |
− | this.lastSavedKey = this.originalKey; | + | this.lastSavedKey = this.originalKey; |
− | this.lastSavedExists = this.originalExists; | + | this.lastSavedExists = this.originalExists; |
− | this.lastSavedHidden = this.originalHidden; | + | this.lastSavedHidden = this.originalHidden; |
− | if (this.catLink && this.currentKey) { | + | if ( this.catLink && this.currentKey ) this.catLink.title = this.currentKey; |
− | this.catLink.title = this.currentKey;
| + | |
− | }
| + | editors[ editors.length ] = this; |
− | editors[editors.length] = this; | |
| }, | | }, |
| | | |
− | makeLinkSpan : function () { | + | makeLinkSpan: function () { |
− | this.normalLinks = make ('span'); | + | this.normalLinks = make( 'span' ); |
| var lk = null; | | var lk = null; |
− | if (this.originalCategory && this.originalCategory.length > 0) { | + | if ( this.originalCategory && this.originalCategory.length ) { |
− | lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.remove, this); | + | lk = make( 'a' ); |
− | lk.appendChild (make (HotCat.links.remove, true)); lk.title = HotCat.tooltips.remove; | + | lk.href = '#catlinks'; |
− | this.normalLinks.appendChild (make (' ', true)); | + | lk.onclick = this.remove.bind( this ); |
− | this.normalLinks.appendChild (lk); | + | lk.appendChild( make( HC.links.remove, true ) ); |
| + | lk.title = HC.tooltips.remove; |
| + | this.normalLinks.appendChild( make( ' ', true ) ); |
| + | this.normalLinks.appendChild( lk ); |
| } | | } |
− | if (!HotCat.template_categories[this.originalCategory]) { | + | if ( !HC.template_categories[ this.originalCategory ] ) { |
− | lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.open, this); | + | lk = make( 'a' ); |
− | lk.appendChild (make (HotCat.links.change, true)); lk.title = HotCat.tooltips.change; | + | lk.href = '#catlinks'; |
− | this.normalLinks.appendChild (make (' ', true)); | + | lk.onclick = this.open.bind( this ); |
− | this.normalLinks.appendChild (lk); | + | lk.appendChild( make( HC.links.change, true ) ); |
− | if (!noSuggestions && HotCat.use_up_down) { | + | lk.title = HC.tooltips.change; |
− | this.upDownLinks = make ('span'); | + | this.normalLinks.appendChild( make( ' ', true ) ); |
− | lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.down, this); | + | this.normalLinks.appendChild( lk ); |
− | lk.appendChild (make (HotCat.links.down, true)); lk.title = HotCat.tooltips.down; | + | if ( !noSuggestions && HC.use_up_down ) { |
− | this.upDownLinks.appendChild (make (' ', true)); | + | this.upDownLinks = make( 'span' ); |
− | this.upDownLinks.appendChild (lk); | + | lk = make( 'a' ); |
− | lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.up, this); | + | lk.href = '#catlinks'; |
− | lk.appendChild (make (HotCat.links.up, true)); lk.title = HotCat.tooltips.up; | + | lk.onclick = this.down.bind( this ); |
− | this.upDownLinks.appendChild (make (' ', true)); | + | lk.appendChild( make( HC.links.down, true ) ); |
− | this.upDownLinks.appendChild (lk); | + | lk.title = HC.tooltips.down; |
− | this.normalLinks.appendChild (this.upDownLinks); | + | this.upDownLinks.appendChild( make( ' ', true ) ); |
| + | this.upDownLinks.appendChild( lk ); |
| + | lk = make( 'a' ); |
| + | lk.href = '#catlinks'; |
| + | lk.onclick = this.up.bind( this ); |
| + | lk.appendChild( make( HC.links.up, true ) ); |
| + | lk.title = HC.tooltips.up; |
| + | this.upDownLinks.appendChild( make( ' ', true ) ); |
| + | this.upDownLinks.appendChild( lk ); |
| + | this.normalLinks.appendChild( this.upDownLinks ); |
| } | | } |
| } | | } |
− | this.linkSpan = make ('span'); | + | this.linkSpan = make( 'span' ); |
| this.linkSpan.className = 'noprint nopopups hotcatlink'; | | this.linkSpan.className = 'noprint nopopups hotcatlink'; |
− | this.linkSpan.appendChild (this.normalLinks); | + | this.linkSpan.appendChild( this.normalLinks ); |
− | this.undelLink = make ('span'); | + | this.undelLink = make( 'span' ); |
| this.undelLink.className = 'nopopups hotcatlink'; | | this.undelLink.className = 'nopopups hotcatlink'; |
| this.undelLink.style.display = 'none'; | | this.undelLink.style.display = 'none'; |
− | lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.restore, this); | + | lk = make( 'a' ); |
− | lk.appendChild (make (HotCat.links.restore, true)); lk.title = HotCat.tooltips.restore; | + | lk.href = '#catlinks'; |
− | this.undelLink.appendChild (make (' ', true)); | + | lk.onclick = this.restore.bind( this ); |
− | this.undelLink.appendChild (lk); | + | lk.appendChild( make( HC.links.restore, true ) ); |
− | this.linkSpan.appendChild (this.undelLink); | + | lk.title = HC.tooltips.restore; |
| + | this.undelLink.appendChild( make( ' ', true ) ); |
| + | this.undelLink.appendChild( lk ); |
| + | this.linkSpan.appendChild( this.undelLink ); |
| }, | | }, |
| | | |
− | invokeSuggestions : function (dont_autocomplete) { | + | invokeSuggestions: function ( dont_autocomplete ) { |
− | if (this.engine && suggestionConfigs[this.engine] && suggestionConfigs[this.engine].temp && !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; | | this.state = CategoryEditor.CHANGE_PENDING; |
| var self = this; | | var self = this; |
− | window.setTimeout (function () {self.textchange (dont_autocomplete);}, HotCat.suggest_delay); | + | window.setTimeout( function () { |
| + | self.textchange( dont_autocomplete ); |
| + | }, HC.suggest_delay ); |
| }, | | }, |
| | | |
− | makeForm : function () { | + | makeForm: function () { |
− | var form = make ('form'); | + | var form = make( 'form' ); |
− | form.method = 'POST'; form.onsubmit = bind (this.accept, this); | + | form.method = 'POST'; |
| + | form.onsubmit = this.accept.bind( this ); |
| this.form = form; | | this.form = form; |
| var self = this; | | var self = this; |
− | var text = make ('input'); text.type = 'text'; text.size = HotCat.editbox_width; | + | var text = make( 'input' ); |
− | if (!noSuggestions) { | + | 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: | | // Be careful here to handle IME input. This is browser/OS/IME dependent, but basically there are two mechanisms: |
| // - Modern (DOM Level 3) browsers use compositionstart/compositionend events to signal composition; if the | | // - Modern (DOM Level 3) browsers use compositionstart/compositionend events to signal composition; if the |
Line 1,453: |
Line 1,515: |
| // - Older browsers signal composition by keyDown === IME for the first and subsequent keys for a composition. The | | // - 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 | | // 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) | + | // detected by a keyDown IME with a keyUp of space, tab, escape, or return. |
− | text.onkeyup = | + | text.onkeyup = function ( evt ) { |
− | function (evt) {
| + | var key = evt.keyCode || 0; |
− | evt = evt || window.event || window.Event; // W3C, IE, Netscape
| + | if ( self.ime && self.lastKey === IME && !self.usesComposition && ( key === TAB || key === RET || key === ESC || key === SPACE ) ) self.ime = false; |
− | var key = evt.keyCode || 0;
| + | |
− | if (self.ime && self.lastKey === IME && !self.usesComposition && (key === TAB || key === RET || key == ESC || key === SPACE)) self.ime = false;
| + | if ( self.ime ) return true; |
− | if (self.ime) return true;
| + | |
− | if (key === UP || key === DOWN || key === PGUP || key === PGDOWN) {
| + | if ( key === UP || key === DOWN || key === PGUP || key === PGDOWN ) { |
− | // In case a browser doesn't generate keypress events for arrow keys...
| + | // In case a browser doesn't generate keypress events for arrow keys... |
− | if (self.keyCount === 0) return self.processKey (evt);
| + | if ( self.keyCount === 0 ) return self.processKey( evt ); |
− | } else {
| + | } else { |
− | if (key === ESC && self.lastKey !== IME) {
| + | if ( key === ESC && self.lastKey !== IME ) { |
− | if (!self.resetKeySelection ()) {
| + | if ( !self.resetKeySelection() ) { |
− | // No undo of key selection: treat ESC as "cancel".
| + | // No undo of key selection: treat ESC as "cancel". |
− | self.cancel ();
| + | self.cancel(); |
− | return;
| + | return; |
− | }
| |
| } | | } |
− | // Also do this for ESC as a workaround for Firefox bug 524360
| |
− | // https://bugzilla.mozilla.org/show_bug.cgi?id=524360
| |
− | self.invokeSuggestions (key === BS || key === DEL || key === ESC);
| |
| } | | } |
− | return true; | + | // Also do this for ESC as a workaround for Firefox bug 524360 |
− | };
| + | // https://bugzilla.mozilla.org/show_bug.cgi?id=524360 |
− | text.onkeydown = | + | self.invokeSuggestions( key === BS || key === DEL || key === ESC ); |
− | function (evt) {
| + | } |
− | evt = evt || window.event || window.Event; // W3C, IE, Netscape
| + | return true; |
− | var key = evt.keyCode || 0;
| + | }; |
− | self.lastKey = key;
| + | text.onkeydown = function ( evt ) { |
− | self.keyCount = 0;
| + | var key = evt.keyCode || 0; |
− | // DOM Level < 3 IME input
| + | self.lastKey = key; |
− | if (!self.ime && key === IME && !self.usesComposition) {
| + | self.keyCount = 0; |
− | // self.usesComposition catches browsers that may emit spurious keydown IME after a composition has ended
| + | // DOM Level < 3 IME input |
− | self.ime = true;
| + | if ( !self.ime && key === IME && !self.usesComposition ) { |
− | } else if (self.ime && key !== IME && !(key >= 16 && key <= 20 || key >= 91 && key <= 93 || key === 144)) {
| + | // self.usesComposition catches browsers that may emit spurious keydown IME after a composition has ended |
− | // Ignore control keys: ctrl, shift, alt, alt gr, caps lock, windows/apple cmd keys, num lock. Only the windows keys
| + | self.ime = true; |
− | // terminate IME (apple cmd doesn't), but they also cause a blur, so it's OK to ignore them here.
| + | } else if ( self.ime && key !== IME && !( key >= 16 && key <= 20 || key >= 91 && key <= 93 || key === 144 ) ) { |
− | // Note: Safari 4 (530.17) propagates ESC out of an IME composition (observed at least on Win XP).
| + | // Ignore control keys: ctrl, shift, alt, alt gr, caps lock, windows/apple cmd keys, num lock. Only the windows keys |
− | self.ime = false;
| + | // 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 (self.ime) return true;
| + | self.ime = false; |
− | // Handle return explicitly, to override the default form submission to be able to check for ctrl
| + | } |
− | if (key === RET) return self.accept (evt);
| + | if ( self.ime ) return true; |
− | // Inhibit default behavior of ESC (revert to last real input in FF: we do that ourselves)
| + | |
− | return (key === ESC) ? evtKill(evt) : true;
| + | // Handle return explicitly, to override the default form submission to be able to check for ctrl |
− | };
| + | if ( key === RET ) return self.accept( evt ); |
| + | |
| + | // Inhibit default behavior of ESC (revert to last real input in FF: we do that ourselves) |
| + | return ( key === ESC ) ? evtKill( evt ) : true; |
| + | }; |
| // And handle continued pressing of arrow keys | | // And handle continued pressing of arrow keys |
− | text.onkeypress = function (evt) {self.keyCount++; return self.processKey (evt);}; | + | text.onkeypress = function ( evt ) { |
− | addEvent (text, 'focus', function () { makeActive(self); }); | + | 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 | | // 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. | | // 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... | | // 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 | + | // Don't test for text.selectionStart being defined; |
− | // property while the element is not being displayed. | + | $( text ).on( |
− | addEvent (text
| + | ( text.onbeforedeactivate !== undefined && text.createTextRange ) ? 'beforedeactivate' : 'blur', |
− | , (typeof text.onbeforedeactivate != 'undefined' && text.createTextRange) ? 'beforedeactivate' : 'blur' | + | this.saveView.bind( this ) ); |
− | , bind (this.saveView, this) | |
− | );
| |
| // DOM Level 3 IME handling | | // DOM Level 3 IME handling |
| try { | | try { |
| // Setting lastKey = IME provides a fake keyDown for Gecko's single keyUp after a cmposition. If we didn't do this, | | // 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. | | // cancelling a composition via ESC would also cancel and close the whole category input editor. |
− | addEvent(text, 'compositionstart', function (evt) { self.lastKey = IME; self.usesComposition = true; self.ime = true; }); | + | $( text ).on( 'compositionstart', function () { |
− | addEvent(text, 'compositionend', function (evt) { self.lastKey = IME; self.usesComposition = true; self.ime = false; }); | + | self.lastKey = IME; |
− | addEvent(text, 'textInput', function (evt) { self.ime = false; self.invokeSuggestions(false); }); | + | self.usesComposition = true; |
− | } catch (any) { | + | 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 | | // Just in case some browsers might produce exceptions with these DOM Level 3 events |
| } | | } |
− | addEvent(text, 'blur', function (evt) { self.usesComposition = false; self.ime = false; }); | + | $( text ).on( 'blur', function () { |
| + | self.usesComposition = false; |
| + | self.ime = false; |
| + | } ); |
| } | | } |
| this.text = text; | | this.text = text; |
| | | |
− | this.icon = make ('img'); | + | this.icon = make( 'img' ); |
| | | |
| var list = null; | | var list = null; |
− | if (!noSuggestions) { | + | if ( !noSuggestions ) { |
− | list = make ('select'); | + | list = make( 'select' ); |
− | list.onclick = function (e) { if (self.highlightSuggestion(0)) self.textchange (false, true); }; | + | list.onclick = function () { |
− | list.ondblclick = function (e) { if (self.highlightSuggestion(0)) self.accept (e); }; | + | if ( self.highlightSuggestion( 0 ) ) self.textchange( false, true ); |
− | list.onchange = function (e) { self.highlightSuggestion(0); self.text.focus(); }; | + | }; |
− | list.onkeyup = | + | list.ondblclick = function ( e ) { |
− | function (evt) {
| + | if ( self.highlightSuggestion( 0 ) ) self.accept( e ); |
− | evt = evt || window.event || window.Event; // W3C, IE, Netscape
| + | }; |
− | if (evt.keyCode === ESC) {
| + | list.onchange = function () { |
− | self.resetKeySelection ();
| + | self.highlightSuggestion( 0 ); |
− | self.text.focus();
| + | self.text.focus(); |
− | window.setTimeout (function () {self.textchange (true);}, HotCat.suggest_delay);
| + | }; |
− | } else if (evt.keyCode === RET) {
| + | list.onkeyup = function ( evt ) { |
− | self.accept (evt);
| + | if ( evt.keyCode === ESC ) { |
− | }
| + | self.resetKeySelection(); |
− | };
| + | self.text.focus(); |
− | if (!HotCat.fixed_search) { | + | window.setTimeout( function () { |
− | var engineSelector = make ('select'); | + | self.textchange( true ); |
− | for (var key in suggestionConfigs) { | + | }, HC.suggest_delay ); |
− | if (suggestionConfigs[key].show) { | + | } else if ( evt.keyCode === RET ) { |
− | var opt = make ('option'); | + | self.accept( evt ); |
| + | } |
| + | }; |
| + | if ( !HC.fixed_search ) { |
| + | var engineSelector = make( 'select' ); |
| + | for ( var key in suggestionConfigs ) { |
| + | if ( suggestionConfigs[ key ].show ) { |
| + | var opt = make( 'option' ); |
| opt.value = key; | | opt.value = key; |
− | if (key == this.engine) opt.selected = true; | + | if ( key === this.engine ) opt.selected = true; |
− | opt.appendChild (make (suggestionConfigs[key].name, true)); | + | |
− | engineSelector.appendChild (opt); | + | opt.appendChild( make( suggestionConfigs[ key ].name, true ) ); |
| + | engineSelector.appendChild( opt ); |
| } | | } |
| } | | } |
− | engineSelector.onchange = | + | engineSelector.onchange = function () { |
− | function () {
| + | self.engine = self.engineSelector.options[ self.engineSelector.selectedIndex ].value; |
− | self.engine = self.engineSelector.options[self.engineSelector.selectedIndex].value;
| + | self.text.focus(); |
− | self.text.focus();
| + | self.textchange( true, true ); // Don't autocomplete, force re-display of list |
− | self.textchange (true, true); // Don't autocomplete, force re-display of list
| + | }; |
− | };
| |
| this.engineSelector = engineSelector; | | this.engineSelector = engineSelector; |
| } | | } |
Line 1,566: |
Line 1,652: |
| this.list = list; | | this.list = list; |
| | | |
− | function button_label (id, defaultText) { | + | function button_label( id, defaultText ) { |
| var label = null; | | var label = null; |
− | if ( onUpload | + | if ( |
− | && typeof UFUI != 'undefined' | + | onUpload && |
− | && typeof UIElements != 'undefined' | + | window.UFUI !== undefined && |
− | && typeof UFUI.getLabel == 'function') | + | window.UIElements !== undefined && |
− | { | + | UFUI.getLabel instanceof Function |
| + | ) { |
| try { | | try { |
− | label = UFUI.getLabel (id, true); | + | label = UFUI.getLabel( id, true ); |
| // Extract the plain text. IE doesn't know that Node.TEXT_NODE === 3 | | // Extract the plain text. IE doesn't know that Node.TEXT_NODE === 3 |
− | while (label && label.nodeType != 3) label = label.firstChild; | + | while ( label && label.nodeType !== 3 ) label = label.firstChild; |
− | } catch (ex) { | + | } catch ( ex ) { |
| label = null; | | label = null; |
| } | | } |
| } | | } |
− | if (!label || !label.data) return defaultText; | + | if ( !label || !label.data ) return defaultText; |
| + | |
| return label.data; | | return label.data; |
| } | | } |
| | | |
| // Do not use type 'submit'; we cannot detect modifier keys if we do | | // Do not use type 'submit'; we cannot detect modifier keys if we do |
− | var OK = make ('input'); OK.type = 'button'; | + | var OK = make( 'input' ); |
− | OK.value = button_label ('wpOkUploadLbl', HotCat.messages.ok); | + | OK.type = 'button'; |
− | OK.onclick = bind (this.accept, this); | + | OK.value = button_label( 'wpOkUploadLbl', HC.messages.ok ); |
| + | OK.onclick = this.accept.bind( this ); |
| this.ok = OK; | | this.ok = OK; |
| | | |
− | var cancel = make ('input'); cancel.type = 'button'; | + | var cancel = make( 'input' ); |
− | cancel.value = button_label ('wpCancelUploadLbl', HotCat.messages.cancel); | + | cancel.type = 'button'; |
− | cancel.onclick = bind (this.cancel, this); | + | cancel.value = button_label( 'wpCancelUploadLbl', HC.messages.cancel ); |
| + | cancel.onclick = this.cancel.bind( this ); |
| this.cancelButton = cancel; | | this.cancelButton = cancel; |
| | | |
− | var span = make ('span'); | + | var span = make( 'span' ); |
| span.className = 'hotcatinput'; | | span.className = 'hotcatinput'; |
| span.style.position = 'relative'; | | span.style.position = 'relative'; |
− | // FF3.6: add the input field first, then the two absolutely positioned elements. Otherwise, FF3.6 may leave the
| + | span.appendChild( text ); |
− | // suggestions and the selector at the right edge of the screen if display of the input field causes a re-layout
| |
− | // moving the form to the front of the next line.
| |
− | span.appendChild (text); | |
| | | |
− | // IE8/IE9: put some text into this span (a0 is nbsp) and make sure it always stays on the | + | // Support: IE8, IE9 |
− | // same line as the input field, otherwise, IE8/9 miscalculates the height of the span and | + | // 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. | | // then the engine selector may overlap the input field. |
− | span.appendChild (make ('\xa0', true)); | + | span.appendChild( make( '\xa0', true ) ); |
| span.style.whiteSpace = 'nowrap'; | | span.style.whiteSpace = 'nowrap'; |
| | | |
− | if (list) span.appendChild (list); | + | if ( list ) span.appendChild( list ); |
− | if (this.engineSelector) span.appendChild (this.engineSelector); | + | |
− | if (!noSuggestions) span.appendChild (this.icon); | + | if ( this.engineSelector ) span.appendChild( this.engineSelector ); |
− | span.appendChild (OK); | + | |
− | span.appendChild (cancel); | + | if ( !noSuggestions ) span.appendChild( this.icon ); |
− | form.appendChild(span); | + | |
| + | span.appendChild( OK ); |
| + | span.appendChild( cancel ); |
| + | form.appendChild( span ); |
| form.style.display = 'none'; | | form.style.display = 'none'; |
− | this.span.appendChild (form); | + | this.span.appendChild( form ); |
| }, | | }, |
| | | |
− | display : function (evt) { | + | display: function ( evt ) { |
− | if (this.isAddCategory && !onUpload) { | + | if ( this.isAddCategory && !onUpload && this.line ) { |
− | var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one | + | // eslint-disable-next-line no-new |
| + | new CategoryEditor( this.line, null, this.span, true ); // Create a new one |
| } | | } |
− | if (!commitButton && !onUpload) { | + | if ( !commitButton && !onUpload ) { |
− | for (var i = 0; i < editors.length; i++) { | + | for ( var i = 0; i < editors.length; i++ ) { |
− | if (editors[i].state != CategoryEditor.UNCHANGED) { | + | if ( editors[ i ].state !== CategoryEditor.UNCHANGED ) { |
| setMultiInput(); | | setMultiInput(); |
| break; | | break; |
Line 1,632: |
Line 1,724: |
| } | | } |
| } | | } |
− | if (!this.form) { | + | 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'; | + | if ( this.engineSelector ) this.engineSelector.style.display = 'none'; |
| + | |
| this.currentCategory = this.lastSavedCategory; | | this.currentCategory = this.lastSavedCategory; |
− | this.currentExists = this.lastSavedExists; | + | this.currentExists = this.lastSavedExists; |
− | this.currentHidden = this.lastSavedHidden; | + | this.currentHidden = this.lastSavedHidden; |
− | this.currentKey = this.lastSavedKey; | + | this.currentKey = this.lastSavedKey; |
− | this.icon.src = armorUri(this.currentExists ? HotCat.existsYes : HotCat.existsNo); | + | this.icon.src = ( this.currentExists ? HC.existsYes : HC.existsNo ); |
− | this.text.value = this.currentCategory + (this.currentKey !== null ? '|' + this.currentKey : ""); | + | this.text.value = this.currentCategory + ( this.currentKey !== null ? '|' + this.currentKey : '' ); |
| this.originalState = this.state; | | this.originalState = this.state; |
− | this.lastInput = this.currentCategory; | + | this.lastInput = this.currentCategory; |
− | this.inputExists = this.currentExists; | + | this.inputExists = this.currentExists; |
− | this.state = this.state == CategoryEditor.UNCHANGED ? CategoryEditor.OPEN : CategoryEditor.CHANGE_PENDING; | + | this.state = this.state === CategoryEditor.UNCHANGED ? CategoryEditor.OPEN : CategoryEditor.CHANGE_PENDING; |
− | this.lastSelection = {start: this.currentCategory.length, end: this.currentCategory.length}; | + | this.lastSelection = { |
| + | start: this.currentCategory.length, |
| + | end: this.currentCategory.length |
| + | }; |
| this.showsList = false; | | this.showsList = false; |
| // Display the form | | // Display the form |
− | if (this.catLink) this.catLink.style.display = 'none'; | + | if ( this.catLink ) this.catLink.style.display = 'none'; |
| + | |
| this.linkSpan.style.display = 'none'; | | this.linkSpan.style.display = 'none'; |
| this.form.style.display = 'inline'; | | this.form.style.display = 'inline'; |
| this.ok.disabled = false; | | this.ok.disabled = false; |
| // Kill the event before focussing, otherwise IE will kill the onfocus event! | | // Kill the event before focussing, otherwise IE will kill the onfocus event! |
− | var result = evtKill (evt); | + | var result = evtKill( evt ); |
| this.text.focus(); | | this.text.focus(); |
| this.text.readOnly = false; | | this.text.readOnly = false; |
− | checkMultiInput (); | + | checkMultiInput(); |
| return result; | | return result; |
| }, | | }, |
| | | |
− | show : function (evt, engine, readOnly) { | + | show: function ( evt, engine, readOnly ) { |
− | var result = this.display (evt); | + | var result = this.display( evt ); |
| var v = this.lastSavedCategory; | | var v = this.lastSavedCategory; |
− | if (v.length === 0) return result; | + | if ( !v.length ) return result; |
| + | |
| this.text.readOnly = !!readOnly; | | this.text.readOnly = !!readOnly; |
| this.engine = engine; | | this.engine = engine; |
− | this.textchange (false, true); // do autocompletion, force display of suggestions | + | this.textchange( false, true ); // do autocompletion, force display of suggestions |
− | forceRedraw (); | + | forceRedraw(); |
| return result; | | return result; |
| }, | | }, |
| | | |
− | open : function (evt) { | + | open: function ( evt ) { |
− | return this.show (evt, (this.engine && suggestionConfigs[this.engine].temp) ? HotCat.suggestions : this.engine); | + | return this.show( evt, ( this.engine && suggestionConfigs[ this.engine ].temp ) ? HC.suggestions : this.engine ); |
| }, | | }, |
| | | |
− | down : function (evt) { | + | down: function ( evt ) { |
− | return this.show (evt, 'subcat', true); | + | return this.show( evt, 'subcat', true ); |
| }, | | }, |
| | | |
− | up : function (evt) { | + | up: function ( evt ) { |
− | return this.show (evt, 'parentcat'); | + | return this.show( evt, 'parentcat' ); |
| }, | | }, |
| | | |
− | cancel : function () { | + | cancel: function () { |
− | if (this.isAddCategory && !onUpload) { | + | if ( this.isAddCategory && !onUpload ) { |
| this.removeEditor(); // We added a new adder when opening | | this.removeEditor(); // We added a new adder when opening |
| return; | | return; |
Line 1,693: |
Line 1,791: |
| this.inactivate(); | | this.inactivate(); |
| this.form.style.display = 'none'; | | this.form.style.display = 'none'; |
− | if (this.catLink) this.catLink.style.display = ""; | + | if ( this.catLink ) this.catLink.style.display = ''; |
− | this.linkSpan.style.display = ""; | + | |
| + | this.linkSpan.style.display = ''; |
| this.state = this.originalState; | | this.state = this.originalState; |
| this.currentCategory = this.lastSavedCategory; | | this.currentCategory = this.lastSavedCategory; |
− | this.currentKey = this.lastSavedKey; | + | this.currentKey = this.lastSavedKey; |
− | this.currentExists = this.lastSavedExists; | + | this.currentExists = this.lastSavedExists; |
− | this.currentHidden = this.lastSavedHidden; | + | this.currentHidden = this.lastSavedHidden; |
− | if (this.catLink) { | + | if ( this.catLink ) |
− | if (this.currentKey && this.currentKey.length > 0) { | + | if ( this.currentKey && this.currentKey.length ) { this.catLink.title = this.currentKey; } else { this.catLink.title = ''; } |
− | this.catLink.title = this.currentKey;
| + | |
− | } else {
| + | if ( this.state === CategoryEditor.UNCHANGED ) { |
− | this.catLink.title = "";
| + | if ( this.catLink ) this.catLink.style.backgroundColor = 'transparent'; |
− | }
| |
− | }
| |
− | if (this.state == CategoryEditor.UNCHANGED) { | |
− | if (this.catLink) this.catLink.style.backgroundColor = 'transparent'; | |
| } else { | | } else { |
− | if (!onUpload) { | + | if ( !onUpload ) { |
| try { | | try { |
− | this.catLink.style.backgroundColor = HotCat.bg_changed; | + | this.catLink.style.backgroundColor = HC.bg_changed; |
− | } catch (ex) {} | + | } catch ( ex ) {} |
| } | | } |
| } | | } |
− | checkMultiInput (); | + | checkMultiInput(); |
− | forceRedraw (); | + | forceRedraw(); |
| }, | | }, |
| | | |
− | removeEditor : function () { | + | removeEditor: function () { |
− | if (!newDOM) { | + | if ( !newDOM ) { |
| var next = this.span.nextSibling; | | var next = this.span.nextSibling; |
− | if (next) next.parentNode.removeChild (next); | + | if ( next ) next.parentNode.removeChild( next ); |
| } | | } |
− | this.span.parentNode.removeChild (this.span); | + | this.span.parentNode.removeChild( this.span ); |
− | for (var i = 0; i < editors.length; i++) { | + | for ( var i = 0; i < editors.length; i++ ) { |
− | if (editors[i] == this) { | + | if ( editors[ i ] === this ) { |
− | editors.splice (i, 1); | + | editors.splice( i, 1 ); |
| break; | | break; |
| } | | } |
| } | | } |
− | checkMultiInput (); | + | checkMultiInput(); |
− | var self = this;
| |
− | window.setTimeout (function () {delete self;}, 10);
| |
| }, | | }, |
| | | |
− | rollback : function (evt) { | + | rollback: function ( evt ) { |
− | this.undoLink.parentNode.removeChild (this.undoLink); | + | this.undoLink.parentNode.removeChild( this.undoLink ); |
| this.undoLink = null; | | this.undoLink = null; |
| this.currentCategory = this.originalCategory; | | this.currentCategory = this.originalCategory; |
Line 1,749: |
Line 1,842: |
| this.lastSavedHidden = this.originalHidden; | | this.lastSavedHidden = this.originalHidden; |
| this.state = CategoryEditor.UNCHANGED; | | this.state = CategoryEditor.UNCHANGED; |
− | if (!this.currentCategory || this.currentCategory.length === 0) { | + | if ( !this.currentCategory || !this.currentCategory.length ) { |
| // It was a newly added category. Remove the whole editor. | | // It was a newly added category. Remove the whole editor. |
| this.removeEditor(); | | this.removeEditor(); |
| } else { | | } else { |
| // Redisplay the link... | | // Redisplay the link... |
− | this.catLink.removeChild (this.catLink.firstChild); | + | this.catLink.removeChild( this.catLink.firstChild ); |
− | this.catLink.appendChild (make (this.currentCategory, true)); | + | this.catLink.appendChild( make( this.currentCategory, true ) ); |
− | this.catLink.href = wikiPagePath (HotCat.category_canonical + ':' + this.currentCategory); | + | this.catLink.href = wikiPagePath( HC.category_canonical + ':' + this.currentCategory ); |
− | this.catLink.title = this.currentKey || ""; | + | this.catLink.title = this.currentKey || ''; |
− | this.catLink.className = this.currentExists ? "" : 'new'; | + | this.catLink.className = this.currentExists ? '' : 'new'; |
| this.catLink.style.backgroundColor = 'transparent'; | | this.catLink.style.backgroundColor = 'transparent'; |
− | if (this.upDownLinks) this.upDownLinks.style.display = this.currentExists ? "" : 'none'; | + | if ( this.upDownLinks ) this.upDownLinks.style.display = this.currentExists ? '' : 'none'; |
− | checkMultiInput (); | + | |
| + | checkMultiInput(); |
| } | | } |
− | return evtKill (evt); | + | return evtKill( evt ); |
| }, | | }, |
| | | |
− | inactivate : function () { | + | inactivate: function () { |
− | if (this.list) this.list.style.display = 'none'; | + | 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; | | this.is_active = false; |
| }, | | }, |
| | | |
− | acceptCheck : function (dontCheck) { | + | acceptCheck: function ( dontCheck ) { |
− | this.sanitizeInput (); | + | this.sanitizeInput(); |
− | var value = this.text.value.split('|'); | + | var value = this.text.value.split( '|' ); |
− | var key = null; | + | var key = null; |
− | if (value.length > 1) key = value[1]; | + | if ( value.length > 1 ) key = value[ 1 ]; |
− | var v = value[0].replace(/_/g, ' ').replace(/^\s+|\s+$/g, ""); | + | |
− | if (HotCat.capitalizePageNames) v = capitalize (v); | + | var v = value[ 0 ].replace( /_/g, ' ' ).replace( /^\s+|\s+$/g, '' ); |
| + | if ( HC.capitalizePageNames ) v = capitalize( v ); |
| + | |
| this.lastInput = v; | | this.lastInput = v; |
− | v = replaceShortcuts(v, HotCat.shortcuts); | + | v = replaceShortcuts( v, HC.shortcuts ); |
− | if (v.length === 0) { | + | if ( !v.length ) { |
− | this.cancel (); | + | this.cancel(); |
| return false; | | return false; |
| } | | } |
− | if (!dontCheck | + | if ( !dontCheck && ( |
− | && ( conf.wgNamespaceNumber === 14 && v == conf.wgTitle
| + | conf.wgNamespaceNumber === 14 && v === conf.wgTitle || HC.blacklist && HC.blacklist.test( v ) ) ) { |
− | || HotCat.blacklist && HotCat.blacklist.test(v))
| + | this.cancel(); |
− | )
| |
− | {
| |
− | this.cancel (); | |
| return false; | | return false; |
| } | | } |
Line 1,799: |
Line 1,894: |
| }, | | }, |
| | | |
− | accept : function (evt) { | + | accept: function ( evt ) { |
− | this.noCommit = (evtKeys (evt) & 1) !== 0; | + | // eslint-disable-next-line no-bitwise |
− | var result = evtKill (evt); | + | this.noCommit = ( evtKeys( evt ) & 1 ) !== 0; |
− | if (this.acceptCheck ()) { | + | var result = evtKill( evt ); |
− | var toResolve = [this]; | + | if ( this.acceptCheck() ) { |
− | var original = this.currentCategory; | + | var toResolve = [ this ]; |
− | resolveMulti ( | + | var original = this.currentCategory; |
− | toResolve
| + | resolveMulti( toResolve, function ( resolved ) { |
− | ,function (resolved) {
| + | if ( resolved[ 0 ].dab ) { |
− | if (resolved[0].dab) {
| + | showDab( resolved[ 0 ] ); |
− | showDab (resolved[0]);
| + | } else { |
− | } else {
| + | if ( resolved[ 0 ].acceptCheck( true ) ) { |
− | if (resolved[0].acceptCheck(true)) {
| + | resolved[ 0 ].commit( |
− | resolved[0].commit (
| + | ( resolved[ 0 ].currentCategory !== original ) ? |
− | (resolved[0].currentCategory != original)
| + | HC.messages.cat_resolved.replace( /\$1/g, original ) : |
− | ? HotCat.messages.cat_resolved.replace (/\$1/g, original)
| + | null ); |
− | : null
| |
− | );
| |
− | }
| |
| } | | } |
− | }
| + | } |
− | ); | + | } ); |
| } | | } |
| return result; | | return result; |
| }, | | }, |
| | | |
− | close : function () { | + | close: function () { |
− | if (!this.catLink) { | + | if ( !this.catLink ) { |
| // Create a catLink | | // Create a catLink |
− | this.catLink = make ('a'); | + | this.catLink = make( 'a' ); |
− | this.catLink.appendChild (make ('foo', true)); | + | this.catLink.appendChild( make( 'foo', true ) ); |
| this.catLink.style.display = 'none'; | | this.catLink.style.display = 'none'; |
− | this.span.insertBefore (this.catLink, this.span.firstChild.nextSibling); | + | this.span.insertBefore( this.catLink, this.span.firstChild.nextSibling ); |
| } | | } |
− | this.catLink.removeChild (this.catLink.firstChild); | + | this.catLink.removeChild( this.catLink.firstChild ); |
− | this.catLink.appendChild (make (this.currentCategory, true)); | + | this.catLink.appendChild( make( this.currentCategory, true ) ); |
− | this.catLink.href = wikiPagePath (HotCat.category_canonical + ':' + this.currentCategory); | + | this.catLink.href = wikiPagePath( HC.category_canonical + ':' + this.currentCategory ); |
− | this.catLink.className = this.currentExists ? "" : 'new'; | + | this.catLink.className = this.currentExists ? '' : 'new'; |
| this.lastSavedCategory = this.currentCategory; | | this.lastSavedCategory = this.currentCategory; |
− | this.lastSavedKey = this.currentKey; | + | this.lastSavedKey = this.currentKey; |
− | this.lastSavedExists = this.currentExists; | + | this.lastSavedExists = this.currentExists; |
− | this.lastSavedHidden = this.currentHidden; | + | this.lastSavedHidden = this.currentHidden; |
| // Close form and redisplay category | | // Close form and redisplay category |
| this.inactivate(); | | this.inactivate(); |
| this.form.style.display = 'none'; | | this.form.style.display = 'none'; |
− | this.catLink.title = this.currentKey || ""; | + | this.catLink.title = this.currentKey || ''; |
− | this.catLink.style.display = ""; | + | this.catLink.style.display = ''; |
− | if (this.isAddCategory) { | + | if ( this.isAddCategory ) { |
− | if (onUpload) { | + | if ( onUpload && this.line ) { |
− | var newAdder = new CategoryEditor (this.line, null, this.span, true); // Create a new one | + | // eslint-disable-next-line no-new |
| + | new CategoryEditor( this.line, null, this.span, true ); // Create a new one |
| } | | } |
| this.isAddCategory = false; | | this.isAddCategory = false; |
− | this.linkSpan.parentNode.removeChild (this.linkSpan); | + | this.linkSpan.parentNode.removeChild( this.linkSpan ); |
− | this.makeLinkSpan (); | + | this.makeLinkSpan(); |
− | this.span.appendChild (this.linkSpan); | + | this.span.appendChild( this.linkSpan ); |
| } | | } |
− | if (!this.undoLink) { | + | if ( !this.undoLink ) { |
| // Append an undo link. | | // Append an undo link. |
− | var span = make ('span'); | + | var span = make( 'span' ); |
− | var lk = make ('a'); lk.href = '#catlinks'; lk.onclick = bind (this.rollback, this); | + | var lk = make( 'a' ); |
− | lk.appendChild (make (HotCat.links.undo, true)); lk.title = HotCat.tooltips.undo; | + | lk.href = '#catlinks'; |
− | span.appendChild (make (' ', true)); | + | lk.onclick = this.rollback.bind( this ); |
− | span.appendChild (lk); | + | lk.appendChild( make( HC.links.undo, true ) ); |
− | this.normalLinks.appendChild (span); | + | lk.title = HC.tooltips.undo; |
| + | span.appendChild( make( ' ', true ) ); |
| + | span.appendChild( lk ); |
| + | this.normalLinks.appendChild( span ); |
| this.undoLink = span; | | this.undoLink = span; |
− | if (!onUpload) { | + | if ( !onUpload ) { |
| try { | | try { |
− | this.catLink.style.backgroundColor = HotCat.bg_changed; | + | this.catLink.style.backgroundColor = HC.bg_changed; |
− | } catch (ex) {} | + | } catch ( ex ) {} |
| } | | } |
| } | | } |
− | if (this.upDownLinks) this.upDownLinks.style.display = this.lastSavedExists ? "" : 'none'; | + | if ( this.upDownLinks ) this.upDownLinks.style.display = this.lastSavedExists ? '' : 'none'; |
− | this.linkSpan.style.display = ""; | + | |
| + | this.linkSpan.style.display = ''; |
| this.state = CategoryEditor.CHANGED; | | this.state = CategoryEditor.CHANGED; |
− | checkMultiInput (); | + | checkMultiInput(); |
− | forceRedraw (); | + | forceRedraw(); |
| }, | | }, |
| | | |
− | commit : function (comment) { | + | commit: function () { |
| // Check again to catch problem cases after redirect resolution | | // Check again to catch problem cases after redirect resolution |
− | if ( ( this.currentCategory == this.originalCategory | + | if ( |
− | && (this.currentKey == this.originalKey | + | ( |
− | || this.currentKey === null && this.originalKey.length === 0 | + | this.currentCategory === this.originalCategory && |
− | )
| + | ( |
− | )
| + | this.currentKey === this.originalKey || |
− | || conf.wgNamespaceNumber == 14 && this.currentCategory == conf.wgTitle | + | this.currentKey === null && !this.originalKey.length |
− | || HotCat.blacklist && HotCat.blacklist.test (this.currentCategory) | + | ) |
− | )
| + | ) || |
− | {
| + | conf.wgNamespaceNumber === 14 && this.currentCategory === conf.wgTitle || |
− | this.cancel (); | + | HC.blacklist && HC.blacklist.test( this.currentCategory ) |
| + | ) { |
| + | this.cancel(); |
| return; | | return; |
| } | | } |
− | if (commitButton || onUpload) { | + | this.close(); |
− | this.close ();
| + | if ( !commitButton && !onUpload ) { |
− | } else { | |
− | this.close ();
| |
| var self = this; | | var self = this; |
− | initiateEdit (function (failure) {performChanges (failure, self);}, function (msg) {alert (msg);}); | + | initiateEdit( function ( failure ) { |
| + | performChanges( failure, self ); |
| + | }, function ( msg ) { |
| + | alert( msg ); |
| + | } ); |
| } | | } |
| }, | | }, |
| | | |
− | remove : function (evt) { | + | remove: function ( evt ) { |
− | this.doRemove (evtKeys (evt) & 1); | + | // eslint-disable-next-line no-bitwise |
− | return evtKill (evt); | + | this.doRemove( evtKeys( evt ) & 1 ); |
| + | return evtKill( evt ); |
| }, | | }, |
| | | |
− | doRemove : function (noCommit) { | + | doRemove: function ( noCommit ) { |
− | if (this.isAddCategory) { // Empty input on adding a new category | + | if ( this.isAddCategory ) { // Empty input on adding a new category |
− | this.cancel (); | + | this.cancel(); |
| return; | | return; |
| } | | } |
− | if (!commitButton && !onUpload) { | + | if ( !commitButton && !onUpload ) { |
− | for (var i = 0; i < editors.length; i++) { | + | for ( var i = 0; i < editors.length; i++ ) { |
− | if (editors[i].state != CategoryEditor.UNCHANGED) { | + | if ( editors[ i ].state !== CategoryEditor.UNCHANGED ) { |
| setMultiInput(); | | setMultiInput(); |
| break; | | break; |
Line 1,918: |
Line 2,020: |
| } | | } |
| } | | } |
− | if (commitButton) { | + | if ( commitButton ) { |
− | this.catLink.title = ""; | + | this.catLink.title = ''; |
| this.catLink.style.cssText += '; text-decoration : line-through !important;'; | | this.catLink.style.cssText += '; text-decoration : line-through !important;'; |
| try { | | try { |
− | this.catLink.style.backgroundColor = HotCat.bg_changed; | + | this.catLink.style.backgroundColor = HC.bg_changed; |
− | } catch (ex) {} | + | } catch ( ex ) {} |
| this.originalState = this.state; | | this.originalState = this.state; |
| this.state = CategoryEditor.DELETED; | | this.state = CategoryEditor.DELETED; |
| this.normalLinks.style.display = 'none'; | | this.normalLinks.style.display = 'none'; |
− | this.undelLink.style.display = ""; | + | this.undelLink.style.display = ''; |
− | checkMultiInput (); | + | checkMultiInput(); |
| } else { | | } else { |
− | if (onUpload) { | + | if ( onUpload ) { |
| // Remove this editor completely | | // Remove this editor completely |
− | this.removeEditor (); | + | this.removeEditor(); |
| } else { | | } else { |
| this.originalState = this.state; | | this.originalState = this.state; |
| this.state = CategoryEditor.DELETED; | | this.state = CategoryEditor.DELETED; |
− | this.noCommit = noCommit || HotCat.del_needs_diff; | + | this.noCommit = noCommit || HC.del_needs_diff; |
| var self = this; | | var self = this; |
− | initiateEdit (function (failure) {performChanges (failure, self);}, function (msg) {self.state = self.originalState; alert (msg);}); | + | initiateEdit( |
| + | function ( failure ) { |
| + | performChanges( failure, self ); |
| + | }, |
| + | function ( msg ) { |
| + | self.state = self.originalState; |
| + | alert( msg ); |
| + | } ); |
| } | | } |
| } | | } |
| }, | | }, |
| | | |
− | restore : function (evt) { | + | restore: function ( evt ) { |
| // Can occur only if we do have a commit button and are not on the upload form | | // Can occur only if we do have a commit button and are not on the upload form |
− | this.catLink.title = this.currentKey || ""; | + | this.catLink.title = this.currentKey || ''; |
− | this.catLink.style.textDecoration = ""; | + | this.catLink.style.textDecoration = ''; |
| this.state = this.originalState; | | this.state = this.originalState; |
− | if (this.state == CategoryEditor.UNCHANGED) { | + | if ( this.state === CategoryEditor.UNCHANGED ) { |
| this.catLink.style.backgroundColor = 'transparent'; | | this.catLink.style.backgroundColor = 'transparent'; |
| } else { | | } else { |
| try { | | try { |
− | this.catLink.style.backgroundColor = HotCat.bg_changed; | + | this.catLink.style.backgroundColor = HC.bg_changed; |
− | } catch (ex) {} | + | } catch ( ex ) {} |
| } | | } |
− | this.normalLinks.style.display = ""; | + | this.normalLinks.style.display = ''; |
| this.undelLink.style.display = 'none'; | | this.undelLink.style.display = 'none'; |
− | checkMultiInput (); | + | checkMultiInput(); |
− | return evtKill (evt); | + | return evtKill( evt ); |
| }, | | }, |
| | | |
| // Internal operations | | // Internal operations |
| | | |
− | selectEngine : function (engineName) { | + | selectEngine: function ( engineName ) { |
− | if (!this.engineSelector) return; | + | if ( !this.engineSelector ) return; |
− | for (var i = 0; i < this.engineSelector.options.length; i++) { | + | 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;
| |
− | }
| |
| }, | | }, |
| | | |
− | sanitizeInput : function () { | + | sanitizeInput: function () { |
− | var v = this.text.value || ""; | + | var v = this.text.value || ''; |
− | v = v.replace(/^(\s|_)+/, ""); // Trim leading blanks and underscores | + | v = v.replace( /^(\s|_)+/, '' ); // Trim leading blanks and underscores |
− | var re = new RegExp ('^(' + HotCat.category_regexp + '):'); | + | var re = new RegExp( '^(' + HC.category_regexp + '):' ); |
− | if (re.test (v)) { | + | if ( re.test( v ) ) v = v.substring( v.indexOf( ':' ) + 1 ).replace( /^(\s|_)+/, '' ); |
− | v = v.substring (v.indexOf (':') + 1).replace(/^(\s|_)+/, "");
| + | v = v.replace(/\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. IE8 appears to reset the selection | + | // Only update the input field if there is a difference. Various browsers otherwise |
− | // and place the cursor at the front upon reset, which makes our autocompletetion become a | + | // reset the selection and cursor position after each value re-assignment. |
− | // nuisance. FF and IE6 don't seem to have this problem.
| + | if ( this.text.value !== null && this.text.value !== v ) this.text.value = v; |
− | if (this.text.value !== null && this.text.value != v) | |
− | this.text.value = v;
| |
| }, | | }, |
| | | |
− | makeCall : function (url, callbackObj, engine, queryKey, cleanKey) { | + | makeCall: function ( url, callbackObj, engine, queryKey, cleanKey ) { |
− | var cb = callbackObj; | + | var cb = callbackObj, |
− | var e = engine;
| + | e = engine, |
− | var v = queryKey;
| + | v = queryKey, |
− | var z = cleanKey;
| + | z = cleanKey, |
− | var thisObj = this;
| + | thisObj = this; |
| | | |
− | function done () { | + | function done() { |
| cb.callsMade++; | | cb.callsMade++; |
− | if (cb.callsMade === cb.nofCalls) { | + | if ( cb.callsMade === cb.nofCalls ) { |
− | if (cb.exists) cb.allTitles.exists = true; | + | if ( cb.exists ) cb.allTitles.exists = true; |
− | if (cb.normalized) cb.allTitles.normalized = cb.normalized; | + | |
− | if (!cb.dontCache && !suggestionConfigs[cb.engineName].cache[z]) { | + | if ( cb.normalized ) cb.allTitles.normalized = 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; | | thisObj.text.readOnly = false; |
− | if (!cb.cancelled) thisObj.showSuggestions (cb.allTitles, cb.noCompletion, v, cb.engineName); | + | if ( !cb.cancelled ) thisObj.showSuggestions( cb.allTitles, cb.noCompletion, v, cb.engineName ); |
− | if (cb === thisObj.callbackObj) thisObj.callbackObj = null; | + | |
− | delete cb; | + | if ( cb === thisObj.callbackObj ) thisObj.callbackObj = null; |
| + | |
| + | cb = undefined; |
| } | | } |
| } | | } |
| | | |
− | getJSON ({ | + | $.getJSON( url, function ( json ) { |
− | uri : url
| + | var titles = e.handler( json, z ); |
− | ,success : function (json) {
| + | if ( titles && titles.length ) { |
− | var titles = e.handler (json, z); | + | if ( cb.allTitles === null ) cb.allTitles = titles; else cb.allTitles = cb.allTitles.concat( titles ); |
− | if (titles && titles.length > 0) { | + | if ( titles.exists ) cb.exists = true; |
− | if (cb.allTitles === null) { | + | if ( titles.normalized ) cb.normalized = 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(); | | done(); |
− | }
| + | } ).fail( function ( req ) { |
− | ,error : function (req) {if (!req) noSuggestions = true; cb.dontCache = true; done(); }
| + | if ( !req ) noSuggestions = true; |
− | }); | + | cb.dontCache = true; |
| + | done(); |
| + | } ); |
| }, | | }, |
| | | |
− | callbackObj : null, | + | callbackObj: null, |
| | | |
− | textchange : function (dont_autocomplete, force) { | + | textchange: function ( dont_autocomplete, force ) { |
| // Hide all other lists | | // Hide all other lists |
− | makeActive (this); | + | makeActive( this ); |
| // Get input value, omit sort key, if any | | // Get input value, omit sort key, if any |
− | this.sanitizeInput (); | + | this.sanitizeInput(); |
| var v = this.text.value; | | var v = this.text.value; |
| // Disregard anything after a pipe. | | // Disregard anything after a pipe. |
− | var pipe = v.indexOf ('|'); | + | var pipe = v.indexOf( '|' ); |
− | if (pipe >= 0) { | + | if ( pipe >= 0 ) { |
− | this.currentKey = v.substring (pipe+1); | + | this.currentKey = v.substring( pipe + 1 ); |
− | v = v.substring (0, pipe); | + | v = v.substring( 0, pipe ); |
| } else { | | } else { |
| this.currentKey = null; | | this.currentKey = null; |
| } | | } |
− | if (this.lastInput == v && !force) return; // No change | + | if ( this.lastInput === v && !force ) return; // No change |
− | if (this.lastInput != v) checkMultiInput (); | + | if ( this.lastInput !== v ) checkMultiInput(); |
| + | |
| this.lastInput = v; | | this.lastInput = v; |
| this.lastRealInput = v; | | this.lastRealInput = v; |
| | | |
| // Mark blacklisted inputs. | | // Mark blacklisted inputs. |
− | this.ok.disabled = v.length > 0 && HotCat.blacklist && HotCat.blacklist.test (v); | + | this.ok.disabled = v.length && HC.blacklist && HC.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 (noSuggestions) { | + | if ( !v.length ) { |
− | // No Ajax: just make sure the list is hidden | + | this.showSuggestions( [] ); |
− | if (this.list) this.list.style.display = 'none'; | + | return; |
− | if (this.engineSelector) this.engineSelector.style.display = 'none';
| + | } |
− | if (this.icon) this.icon.style.display = 'none';
| + | var cleanKey = v.replace( /[\u200E\u200F\u202A-\u202E]/g, '' ).replace( wikiTextBlankRE, ' ' ); |
| + | cleanKey = replaceShortcuts( cleanKey, HC.shortcuts ); |
| + | cleanKey = cleanKey.replace( /^\s+|\s+$/g, '' ); |
| + | if ( !cleanKey.length ) { |
| + | this.showSuggestions( [] ); |
| return; | | return; |
| } | | } |
| | | |
− | if (v.length === 0) { this.showSuggestions([]); return; } | + | if ( this.callbackObj ) this.callbackObj.cancelled = true; |
− | var cleanKey = v.replace(/[\u200E\u200F\u202A-\u202E]/g, "").replace(wikiTextBlankRE, ' ');
| |
− | cleanKey = replaceShortcuts(cleanKey, HotCat.shortcuts);
| |
− | cleanKey = cleanKey.replace(/^\s+|\s+$/g, '');
| |
− | if (cleanKey.length === 0) { this.showSuggestions([]); return; }
| |
| | | |
− | if (this.callbackObj) this.callbackObj.cancelled = true;
| + | var engineName = suggestionConfigs[ this.engine ] ? this.engine : 'combined'; |
− | var engineName = suggestionConfigs[this.engine] ? this.engine : 'combined'; | |
| | | |
− | dont_autocomplete = dont_autocomplete || suggestionConfigs[engineName].noCompletion; | + | dont_autocomplete = dont_autocomplete || suggestionConfigs[ engineName ].noCompletion; |
− | if (suggestionConfigs[engineName].cache[cleanKey]) { | + | if ( suggestionConfigs[ engineName ].cache[ cleanKey ] ) { |
− | this.showSuggestions (suggestionConfigs[engineName].cache[cleanKey], dont_autocomplete, v, engineName); | + | this.showSuggestions( suggestionConfigs[ engineName ].cache[ cleanKey ], dont_autocomplete, v, engineName ); |
| return; | | return; |
| } | | } |
| | | |
− | var engines = suggestionConfigs[engineName].engines; | + | var engines = suggestionConfigs[ engineName ].engines; |
− | this.callbackObj = | + | this.callbackObj = { |
− | {allTitles: null, callsMade: 0, nofCalls: engines.length, noCompletion: dont_autocomplete, engineName: engineName}; | + | allTitles: null, |
− | this.makeCalls (engines, this.callbackObj, v, cleanKey); | + | callsMade: 0, |
| + | nofCalls: engines.length, |
| + | noCompletion: dont_autocomplete, |
| + | engineName: engineName |
| + | }; |
| + | this.makeCalls( engines, this.callbackObj, v, cleanKey ); |
| }, | | }, |
| | | |
− | makeCalls : function (engines, cb, v, cleanKey) { | + | makeCalls: function ( engines, cb, v, cleanKey ) { |
− | for (var j = 0; j < engines.length; j++) { | + | for ( var j = 0; j < engines.length; j++ ) { |
− | var engine = suggestionEngines[engines[j]]; | + | var engine = suggestionEngines[ engines[ j ] ]; |
− | var url = conf.wgServer + conf.wgScriptPath + engine.uri.replace (/\$1/g, encodeURIComponent (cleanKey)); | + | var url = conf.wgServer + conf.wgScriptPath + engine.uri.replace( /\$1/g, encodeURIComponent( cleanKey ) ); |
− | this.makeCall (url, cb, engine, v, cleanKey); | + | this.makeCall( url, cb, engine, v, cleanKey ); |
| } | | } |
| }, | | }, |
| | | |
− | showSuggestions : function (titles, dontAutocomplete, queryKey, engineName) { | + | showSuggestions: function ( titles, dontAutocomplete, queryKey, engineName ) { |
| this.text.readOnly = false; | | this.text.readOnly = false; |
| this.dab = null; | | this.dab = null; |
| this.showsList = false; | | this.showsList = false; |
− | if (!this.list) return; | + | if ( !this.list ) return; |
− | if (noSuggestions) { | + | if ( noSuggestions ) { |
− | if (this.list) this.list.style.display = 'none'; | + | if ( this.list ) this.list.style.display = 'none'; |
− | if (this.engineSelector) this.engineSelector.style.display = 'none'; | + | |
− | if (this.icon) this.icon.style.display = 'none'; | + | if ( this.engineSelector ) this.engineSelector.style.display = 'none'; |
| + | |
| + | if ( this.icon ) this.icon.style.display = 'none'; |
| + | |
| this.inputExists = true; // Default... | | this.inputExists = true; // Default... |
| return; | | return; |
| } | | } |
| this.engineName = engineName; | | this.engineName = engineName; |
− | if (engineName) { | + | if ( engineName ) { |
− | if (!this.engineSelector) this.engineName = null; | + | if ( !this.engineSelector ) this.engineName = null; |
| } else { | | } else { |
− | if (this.engineSelector) this.engineSelector.style.display = 'none'; | + | if ( this.engineSelector ) this.engineSelector.style.display = 'none'; |
| } | | } |
− | if (queryKey) { | + | if ( queryKey ) { |
− | if (this.lastInput.indexOf (queryKey) !== 0) return; | + | if ( this.lastInput.indexOf( queryKey ) ) return; |
− | if (this.lastQuery && this.lastInput.indexOf (this.lastQuery) === 0 && this.lastQuery.length > queryKey.length) | + | if ( this.lastQuery && this.lastInput.indexOf( this.lastQuery ) === 0 && this.lastQuery.length > queryKey.length ) return; |
− | return;
| |
| } | | } |
| this.lastQuery = queryKey; | | this.lastQuery = queryKey; |
| | | |
| // Get current input text | | // Get current input text |
− | var v = this.text.value.split('|'); | + | var v = this.text.value.split( '|' ); |
− | var key = v.length > 1 ? '|' + v[1] : ""; | + | var key = v.length > 1 ? '|' + v[ 1 ] : ''; |
− | v = (HotCat.capitalizePageNames ? capitalize (v[0]) : v[0]); | + | v = ( HC.capitalizePageNames ? capitalize( v[ 0 ] ) : v[ 0 ] ); |
| var vNormalized = v; | | var vNormalized = v; |
| var knownToExist = titles && titles.exists; | | var knownToExist = titles && titles.exists; |
| var i; | | var i; |
− | if (titles) { | + | if ( titles ) { |
− | if (titles.normalized && v.indexOf(queryKey) === 0) { | + | if ( titles.normalized && v.indexOf( queryKey ) === 0 ) { |
− | // We got back a different normalization than what is in the input field
| + | // We got back a different normalization than what is in the input field |
− | vNormalized = titles.normalized + v.substring(queryKey.length); | + | vNormalized = titles.normalized + v.substring( queryKey.length ); |
| } | | } |
− | var vLow = vNormalized.toLowerCase (); | + | var vLow = vNormalized.toLowerCase(); |
| // Strip blacklisted categories | | // Strip blacklisted categories |
− | if (HotCat.blacklist) { | + | if ( HC.blacklist ) { |
− | for (i = 0; i < titles.length; i++) { | + | for ( i = 0; i < titles.length; i++ ) { |
− | if (HotCat.blacklist.test (titles[i])) { | + | if ( HC.blacklist.test( titles[ i ] ) ) { |
− | titles.splice(i, 1); | + | titles.splice( i, 1 ); |
| i--; | | i--; |
| } | | } |
| } | | } |
| } | | } |
− | titles.sort ( | + | titles.sort( |
− | function (a, b) { | + | function ( a, b ) { |
− | if (a == b) return 0; | + | if ( a === b ) return 0; |
− | if (a.indexOf (b) === 0) return 1; // a begins with b: a > b | + | |
− | if (b.indexOf (a) === 0) return -1; // b begins with a: a < b | + | if ( a.indexOf( b ) === 0 ) return 1; |
| + | // a begins with b: a > b |
| + | if ( b.indexOf( a ) === 0 ) return -1; |
| + | // b begins with a: a < b |
| // Opensearch may return stuff not beginning with the search prefix! | | // Opensearch may return stuff not beginning with the search prefix! |
− | var prefixMatchA = (a.indexOf (vNormalized) === 0 ? 1 : 0); | + | var prefixMatchA = ( a.indexOf( vNormalized ) === 0 ? 1 : 0 ); |
− | var prefixMatchB = (b.indexOf (vNormalized) === 0 ? 1 : 0); | + | var prefixMatchB = ( b.indexOf( vNormalized ) === 0 ? 1 : 0 ); |
− | if (prefixMatchA != prefixMatchB) return prefixMatchB - prefixMatchA; | + | if ( prefixMatchA !== prefixMatchB ) return prefixMatchB - prefixMatchA; |
| + | |
| // Case-insensitive prefix match! | | // Case-insensitive prefix match! |
− | var aLow = a.toLowerCase(), bLow = b.toLowerCase(); | + | var aLow = a.toLowerCase(), |
− | prefixMatchA = (aLow.indexOf (vLow) === 0 ? 1 : 0); | + | bLow = b.toLowerCase(); |
− | prefixMatchB = (bLow.indexOf (vLow) === 0 ? 1 : 0); | + | prefixMatchA = ( aLow.indexOf( vLow ) === 0 ? 1 : 0 ); |
− | if (prefixMatchA != prefixMatchB) return prefixMatchB - prefixMatchA; | + | prefixMatchB = ( bLow.indexOf( vLow ) === 0 ? 1 : 0 ); |
− | if (a < b) return -1; | + | if ( prefixMatchA !== prefixMatchB ) return prefixMatchB - prefixMatchA; |
− | if (b < a) return 1; | + | |
| + | if ( a < b ) return -1; |
| + | |
| + | if ( b < a ) return 1; |
| + | |
| return 0; | | return 0; |
− | } | + | } ); |
− | );
| |
| // Remove duplicates and self-references | | // Remove duplicates and self-references |
− | for (i = 0; i < titles.length; i++) { | + | for ( i = 0; i < titles.length; i++ ) { |
− | if ( i+1 < titles.length && titles[i] == titles[i+1] | + | if ( |
− | || conf.wgNamespaceNumber == 14 && titles[i] == conf.wgTitle | + | i + 1 < titles.length && titles[ i ] === titles[ i + 1 ] || |
− | )
| + | conf.wgNamespaceNumber === 14 && titles[ i ] === conf.wgTitle |
− | {
| + | ) { |
− | titles.splice (i, 1); | + | titles.splice( i, 1 ); |
| i--; | | i--; |
| } | | } |
| } | | } |
| } | | } |
− | if (!titles || titles.length === 0) { | + | if ( !titles || !titles.length ) { |
− | if (this.list) this.list.style.display = 'none'; | + | if ( this.list ) this.list.style.display = 'none'; |
− | if (this.engineSelector) this.engineSelector.style.display = 'none'; | + | |
− | if (engineName && suggestionConfigs[engineName] && !suggestionConfigs[engineName].temp) { | + | if ( this.engineSelector ) this.engineSelector.style.display = 'none'; |
− | if (this.icon) this.icon.src = armorUri(HotCat.existsNo); | + | |
| + | if ( engineName && suggestionConfigs[ engineName ] && !suggestionConfigs[ engineName ].temp ) { |
| + | if ( this.icon ) this.icon.src = HC.existsNo; |
| + | |
| this.inputExists = false; | | this.inputExists = false; |
| } | | } |
Line 2,174: |
Line 2,303: |
| } | | } |
| | | |
− | var firstTitle = titles[0]; | + | var firstTitle = titles[ 0 ]; |
− | var completed = this.autoComplete (firstTitle, v, vNormalized, key, dontAutocomplete); | + | var completed = this.autoComplete( firstTitle, v, vNormalized, key, dontAutocomplete ); |
− | var existing = completed || knownToExist || firstTitle == replaceShortcuts(v, HotCat.shortcuts); | + | var existing = completed || knownToExist || firstTitle === replaceShortcuts( v, HC.shortcuts ); |
− | if (engineName && suggestionConfigs[engineName] && !suggestionConfigs[engineName].temp) { | + | if ( engineName && suggestionConfigs[ engineName ] && !suggestionConfigs[ engineName ].temp ) { |
− | this.icon.src = armorUri(existing ? HotCat.existsYes : HotCat.existsNo); | + | this.icon.src = ( existing ? HC.existsYes : HC.existsNo ); |
| this.inputExists = existing; | | this.inputExists = existing; |
| } | | } |
− | if (completed) { | + | if ( completed ) { |
| this.lastInput = firstTitle; | | this.lastInput = firstTitle; |
− | if (titles.length === 1) { | + | if ( titles.length === 1 ) { |
| this.list.style.display = 'none'; | | this.list.style.display = 'none'; |
− | if (this.engineSelector) this.engineSelector.style.display = 'none'; | + | if ( this.engineSelector ) this.engineSelector.style.display = 'none'; |
| + | |
| return; | | return; |
| } | | } |
| } | | } |
| // (Re-)fill the list | | // (Re-)fill the list |
− | while (this.list.firstChild) this.list.removeChild (this.list.firstChild); | + | while ( this.list.firstChild ) this.list.removeChild( this.list.firstChild ); |
− | for (i = 0 ; i < titles.length ; i++) { | + | |
− | var opt = make ('option') ; | + | for ( i = 0; i < titles.length; i++ ) { |
− | opt.appendChild (make (titles[i], true)); | + | var opt = make( 'option' ); |
− | opt.selected = completed && (i === 0); | + | opt.appendChild( make( titles[ i ], true ) ); |
− | this.list.appendChild (opt); | + | opt.selected = completed && ( i === 0 ); |
| + | this.list.appendChild( opt ); |
| } | | } |
| this.displayList(); | | this.displayList(); |
| }, | | }, |
| | | |
− | displayList : function () { | + | displayList: function () { |
| this.showsList = true; | | this.showsList = true; |
− | if (!this.is_active) { | + | if ( !this.is_active ) { |
| this.list.style.display = 'none'; | | this.list.style.display = 'none'; |
− | if (this.engineSelector) this.engineSelector.style.display = 'none'; | + | if ( this.engineSelector ) this.engineSelector.style.display = 'none'; |
| + | |
| return; | | return; |
| } | | } |
− | var nofItems = (this.list.options.length > HotCat.list_size ? HotCat.list_size : this.list.options.length); | + | var nofItems = ( this.list.options.length > HC.listSize ? HC.listSize : this.list.options.length ); |
− | if (nofItems <= 1) nofItems = 2; | + | if ( nofItems <= 1 ) nofItems = 2; |
| + | |
| this.list.size = nofItems; | | this.list.size = nofItems; |
− | this.list.style.align = is_rtl ? 'right' : 'left'; | + | this.list.style.align = is_rtl ? 'right' : 'left'; |
− | this.list.style.zIndex = 5; | + | this.list.style.zIndex = 5; |
| this.list.style.position = 'absolute'; | | this.list.style.position = 'absolute'; |
| // Compute initial list position. First the height. | | // Compute initial list position. First the height. |
| var anchor = is_rtl ? 'right' : 'left'; | | var anchor = is_rtl ? 'right' : 'left'; |
| var listh = 0; | | var listh = 0; |
− | if (this.list.style.display == 'none') { | + | if ( this.list.style.display === 'none' ) { |
| // Off-screen display to get the height | | // Off-screen display to get the height |
| this.list.style.top = this.text.offsetTop + 'px'; | | this.list.style.top = this.text.offsetTop + 'px'; |
− | this.list.style[anchor] = '-10000px'; | + | this.list.style[ anchor ] = '-10000px'; |
− | this.list.style.display = ""; | + | this.list.style.display = ''; |
| listh = this.list.offsetHeight; | | listh = this.list.offsetHeight; |
| this.list.style.display = 'none'; | | this.list.style.display = 'none'; |
Line 2,228: |
Line 2,361: |
| // Approximate calculation of maximum list size | | // Approximate calculation of maximum list size |
| var maxListHeight = listh; | | var maxListHeight = listh; |
− | if (nofItems < HotCat.list_size) maxListHeight = (listh / nofItems) * HotCat.list_size; | + | if ( nofItems < HC.listSize ) maxListHeight = ( listh / nofItems ) * HC.listSize; |
| | | |
− | function viewport (what) { | + | function viewport( what ) { |
− | if (is_webkit && !document.evaluate) | + | if ( is_webkit && !document.evaluate ) { |
− | return window['inner' + what]; // Safari < 3.0 | + | // Safari < 3.0 |
| + | return window[ 'inner' + what ]; |
| + | } |
| var s = 'client' + what; | | var s = 'client' + what; |
− | if (window.opera) return document.body[s]; | + | if ( window.opera ) return document.body[ s ]; |
− | return (document.documentElement ? document.documentElement[s] : 0) | + | |
− | || document.body[s] || 0;
| + | return ( document.documentElement ? document.documentElement[ s ] : 0 ) || document.body[ s ] || 0; |
| } | | } |
− | function scroll_offset (what) { | + | function scroll_offset( what ) { |
| var s = 'scroll' + what; | | var s = 'scroll' + what; |
− | var result = (document.documentElement ? document.documentElement[s] : 0) | + | var result = ( document.documentElement ? document.documentElement[ s ] : 0 ) || document.body[ s ] || 0; |
− | || document.body[s] || 0;
| + | if ( is_rtl && what === 'Left' ) { |
− | if (is_rtl && what == 'Left') { | |
| // RTL inconsistencies. | | // RTL inconsistencies. |
| // FF: 0 at the far right, then increasingly negative values. | | // FF: 0 at the far right, then increasingly negative values. |
| // IE >= 8: 0 at the far right, then increasingly positive values. | | // IE >= 8: 0 at the far right, then increasingly positive values. |
| // Webkit: scrollWidth - clientWidth at the far right, then down to zero. | | // 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... | | // Opera: don't know... |
− | if (result < 0) result = - result; | + | if ( result < 0 ) result = -result; |
− | if (!is_webkit && !is_ie_lt8) { | + | |
− | result = scroll_offset('Width') - viewport('Width') - result;
| + | if ( !is_webkit ) result = scroll_offset( 'Width' ) - viewport( 'Width' ) - result; |
− | }
| + | |
| // Now all have webkit behavior, i.e. zero if at the leftmost edge. | | // Now all have webkit behavior, i.e. zero if at the leftmost edge. |
| } | | } |
| return result; | | return result; |
| } | | } |
− | function position (node) { | + | function position( node ) { |
| // Stripped-down simplified position function. It's good enough for our purposes. | | // Stripped-down simplified position function. It's good enough for our purposes. |
− | if (node.getBoundingClientRect) { | + | if ( node.getBoundingClientRect ) { |
− | var box = node.getBoundingClientRect (); | + | var box = node.getBoundingClientRect(); |
− | return { x : Math.round (box.left + scroll_offset ('Left')) | + | return { |
− | ,y : Math.round (box.top + scroll_offset ('Top'))
| + | x: Math.round( box.left + scroll_offset( 'Left' ) ), |
− | };
| + | y: Math.round( box.top + scroll_offset( 'Top' ) ) |
| + | }; |
| } | | } |
− | var t = 0, l = 0; | + | var t = 0, |
| + | l = 0; |
| do { | | do { |
− | t = t + (node.offsetTop || 0); | + | t += ( node.offsetTop || 0 ); |
− | l = l + (node.offsetLeft || 0); | + | l += ( node.offsetLeft || 0 ); |
| node = node.offsetParent; | | node = node.offsetParent; |
− | } while (node); | + | } while ( node ); |
− | return {x : l, y : t}; | + | return { |
| + | x: l, |
| + | y: t |
| + | }; |
| } | | } |
| | | |
− | var textPos = position (this.text); | + | var textPos = position( this.text ), |
− | var nl = 0;
| + | nl = 0, |
− | var nt = 0;
| + | nt = 0, |
− | var offset = 0;
| + | offset = 0, |
− | // Opera 9.5 somehow has offsetWidth = 0 here?? Use the next best value...
| + | // Opera 9.5 somehow has offsetWidth = 0 here?? Use the next best value... |
− | var textBoxWidth = this.text.offsetWidth || this.text.clientWidth;
| + | textBoxWidth = this.text.offsetWidth || this.text.clientWidth; |
− | if (this.engineName) { | + | if ( this.engineName ) { |
| this.engineSelector.style.zIndex = 5; | | this.engineSelector.style.zIndex = 5; |
| this.engineSelector.style.position = 'absolute'; | | this.engineSelector.style.position = 'absolute'; |
| this.engineSelector.style.width = textBoxWidth + 'px'; | | this.engineSelector.style.width = textBoxWidth + 'px'; |
| // Figure out the height of this selector: display it off-screen, then hide it again. | | // Figure out the height of this selector: display it off-screen, then hide it again. |
− | if (this.engineSelector.style.display == 'none') { | + | if ( this.engineSelector.style.display === 'none' ) { |
− | this.engineSelector.style[anchor] = '-10000px'; | + | this.engineSelector.style[ anchor ] = '-10000px'; |
− | this.engineSelector.style.top = '0px'; | + | this.engineSelector.style.top = '0'; |
− | this.engineSelector.style.display = ""; | + | this.engineSelector.style.display = ''; |
| offset = this.engineSelector.offsetHeight; | | offset = this.engineSelector.offsetHeight; |
| this.engineSelector.style.display = 'none'; | | this.engineSelector.style.display = 'none'; |
Line 2,294: |
Line 2,432: |
| offset = this.engineSelector.offsetHeight; | | offset = this.engineSelector.offsetHeight; |
| } | | } |
− | this.engineSelector.style[anchor] = nl + 'px'; | + | this.engineSelector.style[ anchor ] = nl + 'px'; |
| } | | } |
− | if (textPos.y < maxListHeight + offset + 1) { | + | if ( textPos.y < maxListHeight + offset + 1 ) { |
− | // The list might extend beyond the upper border of the page. Let's avoid that by placing it
| + | // The list might extend beyond the upper border of the page. Let's avoid that by placing it |
− | // below the input text field.
| + | // below the input text field. |
| nt = this.text.offsetHeight + offset + 1; | | nt = this.text.offsetHeight + offset + 1; |
− | if (this.engineName) this.engineSelector.style.top = this.text.offsetHeight + 'px'; | + | if ( this.engineName ) this.engineSelector.style.top = this.text.offsetHeight + 'px'; |
| } else { | | } else { |
− | nt = - listh - offset - 1; | + | nt = -listh - offset - 1; |
− | if (this.engineName) this.engineSelector.style.top = - (offset + 1) + 'px'; | + | if ( this.engineName ) this.engineSelector.style.top = -( offset + 1 ) + 'px'; |
| } | | } |
| this.list.style.top = nt + 'px'; | | this.list.style.top = nt + 'px'; |
− | this.list.style.width = ""; // No fixed width (yet) | + | this.list.style.width = ''; // No fixed width (yet) |
− | this.list.style[anchor] = nl + 'px'; | + | this.list.style[ anchor ] = nl + 'px'; |
− | if (this.engineName) { | + | if ( this.engineName ) { |
− | this.selectEngine (this.engineName); | + | this.selectEngine( this.engineName ); |
− | this.engineSelector.style.display = ""; | + | this.engineSelector.style.display = ''; |
| } | | } |
| this.list.style.display = 'block'; | | this.list.style.display = 'block'; |
| // Set the width of the list | | // Set the width of the list |
− | if (this.list.offsetWidth < textBoxWidth ) { | + | if ( this.list.offsetWidth < textBoxWidth ) { |
| this.list.style.width = textBoxWidth + 'px'; | | this.list.style.width = textBoxWidth + 'px'; |
| return; | | return; |
| } | | } |
| // If the list is wider than the textbox: make sure it fits horizontally into the browser window | | // If the list is wider than the textbox: make sure it fits horizontally into the browser window |
− | var scroll = scroll_offset ('Left'); | + | var scroll = scroll_offset( 'Left' ); |
− | var view_w = viewport ('Width'); | + | var view_w = viewport( 'Width' ); |
− | var w = this.list.offsetWidth; | + | var w = this.list.offsetWidth; |
− | var l_pos = position (this.list); | + | var l_pos = position( this.list ); |
− | var left = l_pos.x; | + | var left = l_pos.x; |
− | var right = left + w; | + | var right = left + w; |
− | if (left < scroll || right > scroll + view_w) { | + | if ( left < scroll || right > scroll + view_w ) { |
− | if (w > view_w) { | + | if ( w > view_w ) { |
| w = view_w; | | w = view_w; |
| this.list.style.width = w + 'px'; | | this.list.style.width = w + 'px'; |
− | if (is_rtl) { | + | if ( is_rtl ) left = right - w; else right = left + w; |
− | left = right - w;
| |
− | } else {
| |
− | right = left + w;
| |
− | }
| |
| } | | } |
| var relative_offset = 0; | | var relative_offset = 0; |
− | if (left < scroll) { | + | if ( left < scroll ) relative_offset = scroll - left; else if ( right > scroll + view_w ) relative_offset = -( right - scroll - view_w ); |
− | relative_offset = scroll - left;
| + | |
− | } else if (right > scroll + view_w) {
| + | if ( is_rtl ) relative_offset = -relative_offset; |
− | 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) { | + | autoComplete: function ( newVal, actVal, normalizedActVal, key, dontModify ) { |
− | if (newVal == actVal) return true; | + | 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 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) { | + | if ( newVal.indexOf( actVal ) ) { |
| // Maybe it'll work with the normalized value (NFC)? | | // Maybe it'll work with the normalized value (NFC)? |
− | if (normalizedActVal && newVal.indexOf(normalizedActVal) === 0) { | + | if ( normalizedActVal && newVal.indexOf( normalizedActVal ) === 0 ) { |
− | if (this.lastRealInput == actVal) this.lastRealInput = normalizedActVal; | + | if ( this.lastRealInput === actVal ) this.lastRealInput = normalizedActVal; |
| + | |
| actVal = normalizedActVal; | | actVal = normalizedActVal; |
| } else { | | } else { |
Line 2,365: |
Line 2,498: |
| this.text.focus(); | | this.text.focus(); |
| this.text.value = newVal + key; | | this.text.value = newVal + key; |
− | this.setSelection (actVal.length, newVal.length); | + | this.setSelection( actVal.length, newVal.length ); |
| return true; | | return true; |
| }, | | }, |
| | | |
− | canSelect : function () { | + | canSelect: function () { |
− | return this.text.setSelectionRange | + | return this.text.setSelectionRange || |
− | || this.text.createTextRange
| + | this.text.createTextRange || |
− | || typeof this.text.selectionStart != 'undefined'
| + | this.text.selectionStart !== undefined && |
− | && typeof this.text.selectionEnd != 'undefined';
| + | this.text.selectionEnd !== undefined; |
| }, | | }, |
| | | |
− | setSelection : function (from, to) { | + | setSelection: function ( from, to ) { |
| // this.text must be focused (at least on IE) | | // this.text must be focused (at least on IE) |
− | if (!this.text.value) return; | + | if ( !this.text.value ) return; |
− | if (this.text.setSelectionRange) { // e.g. khtml | + | if ( this.text.setSelectionRange ) { // e.g. khtml |
− | this.text.setSelectionRange (from, to); | + | this.text.setSelectionRange( from, to ); |
− | } else if (typeof this.text.selectionStart != 'undefined') { | + | } else if ( this.text.selectionStart !== undefined ) { |
− | if (from > this.text.selectionStart) { | + | if ( from > this.text.selectionStart ) { |
− | this.text.selectionEnd = to; | + | this.text.selectionEnd = to; |
| this.text.selectionStart = from; | | this.text.selectionStart = from; |
| } else { | | } else { |
| this.text.selectionStart = from; | | this.text.selectionStart = from; |
− | this.text.selectionEnd = to; | + | this.text.selectionEnd = to; |
| } | | } |
− | } else if (this.text.createTextRange) { // IE | + | } else if ( this.text.createTextRange ) { // IE |
| var new_selection = this.text.createTextRange(); | | var new_selection = this.text.createTextRange(); |
− | new_selection.move ('character', from); | + | new_selection.move( 'character', from ); |
− | new_selection.moveEnd ('character', to - from); | + | new_selection.moveEnd( 'character', to - from ); |
| new_selection.select(); | | new_selection.select(); |
| } | | } |
| }, | | }, |
| | | |
− | getSelection : function () { | + | getSelection: function () { |
− | var from = 0, to = 0; | + | var from = 0, |
| + | to = 0; |
| // this.text must be focused (at least on IE) | | // this.text must be focused (at least on IE) |
− | if (!this.text.value) { | + | if ( !this.text.value ) { |
| // No text. | | // No text. |
− | } else if (typeof this.text.selectionStart != 'undefined') { | + | } else if ( this.text.selectionStart !== undefined ) { |
| from = this.text.selectionStart; | | from = this.text.selectionStart; |
− | to = this.text.selectionEnd; | + | to = this.text.selectionEnd; |
− | } else if (document.selection && document.selection.createRange) { // IE | + | } else if ( document.selection && document.selection.createRange ) { // IE |
| var rng = document.selection.createRange().duplicate(); | | var rng = document.selection.createRange().duplicate(); |
− | if (rng.parentElement() === this.text) { | + | if ( rng.parentElement() === this.text ) { |
| try { | | try { |
| var textRng = this.text.createTextRange(); | | var textRng = this.text.createTextRange(); |
− | textRng.move('character', 0); | + | textRng.move( 'character', 0 ); |
− | textRng.setEndPoint('EndToEnd', rng); | + | textRng.setEndPoint( 'EndToEnd', rng ); |
| // We're in a single-line input box: no need to care about IE's strange | | // We're in a single-line input box: no need to care about IE's strange |
| // handling of line ends | | // handling of line ends |
| to = textRng.text.length; | | to = textRng.text.length; |
− | textRng.setEndPoint('EndToStart', rng); | + | textRng.setEndPoint( 'EndToStart', rng ); |
| from = textRng.text.length; | | from = textRng.text.length; |
− | } catch (notFocused) { | + | } catch ( notFocused ) { |
− | from = this.text.value.length; to = from; // At end of text | + | from = this.text.value.length; |
| + | to = from; // At end of text |
| } | | } |
| } | | } |
| } | | } |
− | return {start: from, end: to}; | + | return { |
| + | start: from, |
| + | end: to |
| + | }; |
| }, | | }, |
| | | |
− | saveView : function (evt) { | + | saveView: function () { |
− | this.lastSelection = this.getSelection (); | + | this.lastSelection = this.getSelection(); |
| }, | | }, |
| | | |
− | processKey : function (evt) { | + | processKey: function ( evt ) { |
| var dir = 0; | | var dir = 0; |
− | switch (this.lastKey) { | + | switch ( this.lastKey ) { |
− | case UP: dir = -1; | + | case UP: |
− | case DOWN: if (dir === 0) dir = 1; | + | dir = -1; |
− | case PGUP: if (dir === 0) dir = -HotCat.list_size; | + | break; |
− | case PGDOWN: if (dir === 0) dir = HotCat.list_size; | + | case DOWN: |
− | if (this.list.style.display != 'none') { | + | dir = 1; |
− | // List is visible, so there are suggestions
| + | break; |
− | this.highlightSuggestion (dir);
| + | case PGUP: |
− | // Kill the event, otherwise some browsers (e.g., Firefox) may additionally treat an up-arrow
| + | dir = -HC.listSize; |
− | // as "place the text cursor at the front", which we don't want here.
| + | break; |
− | return evtKill (evt);
| + | case PGDOWN: |
− | } else if ( this.keyCount <= 1
| + | dir = HC.listSize; |
− | && (!this.callbackObj || this.callbackObj.callsMade == this.callbackObj.nofCalls)
| |
− | )
| |
− | {
| |
− | // If no suggestions displayed, get them, unless we're already getting them.
| |
− | this.textchange ();
| |
− | }
| |
| break; | | break; |
| case ESC: // Inhibit default behavior (revert to last real input in FF: we do that ourselves) | | case ESC: // Inhibit default behavior (revert to last real input in FF: we do that ourselves) |
− | return evtKill (evt); | + | return evtKill( evt ); |
| + | } |
| + | if ( dir ) { |
| + | if ( this.list.style.display !== 'none' ) { |
| + | // List is visible, so there are suggestions |
| + | this.highlightSuggestion( dir ); |
| + | // Kill the event, otherwise some browsers (e.g., Firefox) may additionally treat an up-arrow |
| + | // as "place the text cursor at the front", which we don't want here. |
| + | return evtKill( evt ); |
| + | } else if ( |
| + | this.keyCount <= 1 && |
| + | ( !this.callbackObj || this.callbackObj.callsMade === this.callbackObj.nofCalls ) |
| + | ) { |
| + | // If no suggestions displayed, get them, unless we're already getting them. |
| + | this.textchange(); |
| + | } |
| } | | } |
| return true; | | return true; |
| }, | | }, |
| | | |
− | highlightSuggestion : function (dir) { | + | highlightSuggestion: function ( dir ) { |
− | if (noSuggestions || !this.list || this.list.style.display == 'none') return false; | + | if ( noSuggestions || !this.list || this.list.style.display === 'none' ) return false; |
| + | |
| var curr = this.list.selectedIndex; | | var curr = this.list.selectedIndex; |
− | var tgt = -1; | + | var tgt = -1; |
− | if (dir === 0) { | + | if ( dir === 0 ) { |
− | if (curr < 0 || curr >= this.list.options.length) return false; | + | if ( curr < 0 || curr >= this.list.options.length ) return false; |
| + | |
| tgt = curr; | | tgt = curr; |
| } else { | | } else { |
| tgt = curr < 0 ? 0 : curr + dir; | | tgt = curr < 0 ? 0 : curr + dir; |
| tgt = tgt < 0 ? 0 : tgt; | | tgt = tgt < 0 ? 0 : tgt; |
− | if (tgt >= this.list.options.length) tgt = this.list.options.length - 1; | + | if ( tgt >= this.list.options.length ) tgt = this.list.options.length - 1; |
| } | | } |
− | if (tgt != curr || dir === 0) { | + | if ( tgt !== curr || dir === 0 ) { |
− | if (curr >= 0 && curr < this.list.options.length && dir !== 0) this.list.options[curr].selected = false; | + | if ( curr >= 0 && curr < this.list.options.length && dir !== 0 ) this.list.options[ curr ].selected = false; |
− | this.list.options[tgt].selected = true; | + | |
| + | this.list.options[ tgt ].selected = true; |
| // Get current input text | | // Get current input text |
− | var v = this.text.value.split('|'); | + | var v = this.text.value.split( '|' ); |
− | var key = v.length > 1 ? '|' + v[1] : ""; | + | var key = v.length > 1 ? '|' + v[ 1 ] : ''; |
− | var completed = this.autoComplete (this.list.options[tgt].text, this.lastRealInput, null, key, false); | + | var completed = this.autoComplete( this.list.options[ tgt ].text, this.lastRealInput, null, key, false ); |
− | if (!completed || this.list.options[tgt].text == this.lastRealInput) { | + | if ( !completed || this.list.options[ tgt ].text === this.lastRealInput ) { |
− | this.text.value = this.list.options[tgt].text + key; | + | 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); | + | if ( this.canSelect() ) this.setSelection( this.list.options[ tgt ].text.length, this.list.options[ tgt ].text.length ); |
| } | | } |
− | this.lastInput = this.list.options[tgt].text; | + | this.lastInput = this.list.options[ tgt ].text; |
| this.inputExists = true; // Might be wrong if from a dab list... | | this.inputExists = true; // Might be wrong if from a dab list... |
− | if (this.icon) this.icon.src = armorUri(HotCat.existsYes); | + | if ( this.icon ) this.icon.src = HC.existsYes; |
| + | |
| this.state = CategoryEditor.CHANGE_PENDING; | | this.state = CategoryEditor.CHANGE_PENDING; |
| } | | } |
Line 2,487: |
Line 2,638: |
| }, | | }, |
| | | |
− | resetKeySelection : function () { | + | resetKeySelection: function () { |
− | if (noSuggestions || !this.list || this.list.style.display == 'none') return false; | + | if ( noSuggestions || !this.list || this.list.style.display === 'none' ) return false; |
| + | |
| var curr = this.list.selectedIndex; | | var curr = this.list.selectedIndex; |
− | if (curr >= 0 && curr < this.list.options.length) { | + | if ( curr >= 0 && curr < this.list.options.length ) { |
− | this.list.options[curr].selected = false; | + | this.list.options[ curr ].selected = false; |
| // Get current input text | | // Get current input text |
− | var v = this.text.value.split('|'); | + | var v = this.text.value.split( '|' ); |
− | var key = v.length > 1 ? '|' + v[1] : ""; | + | var key = v.length > 1 ? '|' + v[ 1 ] : ''; |
| // ESC is handled strangely by some browsers (e.g., FF); somehow it resets the input value before | | // ESC is handled strangely by some browsers (e.g., FF); somehow it resets the input value before |
| // our event handlers ever get a chance to run. | | // our event handlers ever get a chance to run. |
− | var result = v[0] != this.lastInput; | + | var result = v[ 0 ] !== this.lastInput; |
− | if (v[0] != this.lastRealInput) { | + | if ( v[ 0 ] !== this.lastRealInput ) { |
| this.text.value = this.lastRealInput + key; | | this.text.value = this.lastRealInput + key; |
| result = true; | | result = true; |
Line 2,507: |
Line 2,659: |
| return false; | | return false; |
| } | | } |
− |
| |
| }; // end CategoryEditor.prototype | | }; // end CategoryEditor.prototype |
| | | |
− | function initialize () { | + | function initialize() { |
− | // User configurations. Do this here, called from the onload handler, so that users can | + | // 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 | | // override it easily in their own user script files by just declaring variables. JSconfig |
| // is some feature used at Wikimedia Commons. | | // is some feature used at Wikimedia Commons. |
− | var config = (typeof JSconfig != 'undefined' && JSconfig.keys) ? JSconfig.keys : {}; | + | var config = ( window.JSconfig !== undefined && JSconfig.keys ) ? JSconfig.keys : {}; |
− | HotCat.dont_add_to_watchlist = | + | HC.dont_add_to_watchlist = ( window.hotcat_dont_add_to_watchlist !== undefined ? |
− | (typeof window.hotcat_dont_add_to_watchlist != 'undefined'
| + | !!window.hotcat_dont_add_to_watchlist : |
− | ? !!window.hotcat_dont_add_to_watchlist
| + | ( config.HotCatDontAddToWatchlist !== undefined ? config.HotCatDontAddToWatchlist : |
− | : (typeof config.HotCatDontAddToWatchlist != 'undefined'
| + | HC.dont_add_to_watchlist ) ); |
− | ? config.HotCatDontAddToWatchlist
| + | HC.no_autocommit = ( window.hotcat_no_autocommit !== undefined ? |
− | : HotCat.dont_add_to_watchlist
| + | !!window.hotcat_no_autocommit : ( config.HotCatNoAutoCommit !== undefined ? |
− | )
| + | config.HotCatNoAutoCommit : |
− | );
| + | // On talk namespace default autocommit off |
− | HotCat.no_autocommit = | + | ( conf.wgNamespaceNumber % 2 ? |
− | (typeof window.hotcat_no_autocommit != 'undefined'
| + | true : HC.no_autocommit ) ) ); |
− | ? !!window.hotcat_no_autocommit
| + | HC.del_needs_diff = ( window.hotcat_del_needs_diff !== undefined ? |
− | : (typeof config.HotCatNoAutoCommit != 'undefined'
| + | !!window.hotcat_del_needs_diff : |
− | ? config.HotCatNoAutoCommit
| + | ( config.HotCatDelNeedsDiff !== undefined ? |
− | : HotCat.no_autocommit | + | config.HotCatDelNeedsDiff : |
− | )
| + | HC.del_needs_diff ) ); |
− | );
| + | HC.suggest_delay = window.hotcat_suggestion_delay || config.HotCatSuggestionDelay || HC.suggest_delay; |
− | HotCat.del_needs_diff = | + | HC.editbox_width = window.hotcat_editbox_width || config.HotCatEditBoxWidth || HC.editbox_width; |
− | (typeof window.hotcat_del_needs_diff != 'undefined'
| + | HC.suggestions = window.hotcat_suggestions || config.HotCatSuggestions || HC.suggestions; |
− | ? !!window.hotcat_del_needs_diff
| + | if ( typeof HC.suggestions !== 'string' || !suggestionConfigs[ HC.suggestions ] ) HC.suggestions = 'combined'; |
− | : (typeof config.HotCatDelNeedsDiff != 'undefined'
| + | |
− | ? config.HotCatDelNeedsDiff
| + | HC.fixed_search = ( window.hotcat_suggestions_fixed !== undefined ? |
− | : HotCat.del_needs_diff
| + | !!window.hotcat_suggestions_fixed : ( config.HotCatFixedSuggestions !== undefined ? |
− | )
| + | config.HotCatFixedSuggestions : HC.fixed_search ) ); |
− | );
| + | HC.single_minor = ( window.hotcat_single_changes_are_minor !== undefined ? |
− | HotCat.suggest_delay = window.hotcat_suggestion_delay | + | !!window.hotcat_single_changes_are_minor : |
− | || config['HotCatSuggestionDelay']
| + | ( config.HotCatMinorSingleChanges !== undefined ? |
− | || HotCat.suggest_delay;
| + | config.HotCatMinorSingleChanges : |
− | HotCat.editbox_width = window.hotcat_editbox_width | + | HC.single_minor ) ); |
− | || config['HotCatEditBoxWidth']
| + | HC.bg_changed = window.hotcat_changed_background || config.HotCatChangedBackground || HC.bg_changed; |
− | || HotCat.editbox_width;
| + | HC.use_up_down = ( window.hotcat_use_category_links !== undefined ? |
− | HotCat.suggestions = window.hotcat_suggestions | + | !!window.hotcat_use_category_links : |
− | || config['HotCatSuggestions']
| + | ( config.HotCatUseCategoryLinks !== undefined ? |
− | || HotCat.suggestions;
| + | config.HotCatUseCategoryLinks : |
− | if (typeof HotCat.suggestions != 'string' || !suggestionConfigs[HotCat.suggestions]) | + | HC.use_up_down ) ); |
− | HotCat.suggestions = 'combined';
| + | HC.listSize = window.hotcat_list_size || config.HotCatListSize || HC.listSize; |
− | HotCat.fixed_search = | + | if ( conf.wgDBname !== 'commonswiki' ) HC.changeTag = config.HotCatChangeTag || ''; |
− | (typeof window.hotcat_suggestions_fixed != 'undefined'
| + | |
− | ? !!window.hotcat_suggestions_fixed
| + | // The next whole shebang is needed, because manual tags get not submitted except of save |
− | : (typeof config.HotCatFixedSuggestions != 'undefined'
| + | if ( HC.changeTag ) { |
− | ? config.HotCatFixedSuggestions
| + | var eForm = document.editform, |
− | : HotCat.fixed_search
| + | catRegExp = new RegExp( '^\\[\\[(' + HC.category_regexp + '):' ), |
− | )
| + | oldTxt; |
− | );
| + | // Returns true if minor change |
− | HotCat.single_minor = | + | var isMinorChange = function () { |
− | (typeof window.hotcat_single_changes_are_minor != 'undefined'
| + | var newTxt = eForm.wpTextbox1; |
− | ? !!window.hotcat_single_changes_are_minor
| + | if ( !newTxt ) return; |
− | : (typeof config.HotCatMinorSingleChanges != 'undefined'
| + | newTxt = newTxt.value; |
− | ? config.HotCatMinorSingleChanges
| + | var oldLines = oldTxt.match( /^.*$/gm ), |
− | : HotCat.single_minor
| + | newLines = newTxt.match( /^.*$/gm ), |
− | )
| + | cArr; // changes |
− | );
| + | var except = function ( aArr, bArr ) { |
− | HotCat.bg_changed = window.hotcat_changed_background | + | var result = [], |
− | || config.HotCatChangedBackground
| + | lArr, // larger |
− | || HotCat.bg_changed;
| + | sArr; // smaller |
− | HotCat.use_up_down = | + | if ( aArr.length < bArr.length ) { |
− | (typeof window.hotcat_use_category_links != 'undefined'
| + | lArr = bArr; |
− | ? !!window.hotcat_use_category_links
| + | sArr = aArr; |
− | : (typeof config.HotCatUseCategoryLinks != 'undefined'
| + | } else { |
− | ? config.HotCatUseCategoryLinks
| + | lArr = aArr; |
− | : HotCat.use_up_down
| + | sArr = bArr; |
− | )
| + | } |
− | );
| + | for ( var i = 0; i < lArr.length; i++ ) { |
− | HotCat.list_size = window.hotcat_list_size | + | var item = lArr[ i ]; |
− | || config.HotCatListSize
| + | var ind = $.inArray( item, sArr ); |
− | || HotCat.list_size; | + | 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 | | // Numeric input, make sure we have a numeric value |
− | HotCat.list_size = parseInt (HotCat.list_size, 10); | + | HC.listSize = parseInt( HC.listSize, 10 ); |
− | if (isNaN (HotCat.list_size) || HotCat.list_size < 5) HotCat.list_size = 5; | + | if ( isNaN( HC.listSize ) || HC.listSize < 5 ) HC.listSize = 5; |
− | if (HotCat.list_size > 15) HotCat.list_size = 15; | + | |
| + | HC.listSize = Math.min( HC.listSize, 30 ); // Max size |
| + | |
| // Localize search engine names | | // Localize search engine names |
− | if (HotCat.engine_names) { | + | if ( HC.engine_names ) { |
− | for (var key in HotCat.engine_names) { | + | for ( var key in HC.engine_names ) |
− | if (suggestionConfigs[key] && HotCat.engine_names[key]) { | + | if ( suggestionConfigs[ key ] && HC.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]] | | // Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]] |
− | is_rtl = hasClass (document.body, 'rtl'); | + | is_rtl = hasClass( document.body, 'rtl' ); |
− | if (!is_rtl) { | + | if ( !is_rtl ) { |
− | if (document.defaultView && document.defaultView.getComputedStyle) { // Gecko etc. | + | if ( document.defaultView && document.defaultView.getComputedStyle ) { // Gecko etc. |
− | is_rtl = document.defaultView.getComputedStyle (document.body, null).getPropertyValue ('direction'); | + | is_rtl = document.defaultView.getComputedStyle( document.body, null ).getPropertyValue( 'direction' ); |
− | } else if (document.body.currentStyle) { // IE, has subtle differences to getComputedStyle | + | } else if ( document.body.currentStyle ) { // IE, has subtle differences to getComputedStyle |
− | is_rtl = document.body.currentStyle['direction']; | + | is_rtl = document.body.currentStyle.direction; |
| } else { // Not exactly right, but best effort | | } else { // Not exactly right, but best effort |
− | is_rtl = document.body.style['direction']; | + | is_rtl = document.body.style.direction; |
| } | | } |
− | is_rtl = (is_rtl == 'rtl'); | + | is_rtl = ( is_rtl === 'rtl' ); |
| } | | } |
| } | | } |
| | | |
− | function can_edit () { | + | function can_edit() { |
| var container = null; | | var container = null; |
− | switch (mw.config.get('skin')) { | + | switch ( mw.config.get( 'skin' ) ) { |
| case 'cologneblue': | | case 'cologneblue': |
− | container = document.getElementById ('quickbar'); | + | container = document.getElementById( 'quickbar' ); |
− | // Fall through
| + | /* fall through */ |
| case 'standard': | | case 'standard': |
| case 'nostalgia': | | case 'nostalgia': |
− | if (!container) container = document.getElementById ('topbar'); | + | if ( !container ) container = document.getElementById( 'topbar' ); |
− | var lks = container.getElementsByTagName ('a'); | + | var lks = container.getElementsByTagName( 'a' ); |
− | for (var i = 0; i < lks.length; i++) { | + | for ( var i = 0; i < lks.length; i++ ) { |
− | if ( param ('title', lks[i].href) == conf.wgPageName | + | if ( |
− | && param ('action', lks[i].href) == 'edit') | + | param( 'title', lks[ i ].href ) === conf.wgPageName && |
| + | param( 'action', lks[ i ].href ) === 'edit' |
| + | ) { |
| return true; | | return true; |
| + | } |
| } | | } |
| return false; | | return false; |
| default: | | default: |
| // all modern skins: | | // all modern skins: |
− | return document.getElementById ('ca-edit') !== null; | + | 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(); |
| + | } |
| + | } |
| } | | } |
− | return false;
| |
| } | | } |
| | | |
− | function setup_upload () { | + | function setup_upload() { |
| onUpload = true; | | onUpload = true; |
| // Add an empty category bar at the end of the table containing the description, and change the onsubmit handler. | | // Add an empty category bar at the end of the table containing the description, and change the onsubmit handler. |
− | var ip = document.getElementById ('mw-htmlform-description') || document.getElementById ('wpDestFile'); | + | var ip = document.getElementById( 'mw-htmlform-description' ) || document.getElementById( 'wpDestFile' ); |
− | if (!ip) { | + | if ( !ip ) { |
− | ip = document.getElementById ('wpDestFile'); | + | ip = document.getElementById( 'wpDestFile' ); |
− | while (ip && ip.nodeName.toLowerCase() != 'table') ip = ip.parentNode; | + | while ( ip && ip.nodeName.toLowerCase() !== 'table' ) ip = ip.parentNode; |
| } | | } |
− | if (!ip) return; | + | if ( !ip ) return; |
− | var reupload = document.getElementById ('wpForReUpload'); | + | var reupload = document.getElementById( 'wpForReUpload' ); |
− | var destFile = document.getElementById ('wpDestFile'); | + | var destFile = document.getElementById( 'wpDestFile' ); |
− | if ( (reupload && !!reupload.value) | + | if ( |
− | || (destFile && (destFile.disabled || destFile.readOnly))) | + | ( reupload && !!reupload.value ) || |
| + | ( destFile && ( destFile.disabled || destFile.readOnly ) ) |
| + | ) { |
| return; // re-upload form... | | return; // re-upload form... |
| + | } |
| // Insert a table row with two fields (label and empty category bar) | | // Insert a table row with two fields (label and empty category bar) |
− | var labelCell = make ('td'); | + | var labelCell = make( 'td' ); |
− | var lineCell = make ('td'); | + | var lineCell = make( 'td' ); |
| // Create the category line | | // Create the category line |
− | catLine = make ('div'); | + | catLine = make( 'div' ); |
| catLine.className = 'catlinks'; | | catLine.className = 'catlinks'; |
| catLine.id = 'catlinks'; | | catLine.id = 'catlinks'; |
Line 2,654: |
Line 2,879: |
| catLine.style.margin = '0'; | | catLine.style.margin = '0'; |
| catLine.style.border = 'none'; | | catLine.style.border = 'none'; |
− | lineCell.appendChild (catLine); | + | lineCell.appendChild( catLine ); |
| // Create the label | | // Create the label |
| var label = null; | | var label = null; |
− | if ( typeof UFUI != 'undefined' | + | if ( window.UFUI && window.UIElements && UFUI.getLabel instanceof Function ) { |
− | && typeof UIElements != 'undefined'
| |
− | && typeof UFUI.getLabel == 'function'
| |
− | )
| |
− | {
| |
| try { | | try { |
− | label = UFUI.getLabel('wpCategoriesUploadLbl'); | + | label = UFUI.getLabel( 'wpCategoriesUploadLbl' ); |
− | } catch (ex) { | + | } catch ( ex ) { |
| label = null; | | label = null; |
| } | | } |
| } | | } |
− | if (!label) { | + | if ( !label ) { |
| labelCell.id = 'hotcatLabel'; | | labelCell.id = 'hotcatLabel'; |
− | labelCell.appendChild (make (HotCat.categories, true)); | + | labelCell.appendChild( make( HC.categories, true ) ); |
| } else { | | } else { |
| labelCell.id = 'hotcatLabelTranslated'; | | labelCell.id = 'hotcatLabelTranslated'; |
− | labelCell.appendChild (label); | + | labelCell.appendChild( label ); |
| } | | } |
− | labelCell.className = 'mw-label'; | + | labelCell.className = 'mw-label'; |
− | labelCell.style.textAlign = 'right'; | + | labelCell.style.textAlign = 'right'; |
| labelCell.style.verticalAlign = 'middle'; | | labelCell.style.verticalAlign = 'middle'; |
| // Change the onsubmit handler | | // Change the onsubmit handler |
− | var form = document.getElementById('upload') || document.getElementById('mw-upload-form'); | + | var form = document.getElementById( 'upload' ) || document.getElementById( 'mw-upload-form' ); |
− | if (form) { | + | if ( form ) { |
− | var newRow = ip.insertRow (-1); | + | var newRow = ip.insertRow( -1 ); |
− | newRow.appendChild (labelCell); | + | newRow.appendChild( labelCell ); |
− | newRow.appendChild (lineCell); | + | newRow.appendChild( lineCell ); |
− | form.onsubmit = (function (oldSubmit) { | + | form.onsubmit = ( function ( oldSubmit ) { |
| return function () { | | return function () { |
| var do_submit = true; | | var do_submit = true; |
− | if (oldSubmit) { | + | if ( oldSubmit ) { |
− | if (typeof oldSubmit == 'string') | + | if ( typeof oldSubmit === 'string' ) { |
− | do_submit = eval (oldSubmit); | + | // eslint-disable-next-line no-eval |
− | else if (typeof oldSubmit == 'function') | + | do_submit = eval( oldSubmit ); |
− | do_submit = oldSubmit.apply (form, arguments); | + | } else if ( oldSubmit instanceof Function ) { |
| + | do_submit = oldSubmit.apply( form, arguments ); |
| + | } |
| } | | } |
− | if (!do_submit) return false; | + | if ( !do_submit ) return false; |
− | closeForm (); | + | closeForm(); |
| // Copy the categories | | // Copy the categories |
− | var eb = document.getElementById ('wpUploadDescription') | + | var eb = document.getElementById( 'wpUploadDescription' ) || document.getElementById( 'wpDesc' ); |
− | || document.getElementById ('wpDesc');
| |
| var addedOne = false; | | var addedOne = false; |
− | for (var i = 0; i < editors.length; i++) { | + | for ( var i = 0; i < editors.length; i++ ) { |
− | var t = editors[i].currentCategory; | + | var t = editors[ i ].currentCategory; |
− | if (!t) continue ; | + | if ( !t ) continue; |
− | var key = editors[i].currentKey; | + | var key = editors[ i ].currentKey; |
− | var new_cat = '[[' + HotCat.category_canonical + ':' + t + (key ? '|' + key : "") + ']]'; | + | var new_cat = '[[' + HC.category_canonical + ':' + t + ( key ? '|' + key : '' ) + ']]'; |
| // Only add if not already present | | // Only add if not already present |
| var cleanedText = eb.value | | var cleanedText = eb.value |
− | .replace(/<\!--(\s|\S)*?--\>/g, "")
| + | .replace( /<!--(\s|\S)*?-->/g, '' ) |
− | .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, "");
| + | .replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, '' ); |
− | if (!find_category (cleanedText, t, true)) { | + | if ( !find_category( cleanedText, t, true ) ) { |
| eb.value += '\n' + new_cat; | | eb.value += '\n' + new_cat; |
| addedOne = true; | | addedOne = true; |
| } | | } |
| } | | } |
− | if (addedOne) { | + | if ( addedOne ) { |
− | // Remove "subst:unc" added by Flinfo if it didn't find categories
| + | // Remove "subst:unc" added by Flinfo if it didn't find categories |
− | eb.value = eb.value.replace(/\{\{subst:unc\}\}/g, ""); | + | eb.value = eb.value.replace( /\{\{subst:unc\}\}/g, '' ); |
| } | | } |
| return true; | | return true; |
| }; | | }; |
− | }) (form.onsubmit); | + | }( form.onsubmit ) ); |
| } | | } |
| } | | } |
Line 2,725: |
Line 2,947: |
| var cleanedText = null; | | var cleanedText = null; |
| | | |
− | function isOnPage (span) { | + | function isOnPage( span ) { |
− | var catTitle = title (span.firstChild.getAttribute ('href', 2)); | + | if ( span.firstChild.nodeType !== Node.ELEMENT_NODE ) return null; |
− | if (!catTitle) return null; | + | |
− | catTitle = catTitle.substr (catTitle.indexOf (':') + 1).replace (/_/g, ' '); | + | var catTitle = title( span.firstChild.getAttribute( 'href' ) ); |
− | if (HotCat.blacklist && HotCat.blacklist.test (catTitle)) return null; | + | if ( !catTitle ) return null; |
− | var result = { title : catTitle, match : ["", "", ""] }; | + | |
− | if (pageText === null) return result; | + | catTitle = catTitle.substr( catTitle.indexOf( ':' ) + 1 ).replace( /_/g, ' ' ); |
− | if (cleanedText === null) { | + | if ( HC.blacklist && HC.blacklist.test( catTitle ) ) return null; |
| + | |
| + | var result = { |
| + | title: catTitle, |
| + | match: [ '', '', '' ] |
| + | }; |
| + | if ( pageText === null ) return result; |
| + | |
| + | if ( cleanedText === null ) { |
| cleanedText = pageText | | cleanedText = pageText |
− | .replace(/<\!--(\s|\S)*?--\>/g, "") | + | .replace( /<!--(\s|\S)*?-->/g, '' ) |
− | .replace(/<nowiki\>(\s|\S)*?<\/nowiki>/g, ""); | + | .replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, '' ); |
| } | | } |
− | result.match = find_category (cleanedText, catTitle, true); | + | result.match = find_category( cleanedText, catTitle, true ); |
| return result; | | return result; |
| } | | } |
Line 2,744: |
Line 2,974: |
| var setupTimeout = null; | | var setupTimeout = null; |
| | | |
− | function findByClass (scope, tag, className) { | + | function findByClass( scope, tag, className ) { |
− | // Compatibility routine. Uses jQuery if available, otherwise works with older getElementsByClassName
| + | var result = $( scope ).find( tag + '.' + className ); |
− | var result; | + | return ( result && result.length ) ? result[ 0 ] : null; |
− | if (window.jQuery) {
| |
− | result = window.jQuery(scope).find(tag + '.' + className);
| |
− | } else {
| |
− | result = getElementsByClassName(scope, tag, className);
| |
− | }
| |
− | return (result && result.length) ? result[0] : null; | |
| } | | } |
| | | |
− | function setup (additionalWork) { | + | function setup( additionalWork ) { |
− | if (initialized) return; | + | if ( initialized ) return; |
| initialized = true; | | initialized = true; |
− | if (setupTimeout) { | + | if ( setupTimeout ) { |
− | window.clearTimeout (setupTimeout); | + | window.clearTimeout( setupTimeout ); |
| setupTimeout = null; | | setupTimeout = null; |
| } | | } |
| // Find the category bar, or create an empty one if there isn't one. Then add -/+- links after | | // Find the category bar, or create an empty one if there isn't one. Then add -/+- links after |
| // each category, and add the + link. | | // each category, and add the + link. |
− | catLine = catLine // Special:Upload | + | catLine = |
− | || document.getElementById ('mw-normal-catlinks') // MW >= 1.13alpha
| + | // Special:Upload |
− | || findByClass (document , 'p' , 'catlinks'); // MW < 1.13
| + | catLine || |
− | var hiddenCats = document.getElementById ('mw-hidden-catlinks'); | + | document.getElementById( 'mw-normal-catlinks' ); |
− | if (!catLine) { | + | var hiddenCats = document.getElementById( 'mw-hidden-catlinks' ); |
| + | if ( !catLine ) { |
| var footer = null; | | var footer = null; |
− | if (!hiddenCats) { | + | if ( !hiddenCats ) { |
− | footer = findByClass (document , 'div' , 'printfooter'); | + | footer = findByClass( document, 'div', 'printfooter' ); |
− | if (!footer) return; // Don't know where to insert the category line | + | if ( !footer ) return; // Don't know where to insert the category line |
| } | | } |
− | catLine = make ('div'); | + | catLine = make( 'div' ); |
| catLine.id = 'mw-normal-catlinks'; | | catLine.id = 'mw-normal-catlinks'; |
| catLine.style.textAlign = is_rtl ? 'right' : 'left'; | | catLine.style.textAlign = is_rtl ? 'right' : 'left'; |
| // Add a label | | // Add a label |
− | var label = make ('a'); | + | var label = make( 'a' ); |
− | label.href = conf.wgArticlePath.replace ('$1', 'Special:Categories'); | + | label.href = conf.wgArticlePath.replace( '$1', 'Special:Categories' ); |
− | label.title = HotCat.categories; | + | label.title = HC.categories; |
− | label.appendChild (make (HotCat.categories, true)); | + | label.appendChild( make( HC.categories, true ) ); |
− | catLine.appendChild (label); | + | catLine.appendChild( label ); |
− | catLine.appendChild (make (':', true)); | + | catLine.appendChild( make( ':', true ) ); |
| // Insert the new category line | | // Insert the new category line |
− | var container = (hiddenCats ? hiddenCats.parentNode : document.getElementById ('catlinks')); | + | var container = ( hiddenCats ? hiddenCats.parentNode : document.getElementById( 'catlinks' ) ); |
− | if (!container) { | + | if ( !container ) { |
− | container = make ('div'); | + | container = make( 'div' ); |
| container.id = 'catlinks'; | | container.id = 'catlinks'; |
− | footer.parentNode.insertBefore (container, footer.nextSibling); | + | footer.parentNode.insertBefore( container, footer.nextSibling ); |
| } | | } |
| container.className = 'catlinks noprint'; | | container.className = 'catlinks noprint'; |
− | container.style.display = ""; | + | container.style.display = ''; |
− | if (!hiddenCats) { | + | if ( !hiddenCats ) container.appendChild( catLine ); else container.insertBefore( catLine, hiddenCats ); |
− | container.appendChild (catLine);
| |
− | } else {
| |
− | container.insertBefore (catLine, hiddenCats);
| |
− | }
| |
| } // end if catLine exists | | } // end if catLine exists |
− | if (is_rtl) catLine.dir = 'rtl'; | + | if ( is_rtl ) catLine.dir = 'rtl'; |
| | | |
| // Create editors for all existing categories | | // Create editors for all existing categories |
| | | |
− | function createEditors (line, is_hidden) { | + | function createEditors( line, is_hidden ) { |
| var i; | | var i; |
− | var cats = line.getElementsByTagName ('li'); | + | var cats = line.getElementsByTagName( 'li' ); |
− | if (cats.length > 0) { | + | if ( cats.length ) { |
− | newDOM = true; line = cats[0].parentNode; | + | newDOM = true; |
| + | line = cats[ 0 ].parentNode; |
| } else { | | } else { |
− | cats = line.getElementsByTagName ('span'); | + | cats = line.getElementsByTagName( 'span' ); |
| } | | } |
| // Copy cats, otherwise it'll also magically contain our added spans as it is a live collection! | | // Copy cats, otherwise it'll also magically contain our added spans as it is a live collection! |
− | var copyCats = new Array (cats.length); | + | var copyCats = new Array( cats.length ); |
− | for (i = 0; i < cats.length; i++) copyCats[i] = cats[i]; | + | for ( i = 0; i < cats.length; i++ ) copyCats[ i ] = cats[ i ]; |
− | var editor = null;
| + | for ( i = 0; i < copyCats.length; i++ ) { |
− | for (i = 0; i < copyCats.length; i++) { | + | var test = isOnPage( copyCats[ i ] ); |
− | var test = isOnPage (copyCats[i]); | + | if ( test !== null && test.match !== null && line ) { |
− | if (test !== null && test.match !== null) { | + | // eslint-disable-next-line no-new |
− | editor = new CategoryEditor (line, copyCats[i], test.title, test.match[2], is_hidden); | + | new CategoryEditor( line, copyCats[ i ], test.title, test.match[ 2 ], is_hidden ); |
| } | | } |
| } | | } |
− | return copyCats.length > 0 ? copyCats[copyCats.length-1] : null; | + | return copyCats.length ? copyCats[ copyCats.length - 1 ] : null; |
| } | | } |
| | | |
− | var lastSpan = createEditors (catLine, false); | + | var lastSpan = createEditors( catLine, false ); |
| // Create one to add a new category | | // Create one to add a new category |
− | var editor = new CategoryEditor(newDOM ? catLine.getElementsByTagName('ul')[0] : catLine, null, null, lastSpan !== null, false); | + | // eslint-disable-next-line no-new |
− | if (!onUpload) { | + | new CategoryEditor( newDOM ? catLine.getElementsByTagName( 'ul' )[ 0 ] : catLine, null, null, lastSpan !== null, false ); |
− | if (pageText !== null && hiddenCats) { | + | if ( !onUpload ) { |
− | if (is_rtl) hiddenCats.dir = 'rtl'; | + | if ( pageText !== null && hiddenCats ) { |
− | createEditors (hiddenCats, true); | + | if ( is_rtl ) hiddenCats.dir = 'rtl'; |
| + | createEditors( hiddenCats, true ); |
| } | | } |
| // And finally add the "multi-mode" span. (Do this at the end, otherwise it ends up in the list above.) | | // And finally add the "multi-mode" span. (Do this at the end, otherwise it ends up in the list above.) |
− | var enableMulti = make ('span'); | + | var enableMulti = make( 'span' ); |
| enableMulti.className = 'noprint'; | | enableMulti.className = 'noprint'; |
− | if (is_rtl) enableMulti.dir = 'rtl'; | + | if ( is_rtl ) enableMulti.dir = 'rtl'; |
− | catLine.insertBefore (enableMulti, catLine.firstChild.nextSibling); | + | catLine.insertBefore( enableMulti, catLine.firstChild.nextSibling ); |
− | enableMulti.appendChild (make ('\xa0', true)); // nbsp | + | enableMulti.appendChild( make( '\xa0', true ) ); // nbsp |
− | multiSpan = make ('span'); | + | multiSpan = make( 'span' ); |
− | enableMulti.appendChild (multiSpan); | + | enableMulti.appendChild( multiSpan ); |
− | multiSpan.innerHTML = '(<a>' + HotCat.addmulti + '</a>)'; | + | multiSpan.innerHTML = '(<a>' + HC.addmulti + '</a>)'; |
− | var lk = multiSpan.getElementsByTagName ('a')[0]; | + | var lk = multiSpan.getElementsByTagName( 'a' )[ 0 ]; |
− | lk.onclick = function (evt) {setMultiInput (); checkMultiInput (); return evtKill (evt);}; | + | lk.onclick = function ( evt ) { |
− | lk.title = HotCat.multi_tooltip; | + | setMultiInput(); |
| + | checkMultiInput(); |
| + | return evtKill( evt ); |
| + | }; |
| + | lk.title = HC.multi_tooltip; |
| lk.style.cursor = 'pointer'; | | lk.style.cursor = 'pointer'; |
| } | | } |
| cleanedText = null; | | cleanedText = null; |
− | if (typeof additionalWork == 'function') additionalWork(); | + | if ( additionalWork instanceof Function ) additionalWork(); |
− | setupCompleted.loaded(); // Trigger signal; execute registered functions | + | mw.hook( 'hotcat.ready' ).fire(); // Execute registered callback functions |
− | if (window.jQuery) jQuery('body').trigger('hotcatSetupCompleted');
| + | $( '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" : "" + 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) {
| |
− | 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 () { | + | function createCommitForm() { |
− | if (commitForm) return; | + | if ( commitForm ) return; |
− | var formContainer = make ('div'); | + | var formContainer = make( 'div' ); |
| formContainer.style.display = 'none'; | | formContainer.style.display = 'none'; |
− | document.body.appendChild (formContainer); | + | document.body.appendChild( formContainer ); |
| formContainer.innerHTML = | | formContainer.innerHTML = |
− | '<form id="hotcatCommitForm" method="post" enctype="multipart/form-data" action="' | + | '<form id="hotcatCommitForm" method="post" enctype="multipart/form-data" action="' + |
− | + conf.wgScript + '?title=' + encodeURIComponent (conf.wgPageName) | + | conf.wgScript + '?title=' + encodeURIComponent( conf.wgPageName ) + '&action=submit">' + |
− | + '&action=edit">'
| + | '<input type="hidden" name="wpTextbox1">' + |
− | + '<input type="hidden" name="wpTextbox1" />' | + | '<input type="hidden" name="model" value="' + conf.wgPageContentModel + '">' + |
− | + '<input type="hidden" name="model" value="wikitext" />' | + | '<input type="hidden" name="format" value="text/x-wiki">' + |
− | + '<input type="hidden" name="format" value="text/x-wiki" />' | + | '<input type="hidden" name="wpSummary" value="">' + |
− | + '<input type="hidden" name="wpSummary" value="" />' | + | '<input type="checkbox" name="wpMinoredit" value="1">' + |
− | + '<input type="checkbox" name="wpMinoredit" value="1" />' | + | '<input type="checkbox" name="wpWatchthis" value="1">' + |
− | + '<input type="checkbox" name="wpWatchthis" value="1" />' | + | '<input type="hidden" name="wpAutoSummary" value="d41d8cd98f00b204e9800998ecf8427e">' + |
− | + '<input type="hidden" name="wpAutoSummary" value="" />' | + | '<input type="hidden" name="wpEdittime">' + |
− | + '<input type="hidden" name="wpEdittime" />' | + | '<input type="hidden" name="wpStarttime">' + |
− | + '<input type="hidden" name="wpStarttime" />' | + | '<input type="hidden" name="wpDiff" value="wpDiff">' + |
− | + '<input type="hidden" name="wpDiff" value="wpDiff" />' | + | '<input type="hidden" name="oldid" value="0">' + |
− | + '<input type="hidden" name="oldid" value="0" />' | + | '<input type="submit" name="hcCommit" value="hcCommit">' + |
− | + '<input type="submit" name="hcCommit" value="hcCommit" />' | + | '<input type="hidden" name="wpEditToken">' + |
− | + '<input type="hidden" name="wpEditToken" />' | + | '<input type="hidden" name="wpUltimateParam" value="1">' + |
− | + '<input type="hidden" name="wpUltimateParam" value="1" />' | + | '<input type="hidden" name="wpChangeTags">' + |
− | + '</form>'; | + | '<input type="hidden" value="ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ" name="wpUnicodeCheck">' + |
− | commitForm = document.getElementById ('hotcatCommitForm'); | + | '</form>'; |
| + | commitForm = document.getElementById( 'hotcatCommitForm' ); |
| } | | } |
| | | |
− | function getPage () { | + | function getPage() { |
| // We know we have an article here. | | // We know we have an article here. |
− | if (conf.wgArticleId === 0) { | + | if ( !conf.wgArticleId ) { |
− | // Doesn't exist yet. | + | // Doesn't exist yet. Disable on non-existing User pages -- might be a global user page. |
− | if (conf.wgNamespaceNumber === 2) {
| + | if ( conf.wgNamespaceNumber === 2 ) return; |
− | // Disable on non-existing User pages -- might be a global user page.
| + | pageText = ''; |
− | return;
| |
− | }
| |
− | pageText = ""; | |
| pageTime = null; | | pageTime = null; |
− | setup (createCommitForm); | + | setup( createCommitForm ); |
| } else { | | } else { |
− | var url = conf.wgServer + conf.wgScriptPath + '/api.php?format=json&callback=HotCat.start&action=query&rawcontinue=&titles=' | + | var url = conf.wgServer + conf.wgScriptPath + '/api.php?format=json&callback=HotCat.start&action=query&rawcontinue=&titles=' + |
− | + encodeURIComponent (conf.wgPageName)
| + | encodeURIComponent( conf.wgPageName ) + |
− | + '&prop=info%7Crevisions&rvprop=content%7Ctimestamp%7Cids&meta=siteinfo&rvlimit=1&rvstartid='
| + | '&prop=info%7Crevisions&rvprop=content%7Ctimestamp%7Cids&meta=siteinfo&rvlimit=1&rvstartid=' + |
− | + conf.wgCurRevisionId;
| + | conf.wgCurRevisionId; |
− | var s = make ('script'); | + | var s = make( 'script' ); |
− | s.src = armorUri(url); | + | s.src = url; |
− | s.type = 'text/javascript';
| + | HC.start = function ( json ) { |
− | HotCat.start = function (json) { setPage (json); setup (createCommitForm); }; | + | setPage( json ); |
− | document.getElementsByTagName ('head')[0].appendChild (s); | + | setup( createCommitForm ); |
− | setupTimeout = window.setTimeout (function () {setup (createCommitForm);}, 4000); // 4 sec, just in case getting the wikitext takes longer. | + | }; |
| + | document.getElementsByTagName( 'head' )[ 0 ].appendChild( s ); |
| + | setupTimeout = window.setTimeout( function () { |
| + | setup( createCommitForm ); |
| + | }, 4000 ); // 4 sec, just in case getting the wikitext takes longer. |
| } | | } |
| } | | } |
| | | |
− | function run () { | + | function setState( state ) { |
− | if (HotCat.started) return; | + | var cats = state.split( '\n' ); |
− | HotCat.started = true; | + | if ( !cats.length ) return null; |
− | loadTrigger.register(really_run);
| |
− | }
| |
| | | |
− | function really_run () {
| + | if ( initialized && editors.length === 1 && editors[ 0 ].isAddCategory ) { |
− | initialize ();
| + | // Insert new spans and create new editors for them. |
| + | var newSpans = []; |
| + | var before = editors.length === 1 ? editors[ 0 ].span : null; |
| + | var i; |
| + | for ( i = 0; i < cats.length; i++ ) { |
| + | if ( !cats[ i ].length ) continue; |
| + | var cat = cats[ i ].split( '|' ); |
| + | var 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( make( ' ', true ), before ); |
| | | |
− | if (is_rtl && is_ie6) return; // Disabled! IE6 with RTL is just too broken...
| + | catLine.insertBefore( span, before ); |
− | if (!HotCat.upload_disabled && conf.wgNamespaceNumber === -1 && conf.wgCanonicalSpecialPageName == 'Upload' && conf.wgUserName) {
| + | if ( before && i + 1 < cats.length ) parent.insertBefore( make( ' | ', true ), before ); |
− | 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
| + | newSpans.push( { |
| + | element: span, |
| + | title: cat, |
| + | key: key |
| + | } ); |
| + | } |
| + | // And change the last one... |
| + | if ( before ) before.parentNode.insertBefore( make( ' | ', true ), before ); |
| | | |
− | function closeForm () {
| + | for ( i = 0; i < newSpans.length; i++ ) { |
− | // Close all open editors without redirect resolution and other asynchronous stuff.
| + | // eslint-disable-next-line no-new |
− | for (var i = 0; i < editors.length; i++) {
| + | new CategoryEditor( catLine, newSpans[ i ].element, newSpans[ i ].title, newSpans[ i ].key ); |
− | 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 ();
| |
− | }
| |
| } | | } |
| } | | } |
| + | return null; |
| } | | } |
| | | |
− | function getState () { | + | function getState() { |
| var result = null; | | var result = null; |
− | for (var i = 0; i < editors.length; i++) { | + | for ( var i = 0; i < editors.length; i++ ) { |
− | var text = editors[i].currentCategory; | + | var text = editors[ i ].currentCategory; |
− | var key = editors[i].currentKey; | + | var key = editors[ i ].currentKey; |
− | if (text && text.length > 0) { | + | if ( text && text.length ) { |
− | if (key !== null) text += '|' + key; | + | if ( key !== null ) text += '|' + key; |
− | if (result === null) | + | if ( result === null ) result = text; else result += '\n' + text; |
− | result = text;
| |
− | else
| |
− | result = result + '\n' + text;
| |
| } | | } |
| } | | } |
Line 3,021: |
Line 3,188: |
| } | | } |
| | | |
− | function setState (state) { | + | function really_run() { |
− | var cats = state.split ('\n'); | + | initialize(); |
− | if (cats.length === 0) return null; | + | |
− | if (initialized && editors.length == 1 && editors[0].isAddCategory) {
| + | if ( !HC.upload_disabled && conf.wgNamespaceNumber === -1 && conf.wgCanonicalSpecialPageName === 'Upload' && conf.wgUserName ) { |
− | // Insert new spans and create new editors for them.
| + | setup_upload(); |
− | var newSpans = [];
| + | setup( function () { |
− | var before = editors.length == 1 ? editors[0].span : null;
| + | // Check for state restoration once the setup is done otherwise, but before signalling setup completion |
− | var i;
| + | if ( window.UploadForm && UploadForm.previous_hotcat_state ) UploadForm.previous_hotcat_state = setState( UploadForm.previous_hotcat_state ); |
− | for (i = 0; i < cats.length; i++) {
| + | } ); |
− | if (cats[i].length === 0) continue;
| + | } else { |
− | var cat = cats[i].split ('|');
| + | if ( !conf.wgIsArticle || conf.wgAction !== 'view' || param( 'diff' ) !== null || param( 'oldid' ) !== null || !can_edit() || HC.disable() ) return; |
− | var key = cat.length > 1 ? cat[1] : null;
| + | getPage(); |
− | cat = cat[0];
| |
− | var lk = make ('a'); lk.href = wikiPagePath (HotCat.category_canonical + ':' + cat);
| |
− | lk.appendChild (make (cat, true));
| |
− | lk.title = cat;
| |
− | var span = make ('span');
| |
− | span.appendChild (lk);
| |
− | if (i === 0) catLine.insertBefore (make (' ', true), before); | |
− | catLine.insertBefore (span, before);
| |
− | if (before && i+1 < cats.length) parent.insertBefore (make (' | ', true), before); | |
− | newSpans.push ({element: span, title: cat, 'key': key});
| |
− | }
| |
− | // And change the last one...
| |
− | if (before) { | |
− | before.parentNode.insertBefore (make (' | ', true), before);
| |
− | }
| |
− | var editor = null;
| |
− | for (i = 0; i < newSpans.length; i++) {
| |
− | editor = new CategoryEditor (catLine, newSpans[i].element, newSpans[i].title, newSpans[i].key);
| |
− | } | |
| } | | } |
− | return null; | + | } |
| + | |
| + | function run() { |
| + | if ( HC.started ) return; |
| + | HC.started = true; |
| + | loadTrigger.register( really_run ); |
| } | | } |
| | | |
| // Export legacy functions | | // Export legacy functions |
− | window.hotcat_get_state = function () { return getState(); }; | + | window.hotcat_get_state = function () { |
− | window.hotcat_set_state = function (state) { return setState (state); }; | + | return getState(); |
− | 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 |
| + | // replace HotCat). |
| + | mw.config.set( 'disableAJAXCategories', true ); |
| | | |
− | if (window.mw) {
| |
− | // Make sure we don't get conflicts with AjaxCategories (core development that should one day
| |
− | // replace HotCat).
| |
− | mw.config.set('disableAJAXCategories', true);
| |
− | }
| |
| // Run as soon as possible. This varies depending on MediaWiki version; | | // Run as soon as possible. This varies depending on MediaWiki version; |
| // window's 'load' event is always safe, but usually we can do better than that. | | // window's 'load' event is always safe, but usually we can do better than that. |
| | | |
− | // Check for version to avoid MediaWiki bug 32537.
| + | if ( conf.wgCanonicalSpecialPageName !== 'Upload' ) { |
− | var mwVersion = conf.wgVersion.split('.');
| + | // Reload HotCat after (VE) edits (bug T103285) |
− | if (mwVersion[0] >= 1 && parseFloat(mwVersion[1]) > 20) { | + | mw.hook( 'postEdit' ).add( function () { |
− | if (parseFloat(mwVersion[1]) > 21 && conf.wgCanonicalSpecialPageName !== 'Upload') {
| + | // Reset HotCat in case this is a soft reload (VE edit) |
− | // Use wikipage.content hook so that HotCat reloads after VE edits (bug T103285)
| + | catLine = null; |
− | var startHotCat = function() {
| + | editors = []; |
− | mw.hook('wikipage.content').add( function() {
| + | initialized = false; |
− | // Reset HotCat in case this is a soft reload (VE edit)
| + | HC.started = false; |
− | catLine = null;
| + | run(); |
− | editors = [];
| + | } ); |
− | initialized = false;
| |
− | HotCat.started = false;
| |
− | run ();
| |
− | } );
| |
− | };
| |
− | } else {
| |
− | // We are using MediaWiki 1.21, which doesn't support mw.hook. Fall back to dom-ready.
| |
− | // OR: We're running on Special:Upload, where the 'wikipage.content' hook is fired for
| |
− | // various previewed wikitext snippets, which shouldn't reload HotCat interface.
| |
− | var startHotCat = function() {
| |
− | jQuery(document).ready(run);
| |
− | };
| |
− | } | |
− | // We can safely trigger just after user configuration is loaded. Also start HotCat if the user module fails to load.
| |
− | // Avoid using Promise methods of mw.loader.using as those aren't supported in older
| |
− | // MediaWiki versions.
| |
− | mw.loader.using('user', startHotCat, startHotCat);
| |
− | } else {
| |
− | // mw.loader.using('user', ...) could have unintended side-effects on MW <= 1.20. Fall back to dom-ready.
| |
− | jQuery(document).ready(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> | | // </nowiki> |