跳转到内容

MediaWiki:Gadget-HotCat.js:修订间差异

勤求古训,博采众方
删除的内容 添加的内容
创建页面,内容为“/** HotCat V2.46 Ajax-based simple Category manager. Allows adding/removing/changing categories on a page view. Supports multiple category changes, as well as redirect and disambiguation resolution. Also plugs into the upload form. Search engines to use for the suggestion list are configurable, and can be selected interactively. Documentation: https://commons.wikimedia.org/wiki/Help:Gadget-HotCat List of main authors: https://commons.wikimedia.org/wiki/Help:G…”
 
无编辑摘要
 
(未显示2个用户的5个中间版本)
第1行: 第1行:
/**
/**
* SPDX-License-Identifier: CC-BY-SA-4.0
HotCat V2.46
* _addText: '{{Gadget Header|title=HotCat|license=CC-BY-SA-4.0}}'
*
* @base {@link https://commons.wikimedia.org/wiki/MediaWiki:Gadget-HotCat.js}
* @base {@link https://commons.wikimedia.org/wiki/MediaWiki:Gadget-HotCat.js/zh-hans}
* @base {@link https://zh.wikipedia.org/wiki/MediaWiki:Gadget-HotCat.js/local_defaults}
* @source {@link https://git.qiuwen.net.cn/InterfaceAdmin/QiuwenGadgets/src/branch/master/src/HotCat}
* @license CC-BY-SA-4.0 {@link https://www.qiuwenbaike.cn/wiki/H:CC-BY-SA-4.0}
*/


/**
Ajax-based simple Category manager. Allows adding/removing/changing categories on a page view.
* SPDX-License-Identifier: CC-BY-SA-4.0
Supports multiple category changes, as well as redirect and disambiguation resolution. Also
* _addText: '{{Gadget Header|title=CheckCategories HotCat Extension|license=CC-BY-SA-4.0}}'
plugs into the upload form. Search engines to use for the suggestion list are configurable, and
*
can be selected interactively.
* @base {@link https://commons.wikimedia.org/wiki/MediaWiki:Gadget-Hotcatcheck.js}
* @source {@link https://git.qiuwen.net.cn/InterfaceAdmin/QiuwenGadgets/src/branch/master/src/HotCat/HotCat-check.js}
* @license CC-BY-SA-4.0 {@link https://www.qiuwenbaike.cn/wiki/H:CC-BY-SA-4.0}
*/


/**
Documentation: https://commons.wikimedia.org/wiki/Help:Gadget-HotCat
* SPDX-License-Identifier: CC-BY-SA-3.0 + GFDL-1.3-no-invariants-or-later
List of main authors: https://commons.wikimedia.org/wiki/Help:Gadget-HotCat/Version_history
*
* P no
*
* @base {@link https://www.qiuwenbaike.cn/wiki/File:P_no.svg}
* @source {@link https://git.qiuwen.net.cn/InterfaceAdmin/QiuwenGadgets/src/branch/master/src/HotCat/images}
* @license CC-BY-SA-3.0 + GFDL-1.3-no-invariants-or-later {@link https://www.qiuwenbaike.cn/wiki/H:CC-BY-SA-3.0}
*/


/**
License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0)
* SPDX-License-Identifier: CC-BY-SA-3.0 + GFDL-1.3-no-invariants-or-later
*
* P yes
*
* @base {@link https://www.qiuwenbaike.cn/wiki/File:P_yes.svg}
* @source {@link https://git.qiuwen.net.cn/InterfaceAdmin/QiuwenGadgets/src/branch/master/src/HotCat/images}
* @license CC-BY-SA-3.0 + GFDL-1.3-no-invariants-or-later {@link https://www.qiuwenbaike.cn/wiki/H:CC-BY-SA-3.0}
*/
/**
* +------------------------------------------------------------+
* | === WARNING: GLOBAL GADGET FILE === |
* +------------------------------------------------------------+
* | All changes should be made in the repository, |
* | otherwise they will be lost. |
* +------------------------------------------------------------+
* | Changes to this page may affect many users. |
* | Please discuss changes by opening an issue before editing. |
* +------------------------------------------------------------+
*/
/* <nowiki> */


(() => {
Choose whichever license of these you like best :-)


"use strict";
This code should run on any MediaWiki installation >= MW 1.32.


// dist/HotCat/HotCat.js
For use with older versions of MediaWiki, use the archived versions below:
//! src/HotCat/modules/check.module.less
var _templateObject;
var _templateObject2;
var _templateObject3;
var _templateObject4;
var _templateObject5;
var _templateObject6;
var _templateObject7;
var _templateObject8;
var _templateObject9;
function _createForOfIteratorHelper(r, e) {
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (!t) {
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {
t && (r = t);
var n = 0, F = function() {
};
return { s: F, n: function() {
return n >= r.length ? { done: true } : { done: false, value: r[n++] };
}, e: function(r2) {
throw r2;
}, f: F };
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var o, a = true, u = false;
return { s: function() {
t = t.call(r);
}, n: function() {
var r2 = t.next();
return a = r2.done, r2;
}, e: function(r2) {
u = true, o = r2;
}, f: function() {
try {
a || null == t.return || t.return();
} finally {
if (u) throw o;
}
} };
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _taggedTemplateLiteral(e, t) {
return t || (t = e.slice(0)), Object.freeze(Object.defineProperties(e, { raw: { value: Object.freeze(t) } }));
}
var catcheckInlineIcon = "check-module__catcheckInlineIcon_mJ5NDq__4100";
//! src/HotCat/modules/api.ts
var import_ext_gadget = require("ext.gadget.Util");
var mwApi = (userAgent) => {
return (0, import_ext_gadget.initMwApi)(userAgent);
};
//! src/HotCat/modules/check.ts
(function hotCatCheck() {
if (mw.config.get("wgNamespaceNumber") !== 6 || window.HotCatAutoRemoveCheckCatOptOut || !document.querySelector(".checkcategories")) {
return;
}
const api = mwApi("hotCatCheck/2.0");
const checkCategoriesRegExp = /{{[Cc]heck[ _]categories[^{}]*}}/g;
const selfName = "([[MediaWiki:Gadget-HotCat-check.js|Script]]): ";
const storageItemName = "checkCat";
const storageItem = mw.storage.get(storageItemName);
const createjIcon = (iconClass) => {
return $("<span>").attr("class", "ui-icon ".concat(iconClass, " ").concat(catcheckInlineIcon)).text(" ");
};
const createNotifyArea = (textNode, icon, state) => {
return $("<div>").addClass("ui-widget").append($("<div>").attr("class", "".concat(state, " ui-corner-all")).css({
"margin-top": "20px",
padding: "0.7em"
}).append($("<p>").append(createjIcon(icon).css("marginRight", "0.3em"), textNode)));
};
$("body").one("submit.checkCatListener", "#hotcatCommitForm", function(e) {
var _self$wpTextbox1$valu;
if (storageItem === "disabled") {
return true;
}
const self = this;
const newVal = (_self$wpTextbox1$valu = self.wpTextbox1.value) === null || _self$wpTextbox1$valu === void 0 ? void 0 : _self$wpTextbox1$valu.replace(checkCategoriesRegExp, "");
const dlgButtons = {};
let $dialogCheckStorage;
let $permaSaveHint;
let $textHintNode;
let $dialog;
const doRemove = () => {
self.wpSummary.value = "Removing [[Template:Check categories|{{Check categories}}]] ".concat(self.wpSummary.value);
self.wpTextbox1.value = newVal;
};
const writeStorage = (val) => {
mw.storage.set(storageItemName, val, 60 * 60 * 24 * 7);
};
dlgButtons["Yes, Remove"] = function() {
var _$dialogCheckStorage$;
doRemove();
if ((_$dialogCheckStorage$ = $dialogCheckStorage[0]) !== null && _$dialogCheckStorage$ !== void 0 && _$dialogCheckStorage$.checked) {
writeStorage("auto");
}
$(this).dialog("close");
};
dlgButtons["No, keep it"] = function() {
var _$dialogCheckStorage$2;
if ((_$dialogCheckStorage$2 = $dialogCheckStorage[0]) !== null && _$dialogCheckStorage$2 !== void 0 && _$dialogCheckStorage$2.checked) {
writeStorage("disabled");
}
$(this).dialog("close");
};
const _addToJS = function(_e) {
_e.preventDefault();
if ($permaSaveHint.hasClass("ui-state-disabled")) {
return;
}
const $el = $(this);
$el.off("click").text("Please wait.");
$permaSaveHint.addClass("ui-state-disabled");
const params = {
action: "edit",
format: "json",
title: "User:".concat(mw.config.get("wgUserName"), "/common.js"),
summary: "".concat(selfName, "Saving HotCat configuration."),
appendtext: $el.data("addText")
};
const editDone = (editStat) => {
if (!editStat) {
return;
}
if (editStat.error) {
void mw.notify("Unable to save to your common.js using the API\n".concat(editStat.error.code, "\n").concat(editStat.error.info), {
tag: "hotCatCheck",
type: "error"
});
$el.text("Edit-Error!");
} else {
$el.text("Done.");
$permaSaveHint.fadeOut();
}
};
void api.postWithToken("csrf", params).then(editDone);
};
const prompt = () => {
$dialogCheckStorage = $("<input>").attr({
type: "checkbox",
id: "hotCatAutoRemoveCheckCatStorage"
}).on("change", function() {
if (this.checked) {
$permaSaveHint.fadeIn();
} else {
$permaSaveHint.fadeOut();
}
});
$textHintNode = $("<ul>");
$("<li>").append($("<a>").attr("href", "#").text("Disable this feature.").data("addText", "\nwindow.HotCatAutoRemoveCheckCatOptOut = true;").on("click", _addToJS)).appendTo($textHintNode);
$("<li>").append($("<a>").attr("href", "#").text("Remove {{check categories}} when editing using HotCat without prompting.").data("addText", "\nwindow.HotCatAutoRemoveCheckCat = true;").on("click", _addToJS)).appendTo($textHintNode);
$permaSaveHint = createNotifyArea($("<span>").text("Save these setting in your common.js: ").append($textHintNode), "ui-icon-info", "ui-state-highlight");
$dialog = $("<div>").append($("<span>").css({
"font-size": "2em",
"line-height": "1.8em"
}).append($("<span>").text(" {{check categories}} ").css({
"background-color": "#F8CCB0",
"text-decoration": "line-through !important",
display: "inline-block"
}), $("<span>").text(" ?"))).append("<br>", $dialogCheckStorage, $("<label>").attr("for", "hotCatAutoRemoveCheckCatStorage").text("Don't ask again"), "<br>").append(mw.user.isAnon() ? "" : $permaSaveHint.hide());
$dialog.dialog({
modal: true,
closeOnEscape: true,
title: "{{check categories}} (−)?",
width: 450,
buttons: dlgButtons,
close: () => {
const $body = $("body");
$body.find("#hotcatCommitForm").trigger("submit");
},
open() {
const $buttons = $(this).parent().find(".ui-dialog-buttonpane button");
$buttons.eq(0).button({
icons: {
primary: "ui-icon-circle-check"
}
});
$buttons.eq(1).button({
icons: {
primary: "ui-icon-cancel"
}
});
}
});
};
if (newVal !== self.wpTextbox1.value) {
if (window.HotCatAutoRemoveCheckCat || storageItem === "auto") {
doRemove();
return true;
}
e.preventDefault();
prompt();
}
return true;
});
const $okLink = $("<a>").attr({
href: "#",
title: "Categories are OK! Immediately remove the template."
}).append("<s>").text("{{Check categories}}");
$okLink.on("click", function(e) {
e.preventDefault();
const $el = $(this);
$el.off("click");
const doEdit = (result) => {
if (!result) {
return;
}
$el.text("Doing.");
const text = result.replace(checkCategoriesRegExp, "");
if (text === result) {
$el.text("Template not found!");
return;
}
const params = {
text,
action: "edit",
format: "json",
title: mw.config.get("wgPageName"),
summary: "".concat(selfName, "Categories are checked and OK. You can help [[Category:Media needing category review|reviewing]]!"),
nocreate: true
};
const editDone = (editStat) => {
if (!editStat) {
return;
}
if (editStat.error) {
void mw.notify('Unable to remove "Check categories" with the API\n'.concat(editStat.error.code, "\n").concat(editStat.error.info), {
tag: "hotCatCheck",
type: "error"
});
$el.text("Edit-Error!");
} else {
$el.text("Edit Done.");
}
const $body = $("body");
$body.find(".checkcategories").fadeOut();
};
$el.text("Doing..");
void api.postWithToken("csrf", params).then(editDone);
};
$el.text("Doing");
void $.ajax({
url: mw.config.get("wgScript"),
data: {
action: "raw",
title: mw.config.get("wgPageName").replace(/ /g, "_")
},
dataType: "text",
error: () => {
$el.text("Error!");
},
success: doEdit,
type: "GET",
cache: false
});
});
$(function loadHotCatCheck() {
const $body = $("body");
$body.find("#catlinks").find("ul:first").append($("<li>").append($okLink));
});
})();
//! src/HotCat/HotCat.js
var import_ext_gadget2 = require("ext.gadget.Util");
//! src/HotCat/modules/getMessage.ts
var getMessage = (key, ...args) => {
key = "hotcat-".concat(key);
return mw.message(key, ...args).plain();
};
//! src/HotCat/modules/messages.ts
var hotCatMessages = () => {
const {
wgUserLanguage
} = mw.config.get();
if (["zh-hant", "zh-hk", "zh-mo", "zh-tw"].includes(wgUserLanguage)) {
mw.messages.set({
"hotcat-messages-cat_removed": "已移除[[Category:$1]]",
"hotcat-messages-template_removed": "已移除{{[[Category:$1]]}}",
"hotcat-messages-cat_added": "已添加[[Category:$1]]",
// $2 is the new key
"hotcat-messages-cat_keychange": '已設置[[Category:$1]]的新排序字:"$2"',
"hotcat-messages-cat_notFound": "分類“$1”沒有找到",
"hotcat-messages-cat_exists": "分類“$1”已經存在,沒有添加。",
"hotcat-messages-cat_resolved": "(重定向[[Category:$1]]已處理)",
"hotcat-messages-uncat_removed": "已移除{{uncategorized}}",
// Some text to prefix to the edit summary.
"hotcat-messages-prefix": "使用[[Help:HotCat|HotCat]]",
// 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.
"hotcat-messages-using": "",
"hotcat-messages-multi_change": "$1個分類",
// Any category in this category is deemed a disambiguation category; i.e., a category that should not contain
// any items, but that contains links to other categories where stuff should be categorized. If you don't have
// that concept on your wiki, set it to blank string. Use blanks, not underscores.
"hotcat-disambig_category": "",
// Any category in this category is deemed a (soft) redirect to some other category defined by a link
// to another non-blacklisted category. If your wiki doesn't have soft category redirects, set this to null.
// If a soft-redirected category contains more than one link to another non-blacklisted category, it's considered
// a disambiguation category instead.
"hotcat-redir_category": "已重定向的分类",
"hotcat-messages-separator": "; ",
// $1 is replaced by a number. If your language has several plural forms (c.f. [[:enwiki:Dual (grammatical form)]]),
// 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
// entry in the array.
// Defaults to '[[' + category_canonical + ':$1]]'. Can be overridden if in the short edit summaries
// not the standard category name should be used but, say, a shorter namespace alias. $1 is replaced
// by a category name.
"hotcat-messages-short_catchange": "$1",
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
// see localization hook below.
"hotcat-messages-commit": "儲存",
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
// see localization hook below.
"hotcat-messages-ok": "確定",
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
// see localization hook below.
"hotcat-messages-cancel": "取消",
// Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
// see localization hook below.
"hotcat-messages-multi_error": "無法從伺服器取得頁面文字。因此,您的分類變更無法儲存。我們為此不便表示抱歉。",
// Plural of category_canonical.
"hotcat-categories": "分類",
// Names for the search engines
"hotcat-engine_names-searchindex": "搜尋索引",
"hotcat-engine_names-pagelist": "頁面列表",
"hotcat-engine_names-combined": "合併搜尋",
"hotcat-engine_names-subcat": "子分類",
"hotcat-engine_names-parentcat": "上層分類",
// The tooltips for the above links
"hotcat-tooltips-change": "修改",
"hotcat-tooltips-remove": "移除",
"hotcat-tooltips-add": "增加一個新分類",
"hotcat-tooltips-restore": "復原變更",
"hotcat-tooltips-undo": "復原變更",
"hotcat-tooltips-down": "打開以修改並顯示子分類",
"hotcat-tooltips-up": "打開以修改並顯示上層分類",
// Tooltip for the "enter multi-mode" link
"hotcat-multi_tooltip": "修改多個分類"
});
} else {
mw.messages.set({
"hotcat-messages-cat_removed": "已移除[[Category:$1]]",
"hotcat-messages-template_removed": "已移除{{[[Category:$1]]}}",
"hotcat-messages-cat_added": "已添加[[Category:$1]]",
// $2 is the new key
"hotcat-messages-cat_keychange": '已设置[[Category:$1]]的新排序字:"$2"',
"hotcat-messages-cat_notFound": "分类“$1”没有找到",
"hotcat-messages-cat_exists": "分类“$1”已经存在,没有添加。",
"hotcat-messages-cat_resolved": "(重定向[[Category:$1]]已处理)",
"hotcat-messages-uncat_removed": "已移除{{uncategorized}}",
// Some text to prefix to the edit summary.
"hotcat-messages-prefix": "使用[[Help:HotCat|HotCat]]",
// 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.
"hotcat-messages-using": "",
"hotcat-messages-multi_change": "$1个分类",
// Any category in this category is deemed a disambiguation category; i.e., a category that should not contain
// any items, but that contains links to other categories where stuff should be categorized. If you don't have
// that concept on your wiki, set it to blank string. Use blanks, not underscores.
"hotcat-disambig_category": "",
// Any category in this category is deemed a (soft) redirect to some other category defined by a link
// to another non-blacklisted category. If your wiki doesn't have soft category redirects, set this to null.
// If a soft-redirected category contains more than one link to another non-blacklisted category, it's considered
// a disambiguation category instead. 'hotcat-redir_category': '已重定向的分类',
"hotcat-messages-separator": "; ",
// $1 is replaced by a number. If your language has several plural forms (c.f. [[:enwiki:Dual (grammatical form)]]),
// 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
// entry in the array.
// Defaults to '[[' + category_canonical + ':$1]]'. Can be overridden if in the short edit summaries
// not the standard category name should be used but, say, a shorter namespace alias. $1 is replaced
// by a category name.
"hotcat-messages-short_catchange": "$1",
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
// see localization hook below.
"hotcat-messages-commit": "保存",
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
// see localization hook below.
"hotcat-messages-ok": "确定",
// Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
// see localization hook below.
"hotcat-messages-cancel": "取消",
// Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
// see localization hook below.
"hotcat-messages-multi_error": "无法从服务器取得页面文字。因此,您的分类更改无法保存。我们为此不便表示抱歉。",
// Plural of category_canonical.
"hotcat-categories": "分类",
// Names for the search engines
"hotcat-engine_names-searchindex": "搜索索引",
"hotcat-engine_names-pagelist": "页面列表",
"hotcat-engine_names-combined": "合并搜索",
"hotcat-engine_names-subcat": "子分类",
"hotcat-engine_names-parentcat": "上层分类",
// The tooltips for the above links
"hotcat-tooltips-change": "修改",
"hotcat-tooltips-remove": "移除",
"hotcat-tooltips-add": "增加一个新分类",
"hotcat-tooltips-restore": "撤销更改",
"hotcat-tooltips-undo": "撤销更改",
"hotcat-tooltips-down": "打开以修改并显示子分类",
"hotcat-tooltips-up": "打开以修改并显示上层分类",
// Tooltip for the "enter multi-mode" link
"hotcat-multi_tooltip": "修改多个分类"
});
}
};
//! src/HotCat/images/P_no.png
var P_no_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAABmJLR0QA/wD/AP+gvaeTAAADOUlEQVQ4jY2SXUzbZRjFf/9/y1fsKEGQQaDbmEKZzrmL6Qg6RzQQso9ELcm6i043zURJTPy40IQF9WqJmZpdaEIWIyH76EzwZsZEs6spc2TSJgjFhQEZUrqh2RAK9P++7+ONLbVs6pOcq+e853lzzrH4n3PtmhTny0qZyRMzOVk43dxsqbvxrH8XcRot0YcEaw9QnbVaBC4JcrKurvDCfwqOjCw/ZIucxKI1d6e1Jh6/QWVlNS6XG4GzKafw5W3brEUAO/dBNLoQPHGia/hYd2frTHwGpSWDocggzc/U8WxLA/1fn8ZRBq3lgMu19IWIWGsEoz8vhMTQt6Vhe374/Ck6OwPM/zmPUoapqet0vPYcN2/OEHjhMMXFpcTjv6GUQTmmPRJJ7v+H4ODgnccdI6eUFnvX0/tob3+FkdEhurpeZSWlePe9I9y+/Tu7d++j6ck2Nmz0U3p/Zeb3xuijGQ/DYXH5fPPDgD99wBhNR8ceotEB/P7HiMUi1NRsprPzQ2pqaqmtbch1a27nTm+5DVBdfed5rcWvtZCGiM3x42eoqKgmFovgdrsJBI7i8ZTg89WTzf0b6wDcAI6jg3cLvKiomKqqjSQS04hAMrnIpk0Po7UAkkv/I+Oh0jRlp5nGwMD3DA1dwuPxImLo7f2I8euja3hKC46WH1cFlSlVypANx1H09HwAQEtrkJbWIEtLi7zf/RK3biXI5SutzmQEtTbzuZ5cvNjP+Pgw6ys3sPWRJg4efIfGxjbm5uJ88vGbOI5a5SsZvvzDA/0ZQUeZwdyL5859CsCOHS0UFHrweit46+3P8fnquXLlO8ZiEZQyaGUcY9SL3d2WWf2hWJ9l+zExEWNqcpSiIg8NW56grNyHNmC78uk6dpqSknJmE9OkHGWUMof37q26mk7GAhAR6/xX8TCWBABSqWVm4xNMTP5CibeMrY8+hW27MnGurCQZG7uarHtw+5FQqP5sdtSZrvT2zt7nLnB6wAqmi+04KWzbRV5efk5DrG8dWX7j0IHNv+Z2Z035+vputAm8jmV2gbUuizoF8o3Y1pehYM1Pue/uKZiecFhcC3qmCqPy3LogEQqtX7wXN3v+ApHw44zjlQ7KAAAAAElFTkSuQmCC";
//! src/HotCat/images/P_yes.png
var P_yes_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAABmJLR0QA/wD/AP+gvaeTAAADAElEQVQ4jY2S32vbZRSHnzffdMvWJVmWtJamnUXWbnZgHKuTMSnOm1GcCJ3iil0qcyBDUIbzYl5N8K43SileFDc60h/0RlR0/gE6JkVpIQy0DLo4pavbsEvTtP2+7zlezKRZ2joPnJv3fM7DOef9GP5nzMxoZIuuJKRGZXY2dPvoUWM30pn/hviHjbo+xbwMNFWUCsAPig60tYW+fSzwxo3l1oDqAIZjj5tcYXzVD51JpUwBIFgtyGaXToqTIYEdGwHy+QUyI4P8cfsWZ89eIJlsOel5RU9V3zDG6CPA6V8W09aXy0BgI9jdu3OcfvsYudxNUqnnmZ+fIxpNEAptf31qamkE+KrcODm5cMgX/cI6DVinVOfKqs8H53vJ5W6yZ89+envfxzoIeFuwThFx75RXnphQz7kHwyDrTlCKwcGPmZ6+TjS6i1OnzrFzZ4K9ew8gYhARgOfKwKamhW7n2LcZbGrqGpnMZwB0d58hGo3T2vosqgbntCQLl4G+73o2c5Dvr9Lffw5VpaPjRdraDtDS0o4xQayVSun9MtA6joCuVe7PE4vVYYxhdHSA2dlfqa2N0NXVSzzRSDiSwK5NBoDCNfj3N62VXdYKvu/45uth+tKHGBn5lHv3/mJs7OGqnZ2vsm17hGSyFWtlfTo7VgY6Jw+cU5aXV7h6NcPSUp7Llz6hL91BsbhILFbPwYMv0dDwFMYEcU4fTavZ6z/Wf1kG+lYmrRUCgRoufHSJaDQOQLG4CMCRF14htC1MPN64bjJnxRexb128aGRtQjWfl/wWiT7B+Q+HCAQeWjQUquXp9sPU17fgxFT7U6yV08ePN/5cuqUHMD7a/1s2m98vqu0iSiLRhOcFsb7PM6lOdjfvY/eT7agaRLSUeXWSPnEiOV75OWWvXLkyVxvc6g+B6Sm9zd/JUSj8zY5wjLq65sq279WT9958rXmm2mbrzJfJ/N6l8C5GOsGEK6S3QL/TgBlO9zT/VN23KbAUExPqLbo/GxFbE3Rb76TTDYXNtJXxD+yujTqqNmcnAAAAAElFTkSuQmCC";
//! src/HotCat/HotCat.js
hotCatMessages();
(function hotCat() {
const conf = mw.config.values;
if (window.HotCat && !window.HotCat.nodeName || conf.wgAction === "edit") {
return;
}
const api = mwApi("HotCat/3.0");
window.HotCat = {
// The little modification links displayed after category names. U+2212 is a minus sign; U+2193 and U+2191 are
// downward and upward pointing arrows. Do not use ↓ and ↑ in the code!
links: {
change: "(±)",
remove: "(−)",
add: "(+)",
restore: "(×)",
undo: "(×)",
down: "(↓)",
up: "(↑)"
},
changeTag: "HotCat",
// The HTML content of the "enter multi-mode" link at the front.
addmulti: "<span>+<sup>+</sup></span>",
// Return true to disable HotCat.
disable: () => {
const ns = conf.wgNamespaceNumber;
const nsIds = conf.wgNamespaceIds;
return ns < 0 || // Special pages; Special:Upload is handled differently
ns === 10 || // Templates
ns === 828 || // Module (Lua)
ns === 8 || // MediaWiki
ns === 6 && !conf.wgArticleId || // Non-existing file pages
ns === 2 && /\.(js|css)$/.test(conf.wgTitle) || // User scripts
nsIds && (ns === nsIds.creator || ns === nsIds.timedtext || ns === nsIds.institution);
},
// A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
// If not, set it to null.
uncat_regexp: /{{\s*[Uu]ncategorized\s*[^}]*}}\s*(<!--.*?-->\s*)?/g,
// The images used for the little indication icon. Should not need changing.
existsYes: P_yes_default,
existsNo: P_no_default,
// a list of categories which can be removed by removing a template
// key: the category without namespace
// value: A regexp matching the template name, again without namespace
// If you don't have this at your wiki, or don't want this, set it to an empty object {}.
template_categories: {},
// Override the decision of whether HotCat should help users by automatically
// capitalising the title in the user input text if the wiki has case-sensitive page names.
// Basically, this will make an API query to check the MediaWiki configuration and HotCat then sets
// this to true for most wikis, and to false on Wiktionary.
//
// You can set this directly if there is a problem with it. For example, Georgian Wikipedia (kawiki),
// is known to have different capitalisation logic between MediaWiki PHP and JavaScript. As such, automatic
// case changes in JavaScript by HotCat would be wrong.
capitalizePageNames: null,
// If upload_disabled is true, HotCat will not be used on the Upload form.
upload_disabled: false,
// Single regular expression matching blacklisted categories that cannot be changed or
// 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.
blacklist: null,
// Stuff changeable by users:
// Background for changed categories in multi-edit mode. Default is a very light salmon pink.
bg_changed: "#FCA",
// If true, HotCat will never automatically submit changes. HotCat will only open an edit page with
// the changes; users must always save explicitly.
no_autocommit: false,
// If true, the "category deletion" link "(-)" will never save automatically but always show an
// edit page where the user has to save the edit manually. Is false by default because that's the
// traditional behavior. This setting overrides no_autocommit for "(-)" links.
del_needs_diff: false,
// Time, in milliseconds, that HotCat waits after a keystroke before making a request to the
// server to get suggestions.
suggest_delay: 1e3,
// Default width, in characters, of the text input field.
editbox_width: 40,
// One of the engine_names above, to be used as the default suggestion engine.
suggestions: "combined",
// If true, always use the default engine, and never display a selector.
fixed_search: false,
// If false, do not display the "up" and "down" links
use_up_down: true,
// Default list size
listSize: 10,
// If true, single category changes are marked as minor edits. If false, they're not.
single_minor: true,
// 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"
// options in his or her preferences set.
dont_add_to_watchlist: false,
shortcuts: null,
addShortcuts: (map) => {
var _window$HotCat;
if (!map) {
return;
}
(_window$HotCat = window.HotCat).shortcuts || (_window$HotCat.shortcuts = {});
for (let k in map) {
if (!Object.hasOwn(map, k) || typeof k !== "string") {
continue;
}
let v = map[k];
if (typeof v !== "string") {
continue;
}
k = k.trim();
v = v.trim();
if (k.length === 0 || v.length === 0) {
continue;
}
window.HotCat.shortcuts[k] = v;
}
}
};
const HC = window.HotCat;
const ua = navigator.userAgent.toLowerCase();
const is_webkit = /applewebkit\/\d+/.test(ua) && !ua.includes("spoofer");
let cat_prefix = null;
let noSuggestions = false;
const wikiTextBlank = String.raw(_templateObject || (_templateObject = _taggedTemplateLiteral(["[ _  ᠎ - \u2028\u2029   ]+"], ["[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+"])));
const wikiTextBlankRE = new RegExp(wikiTextBlank, "g");
const wikiTextBlankOrBidi = String.raw(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["[ _  ᠎ -​‎‏\u2028-   ]*"], ["[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200B\\u200E\\u200F\\u2028-\\u202F\\u205F\\u3000]*"])));
const formattedNamespaces = conf.wgFormattedNamespaces;
const namespaceIds = conf.wgNamespaceIds;
const autoLocalize = (namespaceNumber, fallback) => {
const createRegexpStr = (name) => {
if (!name || name.length === 0) {
return;
}
let regex_name = "";
for (let i = 0; i < name.length; i++) {
const initial = name.charAt(i);
const ll = initial.toLowerCase();
const ul = initial.toUpperCase();
regex_name += ll === ul ? initial : "[".concat(ll).concat(ul, "]");
}
return regex_name.replace(/([$()*+.?\\^])/g, String.raw(_templateObject3 || (_templateObject3 = _taggedTemplateLiteral(["$1"], ["\\$1"])))).replace(wikiTextBlankRE, wikiTextBlank);
};
fallback = fallback.toLowerCase();
const canonical = formattedNamespaces[String(namespaceNumber)].toLowerCase();
let regexp = createRegexpStr(canonical);
if (fallback && canonical !== fallback) {
regexp += "|".concat(createRegexpStr(fallback));
}
if (namespaceIds) {
for (const cat_name in namespaceIds) {
if (typeof cat_name === "string" && cat_name.toLowerCase() !== canonical && cat_name.toLowerCase() !== fallback && namespaceIds[cat_name] === namespaceNumber) {
regexp += "|".concat(createRegexpStr(cat_name));
}
}
}
return regexp;
};
HC.category_canonical = formattedNamespaces["14"];
HC.category_regexp = autoLocalize(14, "category");
if (formattedNamespaces["10"]) {
HC.template_regexp = autoLocalize(10, "template");
}
const make = (arg, literal) => {
if (!arg) {
return null;
}
return literal ? document.createTextNode(arg) : document.createElement(arg);
};
const param = (name, uri) => {
uri || (uri = document.location.href);
const re = new RegExp("[&?]".concat(name, "=([^&#]*)"));
const m = re.exec(uri);
if (m && m.length > 1) {
return decodeURIComponent(m[1]);
}
return null;
};
const title = (href) => {
if (!href) {
return null;
}
const script = "".concat(conf.wgScript, "?");
if (href.indexOf(script) === 0 || href.indexOf(conf.wgServer + script) === 0 || conf.wgServer.slice(0, 2) === "//" && href.indexOf(document.location.protocol + conf.wgServer + script) === 0) {
return param("title", href);
}
let prefix = conf.wgArticlePath.replace("$1", "");
if (href.indexOf(prefix)) {
prefix = conf.wgServer + prefix;
}
if (href.indexOf(prefix) && prefix.slice(0, 2) === "//") {
prefix = document.location.protocol + prefix;
}
if (href.indexOf(prefix) === 0) {
return decodeURIComponent(href.slice(prefix.length));
}
return null;
};
const hasClass = ({
className
}, name) => {
return " ".concat(className, " ").includes(" ".concat(name, " "));
};
const capitalize = (str) => {
if (!str || str.length === 0) {
return str;
}
return str.slice(0, 1).toUpperCase() + str.slice(1);
};
const wikiPagePath = (pageName) => {
return conf.wgArticlePath.replace("$1", encodeURIComponent(pageName).replace(/%3A/g, ":").replace(/%2F/g, "/"));
};
const escapeRE = (str) => {
return str.replace(/([$()*+.?[\\\]^])/g, String.raw(_templateObject4 || (_templateObject4 = _taggedTemplateLiteral(["$1"], ["\\$1"]))));
};
const substituteFactory = (options) => {
options || (options = {});
const lead = options.indicator || "$";
const indicator = escapeRE(lead);
const lbrace = escapeRE(options.lbrace || "{");
const rbrace = escapeRE(options.rbrace || "}");
const re = new RegExp(
// $$
"(?:".concat(indicator, "(").concat(indicator, "))|(?:").concat(indicator, "(\\d+))|(?:").concat(indicator, "(?:").concat(lbrace, "([^").concat(lbrace).concat(rbrace, "]+)").concat(rbrace, "))|(?:").concat(indicator, "(?!(?:[").concat(indicator).concat(lbrace, "]|\\d))(\\S+?)\\b)"),
"g"
);
return (str, map) => {
if (!map) {
return str;
}
return str.replace(re, (match, prefix, idx, key, alpha) => {
if (prefix === lead) {
return lead;
}
const k = alpha || key || idx;
const replacement = typeof map[k] === "function" ? map[k](match, k) : map[k];
return typeof replacement === "string" ? replacement : replacement || match;
});
};
};
const replaceShortcuts = (() => {
const replaceHash = substituteFactory({
indicator: "#",
lbrace: "[",
rbrace: "]"
});
return (str, map) => {
const s = replaceHash(str, map);
return HC.capitalizePageNames ? capitalize(s) : s;
};
})();
const findCatsRE = new RegExp("\\[\\[".concat(wikiTextBlankOrBidi, "(?:").concat(HC.category_regexp, ")").concat(wikiTextBlankOrBidi, ":[^\\]]+\\]\\]"), "g");
const replaceByBlanks = (match) => {
return match.replace(/(\s|\S)/g, " ");
};
const find_category = (wikitext, category, once) => {
let cat_regex = null;
if (HC.template_categories[category]) {
cat_regex = new RegExp("\\{\\{".concat(wikiTextBlankOrBidi, "(").concat(HC.template_regexp, "(?=").concat(wikiTextBlankOrBidi, ":))?").concat(wikiTextBlankOrBidi, "(?:").concat(HC.template_categories[category], ")").concat(wikiTextBlankOrBidi, "(\\|.*?)?\\}\\}"), "g");
} else {
const cat_name = escapeRE(category);
const initial = cat_name.slice(0, 1);
cat_regex = new RegExp("\\[\\[".concat(wikiTextBlankOrBidi, "(").concat(HC.category_regexp, ")").concat(wikiTextBlankOrBidi, ":").concat(wikiTextBlankOrBidi).concat(initial === "\\" || !HC.capitalizePageNames ? initial : "[".concat(initial.toUpperCase()).concat(initial.toLowerCase(), "]")).concat(cat_name.slice(1).replace(wikiTextBlankRE, wikiTextBlank)).concat(wikiTextBlankOrBidi, "(\\|.*?)?\\]\\]"), "g");
}
if (once) {
return cat_regex.exec(wikitext);
}
const nowikiRegex = new RegExp("<no".concat("wiki>", String.raw(_templateObject5 || (_templateObject5 = _taggedTemplateLiteral(["(s|S)*?</no"], ["(\\s|\\S)*?</no"]))), "wiki", ">"), "g");
const copiedtext = wikitext.replace(/<!--(\s|\S)*?-->/g, replaceByBlanks).replace(nowikiRegex, replaceByBlanks);
const result = [];
let curr_match = null;
while ((curr_match = cat_regex.exec(copiedtext)) !== null) {
result[result.length] = {
match: curr_match
};
}
result.re = cat_regex;
return result;
};
let interlanguageRE = null;
const change_category = (wikitext, toRemove, toAdd, key, is_hidden) => {
const find_insertionpoint = (_wikitext) => {
const nowikiRegex = new RegExp("<no".concat("wiki>", String.raw(_templateObject6 || (_templateObject6 = _taggedTemplateLiteral(["(s|S)*?</no"], ["(\\s|\\S)*?</no"]))), "wiki", ">"), "g");
const copiedtext = _wikitext.replace(/<!--(\s|\S)*?-->/g, replaceByBlanks).replace(nowikiRegex, replaceByBlanks);
let index = -1;
findCatsRE.lastIndex = 0;
while (findCatsRE.exec(copiedtext) !== null) {
index = findCatsRE.lastIndex;
}
if (index < 0) {
let match = null;
if (interlanguageRE) {
match = interlanguageRE.exec(copiedtext);
} else {
match = /((^|\n\r?)(\[\[\s*(([a-z]{2,3}(-[a-z]+)*)|simple|tokipona)\s*:[^\]]+]]\s*))+$/.exec(copiedtext);
}
if (match) {
({
index
} = match);
}
return {
idx: index,
onCat: false
};
}
return {
idx: index,
onCat: index >= 0
};
};
const summary = [];
const nameSpace = HC.category_canonical;
const keyChange = toRemove && toAdd && toRemove === toAdd && toAdd.length > 0;
let matches;
let cat_point = -1;
key && (key = "|".concat(key));
if (toRemove && toRemove.length > 0) {
matches = find_category(wikitext, toRemove);
if (!matches || matches.length === 0) {
return {
text: wikitext,
summary,
error: getMessage("messages-cat_notFound", toRemove)
};
}
let before = wikitext.slice(0, Math.max(0, matches[0].match.index));
let after = wikitext.slice(Math.max(0, matches[0].match.index + matches[0].match[0].length));
if (matches.length > 1) {
matches.re.lastIndex = 0;
after = after.replace(matches.re, "");
}
if (toAdd && // nameSpace = matches[ 0 ].match[ 1 ] || nameSpace; Canonical namespace should be always preferred
key === null) {
[, , key] = matches[0].match;
}
let i = before.length - 1;
while (i >= 0 && before.charAt(i) !== "\n" && before.slice(i, i + 1).search(/\s/) >= 0) {
i--;
}
let j = 0;
while (j < after.length && after.charAt(j) !== "\n" && after.slice(j, j + 1).search(/\s/) >= 0) {
j++;
}
if (i >= 0 && before.charAt(i) === "\n" && (after.length === 0 || j < after.length && after.charAt(j) === "\n")) {
i--;
}
before = i >= 0 ? before.slice(0, Math.max(0, i + 1)) : "";
after = j < after.length ? after.slice(Math.max(0, j)) : "";
if (before.length > 0 && before.slice(Math.max(0, before.length - 1)).search(/\S/) >= 0 && after.length > 0 && after.slice(0, 1).search(/\S/) >= 0) {
before += " ";
}
cat_point = before.length;
if (cat_point === 0 && after.length > 0 && after.slice(0, 1) === "\n") {
after = after.slice(1);
}
wikitext = before + after;
if (!keyChange) {
if (HC.template_categories[toRemove]) {
summary[summary.length] = getMessage("messages-template_removed", toRemove);
} else {
summary[summary.length] = getMessage("messages-cat_removed", toRemove);
}
}
}
if (toAdd && toAdd.length > 0) {
matches = find_category(wikitext, toAdd);
if (matches && matches.length > 0) {
return {
text: wikitext,
summary,
error: getMessage("messages-cat_exists", toAdd)
};
}
let onCat = false;
if (cat_point < 0) {
const point = find_insertionpoint(wikitext);
cat_point = point.idx;
({
onCat
} = point);
} else {
onCat = true;
}
const newcatstring = "[[".concat(nameSpace, ":").concat(toAdd).concat(key || "", "]]");
if (cat_point >= 0) {
const suffix = wikitext.slice(Math.max(0, cat_point));
wikitext = wikitext.slice(0, Math.max(0, cat_point)) + (cat_point > 0 ? "\n" : "") + newcatstring + (onCat ? "" : "\n");
wikitext += suffix.length > 0 && suffix.slice(0, 1) !== "\n" ? "\n".concat(suffix) : suffix;
} else {
if (wikitext.length > 0 && wikitext.slice(-1, wikitext.length - 1 + 1) !== "\n") {
wikitext += "\n";
}
wikitext += (wikitext.length > 0 ? "\n" : "") + newcatstring;
}
if (keyChange) {
let k = key || "";
if (k.length > 0) {
k = k.slice(1);
}
summary[summary.length] = getMessage("messages-cat_keychange", toAdd, k);
} else {
summary[summary.length] = getMessage("messages-cat_added", toAdd);
}
if (HC.uncat_regexp && !is_hidden) {
const txt = wikitext.replace(HC.uncat_regexp, "");
if (txt.length !== wikitext.length) {
wikitext = txt;
summary[summary.length] = getMessage("messages-uncat_removed");
}
}
}
return {
text: wikitext,
summary,
error: null
};
};
const evtKeys = ({
ctrlKey,
metaKey,
shiftKey
}) => {
let code = 0;
if (ctrlKey) {
if (ctrlKey || metaKey) {
code || (code = 1);
}
if (shiftKey) {
code || (code = 2);
}
}
return code;
};
const evtKill = (e) => {
if (e.preventDefault) {
e.preventDefault();
e.stopPropagation();
} else {
e.cancelBubble = true;
}
return false;
};
let catLine = null;
let onUpload = false;
let editors = [];
let commitButton = null;
let commitForm = null;
let multiSpan = null;
let pageText = null;
let pageTime = null;
let pageWatched = false;
let watchCreate = false;
let watchEdit = false;
let minorEdits = false;
let editToken = null;
let is_rtl = false;
let serverTime = null;
let lastRevId = null;
let pageTextRevId = null;
let conflictingUser = null;
let newDOM = false;
const UNCHANGED = 0;
const OPEN = 1;
const CHANGE_PENDING = 2;
const CHANGED = 3;
const DELETED = 4;
const setPage = (data) => {
let startTime = null;
if (data && data.query) {
if (data.query.pages) {
const [page] = data.query.pages;
if (page) {
if (page.revisions && page.revisions.length > 0) {
pageText = page.revisions[0].slots.main.content;
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";
if (data.query.tokens) {
editToken = data.query.tokens.csrftoken;
}
if (page.langlinks && (!data["query-continue"] || !data["query-continue"].langlinks)) {
let re = "";
for (let i = 0; i < page.langlinks.length; i++) {
re += (i > 0 ? "|" : "") + page.langlinks[i].lang.replace(/([$()*+.?\\^])/g, String.raw(_templateObject7 || (_templateObject7 = _taggedTemplateLiteral(["$1"], ["\\$1"]))));
}
if (re.length > 0) {
interlanguageRE = new RegExp("((^|\\n\\r?)(\\[\\[\\s*(".concat(re, ")\\s*:[^\\]]+\\]\\]\\s*))+$"));
}
}
}
}
if (data.query.general) {
if (data.query.general.time && !startTime) {
startTime = data.query.general.time.replace(/\D/g, "");
}
if (HC.capitalizePageNames === null) {
HC.capitalizePageNames = data.query.general.case === "first-letter";
}
}
serverTime = startTime;
if (data.query.userinfo && data.query.userinfo.options) {
watchCreate = !HC.dont_add_to_watchlist && data.query.userinfo.options.watchcreations === "1";
watchEdit = !HC.dont_add_to_watchlist && data.query.userinfo.options.watchdefault === "1";
minorEdits = data.query.userinfo.options.minordefault === 1;
if (minorEdits) {
HC.single_minor = true;
}
}
}
};
let saveInProgress = false;
const initiateEdit = (doEdit, failure) => {
if (saveInProgress) {
return;
}
saveInProgress = true;
let oldButtonState;
if (commitButton) {
oldButtonState = commitButton.disabled;
commitButton.disabled = true;
}
const fail = function(...args) {
saveInProgress = false;
if (commitButton) {
commitButton.disabled = oldButtonState;
}
failure.apply(this, args);
};
const params = {
action: "query",
format: "json",
formatversion: "2",
rawcontinue: "",
titles: conf.wgPageName,
prop: ["info", "revisions", "langlinks"],
inprop: "watched",
rvprop: ["content", "timestamp", "ids", "user"],
rvslots: "main",
rvlimit: "2",
rvdir: "newer",
rvstartid: conf.wgCurRevisionId,
lllimit: "500",
meta: ["siteinfo", "userinfo", "tokens"],
type: "csrf",
uiprop: ["options"]
};
api.get(params).done((data) => {
setPage(data);
doEdit(fail);
}).fail(({
status,
statusText
}) => {
fail("".concat(status, " ").concat(statusText));
});
};
const multiChangeMsg = (count) => {
return getMessage("messages-multi_change", String(count));
};
const currentTimestamp = () => {
const now = /* @__PURE__ */ new Date();
let ts = String(now.getUTCFullYear());
const two = (s) => {
return s.slice(-2);
};
ts += two("0".concat(now.getUTCMonth() + 1)) + two("0".concat(now.getUTCDate())) + two("00".concat(now.getUTCHours())) + two("00".concat(now.getUTCMinutes())) + two("00".concat(now.getUTCSeconds()));
return ts;
};
const performChanges = (failure, singleEditor) => {
if (pageText === null) {
failure(getMessage("messages-multi_error"));
return;
}
let action;
const selfEditConflict = (lastRevId !== null && lastRevId !== conf.wgCurRevisionId || pageTextRevId !== null && pageTextRevId !== conf.wgCurRevisionId) && conflictingUser && conflictingUser === conf.wgUserName;
if (singleEditor && !singleEditor.noCommit && !HC.no_autocommit && editToken && !selfEditConflict) {
commitForm.wpEditToken.value = editToken;
action = commitForm.wpDiff;
if (action) {
action.value = "wpSave";
action.name = action.value;
}
} else {
action = commitForm.wpSave;
if (action) {
action.value = "wpDiff";
action.name = action.value;
}
}
let result = {
text: pageText
};
const changed = [];
const added = [];
const deleted = [];
const toEdit = singleEditor ? [singleEditor] : editors;
let edit;
let i;
let error = null;
let changes = 0;
for (i = 0; i < toEdit.length; i++) {
edit = toEdit[i];
if (edit.state === CHANGED) {
result = change_category(result.text, edit.originalCategory, edit.currentCategory, edit.currentKey, edit.currentHidden);
if (!result.error) {
changes++;
if (!edit.originalCategory || edit.originalCategory.length === 0) {
added[added.length] = edit.currentCategory;
} else {
changed[changed.length] = {
from: edit.originalCategory,
to: edit.currentCategory
};
}
} else if (error === null) {
({
error
} = result);
}
} else if (edit.state === DELETED && edit.originalCategory && edit.originalCategory.length > 0) {
result = change_category(result.text, edit.originalCategory, null, null, false);
if (!result.error) {
changes++;
deleted[deleted.length] = edit.originalCategory;
} else if (error === null) {
({
error
} = result);
}
}
}
if (error !== null) {
action = commitForm.wpSave;
if (action) {
action.value = "wpDiff";
action.name = action.value;
}
}
commitForm.wpMinoredit.checked = minorEdits;
commitForm.wpWatchthis.checked = !conf.wgArticleId && watchCreate || watchEdit || pageWatched;
if (conf.wgArticleId || !!singleEditor) {
if (action && action.value === "wpSave") {
if (HC.changeTag) {
commitForm.wpChangeTags.value = HC.changeTag;
}
} else {
commitForm.wpAutoSummary.value = HC.changeTag;
}
if (changes === 1) {
if (result.summary && result.summary.length > 0) {
commitForm.wpSummary.value = (HC.changeTag ? "" : getMessage("messages-prefix")) + result.summary.join(getMessage("messages-separator")) + (HC.changeTag ? "" : getMessage("messages-using"));
}
commitForm.wpMinoredit.checked = HC.single_minor || minorEdits;
} else if (changes) {
let summary = [];
const shortSummary = [];
for (i = 0; i < deleted.length; i++) {
summary[summary.length] = "−".concat(getMessage("messages-short_catchange", deleted[i]));
}
if (deleted.length === 1) {
shortSummary[shortSummary.length] = "−".concat(getMessage("messages-short_catchange", deleted[0]));
} else if (deleted.length > 0) {
shortSummary[shortSummary.length] = "− ".concat(multiChangeMsg(deleted.length));
}
for (i = 0; i < added.length; i++) {
summary[summary.length] = "+".concat(getMessage("messages-short_catchange", added[i]));
}
if (added.length === 1) {
shortSummary[shortSummary.length] = "+".concat(getMessage("messages-short_catchange", added[0]));
} else if (added.length > 0) {
shortSummary[shortSummary.length] = "+ ".concat(multiChangeMsg(added.length));
}
const arrow = is_rtl ? "←" : "→";
for (i = 0; i < changed.length; i++) {
if (changed[i].from === changed[i].to) {
summary[summary.length] = "±".concat(getMessage("messages-short_catchange", changed[i].from));
} else {
summary[summary.length] = "±".concat(getMessage("messages-short_catchange", changed[i].from)).concat(arrow).concat(getMessage("messages-short_catchange", changed[i].to));
}
}
if (changed.length === 1) {
if (changed[0].from === changed[0].to) {
shortSummary[shortSummary.length] = "±".concat(getMessage("messages-short_catchange", changed[0].from));
} else {
shortSummary[shortSummary.length] = "±".concat(getMessage("messages-short_catchange", changed[0].from)).concat(arrow).concat(getMessage("messages-short_catchange", changed[0].to));
}
} else if (changed.length > 0) {
shortSummary[shortSummary.length] = "± ".concat(multiChangeMsg(changed.length));
}
if (summary.length > 0) {
summary = summary.join(getMessage("messages-separator"));
if (summary.length > 200 - (HC.changeTag ? "" : getMessage("messages-prefix")).length - (HC.changeTag ? "" : getMessage("messages-using")).length) {
summary = shortSummary.join(getMessage("messages-separator"));
}
commitForm.wpSummary.value = (HC.changeTag ? "" : getMessage("messages-prefix")) + summary + (HC.changeTag ? "" : getMessage("messages-using"));
}
}
}
commitForm.wpTextbox1.value = result.text;
commitForm.wpStarttime.value = serverTime || currentTimestamp();
commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value;
if (selfEditConflict) {
commitForm.oldid.value = String(pageTextRevId || conf.wgCurRevisionId);
}
commitForm.hcCommit.click();
};
const resolveOne = (page, toResolve) => {
const cats = page.categories;
const {
links
} = page;
let is_dab = false;
let is_redir = typeof page.redirect === "string";
let i;
const is_hidden = page.categoryinfo && typeof page.categoryinfo.hidden === "string";
const is_missing = typeof page.missing === "string";
for (i = 0; i < toResolve.length; i++) {
if (i && toResolve[i].dabInputCleaned !== page.title.slice(Math.max(0, page.title.indexOf(":") + 1))) {
continue;
}
toResolve[i].currentHidden = is_hidden;
toResolve[i].inputExists = !is_missing;
toResolve[i].icon.src = is_missing ? HC.existsNo : HC.existsYes;
}
if (is_missing) {
return;
}
if (!is_redir && cats && (getMessage("disambig_category") || getMessage("redir_category"))) {
var _iterator = _createForOfIteratorHelper(cats), _step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done; ) {
const cat_ = _step.value;
let cat = cat_.title;
if (cat) {
cat = cat.slice(Math.max(0, cat.indexOf(":") + 1)).replace(/_/g, " ");
if (cat === getMessage("disambig_category")) {
is_dab = true;
break;
} else if (cat === getMessage("redir_category")) {
is_redir = true;
break;
}
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
if (!is_redir && !is_dab) {
return;
}
if (!links || links.length === 0) {
return;
}
const titles = [];
for (i = 0; i < links.length; i++) {
if (
// Category namespace -- always true since we ask only for the category links
links[i].ns === 14 && // Name not empty
links[i].title && links[i].title.length > 0
) {
let match = links[i].title;
match = match.slice(Math.max(0, match.indexOf(":") + 1));
if (!HC.blacklist || !HC.blacklist.test(match)) {
titles[titles.length] = match;
}
}
}
if (titles.length === 0) {
return;
}
for (i = 0; i < toResolve.length; i++) {
if (i && toResolve[i].dabInputCleaned !== page.title.slice(Math.max(0, page.title.indexOf(":") + 1))) {
continue;
}
toResolve[i].inputExists = true;
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 ? "" : "|".concat(toResolve[i].currentKey));
}
}
};
const resolveRedirects = (toResolve, params) => {
if (!params || !params.query || !params.query.pages) {
return;
}
for (const p in params.query.pages) {
if (!Object.hasOwn(params.query.pages, p)) {
continue;
}
resolveOne(params.query.pages[p], toResolve);
}
};
const resolveMulti = (toResolve, callback) => {
let i;
for (i = 0; i < toResolve.length; i++) {
toResolve[i].dab = null;
toResolve[i].dabInput = toResolve[i].lastInput;
}
if (noSuggestions) {
callback(toResolve);
return;
}
const params = {
action: "query",
format: "json",
prop: "info|links|categories|categoryinfo",
plnamespace: "14",
pllimit: toResolve.length * 10,
cllimit: toResolve.length * 10
};
const titles = [];
for (i = 0; i < toResolve.length; i++) {
let v = toResolve[i].dabInput;
v = replaceShortcuts(v, HC.shortcuts);
toResolve[i].dabInputCleaned = v;
titles[titles.length] = "Category:".concat(v);
}
params.titles = titles.join("|");
api.get(params).done((json) => {
resolveRedirects(toResolve, json);
callback(toResolve);
}).fail((req) => {
if (!req) {
noSuggestions = true;
}
callback(toResolve);
});
};
const makeActive = (which) => {
if (which.is_active) {
return;
}
for (var _i = 0, _editors = editors; _i < _editors.length; _i++) {
const editor = _editors[_i];
if (editor !== which) {
editor.inactivate();
}
}
which.is_active = true;
if (which.dab) {
showDab(which);
} else {
const expectedInput = which.lastRealInput || which.lastInput || "";
const actualValue = which.text.value || "";
if (expectedInput.length === 0 && actualValue.length > 0 || expectedInput.length > 0 && actualValue.indexOf(expectedInput)) {
which.showsList = false;
const v = actualValue.split("|");
[which.lastInput] = v;
which.lastRealInput = which.lastInput;
if (v.length > 1) {
[, which.currentKey] = v;
}
which.lastSelection && (which.lastSelection = {
start: v[0].length,
end: v[0].length
});
}
if (which.showsList) {
which.displayList();
}
if (which.lastSelection) {
setTimeout(() => {
which.setSelection(which.lastSelection.start, which.lastSelection.end);
}, 0);
}
}
};
const showDab = (which) => {
if (which.is_active) {
which.showSuggestions(which.dab, false, null, null);
which.dab = null;
} else {
makeActive(which);
}
};
const multiSubmit = () => {
const toResolve = [];
for (var _i2 = 0, _editors2 = editors; _i2 < _editors2.length; _i2++) {
const editor = _editors2[_i2];
if (editor.state === CHANGE_PENDING || editor.state === OPEN) {
toResolve[toResolve.length] = editor;
}
}
if (toResolve.length === 0) {
initiateEdit((failure) => {
performChanges(failure);
}, (msg) => {
void mw.notify(msg, {
tag: "hotCat"
});
});
return;
}
resolveMulti(toResolve, (resolved) => {
let firstDab = null;
let dontChange = false;
var _iterator2 = _createForOfIteratorHelper(resolved), _step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done; ) {
const element = _step2.value;
if (element.lastInput === element.dabInput) {
if (element.dab) {
firstDab || (firstDab = element);
} else if (element.acceptCheck(true)) {
element.commit();
}
} else {
dontChange = true;
}
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
if (firstDab) {
showDab(firstDab);
} else if (!dontChange) {
initiateEdit((failure) => {
performChanges(failure);
}, (msg) => {
void mw.notify(msg, {
tag: "hotCat"
});
});
}
});
};
const setMultiInput = () => {
if (commitButton || onUpload) {
return;
}
commitButton = make("input");
commitButton.type = "button";
commitButton.value = getMessage("messages-commit");
commitButton.addEventListener("click", multiSubmit);
if (multiSpan) {
multiSpan.replaceWith(commitButton);
} else {
catLine.append(commitButton);
}
};
const checkMultiInput = () => {
if (!commitButton) {
return;
}
let hasChanges = false;
for (var _i3 = 0, _editors3 = editors; _i3 < _editors3.length; _i3++) {
const editor = _editors3[_i3];
if (editor.state !== UNCHANGED) {
hasChanges = true;
break;
}
}
commitButton.disabled = !hasChanges;
};
const suggestionEngines = {
opensearch: {
uri: "".concat(mw.config.get("wgScriptPath"), "/api.php?format=json&action=opensearch&namespace=14&limit=30&search=Category:$1"),
// $1 = search term
// Function to convert result of uri into an array of category names
handler: (queryResult, queryKey) => {
if (queryResult && queryResult.length >= 2) {
const key = queryResult[0].slice(Math.max(0, queryResult[0].indexOf(":") + 1));
const [, titles] = queryResult;
let exists = false;
cat_prefix || (cat_prefix = new RegExp("^(".concat(HC.category_regexp, "):")));
for (let i = 0; i < titles.length; i++) {
cat_prefix.lastIndex = 0;
const m = cat_prefix.exec(titles[i]);
if (m && m.length > 1) {
titles[i] = titles[i].slice(Math.max(0, titles[i].indexOf(":") + 1));
if (key === titles[i]) {
exists = true;
}
} else {
titles.splice(i, 1);
i--;
}
}
titles.exists = exists;
if (queryKey !== key) {
titles.normalized = key;
}
return titles;
}
return null;
}
},
internalsearch: {
uri: "".concat(mw.config.get("wgScriptPath"), "/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1&apprefix=$1"),
handler: (queryResult) => {
if (queryResult && queryResult.query && queryResult.query.allpages) {
const titles = queryResult.query.allpages;
for (let i = 0; i < titles.length; i++) {
titles[i] = titles[i].title.slice(Math.max(0, titles[i].title.indexOf(":") + 1));
}
return titles;
}
return null;
}
},
exists: {
uri: "".concat(mw.config.get("wgScriptPath"), "/api.php?format=json&action=query&prop=info&titles=Category:$1"),
handler: (queryResult, queryKey) => {
if (queryResult && queryResult.query && queryResult.query.pages && !queryResult.query.pages[-1]) {
for (const p in queryResult.query.pages) {
if (!Object.hasOwn(queryResult.query.pages, p)) {
continue;
}
let _title = queryResult.query.pages[p].title;
_title = _title.slice(Math.max(0, _title.indexOf(":") + 1));
const titles = [_title];
titles.exists = true;
if (queryKey !== _title) {
titles.normalized = _title;
}
return titles;
}
}
return null;
}
},
subcategories: {
uri: "".concat(mw.config.get("wgScriptPath"), "/api.php?format=json&action=query&list=categorymembers&cmtype=subcat&cmlimit=max&cmtitle=Category:$1"),
handler: (queryResult) => {
if (queryResult && queryResult.query && queryResult.query.categorymembers) {
const titles = queryResult.query.categorymembers;
for (let i = 0; i < titles.length; i++) {
titles[i] = titles[i].title.slice(Math.max(0, titles[i].title.indexOf(":") + 1));
}
return titles;
}
return null;
}
},
parentcategories: {
uri: "".concat(mw.config.get("wgScriptPath"), "/api.php?format=json&action=query&prop=categories&titles=Category:$1&cllimit=max"),
handler: (queryResult) => {
if (queryResult && queryResult.query && queryResult.query.pages) {
for (const p in queryResult.query.pages) {
if (queryResult.query.pages[p].categories) {
const titles = queryResult.query.pages[p].categories;
for (let i = 0; i < titles.length; i++) {
titles[i] = titles[i].title.slice(Math.max(0, titles[i].title.indexOf(":") + 1));
}
return titles;
}
}
}
return null;
}
}
};
const suggestionConfigs = {
searchindex: {
name: "Search index",
engines: ["opensearch"],
cache: {},
show: true,
temp: false,
noCompletion: false
},
pagelist: {
name: "Page list",
engines: ["internalsearch", "exists"],
cache: {},
show: true,
temp: false,
noCompletion: false
},
combined: {
name: "Combined search",
engines: ["opensearch", "internalsearch"],
cache: {},
show: true,
temp: false,
noCompletion: false
},
subcat: {
name: "Subcategories",
engines: ["subcategories"],
cache: {},
show: true,
temp: true,
noCompletion: true
},
parentcat: {
name: "Parent categories",
engines: ["parentcategories"],
cache: {},
show: true,
temp: true,
noCompletion: true
}
};
const BS = 8;
const TAB = 9;
const RET = 13;
const ESC = 27;
const SPACE = 32;
const PGUP = 33;
const PGDOWN = 34;
const UP = 38;
const DOWN = 40;
const DEL = 46;
const IME = 229;
class CategoryEditor {
constructor(...args) {
this.isCompositionStart = false;
this.callbackObj = null;
this.initialize(...args);
}
initialize(line, span, after, key, is_hidden) {
if (span) {
if (is_rtl) {
span.dir = "rtl";
}
this.isAddCategory = false;
this.catLink = span.firstChild;
this.originalCategory = after;
this.originalKey = key && key.length > 1 ? key.slice(1) : null;
this.originalExists = !hasClass(this.catLink, "new");
this.makeLinkSpan();
if (!this.originalExists && this.upDownLinks) {
this.upDownLinks.style.display = "none";
}
span.append(this.linkSpan);
} else {
this.isAddCategory = true;
this.originalCategory = "";
this.originalKey = null;
this.originalExists = false;
if (!newDOM) {
span = make("span");
span.className = "noprint";
if (key) {
span.append(make(" | ", true));
if (after) {
after.parentNode.insertBefore(span, after.nextSibling);
after = after.nextSibling;
} else if (line) {
line.append(span);
}
} else if (line && line.firstChild) {
span.append(make(" ", true));
line.append(span);
}
}
this.linkSpan = make("span");
this.linkSpan.className = "noprint nopopups hotcatlink";
const link = make("a");
link.href = "#catlinks";
link.addEventListener("click", this.open.bind(this));
link.append(make(HC.links.add, true));
link.title = getMessage("tooltips-add");
this.linkSpan.append(link);
span = make(newDOM ? "li" : "span");
span.className = "noprint";
if (is_rtl) {
span.dir = "rtl";
}
span.append(this.linkSpan);
if (after) {
after.parentNode.insertBefore(span, after.nextSibling);
} else if (line) {
line.append(span);
}
this.normalLinks = null;
this.undelLink = null;
this.catLink = null;
}
this.originalHidden = is_hidden;
this.line = line;
this.engine = HC.suggestions;
this.span = span;
this.currentCategory = this.originalCategory;
this.currentExists = this.originalExists;
this.currentHidden = this.originalHidden;
this.currentKey = this.originalKey;
this.state = UNCHANGED;
this.lastSavedState = UNCHANGED;
this.lastSavedCategory = this.originalCategory;
this.lastSavedKey = this.originalKey;
this.lastSavedExists = this.originalExists;
this.lastSavedHidden = this.originalHidden;
if (this.catLink && this.currentKey) {
this.catLink.title = this.currentKey;
}
editors[editors.length] = this;
}
makeLinkSpan() {
this.normalLinks = make("span");
let link = null;
if (this.originalCategory && this.originalCategory.length > 0) {
link = make("a");
link.href = "#catlinks";
link.addEventListener("click", this.remove.bind(this));
link.append(make(HC.links.remove, true));
link.title = getMessage("tooltips-remove");
this.normalLinks.append(make(" ", true));
this.normalLinks.append(link);
}
if (!HC.template_categories[this.originalCategory]) {
link = make("a");
link.href = "#catlinks";
link.addEventListener("click", this.open.bind(this));
link.append(make(HC.links.change, true));
link.title = getMessage("tooltips-change");
this.normalLinks.append(make(" ", true));
this.normalLinks.append(link);
if (!noSuggestions && HC.use_up_down) {
this.upDownLinks = make("span");
link = make("a");
link.href = "#catlinks";
link.addEventListener("click", this.down.bind(this));
link.append(make(HC.links.down, true));
link.title = getMessage("tooltips-down");
this.upDownLinks.append(make(" ", true));
this.upDownLinks.append(link);
link = make("a");
link.href = "#catlinks";
link.addEventListener("click", this.up.bind(this));
link.append(make(HC.links.up, true));
link.title = getMessage("tooltips-up");
this.upDownLinks.append(make(" ", true));
this.upDownLinks.append(link);
this.normalLinks.append(this.upDownLinks);
}
}
this.linkSpan = make("span");
this.linkSpan.className = "noprint nopopups hotcatlink";
this.linkSpan.append(this.normalLinks);
this.undelLink = make("span");
this.undelLink.className = "nopopups hotcatlink";
this.undelLink.style.display = "none";
link = make("a");
link.href = "#catlinks";
link.addEventListener("click", this.restore.bind(this));
link.append(make(HC.links.restore, true));
link.title = getMessage("tooltips-restore");
this.undelLink.append(make(" ", true));
this.undelLink.append(link);
this.linkSpan.append(this.undelLink);
}
invokeSuggestions(dont_autocomplete) {
if (this.engine && suggestionConfigs[this.engine] && suggestionConfigs[this.engine].temp && !dont_autocomplete) {
this.engine = HC.suggestions;
}
this.state = CHANGE_PENDING;
const self = this;
setTimeout(() => {
self.textchange(dont_autocomplete);
}, HC.suggest_delay);
}
makeForm() {
const form = make("form");
form.method = "POST";
form.addEventListener("submit", this.accept.bind(this));
this.form = form;
const self = this;
const text = make("input");
text.type = "text";
text.size = HC.editbox_width;
if (!noSuggestions) {
text.addEventListener("keyup", (event) => {
const key = event.key || 0;
if (self.ime && self.lastKey === IME && !self.usesComposition && (key === TAB || key === RET || key === ESC || key === SPACE)) {
self.ime = false;
}
if (self.ime) {
return true;
}
if (key === UP || key === DOWN || key === PGUP || key === PGDOWN) {
if (self.keyCount === 0) {
return self.processKey(event);
}
} else {
if (key === ESC && self.lastKey !== IME && !self.resetKeySelection()) {
self.cancel();
return;
}
if (self.isCompositionStart) {
return;
}
self.invokeSuggestions(key === BS || key === DEL || key === ESC);
}
return true;
});
text.addEventListener("keydown", (event) => {
const key = event.key || 0;
self.lastKey = key;
self.keyCount = 0;
if (!self.ime && key === IME && !self.usesComposition) {
self.ime = true;
} else if (self.ime && key !== IME && !(key >= 16 && key <= 20 || key >= 91 && key <= 93 || key === 144)) {
self.ime = false;
}
if (self.ime) {
return true;
}
if (key === RET) {
return self.accept(event);
}
return key === ESC ? evtKill(event) : true;
});
text.addEventListener("keypress", (event) => {
self.keyCount++;
return self.processKey(event);
});
text.addEventListener("focus", () => {
makeActive(self);
});
$(text).on("blur", this.saveView.bind(this));
try {
$(text).on("compositionstart", () => {
self.lastKey = IME;
self.usesComposition = true;
self.ime = true;
self.isCompositionStart = true;
});
$(text).on("compositionend", () => {
self.lastKey = IME;
self.usesComposition = true;
self.ime = false;
self.isCompositionStart = false;
});
$(text).on("textInput", () => {
self.ime = false;
if (self.isCompositionStart) {
return;
}
self.invokeSuggestions(false);
});
} catch (e) {
}
$(text).on("blur", () => {
self.usesComposition = false;
self.ime = false;
});
}
this.text = text;
this.icon = make("img");
let list = null;
if (!noSuggestions) {
list = make("select");
list.addEventListener("click", () => {
if (self.highlightSuggestion(0)) {
self.textchange(false, true);
}
});
list.addEventListener("dblclick", (e) => {
if (self.highlightSuggestion(0)) {
self.accept(e);
}
});
list.addEventListener("change", () => {
self.highlightSuggestion(0);
self.text.focus();
});
list.addEventListener("keyup", (event) => {
if (event.key === ESC) {
self.resetKeySelection();
self.text.focus();
setTimeout(() => {
self.textchange(true);
}, HC.suggest_delay);
} else if (event.key === RET) {
self.accept(event);
}
});
if (!HC.fixed_search) {
const engineSelector = make("select");
for (const key in suggestionConfigs) {
if (suggestionConfigs[key].show) {
const opt = make("option");
opt.value = key;
if (key === this.engine) {
opt.selected = true;
}
opt.append(make(suggestionConfigs[key].name, true));
engineSelector.append(opt);
}
}
engineSelector.addEventListener("change", () => {
self.engine = self.engineSelector.options[self.engineSelector.selectedIndex].value;
self.text.focus();
self.textchange(true, true);
});
this.engineSelector = engineSelector;
}
}
this.list = list;
const button_label = (_id, defaultText) => {
const label = null;
if (!label || !label.data) {
return defaultText;
}
return label.data;
};
const OK = make("input");
OK.type = "button";
OK.value = button_label("wpOkUploadLbl", getMessage("messages-ok"));
OK.addEventListener("click", this.accept.bind(this));
this.ok = OK;
const cancel = make("input");
cancel.type = "button";
cancel.value = button_label("wpCancelUploadLbl", getMessage("messages-cancel"));
cancel.addEventListener("click", this.cancel.bind(this));
this.cancelButton = cancel;
const span = make("span");
span.className = "hotcatinput";
span.style.position = "relative";
span.append(text);
span.style.whiteSpace = "nowrap";
if (list) {
span.append(list);
}
if (this.engineSelector) {
span.append(this.engineSelector);
}
if (!noSuggestions) {
span.append(this.icon);
}
span.append(OK);
span.append(cancel);
form.append(span);
form.style.display = "none";
this.span.append(form);
}
display(event) {
if (this.isAddCategory && !onUpload && this.line) {
new CategoryEditor(this.line, null, this.span, true);
}
if (!commitButton && !onUpload) {
for (var _i4 = 0, _editors4 = editors; _i4 < _editors4.length; _i4++) {
const editor = _editors4[_i4];
if (editor.state !== UNCHANGED) {
setMultiInput();
break;
}
}
}
if (!this.form) {
this.makeForm();
}
if (this.list) {
this.list.style.display = "none";
}
if (this.engineSelector) {
this.engineSelector.style.display = "none";
}
this.currentCategory = this.lastSavedCategory;
this.currentExists = this.lastSavedExists;
this.currentHidden = this.lastSavedHidden;
this.currentKey = this.lastSavedKey;
this.icon.src = this.currentExists ? HC.existsYes : HC.existsNo;
this.text.value = this.currentCategory + (this.currentKey === null ? "" : "|".concat(this.currentKey));
this.originalState = this.state;
this.lastInput = this.currentCategory;
this.inputExists = this.currentExists;
this.state = this.state === UNCHANGED ? OPEN : CHANGE_PENDING;
this.lastSelection = {
start: this.currentCategory.length,
end: this.currentCategory.length
};
this.showsList = false;
if (this.catLink) {
this.catLink.style.display = "none";
}
this.linkSpan.style.display = "none";
this.form.style.display = "inline";
this.ok.disabled = false;
const result = evtKill(event);
this.text.focus();
this.text.readOnly = false;
checkMultiInput();
return result;
}
show(event, engine, readOnly) {
const result = this.display(event);
const v = this.lastSavedCategory;
if (v.length === 0) {
return result;
}
this.text.readOnly = !!readOnly;
this.engine = engine;
this.textchange(false, true);
return result;
}
open(event) {
return this.show(event, this.engine && suggestionConfigs[this.engine].temp ? HC.suggestions : this.engine);
}
down(event) {
return this.show(event, "subcat", true);
}
up(event) {
return this.show(event, "parentcat");
}
cancel() {
if (this.isAddCategory && !onUpload) {
this.removeEditor();
return;
}
this.inactivate();
this.form.style.display = "none";
if (this.catLink) {
this.catLink.style.display = "";
}
this.linkSpan.style.display = "";
this.state = this.originalState;
this.currentCategory = this.lastSavedCategory;
this.currentKey = this.lastSavedKey;
this.currentExists = this.lastSavedExists;
this.currentHidden = this.lastSavedHidden;
if (this.catLink) {
this.catLink.title = this.currentKey && this.currentKey.length > 0 ? this.currentKey : "";
}
if (this.state === UNCHANGED) {
if (this.catLink) {
this.catLink.style.backgroundColor = "transparent";
}
} else if (!onUpload) {
try {
this.catLink.style.backgroundColor = HC.bg_changed;
} catch (e) {
}
}
checkMultiInput();
}
removeEditor() {
if (!newDOM) {
const next = this.span.nextSibling;
if (next) {
next.remove();
}
}
if (this.span && this.span.parentNode) {
this.span.remove();
}
for (let i = 0; i < editors.length; i++) {
if (editors[i] === this) {
editors.splice(i, 1);
break;
}
}
checkMultiInput();
}
rollback(event) {
this.undoLink.remove();
this.undoLink = null;
this.currentCategory = this.originalCategory;
this.currentKey = this.originalKey;
this.currentExists = this.originalExists;
this.currentHidden = this.originalHidden;
this.lastSavedCategory = this.originalCategory;
this.lastSavedKey = this.originalKey;
this.lastSavedExists = this.originalExists;
this.lastSavedHidden = this.originalHidden;
this.state = UNCHANGED;
if (!this.currentCategory || this.currentCategory.length === 0) {
this.removeEditor();
} else {
this.catLink.firstChild.remove();
this.catLink.append(make(this.currentCategory, true));
this.catLink.href = wikiPagePath("".concat(HC.category_canonical, ":").concat(this.currentCategory));
this.catLink.title = this.currentKey || "";
this.catLink.className = this.currentExists ? "" : "new";
this.catLink.style.backgroundColor = "transparent";
if (this.upDownLinks) {
this.upDownLinks.style.display = this.currentExists ? "" : "none";
}
checkMultiInput();
}
return evtKill(event);
}
inactivate() {
if (this.list) {
this.list.style.display = "none";
}
if (this.engineSelector) {
this.engineSelector.style.display = "none";
}
this.is_active = false;
}
acceptCheck(dontCheck) {
this.sanitizeInput();
const value = this.text.value.split("|");
let key = null;
if (value.length > 1) {
[, key] = value;
}
let v = value[0].replace(/_/g, " ").trim();
if (HC.capitalizePageNames) {
v = capitalize(v);
}
this.lastInput = v;
v = replaceShortcuts(v, HC.shortcuts);
if (v.length === 0) {
this.cancel();
return false;
}
if (!dontCheck && (conf.wgNamespaceNumber === 14 && v === conf.wgTitle || HC.blacklist && HC.blacklist.test(v))) {
this.cancel();
return false;
}
this.currentCategory = v;
this.currentKey = key;
this.currentExists = this.inputExists;
return true;
}
accept(event) {
this.noCommit = evtKeys(event) === 1;
const result = evtKill(event);
if (this.acceptCheck()) {
const toResolve = [this];
const original = this.currentCategory;
resolveMulti(toResolve, (resolved) => {
if (resolved[0].dab) {
showDab(resolved[0]);
} else if (resolved[0].acceptCheck(true)) {
resolved[0].commit(resolved[0].currentCategory === original ? null : getMessage("messages-cat_resolved", original));
}
});
}
return result;
}
close() {
if (!this.catLink) {
this.catLink = make("a");
this.catLink.append(make("foo", true));
this.catLink.style.display = "none";
this.span.insertBefore(this.catLink, this.span.firstChild.nextSibling);
}
this.catLink.firstChild.remove();
this.catLink.append(make(this.currentCategory, true));
this.catLink.href = wikiPagePath("".concat(HC.category_canonical, ":").concat(this.currentCategory));
this.catLink.className = this.currentExists ? "" : "new";
this.lastSavedCategory = this.currentCategory;
this.lastSavedKey = this.currentKey;
this.lastSavedExists = this.currentExists;
this.lastSavedHidden = this.currentHidden;
this.inactivate();
this.form.style.display = "none";
this.catLink.title = this.currentKey || "";
this.catLink.style.display = "";
if (this.isAddCategory) {
if (onUpload && this.line) {
new CategoryEditor(this.line, null, this.span, true);
}
this.isAddCategory = false;
this.linkSpan.remove();
this.makeLinkSpan();
this.span.append(this.linkSpan);
}
if (!this.undoLink) {
const span = make("span");
const link = make("a");
link.href = "#catlinks";
link.addEventListener("click", this.rollback.bind(this));
link.append(make(HC.links.undo, true));
link.title = getMessage("tooltips-undo");
span.append(make(" ", true));
span.append(link);
this.normalLinks.append(span);
this.undoLink = span;
if (!onUpload) {
try {
this.catLink.style.backgroundColor = HC.bg_changed;
} catch (e) {
}
}
}
if (this.upDownLinks) {
this.upDownLinks.style.display = this.lastSavedExists ? "" : "none";
}
this.linkSpan.style.display = "";
this.state = CHANGED;
checkMultiInput();
}
commit() {
if (this.currentCategory === this.originalCategory && (this.currentKey === this.originalKey || this.currentKey === null && this.originalKey.length === 0) || conf.wgNamespaceNumber === 14 && this.currentCategory === conf.wgTitle || HC.blacklist && HC.blacklist.test(this.currentCategory)) {
this.cancel();
return;
}
this.close();
if (!commitButton && !onUpload) {
const self = this;
initiateEdit((failure) => {
performChanges(failure, self);
}, (msg) => {
void mw.notify(msg, {
tag: "hotCat"
});
});
}
}
remove(event) {
this.doRemove(evtKeys(event) === 1);
return evtKill(event);
}
doRemove(noCommit) {
if (this.isAddCategory) {
this.cancel();
return;
}
if (!commitButton && !onUpload) {
for (var _i5 = 0, _editors5 = editors; _i5 < _editors5.length; _i5++) {
const editor = _editors5[_i5];
if (editor.state !== UNCHANGED) {
setMultiInput();
break;
}
}
}
if (commitButton) {
this.catLink.title = "";
this.catLink.style.cssText += "; text-decoration : line-through !important;";
try {
this.catLink.style.backgroundColor = HC.bg_changed;
} catch (e) {
}
this.originalState = this.state;
this.state = DELETED;
this.normalLinks.style.display = "none";
this.undelLink.style.display = "";
checkMultiInput();
} else if (onUpload) {
this.removeEditor();
} else {
this.originalState = this.state;
this.state = DELETED;
this.noCommit = noCommit || HC.del_needs_diff;
const self = this;
initiateEdit((failure) => {
performChanges(failure, self);
}, (msg) => {
self.state = self.originalState;
void mw.notify(msg, {
tag: "hotCat"
});
});
}
}
restore(event) {
this.catLink.title = this.currentKey || "";
this.catLink.style.textDecoration = "";
this.state = this.originalState;
if (this.state === UNCHANGED) {
this.catLink.style.backgroundColor = "transparent";
} else {
try {
this.catLink.style.backgroundColor = HC.bg_changed;
} catch (e) {
}
}
this.normalLinks.style.display = "";
this.undelLink.style.display = "none";
checkMultiInput();
return evtKill(event);
}
// Internal operations
selectEngine(engineName) {
if (!this.engineSelector) {
return;
}
for (let i = 0; i < this.engineSelector.options.length; i++) {
this.engineSelector.options[i].selected = this.engineSelector.options[i].value === engineName;
}
}
sanitizeInput() {
let v = this.text.value || "";
v = v.replace(/^(\s|_)+/, "");
const re = new RegExp("^(".concat(HC.category_regexp, "):"));
if (re.test(v)) {
v = v.slice(Math.max(0, v.indexOf(":") + 1)).replace(/^(\s|_)+/, "");
}
v = v.replace(/\u200E$/, "");
if (HC.capitalizePageNames) {
v = capitalize(v);
}
if (this.text.value !== null && this.text.value !== v) {
this.text.value = v;
}
}
makeCall(url, callbackObj, engine, queryKey, cleanKey) {
let cb = callbackObj;
const e = engine;
const v = queryKey;
const z = cleanKey;
const self = this;
const done = () => {
cb.callsMade++;
if (cb.callsMade === cb.nofCalls) {
if (cb.exists) {
cb.allTitles.exists = true;
}
if (cb.normalized) {
cb.allTitles.normalized = cb.normalized;
}
if (!cb.dontCache && !suggestionConfigs[cb.engineName].cache[z]) {
suggestionConfigs[cb.engineName].cache[z] = cb.allTitles;
}
self.text.readOnly = false;
if (!cb.cancelled) {
self.showSuggestions(cb.allTitles, cb.noCompletion, v, cb.engineName);
}
if (cb === self.callbackObj) {
self.callbackObj = null;
}
cb = void 0;
}
};
$.getJSON(url, (json) => {
const titles = e.handler(json, z);
if (titles && titles.length > 0) {
cb.allTitles = cb.allTitles === null ? titles : [...cb.allTitles, ...(0, import_ext_gadget2.generateArray)(titles)];
if (titles.exists) {
cb.exists = true;
}
if (titles.normalized) {
cb.normalized = titles.normalized;
}
}
done();
}).fail((req) => {
if (!req) {
noSuggestions = true;
}
cb.dontCache = true;
done();
});
}
textchange(dont_autocomplete, force) {
makeActive(this);
this.sanitizeInput();
let v = this.text.value;
const pipe = v.indexOf("|");
if (pipe === -1) {
this.currentKey = null;
} else {
this.currentKey = v.slice(Math.max(0, pipe + 1));
v = v.slice(0, Math.max(0, pipe));
}
if (this.lastInput === v && !force) {
return;
}
if (this.lastInput !== v) {
checkMultiInput();
}
this.lastInput = v;
this.lastRealInput = v;
this.ok.disabled = v.length > 0 && HC.blacklist && HC.blacklist.test(v);
if (noSuggestions) {
if (this.list) {
this.list.style.display = "none";
}
if (this.engineSelector) {
this.engineSelector.style.display = "none";
}
if (this.icon) {
this.icon.style.display = "none";
}
return;
}
if (v.length === 0) {
this.showSuggestions([]);
return;
}
let cleanKey = v.replace(/[\u200E\u200F\u202A-\u202E]/g, "").replace(wikiTextBlankRE, " ");
cleanKey = replaceShortcuts(cleanKey, HC.shortcuts);
cleanKey = cleanKey.trim();
if (cleanKey.length === 0) {
this.showSuggestions([]);
return;
}
if (this.callbackObj) {
this.callbackObj.cancelled = true;
}
const engineName = suggestionConfigs[this.engine] ? this.engine : "combined";
dont_autocomplete || (dont_autocomplete = suggestionConfigs[engineName].noCompletion);
if (suggestionConfigs[engineName].cache[cleanKey]) {
this.showSuggestions(suggestionConfigs[engineName].cache[cleanKey], dont_autocomplete, v, engineName);
return;
}
const {
engines
} = suggestionConfigs[engineName];
this.callbackObj = {
allTitles: null,
callsMade: 0,
nofCalls: engines.length,
noCompletion: dont_autocomplete,
engineName
};
this.makeCalls(engines, this.callbackObj, v, cleanKey);
}
makeCalls(engines, cb, v, cleanKey) {
var _iterator3 = _createForOfIteratorHelper(engines), _step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done; ) {
const engine_ = _step3.value;
const engine = suggestionEngines[engine_];
const url = conf.wgScriptPath + engine.uri.replace(/\$1/g, encodeURIComponent(cleanKey));
this.makeCall(url, cb, engine, v, cleanKey);
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
}
showSuggestions(titles, dontAutocomplete, queryKey, engineName) {
this.text.readOnly = false;
this.dab = null;
this.showsList = false;
if (!this.list) {
return;
}
if (noSuggestions) {
if (this.list) {
this.list.style.display = "none";
}
if (this.engineSelector) {
this.engineSelector.style.display = "none";
}
if (this.icon) {
this.icon.style.display = "none";
}
this.inputExists = true;
return;
}
this.engineName = engineName;
if (engineName) {
if (!this.engineSelector) {
this.engineName = null;
}
} else if (this.engineSelector) {
this.engineSelector.style.display = "none";
}
if (queryKey) {
if (this.lastInput.indexOf(queryKey)) {
return;
}
if (this.lastQuery && this.lastInput.indexOf(this.lastQuery) === 0 && this.lastQuery.length > queryKey.length) {
return;
}
}
this.lastQuery = queryKey;
let v = this.text.value.split("|");
const key = v.length > 1 ? "|".concat(v[1]) : "";
v = HC.capitalizePageNames ? capitalize(v[0]) : v[0];
let vNormalized = v;
const knownToExist = titles && titles.exists;
let i;
if (titles) {
if (titles.normalized && v.indexOf(queryKey) === 0) {
vNormalized = titles.normalized + v.slice(queryKey.length);
}
const vLow = vNormalized.toLowerCase();
if (HC.blacklist) {
for (i = 0; i < titles.length; i++) {
if (HC.blacklist.test(titles[i])) {
titles.splice(i, 1);
i--;
}
}
}
titles.sort((a, b) => {
if (a === b) {
return 0;
}
if (a.indexOf(b) === 0) {
return 1;
}
if (b.indexOf(a) === 0) {
return -1;
}
let prefixMatchA = a.indexOf(vNormalized) === 0 ? 1 : 0;
let prefixMatchB = b.indexOf(vNormalized) === 0 ? 1 : 0;
if (prefixMatchA !== prefixMatchB) {
return prefixMatchB - prefixMatchA;
}
const aLow = a.toLowerCase();
const bLow = b.toLowerCase();
prefixMatchA = aLow.indexOf(vLow) === 0 ? 1 : 0;
prefixMatchB = bLow.indexOf(vLow) === 0 ? 1 : 0;
if (prefixMatchA !== prefixMatchB) {
return prefixMatchB - prefixMatchA;
}
if (a < b) {
return -1;
}
if (b < a) {
return 1;
}
return 0;
});
for (i = 0; i < titles.length; i++) {
if (i + 1 < titles.length && titles[i] === titles[i + 1] || conf.wgNamespaceNumber === 14 && titles[i] === conf.wgTitle) {
titles.splice(i, 1);
i--;
}
}
}
if (!titles || titles.length === 0) {
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.icon) {
this.icon.src = HC.existsNo;
}
this.inputExists = false;
}
return;
}
const [firstTitle] = titles;
const completed = this.autoComplete(firstTitle, v, vNormalized, key, dontAutocomplete);
const existing = completed || knownToExist || firstTitle === replaceShortcuts(v, HC.shortcuts);
if (engineName && suggestionConfigs[engineName] && !suggestionConfigs[engineName].temp) {
this.icon.src = existing ? HC.existsYes : HC.existsNo;
this.inputExists = existing;
}
if (completed) {
this.lastInput = firstTitle;
if (titles.length === 1) {
this.list.style.display = "none";
if (this.engineSelector) {
this.engineSelector.style.display = "none";
}
return;
}
}
while (this.list.firstChild) {
this.list.firstChild.remove();
}
for (i = 0; i < titles.length; i++) {
const opt = make("option");
opt.append(make(titles[i], true));
opt.selected = completed && i === 0;
this.list.append(opt);
}
this.displayList();
}
displayList() {
this.showsList = true;
if (!this.is_active) {
this.list.style.display = "none";
if (this.engineSelector) {
this.engineSelector.style.display = "none";
}
return;
}
let nofItems = Math.min(this.list.options.length, HC.listSize);
if (nofItems <= 1) {
nofItems = 2;
}
this.list.size = nofItems;
this.list.style.align = is_rtl ? "right" : "left";
this.list.style.zIndex = 5;
this.list.style.position = "absolute";
const anchor = is_rtl ? "right" : "left";
let listh = 0;
if (this.list.style.display === "none") {
this.list.style.top = "".concat(this.text.offsetTop, "px");
this.list.style[anchor] = "-10000px";
this.list.style.display = "";
listh = this.list.offsetHeight;
this.list.style.display = "none";
} else {
listh = this.list.offsetHeight;
}
let maxListHeight = listh;
if (nofItems < HC.listSize) {
maxListHeight = listh / nofItems * HC.listSize;
}
const viewport = (what) => {
const s = "client".concat(what);
return document.documentElement ? document.documentElement[s] : 0;
};
const scroll_offset = (what) => {
const s = "scroll".concat(what);
let result = document.documentElement ? document.documentElement[s] : 0;
if (is_rtl && what === "Left") {
if (result < 0) {
result = -result;
}
if (!is_webkit) {
result = scroll_offset("Width") - viewport("Width") - result;
}
}
return result;
};
const position = (node) => {
if (node.getBoundingClientRect) {
const box = node.getBoundingClientRect();
return {
x: Math.round(box.left + scroll_offset("Left")),
y: Math.round(box.top + scroll_offset("Top"))
};
}
let t = 0;
let l = 0;
do {
t += node.offsetTop || 0;
l += node.offsetLeft || 0;
node = node.offsetParent;
} while (node);
return {
x: l,
y: t
};
};
const textPos = position(this.text);
const nl = 0;
let nt = 0;
let offset = 0;
const textBoxWidth = this.text.offsetWidth || this.text.clientWidth;
if (this.engineName) {
this.engineSelector.style.zIndex = 5;
this.engineSelector.style.position = "absolute";
this.engineSelector.style.width = "".concat(textBoxWidth, "px");
if (this.engineSelector.style.display === "none") {
this.engineSelector.style[anchor] = "-10000px";
this.engineSelector.style.top = "0";
this.engineSelector.style.display = "";
offset = this.engineSelector.offsetHeight;
this.engineSelector.style.display = "none";
} else {
offset = this.engineSelector.offsetHeight;
}
this.engineSelector.style[anchor] = "".concat(nl, "px");
}
if (textPos.y < maxListHeight + offset + 1) {
nt = this.text.offsetHeight + offset + 1;
if (this.engineName) {
this.engineSelector.style.top = "".concat(this.text.offsetHeight, "px");
}
} else {
nt = -listh - offset - 1;
if (this.engineName) {
this.engineSelector.style.top = "".concat(-(offset + 1), "px");
}
}
this.list.style.top = "".concat(nt, "px");
this.list.style.width = "";
this.list.style[anchor] = "".concat(nl, "px");
if (this.engineName) {
this.selectEngine(this.engineName);
this.engineSelector.style.display = "";
}
this.list.style.display = "block";
if (this.list.offsetWidth < textBoxWidth) {
this.list.style.width = "".concat(textBoxWidth, "px");
return;
}
const scroll = scroll_offset("Left");
const view_w = viewport("Width");
let w = this.list.offsetWidth;
const l_pos = position(this.list);
let left = l_pos.x;
let right = left + w;
if (left < scroll || right > scroll + view_w) {
if (w > view_w) {
w = view_w;
this.list.style.width = "".concat(w, "px");
if (is_rtl) {
left = right - w;
} else {
right = left + w;
}
}
let relative_offset = 0;
if (left < scroll) {
relative_offset = scroll - left;
} else if (right > scroll + view_w) {
relative_offset = -(right - scroll - view_w);
}
if (is_rtl) {
relative_offset = -relative_offset;
}
if (relative_offset) {
this.list.style[anchor] = "".concat(nl + relative_offset, "px");
}
}
}
autoComplete(newVal, actVal, normalizedActVal, key, dontModify) {
if (newVal === actVal) {
return true;
}
if (dontModify || this.ime || !this.canSelect()) {
return false;
}
if (newVal.indexOf(actVal)) {
if (normalizedActVal && newVal.indexOf(normalizedActVal) === 0) {
if (this.lastRealInput === actVal) {
this.lastRealInput = normalizedActVal;
}
actVal = normalizedActVal;
} else {
return false;
}
}
this.text.focus();
this.text.value = newVal + key;
this.setSelection(actVal.length, newVal.length);
return true;
}
canSelect() {
return this.text.setSelectionRange || this.text.selectionStart !== void 0 && this.text.selectionEnd !== void 0;
}
setSelection(from, to) {
if (!this.text.value) {
return;
}
if (this.text.setSelectionRange) {
this.text.setSelectionRange(from, to);
} else if (this.text.selectionStart !== void 0) {
if (from > this.text.selectionStart) {
this.text.selectionEnd = to;
this.text.selectionStart = from;
} else {
this.text.selectionStart = from;
this.text.selectionEnd = to;
}
}
}
getSelection() {
let from = 0;
let to = 0;
if (!this.text.value) {
} else if (this.text.selectionStart !== void 0) {
from = this.text.selectionStart;
to = this.text.selectionEnd;
}
return {
start: from,
end: to
};
}
saveView() {
this.lastSelection = this.getSelection();
}
processKey(event) {
let dir = 0;
switch (this.lastKey) {
case UP:
dir = -1;
break;
case DOWN:
dir = 1;
break;
case PGUP:
dir = -HC.listSize;
break;
case PGDOWN:
dir = HC.listSize;
break;
case ESC:
return evtKill(event);
}
if (dir) {
if (this.list.style.display !== "none") {
this.highlightSuggestion(dir);
return evtKill(event);
} else if (this.keyCount <= 1 && (!this.callbackObj || this.callbackObj.callsMade === this.callbackObj.nofCalls)) {
this.textchange();
}
}
return true;
}
highlightSuggestion(dir) {
if (noSuggestions || !this.list || this.list.style.display === "none") {
return false;
}
const curr = this.list.selectedIndex;
let tgt = -1;
if (dir === 0) {
if (curr < 0 || curr >= this.list.options.length) {
return false;
}
tgt = curr;
} else {
tgt = curr < 0 ? 0 : curr + dir;
tgt = Math.max(tgt, 0);
if (tgt >= this.list.options.length) {
tgt = this.list.options.length - 1;
}
}
if (tgt !== curr || dir === 0) {
if (curr >= 0 && curr < this.list.options.length && dir !== 0) {
this.list.options[curr].selected = false;
}
this.list.options[tgt].selected = true;
const v = this.text.value.split("|");
const key = v.length > 1 ? "|".concat(v[1]) : "";
const completed = this.autoComplete(this.list.options[tgt].text, this.lastRealInput, null, key, false);
if (!completed || this.list.options[tgt].text === this.lastRealInput) {
this.text.value = this.list.options[tgt].text + key;
if (this.canSelect()) {
this.setSelection(this.list.options[tgt].text.length, this.list.options[tgt].text.length);
}
}
this.lastInput = this.list.options[tgt].text;
this.inputExists = true;
if (this.icon) {
this.icon.src = HC.existsYes;
}
this.state = CHANGE_PENDING;
}
return true;
}
resetKeySelection() {
if (noSuggestions || !this.list || this.list.style.display === "none") {
return false;
}
const curr = this.list.selectedIndex;
if (curr >= 0 && curr < this.list.options.length) {
this.list.options[curr].selected = false;
const v = this.text.value.split("|");
const key = v.length > 1 ? "|".concat(v[1]) : "";
let result = v[0] !== this.lastInput;
if (v[0] !== this.lastRealInput) {
this.text.value = this.lastRealInput + key;
result = true;
}
this.lastInput = this.lastRealInput;
return result;
}
return false;
}
}
const initialize = () => {
const config = {};
HC.dont_add_to_watchlist = window.hotcat_dont_add_to_watchlist === void 0 ? config.HotCatDontAddToWatchlist === void 0 ? HC.dont_add_to_watchlist : config.HotCatDontAddToWatchlist : !!window.hotcat_dont_add_to_watchlist;
HC.no_autocommit = window.hotcat_no_autocommit === void 0 ? config.HotCatNoAutoCommit === void 0 ? conf.wgNamespaceNumber % 2 ? true : HC.no_autocommit : config.HotCatNoAutoCommit : !!window.hotcat_no_autocommit;
HC.del_needs_diff = window.hotcat_del_needs_diff === void 0 ? config.HotCatDelNeedsDiff === void 0 ? HC.del_needs_diff : config.HotCatDelNeedsDiff : !!window.hotcat_del_needs_diff;
HC.suggest_delay = window.hotcat_suggestion_delay || config.HotCatSuggestionDelay || HC.suggest_delay;
HC.editbox_width = window.hotcat_editbox_width || config.HotCatEditBoxWidth || HC.editbox_width;
HC.suggestions = window.hotcat_suggestions || config.HotCatSuggestions || HC.suggestions;
if (typeof HC.suggestions !== "string" || !suggestionConfigs[HC.suggestions]) {
HC.suggestions = "combined";
}
HC.fixed_search = window.hotcat_suggestions_fixed === void 0 ? config.HotCatFixedSuggestions === void 0 ? HC.fixed_search : config.HotCatFixedSuggestions : !!window.hotcat_suggestions_fixed;
HC.single_minor = window.hotcat_single_changes_are_minor === void 0 ? config.HotCatMinorSingleChanges === void 0 ? HC.single_minor : config.HotCatMinorSingleChanges : !!window.hotcat_single_changes_are_minor;
HC.bg_changed = window.hotcat_changed_background || config.HotCatChangedBackground || HC.bg_changed;
HC.use_up_down = window.hotcat_use_category_links === void 0 ? config.HotCatUseCategoryLinks === void 0 ? HC.use_up_down : config.HotCatUseCategoryLinks : !!window.hotcat_use_category_links;
HC.listSize = window.hotcat_list_size || config.HotCatListSize || HC.listSize;
HC.changeTag = config.HotCatChangeTag || "";
if (HC.changeTag) {
const eForm = document.editform;
const catRegExp = new RegExp("^\\[\\[(".concat(HC.category_regexp, "):"));
let oldTxt;
const isMinorChange = () => {
let newTxt = eForm.wpTextbox1;
if (!newTxt) {
return;
}
newTxt = newTxt.value;
const oldLines = oldTxt.match(/^.*$/gm);
const newLines = newTxt.match(/^.*$/gm);
let cArr;
const except = (aArr, bArr) => {
const result = [];
let lArr;
let sArr;
if (aArr.length < bArr.length) {
lArr = bArr;
sArr = aArr;
} else {
lArr = aArr;
sArr = bArr;
}
var _iterator4 = _createForOfIteratorHelper(lArr), _step4;
try {
for (_iterator4.s(); !(_step4 = _iterator4.n()).done; ) {
const item = _step4.value;
const ind = sArr.indexOf(item);
if (ind === -1) {
result[result.length] = item;
} else {
sArr.splice(ind, 1);
}
}
} catch (err) {
_iterator4.e(err);
} finally {
_iterator4.f();
}
return [...result, ...sArr];
};
cArr = except(oldLines, newLines);
if (cArr.length > 0) {
cArr = cArr.filter((c) => {
c = c.trim();
return c && !catRegExp.test(c);
});
}
if (cArr.length === 0) {
oldTxt = newTxt;
return true;
}
};
if (conf.wgAction === "submit" && conf.wgArticleId && eForm && eForm.wpSummary && document.querySelector("#wikiDiff")) {
const sum = eForm.wpSummary;
const sumA = eForm.wpAutoSummary;
if (sum.value && sumA.value === HC.changeTag) {
sumA.value = sumA.value.replace(HC.changeTag, "d41d8cd98f00b204e9800998ecf8427e");
const $ct = $("<input>").attr({
type: "hidden",
name: "wpChangeTags"
}).val(HC.changeTag);
$(eForm).append($ct);
oldTxt = eForm.wpTextbox1.value;
const $body = $("body");
$body.find("input[name=wpSave]").one("click", () => {
if ($ct.val()) {
sum.value = sum.value.replace(getMessage("messages-using") || getMessage("messages-prefix"), "");
}
});
const removeChangeTag = () => {
$(eForm.wpTextbox1).add(sum).one("input", () => {
setTimeout(() => {
if (isMinorChange()) {
removeChangeTag();
} else {
$ct.val("");
}
}, 500);
});
};
removeChangeTag();
}
}
}
HC.listSize = Number.parseInt(HC.listSize, 10);
if (Number.isNaN(HC.listSize) || HC.listSize < 5) {
HC.listSize = 5;
}
HC.listSize = Math.min(HC.listSize, 30);
for (var _i6 = 0, _Object$entries = Object.entries(suggestionConfigs); _i6 < _Object$entries.length; _i6++) {
const [key, suggestionConfig] = _Object$entries[_i6];
try {
if (key && getMessage("engine_names-".concat(key))) {
suggestionConfig.name = getMessage("engine_names-".concat(key));
}
} catch (e) {
continue;
}
}
is_rtl = hasClass(document.querySelector("body"), "rtl");
if (!is_rtl) {
if (document.defaultView && document.defaultView.getComputedStyle) {
is_rtl = document.defaultView.getComputedStyle(document.querySelector("body"), null).getPropertyValue("direction");
} else {
is_rtl = $("body")[0].style.direction;
}
is_rtl = is_rtl === "rtl";
}
};
const can_edit = () => {
return document.querySelector("#ca-edit") !== null;
};
const closeForm = function() {
for (var _i7 = 0, _editors6 = editors; _i7 < _editors6.length; _i7++) {
const edit = _editors6[_i7];
if (edit.state === OPEN) {
edit.cancel();
} else if (edit.state === CHANGE_PENDING) {
edit.sanitizeInput();
const value = edit.text.value.split("|");
let key = null;
if (value.length > 1) {
[, key] = value;
}
const v = value[0].replace(/_/g, " ").trim();
if (v.length === 0) {
edit.cancel();
} else {
edit.currentCategory = v;
edit.currentKey = key;
edit.currentExists = this.inputExists;
edit.close();
}
}
}
};
const setup_upload = () => {
onUpload = true;
let ip = document.querySelector("#mw-htmlform-description") || document.querySelector("input[name=wpDestFile]");
if (!ip) {
ip = document.querySelector("input[name=wpDestFile]");
while (ip && ip.nodeName.toLowerCase() !== "table") {
ip = ip.parentNode;
}
}
if (!ip) {
return;
}
const reupload = document.querySelector("input[name=wpForReUpload]");
const destFile = document.querySelector("input[name=wpDestFile]");
if (reupload && !!reupload.value || destFile && (destFile.disabled || destFile.readOnly)) {
return;
}
const labelCell = make("td");
const lineCell = make("td");
catLine = make("div");
catLine.className = "catlinks";
catLine.id = "catlinks";
catLine.style.textAlign = is_rtl ? "right" : "left";
catLine.style.margin = "0";
catLine.style.border = "none";
lineCell.append(catLine);
const label = null;
if (label) {
labelCell.id = "hotcatLabelTranslated";
labelCell.append(label);
} else {
labelCell.id = "hotcatLabel";
labelCell.append(make(getMessage("categories"), true));
}
labelCell.className = "mw-label";
labelCell.style.textAlign = "right";
labelCell.style.verticalAlign = "middle";
const form = document.querySelector("#upload") || document.querySelector("#mw-upload-form");
if (form) {
const newRow = ip.insertRow(-1);
newRow.append(labelCell);
newRow.append(lineCell);
form.addEventListener("submit", (oldSubmit, ...args) => {
return (() => {
let do_submit = true;
if (oldSubmit) {
if (typeof oldSubmit === "string") {
do_submit = window.eval(oldSubmit);
} else if (oldSubmit instanceof Function) {
do_submit = oldSubmit.apply(form, [oldSubmit, ...args]);
}
}
if (!do_submit) {
return false;
}
closeForm();
const eb = document.querySelector("textarea[name=wpUploadDescription]") || document.querySelector("#wpDesc");
let addedOne = false;
for (var _i8 = 0, _editors7 = editors; _i8 < _editors7.length; _i8++) {
const editor = _editors7[_i8];
const t = editor.currentCategory;
if (!t) {
continue;
}
const key = editor.currentKey;
const new_cat = "[[".concat(HC.category_canonical, ":").concat(t).concat(key ? "|".concat(key) : "", "]]");
const nowikiRegex = new RegExp("<no".concat("wiki>", String.raw(_templateObject8 || (_templateObject8 = _taggedTemplateLiteral(["(s|S)*?</no"], ["(\\s|\\S)*?</no"]))), "wiki", ">"), "g");
const _cleanedText = eb.value.replace(/<!--(\s|\S)*?-->/g, "").replace(nowikiRegex);
if (!find_category(_cleanedText, t, true)) {
eb.value += "\n".concat(new_cat);
addedOne = true;
}
}
if (addedOne) {
const regex = new RegExp("{{$".concat("subst:").concat("unc}}"), "g");
eb.value = eb.value.replace(regex, "");
}
return true;
})(form.onsubmit);
});
}
};
let cleanedText = null;
const isOnPage = ({
firstChild
}) => {
if (firstChild.nodeType !== Node.ELEMENT_NODE) {
return null;
}
let catTitle = title(firstChild.getAttribute("href"));
if (!catTitle) {
return null;
}
catTitle = catTitle.slice(catTitle.indexOf(":") + 1).replace(/_/g, " ");
if (HC.blacklist && HC.blacklist.test(catTitle)) {
return null;
}
const result = {
title: catTitle,
match: ["", "", ""]
};
if (pageText === null) {
return result;
}
if (cleanedText === null) {
const nowikiRegex = new RegExp("<no".concat("wiki>", String.raw(_templateObject9 || (_templateObject9 = _taggedTemplateLiteral(["(s|S)*?</no"], ["(\\s|\\S)*?</no"]))), "wiki", ">"), "g");
cleanedText = pageText.replace(/<!--(\s|\S)*?-->/g, "").replace(nowikiRegex, "");
}
result.match = find_category(cleanedText, catTitle, true);
return result;
};
let initialized = false;
let setupTimeout = null;
const findByClass = (scope, tag, className) => {
const result = $(scope).find("".concat(tag, ".").concat(className));
return result && result.length > 0 ? result[0] : null;
};
const setup = (additionalWork) => {
if (initialized) {
return;
}
initialized = true;
if (setupTimeout) {
window.clearTimeout(setupTimeout);
setupTimeout = null;
}
catLine || (catLine = document.querySelector("#mw-normal-catlinks"));
const hiddenCats = document.querySelector("#mw-hidden-catlinks");
if (!catLine) {
let footer = null;
if (!hiddenCats) {
footer = findByClass(document, "div", "printfooter");
if (!footer) {
return;
}
}
catLine = make("div");
catLine.id = "mw-normal-catlinks";
catLine.style.textAlign = is_rtl ? "right" : "left";
const label = make("a");
label.href = conf.wgArticlePath.replace("$1", "Special:Categories");
label.title = getMessage("categories");
label.append(make(getMessage("categories"), true));
catLine.append(label);
catLine.append(make(":", true));
let container = hiddenCats ? hiddenCats.parentNode : document.querySelector("#catlinks");
if (!container) {
container = make("div");
container.id = "catlinks";
footer.parentNode.insertBefore(container, footer.nextSibling);
}
container.className = "catlinks noprint";
container.style.display = "";
if (hiddenCats) {
hiddenCats.before(catLine);
} else {
container.append(catLine);
}
}
if (is_rtl) {
catLine.dir = "rtl";
}
const createEditors = (line, is_hidden) => {
let i;
let cats = line.querySelectorAll("li");
if (cats.length > 0) {
newDOM = true;
line = cats[0].parentNode;
} else {
cats = line.querySelectorAll("span");
}
const copyCats = Array.from({
length: cats.length
});
for (i = 0; i < cats.length; i++) {
copyCats[i] = cats[i];
}
for (i = 0; i < copyCats.length; i++) {
const test = isOnPage(copyCats[i]);
if (test !== null && test.match !== null && line) {
new CategoryEditor(line, copyCats[i], test.title, test.match[2], is_hidden);
}
}
return copyCats.length > 0 ? copyCats.at(-1) : null;
};
const lastSpan = createEditors(catLine, false);
new CategoryEditor(newDOM ? catLine.querySelectorAll("ul")[0] : catLine, null, null, lastSpan !== null, false);
if (!onUpload) {
if (pageText !== null && hiddenCats) {
if (is_rtl) {
hiddenCats.dir = "rtl";
}
createEditors(hiddenCats, true);
}
const enableMulti = make("span");
enableMulti.className = "noprint";
if (is_rtl) {
enableMulti.dir = "rtl";
}
catLine.insertBefore(enableMulti, catLine.firstChild.nextSibling);
enableMulti.append(make(" ", true));
multiSpan = make("span");
enableMulti.append(multiSpan);
multiSpan.innerHTML = "(<a>".concat(HC.addmulti, "</a>)");
const [link] = multiSpan.querySelectorAll("a");
link.addEventListener("click", (event) => {
setMultiInput();
checkMultiInput();
return evtKill(event);
});
link.title = getMessage("multi_tooltip");
link.style.cursor = "pointer";
}
cleanedText = null;
if (additionalWork instanceof Function) {
additionalWork();
}
mw.hook("hotcat.ready").fire();
$("body").trigger("hotcatSetupCompleted");
};
const createCommitForm = () => {
if (commitForm) {
return;
}
const formContainer = make("div");
formContainer.style.display = "none";
document.querySelector("body").append(formContainer);
formContainer.innerHTML = '<form id="hotcatCommitForm" method="post" enctype="multipart/form-data" action="'.concat(conf.wgScript, "?title=").concat(encodeURIComponent(conf.wgPageName), '&action=submit">\n <input type="hidden" name="wpTextbox1">\n <input type="hidden" name="model" value="').concat(conf.wgPageContentModel, '">\n <input type="hidden" name="format" value="text/x-wiki">\n <input type="hidden" name="wpSummary" value="">\n <input type="checkbox" name="wpMinoredit" title="wpMinoredit" value="1">\n <input type="checkbox" name="wpWatchthis" title="wpWatchthis" value="1">\n <input type="hidden" name="wpAutoSummary" value="d41d8cd98f00b204e9800998ecf8427e">\n <input type="hidden" name="wpEdittime">\n <input type="hidden" name="wpStarttime">\n <input type="hidden" name="wpDiff" value="wpDiff">\n <input type="hidden" name="oldid" value="0">\n <input type="submit" name="hcCommit" value="hcCommit">\n <input type="hidden" name="wpEditToken">\n <input type="hidden" name="wpUltimateParam" value="1">\n <input type="hidden" name="wpChangeTags">\n <input type="hidden" value="ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ" name="wpUnicodeCheck">\n </form>');
commitForm = document.querySelector("#hotcatCommitForm");
};
const getPage = () => {
if (conf.wgArticleId) {
const params = {
action: "query",
format: "json",
formatversion: "2",
rawcontinue: "",
titles: conf.wgPageName,
prop: ["info", "revisions"],
rvprop: ["content", "timestamp", "ids"],
rvlimit: "1",
rvstartid: conf.wgCurRevisionId,
rvslots: "main",
meta: ["siteinfo"]
};
HC.start = (data) => {
setPage(data);
setup(createCommitForm);
};
api.get(params).then((data) => {
HC.start(data);
});
setupTimeout = setTimeout(() => {
setup(createCommitForm);
}, 4e3);
} else {
if (conf.wgNamespaceNumber === 2) {
return;
}
pageText = "";
pageTime = null;
setup(createCommitForm);
}
};
const setState = (state) => {
const cats = state.split("\n");
if (cats.length === 0) {
return null;
}
if (initialized && editors.length === 1 && editors[0].isAddCategory) {
const newSpans = [];
const before = editors.length === 1 ? editors[0].span : null;
let i;
for (i = 0; i < cats.length; i++) {
if (cats[i].length === 0) {
continue;
}
let cat = cats[i].split("|");
const key = cat.length > 1 ? cat[1] : null;
[cat] = cat;
const link = make("a");
link.href = wikiPagePath("".concat(HC.category_canonical, ":").concat(cat));
link.append(make(cat, true));
link.title = cat;
const span = make("span");
span.append(link);
if (!i) {
catLine.insertBefore(make(" ", true), before);
}
before.before(span);
if (before && i + 1 < cats.length) {
parent.insertBefore(make(" | ", true), before);
}
newSpans[newSpans.length] = {
element: span,
title: cat,
key
};
}
if (before) {
before.parentNode.insertBefore(make(" | ", true), before);
}
for (i = 0; i < newSpans.length; i++) {
new CategoryEditor(catLine, newSpans[i].element, newSpans[i].title, newSpans[i].key);
}
}
return null;
};
const getState = () => {
let result = null;
for (var _i9 = 0, _editors8 = editors; _i9 < _editors8.length; _i9++) {
const editor = _editors8[_i9];
let text = editor.currentCategory;
const key = editor.currentKey;
if (text && text.length > 0) {
if (key !== null) {
text += "|".concat(key);
}
if (result === null) {
result = text;
} else {
result += "\n".concat(text);
}
}
}
return result;
};
const really_run = () => {
initialize();
if (!HC.upload_disabled && conf.wgNamespaceNumber === -1 && conf.wgCanonicalSpecialPageName === "Upload" && conf.wgUserName) {
setup_upload();
setup(() => {
if (window.UploadForm && UploadForm.previous_hotcat_state) {
UploadForm.previous_hotcat_state = setState(UploadForm.previous_hotcat_state);
}
});
} else {
if (!conf.wgIsArticle || conf.wgAction !== "view" || param("diff") !== null || param("oldid") !== null || !can_edit() || HC.disable()) {
return;
}
getPage();
}
};
const run = () => {
if (HC.started) {
return;
}
HC.started = true;
really_run();
};
window.hotcat_get_state = () => {
return getState();
};
window.hotcat_set_state = (state) => {
return setState(state);
};
window.hotcat_close_form = () => {
closeForm();
};
HC.runWhenReady = (callback) => {
mw.hook("hotcat.ready").add(callback);
};
if (conf.wgCanonicalSpecialPageName !== "Upload") {
mw.hook("postEdit").add(() => {
if (document.querySelector("#catlinks .hotcatlink")) {
return;
}
catLine = null;
editors = [];
initialized = false;
HC.started = false;
run();
});
}
$(run);
})();


})();
<=1.31: https://commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-HotCat.js&oldid=1001064319
<=1.26: https://commons.wikimedia.org/w/index.php?title=MediaWiki:Gadget-HotCat.js&oldid=211134664
*/
// <nowiki>
/* eslint-disable vars-on-top, one-var, camelcase, no-alert, curly */
/* global jQuery, mediaWiki, UFUI, JSconfig, UploadForm */
/* jslint strict:false, nonew:false, bitwise:true */
( function ( $, mw ) {
// Don't use mw.config.get() as that takes a copy of the config, and so doesn't
// account for values changing, e.g. wgCurRevisionId after a VE edit
var conf = mw.config.values;
// Expand relative to location.href, so that we have a reliable prefix for other expanded
// links in the current document, regardless of context (e.g. mobile domain)
var expandedScriptPrefix = new URL(conf.wgScript, location.href).href;
var expandedArticlePrefix = new URL(conf.wgArticlePath.replace( '$1', '' ), location.href).href;


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

// Configuration holder. This is here so that the most common config and interface messages
// can be defined using the same format as translation subpages and /local_defaults.
// The rest is added later.
var HC = HotCat = window.HotCat = {
messages: {},
// Tooltips for the HotCat.links icons
tooltips: {},
engine_names: {}
};

// Common site configuration
//
// DO NOT EDIT
//
// Create the [[MediaWiki:Gadget-HotCat.js/local_defaults]] page
// on your wiki, and copy only the line you want to change there,
// and then change it there.
//
HotCat.messages.cat_removed = 'removed [[Category:$1]]';
HotCat.messages.template_removed = 'removed {{[[Category:$1]]}}';
HotCat.messages.cat_added = 'added [[Category:$1]]';
// * $1 - Category name
// * $2 - New key
HotCat.messages.cat_keychange = 'new key for [[Category:$1]]: "$2"';
HotCat.messages.cat_resolved = ' (redirect [[Category:$1]] resolved)';
HotCat.messages.uncat_removed = 'removed {{uncategorized}}';
HotCat.messages.separator = '; ';
// This text is added to the start of the edit summary.
HotCat.messages.prefix = '';
// This text is added to the end of the edit summary.
// If you prefer to have a marker at the front, use prefix instead
// and set this to the empty string.
HotCat.messages.using = ' using [[Help:Gadget-HotCat|HotCat]]';
// If your language has several plural forms, [[:en:Dual (grammatical form)]],
// you can set HotCat.messages.multi_change to an array of strings,
// which works the same way as {{PLURAL:}} and mw.language.convertPlural().
// https://translatewiki.net/wiki/Plural#Plural_syntax_in_MediaWiki
//
// Example for {{PLURAL:$1|one category|$1 categories}}:
// HotCat.messages.multi_change = ['one category', '$1 categories'];
HotCat.messages.multi_change = '$1 categories';
// Automatic default is '[[' + category_canonical + ':$1]]'
//
// Override if, for example, the short edit summaries should mention
// not the standard category namespace, but a shorter namespace alias.
//
// * $1 - Category name
HotCat.messages.short_catchange = null;
// Any subcategory of this category is deemed a disambiguation category,
// i.e., a category that should not contain any items, but that contains
// links to other categories where stuff should be categorized.
// Use spaces, not underscores.
// If you don't have that concept on your wiki, set it to null.
HotCat.disambig_category = 'Disambiguation';
// Any category in this category is deemed a (soft) redirect to some other category defined by a link
// to another non-blacklisted category. If your wiki doesn't have soft category redirects, set this to null.
// If a soft-redirected category contains more than one link to another non-blacklisted category, it's considered
// a disambiguation category instead.
HotCat.redir_category = 'Category redirects';
// a list of categories which can be removed by removing a template
// key: the category without namespace
// value: A regexp matching the template name, again without namespace
// If you don't have this at your wiki, or don't want this, leave it as an empty object {}.
HotCat.template_categories = {};
// A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
// If not, set it to null.
HotCat.uncat_regexp = /\{\{\s*[Uu]ncategorized\s*[^}]*\}\}\s*(<!--.*?-->\s*)?/g;

// User-interface messages
//
// DO NOT EDIT
//
// These are site-agnostic and must not be translated here.
// HotCat automatically replaces these with translations from here:
// https://commons.wikimedia.org/wiki/Special:Prefixindex/MediaWiki:Gadget-HotCat.js
//
// See also: window.hotcat_translations_from_commons
//
HotCat.messages.cat_notFound = 'Category "$1" not found';
HotCat.messages.cat_exists = 'Category "$1" already exists; not added.';
HotCat.messages.commit = 'Save';
HotCat.messages.ok = 'OK';
HotCat.messages.cancel = 'Cancel';
HotCat.messages.multi_error = 'Could not retrieve the page text from the server. Therefore, your category changes cannot be saved. We apologize for the inconvenience.';
HotCat.categories = 'Categories';
HotCat.tooltips.change = 'Modify';
HotCat.tooltips.remove = 'Remove';
HotCat.tooltips.add = 'Add a new category';
HotCat.tooltips.restore = 'Undo changes';
HotCat.tooltips.undo = 'Undo changes';
HotCat.tooltips.down = 'Open for modifying and display subcategories';
HotCat.tooltips.up = 'Open for modifying and display parent categories';
HotCat.multi_tooltip = 'Modify several categories';
HotCat.engine_names.searchindex = 'Search index';
HotCat.engine_names.pagelist = 'Page list';
HotCat.engine_names.combined = 'Combined search';
HotCat.engine_names.subcat = 'Subcategories';
HotCat.engine_names.parentcat = 'Parent categories';

// Misc site configuration
//
// DO NOT EDIT
//
// Create the [[MediaWiki:Gadget-HotCat.js/local_defaults]] page
// on your wiki, and override only the configuration you want to change.
//
// For example:
//
// HotCat.upload_disabled = false;
//
Object.assign(HC, {
// The little modification links displayed after category names. U+2212 is a minus sign; U+2193 and U+2191 are
// downward and upward pointing arrows. Do not use ↓ and ↑ in the code!
links: {
change: '(±)',
remove: '(\u2212)',
add: '(+)',
restore: '(×)',
undo: '(×)',
down: '(\u2193)',
up: '(\u2191)'
},
changeTag: conf.wgUserName ? 'HotCat' : '', // if tag is missing, edit is rejected
// The HTML content of the "enter multi-mode" link at the front.
addmulti: '<span>+<sup>+</sup></span>',
isSupportedContentModel: function() {
return HC.wikitextContentModels.includes(conf.wgPageContentModel) ||
HC.jsonContentModels.includes(conf.wgPageContentModel);
},
// Return true to disable HotCat.
disable: function () {
var ns = conf.wgNamespaceNumber;
var nsIds = conf.wgNamespaceIds;
return (
ns < 0 || // Special pages; Special:Upload is handled differently
ns === 10 || // Templates
ns === 828 || // Module (Lua)
ns === 8 || // MediaWiki
ns === 6 && !conf.wgArticleId || // Non-existing file pages
ns === 2 && /\.(js|css|json)$/.test( conf.wgTitle ) || // User scripts
nsIds &&
( ns === nsIds.creator ||
ns === nsIds.timedtext ||
ns === nsIds.institution ) );
},
// https://www.mediawiki.org/wiki/Content_handlers
wikitextContentModels: [ 'wikitext', 'proofread-index', 'proofread-page' ],
jsonContentModels: [ 'Tabular.JsonConfig', 'Map.JsonConfig', 'Chart.JsonConfig' ],
// 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',
existsNo: '//upload.wikimedia.org/wikipedia/commons/thumb/4/42/P_no.svg/20px-P_no.svg.png',

// Override the decision of whether HotCat should help users by automatically
// capitalising the title in the user input text if the wiki has case-sensitive page names.
// Basically, this will make an API query to check the MediaWiki configuration and HotCat then sets
// this to true for most wikis, and to false on Wiktionary.
//
// You can set this directly if there is a problem with it. For example, Georgian Wikipedia (kawiki),
// is known to have different capitalisation logic between MediaWiki PHP and JavaScript. As such, automatic
// case changes in JavaScript by HotCat would be wrong.
capitalizePageNames: null,
// If upload_disabled is true, HotCat will not be used on the Upload form.
upload_disabled: false,
// Single regular expression matching blacklisted categories that cannot be changed or
// 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.
blacklist: null,

// Stuff changeable by users:
// Background for changed categories in multi-edit mode. Default is a very light salmon pink.
bg_changed: '#FCA',
// If true, HotCat will never automatically submit changes. HotCat will only open an edit page with
// the changes; users must always save explicitly.
no_autocommit: false,
// If true, the "category deletion" link "(-)" will never save automatically but always show an
// edit page where the user has to save the edit manually. Is false by default because that's the
// traditional behavior. This setting overrides no_autocommit for "(-)" links.
del_needs_diff: false,
// Time, in milliseconds, that HotCat waits after a keystroke before making a request to the
// server to get suggestions.
suggest_delay: 100,
// Default width, in characters, of the text input field.
editbox_width: 40,
// One of the engine_names above, to be used as the default suggestion engine.
suggestions: 'combined',
// If true, always use the default engine, and never display a selector.
fixed_search: false,
// If false, do not display the "up" and "down" links
use_up_down: true,
// Default list size
listSize: 10,
// If true, single category changes are marked as minor edits. If false, they're not.
single_minor: true,
// 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"
// options in his or her preferences set.
dont_add_to_watchlist: false,
shortcuts: null,
addShortcuts: function ( map ) {
if ( !map ) return;
window.HotCat.shortcuts = window.HotCat.shortcuts || {};
for ( var k in map ) {
if ( !map.hasOwnProperty( k ) || typeof k !== 'string' ) continue;

var v = map[ k ];
if ( typeof v !== 'string' ) continue;

k = k.replace( /^\s+|\s+$/g, '' );
v = v.replace( /^\s+|\s+$/g, '' );
if ( !k.length || !v.length ) continue;

window.HotCat.shortcuts[ k ] = v;
}
}
});

// More backwards compatibility. We have a few places where we test for the browser: once for
// Safari < 3.0, and twice for WebKit (Chrome or Safari, any versions)
var ua = navigator.userAgent.toLowerCase();
var is_webkit = /applewebkit\/\d+/.test( ua ) && ua.indexOf( 'spoofer' ) < 0;
var cat_prefix = null;
var noSuggestions = false;

function LoadTrigger( needed ) {
// Define methods in a closure so that self reference is available,
// also allows method calls to be detached.
var self = this;
self.queue = [];
self.needed = needed;
self.register = function ( callback ) {
if ( self.needed <= 0 ) callback(); // Execute directly
else self.queue.push( callback );
};
self.loaded = function () {
self.needed--;
if ( self.needed === 0 ) {
// Run queued callbacks once
for ( var i = 0; i < self.queue.length; i++ ) self.queue[ i ]();
self.queue = [];
}
};
}

// Used to delay running the HotCat setup until /local_defaults and localizations have been loaded.
var loadTrigger = new LoadTrigger( 2 );

function load( uri, callback ) {
var s = document.createElement( 'script' );
s.src = uri;
var called = false;

s.onload = s.onerror = function () {
if ( !called && callback ) {
called = true;
callback();
}
if ( s.parentNode ) {
s.parentNode.removeChild( s );
}
};
document.head.appendChild( s );
}

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

function loadURI( href, callback ) {
load( href, callback );
}

// 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 Commons. This can
// be used to change the default settings, or to provide localized interface texts for edit summaries and so on.
loadJS( 'MediaWiki:Gadget-HotCat.js/local_defaults', loadTrigger.loaded );

// Load localized UI texts. These are the texts that HotCat displays on the page itself. Texts shown in edit summaries
// should be localized in /local_defaults above.
if ( conf.wgUserLanguage !== 'en' ) {
// Lupo: somebody thought it would be a good idea to add this. So the default is true, and you have to set it to false
// explicitly if you're not on Commons and don't want that.
if ( window.hotcat_translations_from_commons === undefined ) window.hotcat_translations_from_commons = true;

// Localization hook to localize HotCat messages, tooltips, and engine names for wgUserLanguage.
if ( window.hotcat_translations_from_commons && conf.wgDBname !== 'commonswiki' ) {
loadURI( '//commons.wikimedia.org/w/index.php?title=' +
'MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage +
'&action=raw&ctype=text/javascript', loadTrigger.loaded );
} else {
// Load translations locally
loadJS( 'MediaWiki:Gadget-HotCat.js/' + conf.wgUserLanguage, loadTrigger.loaded );
}
} else {
loadTrigger.loaded();
}

// No further changes should be necessary here.

// The following regular expression strings are used when searching for categories in wikitext.
var wikiTextBlank = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+';
var wikiTextBlankRE = new RegExp( wikiTextBlank, 'g' );
// Regexp for handling blanks inside a category title or namespace name.
// See http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Title.php?revision=104051&view=markup#l2722
// See also http://www.fileformat.info/info/unicode/category/Zs/list.htm
// MediaWiki collapses several contiguous blanks inside a page title to one single blank. It also replace a
// number of special whitespace characters by simple blanks. And finally, blanks are treated as underscores.
// Therefore, when looking for page titles in wikitext, we must handle all these cases.
// Note: we _do_ include the horizontal tab in the above list, even though the MediaWiki software for some reason
// appears to not handle it. The zero-width space \u200B is _not_ handled as a space inside titles by MW.
var wikiTextBlankOrBidi = '[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200B\\u200E\\u200F\\u2028-\\u202F\\u205F\\u3000]*';
// Whitespace regexp for handling whitespace between link components. Including the horizontal tab, but not \n\r\f\v:
// a link must be on one single line.
// MediaWiki also removes Unicode bidi override characters in page titles (and namespace names) completely.
// This is *not* handled, as it would require us to allow any of [\u200E\u200F\u202A-\u202E] between any two
// characters inside a category link. It _could_ be done though... We _do_ handle strange spaces, including the
// zero-width space \u200B, and bidi overrides between the components of a category link (adjacent to the colon,
// or adjacent to and inside of "[[" and "]]").

// First auto-localize the regexps for the category and the template namespaces.
var formattedNamespaces = conf.wgFormattedNamespaces;
var namespaceIds = conf.wgNamespaceIds;
function autoLocalize( namespaceNumber, fallback ) {
function createRegexpStr( name ) {
if ( !name || !name.length ) return '';

var regex_name = '';
for ( var i = 0; i < name.length; i++ ) {
var initial = name.charAt( i ),
ll = initial.toLowerCase(),
ul = initial.toUpperCase();
if ( ll === ul ) regex_name += initial; else regex_name += '[' + ll + ul + ']';
}
return regex_name
.replace( /([\\^$.?*+()])/g, '\\$1' )
.replace( wikiTextBlankRE, wikiTextBlank );
}

fallback = fallback.toLowerCase();
var canonical = formattedNamespaces[ String( namespaceNumber ) ].toLowerCase();
var regexp = createRegexpStr( canonical );
if ( fallback && canonical !== fallback ) regexp += '|' + createRegexpStr( fallback );

if ( namespaceIds ) {
for ( var cat_name in namespaceIds ) {
if (
typeof cat_name === 'string' &&
cat_name.toLowerCase() !== canonical &&
cat_name.toLowerCase() !== fallback &&
namespaceIds[ cat_name ] === namespaceNumber
) {
regexp += '|' + createRegexpStr( cat_name );
}
}
}
return regexp;
}

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

// Utility functions. Yes, this duplicates some functionality that also exists in other places, but
// to keep this whole stuff in a single file not depending on any other on-wiki JavaScripts, we re-do
// these few operations here.
function make( arg, literal ) {
if ( !arg ) return null;

return literal ? document.createTextNode( arg ) : document.createElement( arg );
}
function param( name, uri ) {
uri = uri || location.href;
var re = new RegExp( '[&?]' + name + '=([^&#]*)' );
var m = re.exec( uri );
if ( m && m.length > 1 ) return decodeURIComponent( m[ 1 ] );
return null;
}
function title( href ) {
if ( !href ) return null;
// href="/w/index.php?title=..."
if ( href.indexOf( expandedScriptPrefix ) === 0 ) {
return param( 'title', href );
}
// href="/wiki/..."
if ( href.indexOf( expandedArticlePrefix ) === 0 ) {
return decodeURIComponent( href.slice( expandedArticlePrefix.length ) );
}
return null;
}
function hasClass( elem, name ) {
return ( ' ' + elem.className + ' ' ).indexOf( ' ' + name + ' ' ) >= 0;
}
function capitalize( str ) {
if ( !str || !str.length ) return str;

return str.substr( 0, 1 ).toUpperCase() + str.substr( 1 );
}
function wikiPagePath( pageName ) {
// Note: do not simply use encodeURI, it doesn't encode '&', which might break if wgArticlePath actually has the $1 in
// a query parameter.
return conf.wgArticlePath.replace( '$1', encodeURIComponent( pageName ).replace( /%3A/g, ':' ).replace( /%2F/g, '/' ) );
}
function escapeRE( str ) {
return str.replace( /([\\^$.?*+()[\]])/g, '\\$1' );
}

function substituteFactory( options ) {
options = options || {};
var lead = options.indicator || '$';
var indicator = escapeRE( lead );
var lbrace = escapeRE( options.lbrace || '{' );
var rbrace = escapeRE( options.rbrace || '}' );
var re;

re = new RegExp(
// $$
'(?:' + indicator + '(' + indicator + '))|' +
// $0, $1
'(?:' + indicator + '(\\d+))|' +
// ${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 $.
return function ( str, map ) {
if ( !map ) return str;

return str.replace( re, function ( match, prefix, idx, key, alpha ) {
if ( prefix === lead ) return lead;

var k = alpha || key || idx;
var replacement = typeof map[ k ] === 'function' ? map[ k ]( match, k ) : map[ k ];
return typeof replacement === 'string' ? replacement : ( replacement || match );
} );
};
}

var substitute = substituteFactory();
var replaceShortcuts = ( function () {
var replaceHash = substituteFactory( {
indicator: '#',
lbrace: '[',
rbrace: ']'
} );
return function ( str, map ) {
var s = replaceHash( str, map );
return HC.capitalizePageNames ? capitalize( s ) : s;
};
}() );

// Text modification

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

function replaceByBlanks( match ) {
return match.replace( /(\s|\S)/g, ' ' ); // /./ doesn't match linebreaks. /(\s|\S)/ does.
}
/**
* find_category() Tests whether a specified category exists in the given text and returns match information.
*
* @param {string} text - The input string to search for category patterns (JSON or wikitext).
* @param {string} category - The target category name (without the "Category:" prefix).
* @param {boolean} once - If true, return only the first match; if false, return all matches.
* @returns {Array<string>|Array<Array<string>>|null}
* - When `once` is true:
* - Returns an Array of strings representing the first regexp match groups.
* - Returns `null` if no match is found.
* - When `once` is false:
* - Returns an Array of Arrays, each inner Array containing regexp match groups for each occurrence.
* - Returns an empty Array if no matches are found.
*/
function find_category( text, category, once ) {
if ( HC.jsonContentModels.includes( conf.wgPageContentModel ) ) {
// Handle Data namespace JSON content
return find_category_json( text, category, once );
}
else {
// For backwards-compatibility the default handler is wikitext
return find_category_wikitext( text, category, once );
}
}

function find_category_json( jsontext, category, once ) {
// Handle Data namespace JSON content
let jsonData;
try {
jsonData = JSON.parse( jsontext );
} catch ( err ) {
console.error( 'Invalid JSON in find_category_json:', err );
return once ? null : [];
}
// Check if mediawikiCategories exists and if not then return
if ( !Array.isArray( jsonData.mediawikiCategories ) ) {
return once ? null : [];
}
// Handle actual data
var matches = [];
const targetCategoryName = mw.Title.newFromText( "category:" + category ).getPrefixedText();
// Search through each category object
for ( var i = 0; i < jsonData.mediawikiCategories.length; i++ ) {
var catObj = jsonData.mediawikiCategories[ i ];
const currentCategoryName = mw.Title.newFromText( "category:" + catObj.name ).getPrefixedText();
// Compare category name (case-sensitive)
if ( currentCategoryName === targetCategoryName ) {
var match = [
'', // Full match placeholder
'', // Namespace placeholder
catObj.sort || '' // Sortkey if exists
];
if ( once ) {
return { match };
}
matches.push( { match: match } );
}
}
return once ? null : matches;
}
function find_category_wikitext( wikitext, category, once ) {
var cat_regex = null;
if ( HC.template_categories[ category ] ) {
cat_regex = new RegExp(
'\\{\\{' + wikiTextBlankOrBidi + '(' + HC.template_regexp + '(?=' + wikiTextBlankOrBidi + ':))?' + wikiTextBlankOrBidi +
'(?:' + HC.template_categories[ category ] + ')' +
wikiTextBlankOrBidi + '(\\|.*?)?\\}\\}',
'g'
);
} else {
var cat_name = escapeRE( category );
var initial = cat_name.substr( 0, 1 );
cat_regex = new RegExp(
'\\[\\[' + wikiTextBlankOrBidi + '(' + HC.category_regexp + ')' + wikiTextBlankOrBidi + ':' + wikiTextBlankOrBidi +
( initial === '\\' || !HC.capitalizePageNames ?
initial :
'[' + initial.toUpperCase() + initial.toLowerCase() + ']' ) +
cat_name.substring( 1 ).replace( wikiTextBlankRE, wikiTextBlank ) +
wikiTextBlankOrBidi + '(\\|.*?)?\\]\\]',
'g'
);
}
if ( once ) return cat_regex.exec( wikitext );

var copiedtext = wikitext
.replace( /<!--(\s|\S)*?-->/g, replaceByBlanks )
.replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, replaceByBlanks );
var result = [];
var curr_match = null;
while ( ( curr_match = cat_regex.exec( copiedtext ) ) !== null ) {
result.push( {
match: curr_match
} );
}
result.re = cat_regex;
return result; // An array containing all matches, with positions, in result[ i ].match
}

var interlanguageRE = null;
function change_category( text, toRemove, toAdd, key, is_hidden ) {
if ( HC.jsonContentModels.includes( conf.wgPageContentModel ) ) {
// Handle Data namespace JSON content
return change_category_json( text, toRemove, toAdd, key, is_hidden );
}
else {
// For backwards-compatibility the default handler is wikitext
return change_category_wikitext( text, toRemove, toAdd, key, is_hidden );
}
// In cases of unknown page content model
return {
text: text,
summary: [],
error: 'Unknown wgPageContentModel: ' + conf.wgPageContentModel
};
}

function change_category_json( jsontext, toRemove, toAdd, key, is_hidden ) {
try {
var jsonData = JSON.parse( jsontext );
if ( !jsonData.mediawikiCategories ) {
jsonData.mediawikiCategories = [];
}
var summary = [];
// Remove category if specified
if ( toRemove ) {
var initialLength = jsonData.mediawikiCategories.length;
var normalizedToRemoveName = mw.Title.newFromText( "category:" + toRemove ).getPrefixedText();
jsonData.mediawikiCategories = jsonData.mediawikiCategories.filter( function( cat ) {
var normalizedCatName = mw.Title.newFromText( "category:" + cat.name ).getPrefixedText();
return normalizedCatName !== normalizedToRemoveName;
} );
if ( jsonData.mediawikiCategories.length < initialLength ) {
summary.push( HC.messages.cat_removed.replace( /\$1/g, toRemove ) );
}
}
// Add category if specified
if ( toAdd ) {
var normalizedToAddName = mw.Title.newFromText( "category:" + toAdd ).getPrefixedText();
var exists = jsonData.mediawikiCategories.some( function( cat ) {
var normalizedCatName = mw.Title.newFromText( "category:" + cat.name ).getPrefixedText();
return normalizedCatName === normalizedToAddName;
} );
if ( !exists ) {
jsonData.mediawikiCategories.push( {
name: toAdd,
sort: key || ''
} );
summary.push( HC.messages.cat_added.replace( /\$1/g, toAdd ) );
} else if ( key ) {
// Update sortkey if category exists and new key is provided
for ( var i = 0; i < jsonData.mediawikiCategories.length; i++ ) {
var cat = jsonData.mediawikiCategories[ i ];
var normalizedCatName = mw.Title.newFromText( "category:" + cat.name ).getPrefixedText();
if ( normalizedCatName === normalizedToAddName && ( !cat.sort || cat.sort !== key ) ) {
jsonData.mediawikiCategories[ i ].sort = key;
summary.push( HC.messages.cat_keychange.replace( /\$1/g, toAdd ).replace( /\$2/g, key ) );
break;
}
}
}
}

return {
text: JSON.stringify( jsonData, null, 2 ),
summary: summary,
error: null
};
} catch ( e ) {
return {
text: jsontext,
summary: [],
error: 'Failed to modify JSON categories: ' + e.message
};
}
}
function change_category_wikitext( wikitext, toRemove, toAdd, key, is_hidden ) {
function find_insertionpoint( wikitext ) {
var copiedtext = wikitext
.replace( /<!--(\s|\S)*?-->/g, replaceByBlanks )
.replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, replaceByBlanks );
// Search in copiedtext to avoid that we insert inside an HTML comment or a nowiki "element".
var index = -1;
findCatsRE.lastIndex = 0;
while ( findCatsRE.exec( copiedtext ) !== null ) index = findCatsRE.lastIndex;

if ( index < 0 ) {
// Find the index of the first interlanguage link...
var match = null;
if ( !interlanguageRE ) {
// Approximation without API: interlanguage links start with 2 to 3 lower case letters, optionally followed by
// a sequence of groups consisting of a dash followed by one or more lower case letters. Exceptions are "simple"
// and "tokipona".
match = /((^|\n\r?)(\[\[\s*(([a-z]{2,3}(-[a-z]+)*)|simple|tokipona)\s*:[^\]]+\]\]\s*))+$/.exec( copiedtext );
} else {
match = interlanguageRE.exec( copiedtext );
}
if ( match ) index = match.index;

return {
idx: index,
onCat: false
};
}
return {
idx: index,
onCat: index >= 0
};
}

var summary = [],
nameSpace = HC.category_canonical,
cat_point = -1, // Position of removed category;
keyChange = ( toRemove && toAdd && toRemove === toAdd && toAdd.length ),
matches;
if ( key ) key = '|' + key;
// Remove
if ( toRemove && toRemove.length ) {
matches = find_category( wikitext, toRemove );
if ( !matches || !matches.length ) {
return {
text: wikitext,
summary: summary,
error: HC.messages.cat_notFound.replace( /\$1/g, toRemove )
};
} else {
var before = wikitext.substring( 0, matches[ 0 ].match.index ),
after = wikitext.substring( matches[ 0 ].match.index + matches[ 0 ].match[ 0 ].length );
if ( matches.length > 1 ) {
// Remove all occurrences in after
matches.re.lastIndex = 0;
after = after.replace( matches.re, '' );
}
if ( toAdd ) {
// nameSpace = matches[ 0 ].match[ 1 ] || nameSpace; Canonical namespace should be always preferred
if ( key === null ) key = matches[ 0 ].match[ 2 ];
// Remember the category key, if any.
}
// Remove whitespace (properly): strip whitespace, but only up to the next line feed.
// If we then have two linefeeds in a row, remove one. Otherwise, if we have two non-
// whitespace characters, insert a blank.
var i = before.length - 1;
while ( i >= 0 && before.charAt( i ) !== '\n' && before.substr( i, 1 ).search( /\s/ ) >= 0 ) i--;

var j = 0;
while ( j < after.length && after.charAt( j ) !== '\n' && after.substr( j, 1 ).search( /\s/ ) >= 0 ) j++;

if ( i >= 0 && before.charAt( i ) === '\n' && ( !after.length || j < after.length && after.charAt( j ) === '\n' ) ) i--;

if ( i >= 0 ) before = before.substring( 0, i + 1 ); else before = '';

if ( j < after.length ) after = after.substring( j ); else after = '';

if (
before.length && before.substring( before.length - 1 ).search( /\S/ ) >= 0 &&
after.length && after.substr( 0, 1 ).search( /\S/ ) >= 0
) {
before += ' ';
}

cat_point = before.length;
if ( cat_point === 0 && after.length && after.substr( 0, 1 ) === '\n' ) after = after.substr( 1 );

wikitext = before + after;
if ( !keyChange ) {
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 ) ); }
}

}
}
// Add
if ( toAdd && toAdd.length ) {
matches = find_category( wikitext, toAdd );
if ( matches && matches.length ) {
// Already exists
return {
text: wikitext,
summary: summary,
error: HC.messages.cat_exists.replace( /\$1/g, toAdd )
};
} else {
var onCat = false;
if ( cat_point < 0 ) {
var point = find_insertionpoint( wikitext );
cat_point = point.idx;
onCat = point.onCat;
} else {
onCat = true;
}
var newcatstring = '[[' + nameSpace + ':' + toAdd + ( key || '' ) + ']]';
if ( cat_point >= 0 ) {
var suffix = wikitext.substring( cat_point );
wikitext = wikitext.substring( 0, cat_point ) + ( cat_point > 0 ? '\n' : '' ) + newcatstring + ( !onCat ? '\n' : '' );
if ( suffix.length && suffix.substr( 0, 1 ) !== '\n' ) wikitext += '\n' + suffix; else wikitext += suffix;
} else {
if ( wikitext.length && wikitext.substr( wikitext.length - 1, 1 ) !== '\n' ) wikitext += '\n';

wikitext += ( wikitext.length ? '\n' : '' ) + newcatstring;
}
if ( keyChange ) {
var k = key || '';
if ( k.length ) k = k.substr( 1 );

summary.push( substitute( HC.messages.cat_keychange, [ null, toAdd, k ] ) );
} else {
summary.push( HC.messages.cat_added.replace( /\$1/g, toAdd ) );
}
if ( HC.uncat_regexp && !is_hidden ) {
var txt = wikitext.replace( HC.uncat_regexp, '' ); // Remove "uncat" templates
if ( txt.length !== wikitext.length ) {
wikitext = txt;
summary.push( HC.messages.uncat_removed );
}
}
}
}
return {
text: wikitext,
summary: summary,
error: null
};
}

// The real HotCat UI

function evtKeys( e ) {
/* eslint-disable no-bitwise */
var code = 0;
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
// as a ctrl-click, too.
if ( e.ctrlKey || e.metaKey ) code |= 1;

if ( e.shiftKey ) code |= 2;
}
return code;
}
function evtKill( e ) {
if ( e.preventDefault ) {
e.preventDefault();
e.stopPropagation();
} else {
e.cancelBubble = true;
}
return false;
}

var catLine = null,
onUpload = false,
editors = [],

commitButton = null,
commitForm = null,
multiSpan = null,

pageText = null,
pageTime = null,
pageWatched = false,
watchCreate = false,
watchCreateExpiry = '',
watchEdit = false,
watchDefaultExpiry = '',
minorEdits = false,
editToken = null,

is_rtl = false,
serverTime = null,
lastRevId = null,
pageTextRevId = null,
conflictingUser = null,

newDOM = false; // true if MediaWiki serves the new UL-LI DOM for categories

function CategoryEditor() {
this.initialize.apply( this, arguments );
}

function setPage( json ) {
var startTime = null;
if ( json && json.query ) {
if ( json.query.pages ) {
var page = json.query.pages[ !conf.wgArticleId ? '-1' : String( conf.wgArticleId ) ];
if ( page ) {
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 ].slots.main[ '*' ];
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';
if ( json.query.tokens ) editToken = json.query.tokens.csrftoken;
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';
watchCreateExpiry = json.query.userinfo.options[' watchcreations-expiry'] || '';
watchEdit = !HC.dont_add_to_watchlist && json.query.userinfo.options.watchdefault === '1';
watchDefaultExpiry = json.query.userinfo.options['watchdefault-expiry'] || '';
minorEdits = json.query.userinfo.options.minordefault === 1;
// If the user has the "All edits are minor" preference enabled, we should honor that
// for single category changes, no matter what the site configuration is.
if ( minorEdits ) HC.single_minor = true;
}
}
}

var saveInProgress = false;
function initiateEdit( doEdit, failure ) {
if ( saveInProgress ) return;
saveInProgress = true;
var oldButtonState;
if ( commitButton ) {
oldButtonState = commitButton.disabled;
commitButton.disabled = true;
}

function fail() {
saveInProgress = false;
if ( commitButton ) commitButton.disabled = oldButtonState;
failure.apply( this, arguments );
}

// Must use Ajax here to get the user options and the edit token.
$.getJSON(
conf.wgScriptPath + '/api.php?' +
'format=json&action=query&rawcontinue=&titles=' + encodeURIComponent( conf.wgPageName ) +
'&prop=info%7Crevisions%7Clanglinks&inprop=watched&rvprop=content%7Ctimestamp%7Cids%7Cuser&lllimit=500&rvslots=main' +
'&rvlimit=2&rvdir=newer&rvstartid=' + conf.wgCurRevisionId + '&meta=siteinfo%7Cuserinfo%7Ctokens&type=csrf&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 ]; }

return substitute( msg, [ null, String( count ) ] );
}

function currentTimestamp() {
var now = new Date();
var ts = String( now.getUTCFullYear() );
function two( s ) {
return s.substr( s.length - 2 );
}
ts +=
two( '0' + ( now.getUTCMonth() + 1 ) ) +
two( '0' + now.getUTCDate() ) +
two( '00' + now.getUTCHours() ) +
two( '00' + now.getUTCMinutes() ) +
two( '00' + now.getUTCSeconds() );
return ts;
}

function performChanges( failure, singleEditor ) {
if ( pageText === null ) {
failure( HC.messages.multi_error );
return;
}
// Backwards compatibility after message change (added $2 to cat_keychange)
if ( HC.messages.cat_keychange.indexOf( '$2' ) < 0 ) HC.messages.cat_keychange += '"$2"';

// More backwards-compatibility with earlier HotCat versions:
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
// (a) sensibly reporting back errors like edit conflicts is always a hassle, and
// (b) we want to show a diff for multi-edits anyway, and
// (c) we want to trigger onsubmit events, allowing user code to intercept the edit.
// Using the form, we can do (b) and (c), and we get (a) for free. And, of course, using the form
// automatically reloads the page with the updated categories on a successful submit, which
// we would have to do explicitly if we used the edit API.
var action;
// Normally, we don't have to care about edit conflicts. If some other user edited the page in the meantime, the
// server will take care of it and merge the edit automatically or present an edit conflict screen. However, the
// server suppresses edit conflicts with oneself. Hence, if we have a conflict, and the conflicting user is the
// current user, then we set the "oldid" value and switch to diff, which gives the "you are editing an old version;
// if you save, any more recent changes will be lost" screen.
var selfEditConflict = ( lastRevId !== null && lastRevId !== conf.wgCurRevisionId || pageTextRevId !== null &&
pageTextRevId !== conf.wgCurRevisionId ) && conflictingUser && conflictingUser === conf.wgUserName;
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
// merge the changes. We just need to make sure that we do present a diff view if it's a self edit conflict.
commitForm.wpEditToken.value = editToken;
action = commitForm.wpDiff;
if ( action ) action.name = action.value = 'wpSave';
} else {
action = commitForm.wpSave;
if ( action ) action.name = action.value = 'wpDiff';
}
var result = {
text: pageText
},
changed = [],
added = [],
deleted = [],
changes = 0,
toEdit = singleEditor ? [ singleEditor ] : editors,
error = null,
edit,
i;
for ( i = 0; i < toEdit.length; i++ ) {
edit = toEdit[ i ];
if ( edit.state === CategoryEditor.CHANGED ) {
result = change_category(
result.text,
edit.originalCategory,
edit.currentCategory,
edit.currentKey,
edit.currentHidden );
if ( !result.error ) {
changes++;
if ( !edit.originalCategory || !edit.originalCategory.length ) {
added.push( edit.currentCategory );
} else {
changed.push( {
from: edit.originalCategory,
to: edit.currentCategory
} );
}
} else if ( error === null ) {
error = result.error;
}
} else if (
edit.state === CategoryEditor.DELETED && edit.originalCategory && edit.originalCategory.length ) {
result = change_category(
result.text,
edit.originalCategory,
null, null, false );
if ( !result.error ) {
changes++;
deleted.push( edit.originalCategory );
} else if ( error === null ) {
error = result.error;
}
}
}
if ( error !== null ) { // Do not commit if there were errors
action = commitForm.wpSave;
if ( action ) action.name = action.value = 'wpDiff';
}
// Fill in the form and submit it
commitForm.wpMinoredit.checked = minorEdits;
commitForm.wpWatchthis.checked = !conf.wgArticleId && watchCreate || watchEdit || pageWatched;
// Set watchlist expiry according to user preference
// If page is already in watchlist do not update expiry
if (commitForm.wpWatchthis.checked && !pageWatched) {
if (!conf.wgArticleId && watchCreate) {
commitForm.wpWatchlistExpiry.value = watchCreateExpiry;
} else {
commitForm.wpWatchlistExpiry.value = watchDefaultExpiry;
}
}
if ( conf.wgArticleId || !!singleEditor ) {
// Prepare change-tag save
if ( action && action.value === 'wpSave' ) {
if ( HC.changeTag ) {
commitForm.wpChangeTags.value = HC.changeTag;
HC.messages.using = '';
HC.messages.prefix = '';
}
} else {
commitForm.wpAutoSummary.value = HC.changeTag;
}
if ( changes === 1 ) {
if ( result.summary && result.summary.length ) commitForm.wpSummary.value = HC.messages.prefix + result.summary.join( HC.messages.separator ) + HC.messages.using;
commitForm.wpMinoredit.checked = HC.single_minor || minorEdits;
} else if ( changes ) {
var summary = [];
var shortSummary = [];
// Deleted
for ( i = 0; i < deleted.length; i++ ) summary.push( '−' + substitute( HC.messages.short_catchange, [ null, deleted[ i ] ] ) );

if ( deleted.length === 1 ) shortSummary.push( '−' + substitute( HC.messages.short_catchange, [ null, deleted[ 0 ] ] ) ); else if ( deleted.length ) shortSummary.push( '− ' + multiChangeMsg( deleted.length ) );

// Added
for ( i = 0; i < added.length; i++ ) summary.push( '+' + substitute( HC.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 ) );

// Changed
var arrow = is_rtl ? '\u2190' : '\u2192'; // left and right arrows. Don't use ← and → in the code.
for ( i = 0; i < changed.length; i++ ) {
if ( changed[ i ].from !== changed[ i ].to ) {
summary.push(
'±' + substitute( HC.messages.short_catchange, [ null, changed[ i ].from ] ) + arrow +
substitute( HC.messages.short_catchange, [ null, changed[ i ].to ] )
);
} else {
summary.push( '±' + substitute( HC.messages.short_catchange, [ null, changed[ i ].from ] ) );
}
}
if ( changed.length === 1 ) {
if ( changed[ 0 ].from !== changed[ 0 ].to ) {
shortSummary.push(
'±' + substitute( HC.messages.short_catchange, [ null, changed[ 0 ].from ] ) + arrow +
substitute( HC.messages.short_catchange, [ null, changed[ 0 ].to ] )
);
} else {
shortSummary.push( '±' + substitute( HC.messages.short_catchange, [ null, changed[ 0 ].from ] ) );
}
} else if ( changed.length ) {
shortSummary.push( '± ' + multiChangeMsg( changed.length ) );
}
if ( summary.length ) {
summary = summary.join( HC.messages.separator );
if ( summary.length > 200 - HC.messages.prefix.length - HC.messages.using.length ) summary = shortSummary.join( HC.messages.separator );

commitForm.wpSummary.value = HC.messages.prefix + summary + HC.messages.using;
}
}
}

commitForm.wpTextbox1.value = result.text;
commitForm.wpStarttime.value = serverTime || currentTimestamp();
commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value;
if ( selfEditConflict ) commitForm.oldid.value = String( pageTextRevId || conf.wgCurRevisionId );

// Submit the form in a way that triggers onsubmit events: commitForm.submit() doesn't.
commitForm.hcCommit.click();
}

function resolveOne( page, toResolve ) {
var cats = page.categories,
lks = page.links,
is_dab = false,
is_redir = typeof page.redirect === 'string', // Hard redirect?
is_hidden = page.categoryinfo && typeof page.categoryinfo.hidden === 'string',
is_missing = typeof page.missing === 'string',
i;
for ( i = 0; i < toResolve.length; i++ ) {
if ( i && toResolve[ i ].dabInputCleaned !== page.title.substring( page.title.indexOf( ':' ) + 1 ) ) continue;
// Note: the server returns in page an NFC normalized Unicode title. If our input was not NFC normalized, we may not find
// any entry here. If we have only one editor to resolve (the most common case, I presume), we may simply skip the check.
toResolve[ i ].currentHidden = is_hidden;
toResolve[ i ].inputExists = !is_missing;
toResolve[ i ].icon.src = ( is_missing ? HC.existsNo : HC.existsYes );
}
if ( is_missing ) return;
if ( !is_redir && cats && ( HC.disambig_category || HC.redir_category ) ) {
for ( var c = 0; c < cats.length; c++ ) {
var cat = cats[ c ].title;
// Strip namespace prefix
if ( cat ) {
cat = cat.substring( cat.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
if ( cat === HC.disambig_category ) {
is_dab = true;
break;
} else if ( cat === HC.redir_category ) {
is_redir = true;
break;
}
}
}
}
if ( !is_redir && !is_dab ) return;
if ( !lks || !lks.length ) return;
var titles = [];
for ( i = 0; i < lks.length; i++ ) {
if (
// 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.
var match = lks[ i ].title;
match = match.substring( match.indexOf( ':' ) + 1 );
// Exclude blacklisted categories.
if ( !HC.blacklist || !HC.blacklist.test( 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 : '' );
}
}
}

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;
}
// Use %7C instead of |, otherwise Konqueror insists on re-encoding the arguments, resulting in doubly encoded
// category names. (That is a bug in Konqueror. Other browsers don't have this problem.)
var args = 'action=query&prop=info%7Clinks%7Ccategories%7Ccategoryinfo&plnamespace=14' +
'&pllimit=' + ( toResolve.length * 10 ) +
'&cllimit=' + ( toResolve.length * 10 ) +
'&format=json&titles=';
for ( i = 0; i < toResolve.length; i++ ) {
var v = toResolve[ i ].dabInput;
v = replaceShortcuts( v, HC.shortcuts );
toResolve[ i ].dabInputCleaned = v;
args += encodeURIComponent( 'Category:' + v );
if ( i + 1 < toResolve.length ) args += '%7C';
}
$.getJSON( conf.wgScriptPath + '/api.php?' + args,
function ( json ) {
resolveRedirects( toResolve, json );
callback( toResolve );
} ).fail( function ( req ) {
if ( !req ) noSuggestions = true;
callback( toResolve );
} );
}

function makeActive( which ) {
if ( which.is_active ) return;
for ( var i = 0; i < editors.length; i++ )
if ( editors[ i ] !== which ) editors[ i ].inactivate();

which.is_active = true;
if ( which.dab ) {
// eslint-disable-next-line no-use-before-define
showDab( which );
} else {
// Check for programmatic value changes.
var expectedInput = which.lastRealInput || which.lastInput || '';
var actualValue = which.text.value || '';
if ( !expectedInput.length && actualValue.length || expectedInput.length && actualValue.indexOf( expectedInput ) ) {
// Somehow the field's value appears to have changed, and which.lastSelection therefore is no longer valid. Try to set the
// cursor at the end of the category, and do not display the old suggestion list.
which.showsList = false;
var v = actualValue.split( '|' );
which.lastRealInput = which.lastInput = v[ 0 ];
if ( v.length > 1 ) which.currentKey = v[ 1 ];

if ( which.lastSelection ) {
which.lastSelection = {
start: v[ 0 ].length,
end: v[ 0 ].length
};
}
}
if ( which.showsList ) which.displayList();

if ( which.lastSelection ) {
if ( is_webkit ) {
// WebKit (Safari, Chrome) has problems selecting inside focus()
// See http://code.google.com/p/chromium/issues/detail?id=32865#c6
window.setTimeout(
function () {
which.setSelection( which.lastSelection.start, which.lastSelection.end );
},
1 );
} else {
which.setSelection( which.lastSelection.start, which.lastSelection.end );
}
}
}
}

function showDab( which ) {
if ( !which.is_active ) {
makeActive( which );
} else {
which.showSuggestions( which.dab, false, null, null ); // do autocompletion, no key, no engine selector
which.dab = null;
}
}

function multiSubmit() {
var toResolve = [];
for ( var i = 0; i < editors.length; i++ )
if ( editors[ i ].state === CategoryEditor.CHANGE_PENDING || editors[ i ].state === CategoryEditor.OPEN ) toResolve.push( editors[ i ] );

if ( !toResolve.length ) {
initiateEdit( function ( failure ) {
performChanges( failure );
}, function ( msg ) {
alert( msg );
} );
return;
}
resolveMulti( toResolve, function ( resolved ) {
var firstDab = null;
var dontChange = false;
for ( var i = 0; i < resolved.length; i++ ) {
if ( resolved[ i ].lastInput !== resolved[ i ].dabInput ) {
// We didn't disable all the open editors, but we did asynchronous calls. It is
// theoretically possible that the user changed something...
dontChange = true;
} else {
if ( resolved[ i ].dab ) {
if ( !firstDab ) firstDab = resolved[ i ];
} else {
if ( resolved[ i ].acceptCheck( true ) ) resolved[ i ].commit();
}
}
}
if ( firstDab ) {
showDab( firstDab );
} else if ( !dontChange ) {
initiateEdit( function ( failure ) {
performChanges( failure );
}, function ( msg ) {
alert( msg );
} );
}
} );
}

function setMultiInput() {
if ( commitButton || onUpload ) return;
commitButton = make( 'input' );
commitButton.type = 'button';
commitButton.value = HC.messages.commit;
commitButton.onclick = multiSubmit;
if ( multiSpan ) multiSpan.parentNode.replaceChild( commitButton, multiSpan ); else catLine.appendChild( commitButton );
}

function checkMultiInput() {
if ( !commitButton ) return;
var hasChanges = false;
for ( var i = 0; i < editors.length; i++ ) {
if ( editors[ i ].state !== CategoryEditor.UNCHANGED ) {
hasChanges = true;
break;
}
}
commitButton.disabled = !hasChanges;
}

var suggestionEngines = {
opensearch: {
uri: '/api.php?format=json&action=opensearch&namespace=14&limit=30&search=Category:$1', // $1 = search term
// Function to convert result of uri into an array of category names
handler: function ( queryResult, queryKey ) {
if ( queryResult && queryResult.length >= 2 ) {
var key = queryResult[ 0 ].substring( queryResult[ 0 ].indexOf( ':' ) + 1 );
var titles = queryResult[ 1 ];
var exists = false;
if ( !cat_prefix ) cat_prefix = new RegExp( '^(' + HC.category_regexp + '):' );

for ( var i = 0; i < titles.length; i++ ) {
cat_prefix.lastIndex = 0;
var m = cat_prefix.exec( titles[ i ] );
if ( m && m.length > 1 ) {
titles[ i ] = titles[ i ].substring( titles[ i ].indexOf( ':' ) + 1 ); // rm namespace
if ( key === titles[ i ] ) exists = true;
} else {
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;
}
},
internalsearch: {
uri: '/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1&apprefix=$1',
handler: function ( queryResult ) {
if ( queryResult && queryResult.query && queryResult.query.allpages ) {
var titles = queryResult.query.allpages;
for ( var i = 0; i < titles.length; i++ ) titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace

return titles;
}
return null;
}
},
exists: {
uri: '/api.php?format=json&action=query&prop=info&titles=Category:$1',
handler: function ( queryResult, queryKey ) {
if ( queryResult && queryResult.query && queryResult.query.pages && !queryResult.query.pages[ -1 ] ) {
// Should have exactly 1
for ( var p in queryResult.query.pages ) {
var title = queryResult.query.pages[ p ].title;
title = title.substring( title.indexOf( ':' ) + 1 );
var titles = [ title ];
titles.exists = true;
if ( queryKey !== title ) titles.normalized = title;
// NFC
return titles;
}
}
return null;
}
},
subcategories: {
uri: '/api.php?format=json&action=query&list=categorymembers&cmtype=subcat&cmlimit=max&cmtitle=Category:$1',
handler: function ( queryResult ) {
if ( queryResult && queryResult.query && queryResult.query.categorymembers ) {
var titles = queryResult.query.categorymembers;
for ( var i = 0; i < titles.length; i++ ) titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace

return titles;
}
return null;
}
},
parentcategories: {
uri: '/api.php?format=json&action=query&prop=categories&titles=Category:$1&cllimit=max',
handler: function ( queryResult ) {
if ( queryResult && queryResult.query && queryResult.query.pages ) {
for ( var p in queryResult.query.pages ) {
if ( queryResult.query.pages[ p ].categories ) {
var titles = queryResult.query.pages[ p ].categories;
for ( var i = 0; i < titles.length; i++ ) titles[ i ] = titles[ i ].title.substring( titles[ i ].title.indexOf( ':' ) + 1 ); // rm namespace

return titles;
}
}
}
return null;
}
}
};

var suggestionConfigs = {
searchindex: {
name: 'Search index',
engines: [ 'opensearch' ],
cache: {},
show: true,
temp: false,
noCompletion: false
},
pagelist: {
name: 'Page list',
engines: [ 'internalsearch', 'exists' ],
cache: {},
show: true,
temp: false,
noCompletion: false
},
combined: {
name: 'Combined search',
engines: [ 'opensearch', 'internalsearch' ],
cache: {},
show: true,
temp: false,
noCompletion: false
},
subcat: {
name: 'Subcategories',
engines: [ 'subcategories' ],
cache: {},
show: true,
temp: true,
noCompletion: true
},
parentcat: {
name: 'Parent categories',
engines: [ 'parentcategories' ],
cache: {},
show: true,
temp: true,
noCompletion: true
}
};

CategoryEditor.UNCHANGED = 0;
CategoryEditor.OPEN = 1; // Open, but no input yet
CategoryEditor.CHANGE_PENDING = 2; // Open, some input made
CategoryEditor.CHANGED = 3;
CategoryEditor.DELETED = 4;

// Support: IE6
// IE6 sometimes forgets to redraw the list when editors are opened or closed.
// Adding/removing a dummy element helps, at least when opening editors.
var dummyElement = make( '\xa0', true );

function forceRedraw() {
if ( dummyElement.parentNode ) document.body.removeChild( dummyElement ); else document.body.appendChild( dummyElement );
}

// Event keyCodes that we handle in the text input field/suggestion list.
var BS = 8,
TAB = 9,
RET = 13,
ESC = 27,
SPACE = 32,
PGUP = 33,
PGDOWN = 34,
UP = 38,
DOWN = 40,
DEL = 46,
IME = 229;

CategoryEditor.prototype = {

initialize: function ( line, span, after, key, is_hidden ) {
// If a span is given, 'after' is the category title, otherwise it may be an element after which to
// insert the new span. 'key' is likewise overloaded; if a span is given, it is the category key (if
// known), otherwise it is a boolean indicating whether a bar shall be prepended.
if ( !span ) {
this.isAddCategory = true;
// Create add span and append to catLinks
this.originalCategory = '';
this.originalKey = null;
this.originalExists = false;
if ( !newDOM ) {
span = make( 'span' );
span.className = 'noprint';
if ( key ) {
span.appendChild( make( ' | ', true ) );
if ( after ) {
after.parentNode.insertBefore( span, after.nextSibling );
after = after.nextSibling;
} else if (line) {
line.appendChild( span );
}
} else if ( line && line.firstChild ) {
span.appendChild( make( ' ', true ) );
line.appendChild( span );
}
}
this.linkSpan = make( 'span' );
this.linkSpan.className = 'noprint nopopups hotcatlink';
var lk = make( 'a' );
lk.href = '#catlinks';
lk.onclick = this.open.bind( this );
lk.appendChild( make( HC.links.add, true ) );
lk.title = HC.tooltips.add;
this.linkSpan.appendChild( lk );
span = make( newDOM ? 'li' : 'span' );
span.className = 'noprint';
if ( is_rtl ) span.dir = 'rtl';

span.appendChild( this.linkSpan );
if ( after ) {
after.parentNode.insertBefore( span, after.nextSibling );
} else if ( line ) {
line.appendChild( span );
}

this.normalLinks = null;
this.undelLink = null;
this.catLink = null;
} else {
if ( is_rtl ) span.dir = 'rtl';

this.isAddCategory = false;
this.catLink = span.firstChild;
this.originalCategory = after;
this.originalKey = ( key && key.length > 1 ) ? key.substr( 1 ) : null; // > 1 because it includes the leading bar
this.originalExists = !hasClass( this.catLink, 'new' );
// Create change and del links
this.makeLinkSpan();
if ( !this.originalExists && this.upDownLinks ) this.upDownLinks.style.display = 'none';

span.appendChild( this.linkSpan );
}
this.originalHidden = is_hidden;
this.line = line;
this.engine = HC.suggestions;
this.span = span;
this.currentCategory = this.originalCategory;
this.currentExists = this.originalExists;
this.currentHidden = this.originalHidden;
this.currentKey = this.originalKey;
this.state = CategoryEditor.UNCHANGED;
this.lastSavedState = CategoryEditor.UNCHANGED;
this.lastSavedCategory = this.originalCategory;
this.lastSavedKey = this.originalKey;
this.lastSavedExists = this.originalExists;
this.lastSavedHidden = this.originalHidden;
if ( this.catLink && this.currentKey ) this.catLink.title = this.currentKey;

editors[ editors.length ] = this;
},

makeLinkSpan: function () {
this.normalLinks = make( 'span' );
var lk = null;
if ( this.originalCategory && this.originalCategory.length ) {
lk = make( 'a' );
lk.href = '#catlinks';
lk.onclick = this.remove.bind( this );
lk.appendChild( make( HC.links.remove, true ) );
lk.title = HC.tooltips.remove;
this.normalLinks.appendChild( make( ' ', true ) );
this.normalLinks.appendChild( lk );
}
if ( !HC.template_categories[ this.originalCategory ] ) {
lk = make( 'a' );
lk.href = '#catlinks';
lk.onclick = this.open.bind( this );
lk.appendChild( make( HC.links.change, true ) );
lk.title = HC.tooltips.change;
this.normalLinks.appendChild( make( ' ', true ) );
this.normalLinks.appendChild( lk );
if ( !noSuggestions && HC.use_up_down ) {
this.upDownLinks = make( 'span' );
lk = make( 'a' );
lk.href = '#catlinks';
lk.onclick = this.down.bind( this );
lk.appendChild( make( HC.links.down, true ) );
lk.title = HC.tooltips.down;
this.upDownLinks.appendChild( make( ' ', true ) );
this.upDownLinks.appendChild( lk );
lk = make( 'a' );
lk.href = '#catlinks';
lk.onclick = this.up.bind( this );
lk.appendChild( make( HC.links.up, true ) );
lk.title = HC.tooltips.up;
this.upDownLinks.appendChild( make( ' ', true ) );
this.upDownLinks.appendChild( lk );
this.normalLinks.appendChild( this.upDownLinks );
}
}
this.linkSpan = make( 'span' );
this.linkSpan.className = 'noprint nopopups hotcatlink';
this.linkSpan.appendChild( this.normalLinks );
this.undelLink = make( 'span' );
this.undelLink.className = 'nopopups hotcatlink';
this.undelLink.style.display = 'none';
lk = make( 'a' );
lk.href = '#catlinks';
lk.onclick = this.restore.bind( this );
lk.appendChild( make( HC.links.restore, true ) );
lk.title = HC.tooltips.restore;
this.undelLink.appendChild( make( ' ', true ) );
this.undelLink.appendChild( lk );
this.linkSpan.appendChild( this.undelLink );
},

invokeSuggestions: function ( dont_autocomplete ) {
if ( this.engine && suggestionConfigs[ this.engine ] && suggestionConfigs[ this.engine ].temp && !dont_autocomplete ) this.engine = HC.suggestions; // Reset to a search upon input

this.state = CategoryEditor.CHANGE_PENDING;
var self = this;
window.setTimeout( function () {
self.textchange( dont_autocomplete );
}, HC.suggest_delay );
},

makeForm: function () {
var form = make( 'form' );
form.method = 'POST';
form.onsubmit = this.accept.bind( this );
this.form = form;
var self = this;
var text = make( 'input' );
text.type = 'text';
text.size = HC.editbox_width;
if ( !noSuggestions ) {
// Be careful here to handle IME input. This is browser/OS/IME dependent, but basically there are two mechanisms:
// - Modern (DOM Level 3) browsers use compositionstart/compositionend events to signal composition; if the
// composition is not canceled, there'll be a textInput event following. During a composition key events are
// either all suppressed (FF/Gecko), or otherwise have keyDown === IME for all keys (Webkit).
// - Webkit sends a textInput followed by keyDown === IME and a keyUp with the key that ended composition.
// - Gecko doesn't send textInput but just a keyUp with the key that ended composition, without sending keyDown
// first. Gecko doesn't send any keydown while IME is active.
// - Older browsers signal composition by keyDown === IME for the first and subsequent keys for a composition. The
// first keyDown !== IME is certainly after the end of the composition. Typically, composition end can also be
// detected by a keyDown IME with a keyUp of space, tab, escape, or return.
text.onkeyup = function ( evt ) {
var key = evt.keyCode || 0;
if ( self.ime && self.lastKey === IME && !self.usesComposition && ( key === TAB || key === RET || key === ESC || key === SPACE ) ) self.ime = false;

if ( self.ime ) return true;

if ( key === UP || key === DOWN || key === PGUP || key === PGDOWN ) {
// In case a browser doesn't generate keypress events for arrow keys...
if ( self.keyCount === 0 ) return self.processKey( evt );
} else {
if ( key === ESC && self.lastKey !== IME ) {
if ( !self.resetKeySelection() ) {
// No undo of key selection: treat ESC as "cancel".
self.cancel();
return;
}
}
// Also do this for ESC as a workaround for Firefox bug 524360
// https://bugzilla.mozilla.org/show_bug.cgi?id=524360
self.invokeSuggestions( key === BS || key === DEL || key === ESC );
}
return true;
};
text.onkeydown = function ( evt ) {
var key = evt.keyCode || 0;
self.lastKey = key;
self.keyCount = 0;
// DOM Level < 3 IME input
if ( !self.ime && key === IME && !self.usesComposition ) {
// self.usesComposition catches browsers that may emit spurious keydown IME after a composition has ended
self.ime = true;
} else if ( self.ime && key !== IME && !( key >= 16 && key <= 20 || key >= 91 && key <= 93 || key === 144 ) ) {
// Ignore control keys: ctrl, shift, alt, alt gr, caps lock, windows/apple cmd keys, num lock. Only the windows keys
// terminate IME (apple cmd doesn't), but they also cause a blur, so it's OK to ignore them here.
// Note: Safari 4 (530.17) propagates ESC out of an IME composition (observed at least on Win XP).
self.ime = false;
}
if ( self.ime ) return true;

// Handle return explicitly, to override the default form submission to be able to check for ctrl
if ( key === RET ) return self.accept( evt );

// Inhibit default behavior of ESC (revert to last real input in FF: we do that ourselves)
return ( key === ESC ) ? evtKill( evt ) : true;
};
// And handle continued pressing of arrow keys
text.onkeypress = function ( evt ) {
self.keyCount++;
return self.processKey( evt );
};
$( text ).on( 'focus', function () {
makeActive( self );
} );
// On IE, blur events are asynchronous, and may thus arrive after the element has lost the focus. Since IE
// can get the selection only while the element is active (has the focus), we may not always get the selection.
// Therefore, use an IE-specific synchronous event on IE...
// Don't test for text.selectionStart being defined;
$( text ).on(
( text.onbeforedeactivate !== undefined && text.createTextRange ) ? 'beforedeactivate' : 'blur',
this.saveView.bind( this ) );
// DOM Level 3 IME handling
try {
// Setting lastKey = IME provides a fake keyDown for Gecko's single keyUp after a cmposition. If we didn't do this,
// cancelling a composition via ESC would also cancel and close the whole category input editor.
$( text ).on( 'compositionstart', function () {
self.lastKey = IME;
self.usesComposition = true;
self.ime = true;
} );
$( text ).on( 'compositionend', function () {
self.lastKey = IME;
self.usesComposition = true;
self.ime = false;
} );
$( text ).on( 'textInput', function () {
self.ime = false;
self.invokeSuggestions( false );
} );
} catch ( any ) {
// Just in case some browsers might produce exceptions with these DOM Level 3 events
}
$( text ).on( 'blur', function () {
self.usesComposition = false;
self.ime = false;
} );
}
this.text = text;

this.icon = make( 'img' );

var list = null;
if ( !noSuggestions ) {
list = make( 'select' );
list.onclick = function () {
if ( self.highlightSuggestion( 0 ) ) self.textchange( false, true );
};
list.ondblclick = function ( e ) {
if ( self.highlightSuggestion( 0 ) ) self.accept( e );
};
list.onchange = function () {
self.highlightSuggestion( 0 );
self.text.focus();
};
list.onkeyup = function ( evt ) {
if ( evt.keyCode === ESC ) {
self.resetKeySelection();
self.text.focus();
window.setTimeout( function () {
self.textchange( true );
}, HC.suggest_delay );
} else if ( evt.keyCode === RET ) {
self.accept( evt );
}
};
if ( !HC.fixed_search ) {
var engineSelector = make( 'select' );
for ( var key in suggestionConfigs ) {
if ( suggestionConfigs[ key ].show ) {
var opt = make( 'option' );
opt.value = key;
if ( key === this.engine ) opt.selected = true;

opt.appendChild( make( suggestionConfigs[ key ].name, true ) );
engineSelector.appendChild( opt );
}
}
engineSelector.onchange = function () {
self.engine = self.engineSelector.options[ self.engineSelector.selectedIndex ].value;
self.text.focus();
self.textchange( true, true ); // Don't autocomplete, force re-display of list
};
this.engineSelector = engineSelector;
}
}
this.list = list;

function button_label( id, defaultText ) {
var label = null;
if (
onUpload &&
window.UFUI !== undefined &&
window.UIElements !== undefined &&
UFUI.getLabel instanceof Function
) {
try {
label = UFUI.getLabel( id, true );
// Extract the plain text. IE doesn't know that Node.TEXT_NODE === 3
while ( label && label.nodeType !== 3 ) label = label.firstChild;
} catch ( ex ) {
label = null;
}
}
if ( !label || !label.data ) return defaultText;

return label.data;
}

// Do not use type 'submit'; we cannot detect modifier keys if we do
var OK = make( 'input' );
OK.type = 'button';
OK.value = button_label( 'wpOkUploadLbl', HC.messages.ok );
OK.onclick = this.accept.bind( this );
this.ok = OK;

var cancel = make( 'input' );
cancel.type = 'button';
cancel.value = button_label( 'wpCancelUploadLbl', HC.messages.cancel );
cancel.onclick = this.cancel.bind( this );
this.cancelButton = cancel;

var span = make( 'span' );
span.className = 'hotcatinput';
span.style.position = 'relative';
span.appendChild( text );

// Support: IE8, IE9
// Put some text into this span (a0 is nbsp) and make sure it always stays on the same
// line as the input field, otherwise, IE8/9 miscalculates the height of the span and
// then the engine selector may overlap the input field.
span.appendChild( make( '\xa0', true ) );
span.style.whiteSpace = 'nowrap';

if ( list ) span.appendChild( list );

if ( this.engineSelector ) span.appendChild( this.engineSelector );

if ( !noSuggestions ) span.appendChild( this.icon );

span.appendChild( OK );
span.appendChild( cancel );
form.appendChild( span );
form.style.display = 'none';
this.span.appendChild( form );
},

display: function ( evt ) {
if ( this.isAddCategory && !onUpload && this.line ) {
// eslint-disable-next-line no-new
new CategoryEditor( this.line, null, this.span, true ); // Create a new one
}
if ( !commitButton && !onUpload ) {
for ( var i = 0; i < editors.length; i++ ) {
if ( editors[ i ].state !== CategoryEditor.UNCHANGED ) {
setMultiInput();
break;
}
}
}
if ( !this.form ) this.makeForm();

if ( this.list ) this.list.style.display = 'none';

if ( this.engineSelector ) this.engineSelector.style.display = 'none';

this.currentCategory = this.lastSavedCategory;
this.currentExists = this.lastSavedExists;
this.currentHidden = this.lastSavedHidden;
this.currentKey = this.lastSavedKey;
this.icon.src = ( this.currentExists ? HC.existsYes : HC.existsNo );
this.text.value = this.currentCategory + ( this.currentKey !== null ? '|' + this.currentKey : '' );
this.originalState = this.state;
this.lastInput = this.currentCategory;
this.inputExists = this.currentExists;
this.state = this.state === CategoryEditor.UNCHANGED ? CategoryEditor.OPEN : CategoryEditor.CHANGE_PENDING;
this.lastSelection = {
start: this.currentCategory.length,
end: this.currentCategory.length
};
this.showsList = false;
// Display the form
if ( this.catLink ) this.catLink.style.display = 'none';

this.linkSpan.style.display = 'none';
this.form.style.display = 'inline';
this.ok.disabled = false;
// Kill the event before focussing, otherwise IE will kill the onfocus event!
var result = evtKill( evt );
this.text.focus();
this.text.readOnly = false;
checkMultiInput();
return result;
},

show: function ( evt, engine, readOnly ) {
var result = this.display( evt );
var v = this.lastSavedCategory;
if ( !v.length ) return result;

this.text.readOnly = !!readOnly;
this.engine = engine;
this.textchange( false, true ); // do autocompletion, force display of suggestions
forceRedraw();
return result;
},

open: function ( evt ) {
return this.show( evt, ( this.engine && suggestionConfigs[ this.engine ].temp ) ? HC.suggestions : this.engine );
},

down: function ( evt ) {
return this.show( evt, 'subcat', true );
},

up: function ( evt ) {
return this.show( evt, 'parentcat' );
},

cancel: function () {
if ( this.isAddCategory && !onUpload ) {
this.removeEditor(); // We added a new adder when opening
return;
}
// Close, re-display link
this.inactivate();
this.form.style.display = 'none';
if ( this.catLink ) this.catLink.style.display = '';

this.linkSpan.style.display = '';
this.state = this.originalState;
this.currentCategory = this.lastSavedCategory;
this.currentKey = this.lastSavedKey;
this.currentExists = this.lastSavedExists;
this.currentHidden = this.lastSavedHidden;
if ( this.catLink )
if ( this.currentKey && this.currentKey.length ) { this.catLink.title = this.currentKey; } else { this.catLink.title = ''; }

if ( this.state === CategoryEditor.UNCHANGED ) {
if ( this.catLink ) this.catLink.style.backgroundColor = 'transparent';
} else {
if ( !onUpload ) {
try {
this.catLink.style.backgroundColor = HC.bg_changed;
} catch ( ex ) {}
}
}
checkMultiInput();
forceRedraw();
},

removeEditor: function () {
if ( !newDOM ) {
var next = this.span.nextSibling;
if ( next ) next.parentNode.removeChild( next );
}
if (this.span && this.span.parentNode) {
this.span.parentNode.removeChild( this.span );
}
for ( var i = 0; i < editors.length; i++ ) {
if ( editors[ i ] === this ) {
editors.splice( i, 1 );
break;
}
}
checkMultiInput();
},

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

checkMultiInput();
}
return evtKill( evt );
},

inactivate: function () {
if ( this.list ) this.list.style.display = 'none';

if ( this.engineSelector ) this.engineSelector.style.display = 'none';

this.is_active = false;
},

acceptCheck: function ( dontCheck ) {
this.sanitizeInput();
var value = this.text.value.split( '|' );
var key = null;
if ( value.length > 1 ) key = value[ 1 ];

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

this.lastInput = v;
v = replaceShortcuts( v, HC.shortcuts );
if ( !v.length ) {
this.cancel();
return false;
}
if ( !dontCheck && (
conf.wgNamespaceNumber === 14 && v === conf.wgTitle || HC.blacklist && HC.blacklist.test( v ) ) ) {
this.cancel();
return false;
}
this.currentCategory = v;
this.currentKey = key;
this.currentExists = this.inputExists;
return true;
},

accept: function ( evt ) {
// eslint-disable-next-line no-bitwise
this.noCommit = ( evtKeys( evt ) & 1 ) !== 0;
var result = evtKill( evt );
if ( this.acceptCheck() ) {
var toResolve = [ this ];
var original = this.currentCategory;
resolveMulti( toResolve, function ( resolved ) {
if ( resolved[ 0 ].dab ) {
showDab( resolved[ 0 ] );
} else {
if ( resolved[ 0 ].acceptCheck( true ) ) {
resolved[ 0 ].commit(
( resolved[ 0 ].currentCategory !== original ) ?
HC.messages.cat_resolved.replace( /\$1/g, original ) :
null );
}
}
} );
}
return result;
},

close: function () {
if ( !this.catLink ) {
// Create a catLink
this.catLink = make( 'a' );
this.catLink.appendChild( make( 'foo', true ) );
this.catLink.style.display = 'none';
this.span.insertBefore( this.catLink, this.span.firstChild.nextSibling );
}
this.catLink.removeChild( this.catLink.firstChild );
this.catLink.appendChild( make( this.currentCategory, true ) );
this.catLink.href = wikiPagePath( HC.category_canonical + ':' + this.currentCategory );
this.catLink.className = this.currentExists ? '' : 'new';
this.lastSavedCategory = this.currentCategory;
this.lastSavedKey = this.currentKey;
this.lastSavedExists = this.currentExists;
this.lastSavedHidden = this.currentHidden;
// Close form and redisplay category
this.inactivate();
this.form.style.display = 'none';
this.catLink.title = this.currentKey || '';
this.catLink.style.display = '';
if ( this.isAddCategory ) {
if ( onUpload && this.line ) {
// eslint-disable-next-line no-new
new CategoryEditor( this.line, null, this.span, true ); // Create a new one
}
this.isAddCategory = false;
this.linkSpan.parentNode.removeChild( this.linkSpan );
this.makeLinkSpan();
this.span.appendChild( this.linkSpan );
}
if ( !this.undoLink ) {
// Append an undo link.
var span = make( 'span' );
var lk = make( 'a' );
lk.href = '#catlinks';
lk.onclick = this.rollback.bind( this );
lk.appendChild( make( HC.links.undo, true ) );
lk.title = HC.tooltips.undo;
span.appendChild( make( ' ', true ) );
span.appendChild( lk );
this.normalLinks.appendChild( span );
this.undoLink = span;
if ( !onUpload ) {
try {
this.catLink.style.backgroundColor = HC.bg_changed;
} catch ( ex ) {}
}
}
if ( this.upDownLinks ) this.upDownLinks.style.display = this.lastSavedExists ? '' : 'none';

this.linkSpan.style.display = '';
this.state = CategoryEditor.CHANGED;
checkMultiInput();
forceRedraw();
},

commit: function () {
// Check again to catch problem cases after redirect resolution
if (
(
this.currentCategory === this.originalCategory &&
(
this.currentKey === this.originalKey ||
this.currentKey === null && !this.originalKey.length
)
) ||
conf.wgNamespaceNumber === 14 && this.currentCategory === conf.wgTitle ||
HC.blacklist && HC.blacklist.test( this.currentCategory )
) {
this.cancel();
return;
}
this.close();
if ( !commitButton && !onUpload ) {
var self = this;
initiateEdit( function ( failure ) {
performChanges( failure, self );
}, function ( msg ) {
alert( msg );
} );
}
},

remove: function ( evt ) {
// eslint-disable-next-line no-bitwise
this.doRemove( evtKeys( evt ) & 1 );
return evtKill( evt );
},

doRemove: function ( noCommit ) {
if ( this.isAddCategory ) { // Empty input on adding a new category
this.cancel();
return;
}
if ( !commitButton && !onUpload ) {
for ( var i = 0; i < editors.length; i++ ) {
if ( editors[ i ].state !== CategoryEditor.UNCHANGED ) {
setMultiInput();
break;
}
}
}
if ( commitButton ) {
this.catLink.title = '';
this.catLink.style.cssText += '; text-decoration : line-through !important;';
try {
this.catLink.style.backgroundColor = HC.bg_changed;
} catch ( ex ) {}
this.originalState = this.state;
this.state = CategoryEditor.DELETED;
this.normalLinks.style.display = 'none';
this.undelLink.style.display = '';
checkMultiInput();
} else {
if ( onUpload ) {
// Remove this editor completely
this.removeEditor();
} else {
this.originalState = this.state;
this.state = CategoryEditor.DELETED;
this.noCommit = noCommit || HC.del_needs_diff;
var self = this;
initiateEdit(
function ( failure ) {
performChanges( failure, self );
},
function ( msg ) {
self.state = self.originalState;
alert( msg );
} );
}
}
},

restore: function ( evt ) {
// Can occur only if we do have a commit button and are not on the upload form
this.catLink.title = this.currentKey || '';
this.catLink.style.textDecoration = '';
this.state = this.originalState;
if ( this.state === CategoryEditor.UNCHANGED ) {
this.catLink.style.backgroundColor = 'transparent';
} else {
try {
this.catLink.style.backgroundColor = HC.bg_changed;
} catch ( ex ) {}
}
this.normalLinks.style.display = '';
this.undelLink.style.display = 'none';
checkMultiInput();
return evtKill( evt );
},

// Internal operations

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

sanitizeInput: function () {
var v = this.text.value || '';
v = v.replace( /^(\s|_)+/, '' ); // Trim leading blanks and underscores
var re = new RegExp( '^(' + HC.category_regexp + '):' );
if ( re.test( v ) ) v = v.substring( v.indexOf( ':' ) + 1 ).replace( /^(\s|_)+/, '' );
v = v.replace(/\u200E$/, ''); // Trim ending left-to-right mark
if ( HC.capitalizePageNames ) v = capitalize( v );

// Update the input field if needed, manually restoring the cursor afterwards
// (but not the whole selection, so that further typing does not overwrite anything)
if ( this.text.value !== null && this.text.value !== v ) {
var cursorPos = this.text.selectionStart;
this.text.value = v;
this.text.setSelectionRange( cursorPos, cursorPos );
}
},

makeCall: function ( url, callbackObj, engine, queryKey, cleanKey ) {
var cb = callbackObj,
e = engine,
v = queryKey,
z = cleanKey,
thisObj = this;

function done() {
cb.callsMade++;
if ( cb.callsMade === cb.nofCalls ) {
if ( cb.exists ) cb.allTitles.exists = true;

if ( cb.normalized ) cb.allTitles.normalized = cb.normalized;

if ( !cb.dontCache && !suggestionConfigs[ cb.engineName ].cache[ z ] ) suggestionConfigs[ cb.engineName ].cache[ z ] = cb.allTitles;

thisObj.text.readOnly = false;
if ( !cb.cancelled ) thisObj.showSuggestions( cb.allTitles, cb.noCompletion, v, cb.engineName );

if ( cb === thisObj.callbackObj ) thisObj.callbackObj = null;

cb = undefined;
}
}

$.getJSON( url, function ( json ) {
var titles = e.handler( json, z );
if ( titles && titles.length ) {
if ( cb.allTitles === null ) cb.allTitles = titles; else cb.allTitles = cb.allTitles.concat( titles );
if ( titles.exists ) cb.exists = true;
if ( titles.normalized ) cb.normalized = titles.normalized;
}
done();
} ).fail( function ( req ) {
if ( !req ) noSuggestions = true;
cb.dontCache = true;
done();
} );
},

callbackObj: null,

textchange: function ( dont_autocomplete, force ) {
// Hide all other lists
makeActive( this );
// Get input value, omit sort key, if any
this.sanitizeInput();
var v = this.text.value;
// Disregard anything after a pipe.
var pipe = v.indexOf( '|' );
if ( pipe >= 0 ) {
this.currentKey = v.substring( pipe + 1 );
v = v.substring( 0, pipe );
} else {
this.currentKey = null;
}
if ( this.lastInput === v && !force ) return; // No change
if ( this.lastInput !== v ) checkMultiInput();

this.lastInput = v;
this.lastRealInput = v;

// Mark blacklisted inputs.
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 ( !v.length ) {
this.showSuggestions( [] );
return;
}
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;
}

if ( this.callbackObj ) this.callbackObj.cancelled = true;

var engineName = suggestionConfigs[ this.engine ] ? this.engine : 'combined';

dont_autocomplete = dont_autocomplete || suggestionConfigs[ engineName ].noCompletion;
if ( suggestionConfigs[ engineName ].cache[ cleanKey ] ) {
this.showSuggestions( suggestionConfigs[ engineName ].cache[ cleanKey ], dont_autocomplete, v, engineName );
return;
}

var engines = suggestionConfigs[ engineName ].engines;
this.callbackObj = {
allTitles: null,
callsMade: 0,
nofCalls: engines.length,
noCompletion: dont_autocomplete,
engineName: engineName
};
this.makeCalls( engines, this.callbackObj, v, cleanKey );
},

makeCalls: function ( engines, cb, v, cleanKey ) {
for ( var j = 0; j < engines.length; j++ ) {
var engine = suggestionEngines[ engines[ j ] ];
var url = conf.wgScriptPath + engine.uri.replace( /\$1/g, encodeURIComponent( cleanKey ) );
this.makeCall( url, cb, engine, v, cleanKey );
}
},

showSuggestions: function ( titles, dontAutocomplete, queryKey, engineName ) {
this.text.readOnly = false;
this.dab = null;
this.showsList = false;
if ( !this.list ) return;
if ( noSuggestions ) {
if ( this.list ) this.list.style.display = 'none';

if ( this.engineSelector ) this.engineSelector.style.display = 'none';

if ( this.icon ) this.icon.style.display = 'none';

this.inputExists = true; // Default...
return;
}
this.engineName = engineName;
if ( engineName ) {
if ( !this.engineSelector ) this.engineName = null;
} else {
if ( this.engineSelector ) this.engineSelector.style.display = 'none';
}
if ( queryKey ) {
if ( this.lastInput.indexOf( queryKey ) ) return;
if ( this.lastQuery && this.lastInput.indexOf( this.lastQuery ) === 0 && this.lastQuery.length > queryKey.length ) return;
}
this.lastQuery = queryKey;

// Get current input text
var v = this.text.value.split( '|' );
var key = v.length > 1 ? '|' + v[ 1 ] : '';
v = ( HC.capitalizePageNames ? capitalize( v[ 0 ] ) : v[ 0 ] );
var vNormalized = v;
var knownToExist = titles && titles.exists;
var i;
if ( titles ) {
if ( titles.normalized && v.indexOf( queryKey ) === 0 ) {
// We got back a different normalization than what is in the input field
vNormalized = titles.normalized + v.substring( queryKey.length );
}
var vLow = vNormalized.toLowerCase();
// Strip blacklisted categories
if ( HC.blacklist ) {
for ( i = 0; i < titles.length; i++ ) {
if ( HC.blacklist.test( titles[ i ] ) ) {
titles.splice( i, 1 );
i--;
}
}
}
titles.sort(
function ( a, b ) {
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
// Opensearch may return stuff not beginning with the search prefix!
var prefixMatchA = ( a.indexOf( vNormalized ) === 0 ? 1 : 0 );
var prefixMatchB = ( b.indexOf( vNormalized ) === 0 ? 1 : 0 );
if ( prefixMatchA !== prefixMatchB ) return prefixMatchB - prefixMatchA;

// Case-insensitive prefix match!
var aLow = a.toLowerCase(),
bLow = b.toLowerCase();
prefixMatchA = ( aLow.indexOf( vLow ) === 0 ? 1 : 0 );
prefixMatchB = ( bLow.indexOf( vLow ) === 0 ? 1 : 0 );
if ( prefixMatchA !== prefixMatchB ) return prefixMatchB - prefixMatchA;

if ( a < b ) return -1;

if ( b < a ) return 1;

return 0;
} );
// Remove duplicates and self-references
for ( i = 0; i < titles.length; i++ ) {
if (
i + 1 < titles.length && titles[ i ] === titles[ i + 1 ] ||
conf.wgNamespaceNumber === 14 && titles[ i ] === conf.wgTitle
) {
titles.splice( i, 1 );
i--;
}
}
}
if ( !titles || !titles.length ) {
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.icon ) this.icon.src = HC.existsNo;

this.inputExists = false;
}
return;
}

var firstTitle = titles[ 0 ];
var completed = this.autoComplete( firstTitle, v, vNormalized, key, dontAutocomplete );
var existing = completed || knownToExist || firstTitle === replaceShortcuts( v, HC.shortcuts );
if ( engineName && suggestionConfigs[ engineName ] && !suggestionConfigs[ engineName ].temp ) {
this.icon.src = ( existing ? HC.existsYes : HC.existsNo );
this.inputExists = existing;
}
if ( completed ) {
this.lastInput = firstTitle;
if ( titles.length === 1 ) {
this.list.style.display = 'none';
if ( this.engineSelector ) this.engineSelector.style.display = 'none';

return;
}
}
// (Re-)fill the list
while ( this.list.firstChild ) this.list.removeChild( this.list.firstChild );

for ( i = 0; i < titles.length; i++ ) {
var opt = make( 'option' );
opt.appendChild( make( titles[ i ], true ) );
opt.selected = completed && ( i === 0 );
this.list.appendChild( opt );
}
this.displayList();
},

displayList: function () {
this.showsList = true;
if ( !this.is_active ) {
this.list.style.display = 'none';
if ( this.engineSelector ) this.engineSelector.style.display = 'none';

return;
}
var nofItems = ( this.list.options.length > HC.listSize ? HC.listSize : this.list.options.length );
if ( nofItems <= 1 ) nofItems = 2;

this.list.size = nofItems;
this.list.style.align = is_rtl ? 'right' : 'left';
this.list.style.zIndex = 5;
this.list.style.position = 'absolute';
// Compute initial list position. First the height.
var anchor = is_rtl ? 'right' : 'left';
var listh = 0;
if ( this.list.style.display === 'none' ) {
// Off-screen display to get the height
this.list.style.top = this.text.offsetTop + 'px';
this.list.style[ anchor ] = '-10000px';
this.list.style.display = '';
listh = this.list.offsetHeight;
this.list.style.display = 'none';
} else {
listh = this.list.offsetHeight;
}
// Approximate calculation of maximum list size
var maxListHeight = listh;
if ( nofItems < HC.listSize ) maxListHeight = ( listh / nofItems ) * HC.listSize;

function viewport( what ) {
if ( is_webkit && !document.evaluate ) {
// Safari < 3.0
return window[ 'inner' + what ];
}
var s = 'client' + what;
if ( window.opera ) return document.body[ s ];

return ( document.documentElement ? document.documentElement[ s ] : 0 ) || document.body[ s ] || 0;
}
function scroll_offset( what ) {
var s = 'scroll' + what;
var result = ( document.documentElement ? document.documentElement[ s ] : 0 ) || document.body[ s ] || 0;
if ( is_rtl && what === 'Left' ) {
// RTL inconsistencies.
// FF: 0 at the far right, then increasingly negative values.
// IE >= 8: 0 at the far right, then increasingly positive values.
// Webkit: scrollWidth - clientWidth at the far right, then down to zero.
// Opera: don't know...
if ( result < 0 ) result = -result;

if ( !is_webkit ) result = scroll_offset( 'Width' ) - viewport( 'Width' ) - result;

// Now all have webkit behavior, i.e. zero if at the leftmost edge.
}
return result;
}
function position( node ) {
// Stripped-down simplified position function. It's good enough for our purposes.
if ( node.getBoundingClientRect ) {
var box = node.getBoundingClientRect();
return {
x: Math.round( box.left + scroll_offset( 'Left' ) ),
y: Math.round( box.top + scroll_offset( 'Top' ) )
};
}
var t = 0,
l = 0;
do {
t += ( node.offsetTop || 0 );
l += ( node.offsetLeft || 0 );
node = node.offsetParent;
} while ( node );
return {
x: l,
y: t
};
}

var textPos = position( this.text ),
nl = 0,
nt = 0,
offset = 0,
// Opera 9.5 somehow has offsetWidth = 0 here?? Use the next best value...
textBoxWidth = this.text.offsetWidth || this.text.clientWidth;
if ( this.engineName ) {
this.engineSelector.style.zIndex = 5;
this.engineSelector.style.position = 'absolute';
this.engineSelector.style.width = textBoxWidth + 'px';
// Figure out the height of this selector: display it off-screen, then hide it again.
if ( this.engineSelector.style.display === 'none' ) {
this.engineSelector.style[ anchor ] = '-10000px';
this.engineSelector.style.top = '0';
this.engineSelector.style.display = '';
offset = this.engineSelector.offsetHeight;
this.engineSelector.style.display = 'none';
} else {
offset = this.engineSelector.offsetHeight;
}
this.engineSelector.style[ anchor ] = nl + 'px';
}
if ( textPos.y < maxListHeight + offset + 1 ) {
// The list might extend beyond the upper border of the page. Let's avoid that by placing it
// below the input text field.
nt = this.text.offsetHeight + offset + 1;
if ( this.engineName ) this.engineSelector.style.top = this.text.offsetHeight + 'px';
} else {
nt = -listh - offset - 1;
if ( this.engineName ) this.engineSelector.style.top = -( offset + 1 ) + 'px';
}
this.list.style.top = nt + 'px';
this.list.style.width = ''; // No fixed width (yet)
this.list.style[ anchor ] = nl + 'px';
if ( this.engineName ) {
this.selectEngine( this.engineName );
this.engineSelector.style.display = '';
}
this.list.style.display = 'block';
// Set the width of the list
if ( this.list.offsetWidth < textBoxWidth ) {
this.list.style.width = textBoxWidth + 'px';
return;
}
// If the list is wider than the textbox: make sure it fits horizontally into the browser window
var scroll = scroll_offset( 'Left' );
var view_w = viewport( 'Width' );
var w = this.list.offsetWidth;
var l_pos = position( this.list );
var left = l_pos.x;
var right = left + w;
if ( left < scroll || right > scroll + view_w ) {
if ( w > view_w ) {
w = view_w;
this.list.style.width = w + 'px';
if ( is_rtl ) left = right - w; else right = left + w;
}
var relative_offset = 0;
if ( left < scroll ) relative_offset = scroll - left; else if ( right > scroll + view_w ) relative_offset = -( right - scroll - view_w );

if ( is_rtl ) relative_offset = -relative_offset;

if ( relative_offset ) this.list.style[ anchor ] = ( nl + relative_offset ) + 'px';
}
},

autoComplete: function ( newVal, actVal, normalizedActVal, key, dontModify ) {
if ( newVal === actVal ) return true;

if ( dontModify || this.ime || !this.canSelect() ) return false;

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

actVal = normalizedActVal;
} else {
return false;
}
}
// Actual input is a prefix of the new text. Fill in new text, selecting the newly added suffix
// such that it can be easily removed by typing backspace if the suggestion is unwanted.
this.text.focus();
this.text.value = newVal + key;
this.setSelection( actVal.length, newVal.length );
return true;
},

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

setSelection: function ( from, to ) {
// this.text must be focused (at least on IE)
if ( !this.text.value ) return;
if ( this.text.setSelectionRange ) { // e.g. khtml
this.text.setSelectionRange( from, to );
} else if ( this.text.selectionStart !== undefined ) {
if ( from > this.text.selectionStart ) {
this.text.selectionEnd = to;
this.text.selectionStart = from;
} else {
this.text.selectionStart = from;
this.text.selectionEnd = to;
}
} else if ( this.text.createTextRange ) { // IE
var new_selection = this.text.createTextRange();
new_selection.move( 'character', from );
new_selection.moveEnd( 'character', to - from );
new_selection.select();
}
},

getSelection: function () {
var from = 0,
to = 0;
// this.text must be focused (at least on IE)
if ( !this.text.value ) {
// No text.
} else if ( this.text.selectionStart !== undefined ) {
from = this.text.selectionStart;
to = this.text.selectionEnd;
} else if ( document.selection && document.selection.createRange ) { // IE
var rng = document.selection.createRange().duplicate();
if ( rng.parentElement() === this.text ) {
try {
var textRng = this.text.createTextRange();
textRng.move( 'character', 0 );
textRng.setEndPoint( 'EndToEnd', rng );
// We're in a single-line input box: no need to care about IE's strange
// handling of line ends
to = textRng.text.length;
textRng.setEndPoint( 'EndToStart', rng );
from = textRng.text.length;
} catch ( notFocused ) {
from = this.text.value.length;
to = from; // At end of text
}
}
}
return {
start: from,
end: to
};
},

saveView: function () {
this.lastSelection = this.getSelection();
},

processKey: function ( evt ) {
var dir = 0;
switch ( this.lastKey ) {
case UP:
dir = -1;
break;
case DOWN:
dir = 1;
break;
case PGUP:
dir = -HC.listSize;
break;
case PGDOWN:
dir = HC.listSize;
break;
case ESC: // Inhibit default behavior (revert to last real input in FF: we do that ourselves)
return evtKill( evt );
}
if ( dir ) {
if ( this.list.style.display !== 'none' ) {
// List is visible, so there are suggestions
this.highlightSuggestion( dir );
// Kill the event, otherwise some browsers (e.g., Firefox) may additionally treat an up-arrow
// as "place the text cursor at the front", which we don't want here.
return evtKill( evt );
} else if (
this.keyCount <= 1 &&
( !this.callbackObj || this.callbackObj.callsMade === this.callbackObj.nofCalls )
) {
// If no suggestions displayed, get them, unless we're already getting them.
this.textchange();
}
}
return true;
},

highlightSuggestion: function ( dir ) {
if ( noSuggestions || !this.list || this.list.style.display === 'none' ) return false;

var curr = this.list.selectedIndex;
var tgt = -1;
if ( dir === 0 ) {
if ( curr < 0 || curr >= this.list.options.length ) return false;

tgt = curr;
} else {
tgt = curr < 0 ? 0 : curr + dir;
tgt = tgt < 0 ? 0 : tgt;
if ( tgt >= this.list.options.length ) tgt = this.list.options.length - 1;
}
if ( tgt !== curr || dir === 0 ) {
if ( curr >= 0 && curr < this.list.options.length && dir !== 0 ) this.list.options[ curr ].selected = false;

this.list.options[ tgt ].selected = true;
// Get current input text
var v = this.text.value.split( '|' );
var key = v.length > 1 ? '|' + v[ 1 ] : '';
var completed = this.autoComplete( this.list.options[ tgt ].text, this.lastRealInput, null, key, false );
if ( !completed || this.list.options[ tgt ].text === this.lastRealInput ) {
this.text.value = this.list.options[ tgt ].text + key;
if ( this.canSelect() ) this.setSelection( this.list.options[ tgt ].text.length, this.list.options[ tgt ].text.length );
}
this.lastInput = this.list.options[ tgt ].text;
this.inputExists = true; // Might be wrong if from a dab list...
if ( this.icon ) this.icon.src = HC.existsYes;

this.state = CategoryEditor.CHANGE_PENDING;
}
return true;
},

resetKeySelection: function () {
if ( noSuggestions || !this.list || this.list.style.display === 'none' ) return false;

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

function initialize() {
// User configurations: Do this here, called from the onload handler, so that users can
// override it easily in their own user script files by just declaring variables. JSconfig
// is some feature used at Wikimedia Commons.
var config = ( window.JSconfig !== undefined && JSconfig.keys ) ? JSconfig.keys : {};
HC.dont_add_to_watchlist = ( window.hotcat_dont_add_to_watchlist !== undefined ?
!!window.hotcat_dont_add_to_watchlist :
( config.HotCatDontAddToWatchlist !== undefined ? config.HotCatDontAddToWatchlist :
HC.dont_add_to_watchlist ) );
HC.no_autocommit = ( window.hotcat_no_autocommit !== undefined ?
!!window.hotcat_no_autocommit : ( config.HotCatNoAutoCommit !== undefined ?
config.HotCatNoAutoCommit :
// On talk namespace default autocommit off
( conf.wgNamespaceNumber % 2 ?
true : HC.no_autocommit ) ) );
HC.del_needs_diff = ( window.hotcat_del_needs_diff !== undefined ?
!!window.hotcat_del_needs_diff :
( config.HotCatDelNeedsDiff !== undefined ?
config.HotCatDelNeedsDiff :
HC.del_needs_diff ) );
HC.suggest_delay = window.hotcat_suggestion_delay || config.HotCatSuggestionDelay || HC.suggest_delay;
HC.editbox_width = window.hotcat_editbox_width || config.HotCatEditBoxWidth || HC.editbox_width;
HC.suggestions = window.hotcat_suggestions || config.HotCatSuggestions || HC.suggestions;
if ( typeof HC.suggestions !== 'string' || !suggestionConfigs[ HC.suggestions ] ) HC.suggestions = 'combined';

HC.fixed_search = ( window.hotcat_suggestions_fixed !== undefined ?
!!window.hotcat_suggestions_fixed : ( config.HotCatFixedSuggestions !== undefined ?
config.HotCatFixedSuggestions : HC.fixed_search ) );
HC.single_minor = ( window.hotcat_single_changes_are_minor !== undefined ?
!!window.hotcat_single_changes_are_minor :
( config.HotCatMinorSingleChanges !== undefined ?
config.HotCatMinorSingleChanges :
HC.single_minor ) );
HC.bg_changed = window.hotcat_changed_background || config.HotCatChangedBackground || HC.bg_changed;
HC.use_up_down = ( window.hotcat_use_category_links !== undefined ?
!!window.hotcat_use_category_links :
( config.HotCatUseCategoryLinks !== undefined ?
config.HotCatUseCategoryLinks :
HC.use_up_down ) );
HC.listSize = window.hotcat_list_size || config.HotCatListSize || HC.listSize;
if ( conf.wgDBname !== 'commonswiki' ) HC.changeTag = config.HotCatChangeTag || '';

// The next whole shebang is needed, because manual tags get not submitted except of save
if ( HC.changeTag ) {
var eForm = document.editform,
catRegExp = new RegExp( '^\\[\\[(' + HC.category_regexp + '):' ),
oldTxt;
// Returns true if minor change
var isMinorChange = function () {
var newTxt = eForm.wpTextbox1;
if ( !newTxt ) return;
newTxt = newTxt.value;
var oldLines = oldTxt.match( /^.*$/gm ),
newLines = newTxt.match( /^.*$/gm ),
cArr; // changes
var except = function ( aArr, bArr ) {
var result = [],
lArr, // larger
sArr; // smaller
if ( aArr.length < bArr.length ) {
lArr = bArr;
sArr = aArr;
} else {
lArr = aArr;
sArr = bArr;
}
for ( var i = 0; i < lArr.length; i++ ) {
var item = lArr[ i ];
var ind = $.inArray( item, sArr );
if ( ind === -1 ) result.push( item );
else sArr.splice( ind, 1 ); // don't check this item again
}
return result.concat( sArr );
};
cArr = except( oldLines, newLines );
if ( cArr.length ) {
cArr = $.grep( cArr, function ( c ) {
c = $.trim( c );
return ( c && !catRegExp.test( c ) );
} );
}
if ( !cArr.length ) {
oldTxt = newTxt;
return true;
}
};

if ( conf.wgAction === 'submit' && conf.wgArticleId && eForm && eForm.wpSummary && document.getElementById( 'wikiDiff' ) ) {
var sum = eForm.wpSummary,
sumA = eForm.wpAutoSummary;
if ( sum.value && sumA.value === HC.changeTag ) { // HotCat diff
// MD5 hash of the empty string, as HotCat edit is based on empty sum
sumA.value = sumA.value.replace( HC.changeTag, 'd41d8cd98f00b204e9800998ecf8427e' );
// Attr creation and event handling is not same in all (old) browsers so use $
var $ct = $( '<input type="hidden" name="wpChangeTags">' ).val( HC.changeTag );
$( eForm ).append( $ct );
oldTxt = eForm.wpTextbox1.value;
$( '#wpSave' ).one( 'click', function () {
if ( $ct.val() )
sum.value = sum.value.replace( ( HC.messages.using || HC.messages.prefix ), '' );

} );
var removeChangeTag = function () {
$( eForm.wpTextbox1 ).add( sum ).one( 'input', function () {
window.setTimeout( function () {
if ( !isMinorChange() ) $ct.val( '' );
else removeChangeTag();
}, 500 );
} );
};
removeChangeTag();
}
}
}
// Numeric input, make sure we have a numeric value
HC.listSize = parseInt( HC.listSize, 10 );
if ( isNaN( HC.listSize ) || HC.listSize < 5 ) HC.listSize = 5;

HC.listSize = Math.min( HC.listSize, 30 ); // Max size

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

}
// Catch both native RTL and "faked" RTL through [[MediaWiki:Rtl.js]]
is_rtl = hasClass( document.body, 'rtl' );
if ( !is_rtl ) {
if ( document.defaultView && document.defaultView.getComputedStyle ) { // Gecko etc.
is_rtl = document.defaultView.getComputedStyle( document.body, null ).getPropertyValue( 'direction' );
} else if ( document.body.currentStyle ) { // IE, has subtle differences to getComputedStyle
is_rtl = document.body.currentStyle.direction;
} else { // Not exactly right, but best effort
is_rtl = document.body.style.direction;
}
is_rtl = ( is_rtl === 'rtl' );
}
}

function can_edit() {
var container = null;
switch ( mw.config.get( 'skin' ) ) {
case 'cologneblue':
container = document.getElementById( 'quickbar' );
/* fall through */
case 'standard':
case 'nostalgia':
if ( !container ) container = document.getElementById( 'topbar' );
var lks = container.getElementsByTagName( 'a' );
for ( var i = 0; i < lks.length; i++ ) {
if (
param( 'title', lks[ i ].href ) === conf.wgPageName &&
param( 'action', lks[ i ].href ) === 'edit'
) {
return true;
}
}
return false;
default:
// all modern skins:
return document.getElementById( 'ca-edit' ) !== null;
}
}

// Legacy stuff
function closeForm() {
// Close all open editors without redirect resolution and other asynchronous stuff.
for ( var i = 0; i < editors.length; i++ ) {
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();
}
}
}
}

function setup_upload() {
onUpload = true;
// 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' );
if ( !ip ) {
ip = document.getElementById( 'wpDestFile' );
while ( ip && ip.nodeName.toLowerCase() !== 'table' ) ip = ip.parentNode;
}
if ( !ip ) return;
var reupload = document.getElementById( 'wpForReUpload' );
var destFile = document.getElementById( 'wpDestFile' );
if (
( reupload && !!reupload.value ) ||
( destFile && ( destFile.disabled || destFile.readOnly ) )
) {
return; // re-upload form...
}
// Insert a table row with two fields (label and empty category bar)
var labelCell = make( 'td' );
var lineCell = make( 'td' );
// Create the category line
catLine = make( 'div' );
catLine.className = 'catlinks';
catLine.id = 'catlinks';
catLine.style.textAlign = is_rtl ? 'right' : 'left';
// We'll be inside a table row. Make sure that we don't have margins or strange borders.
catLine.style.margin = '0';
catLine.style.border = 'none';
lineCell.appendChild( catLine );
// Create the label
var label = null;
if ( window.UFUI && window.UIElements && UFUI.getLabel instanceof Function ) {
try {
label = UFUI.getLabel( 'wpCategoriesUploadLbl' );
} catch ( ex ) {
label = null;
}
}
if ( !label ) {
labelCell.id = 'hotcatLabel';
labelCell.appendChild( make( HC.categories, true ) );
} else {
labelCell.id = 'hotcatLabelTranslated';
labelCell.appendChild( label );
}
labelCell.className = 'mw-label';
labelCell.style.textAlign = 'right';
labelCell.style.verticalAlign = 'middle';
// Change the onsubmit handler
var form = document.getElementById( 'upload' ) || document.getElementById( 'mw-upload-form' );
if ( form && ip && ip.insertRow ) {
var newRow = ip.insertRow( -1 );
newRow.appendChild( labelCell );
newRow.appendChild( lineCell );
form.onsubmit = ( function ( oldSubmit ) {
return function () {
var do_submit = true;
if ( oldSubmit ) {
if ( typeof oldSubmit === 'string' ) {
// eslint-disable-next-line no-eval
do_submit = eval( oldSubmit );
} else if ( oldSubmit instanceof Function ) {
do_submit = oldSubmit.apply( form, arguments );
}
}
if ( !do_submit ) return false;
closeForm();
// Copy the categories
var eb = document.getElementById( 'wpUploadDescription' ) || document.getElementById( 'wpDesc' );
var addedOne = false;
for ( var i = 0; i < editors.length; i++ ) {
var t = editors[ i ].currentCategory;
if ( !t ) continue;
var key = editors[ i ].currentKey;
var new_cat = '[[' + HC.category_canonical + ':' + t + ( key ? '|' + key : '' ) + ']]';
// Only add if not already present
var cleanedText = eb.value
.replace( /<!--(\s|\S)*?-->/g, '' )
.replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, '' );
if ( !find_category( cleanedText, t, true ) ) {
eb.value += '\n' + new_cat;
addedOne = true;
}
}
if ( addedOne ) {
// Remove "subst:unc" added by Flinfo if it didn't find categories
eb.value = eb.value.replace( /\{\{subst:unc\}\}/g, '' );
}
return true;
};
}( form.onsubmit ) );
}
}

var cleanedText = null;

function isOnPage( span ) {
if ( span.firstChild.nodeType !== Node.ELEMENT_NODE ) return null;

var catTitle = title( span.firstChild.href );
if ( !catTitle ) return null;

catTitle = catTitle.substr( catTitle.indexOf( ':' ) + 1 ).replace( /_/g, ' ' );
if ( HC.blacklist && HC.blacklist.test( catTitle ) ) return null;

var result = {
title: catTitle,
match: [ '', '', '' ]
};
if ( pageText === null ) return result;

if ( cleanedText === null ) {
cleanedText = pageText
.replace( /<!--(\s|\S)*?-->/g, '' )
.replace( /<nowiki>(\s|\S)*?<\/nowiki>/g, '' );
}
result.match = find_category( cleanedText, catTitle, true );
return result;
}

var initialized = false;
var setupTimeout = null;

function findByClass( scope, tag, className ) {
var result = $( scope ).find( tag + '.' + className );
return ( result && result.length ) ? result[ 0 ] : null;
}

function errorAMC() {
alert( 'An error occurred. Unable to setup HotCat' );
}

function enableAMC() {
var api = new mw.Api();
return api.saveOption( 'mf_amc_optin', '1' ).then( function ( r ) {
if ( !r || r.options !== 'success' ) {
errorAMC();
return;
}
if ( window.confirm( 'Please reload your page to use hotcat.' ) ) {
location.reload();
}
}, function () {
errorAMC();
} );
}

function showWarning( text ) {
var warning = document.createElement( 'div' );
warning.setAttribute( 'style', 'padding: 20px; background: orange; color: #333; font-weight: bold; margin-top: 20px;' );
warning.textContent = text;
var btn = document.createElement( 'button' );
btn.classList.add( 'mw-ui-button', 'cdx-button' );
btn.style.display = 'block';
btn.textContent = 'Enable HotCat and AMC mode on this page';
btn.addEventListener( 'click', function () {
enableAMC();
} );
warning.appendChild( btn );
document.getElementById( 'mw-content-text' ).appendChild( warning );
}

function setup( additionalWork ) {
if ( initialized ) return;
initialized = true;
if ( setupTimeout ) {
window.clearTimeout( setupTimeout );
setupTimeout = null;
}
// Find the category bar, or create an empty one if there isn't one. Then add -/+- links after
// each category, and add the + link.
catLine =
// Special:Upload
catLine ||
document.getElementById( 'mw-normal-catlinks' );
var hiddenCats = document.getElementById( 'mw-hidden-catlinks' );
if ( !catLine ) {

// Workaround for T24660
if ( mw.config.get('skin') === 'minerva' ) {
if ( document.body.classList.contains('mw-mf-amc-disabled') ) {
showWarning( 'HotCat requires AMC mode.' );
}
}
var footer = null;
if ( !hiddenCats ) {
footer = findByClass( document, 'div', 'printfooter' );
if ( !footer ) return; // Don't know where to insert the category line
}
catLine = make( 'div' );
catLine.id = 'mw-normal-catlinks';
catLine.style.textAlign = is_rtl ? 'right' : 'left';
// Add a label
var label = make( 'a' );
label.href = conf.wgArticlePath.replace( '$1', 'Special:Categories' );
label.title = HC.categories;
label.appendChild( make( HC.categories, true ) );
catLine.appendChild( label );
catLine.appendChild( make( ':', true ) );
// Insert the new category line
var container = ( hiddenCats ? hiddenCats.parentNode : document.getElementById( 'catlinks' ) );
if ( !container ) {
container = make( 'div' );
container.id = 'catlinks';
footer.parentNode.insertBefore( container, footer.nextSibling );
}
container.className = 'catlinks noprint';
container.style.display = '';
if ( !hiddenCats ) container.appendChild( catLine ); else container.insertBefore( catLine, hiddenCats );
} // end if catLine exists
if ( is_rtl ) catLine.dir = 'rtl';

// Create editors for all existing categories

function createEditors( line, is_hidden ) {
var i;
var cats = line.getElementsByTagName( 'li' );
if ( cats.length ) {
newDOM = true;
line = cats[ 0 ].parentNode;
} else {
cats = line.getElementsByTagName( 'span' );
}
// Copy cats, otherwise it'll also magically contain our added spans as it is a live collection!
var copyCats = new Array( cats.length );
for ( i = 0; i < cats.length; i++ ) copyCats[ i ] = cats[ i ];
for ( i = 0; i < copyCats.length; i++ ) {
var test = isOnPage( copyCats[ i ] );
if ( test !== null && test.match !== null && line ) {
// eslint-disable-next-line no-new
new CategoryEditor( line, copyCats[ i ], test.title, test.match[ 2 ], is_hidden );
}
}
return copyCats.length ? copyCats[ copyCats.length - 1 ] : null;
}

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

function createCommitForm() {
if ( commitForm ) return;
var format = '';
if ( HC.jsonContentModels.includes( conf.wgPageContentModel ) ) {
// Handle Data namespace JSON content
format = 'application/json';
}
else {
// For backwards-compatibility the default handler is wikitext
format = 'text/x-wiki';
}

var formContainer = make( 'div' );
formContainer.style.display = 'none';
document.body.appendChild( formContainer );
formContainer.innerHTML =
'<form id="hotcatCommitForm" method="post" enctype="multipart/form-data" action="' +
conf.wgScript + '?title=' + encodeURIComponent( conf.wgPageName ) + '&action=submit">' +
'<input type="hidden" name="wpTextbox1">' +
'<input type="hidden" name="model" value="' + conf.wgPageContentModel + '">' +
'<input type="hidden" name="format" value="' + format + '">' +
'<input type="hidden" name="wpSummary" value="">' +
'<input type="checkbox" name="wpMinoredit" value="1">' +
'<input type="checkbox" name="wpWatchthis" value="1">' +
'<input type="hidden" name="wpWatchlistExpiry" value="">' +
'<input type="hidden" name="wpAutoSummary" value="d41d8cd98f00b204e9800998ecf8427e">' +
'<input type="hidden" name="wpEdittime">' +
'<input type="hidden" name="wpStarttime">' +
'<input type="hidden" name="wpDiff" value="wpDiff">' +
'<input type="hidden" name="oldid" value="0">' +
'<input type="hidden" name="wpIgnoreBlankSummary" value="1">' +
'<input type="submit" name="hcCommit" value="hcCommit">' +
'<input type="hidden" name="wpEditToken">' +
'<input type="hidden" name="wpUltimateParam" value="1">' +
'<input type="hidden" name="wpChangeTags">' +
'<input type="hidden" value="ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ" name="wpUnicodeCheck">' +
'</form>';
commitForm = document.getElementById( 'hotcatCommitForm' );
}

function getPage() {
// We know we have an article here.
if ( !conf.wgArticleId ) {
// Doesn't exist yet. Disable on non-existing User pages -- might be a global user page.
if ( conf.wgNamespaceNumber === 2 ) return;
pageText = '';
pageTime = null;
setup( createCommitForm );
} else {
var url = conf.wgScriptPath + '/api.php?format=json&callback=HotCat.start&action=query&rawcontinue=&titles=' +
encodeURIComponent( conf.wgPageName ) +
'&prop=info%7Crevisions&rvprop=content%7Ctimestamp%7Cids&meta=siteinfo&rvslots=main&rvlimit=1&rvstartid=' +
conf.wgCurRevisionId;
var s = make( 'script' );
s.src = url;
HC.start = function ( json ) {
setPage( json );
setup( createCommitForm );
};
document.getElementsByTagName( 'head' )[ 0 ].appendChild( s );
setupTimeout = window.setTimeout( function () {
setup( createCommitForm );
}, 4000 ); // 4 sec, just in case getting the wikitext takes longer.
}
}

function setState( state ) {
var cats = state.split( '\n' );
if ( !cats.length ) return null;

if ( initialized && editors.length === 1 && editors[ 0 ].isAddCategory ) {
// Insert new spans and create new editors for them.
var newSpans = [];
var before = editors.length === 1 ? editors[ 0 ].span : null;
var i;
for ( i = 0; i < cats.length; i++ ) {
if ( !cats[ i ].length ) continue;
var cat = cats[ i ].split( '|' );
var 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 );

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 );

for ( i = 0; i < newSpans.length; i++ ) {
// eslint-disable-next-line no-new
new CategoryEditor( catLine, newSpans[ i ].element, newSpans[ i ].title, newSpans[ i ].key );
}
}
return null;
}

function getState() {
if ( HC.jsonContentModels.includes( conf.wgPageContentModel ) ) {
// Handle Data namespace JSON content
return getStateJson();
}
else {
// For backwards-compatibility the default handler is wikitext
return getStateWikitext();
}
}

function getStateJson() {
// Handle Data namespace JSON content
try {
var jsonData = JSON.parse( pageText );
if ( jsonData.mediawikiCategories ) {
return jsonData.mediawikiCategories.map( function( cat ) {
return cat.name + ( cat.sort ? '|' + cat.sort : '' );
} ).join( '\n' );
}
} catch ( e ) {
console.error( '[getStateJson]Error parsing JSON:', e );
}
return null;
}
function getStateWikitext() {
var result = null;
for ( var i = 0; i < editors.length; i++ ) {
var text = editors[ i ].currentCategory;
var key = editors[ i ].currentKey;
if ( text && text.length ) {
if ( key !== null ) text += '|' + key;
if ( result === null ) result = text; else result += '\n' + text;
}
}
return result;
}

function really_run() {
initialize();

if ( !HC.upload_disabled && conf.wgNamespaceNumber === -1 && conf.wgCanonicalSpecialPageName === 'Upload' && conf.wgUserName ) {
setup_upload();
setup( function () {
// Check for state restoration once the setup is done otherwise, but before signalling setup completion
if ( window.UploadForm && UploadForm.previous_hotcat_state ) UploadForm.previous_hotcat_state = setState( UploadForm.previous_hotcat_state );
} );
} else {
if ( !conf.wgIsArticle || conf.wgAction !== 'view' || param( 'diff' ) !== null || param( 'oldid' ) !== null || !can_edit() || HC.disable() ) return;
if (!HC.isSupportedContentModel()) {
console.info("Unknown contentModel: " + conf.wgPageContentModel + " in " + conf.wgPageName +". If you see this please report it to https://commons.wikimedia.org/wiki/MediaWiki_talk:Gadget-Cat-a-lot.js");
}
getPage();
}
}

function run() {
if ( HC.started ) return;
HC.started = true;
loadTrigger.register( really_run );
}

// Export legacy functions
window.hotcat_get_state = function () {
return getState();
};
window.hotcat_set_state = function ( state ) {
return setState( state );
};
window.hotcat_close_form = function () {
closeForm();
};
HC.runWhenReady = function ( callback ) {
// run user-registered code once HotCat is fully set up and ready.
mw.hook( 'hotcat.ready' ).add( callback );
};

// Make sure we don't get conflicts with AjaxCategories (core development that should one day
// replace HotCat).
mw.config.set( 'disableAJAXCategories', true );

// Run as soon as possible. This varies depending on MediaWiki version;
// window's 'load' event is always safe, but usually we can do better than that.

if ( conf.wgCanonicalSpecialPageName !== 'Upload' ) {
// Reload HotCat after (VE) edits (bug T103285)
mw.hook( 'postEdit' ).add( function () {
// Reset HotCat in case this is a soft reload (e.g. VisualEditor edit), unless the categories
// were not re-rendered and our interface is still there (e.g. DiscussionTools edit)
if ( document.querySelector( '#catlinks .hotcatlink' ) ) {
return;
}
catLine = null;
editors = [];
initialized = false;
HC.started = false;
run();
} );
}

// We can safely trigger just after user configuration is loaded.
// Use always() instead of then() to also start HotCat if the user module has problems.
$.when( mw.loader.using( 'user' ), $.ready ).always( run );
}( jQuery, mediaWiki ) );
// </nowiki>

2026年6月16日 (二) 18:59的最新版本

/**
 * SPDX-License-Identifier: CC-BY-SA-4.0
 * _addText: '{{Gadget Header|title=HotCat|license=CC-BY-SA-4.0}}'
 *
 * @base {@link https://commons.wikimedia.org/wiki/MediaWiki:Gadget-HotCat.js}
 * @base {@link https://commons.wikimedia.org/wiki/MediaWiki:Gadget-HotCat.js/zh-hans}
 * @base {@link https://zh.wikipedia.org/wiki/MediaWiki:Gadget-HotCat.js/local_defaults}
 * @source {@link https://git.qiuwen.net.cn/InterfaceAdmin/QiuwenGadgets/src/branch/master/src/HotCat}
 * @license CC-BY-SA-4.0 {@link https://www.qiuwenbaike.cn/wiki/H:CC-BY-SA-4.0}
 */

/**
 * SPDX-License-Identifier: CC-BY-SA-4.0
 * _addText: '{{Gadget Header|title=CheckCategories HotCat Extension|license=CC-BY-SA-4.0}}'
 *
 * @base {@link https://commons.wikimedia.org/wiki/MediaWiki:Gadget-Hotcatcheck.js}
 * @source {@link https://git.qiuwen.net.cn/InterfaceAdmin/QiuwenGadgets/src/branch/master/src/HotCat/HotCat-check.js}
 * @license CC-BY-SA-4.0 {@link https://www.qiuwenbaike.cn/wiki/H:CC-BY-SA-4.0}
 */

/**
 * SPDX-License-Identifier: CC-BY-SA-3.0 + GFDL-1.3-no-invariants-or-later
 *
 * P no
 *
 * @base {@link https://www.qiuwenbaike.cn/wiki/File:P_no.svg}
 * @source {@link https://git.qiuwen.net.cn/InterfaceAdmin/QiuwenGadgets/src/branch/master/src/HotCat/images}
 * @license CC-BY-SA-3.0 + GFDL-1.3-no-invariants-or-later {@link https://www.qiuwenbaike.cn/wiki/H:CC-BY-SA-3.0}
 */

/**
 * SPDX-License-Identifier: CC-BY-SA-3.0 + GFDL-1.3-no-invariants-or-later
 *
 * P yes
 *
 * @base {@link https://www.qiuwenbaike.cn/wiki/File:P_yes.svg}
 * @source {@link https://git.qiuwen.net.cn/InterfaceAdmin/QiuwenGadgets/src/branch/master/src/HotCat/images}
 * @license CC-BY-SA-3.0 + GFDL-1.3-no-invariants-or-later {@link https://www.qiuwenbaike.cn/wiki/H:CC-BY-SA-3.0}
 */
/**
 * +------------------------------------------------------------+
 * |            === WARNING: GLOBAL GADGET FILE ===             |
 * +------------------------------------------------------------+
 * |       All changes should be made in the repository,        |
 * |                otherwise they will be lost.                |
 * +------------------------------------------------------------+
 * |        Changes to this page may affect many users.         |
 * | Please discuss changes by opening an issue before editing. |
 * +------------------------------------------------------------+
 */
/* <nowiki> */

(() => {

"use strict";

// dist/HotCat/HotCat.js
//! src/HotCat/modules/check.module.less
var _templateObject;
var _templateObject2;
var _templateObject3;
var _templateObject4;
var _templateObject5;
var _templateObject6;
var _templateObject7;
var _templateObject8;
var _templateObject9;
function _createForOfIteratorHelper(r, e) {
  var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
  if (!t) {
    if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {
      t && (r = t);
      var n = 0, F = function() {
      };
      return { s: F, n: function() {
        return n >= r.length ? { done: true } : { done: false, value: r[n++] };
      }, e: function(r2) {
        throw r2;
      }, f: F };
    }
    throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  }
  var o, a = true, u = false;
  return { s: function() {
    t = t.call(r);
  }, n: function() {
    var r2 = t.next();
    return a = r2.done, r2;
  }, e: function(r2) {
    u = true, o = r2;
  }, f: function() {
    try {
      a || null == t.return || t.return();
    } finally {
      if (u) throw o;
    }
  } };
}
function _unsupportedIterableToArray(r, a) {
  if (r) {
    if ("string" == typeof r) return _arrayLikeToArray(r, a);
    var t = {}.toString.call(r).slice(8, -1);
    return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
  }
}
function _arrayLikeToArray(r, a) {
  (null == a || a > r.length) && (a = r.length);
  for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
  return n;
}
function _taggedTemplateLiteral(e, t) {
  return t || (t = e.slice(0)), Object.freeze(Object.defineProperties(e, { raw: { value: Object.freeze(t) } }));
}
var catcheckInlineIcon = "check-module__catcheckInlineIcon_mJ5NDq__4100";
//! src/HotCat/modules/api.ts
var import_ext_gadget = require("ext.gadget.Util");
var mwApi = (userAgent) => {
  return (0, import_ext_gadget.initMwApi)(userAgent);
};
//! src/HotCat/modules/check.ts
(function hotCatCheck() {
  if (mw.config.get("wgNamespaceNumber") !== 6 || window.HotCatAutoRemoveCheckCatOptOut || !document.querySelector(".checkcategories")) {
    return;
  }
  const api = mwApi("hotCatCheck/2.0");
  const checkCategoriesRegExp = /{{[Cc]heck[ _]categories[^{}]*}}/g;
  const selfName = "([[MediaWiki:Gadget-HotCat-check.js|Script]]): ";
  const storageItemName = "checkCat";
  const storageItem = mw.storage.get(storageItemName);
  const createjIcon = (iconClass) => {
    return $("<span>").attr("class", "ui-icon ".concat(iconClass, " ").concat(catcheckInlineIcon)).text(" ");
  };
  const createNotifyArea = (textNode, icon, state) => {
    return $("<div>").addClass("ui-widget").append($("<div>").attr("class", "".concat(state, " ui-corner-all")).css({
      "margin-top": "20px",
      padding: "0.7em"
    }).append($("<p>").append(createjIcon(icon).css("marginRight", "0.3em"), textNode)));
  };
  $("body").one("submit.checkCatListener", "#hotcatCommitForm", function(e) {
    var _self$wpTextbox1$valu;
    if (storageItem === "disabled") {
      return true;
    }
    const self = this;
    const newVal = (_self$wpTextbox1$valu = self.wpTextbox1.value) === null || _self$wpTextbox1$valu === void 0 ? void 0 : _self$wpTextbox1$valu.replace(checkCategoriesRegExp, "");
    const dlgButtons = {};
    let $dialogCheckStorage;
    let $permaSaveHint;
    let $textHintNode;
    let $dialog;
    const doRemove = () => {
      self.wpSummary.value = "Removing [[Template:Check categories|{{Check categories}}]] ".concat(self.wpSummary.value);
      self.wpTextbox1.value = newVal;
    };
    const writeStorage = (val) => {
      mw.storage.set(storageItemName, val, 60 * 60 * 24 * 7);
    };
    dlgButtons["Yes, Remove"] = function() {
      var _$dialogCheckStorage$;
      doRemove();
      if ((_$dialogCheckStorage$ = $dialogCheckStorage[0]) !== null && _$dialogCheckStorage$ !== void 0 && _$dialogCheckStorage$.checked) {
        writeStorage("auto");
      }
      $(this).dialog("close");
    };
    dlgButtons["No, keep it"] = function() {
      var _$dialogCheckStorage$2;
      if ((_$dialogCheckStorage$2 = $dialogCheckStorage[0]) !== null && _$dialogCheckStorage$2 !== void 0 && _$dialogCheckStorage$2.checked) {
        writeStorage("disabled");
      }
      $(this).dialog("close");
    };
    const _addToJS = function(_e) {
      _e.preventDefault();
      if ($permaSaveHint.hasClass("ui-state-disabled")) {
        return;
      }
      const $el = $(this);
      $el.off("click").text("Please wait.");
      $permaSaveHint.addClass("ui-state-disabled");
      const params = {
        action: "edit",
        format: "json",
        title: "User:".concat(mw.config.get("wgUserName"), "/common.js"),
        summary: "".concat(selfName, "Saving HotCat configuration."),
        appendtext: $el.data("addText")
      };
      const editDone = (editStat) => {
        if (!editStat) {
          return;
        }
        if (editStat.error) {
          void mw.notify("Unable to save to your common.js using the API\n".concat(editStat.error.code, "\n").concat(editStat.error.info), {
            tag: "hotCatCheck",
            type: "error"
          });
          $el.text("Edit-Error!");
        } else {
          $el.text("Done.");
          $permaSaveHint.fadeOut();
        }
      };
      void api.postWithToken("csrf", params).then(editDone);
    };
    const prompt = () => {
      $dialogCheckStorage = $("<input>").attr({
        type: "checkbox",
        id: "hotCatAutoRemoveCheckCatStorage"
      }).on("change", function() {
        if (this.checked) {
          $permaSaveHint.fadeIn();
        } else {
          $permaSaveHint.fadeOut();
        }
      });
      $textHintNode = $("<ul>");
      $("<li>").append($("<a>").attr("href", "#").text("Disable this feature.").data("addText", "\nwindow.HotCatAutoRemoveCheckCatOptOut = true;").on("click", _addToJS)).appendTo($textHintNode);
      $("<li>").append($("<a>").attr("href", "#").text("Remove {{check categories}} when editing using HotCat without prompting.").data("addText", "\nwindow.HotCatAutoRemoveCheckCat = true;").on("click", _addToJS)).appendTo($textHintNode);
      $permaSaveHint = createNotifyArea($("<span>").text("Save these setting in your common.js: ").append($textHintNode), "ui-icon-info", "ui-state-highlight");
      $dialog = $("<div>").append($("<span>").css({
        "font-size": "2em",
        "line-height": "1.8em"
      }).append($("<span>").text(" {{check categories}} ").css({
        "background-color": "#F8CCB0",
        "text-decoration": "line-through !important",
        display: "inline-block"
      }), $("<span>").text(" ?"))).append("<br>", $dialogCheckStorage, $("<label>").attr("for", "hotCatAutoRemoveCheckCatStorage").text("Don't ask again"), "<br>").append(mw.user.isAnon() ? "" : $permaSaveHint.hide());
      $dialog.dialog({
        modal: true,
        closeOnEscape: true,
        title: "{{check categories}} (−)?",
        width: 450,
        buttons: dlgButtons,
        close: () => {
          const $body = $("body");
          $body.find("#hotcatCommitForm").trigger("submit");
        },
        open() {
          const $buttons = $(this).parent().find(".ui-dialog-buttonpane button");
          $buttons.eq(0).button({
            icons: {
              primary: "ui-icon-circle-check"
            }
          });
          $buttons.eq(1).button({
            icons: {
              primary: "ui-icon-cancel"
            }
          });
        }
      });
    };
    if (newVal !== self.wpTextbox1.value) {
      if (window.HotCatAutoRemoveCheckCat || storageItem === "auto") {
        doRemove();
        return true;
      }
      e.preventDefault();
      prompt();
    }
    return true;
  });
  const $okLink = $("<a>").attr({
    href: "#",
    title: "Categories are OK! Immediately remove the template."
  }).append("<s>").text("{{Check categories}}");
  $okLink.on("click", function(e) {
    e.preventDefault();
    const $el = $(this);
    $el.off("click");
    const doEdit = (result) => {
      if (!result) {
        return;
      }
      $el.text("Doing.");
      const text = result.replace(checkCategoriesRegExp, "");
      if (text === result) {
        $el.text("Template not found!");
        return;
      }
      const params = {
        text,
        action: "edit",
        format: "json",
        title: mw.config.get("wgPageName"),
        summary: "".concat(selfName, "Categories are checked and OK. You can help [[Category:Media needing category review|reviewing]]!"),
        nocreate: true
      };
      const editDone = (editStat) => {
        if (!editStat) {
          return;
        }
        if (editStat.error) {
          void mw.notify('Unable to remove "Check categories" with the API\n'.concat(editStat.error.code, "\n").concat(editStat.error.info), {
            tag: "hotCatCheck",
            type: "error"
          });
          $el.text("Edit-Error!");
        } else {
          $el.text("Edit Done.");
        }
        const $body = $("body");
        $body.find(".checkcategories").fadeOut();
      };
      $el.text("Doing..");
      void api.postWithToken("csrf", params).then(editDone);
    };
    $el.text("Doing");
    void $.ajax({
      url: mw.config.get("wgScript"),
      data: {
        action: "raw",
        title: mw.config.get("wgPageName").replace(/ /g, "_")
      },
      dataType: "text",
      error: () => {
        $el.text("Error!");
      },
      success: doEdit,
      type: "GET",
      cache: false
    });
  });
  $(function loadHotCatCheck() {
    const $body = $("body");
    $body.find("#catlinks").find("ul:first").append($("<li>").append($okLink));
  });
})();
//! src/HotCat/HotCat.js
var import_ext_gadget2 = require("ext.gadget.Util");
//! src/HotCat/modules/getMessage.ts
var getMessage = (key, ...args) => {
  key = "hotcat-".concat(key);
  return mw.message(key, ...args).plain();
};
//! src/HotCat/modules/messages.ts
var hotCatMessages = () => {
  const {
    wgUserLanguage
  } = mw.config.get();
  if (["zh-hant", "zh-hk", "zh-mo", "zh-tw"].includes(wgUserLanguage)) {
    mw.messages.set({
      "hotcat-messages-cat_removed": "已移除[[Category:$1]]",
      "hotcat-messages-template_removed": "已移除{{[[Category:$1]]}}",
      "hotcat-messages-cat_added": "已添加[[Category:$1]]",
      // $2 is the new key
      "hotcat-messages-cat_keychange": '已設置[[Category:$1]]的新排序字:"$2"',
      "hotcat-messages-cat_notFound": "分類“$1”沒有找到",
      "hotcat-messages-cat_exists": "分類“$1”已經存在,沒有添加。",
      "hotcat-messages-cat_resolved": "(重定向[[Category:$1]]已處理)",
      "hotcat-messages-uncat_removed": "已移除{{uncategorized}}",
      // Some text to prefix to the edit summary.
      "hotcat-messages-prefix": "使用[[Help:HotCat|HotCat]]",
      // 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.
      "hotcat-messages-using": "",
      "hotcat-messages-multi_change": "$1個分類",
      // Any category in this category is deemed a disambiguation category; i.e., a category that should not contain
      // any items, but that contains links to other categories where stuff should be categorized. If you don't have
      // that concept on your wiki, set it to blank string. Use blanks, not underscores.
      "hotcat-disambig_category": "",
      // Any category in this category is deemed a (soft) redirect to some other category defined by a link
      // to another non-blacklisted category. If your wiki doesn't have soft category redirects, set this to null.
      // If a soft-redirected category contains more than one link to another non-blacklisted category, it's considered
      // a disambiguation category instead.
      "hotcat-redir_category": "已重定向的分类",
      "hotcat-messages-separator": "; ",
      // $1 is replaced by a number. If your language has several plural forms (c.f. [[:enwiki:Dual (grammatical form)]]),
      // 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
      // entry in the array.
      // Defaults to '[[' + category_canonical + ':$1]]'. Can be overridden if in the short edit summaries
      // not the standard category name should be used but, say, a shorter namespace alias. $1 is replaced
      // by a category name.
      "hotcat-messages-short_catchange": "$1",
      // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
      // see localization hook below.
      "hotcat-messages-commit": "儲存",
      // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
      // see localization hook below.
      "hotcat-messages-ok": "確定",
      // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
      // see localization hook below.
      "hotcat-messages-cancel": "取消",
      // Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
      // see localization hook below.
      "hotcat-messages-multi_error": "無法從伺服器取得頁面文字。因此,您的分類變更無法儲存。我們為此不便表示抱歉。",
      // Plural of category_canonical.
      "hotcat-categories": "分類",
      // Names for the search engines
      "hotcat-engine_names-searchindex": "搜尋索引",
      "hotcat-engine_names-pagelist": "頁面列表",
      "hotcat-engine_names-combined": "合併搜尋",
      "hotcat-engine_names-subcat": "子分類",
      "hotcat-engine_names-parentcat": "上層分類",
      // The tooltips for the above links
      "hotcat-tooltips-change": "修改",
      "hotcat-tooltips-remove": "移除",
      "hotcat-tooltips-add": "增加一個新分類",
      "hotcat-tooltips-restore": "復原變更",
      "hotcat-tooltips-undo": "復原變更",
      "hotcat-tooltips-down": "打開以修改並顯示子分類",
      "hotcat-tooltips-up": "打開以修改並顯示上層分類",
      // Tooltip for the "enter multi-mode" link
      "hotcat-multi_tooltip": "修改多個分類"
    });
  } else {
    mw.messages.set({
      "hotcat-messages-cat_removed": "已移除[[Category:$1]]",
      "hotcat-messages-template_removed": "已移除{{[[Category:$1]]}}",
      "hotcat-messages-cat_added": "已添加[[Category:$1]]",
      // $2 is the new key
      "hotcat-messages-cat_keychange": '已设置[[Category:$1]]的新排序字:"$2"',
      "hotcat-messages-cat_notFound": "分类“$1”没有找到",
      "hotcat-messages-cat_exists": "分类“$1”已经存在,没有添加。",
      "hotcat-messages-cat_resolved": "(重定向[[Category:$1]]已处理)",
      "hotcat-messages-uncat_removed": "已移除{{uncategorized}}",
      // Some text to prefix to the edit summary.
      "hotcat-messages-prefix": "使用[[Help:HotCat|HotCat]]",
      // 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.
      "hotcat-messages-using": "",
      "hotcat-messages-multi_change": "$1个分类",
      // Any category in this category is deemed a disambiguation category; i.e., a category that should not contain
      // any items, but that contains links to other categories where stuff should be categorized. If you don't have
      // that concept on your wiki, set it to blank string. Use blanks, not underscores.
      "hotcat-disambig_category": "",
      // Any category in this category is deemed a (soft) redirect to some other category defined by a link
      // to another non-blacklisted category. If your wiki doesn't have soft category redirects, set this to null.
      // If a soft-redirected category contains more than one link to another non-blacklisted category, it's considered
      // a disambiguation category instead.			'hotcat-redir_category': '已重定向的分类',
      "hotcat-messages-separator": "; ",
      // $1 is replaced by a number. If your language has several plural forms (c.f. [[:enwiki:Dual (grammatical form)]]),
      // 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
      // entry in the array.
      // Defaults to '[[' + category_canonical + ':$1]]'. Can be overridden if in the short edit summaries
      // not the standard category name should be used but, say, a shorter namespace alias. $1 is replaced
      // by a category name.
      "hotcat-messages-short_catchange": "$1",
      // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
      // see localization hook below.
      "hotcat-messages-commit": "保存",
      // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
      // see localization hook below.
      "hotcat-messages-ok": "确定",
      // Button text. Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
      // see localization hook below.
      "hotcat-messages-cancel": "取消",
      // Localize to wgContentLanguage here; localize to wgUserLanguage in a subpage,
      // see localization hook below.
      "hotcat-messages-multi_error": "无法从服务器取得页面文字。因此,您的分类更改无法保存。我们为此不便表示抱歉。",
      // Plural of category_canonical.
      "hotcat-categories": "分类",
      // Names for the search engines
      "hotcat-engine_names-searchindex": "搜索索引",
      "hotcat-engine_names-pagelist": "页面列表",
      "hotcat-engine_names-combined": "合并搜索",
      "hotcat-engine_names-subcat": "子分类",
      "hotcat-engine_names-parentcat": "上层分类",
      // The tooltips for the above links
      "hotcat-tooltips-change": "修改",
      "hotcat-tooltips-remove": "移除",
      "hotcat-tooltips-add": "增加一个新分类",
      "hotcat-tooltips-restore": "撤销更改",
      "hotcat-tooltips-undo": "撤销更改",
      "hotcat-tooltips-down": "打开以修改并显示子分类",
      "hotcat-tooltips-up": "打开以修改并显示上层分类",
      // Tooltip for the "enter multi-mode" link
      "hotcat-multi_tooltip": "修改多个分类"
    });
  }
};
//! src/HotCat/images/P_no.png
var P_no_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAABmJLR0QA/wD/AP+gvaeTAAADOUlEQVQ4jY2SXUzbZRjFf/9/y1fsKEGQQaDbmEKZzrmL6Qg6RzQQso9ELcm6i043zURJTPy40IQF9WqJmZpdaEIWIyH76EzwZsZEs6spc2TSJgjFhQEZUrqh2RAK9P++7+ONLbVs6pOcq+e853lzzrH4n3PtmhTny0qZyRMzOVk43dxsqbvxrH8XcRot0YcEaw9QnbVaBC4JcrKurvDCfwqOjCw/ZIucxKI1d6e1Jh6/QWVlNS6XG4GzKafw5W3brEUAO/dBNLoQPHGia/hYd2frTHwGpSWDocggzc/U8WxLA/1fn8ZRBq3lgMu19IWIWGsEoz8vhMTQt6Vhe374/Ck6OwPM/zmPUoapqet0vPYcN2/OEHjhMMXFpcTjv6GUQTmmPRJJ7v+H4ODgnccdI6eUFnvX0/tob3+FkdEhurpeZSWlePe9I9y+/Tu7d++j6ck2Nmz0U3p/Zeb3xuijGQ/DYXH5fPPDgD99wBhNR8ceotEB/P7HiMUi1NRsprPzQ2pqaqmtbch1a27nTm+5DVBdfed5rcWvtZCGiM3x42eoqKgmFovgdrsJBI7i8ZTg89WTzf0b6wDcAI6jg3cLvKiomKqqjSQS04hAMrnIpk0Po7UAkkv/I+Oh0jRlp5nGwMD3DA1dwuPxImLo7f2I8euja3hKC46WH1cFlSlVypANx1H09HwAQEtrkJbWIEtLi7zf/RK3biXI5SutzmQEtTbzuZ5cvNjP+Pgw6ys3sPWRJg4efIfGxjbm5uJ88vGbOI5a5SsZvvzDA/0ZQUeZwdyL5859CsCOHS0UFHrweit46+3P8fnquXLlO8ZiEZQyaGUcY9SL3d2WWf2hWJ9l+zExEWNqcpSiIg8NW56grNyHNmC78uk6dpqSknJmE9OkHGWUMof37q26mk7GAhAR6/xX8TCWBABSqWVm4xNMTP5CibeMrY8+hW27MnGurCQZG7uarHtw+5FQqP5sdtSZrvT2zt7nLnB6wAqmi+04KWzbRV5efk5DrG8dWX7j0IHNv+Z2Z035+vputAm8jmV2gbUuizoF8o3Y1pehYM1Pue/uKZiecFhcC3qmCqPy3LogEQqtX7wXN3v+ApHw44zjlQ7KAAAAAElFTkSuQmCC";
//! src/HotCat/images/P_yes.png
var P_yes_default = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAABmJLR0QA/wD/AP+gvaeTAAADAElEQVQ4jY2S32vbZRSHnzffdMvWJVmWtJamnUXWbnZgHKuTMSnOm1GcCJ3iil0qcyBDUIbzYl5N8K43SileFDc60h/0RlR0/gE6JkVpIQy0DLo4pavbsEvTtP2+7zlezKRZ2joPnJv3fM7DOef9GP5nzMxoZIuuJKRGZXY2dPvoUWM30pn/hviHjbo+xbwMNFWUCsAPig60tYW+fSzwxo3l1oDqAIZjj5tcYXzVD51JpUwBIFgtyGaXToqTIYEdGwHy+QUyI4P8cfsWZ89eIJlsOel5RU9V3zDG6CPA6V8W09aXy0BgI9jdu3OcfvsYudxNUqnnmZ+fIxpNEAptf31qamkE+KrcODm5cMgX/cI6DVinVOfKqs8H53vJ5W6yZ89+envfxzoIeFuwThFx75RXnphQz7kHwyDrTlCKwcGPmZ6+TjS6i1OnzrFzZ4K9ew8gYhARgOfKwKamhW7n2LcZbGrqGpnMZwB0d58hGo3T2vosqgbntCQLl4G+73o2c5Dvr9Lffw5VpaPjRdraDtDS0o4xQayVSun9MtA6joCuVe7PE4vVYYxhdHSA2dlfqa2N0NXVSzzRSDiSwK5NBoDCNfj3N62VXdYKvu/45uth+tKHGBn5lHv3/mJs7OGqnZ2vsm17hGSyFWtlfTo7VgY6Jw+cU5aXV7h6NcPSUp7Llz6hL91BsbhILFbPwYMv0dDwFMYEcU4fTavZ6z/Wf1kG+lYmrRUCgRoufHSJaDQOQLG4CMCRF14htC1MPN64bjJnxRexb128aGRtQjWfl/wWiT7B+Q+HCAQeWjQUquXp9sPU17fgxFT7U6yV08ePN/5cuqUHMD7a/1s2m98vqu0iSiLRhOcFsb7PM6lOdjfvY/eT7agaRLSUeXWSPnEiOV75OWWvXLkyVxvc6g+B6Sm9zd/JUSj8zY5wjLq65sq279WT9958rXmm2mbrzJfJ/N6l8C5GOsGEK6S3QL/TgBlO9zT/VN23KbAUExPqLbo/GxFbE3Rb76TTDYXNtJXxD+yujTqqNmcnAAAAAElFTkSuQmCC";
//! src/HotCat/HotCat.js
hotCatMessages();
(function hotCat() {
  const conf = mw.config.values;
  if (window.HotCat && !window.HotCat.nodeName || conf.wgAction === "edit") {
    return;
  }
  const api = mwApi("HotCat/3.0");
  window.HotCat = {
    // The little modification links displayed after category names. U+2212 is a minus sign; U+2193 and U+2191 are
    // downward and upward pointing arrows. Do not use ↓ and ↑ in the code!
    links: {
      change: "(±)",
      remove: "(−)",
      add: "(+)",
      restore: "(×)",
      undo: "(×)",
      down: "(↓)",
      up: "(↑)"
    },
    changeTag: "HotCat",
    // The HTML content of the "enter multi-mode" link at the front.
    addmulti: "<span>+<sup>+</sup></span>",
    // Return true to disable HotCat.
    disable: () => {
      const ns = conf.wgNamespaceNumber;
      const nsIds = conf.wgNamespaceIds;
      return ns < 0 || // Special pages; Special:Upload is handled differently
      ns === 10 || // Templates
      ns === 828 || // Module (Lua)
      ns === 8 || // MediaWiki
      ns === 6 && !conf.wgArticleId || // Non-existing file pages
      ns === 2 && /\.(js|css)$/.test(conf.wgTitle) || // User scripts
      nsIds && (ns === nsIds.creator || ns === nsIds.timedtext || ns === nsIds.institution);
    },
    // A regexp matching a templates used to mark uncategorized pages, if your wiki does have that.
    // If not, set it to null.
    uncat_regexp: /{{\s*[Uu]ncategorized\s*[^}]*}}\s*(<!--.*?-->\s*)?/g,
    // The images used for the little indication icon. Should not need changing.
    existsYes: P_yes_default,
    existsNo: P_no_default,
    // a list of categories which can be removed by removing a template
    // key: the category without namespace
    // value: A regexp matching the template name, again without namespace
    // If you don't have this at your wiki, or don't want this, set it to an empty object {}.
    template_categories: {},
    // Override the decision of whether HotCat should help users by automatically
    // capitalising the title in the user input text if the wiki has case-sensitive page names.
    // Basically, this will make an API query to check the MediaWiki configuration and HotCat then sets
    // this to true for most wikis, and to false on Wiktionary.
    //
    // You can set this directly if there is a problem with it. For example, Georgian Wikipedia (kawiki),
    // is known to have different capitalisation logic between MediaWiki PHP and JavaScript. As such, automatic
    // case changes in JavaScript by HotCat would be wrong.
    capitalizePageNames: null,
    // If upload_disabled is true, HotCat will not be used on the Upload form.
    upload_disabled: false,
    // Single regular expression matching blacklisted categories that cannot be changed or
    // 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.
    blacklist: null,
    // Stuff changeable by users:
    // Background for changed categories in multi-edit mode. Default is a very light salmon pink.
    bg_changed: "#FCA",
    // If true, HotCat will never automatically submit changes. HotCat will only open an edit page with
    // the changes; users must always save explicitly.
    no_autocommit: false,
    // If true, the "category deletion" link "(-)" will never save automatically but always show an
    // edit page where the user has to save the edit manually. Is false by default because that's the
    // traditional behavior. This setting overrides no_autocommit for "(-)" links.
    del_needs_diff: false,
    // Time, in milliseconds, that HotCat waits after a keystroke before making a request to the
    // server to get suggestions.
    suggest_delay: 1e3,
    // Default width, in characters, of the text input field.
    editbox_width: 40,
    // One of the engine_names above, to be used as the default suggestion engine.
    suggestions: "combined",
    // If true, always use the default engine, and never display a selector.
    fixed_search: false,
    // If false, do not display the "up" and "down" links
    use_up_down: true,
    // Default list size
    listSize: 10,
    // If true, single category changes are marked as minor edits. If false, they're not.
    single_minor: true,
    // 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"
    // options in his or her preferences set.
    dont_add_to_watchlist: false,
    shortcuts: null,
    addShortcuts: (map) => {
      var _window$HotCat;
      if (!map) {
        return;
      }
      (_window$HotCat = window.HotCat).shortcuts || (_window$HotCat.shortcuts = {});
      for (let k in map) {
        if (!Object.hasOwn(map, k) || typeof k !== "string") {
          continue;
        }
        let v = map[k];
        if (typeof v !== "string") {
          continue;
        }
        k = k.trim();
        v = v.trim();
        if (k.length === 0 || v.length === 0) {
          continue;
        }
        window.HotCat.shortcuts[k] = v;
      }
    }
  };
  const HC = window.HotCat;
  const ua = navigator.userAgent.toLowerCase();
  const is_webkit = /applewebkit\/\d+/.test(ua) && !ua.includes("spoofer");
  let cat_prefix = null;
  let noSuggestions = false;
  const wikiTextBlank = String.raw(_templateObject || (_templateObject = _taggedTemplateLiteral(["[	 _  ᠎ - \u2028\u2029   ]+"], ["[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000]+"])));
  const wikiTextBlankRE = new RegExp(wikiTextBlank, "g");
  const wikiTextBlankOrBidi = String.raw(_templateObject2 || (_templateObject2 = _taggedTemplateLiteral(["[	 _  ᠎ -​‎‏\u2028-   ]*"], ["[\\t _\\xA0\\u1680\\u180E\\u2000-\\u200B\\u200E\\u200F\\u2028-\\u202F\\u205F\\u3000]*"])));
  const formattedNamespaces = conf.wgFormattedNamespaces;
  const namespaceIds = conf.wgNamespaceIds;
  const autoLocalize = (namespaceNumber, fallback) => {
    const createRegexpStr = (name) => {
      if (!name || name.length === 0) {
        return;
      }
      let regex_name = "";
      for (let i = 0; i < name.length; i++) {
        const initial = name.charAt(i);
        const ll = initial.toLowerCase();
        const ul = initial.toUpperCase();
        regex_name += ll === ul ? initial : "[".concat(ll).concat(ul, "]");
      }
      return regex_name.replace(/([$()*+.?\\^])/g, String.raw(_templateObject3 || (_templateObject3 = _taggedTemplateLiteral(["$1"], ["\\$1"])))).replace(wikiTextBlankRE, wikiTextBlank);
    };
    fallback = fallback.toLowerCase();
    const canonical = formattedNamespaces[String(namespaceNumber)].toLowerCase();
    let regexp = createRegexpStr(canonical);
    if (fallback && canonical !== fallback) {
      regexp += "|".concat(createRegexpStr(fallback));
    }
    if (namespaceIds) {
      for (const cat_name in namespaceIds) {
        if (typeof cat_name === "string" && cat_name.toLowerCase() !== canonical && cat_name.toLowerCase() !== fallback && namespaceIds[cat_name] === namespaceNumber) {
          regexp += "|".concat(createRegexpStr(cat_name));
        }
      }
    }
    return regexp;
  };
  HC.category_canonical = formattedNamespaces["14"];
  HC.category_regexp = autoLocalize(14, "category");
  if (formattedNamespaces["10"]) {
    HC.template_regexp = autoLocalize(10, "template");
  }
  const make = (arg, literal) => {
    if (!arg) {
      return null;
    }
    return literal ? document.createTextNode(arg) : document.createElement(arg);
  };
  const param = (name, uri) => {
    uri || (uri = document.location.href);
    const re = new RegExp("[&?]".concat(name, "=([^&#]*)"));
    const m = re.exec(uri);
    if (m && m.length > 1) {
      return decodeURIComponent(m[1]);
    }
    return null;
  };
  const title = (href) => {
    if (!href) {
      return null;
    }
    const script = "".concat(conf.wgScript, "?");
    if (href.indexOf(script) === 0 || href.indexOf(conf.wgServer + script) === 0 || conf.wgServer.slice(0, 2) === "//" && href.indexOf(document.location.protocol + conf.wgServer + script) === 0) {
      return param("title", href);
    }
    let prefix = conf.wgArticlePath.replace("$1", "");
    if (href.indexOf(prefix)) {
      prefix = conf.wgServer + prefix;
    }
    if (href.indexOf(prefix) && prefix.slice(0, 2) === "//") {
      prefix = document.location.protocol + prefix;
    }
    if (href.indexOf(prefix) === 0) {
      return decodeURIComponent(href.slice(prefix.length));
    }
    return null;
  };
  const hasClass = ({
    className
  }, name) => {
    return " ".concat(className, " ").includes(" ".concat(name, " "));
  };
  const capitalize = (str) => {
    if (!str || str.length === 0) {
      return str;
    }
    return str.slice(0, 1).toUpperCase() + str.slice(1);
  };
  const wikiPagePath = (pageName) => {
    return conf.wgArticlePath.replace("$1", encodeURIComponent(pageName).replace(/%3A/g, ":").replace(/%2F/g, "/"));
  };
  const escapeRE = (str) => {
    return str.replace(/([$()*+.?[\\\]^])/g, String.raw(_templateObject4 || (_templateObject4 = _taggedTemplateLiteral(["$1"], ["\\$1"]))));
  };
  const substituteFactory = (options) => {
    options || (options = {});
    const lead = options.indicator || "$";
    const indicator = escapeRE(lead);
    const lbrace = escapeRE(options.lbrace || "{");
    const rbrace = escapeRE(options.rbrace || "}");
    const re = new RegExp(
      // $$
      "(?:".concat(indicator, "(").concat(indicator, "))|(?:").concat(indicator, "(\\d+))|(?:").concat(indicator, "(?:").concat(lbrace, "([^").concat(lbrace).concat(rbrace, "]+)").concat(rbrace, "))|(?:").concat(indicator, "(?!(?:[").concat(indicator).concat(lbrace, "]|\\d))(\\S+?)\\b)"),
      "g"
    );
    return (str, map) => {
      if (!map) {
        return str;
      }
      return str.replace(re, (match, prefix, idx, key, alpha) => {
        if (prefix === lead) {
          return lead;
        }
        const k = alpha || key || idx;
        const replacement = typeof map[k] === "function" ? map[k](match, k) : map[k];
        return typeof replacement === "string" ? replacement : replacement || match;
      });
    };
  };
  const replaceShortcuts = (() => {
    const replaceHash = substituteFactory({
      indicator: "#",
      lbrace: "[",
      rbrace: "]"
    });
    return (str, map) => {
      const s = replaceHash(str, map);
      return HC.capitalizePageNames ? capitalize(s) : s;
    };
  })();
  const findCatsRE = new RegExp("\\[\\[".concat(wikiTextBlankOrBidi, "(?:").concat(HC.category_regexp, ")").concat(wikiTextBlankOrBidi, ":[^\\]]+\\]\\]"), "g");
  const replaceByBlanks = (match) => {
    return match.replace(/(\s|\S)/g, " ");
  };
  const find_category = (wikitext, category, once) => {
    let cat_regex = null;
    if (HC.template_categories[category]) {
      cat_regex = new RegExp("\\{\\{".concat(wikiTextBlankOrBidi, "(").concat(HC.template_regexp, "(?=").concat(wikiTextBlankOrBidi, ":))?").concat(wikiTextBlankOrBidi, "(?:").concat(HC.template_categories[category], ")").concat(wikiTextBlankOrBidi, "(\\|.*?)?\\}\\}"), "g");
    } else {
      const cat_name = escapeRE(category);
      const initial = cat_name.slice(0, 1);
      cat_regex = new RegExp("\\[\\[".concat(wikiTextBlankOrBidi, "(").concat(HC.category_regexp, ")").concat(wikiTextBlankOrBidi, ":").concat(wikiTextBlankOrBidi).concat(initial === "\\" || !HC.capitalizePageNames ? initial : "[".concat(initial.toUpperCase()).concat(initial.toLowerCase(), "]")).concat(cat_name.slice(1).replace(wikiTextBlankRE, wikiTextBlank)).concat(wikiTextBlankOrBidi, "(\\|.*?)?\\]\\]"), "g");
    }
    if (once) {
      return cat_regex.exec(wikitext);
    }
    const nowikiRegex = new RegExp("<no".concat("wiki>", String.raw(_templateObject5 || (_templateObject5 = _taggedTemplateLiteral(["(s|S)*?</no"], ["(\\s|\\S)*?</no"]))), "wiki", ">"), "g");
    const copiedtext = wikitext.replace(/<!--(\s|\S)*?-->/g, replaceByBlanks).replace(nowikiRegex, replaceByBlanks);
    const result = [];
    let curr_match = null;
    while ((curr_match = cat_regex.exec(copiedtext)) !== null) {
      result[result.length] = {
        match: curr_match
      };
    }
    result.re = cat_regex;
    return result;
  };
  let interlanguageRE = null;
  const change_category = (wikitext, toRemove, toAdd, key, is_hidden) => {
    const find_insertionpoint = (_wikitext) => {
      const nowikiRegex = new RegExp("<no".concat("wiki>", String.raw(_templateObject6 || (_templateObject6 = _taggedTemplateLiteral(["(s|S)*?</no"], ["(\\s|\\S)*?</no"]))), "wiki", ">"), "g");
      const copiedtext = _wikitext.replace(/<!--(\s|\S)*?-->/g, replaceByBlanks).replace(nowikiRegex, replaceByBlanks);
      let index = -1;
      findCatsRE.lastIndex = 0;
      while (findCatsRE.exec(copiedtext) !== null) {
        index = findCatsRE.lastIndex;
      }
      if (index < 0) {
        let match = null;
        if (interlanguageRE) {
          match = interlanguageRE.exec(copiedtext);
        } else {
          match = /((^|\n\r?)(\[\[\s*(([a-z]{2,3}(-[a-z]+)*)|simple|tokipona)\s*:[^\]]+]]\s*))+$/.exec(copiedtext);
        }
        if (match) {
          ({
            index
          } = match);
        }
        return {
          idx: index,
          onCat: false
        };
      }
      return {
        idx: index,
        onCat: index >= 0
      };
    };
    const summary = [];
    const nameSpace = HC.category_canonical;
    const keyChange = toRemove && toAdd && toRemove === toAdd && toAdd.length > 0;
    let matches;
    let cat_point = -1;
    key && (key = "|".concat(key));
    if (toRemove && toRemove.length > 0) {
      matches = find_category(wikitext, toRemove);
      if (!matches || matches.length === 0) {
        return {
          text: wikitext,
          summary,
          error: getMessage("messages-cat_notFound", toRemove)
        };
      }
      let before = wikitext.slice(0, Math.max(0, matches[0].match.index));
      let after = wikitext.slice(Math.max(0, matches[0].match.index + matches[0].match[0].length));
      if (matches.length > 1) {
        matches.re.lastIndex = 0;
        after = after.replace(matches.re, "");
      }
      if (toAdd && // nameSpace = matches[ 0 ].match[ 1 ] || nameSpace; Canonical namespace should be always preferred
      key === null) {
        [, , key] = matches[0].match;
      }
      let i = before.length - 1;
      while (i >= 0 && before.charAt(i) !== "\n" && before.slice(i, i + 1).search(/\s/) >= 0) {
        i--;
      }
      let j = 0;
      while (j < after.length && after.charAt(j) !== "\n" && after.slice(j, j + 1).search(/\s/) >= 0) {
        j++;
      }
      if (i >= 0 && before.charAt(i) === "\n" && (after.length === 0 || j < after.length && after.charAt(j) === "\n")) {
        i--;
      }
      before = i >= 0 ? before.slice(0, Math.max(0, i + 1)) : "";
      after = j < after.length ? after.slice(Math.max(0, j)) : "";
      if (before.length > 0 && before.slice(Math.max(0, before.length - 1)).search(/\S/) >= 0 && after.length > 0 && after.slice(0, 1).search(/\S/) >= 0) {
        before += " ";
      }
      cat_point = before.length;
      if (cat_point === 0 && after.length > 0 && after.slice(0, 1) === "\n") {
        after = after.slice(1);
      }
      wikitext = before + after;
      if (!keyChange) {
        if (HC.template_categories[toRemove]) {
          summary[summary.length] = getMessage("messages-template_removed", toRemove);
        } else {
          summary[summary.length] = getMessage("messages-cat_removed", toRemove);
        }
      }
    }
    if (toAdd && toAdd.length > 0) {
      matches = find_category(wikitext, toAdd);
      if (matches && matches.length > 0) {
        return {
          text: wikitext,
          summary,
          error: getMessage("messages-cat_exists", toAdd)
        };
      }
      let onCat = false;
      if (cat_point < 0) {
        const point = find_insertionpoint(wikitext);
        cat_point = point.idx;
        ({
          onCat
        } = point);
      } else {
        onCat = true;
      }
      const newcatstring = "[[".concat(nameSpace, ":").concat(toAdd).concat(key || "", "]]");
      if (cat_point >= 0) {
        const suffix = wikitext.slice(Math.max(0, cat_point));
        wikitext = wikitext.slice(0, Math.max(0, cat_point)) + (cat_point > 0 ? "\n" : "") + newcatstring + (onCat ? "" : "\n");
        wikitext += suffix.length > 0 && suffix.slice(0, 1) !== "\n" ? "\n".concat(suffix) : suffix;
      } else {
        if (wikitext.length > 0 && wikitext.slice(-1, wikitext.length - 1 + 1) !== "\n") {
          wikitext += "\n";
        }
        wikitext += (wikitext.length > 0 ? "\n" : "") + newcatstring;
      }
      if (keyChange) {
        let k = key || "";
        if (k.length > 0) {
          k = k.slice(1);
        }
        summary[summary.length] = getMessage("messages-cat_keychange", toAdd, k);
      } else {
        summary[summary.length] = getMessage("messages-cat_added", toAdd);
      }
      if (HC.uncat_regexp && !is_hidden) {
        const txt = wikitext.replace(HC.uncat_regexp, "");
        if (txt.length !== wikitext.length) {
          wikitext = txt;
          summary[summary.length] = getMessage("messages-uncat_removed");
        }
      }
    }
    return {
      text: wikitext,
      summary,
      error: null
    };
  };
  const evtKeys = ({
    ctrlKey,
    metaKey,
    shiftKey
  }) => {
    let code = 0;
    if (ctrlKey) {
      if (ctrlKey || metaKey) {
        code || (code = 1);
      }
      if (shiftKey) {
        code || (code = 2);
      }
    }
    return code;
  };
  const evtKill = (e) => {
    if (e.preventDefault) {
      e.preventDefault();
      e.stopPropagation();
    } else {
      e.cancelBubble = true;
    }
    return false;
  };
  let catLine = null;
  let onUpload = false;
  let editors = [];
  let commitButton = null;
  let commitForm = null;
  let multiSpan = null;
  let pageText = null;
  let pageTime = null;
  let pageWatched = false;
  let watchCreate = false;
  let watchEdit = false;
  let minorEdits = false;
  let editToken = null;
  let is_rtl = false;
  let serverTime = null;
  let lastRevId = null;
  let pageTextRevId = null;
  let conflictingUser = null;
  let newDOM = false;
  const UNCHANGED = 0;
  const OPEN = 1;
  const CHANGE_PENDING = 2;
  const CHANGED = 3;
  const DELETED = 4;
  const setPage = (data) => {
    let startTime = null;
    if (data && data.query) {
      if (data.query.pages) {
        const [page] = data.query.pages;
        if (page) {
          if (page.revisions && page.revisions.length > 0) {
            pageText = page.revisions[0].slots.main.content;
            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";
          if (data.query.tokens) {
            editToken = data.query.tokens.csrftoken;
          }
          if (page.langlinks && (!data["query-continue"] || !data["query-continue"].langlinks)) {
            let re = "";
            for (let i = 0; i < page.langlinks.length; i++) {
              re += (i > 0 ? "|" : "") + page.langlinks[i].lang.replace(/([$()*+.?\\^])/g, String.raw(_templateObject7 || (_templateObject7 = _taggedTemplateLiteral(["$1"], ["\\$1"]))));
            }
            if (re.length > 0) {
              interlanguageRE = new RegExp("((^|\\n\\r?)(\\[\\[\\s*(".concat(re, ")\\s*:[^\\]]+\\]\\]\\s*))+$"));
            }
          }
        }
      }
      if (data.query.general) {
        if (data.query.general.time && !startTime) {
          startTime = data.query.general.time.replace(/\D/g, "");
        }
        if (HC.capitalizePageNames === null) {
          HC.capitalizePageNames = data.query.general.case === "first-letter";
        }
      }
      serverTime = startTime;
      if (data.query.userinfo && data.query.userinfo.options) {
        watchCreate = !HC.dont_add_to_watchlist && data.query.userinfo.options.watchcreations === "1";
        watchEdit = !HC.dont_add_to_watchlist && data.query.userinfo.options.watchdefault === "1";
        minorEdits = data.query.userinfo.options.minordefault === 1;
        if (minorEdits) {
          HC.single_minor = true;
        }
      }
    }
  };
  let saveInProgress = false;
  const initiateEdit = (doEdit, failure) => {
    if (saveInProgress) {
      return;
    }
    saveInProgress = true;
    let oldButtonState;
    if (commitButton) {
      oldButtonState = commitButton.disabled;
      commitButton.disabled = true;
    }
    const fail = function(...args) {
      saveInProgress = false;
      if (commitButton) {
        commitButton.disabled = oldButtonState;
      }
      failure.apply(this, args);
    };
    const params = {
      action: "query",
      format: "json",
      formatversion: "2",
      rawcontinue: "",
      titles: conf.wgPageName,
      prop: ["info", "revisions", "langlinks"],
      inprop: "watched",
      rvprop: ["content", "timestamp", "ids", "user"],
      rvslots: "main",
      rvlimit: "2",
      rvdir: "newer",
      rvstartid: conf.wgCurRevisionId,
      lllimit: "500",
      meta: ["siteinfo", "userinfo", "tokens"],
      type: "csrf",
      uiprop: ["options"]
    };
    api.get(params).done((data) => {
      setPage(data);
      doEdit(fail);
    }).fail(({
      status,
      statusText
    }) => {
      fail("".concat(status, " ").concat(statusText));
    });
  };
  const multiChangeMsg = (count) => {
    return getMessage("messages-multi_change", String(count));
  };
  const currentTimestamp = () => {
    const now = /* @__PURE__ */ new Date();
    let ts = String(now.getUTCFullYear());
    const two = (s) => {
      return s.slice(-2);
    };
    ts += two("0".concat(now.getUTCMonth() + 1)) + two("0".concat(now.getUTCDate())) + two("00".concat(now.getUTCHours())) + two("00".concat(now.getUTCMinutes())) + two("00".concat(now.getUTCSeconds()));
    return ts;
  };
  const performChanges = (failure, singleEditor) => {
    if (pageText === null) {
      failure(getMessage("messages-multi_error"));
      return;
    }
    let action;
    const selfEditConflict = (lastRevId !== null && lastRevId !== conf.wgCurRevisionId || pageTextRevId !== null && pageTextRevId !== conf.wgCurRevisionId) && conflictingUser && conflictingUser === conf.wgUserName;
    if (singleEditor && !singleEditor.noCommit && !HC.no_autocommit && editToken && !selfEditConflict) {
      commitForm.wpEditToken.value = editToken;
      action = commitForm.wpDiff;
      if (action) {
        action.value = "wpSave";
        action.name = action.value;
      }
    } else {
      action = commitForm.wpSave;
      if (action) {
        action.value = "wpDiff";
        action.name = action.value;
      }
    }
    let result = {
      text: pageText
    };
    const changed = [];
    const added = [];
    const deleted = [];
    const toEdit = singleEditor ? [singleEditor] : editors;
    let edit;
    let i;
    let error = null;
    let changes = 0;
    for (i = 0; i < toEdit.length; i++) {
      edit = toEdit[i];
      if (edit.state === CHANGED) {
        result = change_category(result.text, edit.originalCategory, edit.currentCategory, edit.currentKey, edit.currentHidden);
        if (!result.error) {
          changes++;
          if (!edit.originalCategory || edit.originalCategory.length === 0) {
            added[added.length] = edit.currentCategory;
          } else {
            changed[changed.length] = {
              from: edit.originalCategory,
              to: edit.currentCategory
            };
          }
        } else if (error === null) {
          ({
            error
          } = result);
        }
      } else if (edit.state === DELETED && edit.originalCategory && edit.originalCategory.length > 0) {
        result = change_category(result.text, edit.originalCategory, null, null, false);
        if (!result.error) {
          changes++;
          deleted[deleted.length] = edit.originalCategory;
        } else if (error === null) {
          ({
            error
          } = result);
        }
      }
    }
    if (error !== null) {
      action = commitForm.wpSave;
      if (action) {
        action.value = "wpDiff";
        action.name = action.value;
      }
    }
    commitForm.wpMinoredit.checked = minorEdits;
    commitForm.wpWatchthis.checked = !conf.wgArticleId && watchCreate || watchEdit || pageWatched;
    if (conf.wgArticleId || !!singleEditor) {
      if (action && action.value === "wpSave") {
        if (HC.changeTag) {
          commitForm.wpChangeTags.value = HC.changeTag;
        }
      } else {
        commitForm.wpAutoSummary.value = HC.changeTag;
      }
      if (changes === 1) {
        if (result.summary && result.summary.length > 0) {
          commitForm.wpSummary.value = (HC.changeTag ? "" : getMessage("messages-prefix")) + result.summary.join(getMessage("messages-separator")) + (HC.changeTag ? "" : getMessage("messages-using"));
        }
        commitForm.wpMinoredit.checked = HC.single_minor || minorEdits;
      } else if (changes) {
        let summary = [];
        const shortSummary = [];
        for (i = 0; i < deleted.length; i++) {
          summary[summary.length] = "−".concat(getMessage("messages-short_catchange", deleted[i]));
        }
        if (deleted.length === 1) {
          shortSummary[shortSummary.length] = "−".concat(getMessage("messages-short_catchange", deleted[0]));
        } else if (deleted.length > 0) {
          shortSummary[shortSummary.length] = "− ".concat(multiChangeMsg(deleted.length));
        }
        for (i = 0; i < added.length; i++) {
          summary[summary.length] = "+".concat(getMessage("messages-short_catchange", added[i]));
        }
        if (added.length === 1) {
          shortSummary[shortSummary.length] = "+".concat(getMessage("messages-short_catchange", added[0]));
        } else if (added.length > 0) {
          shortSummary[shortSummary.length] = "+ ".concat(multiChangeMsg(added.length));
        }
        const arrow = is_rtl ? "←" : "→";
        for (i = 0; i < changed.length; i++) {
          if (changed[i].from === changed[i].to) {
            summary[summary.length] = "±".concat(getMessage("messages-short_catchange", changed[i].from));
          } else {
            summary[summary.length] = "±".concat(getMessage("messages-short_catchange", changed[i].from)).concat(arrow).concat(getMessage("messages-short_catchange", changed[i].to));
          }
        }
        if (changed.length === 1) {
          if (changed[0].from === changed[0].to) {
            shortSummary[shortSummary.length] = "±".concat(getMessage("messages-short_catchange", changed[0].from));
          } else {
            shortSummary[shortSummary.length] = "±".concat(getMessage("messages-short_catchange", changed[0].from)).concat(arrow).concat(getMessage("messages-short_catchange", changed[0].to));
          }
        } else if (changed.length > 0) {
          shortSummary[shortSummary.length] = "± ".concat(multiChangeMsg(changed.length));
        }
        if (summary.length > 0) {
          summary = summary.join(getMessage("messages-separator"));
          if (summary.length > 200 - (HC.changeTag ? "" : getMessage("messages-prefix")).length - (HC.changeTag ? "" : getMessage("messages-using")).length) {
            summary = shortSummary.join(getMessage("messages-separator"));
          }
          commitForm.wpSummary.value = (HC.changeTag ? "" : getMessage("messages-prefix")) + summary + (HC.changeTag ? "" : getMessage("messages-using"));
        }
      }
    }
    commitForm.wpTextbox1.value = result.text;
    commitForm.wpStarttime.value = serverTime || currentTimestamp();
    commitForm.wpEdittime.value = pageTime || commitForm.wpStarttime.value;
    if (selfEditConflict) {
      commitForm.oldid.value = String(pageTextRevId || conf.wgCurRevisionId);
    }
    commitForm.hcCommit.click();
  };
  const resolveOne = (page, toResolve) => {
    const cats = page.categories;
    const {
      links
    } = page;
    let is_dab = false;
    let is_redir = typeof page.redirect === "string";
    let i;
    const is_hidden = page.categoryinfo && typeof page.categoryinfo.hidden === "string";
    const is_missing = typeof page.missing === "string";
    for (i = 0; i < toResolve.length; i++) {
      if (i && toResolve[i].dabInputCleaned !== page.title.slice(Math.max(0, page.title.indexOf(":") + 1))) {
        continue;
      }
      toResolve[i].currentHidden = is_hidden;
      toResolve[i].inputExists = !is_missing;
      toResolve[i].icon.src = is_missing ? HC.existsNo : HC.existsYes;
    }
    if (is_missing) {
      return;
    }
    if (!is_redir && cats && (getMessage("disambig_category") || getMessage("redir_category"))) {
      var _iterator = _createForOfIteratorHelper(cats), _step;
      try {
        for (_iterator.s(); !(_step = _iterator.n()).done; ) {
          const cat_ = _step.value;
          let cat = cat_.title;
          if (cat) {
            cat = cat.slice(Math.max(0, cat.indexOf(":") + 1)).replace(/_/g, " ");
            if (cat === getMessage("disambig_category")) {
              is_dab = true;
              break;
            } else if (cat === getMessage("redir_category")) {
              is_redir = true;
              break;
            }
          }
        }
      } catch (err) {
        _iterator.e(err);
      } finally {
        _iterator.f();
      }
    }
    if (!is_redir && !is_dab) {
      return;
    }
    if (!links || links.length === 0) {
      return;
    }
    const titles = [];
    for (i = 0; i < links.length; i++) {
      if (
        // Category namespace -- always true since we ask only for the category links
        links[i].ns === 14 && // Name not empty
        links[i].title && links[i].title.length > 0
      ) {
        let match = links[i].title;
        match = match.slice(Math.max(0, match.indexOf(":") + 1));
        if (!HC.blacklist || !HC.blacklist.test(match)) {
          titles[titles.length] = match;
        }
      }
    }
    if (titles.length === 0) {
      return;
    }
    for (i = 0; i < toResolve.length; i++) {
      if (i && toResolve[i].dabInputCleaned !== page.title.slice(Math.max(0, page.title.indexOf(":") + 1))) {
        continue;
      }
      toResolve[i].inputExists = true;
      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 ? "" : "|".concat(toResolve[i].currentKey));
      }
    }
  };
  const resolveRedirects = (toResolve, params) => {
    if (!params || !params.query || !params.query.pages) {
      return;
    }
    for (const p in params.query.pages) {
      if (!Object.hasOwn(params.query.pages, p)) {
        continue;
      }
      resolveOne(params.query.pages[p], toResolve);
    }
  };
  const resolveMulti = (toResolve, callback) => {
    let i;
    for (i = 0; i < toResolve.length; i++) {
      toResolve[i].dab = null;
      toResolve[i].dabInput = toResolve[i].lastInput;
    }
    if (noSuggestions) {
      callback(toResolve);
      return;
    }
    const params = {
      action: "query",
      format: "json",
      prop: "info|links|categories|categoryinfo",
      plnamespace: "14",
      pllimit: toResolve.length * 10,
      cllimit: toResolve.length * 10
    };
    const titles = [];
    for (i = 0; i < toResolve.length; i++) {
      let v = toResolve[i].dabInput;
      v = replaceShortcuts(v, HC.shortcuts);
      toResolve[i].dabInputCleaned = v;
      titles[titles.length] = "Category:".concat(v);
    }
    params.titles = titles.join("|");
    api.get(params).done((json) => {
      resolveRedirects(toResolve, json);
      callback(toResolve);
    }).fail((req) => {
      if (!req) {
        noSuggestions = true;
      }
      callback(toResolve);
    });
  };
  const makeActive = (which) => {
    if (which.is_active) {
      return;
    }
    for (var _i = 0, _editors = editors; _i < _editors.length; _i++) {
      const editor = _editors[_i];
      if (editor !== which) {
        editor.inactivate();
      }
    }
    which.is_active = true;
    if (which.dab) {
      showDab(which);
    } else {
      const expectedInput = which.lastRealInput || which.lastInput || "";
      const actualValue = which.text.value || "";
      if (expectedInput.length === 0 && actualValue.length > 0 || expectedInput.length > 0 && actualValue.indexOf(expectedInput)) {
        which.showsList = false;
        const v = actualValue.split("|");
        [which.lastInput] = v;
        which.lastRealInput = which.lastInput;
        if (v.length > 1) {
          [, which.currentKey] = v;
        }
        which.lastSelection && (which.lastSelection = {
          start: v[0].length,
          end: v[0].length
        });
      }
      if (which.showsList) {
        which.displayList();
      }
      if (which.lastSelection) {
        setTimeout(() => {
          which.setSelection(which.lastSelection.start, which.lastSelection.end);
        }, 0);
      }
    }
  };
  const showDab = (which) => {
    if (which.is_active) {
      which.showSuggestions(which.dab, false, null, null);
      which.dab = null;
    } else {
      makeActive(which);
    }
  };
  const multiSubmit = () => {
    const toResolve = [];
    for (var _i2 = 0, _editors2 = editors; _i2 < _editors2.length; _i2++) {
      const editor = _editors2[_i2];
      if (editor.state === CHANGE_PENDING || editor.state === OPEN) {
        toResolve[toResolve.length] = editor;
      }
    }
    if (toResolve.length === 0) {
      initiateEdit((failure) => {
        performChanges(failure);
      }, (msg) => {
        void mw.notify(msg, {
          tag: "hotCat"
        });
      });
      return;
    }
    resolveMulti(toResolve, (resolved) => {
      let firstDab = null;
      let dontChange = false;
      var _iterator2 = _createForOfIteratorHelper(resolved), _step2;
      try {
        for (_iterator2.s(); !(_step2 = _iterator2.n()).done; ) {
          const element = _step2.value;
          if (element.lastInput === element.dabInput) {
            if (element.dab) {
              firstDab || (firstDab = element);
            } else if (element.acceptCheck(true)) {
              element.commit();
            }
          } else {
            dontChange = true;
          }
        }
      } catch (err) {
        _iterator2.e(err);
      } finally {
        _iterator2.f();
      }
      if (firstDab) {
        showDab(firstDab);
      } else if (!dontChange) {
        initiateEdit((failure) => {
          performChanges(failure);
        }, (msg) => {
          void mw.notify(msg, {
            tag: "hotCat"
          });
        });
      }
    });
  };
  const setMultiInput = () => {
    if (commitButton || onUpload) {
      return;
    }
    commitButton = make("input");
    commitButton.type = "button";
    commitButton.value = getMessage("messages-commit");
    commitButton.addEventListener("click", multiSubmit);
    if (multiSpan) {
      multiSpan.replaceWith(commitButton);
    } else {
      catLine.append(commitButton);
    }
  };
  const checkMultiInput = () => {
    if (!commitButton) {
      return;
    }
    let hasChanges = false;
    for (var _i3 = 0, _editors3 = editors; _i3 < _editors3.length; _i3++) {
      const editor = _editors3[_i3];
      if (editor.state !== UNCHANGED) {
        hasChanges = true;
        break;
      }
    }
    commitButton.disabled = !hasChanges;
  };
  const suggestionEngines = {
    opensearch: {
      uri: "".concat(mw.config.get("wgScriptPath"), "/api.php?format=json&action=opensearch&namespace=14&limit=30&search=Category:$1"),
      // $1 = search term
      // Function to convert result of uri into an array of category names
      handler: (queryResult, queryKey) => {
        if (queryResult && queryResult.length >= 2) {
          const key = queryResult[0].slice(Math.max(0, queryResult[0].indexOf(":") + 1));
          const [, titles] = queryResult;
          let exists = false;
          cat_prefix || (cat_prefix = new RegExp("^(".concat(HC.category_regexp, "):")));
          for (let i = 0; i < titles.length; i++) {
            cat_prefix.lastIndex = 0;
            const m = cat_prefix.exec(titles[i]);
            if (m && m.length > 1) {
              titles[i] = titles[i].slice(Math.max(0, titles[i].indexOf(":") + 1));
              if (key === titles[i]) {
                exists = true;
              }
            } else {
              titles.splice(i, 1);
              i--;
            }
          }
          titles.exists = exists;
          if (queryKey !== key) {
            titles.normalized = key;
          }
          return titles;
        }
        return null;
      }
    },
    internalsearch: {
      uri: "".concat(mw.config.get("wgScriptPath"), "/api.php?format=json&action=query&list=allpages&apnamespace=14&aplimit=30&apfrom=$1&apprefix=$1"),
      handler: (queryResult) => {
        if (queryResult && queryResult.query && queryResult.query.allpages) {
          const titles = queryResult.query.allpages;
          for (let i = 0; i < titles.length; i++) {
            titles[i] = titles[i].title.slice(Math.max(0, titles[i].title.indexOf(":") + 1));
          }
          return titles;
        }
        return null;
      }
    },
    exists: {
      uri: "".concat(mw.config.get("wgScriptPath"), "/api.php?format=json&action=query&prop=info&titles=Category:$1"),
      handler: (queryResult, queryKey) => {
        if (queryResult && queryResult.query && queryResult.query.pages && !queryResult.query.pages[-1]) {
          for (const p in queryResult.query.pages) {
            if (!Object.hasOwn(queryResult.query.pages, p)) {
              continue;
            }
            let _title = queryResult.query.pages[p].title;
            _title = _title.slice(Math.max(0, _title.indexOf(":") + 1));
            const titles = [_title];
            titles.exists = true;
            if (queryKey !== _title) {
              titles.normalized = _title;
            }
            return titles;
          }
        }
        return null;
      }
    },
    subcategories: {
      uri: "".concat(mw.config.get("wgScriptPath"), "/api.php?format=json&action=query&list=categorymembers&cmtype=subcat&cmlimit=max&cmtitle=Category:$1"),
      handler: (queryResult) => {
        if (queryResult && queryResult.query && queryResult.query.categorymembers) {
          const titles = queryResult.query.categorymembers;
          for (let i = 0; i < titles.length; i++) {
            titles[i] = titles[i].title.slice(Math.max(0, titles[i].title.indexOf(":") + 1));
          }
          return titles;
        }
        return null;
      }
    },
    parentcategories: {
      uri: "".concat(mw.config.get("wgScriptPath"), "/api.php?format=json&action=query&prop=categories&titles=Category:$1&cllimit=max"),
      handler: (queryResult) => {
        if (queryResult && queryResult.query && queryResult.query.pages) {
          for (const p in queryResult.query.pages) {
            if (queryResult.query.pages[p].categories) {
              const titles = queryResult.query.pages[p].categories;
              for (let i = 0; i < titles.length; i++) {
                titles[i] = titles[i].title.slice(Math.max(0, titles[i].title.indexOf(":") + 1));
              }
              return titles;
            }
          }
        }
        return null;
      }
    }
  };
  const suggestionConfigs = {
    searchindex: {
      name: "Search index",
      engines: ["opensearch"],
      cache: {},
      show: true,
      temp: false,
      noCompletion: false
    },
    pagelist: {
      name: "Page list",
      engines: ["internalsearch", "exists"],
      cache: {},
      show: true,
      temp: false,
      noCompletion: false
    },
    combined: {
      name: "Combined search",
      engines: ["opensearch", "internalsearch"],
      cache: {},
      show: true,
      temp: false,
      noCompletion: false
    },
    subcat: {
      name: "Subcategories",
      engines: ["subcategories"],
      cache: {},
      show: true,
      temp: true,
      noCompletion: true
    },
    parentcat: {
      name: "Parent categories",
      engines: ["parentcategories"],
      cache: {},
      show: true,
      temp: true,
      noCompletion: true
    }
  };
  const BS = 8;
  const TAB = 9;
  const RET = 13;
  const ESC = 27;
  const SPACE = 32;
  const PGUP = 33;
  const PGDOWN = 34;
  const UP = 38;
  const DOWN = 40;
  const DEL = 46;
  const IME = 229;
  class CategoryEditor {
    constructor(...args) {
      this.isCompositionStart = false; 
      this.callbackObj = null;
      this.initialize(...args);
    }
    initialize(line, span, after, key, is_hidden) {
      if (span) {
        if (is_rtl) {
          span.dir = "rtl";
        }
        this.isAddCategory = false;
        this.catLink = span.firstChild;
        this.originalCategory = after;
        this.originalKey = key && key.length > 1 ? key.slice(1) : null;
        this.originalExists = !hasClass(this.catLink, "new");
        this.makeLinkSpan();
        if (!this.originalExists && this.upDownLinks) {
          this.upDownLinks.style.display = "none";
        }
        span.append(this.linkSpan);
      } else {
        this.isAddCategory = true;
        this.originalCategory = "";
        this.originalKey = null;
        this.originalExists = false;
        if (!newDOM) {
          span = make("span");
          span.className = "noprint";
          if (key) {
            span.append(make(" | ", true));
            if (after) {
              after.parentNode.insertBefore(span, after.nextSibling);
              after = after.nextSibling;
            } else if (line) {
              line.append(span);
            }
          } else if (line && line.firstChild) {
            span.append(make(" ", true));
            line.append(span);
          }
        }
        this.linkSpan = make("span");
        this.linkSpan.className = "noprint nopopups hotcatlink";
        const link = make("a");
        link.href = "#catlinks";
        link.addEventListener("click", this.open.bind(this));
        link.append(make(HC.links.add, true));
        link.title = getMessage("tooltips-add");
        this.linkSpan.append(link);
        span = make(newDOM ? "li" : "span");
        span.className = "noprint";
        if (is_rtl) {
          span.dir = "rtl";
        }
        span.append(this.linkSpan);
        if (after) {
          after.parentNode.insertBefore(span, after.nextSibling);
        } else if (line) {
          line.append(span);
        }
        this.normalLinks = null;
        this.undelLink = null;
        this.catLink = null;
      }
      this.originalHidden = is_hidden;
      this.line = line;
      this.engine = HC.suggestions;
      this.span = span;
      this.currentCategory = this.originalCategory;
      this.currentExists = this.originalExists;
      this.currentHidden = this.originalHidden;
      this.currentKey = this.originalKey;
      this.state = UNCHANGED;
      this.lastSavedState = UNCHANGED;
      this.lastSavedCategory = this.originalCategory;
      this.lastSavedKey = this.originalKey;
      this.lastSavedExists = this.originalExists;
      this.lastSavedHidden = this.originalHidden;
      if (this.catLink && this.currentKey) {
        this.catLink.title = this.currentKey;
      }
      editors[editors.length] = this;
    }
    makeLinkSpan() {
      this.normalLinks = make("span");
      let link = null;
      if (this.originalCategory && this.originalCategory.length > 0) {
        link = make("a");
        link.href = "#catlinks";
        link.addEventListener("click", this.remove.bind(this));
        link.append(make(HC.links.remove, true));
        link.title = getMessage("tooltips-remove");
        this.normalLinks.append(make(" ", true));
        this.normalLinks.append(link);
      }
      if (!HC.template_categories[this.originalCategory]) {
        link = make("a");
        link.href = "#catlinks";
        link.addEventListener("click", this.open.bind(this));
        link.append(make(HC.links.change, true));
        link.title = getMessage("tooltips-change");
        this.normalLinks.append(make(" ", true));
        this.normalLinks.append(link);
        if (!noSuggestions && HC.use_up_down) {
          this.upDownLinks = make("span");
          link = make("a");
          link.href = "#catlinks";
          link.addEventListener("click", this.down.bind(this));
          link.append(make(HC.links.down, true));
          link.title = getMessage("tooltips-down");
          this.upDownLinks.append(make(" ", true));
          this.upDownLinks.append(link);
          link = make("a");
          link.href = "#catlinks";
          link.addEventListener("click", this.up.bind(this));
          link.append(make(HC.links.up, true));
          link.title = getMessage("tooltips-up");
          this.upDownLinks.append(make(" ", true));
          this.upDownLinks.append(link);
          this.normalLinks.append(this.upDownLinks);
        }
      }
      this.linkSpan = make("span");
      this.linkSpan.className = "noprint nopopups hotcatlink";
      this.linkSpan.append(this.normalLinks);
      this.undelLink = make("span");
      this.undelLink.className = "nopopups hotcatlink";
      this.undelLink.style.display = "none";
      link = make("a");
      link.href = "#catlinks";
      link.addEventListener("click", this.restore.bind(this));
      link.append(make(HC.links.restore, true));
      link.title = getMessage("tooltips-restore");
      this.undelLink.append(make(" ", true));
      this.undelLink.append(link);
      this.linkSpan.append(this.undelLink);
    }
    invokeSuggestions(dont_autocomplete) {
      if (this.engine && suggestionConfigs[this.engine] && suggestionConfigs[this.engine].temp && !dont_autocomplete) {
        this.engine = HC.suggestions;
      }
      this.state = CHANGE_PENDING;
      const self = this;
      setTimeout(() => {
        self.textchange(dont_autocomplete);
      }, HC.suggest_delay);
    }
    makeForm() {
      const form = make("form");
      form.method = "POST";
      form.addEventListener("submit", this.accept.bind(this));
      this.form = form;
      const self = this;
      const text = make("input");
      text.type = "text";
      text.size = HC.editbox_width;
      if (!noSuggestions) {
        text.addEventListener("keyup", (event) => {
          const key = event.key || 0;
          if (self.ime && self.lastKey === IME && !self.usesComposition && (key === TAB || key === RET || key === ESC || key === SPACE)) {
            self.ime = false;
          }
          if (self.ime) {
            return true;
          }
          if (key === UP || key === DOWN || key === PGUP || key === PGDOWN) {
            if (self.keyCount === 0) {
              return self.processKey(event);
            }
          } else {
            if (key === ESC && self.lastKey !== IME && !self.resetKeySelection()) {
              self.cancel();
              return;
            }
            if (self.isCompositionStart) {
              return;
            }
            self.invokeSuggestions(key === BS || key === DEL || key === ESC);
          }
          return true;
        });
        text.addEventListener("keydown", (event) => {
          const key = event.key || 0;
          self.lastKey = key;
          self.keyCount = 0;
          if (!self.ime && key === IME && !self.usesComposition) {
            self.ime = true;
          } else if (self.ime && key !== IME && !(key >= 16 && key <= 20 || key >= 91 && key <= 93 || key === 144)) {
            self.ime = false;
          }
          if (self.ime) {
            return true;
          }
          if (key === RET) {
            return self.accept(event);
          }
          return key === ESC ? evtKill(event) : true;
        });
        text.addEventListener("keypress", (event) => {
          self.keyCount++;
          return self.processKey(event);
        });
        text.addEventListener("focus", () => {
          makeActive(self);
        });
        $(text).on("blur", this.saveView.bind(this));
        try {
          $(text).on("compositionstart", () => {
            self.lastKey = IME;
            self.usesComposition = true;
            self.ime = true;
            self.isCompositionStart = true;
          });
          $(text).on("compositionend", () => {
            self.lastKey = IME;
            self.usesComposition = true;
            self.ime = false;
            self.isCompositionStart = false;
          });
          $(text).on("textInput", () => {
            self.ime = false;
            if (self.isCompositionStart) {
              return;
            }
            self.invokeSuggestions(false);
          });
        } catch (e) {
        }
        $(text).on("blur", () => {
          self.usesComposition = false;
          self.ime = false;
        });
      }
      this.text = text;
      this.icon = make("img");
      let list = null;
      if (!noSuggestions) {
        list = make("select");
        list.addEventListener("click", () => {
          if (self.highlightSuggestion(0)) {
            self.textchange(false, true);
          }
        });
        list.addEventListener("dblclick", (e) => {
          if (self.highlightSuggestion(0)) {
            self.accept(e);
          }
        });
        list.addEventListener("change", () => {
          self.highlightSuggestion(0);
          self.text.focus();
        });
        list.addEventListener("keyup", (event) => {
          if (event.key === ESC) {
            self.resetKeySelection();
            self.text.focus();
            setTimeout(() => {
              self.textchange(true);
            }, HC.suggest_delay);
          } else if (event.key === RET) {
            self.accept(event);
          }
        });
        if (!HC.fixed_search) {
          const engineSelector = make("select");
          for (const key in suggestionConfigs) {
            if (suggestionConfigs[key].show) {
              const opt = make("option");
              opt.value = key;
              if (key === this.engine) {
                opt.selected = true;
              }
              opt.append(make(suggestionConfigs[key].name, true));
              engineSelector.append(opt);
            }
          }
          engineSelector.addEventListener("change", () => {
            self.engine = self.engineSelector.options[self.engineSelector.selectedIndex].value;
            self.text.focus();
            self.textchange(true, true);
          });
          this.engineSelector = engineSelector;
        }
      }
      this.list = list;
      const button_label = (_id, defaultText) => {
        const label = null;
        if (!label || !label.data) {
          return defaultText;
        }
        return label.data;
      };
      const OK = make("input");
      OK.type = "button";
      OK.value = button_label("wpOkUploadLbl", getMessage("messages-ok"));
      OK.addEventListener("click", this.accept.bind(this));
      this.ok = OK;
      const cancel = make("input");
      cancel.type = "button";
      cancel.value = button_label("wpCancelUploadLbl", getMessage("messages-cancel"));
      cancel.addEventListener("click", this.cancel.bind(this));
      this.cancelButton = cancel;
      const span = make("span");
      span.className = "hotcatinput";
      span.style.position = "relative";
      span.append(text);
      span.style.whiteSpace = "nowrap";
      if (list) {
        span.append(list);
      }
      if (this.engineSelector) {
        span.append(this.engineSelector);
      }
      if (!noSuggestions) {
        span.append(this.icon);
      }
      span.append(OK);
      span.append(cancel);
      form.append(span);
      form.style.display = "none";
      this.span.append(form);
    }
    display(event) {
      if (this.isAddCategory && !onUpload && this.line) {
        new CategoryEditor(this.line, null, this.span, true);
      }
      if (!commitButton && !onUpload) {
        for (var _i4 = 0, _editors4 = editors; _i4 < _editors4.length; _i4++) {
          const editor = _editors4[_i4];
          if (editor.state !== UNCHANGED) {
            setMultiInput();
            break;
          }
        }
      }
      if (!this.form) {
        this.makeForm();
      }
      if (this.list) {
        this.list.style.display = "none";
      }
      if (this.engineSelector) {
        this.engineSelector.style.display = "none";
      }
      this.currentCategory = this.lastSavedCategory;
      this.currentExists = this.lastSavedExists;
      this.currentHidden = this.lastSavedHidden;
      this.currentKey = this.lastSavedKey;
      this.icon.src = this.currentExists ? HC.existsYes : HC.existsNo;
      this.text.value = this.currentCategory + (this.currentKey === null ? "" : "|".concat(this.currentKey));
      this.originalState = this.state;
      this.lastInput = this.currentCategory;
      this.inputExists = this.currentExists;
      this.state = this.state === UNCHANGED ? OPEN : CHANGE_PENDING;
      this.lastSelection = {
        start: this.currentCategory.length,
        end: this.currentCategory.length
      };
      this.showsList = false;
      if (this.catLink) {
        this.catLink.style.display = "none";
      }
      this.linkSpan.style.display = "none";
      this.form.style.display = "inline";
      this.ok.disabled = false;
      const result = evtKill(event);
      this.text.focus();
      this.text.readOnly = false;
      checkMultiInput();
      return result;
    }
    show(event, engine, readOnly) {
      const result = this.display(event);
      const v = this.lastSavedCategory;
      if (v.length === 0) {
        return result;
      }
      this.text.readOnly = !!readOnly;
      this.engine = engine;
      this.textchange(false, true);
      return result;
    }
    open(event) {
      return this.show(event, this.engine && suggestionConfigs[this.engine].temp ? HC.suggestions : this.engine);
    }
    down(event) {
      return this.show(event, "subcat", true);
    }
    up(event) {
      return this.show(event, "parentcat");
    }
    cancel() {
      if (this.isAddCategory && !onUpload) {
        this.removeEditor();
        return;
      }
      this.inactivate();
      this.form.style.display = "none";
      if (this.catLink) {
        this.catLink.style.display = "";
      }
      this.linkSpan.style.display = "";
      this.state = this.originalState;
      this.currentCategory = this.lastSavedCategory;
      this.currentKey = this.lastSavedKey;
      this.currentExists = this.lastSavedExists;
      this.currentHidden = this.lastSavedHidden;
      if (this.catLink) {
        this.catLink.title = this.currentKey && this.currentKey.length > 0 ? this.currentKey : "";
      }
      if (this.state === UNCHANGED) {
        if (this.catLink) {
          this.catLink.style.backgroundColor = "transparent";
        }
      } else if (!onUpload) {
        try {
          this.catLink.style.backgroundColor = HC.bg_changed;
        } catch (e) {
        }
      }
      checkMultiInput();
    }
    removeEditor() {
      if (!newDOM) {
        const next = this.span.nextSibling;
        if (next) {
          next.remove();
        }
      }
      if (this.span && this.span.parentNode) {
        this.span.remove();
      }
      for (let i = 0; i < editors.length; i++) {
        if (editors[i] === this) {
          editors.splice(i, 1);
          break;
        }
      }
      checkMultiInput();
    }
    rollback(event) {
      this.undoLink.remove();
      this.undoLink = null;
      this.currentCategory = this.originalCategory;
      this.currentKey = this.originalKey;
      this.currentExists = this.originalExists;
      this.currentHidden = this.originalHidden;
      this.lastSavedCategory = this.originalCategory;
      this.lastSavedKey = this.originalKey;
      this.lastSavedExists = this.originalExists;
      this.lastSavedHidden = this.originalHidden;
      this.state = UNCHANGED;
      if (!this.currentCategory || this.currentCategory.length === 0) {
        this.removeEditor();
      } else {
        this.catLink.firstChild.remove();
        this.catLink.append(make(this.currentCategory, true));
        this.catLink.href = wikiPagePath("".concat(HC.category_canonical, ":").concat(this.currentCategory));
        this.catLink.title = this.currentKey || "";
        this.catLink.className = this.currentExists ? "" : "new";
        this.catLink.style.backgroundColor = "transparent";
        if (this.upDownLinks) {
          this.upDownLinks.style.display = this.currentExists ? "" : "none";
        }
        checkMultiInput();
      }
      return evtKill(event);
    }
    inactivate() {
      if (this.list) {
        this.list.style.display = "none";
      }
      if (this.engineSelector) {
        this.engineSelector.style.display = "none";
      }
      this.is_active = false;
    }
    acceptCheck(dontCheck) {
      this.sanitizeInput();
      const value = this.text.value.split("|");
      let key = null;
      if (value.length > 1) {
        [, key] = value;
      }
      let v = value[0].replace(/_/g, " ").trim();
      if (HC.capitalizePageNames) {
        v = capitalize(v);
      }
      this.lastInput = v;
      v = replaceShortcuts(v, HC.shortcuts);
      if (v.length === 0) {
        this.cancel();
        return false;
      }
      if (!dontCheck && (conf.wgNamespaceNumber === 14 && v === conf.wgTitle || HC.blacklist && HC.blacklist.test(v))) {
        this.cancel();
        return false;
      }
      this.currentCategory = v;
      this.currentKey = key;
      this.currentExists = this.inputExists;
      return true;
    }
    accept(event) {
      this.noCommit = evtKeys(event) === 1;
      const result = evtKill(event);
      if (this.acceptCheck()) {
        const toResolve = [this];
        const original = this.currentCategory;
        resolveMulti(toResolve, (resolved) => {
          if (resolved[0].dab) {
            showDab(resolved[0]);
          } else if (resolved[0].acceptCheck(true)) {
            resolved[0].commit(resolved[0].currentCategory === original ? null : getMessage("messages-cat_resolved", original));
          }
        });
      }
      return result;
    }
    close() {
      if (!this.catLink) {
        this.catLink = make("a");
        this.catLink.append(make("foo", true));
        this.catLink.style.display = "none";
        this.span.insertBefore(this.catLink, this.span.firstChild.nextSibling);
      }
      this.catLink.firstChild.remove();
      this.catLink.append(make(this.currentCategory, true));
      this.catLink.href = wikiPagePath("".concat(HC.category_canonical, ":").concat(this.currentCategory));
      this.catLink.className = this.currentExists ? "" : "new";
      this.lastSavedCategory = this.currentCategory;
      this.lastSavedKey = this.currentKey;
      this.lastSavedExists = this.currentExists;
      this.lastSavedHidden = this.currentHidden;
      this.inactivate();
      this.form.style.display = "none";
      this.catLink.title = this.currentKey || "";
      this.catLink.style.display = "";
      if (this.isAddCategory) {
        if (onUpload && this.line) {
          new CategoryEditor(this.line, null, this.span, true);
        }
        this.isAddCategory = false;
        this.linkSpan.remove();
        this.makeLinkSpan();
        this.span.append(this.linkSpan);
      }
      if (!this.undoLink) {
        const span = make("span");
        const link = make("a");
        link.href = "#catlinks";
        link.addEventListener("click", this.rollback.bind(this));
        link.append(make(HC.links.undo, true));
        link.title = getMessage("tooltips-undo");
        span.append(make(" ", true));
        span.append(link);
        this.normalLinks.append(span);
        this.undoLink = span;
        if (!onUpload) {
          try {
            this.catLink.style.backgroundColor = HC.bg_changed;
          } catch (e) {
          }
        }
      }
      if (this.upDownLinks) {
        this.upDownLinks.style.display = this.lastSavedExists ? "" : "none";
      }
      this.linkSpan.style.display = "";
      this.state = CHANGED;
      checkMultiInput();
    }
    commit() {
      if (this.currentCategory === this.originalCategory && (this.currentKey === this.originalKey || this.currentKey === null && this.originalKey.length === 0) || conf.wgNamespaceNumber === 14 && this.currentCategory === conf.wgTitle || HC.blacklist && HC.blacklist.test(this.currentCategory)) {
        this.cancel();
        return;
      }
      this.close();
      if (!commitButton && !onUpload) {
        const self = this;
        initiateEdit((failure) => {
          performChanges(failure, self);
        }, (msg) => {
          void mw.notify(msg, {
            tag: "hotCat"
          });
        });
      }
    }
    remove(event) {
      this.doRemove(evtKeys(event) === 1);
      return evtKill(event);
    }
    doRemove(noCommit) {
      if (this.isAddCategory) {
        this.cancel();
        return;
      }
      if (!commitButton && !onUpload) {
        for (var _i5 = 0, _editors5 = editors; _i5 < _editors5.length; _i5++) {
          const editor = _editors5[_i5];
          if (editor.state !== UNCHANGED) {
            setMultiInput();
            break;
          }
        }
      }
      if (commitButton) {
        this.catLink.title = "";
        this.catLink.style.cssText += "; text-decoration : line-through !important;";
        try {
          this.catLink.style.backgroundColor = HC.bg_changed;
        } catch (e) {
        }
        this.originalState = this.state;
        this.state = DELETED;
        this.normalLinks.style.display = "none";
        this.undelLink.style.display = "";
        checkMultiInput();
      } else if (onUpload) {
        this.removeEditor();
      } else {
        this.originalState = this.state;
        this.state = DELETED;
        this.noCommit = noCommit || HC.del_needs_diff;
        const self = this;
        initiateEdit((failure) => {
          performChanges(failure, self);
        }, (msg) => {
          self.state = self.originalState;
          void mw.notify(msg, {
            tag: "hotCat"
          });
        });
      }
    }
    restore(event) {
      this.catLink.title = this.currentKey || "";
      this.catLink.style.textDecoration = "";
      this.state = this.originalState;
      if (this.state === UNCHANGED) {
        this.catLink.style.backgroundColor = "transparent";
      } else {
        try {
          this.catLink.style.backgroundColor = HC.bg_changed;
        } catch (e) {
        }
      }
      this.normalLinks.style.display = "";
      this.undelLink.style.display = "none";
      checkMultiInput();
      return evtKill(event);
    }
    // Internal operations
    selectEngine(engineName) {
      if (!this.engineSelector) {
        return;
      }
      for (let i = 0; i < this.engineSelector.options.length; i++) {
        this.engineSelector.options[i].selected = this.engineSelector.options[i].value === engineName;
      }
    }
    sanitizeInput() {
      let v = this.text.value || "";
      v = v.replace(/^(\s|_)+/, "");
      const re = new RegExp("^(".concat(HC.category_regexp, "):"));
      if (re.test(v)) {
        v = v.slice(Math.max(0, v.indexOf(":") + 1)).replace(/^(\s|_)+/, "");
      }
      v = v.replace(/\u200E$/, "");
      if (HC.capitalizePageNames) {
        v = capitalize(v);
      }
      if (this.text.value !== null && this.text.value !== v) {
        this.text.value = v;
      }
    }
    makeCall(url, callbackObj, engine, queryKey, cleanKey) {
      let cb = callbackObj;
      const e = engine;
      const v = queryKey;
      const z = cleanKey;
      const self = this;
      const done = () => {
        cb.callsMade++;
        if (cb.callsMade === cb.nofCalls) {
          if (cb.exists) {
            cb.allTitles.exists = true;
          }
          if (cb.normalized) {
            cb.allTitles.normalized = cb.normalized;
          }
          if (!cb.dontCache && !suggestionConfigs[cb.engineName].cache[z]) {
            suggestionConfigs[cb.engineName].cache[z] = cb.allTitles;
          }
          self.text.readOnly = false;
          if (!cb.cancelled) {
            self.showSuggestions(cb.allTitles, cb.noCompletion, v, cb.engineName);
          }
          if (cb === self.callbackObj) {
            self.callbackObj = null;
          }
          cb = void 0;
        }
      };
      $.getJSON(url, (json) => {
        const titles = e.handler(json, z);
        if (titles && titles.length > 0) {
          cb.allTitles = cb.allTitles === null ? titles : [...cb.allTitles, ...(0, import_ext_gadget2.generateArray)(titles)];
          if (titles.exists) {
            cb.exists = true;
          }
          if (titles.normalized) {
            cb.normalized = titles.normalized;
          }
        }
        done();
      }).fail((req) => {
        if (!req) {
          noSuggestions = true;
        }
        cb.dontCache = true;
        done();
      });
    }
    textchange(dont_autocomplete, force) {
      makeActive(this);
      this.sanitizeInput();
      let v = this.text.value;
      const pipe = v.indexOf("|");
      if (pipe === -1) {
        this.currentKey = null;
      } else {
        this.currentKey = v.slice(Math.max(0, pipe + 1));
        v = v.slice(0, Math.max(0, pipe));
      }
      if (this.lastInput === v && !force) {
        return;
      }
      if (this.lastInput !== v) {
        checkMultiInput();
      }
      this.lastInput = v;
      this.lastRealInput = v;
      this.ok.disabled = v.length > 0 && HC.blacklist && HC.blacklist.test(v);
      if (noSuggestions) {
        if (this.list) {
          this.list.style.display = "none";
        }
        if (this.engineSelector) {
          this.engineSelector.style.display = "none";
        }
        if (this.icon) {
          this.icon.style.display = "none";
        }
        return;
      }
      if (v.length === 0) {
        this.showSuggestions([]);
        return;
      }
      let cleanKey = v.replace(/[\u200E\u200F\u202A-\u202E]/g, "").replace(wikiTextBlankRE, " ");
      cleanKey = replaceShortcuts(cleanKey, HC.shortcuts);
      cleanKey = cleanKey.trim();
      if (cleanKey.length === 0) {
        this.showSuggestions([]);
        return;
      }
      if (this.callbackObj) {
        this.callbackObj.cancelled = true;
      }
      const engineName = suggestionConfigs[this.engine] ? this.engine : "combined";
      dont_autocomplete || (dont_autocomplete = suggestionConfigs[engineName].noCompletion);
      if (suggestionConfigs[engineName].cache[cleanKey]) {
        this.showSuggestions(suggestionConfigs[engineName].cache[cleanKey], dont_autocomplete, v, engineName);
        return;
      }
      const {
        engines
      } = suggestionConfigs[engineName];
      this.callbackObj = {
        allTitles: null,
        callsMade: 0,
        nofCalls: engines.length,
        noCompletion: dont_autocomplete,
        engineName
      };
      this.makeCalls(engines, this.callbackObj, v, cleanKey);
    }
    makeCalls(engines, cb, v, cleanKey) {
      var _iterator3 = _createForOfIteratorHelper(engines), _step3;
      try {
        for (_iterator3.s(); !(_step3 = _iterator3.n()).done; ) {
          const engine_ = _step3.value;
          const engine = suggestionEngines[engine_];
          const url = conf.wgScriptPath + engine.uri.replace(/\$1/g, encodeURIComponent(cleanKey));
          this.makeCall(url, cb, engine, v, cleanKey);
        }
      } catch (err) {
        _iterator3.e(err);
      } finally {
        _iterator3.f();
      }
    }
    showSuggestions(titles, dontAutocomplete, queryKey, engineName) {
      this.text.readOnly = false;
      this.dab = null;
      this.showsList = false;
      if (!this.list) {
        return;
      }
      if (noSuggestions) {
        if (this.list) {
          this.list.style.display = "none";
        }
        if (this.engineSelector) {
          this.engineSelector.style.display = "none";
        }
        if (this.icon) {
          this.icon.style.display = "none";
        }
        this.inputExists = true;
        return;
      }
      this.engineName = engineName;
      if (engineName) {
        if (!this.engineSelector) {
          this.engineName = null;
        }
      } else if (this.engineSelector) {
        this.engineSelector.style.display = "none";
      }
      if (queryKey) {
        if (this.lastInput.indexOf(queryKey)) {
          return;
        }
        if (this.lastQuery && this.lastInput.indexOf(this.lastQuery) === 0 && this.lastQuery.length > queryKey.length) {
          return;
        }
      }
      this.lastQuery = queryKey;
      let v = this.text.value.split("|");
      const key = v.length > 1 ? "|".concat(v[1]) : "";
      v = HC.capitalizePageNames ? capitalize(v[0]) : v[0];
      let vNormalized = v;
      const knownToExist = titles && titles.exists;
      let i;
      if (titles) {
        if (titles.normalized && v.indexOf(queryKey) === 0) {
          vNormalized = titles.normalized + v.slice(queryKey.length);
        }
        const vLow = vNormalized.toLowerCase();
        if (HC.blacklist) {
          for (i = 0; i < titles.length; i++) {
            if (HC.blacklist.test(titles[i])) {
              titles.splice(i, 1);
              i--;
            }
          }
        }
        titles.sort((a, b) => {
          if (a === b) {
            return 0;
          }
          if (a.indexOf(b) === 0) {
            return 1;
          }
          if (b.indexOf(a) === 0) {
            return -1;
          }
          let prefixMatchA = a.indexOf(vNormalized) === 0 ? 1 : 0;
          let prefixMatchB = b.indexOf(vNormalized) === 0 ? 1 : 0;
          if (prefixMatchA !== prefixMatchB) {
            return prefixMatchB - prefixMatchA;
          }
          const aLow = a.toLowerCase();
          const bLow = b.toLowerCase();
          prefixMatchA = aLow.indexOf(vLow) === 0 ? 1 : 0;
          prefixMatchB = bLow.indexOf(vLow) === 0 ? 1 : 0;
          if (prefixMatchA !== prefixMatchB) {
            return prefixMatchB - prefixMatchA;
          }
          if (a < b) {
            return -1;
          }
          if (b < a) {
            return 1;
          }
          return 0;
        });
        for (i = 0; i < titles.length; i++) {
          if (i + 1 < titles.length && titles[i] === titles[i + 1] || conf.wgNamespaceNumber === 14 && titles[i] === conf.wgTitle) {
            titles.splice(i, 1);
            i--;
          }
        }
      }
      if (!titles || titles.length === 0) {
        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.icon) {
            this.icon.src = HC.existsNo;
          }
          this.inputExists = false;
        }
        return;
      }
      const [firstTitle] = titles;
      const completed = this.autoComplete(firstTitle, v, vNormalized, key, dontAutocomplete);
      const existing = completed || knownToExist || firstTitle === replaceShortcuts(v, HC.shortcuts);
      if (engineName && suggestionConfigs[engineName] && !suggestionConfigs[engineName].temp) {
        this.icon.src = existing ? HC.existsYes : HC.existsNo;
        this.inputExists = existing;
      }
      if (completed) {
        this.lastInput = firstTitle;
        if (titles.length === 1) {
          this.list.style.display = "none";
          if (this.engineSelector) {
            this.engineSelector.style.display = "none";
          }
          return;
        }
      }
      while (this.list.firstChild) {
        this.list.firstChild.remove();
      }
      for (i = 0; i < titles.length; i++) {
        const opt = make("option");
        opt.append(make(titles[i], true));
        opt.selected = completed && i === 0;
        this.list.append(opt);
      }
      this.displayList();
    }
    displayList() {
      this.showsList = true;
      if (!this.is_active) {
        this.list.style.display = "none";
        if (this.engineSelector) {
          this.engineSelector.style.display = "none";
        }
        return;
      }
      let nofItems = Math.min(this.list.options.length, HC.listSize);
      if (nofItems <= 1) {
        nofItems = 2;
      }
      this.list.size = nofItems;
      this.list.style.align = is_rtl ? "right" : "left";
      this.list.style.zIndex = 5;
      this.list.style.position = "absolute";
      const anchor = is_rtl ? "right" : "left";
      let listh = 0;
      if (this.list.style.display === "none") {
        this.list.style.top = "".concat(this.text.offsetTop, "px");
        this.list.style[anchor] = "-10000px";
        this.list.style.display = "";
        listh = this.list.offsetHeight;
        this.list.style.display = "none";
      } else {
        listh = this.list.offsetHeight;
      }
      let maxListHeight = listh;
      if (nofItems < HC.listSize) {
        maxListHeight = listh / nofItems * HC.listSize;
      }
      const viewport = (what) => {
        const s = "client".concat(what);
        return document.documentElement ? document.documentElement[s] : 0;
      };
      const scroll_offset = (what) => {
        const s = "scroll".concat(what);
        let result = document.documentElement ? document.documentElement[s] : 0;
        if (is_rtl && what === "Left") {
          if (result < 0) {
            result = -result;
          }
          if (!is_webkit) {
            result = scroll_offset("Width") - viewport("Width") - result;
          }
        }
        return result;
      };
      const position = (node) => {
        if (node.getBoundingClientRect) {
          const box = node.getBoundingClientRect();
          return {
            x: Math.round(box.left + scroll_offset("Left")),
            y: Math.round(box.top + scroll_offset("Top"))
          };
        }
        let t = 0;
        let l = 0;
        do {
          t += node.offsetTop || 0;
          l += node.offsetLeft || 0;
          node = node.offsetParent;
        } while (node);
        return {
          x: l,
          y: t
        };
      };
      const textPos = position(this.text);
      const nl = 0;
      let nt = 0;
      let offset = 0;
      const textBoxWidth = this.text.offsetWidth || this.text.clientWidth;
      if (this.engineName) {
        this.engineSelector.style.zIndex = 5;
        this.engineSelector.style.position = "absolute";
        this.engineSelector.style.width = "".concat(textBoxWidth, "px");
        if (this.engineSelector.style.display === "none") {
          this.engineSelector.style[anchor] = "-10000px";
          this.engineSelector.style.top = "0";
          this.engineSelector.style.display = "";
          offset = this.engineSelector.offsetHeight;
          this.engineSelector.style.display = "none";
        } else {
          offset = this.engineSelector.offsetHeight;
        }
        this.engineSelector.style[anchor] = "".concat(nl, "px");
      }
      if (textPos.y < maxListHeight + offset + 1) {
        nt = this.text.offsetHeight + offset + 1;
        if (this.engineName) {
          this.engineSelector.style.top = "".concat(this.text.offsetHeight, "px");
        }
      } else {
        nt = -listh - offset - 1;
        if (this.engineName) {
          this.engineSelector.style.top = "".concat(-(offset + 1), "px");
        }
      }
      this.list.style.top = "".concat(nt, "px");
      this.list.style.width = "";
      this.list.style[anchor] = "".concat(nl, "px");
      if (this.engineName) {
        this.selectEngine(this.engineName);
        this.engineSelector.style.display = "";
      }
      this.list.style.display = "block";
      if (this.list.offsetWidth < textBoxWidth) {
        this.list.style.width = "".concat(textBoxWidth, "px");
        return;
      }
      const scroll = scroll_offset("Left");
      const view_w = viewport("Width");
      let w = this.list.offsetWidth;
      const l_pos = position(this.list);
      let left = l_pos.x;
      let right = left + w;
      if (left < scroll || right > scroll + view_w) {
        if (w > view_w) {
          w = view_w;
          this.list.style.width = "".concat(w, "px");
          if (is_rtl) {
            left = right - w;
          } else {
            right = left + w;
          }
        }
        let relative_offset = 0;
        if (left < scroll) {
          relative_offset = scroll - left;
        } else if (right > scroll + view_w) {
          relative_offset = -(right - scroll - view_w);
        }
        if (is_rtl) {
          relative_offset = -relative_offset;
        }
        if (relative_offset) {
          this.list.style[anchor] = "".concat(nl + relative_offset, "px");
        }
      }
    }
    autoComplete(newVal, actVal, normalizedActVal, key, dontModify) {
      if (newVal === actVal) {
        return true;
      }
      if (dontModify || this.ime || !this.canSelect()) {
        return false;
      }
      if (newVal.indexOf(actVal)) {
        if (normalizedActVal && newVal.indexOf(normalizedActVal) === 0) {
          if (this.lastRealInput === actVal) {
            this.lastRealInput = normalizedActVal;
          }
          actVal = normalizedActVal;
        } else {
          return false;
        }
      }
      this.text.focus();
      this.text.value = newVal + key;
      this.setSelection(actVal.length, newVal.length);
      return true;
    }
    canSelect() {
      return this.text.setSelectionRange || this.text.selectionStart !== void 0 && this.text.selectionEnd !== void 0;
    }
    setSelection(from, to) {
      if (!this.text.value) {
        return;
      }
      if (this.text.setSelectionRange) {
        this.text.setSelectionRange(from, to);
      } else if (this.text.selectionStart !== void 0) {
        if (from > this.text.selectionStart) {
          this.text.selectionEnd = to;
          this.text.selectionStart = from;
        } else {
          this.text.selectionStart = from;
          this.text.selectionEnd = to;
        }
      }
    }
    getSelection() {
      let from = 0;
      let to = 0;
      if (!this.text.value) {
      } else if (this.text.selectionStart !== void 0) {
        from = this.text.selectionStart;
        to = this.text.selectionEnd;
      }
      return {
        start: from,
        end: to
      };
    }
    saveView() {
      this.lastSelection = this.getSelection();
    }
    processKey(event) {
      let dir = 0;
      switch (this.lastKey) {
        case UP:
          dir = -1;
          break;
        case DOWN:
          dir = 1;
          break;
        case PGUP:
          dir = -HC.listSize;
          break;
        case PGDOWN:
          dir = HC.listSize;
          break;
        case ESC:
          return evtKill(event);
      }
      if (dir) {
        if (this.list.style.display !== "none") {
          this.highlightSuggestion(dir);
          return evtKill(event);
        } else if (this.keyCount <= 1 && (!this.callbackObj || this.callbackObj.callsMade === this.callbackObj.nofCalls)) {
          this.textchange();
        }
      }
      return true;
    }
    highlightSuggestion(dir) {
      if (noSuggestions || !this.list || this.list.style.display === "none") {
        return false;
      }
      const curr = this.list.selectedIndex;
      let tgt = -1;
      if (dir === 0) {
        if (curr < 0 || curr >= this.list.options.length) {
          return false;
        }
        tgt = curr;
      } else {
        tgt = curr < 0 ? 0 : curr + dir;
        tgt = Math.max(tgt, 0);
        if (tgt >= this.list.options.length) {
          tgt = this.list.options.length - 1;
        }
      }
      if (tgt !== curr || dir === 0) {
        if (curr >= 0 && curr < this.list.options.length && dir !== 0) {
          this.list.options[curr].selected = false;
        }
        this.list.options[tgt].selected = true;
        const v = this.text.value.split("|");
        const key = v.length > 1 ? "|".concat(v[1]) : "";
        const completed = this.autoComplete(this.list.options[tgt].text, this.lastRealInput, null, key, false);
        if (!completed || this.list.options[tgt].text === this.lastRealInput) {
          this.text.value = this.list.options[tgt].text + key;
          if (this.canSelect()) {
            this.setSelection(this.list.options[tgt].text.length, this.list.options[tgt].text.length);
          }
        }
        this.lastInput = this.list.options[tgt].text;
        this.inputExists = true;
        if (this.icon) {
          this.icon.src = HC.existsYes;
        }
        this.state = CHANGE_PENDING;
      }
      return true;
    }
    resetKeySelection() {
      if (noSuggestions || !this.list || this.list.style.display === "none") {
        return false;
      }
      const curr = this.list.selectedIndex;
      if (curr >= 0 && curr < this.list.options.length) {
        this.list.options[curr].selected = false;
        const v = this.text.value.split("|");
        const key = v.length > 1 ? "|".concat(v[1]) : "";
        let result = v[0] !== this.lastInput;
        if (v[0] !== this.lastRealInput) {
          this.text.value = this.lastRealInput + key;
          result = true;
        }
        this.lastInput = this.lastRealInput;
        return result;
      }
      return false;
    }
  }
  const initialize = () => {
    const config = {};
    HC.dont_add_to_watchlist = window.hotcat_dont_add_to_watchlist === void 0 ? config.HotCatDontAddToWatchlist === void 0 ? HC.dont_add_to_watchlist : config.HotCatDontAddToWatchlist : !!window.hotcat_dont_add_to_watchlist;
    HC.no_autocommit = window.hotcat_no_autocommit === void 0 ? config.HotCatNoAutoCommit === void 0 ? conf.wgNamespaceNumber % 2 ? true : HC.no_autocommit : config.HotCatNoAutoCommit : !!window.hotcat_no_autocommit;
    HC.del_needs_diff = window.hotcat_del_needs_diff === void 0 ? config.HotCatDelNeedsDiff === void 0 ? HC.del_needs_diff : config.HotCatDelNeedsDiff : !!window.hotcat_del_needs_diff;
    HC.suggest_delay = window.hotcat_suggestion_delay || config.HotCatSuggestionDelay || HC.suggest_delay;
    HC.editbox_width = window.hotcat_editbox_width || config.HotCatEditBoxWidth || HC.editbox_width;
    HC.suggestions = window.hotcat_suggestions || config.HotCatSuggestions || HC.suggestions;
    if (typeof HC.suggestions !== "string" || !suggestionConfigs[HC.suggestions]) {
      HC.suggestions = "combined";
    }
    HC.fixed_search = window.hotcat_suggestions_fixed === void 0 ? config.HotCatFixedSuggestions === void 0 ? HC.fixed_search : config.HotCatFixedSuggestions : !!window.hotcat_suggestions_fixed;
    HC.single_minor = window.hotcat_single_changes_are_minor === void 0 ? config.HotCatMinorSingleChanges === void 0 ? HC.single_minor : config.HotCatMinorSingleChanges : !!window.hotcat_single_changes_are_minor;
    HC.bg_changed = window.hotcat_changed_background || config.HotCatChangedBackground || HC.bg_changed;
    HC.use_up_down = window.hotcat_use_category_links === void 0 ? config.HotCatUseCategoryLinks === void 0 ? HC.use_up_down : config.HotCatUseCategoryLinks : !!window.hotcat_use_category_links;
    HC.listSize = window.hotcat_list_size || config.HotCatListSize || HC.listSize;
    HC.changeTag = config.HotCatChangeTag || "";
    if (HC.changeTag) {
      const eForm = document.editform;
      const catRegExp = new RegExp("^\\[\\[(".concat(HC.category_regexp, "):"));
      let oldTxt;
      const isMinorChange = () => {
        let newTxt = eForm.wpTextbox1;
        if (!newTxt) {
          return;
        }
        newTxt = newTxt.value;
        const oldLines = oldTxt.match(/^.*$/gm);
        const newLines = newTxt.match(/^.*$/gm);
        let cArr;
        const except = (aArr, bArr) => {
          const result = [];
          let lArr;
          let sArr;
          if (aArr.length < bArr.length) {
            lArr = bArr;
            sArr = aArr;
          } else {
            lArr = aArr;
            sArr = bArr;
          }
          var _iterator4 = _createForOfIteratorHelper(lArr), _step4;
          try {
            for (_iterator4.s(); !(_step4 = _iterator4.n()).done; ) {
              const item = _step4.value;
              const ind = sArr.indexOf(item);
              if (ind === -1) {
                result[result.length] = item;
              } else {
                sArr.splice(ind, 1);
              }
            }
          } catch (err) {
            _iterator4.e(err);
          } finally {
            _iterator4.f();
          }
          return [...result, ...sArr];
        };
        cArr = except(oldLines, newLines);
        if (cArr.length > 0) {
          cArr = cArr.filter((c) => {
            c = c.trim();
            return c && !catRegExp.test(c);
          });
        }
        if (cArr.length === 0) {
          oldTxt = newTxt;
          return true;
        }
      };
      if (conf.wgAction === "submit" && conf.wgArticleId && eForm && eForm.wpSummary && document.querySelector("#wikiDiff")) {
        const sum = eForm.wpSummary;
        const sumA = eForm.wpAutoSummary;
        if (sum.value && sumA.value === HC.changeTag) {
          sumA.value = sumA.value.replace(HC.changeTag, "d41d8cd98f00b204e9800998ecf8427e");
          const $ct = $("<input>").attr({
            type: "hidden",
            name: "wpChangeTags"
          }).val(HC.changeTag);
          $(eForm).append($ct);
          oldTxt = eForm.wpTextbox1.value;
          const $body = $("body");
          $body.find("input[name=wpSave]").one("click", () => {
            if ($ct.val()) {
              sum.value = sum.value.replace(getMessage("messages-using") || getMessage("messages-prefix"), "");
            }
          });
          const removeChangeTag = () => {
            $(eForm.wpTextbox1).add(sum).one("input", () => {
              setTimeout(() => {
                if (isMinorChange()) {
                  removeChangeTag();
                } else {
                  $ct.val("");
                }
              }, 500);
            });
          };
          removeChangeTag();
        }
      }
    }
    HC.listSize = Number.parseInt(HC.listSize, 10);
    if (Number.isNaN(HC.listSize) || HC.listSize < 5) {
      HC.listSize = 5;
    }
    HC.listSize = Math.min(HC.listSize, 30);
    for (var _i6 = 0, _Object$entries = Object.entries(suggestionConfigs); _i6 < _Object$entries.length; _i6++) {
      const [key, suggestionConfig] = _Object$entries[_i6];
      try {
        if (key && getMessage("engine_names-".concat(key))) {
          suggestionConfig.name = getMessage("engine_names-".concat(key));
        }
      } catch (e) {
        continue;
      }
    }
    is_rtl = hasClass(document.querySelector("body"), "rtl");
    if (!is_rtl) {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        is_rtl = document.defaultView.getComputedStyle(document.querySelector("body"), null).getPropertyValue("direction");
      } else {
        is_rtl = $("body")[0].style.direction;
      }
      is_rtl = is_rtl === "rtl";
    }
  };
  const can_edit = () => {
    return document.querySelector("#ca-edit") !== null;
  };
  const closeForm = function() {
    for (var _i7 = 0, _editors6 = editors; _i7 < _editors6.length; _i7++) {
      const edit = _editors6[_i7];
      if (edit.state === OPEN) {
        edit.cancel();
      } else if (edit.state === CHANGE_PENDING) {
        edit.sanitizeInput();
        const value = edit.text.value.split("|");
        let key = null;
        if (value.length > 1) {
          [, key] = value;
        }
        const v = value[0].replace(/_/g, " ").trim();
        if (v.length === 0) {
          edit.cancel();
        } else {
          edit.currentCategory = v;
          edit.currentKey = key;
          edit.currentExists = this.inputExists;
          edit.close();
        }
      }
    }
  };
  const setup_upload = () => {
    onUpload = true;
    let ip = document.querySelector("#mw-htmlform-description") || document.querySelector("input[name=wpDestFile]");
    if (!ip) {
      ip = document.querySelector("input[name=wpDestFile]");
      while (ip && ip.nodeName.toLowerCase() !== "table") {
        ip = ip.parentNode;
      }
    }
    if (!ip) {
      return;
    }
    const reupload = document.querySelector("input[name=wpForReUpload]");
    const destFile = document.querySelector("input[name=wpDestFile]");
    if (reupload && !!reupload.value || destFile && (destFile.disabled || destFile.readOnly)) {
      return;
    }
    const labelCell = make("td");
    const lineCell = make("td");
    catLine = make("div");
    catLine.className = "catlinks";
    catLine.id = "catlinks";
    catLine.style.textAlign = is_rtl ? "right" : "left";
    catLine.style.margin = "0";
    catLine.style.border = "none";
    lineCell.append(catLine);
    const label = null;
    if (label) {
      labelCell.id = "hotcatLabelTranslated";
      labelCell.append(label);
    } else {
      labelCell.id = "hotcatLabel";
      labelCell.append(make(getMessage("categories"), true));
    }
    labelCell.className = "mw-label";
    labelCell.style.textAlign = "right";
    labelCell.style.verticalAlign = "middle";
    const form = document.querySelector("#upload") || document.querySelector("#mw-upload-form");
    if (form) {
      const newRow = ip.insertRow(-1);
      newRow.append(labelCell);
      newRow.append(lineCell);
      form.addEventListener("submit", (oldSubmit, ...args) => {
        return (() => {
          let do_submit = true;
          if (oldSubmit) {
            if (typeof oldSubmit === "string") {
              do_submit = window.eval(oldSubmit);
            } else if (oldSubmit instanceof Function) {
              do_submit = oldSubmit.apply(form, [oldSubmit, ...args]);
            }
          }
          if (!do_submit) {
            return false;
          }
          closeForm();
          const eb = document.querySelector("textarea[name=wpUploadDescription]") || document.querySelector("#wpDesc");
          let addedOne = false;
          for (var _i8 = 0, _editors7 = editors; _i8 < _editors7.length; _i8++) {
            const editor = _editors7[_i8];
            const t = editor.currentCategory;
            if (!t) {
              continue;
            }
            const key = editor.currentKey;
            const new_cat = "[[".concat(HC.category_canonical, ":").concat(t).concat(key ? "|".concat(key) : "", "]]");
            const nowikiRegex = new RegExp("<no".concat("wiki>", String.raw(_templateObject8 || (_templateObject8 = _taggedTemplateLiteral(["(s|S)*?</no"], ["(\\s|\\S)*?</no"]))), "wiki", ">"), "g");
            const _cleanedText = eb.value.replace(/<!--(\s|\S)*?-->/g, "").replace(nowikiRegex);
            if (!find_category(_cleanedText, t, true)) {
              eb.value += "\n".concat(new_cat);
              addedOne = true;
            }
          }
          if (addedOne) {
            const regex = new RegExp("{{$".concat("subst:").concat("unc}}"), "g");
            eb.value = eb.value.replace(regex, "");
          }
          return true;
        })(form.onsubmit);
      });
    }
  };
  let cleanedText = null;
  const isOnPage = ({
    firstChild
  }) => {
    if (firstChild.nodeType !== Node.ELEMENT_NODE) {
      return null;
    }
    let catTitle = title(firstChild.getAttribute("href"));
    if (!catTitle) {
      return null;
    }
    catTitle = catTitle.slice(catTitle.indexOf(":") + 1).replace(/_/g, " ");
    if (HC.blacklist && HC.blacklist.test(catTitle)) {
      return null;
    }
    const result = {
      title: catTitle,
      match: ["", "", ""]
    };
    if (pageText === null) {
      return result;
    }
    if (cleanedText === null) {
      const nowikiRegex = new RegExp("<no".concat("wiki>", String.raw(_templateObject9 || (_templateObject9 = _taggedTemplateLiteral(["(s|S)*?</no"], ["(\\s|\\S)*?</no"]))), "wiki", ">"), "g");
      cleanedText = pageText.replace(/<!--(\s|\S)*?-->/g, "").replace(nowikiRegex, "");
    }
    result.match = find_category(cleanedText, catTitle, true);
    return result;
  };
  let initialized = false;
  let setupTimeout = null;
  const findByClass = (scope, tag, className) => {
    const result = $(scope).find("".concat(tag, ".").concat(className));
    return result && result.length > 0 ? result[0] : null;
  };
  const setup = (additionalWork) => {
    if (initialized) {
      return;
    }
    initialized = true;
    if (setupTimeout) {
      window.clearTimeout(setupTimeout);
      setupTimeout = null;
    }
    catLine || (catLine = document.querySelector("#mw-normal-catlinks"));
    const hiddenCats = document.querySelector("#mw-hidden-catlinks");
    if (!catLine) {
      let footer = null;
      if (!hiddenCats) {
        footer = findByClass(document, "div", "printfooter");
        if (!footer) {
          return;
        }
      }
      catLine = make("div");
      catLine.id = "mw-normal-catlinks";
      catLine.style.textAlign = is_rtl ? "right" : "left";
      const label = make("a");
      label.href = conf.wgArticlePath.replace("$1", "Special:Categories");
      label.title = getMessage("categories");
      label.append(make(getMessage("categories"), true));
      catLine.append(label);
      catLine.append(make(":", true));
      let container = hiddenCats ? hiddenCats.parentNode : document.querySelector("#catlinks");
      if (!container) {
        container = make("div");
        container.id = "catlinks";
        footer.parentNode.insertBefore(container, footer.nextSibling);
      }
      container.className = "catlinks noprint";
      container.style.display = "";
      if (hiddenCats) {
        hiddenCats.before(catLine);
      } else {
        container.append(catLine);
      }
    }
    if (is_rtl) {
      catLine.dir = "rtl";
    }
    const createEditors = (line, is_hidden) => {
      let i;
      let cats = line.querySelectorAll("li");
      if (cats.length > 0) {
        newDOM = true;
        line = cats[0].parentNode;
      } else {
        cats = line.querySelectorAll("span");
      }
      const copyCats = Array.from({
        length: cats.length
      });
      for (i = 0; i < cats.length; i++) {
        copyCats[i] = cats[i];
      }
      for (i = 0; i < copyCats.length; i++) {
        const test = isOnPage(copyCats[i]);
        if (test !== null && test.match !== null && line) {
          new CategoryEditor(line, copyCats[i], test.title, test.match[2], is_hidden);
        }
      }
      return copyCats.length > 0 ? copyCats.at(-1) : null;
    };
    const lastSpan = createEditors(catLine, false);
    new CategoryEditor(newDOM ? catLine.querySelectorAll("ul")[0] : catLine, null, null, lastSpan !== null, false);
    if (!onUpload) {
      if (pageText !== null && hiddenCats) {
        if (is_rtl) {
          hiddenCats.dir = "rtl";
        }
        createEditors(hiddenCats, true);
      }
      const enableMulti = make("span");
      enableMulti.className = "noprint";
      if (is_rtl) {
        enableMulti.dir = "rtl";
      }
      catLine.insertBefore(enableMulti, catLine.firstChild.nextSibling);
      enableMulti.append(make(" ", true));
      multiSpan = make("span");
      enableMulti.append(multiSpan);
      multiSpan.innerHTML = "(<a>".concat(HC.addmulti, "</a>)");
      const [link] = multiSpan.querySelectorAll("a");
      link.addEventListener("click", (event) => {
        setMultiInput();
        checkMultiInput();
        return evtKill(event);
      });
      link.title = getMessage("multi_tooltip");
      link.style.cursor = "pointer";
    }
    cleanedText = null;
    if (additionalWork instanceof Function) {
      additionalWork();
    }
    mw.hook("hotcat.ready").fire();
    $("body").trigger("hotcatSetupCompleted");
  };
  const createCommitForm = () => {
    if (commitForm) {
      return;
    }
    const formContainer = make("div");
    formContainer.style.display = "none";
    document.querySelector("body").append(formContainer);
    formContainer.innerHTML = '<form id="hotcatCommitForm" method="post" enctype="multipart/form-data" action="'.concat(conf.wgScript, "?title=").concat(encodeURIComponent(conf.wgPageName), '&action=submit">\n		<input type="hidden" name="wpTextbox1">\n		<input type="hidden" name="model" value="').concat(conf.wgPageContentModel, '">\n		<input type="hidden" name="format" value="text/x-wiki">\n		<input type="hidden" name="wpSummary" value="">\n		<input type="checkbox" name="wpMinoredit" title="wpMinoredit" value="1">\n		<input type="checkbox" name="wpWatchthis" title="wpWatchthis" value="1">\n		<input type="hidden" name="wpAutoSummary" value="d41d8cd98f00b204e9800998ecf8427e">\n		<input type="hidden" name="wpEdittime">\n		<input type="hidden" name="wpStarttime">\n		<input type="hidden" name="wpDiff" value="wpDiff">\n		<input type="hidden" name="oldid" value="0">\n		<input type="submit" name="hcCommit" value="hcCommit">\n		<input type="hidden" name="wpEditToken">\n		<input type="hidden" name="wpUltimateParam" value="1">\n		<input type="hidden" name="wpChangeTags">\n		<input type="hidden" value="ℳ𝒲♥𝓊𝓃𝒾𝒸ℴ𝒹ℯ" name="wpUnicodeCheck">\n		</form>');
    commitForm = document.querySelector("#hotcatCommitForm");
  };
  const getPage = () => {
    if (conf.wgArticleId) {
      const params = {
        action: "query",
        format: "json",
        formatversion: "2",
        rawcontinue: "",
        titles: conf.wgPageName,
        prop: ["info", "revisions"],
        rvprop: ["content", "timestamp", "ids"],
        rvlimit: "1",
        rvstartid: conf.wgCurRevisionId,
        rvslots: "main",
        meta: ["siteinfo"]
      };
      HC.start = (data) => {
        setPage(data);
        setup(createCommitForm);
      };
      api.get(params).then((data) => {
        HC.start(data);
      });
      setupTimeout = setTimeout(() => {
        setup(createCommitForm);
      }, 4e3);
    } else {
      if (conf.wgNamespaceNumber === 2) {
        return;
      }
      pageText = "";
      pageTime = null;
      setup(createCommitForm);
    }
  };
  const setState = (state) => {
    const cats = state.split("\n");
    if (cats.length === 0) {
      return null;
    }
    if (initialized && editors.length === 1 && editors[0].isAddCategory) {
      const newSpans = [];
      const before = editors.length === 1 ? editors[0].span : null;
      let i;
      for (i = 0; i < cats.length; i++) {
        if (cats[i].length === 0) {
          continue;
        }
        let cat = cats[i].split("|");
        const key = cat.length > 1 ? cat[1] : null;
        [cat] = cat;
        const link = make("a");
        link.href = wikiPagePath("".concat(HC.category_canonical, ":").concat(cat));
        link.append(make(cat, true));
        link.title = cat;
        const span = make("span");
        span.append(link);
        if (!i) {
          catLine.insertBefore(make(" ", true), before);
        }
        before.before(span);
        if (before && i + 1 < cats.length) {
          parent.insertBefore(make(" | ", true), before);
        }
        newSpans[newSpans.length] = {
          element: span,
          title: cat,
          key
        };
      }
      if (before) {
        before.parentNode.insertBefore(make(" | ", true), before);
      }
      for (i = 0; i < newSpans.length; i++) {
        new CategoryEditor(catLine, newSpans[i].element, newSpans[i].title, newSpans[i].key);
      }
    }
    return null;
  };
  const getState = () => {
    let result = null;
    for (var _i9 = 0, _editors8 = editors; _i9 < _editors8.length; _i9++) {
      const editor = _editors8[_i9];
      let text = editor.currentCategory;
      const key = editor.currentKey;
      if (text && text.length > 0) {
        if (key !== null) {
          text += "|".concat(key);
        }
        if (result === null) {
          result = text;
        } else {
          result += "\n".concat(text);
        }
      }
    }
    return result;
  };
  const really_run = () => {
    initialize();
    if (!HC.upload_disabled && conf.wgNamespaceNumber === -1 && conf.wgCanonicalSpecialPageName === "Upload" && conf.wgUserName) {
      setup_upload();
      setup(() => {
        if (window.UploadForm && UploadForm.previous_hotcat_state) {
          UploadForm.previous_hotcat_state = setState(UploadForm.previous_hotcat_state);
        }
      });
    } else {
      if (!conf.wgIsArticle || conf.wgAction !== "view" || param("diff") !== null || param("oldid") !== null || !can_edit() || HC.disable()) {
        return;
      }
      getPage();
    }
  };
  const run = () => {
    if (HC.started) {
      return;
    }
    HC.started = true;
    really_run();
  };
  window.hotcat_get_state = () => {
    return getState();
  };
  window.hotcat_set_state = (state) => {
    return setState(state);
  };
  window.hotcat_close_form = () => {
    closeForm();
  };
  HC.runWhenReady = (callback) => {
    mw.hook("hotcat.ready").add(callback);
  };
  if (conf.wgCanonicalSpecialPageName !== "Upload") {
    mw.hook("postEdit").add(() => {
      if (document.querySelector("#catlinks .hotcatlink")) {
        return;
      }
      catLine = null;
      editors = [];
      initialized = false;
      HC.started = false;
      run();
    });
  }
  $(run);
})();

})();

/* </nowiki> */