跳转到内容

User:Royal Sailor/JS/HKCATool.js

维基百科,自由的百科全书
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google ChromeFirefoxMicrosoft EdgeSafari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
/**
 * 修改自:https://zh.wikipedia.org/wiki/User:SuperGrey/gadgets/ACGATool/main.js
 * 原作者:https://zh.wikipedia.org/wiki/User:SuperGrey
 * 原著作权标识:https://opensource.org/licenses/MIT MIT
 */

(function () {
    const HKCATool = {
        /**
         * 維基香港內容獎提名規則。
         * @returns {object} 包含規則組的對象。
         */
        NominationRules: function () {
            return [
                {
                    group: HKCATool.convByVar({
                        hant: '(1) 內容擴充',
                        hans: '(1) 内容扩充',
                    }),
                    rules: [
                        {
                            rule: '1a',
                            desc: HKCATool.convByVar({
                                hant: '3000+',
                                hans: '3000+',
                            }),
                            score: 1,
                        },
                        {
                            rule: '1b',
                            desc: HKCATool.convByVar({
                                hant: '4750+',
                                hans: '4750+',
                            }),
                            score: 2,
                        },
                        {
                            rule: '1c',
                            desc: HKCATool.convByVar({
                                hant: '6500+',
                                hans: '6500+',
                            }),
                            score: 3,
                        },
						{
                            rule: '1d',
                            desc: HKCATool.convByVar({
                                hant: '8250+',
                                hans: '8250+',
                            }),
                            score: 4,
                        },
						{
                            rule: '1e',
                            desc: HKCATool.convByVar({
                                hant: '1萬+',
                                hans: '1万+',
                            }),
                            score: 5,
                        },
                    ],
                    
                },
                {
                    group: HKCATool.convByVar({
                        hant: '(2) 額外加分',
                        hans: '(2) 额外加分',
                    }),
                    rules: [
                        {
                            rule: '2-itn',
                            desc: HKCATool.convByVar({
                                hant: '新聞動態',
                                hans: '新闻动态',
                            }),
                            score: 1,
                        },
                        {
                            rule: '2-dyk',
                            desc: HKCATool.convByVar({
                                hant: 'DYK',
                                hans: 'DYK',
                            }),
                            score: 1,
                        },
                        {
                            rule: '2-ga',
                            desc: HKCATool.convByVar({
                                hant: '優良',
                                hans: '优良',
                            }),
                            score: 5,
                        },
                        {
                            rule: '2-fa',
                            desc: HKCATool.convByVar({
                                hant: '典特',
                                hans: '典特',
                            }),
                            score: 10,
                        },
                    ],
                },
            ];
        },

        /**
         * 提名規則別名。
         * @returns {object} 包含別名的對象。
         */
        NominationRuleAliases: function () {
            return {
                '2a': '2-itn',
                '2b': '2-dyk',
				'2c': '2-ga',
                'ga': '2-ga',
                '2d': '2-fa',
                'fa': '2-fa',
                'fl': '2-fa',
            };
        },

        /**
         * 提名規則集合。
         * @returns {object} 包含規則名稱和規則對象的對象。
         */
        NominationRuleSet: function () {
            let ruleNames = [];
            let ruleDict = {};
            for (let ruleGroup of HKCATool.NominationRules()) {
                if (ruleGroup.rules) {
                    for (let ruleSet of ruleGroup.rules) {
                        ruleNames.push(ruleSet.rule);
                        ruleDict[ruleSet.rule] = ruleSet;
                    }
                } else if (ruleGroup.tabs) {
                    for (let tab of ruleGroup.tabs) {
                        for (let ruleSet of tab.rules) {
                            ruleNames.push(ruleSet.rule);
                            ruleDict[ruleSet.rule] = ruleSet;
                        }
                    }
                }
            }
            return {
                ruleNames: ruleNames,
                ruleDict: ruleDict,
            };
        },

        /**
         * 獲取排序後的規則狀態。
         * @param ruleNames 即 NominationRuleSet().ruleNames。
         * @param ruleStatus 規則狀態對象。
         * @returns {Array} 包含排序後的規則狀態對象的數組。
         */
        getOrderedRuleStatus: function (ruleNames, ruleStatus) {
            return ruleNames.filter(rule => ruleStatus.hasOwnProperty(rule)).map(rule => ({rule, ...ruleStatus[rule]}));
        },

        /**
         * 獲取完整的wikitext。
         * @param {string} pageName 頁面名稱,默認為「Wikipedia:香港維基人佈告板/維基香港內容獎/登記處」。
         * @returns {Promise<string>} 包含完整wikitext的Promise。
         */
        getFullText: async function (pageName) {
            let response = await HKCATool.api.get({
                action: 'query',
                titles: pageName || 'Wikipedia:香港維基人佈告板/維基香港內容獎/登記處',
                prop: 'revisions',
                rvslots: '*',
                rvprop: 'content',
                indexpageids: 1,
            });
            return response.query.pages[response.query.pageids[0]].revisions[0].slots.main['*'];
        },

        /**
         * Trims a token’s text and adjusts its absolute boundaries (skipping leading/trailing whitespace).
         * @param {object} token An object with properties { text, start, end }.
         * @returns {object} An object with properties { text, start, end } after trimming.
         */
        trimToken: function (token) {
            const leadingMatch = token.text.match(/^\s*/);
            const trailingMatch = token.text.match(/\s*$/);
            const leading = leadingMatch ? leadingMatch[0].length : 0;
            const trailing = trailingMatch ? trailingMatch[0].length : 0;
            return {
                text: token.text.trim(),
                start: token.start + leading,
                end: token.end - trailing,
            };
        },

        /**
         * Splits the inner content of a template into tokens by detecting top-level "\n|" delimiters.
         * It scans the inner text (tracking nested braces) and splits only when it sees a newline followed by "|".
         * @param {string} innerContent The content between the outer "{{" and "}}".
         * @param {number} offset The absolute start position of innerContent in the wikitext.
         * @returns {Array} An array of tokens; each token is an object { text, start, end }.
         */
        splitParameters: function (innerContent, offset) {
            let tokens = [];
            let lastIndex = 0;
            let braceCount = 0;
            let i = 0;
            while (i < innerContent.length) {
                if (innerContent.substr(i, 2) === '{{') {
                    braceCount++;
                    i += 2;
                    continue;
                }
                if (innerContent.substr(i, 2) === '}}') {
                    braceCount = Math.max(braceCount - 1, 0);
                    i += 2;
                    continue;
                }
                // At top level, if we see a newline followed by a pipe, split here.
                if (braceCount === 0 && innerContent[i] === '\n' && innerContent[i + 1] === '|') {
                    tokens.push({
                        text: innerContent.slice(lastIndex, i),
                        start: offset + lastIndex,
                        end: offset + i,
                    });
                    i += 2; // skip the "\n|"
                    lastIndex = i;
                    continue;
                }
                i++;
            }
            tokens.push({
                text: innerContent.slice(lastIndex),
                start: offset + lastIndex,
                end: offset + innerContent.length,
            });
            return tokens;
        },

        /**
         * Finds the matching closing braces "}}" for a template that starts at the given index.
         * @param {string} text The full wikitext.
         * @param {number} start The starting index where "{{" is found.
         * @returns {object} An object { endIndex } where endIndex is the index immediately after the closing "}}".
         */
        findTemplateEnd: function (text, start) {
            let braceCount = 0;
            let i = start;
            while (i < text.length) {
                if (text.substr(i, 2) === '{{') {
                    braceCount++;
                    i += 2;
                    continue;
                }
                if (text.substr(i, 2) === '}}') {
                    braceCount--;
                    i += 2;
                    if (braceCount === 0) break;
                    continue;
                }
                i++;
            }
            return {endIndex: i};
        },

        /**
         * Parses a template starting at the given index in the wikitext.
         * For simpler {{HKCA Apply}} templates, parameters are parsed as key=value pairs. The keys are expected to have a trailing number (e.g. 條目名稱1, 用戶名稱1, etc.); entries are grouped by that number.
         * @param {string} text The full wikitext.
         * @param {number} start The starting index of the template (expects "{{").
         * @returns {object} An object { template, endIndex }.
         */
        parseTemplate: function (text, start) {
            const templateStart = start;
            const {endIndex: templateEnd} = HKCATool.findTemplateEnd(text, start);
            // Extract inner content (between outer "{{" and "}}").
            const innerStart = start + 2;
            const innerEnd = templateEnd - 2;
            const innerContent = text.slice(innerStart, innerEnd);
            // Split the inner content into tokens using the top-level "\n|" delimiter.
            const tokens = HKCATool.splitParameters(innerContent, innerStart);
            // The first token is the template name.
            let nameToken = HKCATool.trimToken(tokens[0]);
            let templateObj = {
                name: nameToken.text,
                nameLocation: {
                    start: nameToken.start,
                    end: nameToken.end,
                },
                params: {},
                location: {
                    start: templateStart,
                    end: templateEnd,
                },
            };

            if (templateObj.name.startsWith("HKCA Apply")) {
                // For HKCA Apply, process tokens as key=value pairs.
                // Group parameters by their trailing number.
                let kvGroups = {};
                for (let j = 1; j < tokens.length; j++) {
                    let token = tokens[j];
                    let tokenTrim = HKCATool.trimToken(token);
                    if (tokenTrim.text === "") continue;
                    const eqIndex = tokenTrim.text.indexOf('=');
                    if (eqIndex === -1) continue;
                    let rawKey = tokenTrim.text.substring(0, eqIndex);
                    let rawValue = tokenTrim.text.substring(eqIndex + 1);
                    let keyText = rawKey.trim();
                    let valueText = rawValue.trim();
                    let keyLeading = rawKey.match(/^\s*/)[0].length;
                    let keyLocation = {
                        start: tokenTrim.start + keyLeading,
                        end: tokenTrim.start + keyLeading + keyText.length,
                    };
                    let valueLeading = rawValue.match(/^\s*/)[0].length;
                    let valueLocation = {
                        start: tokenTrim.start + eqIndex + 1 + valueLeading,
                        end: tokenTrim.end,
                    };
                    // Expect keys in the form: prefix + number, e.g. "條目名稱1", "用戶名稱1", etc.
                    let m = keyText.match(/^(.+?)(\d+)$/);
                    if (m) {
                        let prefix = m[1].trim(); // e.g. "條目名稱"
                        let num = parseInt(m[2], 10);
                        if (!kvGroups[num]) kvGroups[num] = {};
                        kvGroups[num][prefix] = {
                            value: valueText,
                            keyLocation: keyLocation,
                            valueLocation: valueLocation,
                            fullLocation: {
                                start: token.start,
                                end: token.end,
                            },
                        };
                    } else {
                        // If the key doesn't match the expected pattern, store it under group "0".
                        if (!kvGroups["0"]) kvGroups["0"] = {};
                        kvGroups["0"][keyText] = {
                            value: valueText,
                            keyLocation: keyLocation,
                            valueLocation: valueLocation,
                            fullLocation: {
                                start: token.start,
                                end: token.end,
                            },
                        };
                    }
                }
                let entries = [];
                let groupNums = Object.keys(kvGroups).filter(k => k !== "0").map(Number).sort((a, b) => a - b);
                for (let num of groupNums) {
                    let group = kvGroups[num];
                    let allTokens = Object.values(group);
                    let startPos = Math.min(...allTokens.map(t => t.fullLocation.start));
                    let endPos = Math.max(...allTokens.map(t => t.fullLocation.end));
                    group.fullLocation = {
                        start: startPos,
                        end: endPos,
                    };
                    entries.push(group);
                }
                templateObj.entries = entries;
            }
            return {
                template: templateObj,
                endIndex: templateEnd,
            };
        },

        /**
         * Returns an array of date sections from the full wikitext.
         * Each section is determined by h3 headings of the form "=== date ===".
         * @param {string} text The full wikitext.
         * @returns {Array} Array of sections: { date, start, end }.
         */
        getDateSections: function (text) {
            const regex = /^===\s*(.+?)\s*===/gm;
            let sections = [];
            let matches = [];
            let match;
            while ((match = regex.exec(text)) !== null) {
                matches.push({
                    date: match[1].trim(),
                    start: match.index,
                    end: regex.lastIndex,
                });
            }
            for (let i = 0; i < matches.length; i++) {
                const sectionStart = matches[i].start;
                const sectionDate = matches[i].date;
                const sectionEnd = (i < matches.length - 1) ? matches[i + 1].start : text.length;
                sections.push({
                    date: sectionDate,
                    start: sectionStart,
                    end: sectionEnd,
                });
            }
            return sections;
        },

        /**
         * In a given date section (from h3 heading to before the next h3), collects all entries.
         * Each entry is an entry from {{HKCA Apply}}.
         * @param {string} text The full wikitext.
         * @param {object} section A section object { date, start, end }.
         * @returns {Array} Array of entry objects: { template, start, end, type }.
         */
        collectEntriesInSection: function (text, section) {
            let entries = [];
            const sectionText = text.slice(section.start, section.end);
            // Regex: match {{HKCA Apply
            const regex = /{{(?:HKCA Apply)/g;
            let match;
            while ((match = regex.exec(sectionText)) !== null) {
                let absolutePos = section.start + match.index;
                let {
                    template,
                    endIndex,
                } = HKCATool.parseTemplate(text, absolutePos);
                if (template.name.startsWith("HKCA Apply")) {
                    // For HKCA Apply, add each grouped entry.
                    if (template.entries) {
                        for (let entry of template.entries) {
                            entries.push({
                                template: entry, // entry holds the grouped key-value parameters
                                start: entry.fullLocation.start,
                                end: entry.fullLocation.end,
                                type: 'hkca1',
                            });
                        }
                    }
                }
                // Advance regex.lastIndex to avoid reparsing inside this template.
                regex.lastIndex = endIndex - section.start;
            }
            // Sort entries in order of appearance.
            entries.sort((a, b) => a.start - b.start);
            return entries;
        },

        /**
         * Queries the full wikitext for an entry given a specific date (from the h3 heading)
         * and an entry index (1-based, counting all entries in order).
         * Returns the entry object including its location.
         * @param {string} text The full wikitext.
         * @param {string} date The date string (e.g. "2月3日").
         * @param {number} index The 1-based index of the entry under that date.
         * @returns {object|null} The entry object { template, start, end, type } or null if not found.
         */
        queryEntry: function (text, date, index) {
            const sections = HKCATool.getDateSections(text);
            const targetSection = sections.find(sec => sec.date === date);
            if (!targetSection) return null;
            const entries = HKCATool.collectEntriesInSection(text, targetSection);
            if (index < 1 || index > entries.length) return null;
            return entries[index - 1]; // 1-based index
        },

        /**
         * Given an entry (from queryEntry) and a set of changes (mapping parameter key to new value),
         * updates only those parameter values in the original wikitext by using the precise location data.
         * This function does not replace the entire entry substring—only the changed parameter values.
         *
         * For main or extra entries, changes should be provided as an object where keys are parameter names
         * (e.g. "條目名稱") and values are the new text.
         *
         * For HKCA Apply entries, use keys like "條目名稱", "用戶名稱", "提名理由", "核對用", etc.
         *
         * @param {string} original The full original wikitext.
         * @param {object} entry The entry object (from queryEntry).
         * @param {object} changes An object mapping parameter keys to new values.
         * @returns {string} The updated wikitext.
         */
        updateEntryParameters: function (original, entry, changes) {
            let mods = [];
            if (entry.type === 'main' || entry.type === 'extra') {
                let params = entry.template.params;
                for (let key in changes) {
                    if (params[key] && params[key].valueLocation) {
                        mods.push({
                            start: params[key].valueLocation.start,
                            end: params[key].valueLocation.end,
                            replacement: changes[key],
                        });
                    }
                }
            } else if (entry.type === 'hkca1') {
                // For HKCA Apply grouped entry, keys are like "條目名稱1", "用戶名稱1", etc.
                for (let key in changes) {
                    if (entry.template[key]) {
                        let token = entry.template[key];
                        // Use token.valueLocation for precise updating.
                        mods.push({
                            start: token.valueLocation.start,
                            end: token.valueLocation.end,
                            replacement: changes[key],
                        });
                    }
                }
            }
            // Apply modifications in descending order of start position.
            mods.sort((a, b) => b.start - a.start);
            let updated = original;
            for (let mod of mods) {
                updated = updated.slice(0, mod.start) + mod.replacement + updated.slice(mod.end);
            }
            return updated;
        },

        /**
         * Removes comments from the given text.
         * @param text {string} The text to remove comments from.
         * @returns {string} The text without comments.
         */
        removeComments: function (text) {
            text = text.replace(/<!--.*?-->/gs, '');
            return text.trim();
        },

        /**
         * 根據用戶的提名理由,解析出規則狀態。
         * @param reason {string} 用戶的提名理由。
         * @returns {object} 規則狀態。
         */
        parseUserReason: function (reason) {
            reason = HKCATool.removeComments(reason);
            let ruleStatus = {};
            if (reason.startsWith('{{HKCA Apply/request|ver=2|')) {
                reason = reason.slice('{{HKCA Apply/request|ver=2|'.length, -2);
            }
            let reasonList = reason.split(/\s+/);

            let ruleSet = HKCATool.NominationRuleSet();
            let ruleNames = ruleSet.ruleNames;
            let ruleAliases = HKCATool.NominationRuleAliases();
            for (let rule of reasonList) {
                if (rule.endsWith('?')) {
                    // 最後一個字符是?,可直接去掉?
                    rule = rule.slice(0, -1);
                }
                // 名稱和分數的自定義
                let ruleNameMod = '',
                    ruleScoreMod = '';
                while (rule.endsWith(')') || rule.endsWith(']')) {
                    if (rule.endsWith(')')) {
                        let lastLeftParen = rule.lastIndexOf('(');
                        if (lastLeftParen === -1) break;
                        ruleNameMod = rule.slice(lastLeftParen + 1, -1);
                        rule = rule.slice(0, lastLeftParen);
                    }
                    if (rule.endsWith(']')) {
                        let lastLeftBracket = rule.lastIndexOf('[');
                        if (lastLeftBracket === -1) break;
                        ruleScoreMod = rule.slice(lastLeftBracket + 1, -1);
                        rule = rule.slice(0, lastLeftBracket);
                    }
                }
                if (rule === '') continue;
                if (ruleAliases[rule]) {  // 是否是別名
                    rule = ruleAliases[rule];
                }
                if (ruleNames.includes(rule)) {  // 是否在提名規則中
                    ruleStatus[rule] = {selected: true};
                    if (ruleNameMod !== '') {
                        ruleStatus[rule].desc = ruleNameMod;
                    }
                    if (ruleScoreMod !== '') {
                        ruleStatus[rule].score = parseFloat(ruleScoreMod);
                        if (isNaN(ruleStatus[rule].score)) {
                            console.log('[HKCATool] 分數不是數字', ruleScoreMod);  // 分數不是數字,報錯
                            return null;
                        }
                    }
                } else {
                    console.log('[HKCATool] 不在提名規則中', rule);  // 不在提名規則中,報錯
                    return null;
                }
            }
            return ruleStatus;
        },

        /**
         * 將queried(查詢結果)轉換為nomData(提名數據)。
         * @param queried {object} 查詢結果。
         * @returns {object} 提名數據。
         */
        queried2NomData: function (queried) {
            let nomData = {};

            if (queried.type === 'main' || queried.type === 'extra') {
                let params = queried.template.params;
                nomData.pageName = params['條目名稱'].value;
                nomData.awarder = params['用戶名稱'].value;
                let reasonWikitext = params['提名理由'].value;
                nomData.ruleStatus = HKCATool.parseUserReason(reasonWikitext);
                if (nomData.ruleStatus == null) {
                    return null;
                }
                return nomData;
            } else if (queried.type === 'hkca1') {
                let params = queried.template;
                nomData.pageName = HKCATool.removeComments(params['條目名稱'].value);
                nomData.awarder = HKCATool.removeComments(params['用戶名稱'].value);
                let reasonWikitext = params['提名理由'].value;
                nomData.ruleStatus = HKCATool.parseUserReason(reasonWikitext);
                if (nomData.ruleStatus == null) {
                    return null;
                }
                return nomData;
            } else {
                return null;
            }
        },

        /**
         * 點擊編輯按鈕時的事件處理。
         * @param date 日期(章節標題)
         * @param index 該章節下第X個提名
         */
        editNomination: function (date, index) {
            HKCATool.getFullText().then(function (fulltext) {
                HKCATool.queried = HKCATool.queryEntry(fulltext, date, index);
                let nomData = HKCATool.queried2NomData(HKCATool.queried);
                if (nomData == null) {
                    mw.notify(HKCATool.convByVar({
                        hant: '小工具無法讀取該提名,請手動編輯。',
                        hans: '小工具无法读取该提名,请手动编辑。',
                    }), {
                        type: 'error',
                        title: HKCATool.convByVar({
                            hant: '錯誤',
                            hans: '错误',
                        }),
                    });
                } else {
                    HKCATool.showEditNominationDialog(nomData);
                }
            });
        },

        /**
         * 點擊核對按鈕時的事件處理。
         * @param date 日期(章節標題)
         * @param index 該章節下第X個提名
         * @param multiCheckStatus 多選核對狀態字串
         * @return {Promise} 返回一個Promise,當核對完成時解析。
         */
        checkNomination: async function (date, index, multiCheckStatus) {
            const fulltext = await HKCATool.getFullText();
            HKCATool.queried = HKCATool.queryEntry(fulltext, date, index);
            const nomData = HKCATool.queried2NomData(HKCATool.queried);
            if (nomData == null) {
                mw.notify(HKCATool.convByVar({
                    hant: '小工具無法讀取該提名,請手動編輯。',
                    hans: '小工具无法读取该提名,请手动编辑。',
                }), {
                    type: 'error',
                    title: HKCATool.convByVar({
                        hant: '錯誤',
                        hans: '错误',
                    }),
                });
                return;
            }
            await HKCATool.showCheckNominationDialog(nomData, multiCheckStatus);
        },

        /**
         * 批量核對提名的事件處理。
         * 遍歷所有帶有 multi-nomCheck 類別的元素,為每個元素添加一個多選核對的複選框和按鈕。
         * 當按下「開始大量核對」按鈕時,會遍歷所有選中的複選框,並對每個選中的提名進行核對。
         * 如果沒有選中任何複選框,則顯示警告通知。
         * 當核對完成後,如果有任何提名被修改,則刷新頁面。
         */
        multiCheckNomination: function () {
            $('.multi-nomCheck').each(function () {
                let $this = $(this);
                let checkbox = $('<input type="checkbox" class="multi-nomCheck-checkbox">');
                checkbox.data('date', $this.data('date'));
                checkbox.data('index', $this.data('index'));
                checkbox.on('change', function () {
                    if ($(this).is(':checked')) {
                        // Change the background color of ancestor <td> to light green.
                        $this.closest('td').css('background-color', '#d4edda');
                    } else {
                        // Reset the background color of ancestor <td> to default.
                        $this.closest('td').css('background-color', '');
                    }
                });
                let startCheckingButton = $('<a>').text(HKCATool.convByVar({
                    hant: '開始批次核對',
                    hans: '开始批量核对',
                })).addClass('multi-nomCheck-startBtn').attr('href', '#');
                startCheckingButton.on('click', async function (e) {
                    e.preventDefault();
                    const $checkboxes = $('.multi-nomCheck-checkbox:checked');
                    if (!$checkboxes.length) {
                        mw.notify(HKCATool.convByVar({
                            hant: '請至少選擇一個提名進行核對。',
                            hans: '请至少选择一个提名进行核对。',
                        }), {
                            type: 'warning',
                            title: HKCATool.convByVar({
                                hant: '提示',
                                hans: '提示',
                            }),
                        });
                        return;
                    }
                    HKCATool.multiNomCheckOngoing = true;
                    HKCATool.multiNomCheckChanged = false;
                    for (let i = 0; i < $checkboxes.length; i++) {
                        const $checkbox = $($checkboxes[i]);
                        const date = $checkbox.data('date');
                        const index = $checkbox.data('index');
                        const status = `${i + 1}/${$checkboxes.length}`;
                        await HKCATool.checkNomination(date, index, status);
                    }
                    HKCATool.multiNomCheckOngoing = false;
                    if (HKCATool.multiNomCheckChanged) {
                        HKCATool.refreshPage();
                    }
                });
                $this.empty(); // 清空之前的內容
                $this.append(checkbox);
                $this.append(' ');
                $this.append(startCheckingButton);
            });
        },

        /**
         * 點擊登記新提名按鈕時的事件處理。
         */
        newNomination: function () {
            HKCATool.showNewNominationDialog();
        },

        /**
         * 點擊歸檔按鈕時的事件處理。
         */
        archiveChapter: function (date) {
            OO.ui.confirm(HKCATool.convByVar({
                hant: '確定要歸檔「',
                hans: '确定要归档「',
            }) + date + HKCATool.convByVar({
                hant: '」章節嗎?',
                hans: '」章节吗?',
            })).done(function (confirmed) {
                if (confirmed) {
                    HKCATool.getFullText().then(function (fulltext) {
                        let sections = HKCATool.getDateSections(fulltext);
                        let targetSection = sections.find(sec => sec.date === date);
                        if (!targetSection) {
                            mw.notify(HKCATool.convByVar({
                                hant: '小工具無法讀取該章節,請手動歸檔。',
                                hans: '小工具无法读取该章节,请手动归档。',
                            }), {
                                type: 'error',
                                title: HKCATool.convByVar({
                                    hant: '錯誤',
                                    hans: '错误',
                                }),
                            });
                            return;
                        }
                        let sectionText = fulltext.slice(targetSection.start, targetSection.end);
                        let fulltextWithoutSection = fulltext.slice(0, targetSection.start) + fulltext.slice(targetSection.end);

                        // 找到包含年份的UTC字串,例如 2025年2月13日 (四) 20:58 (UTC)
                        let utcRegex = /提名人:.+?(\d{4})年(\d{1,2})月(\d{1,2})日 \((.*?)\) (\d{1,2}:\d{2}) \(UTC\)/;
                        let utcMatch = sectionText.match(utcRegex);
                        if (!utcMatch) {
                            mw.notify(HKCATool.convByVar({
                                hant: '小工具無法讀取該章節的UTC時間,請手動歸檔。',
                                hans: '小工具无法读取该章节的UTC时间,请手动归档。',
                            }), {
                                type: 'error',
                                title: HKCATool.convByVar({
                                    hant: '錯誤',
                                    hans: '错误',
                                }),
                            });
                            return;
                        }
                        // 獲得 X年Y月
                        let yearMonth = utcMatch[1] + '年' + utcMatch[2] + '月';
                        let archiveTarget = 'Wikipedia:香港維基人佈告板/維基香港內容獎/存檔/' + yearMonth;

                        mw.notify(HKCATool.convByVar({
                            hant: '小工具正在歸檔中,請耐心等待。',
                            hans: '小工具正在归档中,请耐心等待。',
                        }), {
                            type: 'info',
                            title: HKCATool.convByVar({
                                hant: '提示',
                                hans: '提示',
                            }),
                            autoHide: false,
                        });

                        // 先檢查新的存檔頁面是否存在
                        HKCATool.api.get({
                            action: 'query',
                            titles: archiveTarget,
                            prop: 'revisions',
                            rvslots: '*',
                            rvprop: 'content',
                            indexpageids: 1,
                        }).done(function (data) {
                            if (data.query.pageids[0] && (data.query.pages[data.query.pageids[0]].missing !== undefined || data.query.pages[data.query.pageids[0]].revisions[0].slots.main['*'].trim() === '')) {
                                // 新的存檔頁面不存在或者是空的
                                // 將存檔頁頭加入 sectionText
                                sectionText = '{{Talk archive|Wikipedia:香港維基人佈告板/維基香港內容獎/登記處}}\n\n' + sectionText;
                            } else {
                                // 直接歸檔,補充空行
                                sectionText = '\n\n' + sectionText;
                            }
                            HKCATool.api.postWithToken('csrf', {
                                action: 'edit',
                                title: archiveTarget,
                                appendtext: sectionText,
                                summary: '[[User:Royal Sailor/JS/HKCATool.js|' + HKCATool.convByVar({
                                    hant: '歸檔',
                                    hans: '归档',
                                }) + ']]「' + date + '」' + HKCATool.convByVar({
                                    hant: '章節',
                                    hans: '章节',
                                }),
                            }).done(function () {
                                HKCATool.api.postWithToken('csrf', {
                                    action: 'edit',
                                    title: 'Wikipedia:香港維基人佈告板/維基香港內容獎/登記處',
                                    text: fulltextWithoutSection,
                                    summary: '[[User:Royal Sailor/JS/HKCATool.js|' + HKCATool.convByVar({
                                        hant: '歸檔',
                                        hans: '归档',
                                    }) + ']]「' + date + '」' + HKCATool.convByVar({
                                        hant: '章節至',
                                        hans: '章节至',
                                    }) + '「[[' + archiveTarget + ']]」',
                                }).done(function () {
                                    mw.notify(HKCATool.convByVar({
                                        hant: '小工具已歸檔',
                                        hans: '小工具已归档',
                                    }) + '「' + date + '」' + HKCATool.convBysVar({
                                        hant: '章節至',
                                        hans: '章节至',
                                    }) + '「[[' + archiveTarget + ']]」', {
                                        type: 'success',
                                        title: HKCATool.convByVar({
                                            hant: '成功',
                                            hans: '成功',
                                        }),
                                    });
                                    HKCATool.refreshPage();  // 刷新頁面
                                }).fail(function (error) {
                                    console.log(error);
                                    mw.notify(HKCATool.convByVar({
                                        hant: '小工具無法歸檔,請手動歸檔。',
                                        hans: '小工具无法归档,请手动归档。',
                                    }), {
                                        type: 'error',
                                        title: HKCATool.convByVar({
                                            hant: '錯誤',
                                            hans: '错误',
                                        }),
                                    });
                                });
                            }).fail(function (error) {
                                console.log(error);
                                mw.notify(HKCATool.convByVar({
                                    hant: '小工具無法歸檔,請手動歸檔。',
                                    hans: '小工具无法归档,请手动归档。',
                                }), {
                                    type: 'error',
                                    title: HKCATool.convByVar({
                                        hant: '錯誤',
                                        hans: '错误',
                                    }),
                                });
                            });
                        });
                    });
                }
            });
        },

        /**
         * 在頁面上添加編輯按鈕。
         */
        addEditButtonsToPage: function () {
            // 找到<span role"button">登記新提名</span>
            let newNominationButton = $('span[role="button"]').filter(function () {
                return $(this).text() === '登記新提名' || $(this).text() === '登记新提名';
            });
            if (newNominationButton.length > 0) {
                // 修改原本按鈕的文本為「手動登記新提名」
                newNominationButton.text(HKCATool.convByVar({
                    hant: '手動登記新提名',
                    hans: '手动登记新提名',
                }));
                newNominationButton.removeClass('mw-ui-progressive');

                // 父節點的父節點是<span>,在後面加入編輯按鈕
                let newNominationButtonParent = newNominationButton.parent().parent();
                let editUIButton = $('<span>').addClass('mw-ui-button').addClass('mw-ui-progressive').attr('role', 'button').text(HKCATool.convByVar({
                    hant: '登記新提名',
                    hans: '登记新提名',
                }));
                let editButton = $('<a>').attr('href', 'javascript:void(0)').append(editUIButton).click(HKCATool.newNomination);
                newNominationButtonParent.append(' ').append(editButton);
            }

            // 識別所有h3
            $('div.mw-heading3').each(function () {
                let h3div = $(this);
                let h3 = h3div.find('h3').first();
                let date = h3.text().trim();
                let index = 0;

                // 為h3div底下的span.mw-editsection添加歸檔按鈕
                let editsection = h3div.find('span.mw-editsection').first();
                let editsectionA = editsection.find('a').first();
                $('<a>').attr('href', 'javascript:void(0)').click(function () {
                    HKCATool.archiveChapter(date);
                }).append(HKCATool.convByVar({
                    hant: '歸檔',
                    hans: '归档',
                })).insertAfter(editsectionA);
                $('<span>&nbsp;|&nbsp;</span>').insertAfter(editsectionA);

                h3div.nextUntil('div.mw-heading3', 'table.acgnom-table').each(function () {
                    let table = $(this);
                    let rows = table.find('tr').slice(1);  // 去掉表頭
                    let title = "";
                    rows.each(function () {
                        let row = $(this);
                        let th = row.find('th');
                        if (th.length !== 0) {
                            // 提名行
                            let nomEntry = th.first();
                            let nomEntryA = nomEntry.find('a');
                            if (nomEntryA.length !== 0) {
                                title = nomEntry.find('a').first().attr('title');
                            } else {
                                title = nomEntry.text().trim();
                            }
                            ++index;

                            // 加入編輯按鈕
                            let editIcon = $('<img>').attr('src', 'https://upload.wikimedia.org/wikipedia/commons/8/8a/OOjs_UI_icon_edit-ltr-progressive.svg').css({'width': '12px'});
                            const currentIndex = index;
                            let editButton = $('<a>').attr('href', 'javascript:void(0)').append(editIcon).click(function () {
                                HKCATool.editNomination(date, currentIndex);
                            });
                            nomEntry.append(' ').append(editButton);
                        } else {
                            // 核對行
                            let td = row.find('td').first();
                            let mwNoTalk = td.find('.mw-notalk').first();
                            const currentIndex = index;

                            // 單項核對
                            let checkIcon = $('<img>').attr('src', 'https://upload.wikimedia.org/wikipedia/commons/3/30/OOjs_UI_icon_highlight-progressive.svg').css({
                                'width': '12px',
                                'vertical-align': 'sub',
                            });
                            let checkButton = $('<a>').css({
                                'display': 'inline-block',
                                'margin-left': '5px',
                                'font-size': '.857em',
                                'font-weight': 'bold',
                            }).append(checkIcon).append(' ').append(HKCATool.convByVar({
                                hant: '核對',
                                hans: '核对',
                            })).attr('href', 'javascript:void(0)').click(function () {
                                HKCATool.checkNomination(date, currentIndex);
                            });
                            mwNoTalk.append(checkButton);

                            // 多選核對
                            let multiCheckDiv = $('<div>')
                                .addClass('multi-nomCheck')
                                .attr('data-date', date)
                                .attr('data-index', currentIndex)
                                .css({
                                    'display': 'inline-block',
                                    'margin-left': '5px',
                                    'font-size': '.857em',
                                });
                            let multiCheckButton = $('<a>').attr('href', 'javascript:void(0)').text(HKCATool.convByVar({
                                hant: '多選',
                                hans: '多选',
                            })).click(function () {
                                HKCATool.multiCheckNomination();
                            });
                            multiCheckDiv.append(multiCheckButton);
                            mwNoTalk.append(multiCheckDiv);
                        }
                    });
                });
            });
        },

        /**
         * 動態寬度文本輸入框。
         * @param config
         * @constructor
         */
        DynamicWidthTextInputWidget: function (config) {
            HKCATool.DynamicWidthTextInputWidget.parent.call(this, config);
            this.$measure = $('<span>').css({
                position: 'absolute',
                visibility: 'hidden',
                whiteSpace: 'pre',
                fontSize: '14px',
                fontFamily: 'sans-serif',
            }).appendTo(document.body);  // Create a hidden element for measuring text width.
            this.$input.on('input', this.adjustWidth.bind(this));  // Bind the adjustWidth function to the 'input' event.
        },

        /**
         * 初始化動態寬度文本輸入框。在腳本啟動時執行一次。
         */
        initDynamicWidthTextInputWidget: function () {
            OO.inheritClass(HKCATool.DynamicWidthTextInputWidget, OO.ui.TextInputWidget);
            mw.util.addCSS('.DynamicWidthTextInputWidget { display: inline-block; vertical-align: baseline; width: auto; margin: 0; } .DynamicWidthTextInputWidget input { height: 20px !important; border: none !important; border-bottom: 2px solid #ccc !important; padding: 0 !important; text-align: center; } .DynamicWidthTextInputWidget input:focus { outline: none !important; box-shadow: none !important; border-bottom: 2px solid #36c !important; } .DynamicWidthTextInputWidget input:disabled { background-color: transparent !important; color: #101418 !important; -webkit-text-fill-color: #101418 !important; text-shadow: none !important; border-bottom: 2px solid #fff !important; }');
            HKCATool.DynamicWidthTextInputWidget.prototype.adjustWidth = function () {
                let text = this.getValue() || '';  // Get the current value; use placeholder if empty.
                this.$measure.text(text);  // Update the measurement element.
                let newWidth = this.$measure.width() + 5; // Add a bit of padding.
                this.$input.css('width', newWidth + 'px');  // Apply the new width to the input element.
            };
        },

        /**
         * 規則選框,附帶規則名和分數輸入框。
         * @param config
         * @constructor
         */
        RuleCheckboxInputWidget: function (config) {
            HKCATool.RuleCheckboxInputWidget.parent.call(this, config);
            this.ruleset = config.ruleset;
            this.nomidx = config.nomidx;
            this.check = config.check || false;
            if (!HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule]) {
                HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule] = {
                    selected: this.isSelected(),
                    desc: this.ruleset.desc,
                    ogDesc: this.ruleset.desc,
                    score: this.ruleset.score,
                    maxScore: this.ruleset.score,
                };
            } else {
                this.setSelected(HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].selected);
                if (!HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].desc) {
                    HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].desc = this.ruleset.desc;
                }
                HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].ogDesc = this.ruleset.desc;
                if (!HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].score) {
                    HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].score = this.ruleset.score;
                }
                HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].maxScore = this.ruleset.score;
            }
            this.ruleInputWidget = new HKCATool.DynamicWidthTextInputWidget({
                value: HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].desc,
                disabled: this.check ? false : (!this.isSelected()),
            });
            this.ruleInputWidget.$element.addClass('DynamicWidthTextInputWidget');
            this.ruleInputWidget.$element.css({'margin-left': '5px'});
            this.ruleInputWidget.adjustWidth();
            this.leftBracketLabelWidget = new OO.ui.LabelWidget({label: '('});
            this.leftBracketLabelWidget.$element.css({
                'vertical-align': 'baseline',
                'border-bottom': '2px solid #fff',
            });
            this.scoreInputWidget = new HKCATool.DynamicWidthTextInputWidget({
                value: HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].score,
                disabled: this.check ? false : (!this.isSelected()),
            });
            this.scoreInputWidget.$element.addClass('DynamicWidthTextInputWidget');
            this.scoreInputWidget.adjustWidth();
            this.rightBracketLabelWidget = new OO.ui.LabelWidget({label: '分)'});
            this.rightBracketLabelWidget.$element.css({
                'vertical-align': 'baseline',
                'border-bottom': '2px solid #fff',
                'margin-right': '10px',
            });

            this.$element.append(this.ruleInputWidget.$element);
            this.$element.append(this.leftBracketLabelWidget.$element);
            this.$element.append(this.scoreInputWidget.$element);
            this.$element.append(this.rightBracketLabelWidget.$element);
            this.on('change', this.handleCheckboxChange.bind(this));
            this.ruleInputWidget.on('change', this.handleRuleInputChange.bind(this));
            this.scoreInputWidget.on('change', this.handleScoreInputChange.bind(this));
        },

        /**
         * 初始化規則選框。在腳本啟動時執行一次。
         */
        initRuleCheckboxInputWidget: function () {
            OO.inheritClass(HKCATool.RuleCheckboxInputWidget, OO.ui.CheckboxInputWidget);

            HKCATool.RuleCheckboxInputWidget.prototype.handleCheckboxChange = function (isChecked) {
                HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].selected = isChecked;
                let disableCheck = this.check ? false : (!isChecked);
                this.ruleInputWidget.setDisabled(disableCheck);
                this.scoreInputWidget.setDisabled(disableCheck);
                if (disableCheck) {
                    // 取消選取時,重設規則名和分數
                    this.ruleInputWidget.setValue(this.ruleset.desc);
                    this.ruleInputWidget.adjustWidth();
                    this.scoreInputWidget.setValue(this.ruleset.score);
                    this.scoreInputWidget.adjustWidth();
                }
            };
            HKCATool.RuleCheckboxInputWidget.prototype.handleRuleInputChange = function (newValue) {
                HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].desc = newValue;
            };
            HKCATool.RuleCheckboxInputWidget.prototype.handleScoreInputChange = function (newValue) {
                HKCATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].score = parseFloat(newValue);
            };
        },

        /**
         * 生成提名表單。
         * @param {object} nomData 提名資料(非必須)。無資料時為新提名。
         * @returns {OO.ui.FieldsetLayout} 提名表單。
         */
        generateNominationFieldset: function (nomData) {
            let awarder,
                pageName,
                ruleStatus;
            if (nomData) {
                awarder = nomData.awarder;
                pageName = nomData.pageName;
                ruleStatus = nomData.ruleStatus;
            } else {
                awarder = mw.config.get('wgUserName');
                pageName = '';
                ruleStatus = {};
            }

            const currentNomidx = HKCATool.nominations.length;
            HKCATool.nominations.push({
                awarder: awarder,
                pageName: pageName,
                ruleStatus: ruleStatus,
            });

            // 得分者
            let userNameInput = new OO.ui.TextInputWidget({value: HKCATool.nominations[currentNomidx].awarder});
            userNameInput.on('change', function (newValue) {
                HKCATool.nominations[currentNomidx].awarder = newValue;
            });
            let userNameField = new OO.ui.FieldLayout(userNameInput, {
                label: HKCATool.convByVar({
                    hant: '得分者',
                    hans: '得分者',
                }),
                align: 'left',
            });

            // 條目名稱
            let pageNameInput = new OO.ui.TextInputWidget({value: HKCATool.nominations[currentNomidx].pageName});
            pageNameInput.on('change', function (newValue) {
                HKCATool.nominations[currentNomidx].pageName = newValue;
            });
            let pageNameField = new OO.ui.FieldLayout(pageNameInput, {
                label: HKCATool.convByVar({
                    hant: '得分條目',
                    hans: '得分条目',
                }),
                align: 'left',
            });

            // 提名理由
            let reasonFieldsetLayout = new OO.ui.FieldsetLayout();
            reasonFieldsetLayout.$element.css({'padding-top': '15px'});  // 讓理由區域與上方的輸入框有點距離

            let NominationRules = HKCATool.NominationRules();
            for (let i = 0; i < NominationRules.length; i++) {
                let ruleGroup = NominationRules[i];
                let ruleGroupFieldset = new OO.ui.FieldsetLayout({
                    label: ruleGroup.group,
                    align: 'top',
                    help: ruleGroup.explanation,
                    helpInline: true,
                });

                if (ruleGroup.rules) {
                    let ruleItems = [];
                    for (let j = 0; j < ruleGroup.rules.length; j++) {
                        let rule = ruleGroup.rules[j];
                        let ruleCheckbox = new HKCATool.RuleCheckboxInputWidget({
                            value: rule.rule,
                            ruleset: rule,
                            nomidx: currentNomidx,
                        });
                        ruleItems.push(ruleCheckbox);
                    }
                    let horizontalLayout = new OO.ui.HorizontalLayout({items: ruleItems});
                    ruleGroupFieldset.addItems(horizontalLayout);

                } else {
                    // 處理 (5) 條目評審
                    for (let j = 0; j < ruleGroup.tabs.length; j++) {
                        let tab = ruleGroup.tabs[j];
                        let tabFieldset = new OO.ui.FieldsetLayout({label: tab.tab});
                        tabFieldset.$element.find('legend > span').css({'font-size': '1.05em'});

                        let ruleItems = [];
                        for (let k = 0; k < tab.rules.length; k++) {
                            let rule = tab.rules[k];
                            let ruleCheckbox = new HKCATool.RuleCheckboxInputWidget({
                                value: rule.rule,
                                ruleset: rule,
                                nomidx: currentNomidx,
                            });
                            ruleItems.push(ruleCheckbox);
                        }
                        let horizontalLayout = new OO.ui.HorizontalLayout({items: ruleItems});
                        tabFieldset.addItems(horizontalLayout);
                        ruleGroupFieldset.addItems([tabFieldset]);
                    }
                }
                reasonFieldsetLayout.addItems([ruleGroupFieldset]);
            }

            let nominationFieldset = new OO.ui.FieldsetLayout({
                items: [
                    userNameField,
                    pageNameField,
                    reasonFieldsetLayout,
                ],
            });
            nominationFieldset.$element.css({'margin-top': 0});

            if (!nomData) {
                let hr = $('<hr>').css({
                    'margin-top': '15px',
                    'margin-bottom': '15px',
                });  // 頂部的 <hr>
                nominationFieldset.$element.prepend(hr);
            }

            return nominationFieldset;
        },

        /**
         * 獲取XTools頁面資訊。無法獲取時按下不表,返回空字串。
         * @param pageName
         * @returns {Promise<string>} XTools頁面資訊。
         */
        getXToolsInfo: async function (pageName) {
            try {
                return await $.get('https://xtools.wmcloud.org/api/page/pageinfo/' + mw.config.get('wgServerName') + '/' + pageName.replace(/["?%&+\\]/g, escape) + '?format=html&uselang=' + mw.config.get('wgUserLanguage'));
            } catch (error) {
                console.error('Error fetching XTools data:', error);
                return '';
            }
        },

        /**
         * 生成提名檢查單。
         * @param nomData 提名資料。
         * @returns {OO.ui.FieldsetLayout} 提名檢查單。
         */
        generateChecklistFieldset: async function (nomData) {
            let awarder = nomData.awarder;
            let pageName = nomData.pageName;
            let ruleStatus = nomData.ruleStatus;
            HKCATool.nominations.push({
                awarder: awarder,
                pageName: pageName,
                ruleStatus: ruleStatus,
                invalid: false,
                message: '',
            });

            // 得分者
            let userNameLinkLabelWidget = new OO.ui.LabelWidget({label: $('<a>').attr('href', mw.util.getUrl('User:' + awarder)).text(awarder)});
            let userTalkLinkLabelWidget = new OO.ui.LabelWidget({
                label: $('<a>').attr('href', mw.util.getUrl('User talk:' + awarder)).text(HKCATool.convByVar({
                    hant: '討論',
                    hans: '讨论',
                })),
            });
            let userContribsLinkLabelWidget = new OO.ui.LabelWidget({
                label: $('<a>').attr('href', mw.util.getUrl('Special:用户贡献/' + awarder)).text(HKCATool.convByVar({
                    hant: '貢獻',
                    hans: '贡献',
                })),
            });
            let userNameHorizontalLayout = new OO.ui.HorizontalLayout({
                items: [
                    userNameLinkLabelWidget,
                    new OO.ui.LabelWidget({label: '('}),
                    userTalkLinkLabelWidget,
                    new OO.ui.LabelWidget({label: '·'}),
                    userContribsLinkLabelWidget,
                    new OO.ui.LabelWidget({label: ')'}),
                ],
            });
            userNameHorizontalLayout.$element.css({
                'gap': '4px',
                'width': '80%',
                'flex-shrink': '0',
                'flex-wrap': 'wrap',
            });
            let userNameLabelWidget = new OO.ui.LabelWidget({
                label: HKCATool.convByVar({
                    hant: '得分者',
                    hans: '得分者',
                }),
            });
            userNameLabelWidget.$element.css({
                'flex-grow': 1,
                'align-self': 'stretch',
            });
            let userNameField = new OO.ui.HorizontalLayout({
                items: [
                    userNameLabelWidget,
                    userNameHorizontalLayout,
                ],
            });

            // 條目名稱
            let pageNameHorizontalLayout,
                pageNameLabelWidget;
            if (pageName === '他薦' || pageName === '他荐') {
                pageNameLabelWidget = new OO.ui.LabelWidget({
                    label: HKCATool.convByVar({
                        hant: '他薦',
                        hans: '他荐',
                    }),
                });
                pageNameHorizontalLayout = new OO.ui.HorizontalLayout({items: [pageNameLabelWidget]});
            } else {
                pageNameLabelWidget = new OO.ui.LabelWidget({label: $('<a>').attr('href', mw.util.getUrl(pageName)).text(pageName)});
                let pageTalkLabelWidget = new OO.ui.LabelWidget({
                    label: $('<a>').attr('href', mw.util.getUrl('Talk:' + pageName)).text(HKCATool.convByVar({
                        hant: '討論',
                        hans: '讨论',
                    })),
                });
                let pageHistoryLabelWidget = new OO.ui.LabelWidget({
                    label: $('<a>').attr('href', mw.util.getUrl(pageName, {action: 'history'})).text(HKCATool.convByVar({
                        hant: '歷史',
                        hans: '历史',
                    })),
                });
                let pagesLinkedToPageLabelWidget = new OO.ui.LabelWidget({
                    label: $('<a>').attr('href', mw.util.getUrl('Special:链入页面/' + pageName)).text(HKCATool.convByVar({
                        hant: '連入',
                        hans: '链入',
                    })),
                });
                // 更多頁面資訊 from XTools
                let xtoolsData = await HKCATool.getXToolsInfo(pageName);
                let xtoolsPageInfoLabelWidget = new OO.ui.LabelWidget({label: $('<div>').html(xtoolsData)});
                xtoolsPageInfoLabelWidget.$element.css({'font-size': '0.9em'});
                pageNameHorizontalLayout = new OO.ui.HorizontalLayout({
                    items: [
                        pageNameLabelWidget,
                        new OO.ui.LabelWidget({label: '('}),
                        pageTalkLabelWidget,
                        new OO.ui.LabelWidget({label: '·'}),
                        pageHistoryLabelWidget,
                        new OO.ui.LabelWidget({label: '·'}),
                        pagesLinkedToPageLabelWidget,
                        new OO.ui.LabelWidget({label: ')'}),
                        xtoolsPageInfoLabelWidget,
                    ],
                });
            }
            pageNameHorizontalLayout.$element.css({
                'gap': '4px',
                'width': '80%',
                'flex-shrink': '0',
                'flex-wrap': 'wrap',
            });
            let pageLabelLabelWidget = new OO.ui.LabelWidget({
                label: HKCATool.convByVar({
                    hant: '得分條目',
                    hans: '得分条目',
                }),
            });
            pageLabelLabelWidget.$element.css({
                'flex-grow': 1,
                'align-self': 'stretch',
            });
            let pageNameField = new OO.ui.HorizontalLayout({
                items: [
                    pageLabelLabelWidget,
                    pageNameHorizontalLayout,
                ],
            });
            pageNameField.$element.css({'margin-top': '15px'});

            // 提名無效
            let invalidToggleSwitchWidget = new OO.ui.ToggleSwitchWidget({value: false});
            invalidToggleSwitchWidget.on('change', function (isChecked) {
                HKCATool.nominations[0].invalid = isChecked;
            });
            let invalidField = new OO.ui.FieldLayout(invalidToggleSwitchWidget, {
                label: HKCATool.convByVar({
                    hant: '提名無效',
                    hans: '提名无效',
                }),
                align: 'left',
            });
            invalidField.$element.css({'margin-top': '15px'});
            invalidField.$element.addClass('checklist-field');

            // 提名理由
            let reasonField = new OO.ui.HorizontalLayout();
            reasonField.$element.css({'margin-top': '15px'});
            let reasonLabelWidget = new OO.ui.LabelWidget({
                label: HKCATool.convByVar({
                    hant: '提名理由',
                    hans: '提名理由',
                }),
            });
            reasonLabelWidget.$element.css({
                'flex-grow': 1,
                'align-self': 'stretch',
            });

            let ruleItems = [];
            let {
                ruleNames,
                ruleDict,
            } = HKCATool.NominationRuleSet();
            let orderedRuleStatus = HKCATool.getOrderedRuleStatus(ruleNames, ruleStatus);
            for (const ruleItem of orderedRuleStatus) {
                let ruleSet = ruleDict[ruleItem.rule];
                let ruleCheckbox = new HKCATool.RuleCheckboxInputWidget({
                    value: ruleItem.rule,
                    ruleset: ruleSet,
                    nomidx: 0,
                    check: true,
                });
                ruleItems.push(ruleCheckbox);
            }
            let horizontalLayout = new OO.ui.HorizontalLayout({items: ruleItems});
            horizontalLayout.$element.css({
                'width': '80%',
                'flex-shrink': '0',
                'flex-wrap': 'wrap',
            });
            reasonField.addItems([
                reasonLabelWidget,
                horizontalLayout,
            ]);

            // 附加說明
            let messageInput = new OO.ui.MultilineTextInputWidget({
                autosize: true,
                rows: 1,
            });
            messageInput.on('change', function (newValue) {
                HKCATool.nominations[0].message = newValue;
            });
            messageInput.on('resize', function () {
                try {
                    HKCATool.checkNominationDialog.updateSize();
                } catch (error) {
                    console.error('[HKCATool] Error updating dialog size:', error);
                }
            });
            let messageField = new OO.ui.FieldLayout(messageInput, {
                label: HKCATool.convByVar({
                    hant: '附加說明',
                    hans: '附加说明',
                }),
                align: 'left',
                help: HKCATool.convByVar({
                    hant: '可不填;無須簽名',
                    hans: '可不填;无须签名',
                }),
                helpInline: true,
            });
            messageField.$element.css({'margin-top': '15px'});
            messageField.$element.addClass('checklist-field');

            let nominationFieldset = new OO.ui.FieldsetLayout({
                items: [
                    userNameField,
                    pageNameField,
                    invalidField,
                    reasonField,
                    messageField,
                ],
            });
            nominationFieldset.$element.css({'margin-top': 0});
            return nominationFieldset;
        },

        /**
         * 生成提名理由。
         * @param ruleStatus
         * @param check
         * @returns {{reasonText: string, unselectedReasonText: string}|string|null}
         */
        generateReason: function (ruleStatus, check) {
            // 拼湊提名理由
            let reasonText = '',
                unselectedReasonText = '';
            let reasonScore = 0;
            let {ruleNames} = HKCATool.NominationRuleSet();
            let orderedRuleStatus = HKCATool.getOrderedRuleStatus(ruleNames, ruleStatus);
            for (const ruleItem of orderedRuleStatus) {
                if (check ? true : ruleItem.selected) {
                    if (isNaN(ruleItem.score) || ruleItem.score < 0) {
                        mw.notify(HKCATool.convByVar({
                            hant: '規則',
                            hans: '规则',
                        }) + '「' + ruleItem.rule + '」' + HKCATool.convByVar({
                            hant: '的分數不合法,請檢查!',
                            hans: '的分数不合法,请检查!',
                        }), {
                            type: 'error',
                            title: HKCATool.convByVar({
                                hant: '錯誤',
                                hans: '错误',
                            }),
                        });
                        return null;
                    }
                    // if (ruleItem.score > ruleItem.maxScore) {
                    //     mw.notify(
                    //         HKCATool.convByVar({ hant: '規則', hans: '规则' }) + '「' + ruleItem.rule + '」' + HKCATool.convByVar({ hant: '的分數超過最大值', hans: '的分数超过最大值' }) + '「' + ruleItem.maxScore + '」' + HKCATool.convByVar({ hant: ',請檢查!', hans: ',请检查!' }),
                    //         { type: 'error', title: HKCATool.convByVar({ hant: '錯誤', hans: '错误' }) }
                    //     );
                    //     return null;
                    // }
                    if (ruleItem.selected) {
                        reasonText += ' ' + ruleItem.rule;
                        if (ruleItem.desc !== ruleItem.ogDesc) reasonText += '(' + ruleItem.desc + ')';
                        if (ruleItem.score !== ruleItem.maxScore) reasonText += '[' + ruleItem.score + ']';
                        reasonScore += ruleItem.score;
                    } else if (check) {
                        unselectedReasonText += ' ' + ruleItem.rule;
                        if (ruleItem.desc !== ruleItem.ogDesc) unselectedReasonText += '(' + ruleItem.desc + ')';
                        if (ruleItem.score !== ruleItem.maxScore) unselectedReasonText += '[' + ruleItem.score + ']';
                    }
                }
            }
            reasonText = reasonText.trim();
            unselectedReasonText = unselectedReasonText.trim();
            if (check) {
                return {
                    reasonText: reasonText,
                    unselectedReasonText: unselectedReasonText,
                    reasonScore: reasonScore,
                };
            }
            return reasonText;
        },

        /**
         * 保存新提名。
         * @returns {Promise<boolean>} 是否成功提交。
         */
        saveNewNomination: async function () {
            let proposedWikitext = '{{HKCA Apply';

            for (let i = 0; i < HKCATool.nominations.length; i++) {
                let nomination = HKCATool.nominations[i];
                if (nomination.awarder === '' || nomination.pageName === '') {
                    mw.notify(HKCATool.convByVar({
                        hant: '得分者或得分條目未填寫,請檢查!',
                        hans: '得分者或得分条目未填写,请检查!',
                    }), {
                        type: 'error',
                        title: HKCATool.convByVar({
                            hant: '錯誤',
                            hans: '错误',
                        }),
                    });
                    return true;
                }
                let reasonText = HKCATool.generateReason(nomination.ruleStatus);
                if (reasonText == null) {
                    return true;
                }
                if (reasonText === '') {
                    mw.notify(HKCATool.convByVar({
                        hant: '未選擇任何評審規則,請檢查!',
                        hans: '未选择任何评审规则,请检查!',
                    }), {
                        type: 'error',
                        title: HKCATool.convByVar({
                            hant: '錯誤',
                            hans: '错误',
                        }),
                    });
                    return true;
                }
                proposedWikitext += '\n|條目名稱' + (i + 1) + ' = ' + nomination.pageName.trim();
                proposedWikitext += '\n|用戶名稱' + (i + 1) + ' = ' + nomination.awarder.trim();
                proposedWikitext += '\n|提名理由' + (i + 1) + ' = {{HKCA Apply/request|ver=2|' + reasonText + '}}';
                proposedWikitext += '\n|核對用' + (i + 1) + ' = {{HKCA Apply/check|ver=2|}}';
            }

            const signature = '~' + '~' + '~' + '~';
            proposedWikitext += "\n}}\n'''提名人:'''" + signature;

            // 附加說明
            let message = HKCATool.newNominationDialog.messageInput.getValue().trim();
            if (message !== '') {
                proposedWikitext += "\n: {{說明}}:" + message + '--' + signature;
            }

            // 是否已有今日的date
            let today = new Date();
            let todayDate = (today.getMonth() + 1) + '月' + today.getDate() + '日';
            let fulltext = await HKCATool.getFullText();
            if (!fulltext.includes('=== ' + todayDate + ' ===')) {
                // 沒有今日的date,先新增一個
                proposedWikitext = '=== ' + todayDate + ' ===\n' + proposedWikitext;
            }

            // 提交
            let response = await HKCATool.api.postWithToken('csrf', {
                action: 'edit',
                title: 'Wikipedia:香港維基人佈告板/維基香港內容獎/登記處',
                appendtext: '\n' + proposedWikitext,
                summary: '[[User:Royal Sailor/JS/HKCATool.js|新提名]]',
            });
            if (response.edit.result === 'Success') {
                mw.notify(HKCATool.convByVar({
                    hant: '新提名已成功提交!',
                    hans: '新提名已成功提交!',
                }), {
                    title: HKCATool.convByVar({
                        hant: '成功',
                        hans: '成功',
                    }),
                    autoHide: true,
                });
                HKCATool.refreshPage();
                return false;
            } else {
                mw.notify(HKCATool.convByVar({
                    hant: '新提名提交失敗:',
                    hans: '新提名提交失败:',
                }) + response.edit.result, {
                    type: 'error',
                    title: HKCATool.convByVar({
                        hant: '錯誤',
                        hans: '错误',
                    }),
                });
                return true;
            }
        },

        /**
         * 保存修改提名。
         * @returns {Promise<boolean>} 是否成功提交。
         */
        saveModifiedNomination: async function () {
            let nomination = HKCATool.nominations[0];
            if (nomination.awarder === '' || nomination.pageName === '') {
                mw.notify(HKCATool.convByVar({
                    hant: '得分者或得分條目未填寫,請檢查!',
                    hans: '得分者或得分条目未填写,请检查!',
                }), {
                    type: 'error',
                    title: HKCATool.convByVar({
                        hant: '錯誤',
                        hans: '错误',
                    }),
                });
                return true;
            }

            let reasonText = HKCATool.generateReason(nomination.ruleStatus);
            if (reasonText == null) {
                return true;
            }
            if (reasonText === '') {
                mw.notify(HKCATool.convByVar({
                    hant: '未選擇任何評審規則,請檢查!',
                    hans: '未选择任何评审规则,请检查!',
                }), {
                    type: 'error',
                    title: HKCATool.convByVar({
                        hant: '錯誤',
                        hans: '错误',
                    }),
                });
                return true;
            }

            let fulltext = await HKCATool.getFullText(),
                updatedText;
            if (HKCATool.queried.type === 'main' || HKCATool.queried.type === 'extra') {
                let changes = {
                    '條目名稱': nomination.pageName,
                    '用戶名稱': nomination.awarder,
                    '提名理由': '{{HKCA Apply/request|ver=2|' + reasonText + '}}',
                };
                updatedText = HKCATool.updateEntryParameters(fulltext, HKCATool.queried, changes);
            } else if (HKCATool.queried.type === 'hkca1') {
                let changes = {
                    '條目名稱': nomination.pageName,
                    '用戶名稱': nomination.awarder,
                    '提名理由': '{{HKCA Apply/request|ver=2|' + reasonText + '}}',
                };
                updatedText = HKCATool.updateEntryParameters(fulltext, HKCATool.queried, changes);
            }
            if (updatedText === fulltext) {
                mw.notify(HKCATool.convByVar({
                    hant: '提名並未改動!',
                    hans: '提名并未改动!',
                }), {
                    type: 'warn',
                    title: HKCATool.convByVar({
                        hant: '提示',
                        hans: '提示',
                    }),
                });
                return true;
            }

            let response = await HKCATool.api.postWithToken('csrf', {
                action: 'edit',
                title: 'Wikipedia:香港維基人佈告板/維基香港內容獎/登記處',
                text: updatedText,
                summary: '[[User:Royal Sailor/JS/HKCATool.js|編輯提名]]',
            });
            if (response.edit.result === 'Success') {
                mw.notify(HKCATool.convByVar({
                    hant: '提名已成功修改!',
                    hans: '提名已成功修改!',
                }), {
                    title: HKCATool.convByVar({
                        hant: '成功',
                        hans: '成功',
                    }),
                    autoHide: true,
                });
                HKCATool.refreshPage();
                return false;
            } else {
                mw.notify(HKCATool.convByVar({
                    hant: '提名修改失敗:',
                    hans: '提名修改失败:',
                }) + response.edit.result, {
                    type: 'error',
                    title: HKCATool.convByVar({
                        hant: '錯誤',
                        hans: '错误',
                    }),
                });
                return true;
            }
        },

        /**
         * 保存核分。
         * @returns {Promise<boolean>} 是否成功提交。
         */
        saveNominationCheck: async function () {
            let nomination = HKCATool.nominations[0];
            let checkText = '{{HKCA Apply/check|ver=2|';
            let reasonScore = 0;
            // 是否選擇「提名無效」
            if (nomination.invalid) {
                checkText += '0';
            } else {
                let reasonObject = HKCATool.generateReason(nomination.ruleStatus, true);
                if (reasonObject == null) {
                    return true;
                }
                let reasonText = reasonObject.reasonText;
                let unselectedReasonText = reasonObject.unselectedReasonText;
                reasonScore = reasonObject.reasonScore;
                checkText += reasonText;
                if (unselectedReasonText !== '') {
                    checkText += '|no=' + unselectedReasonText;
                }
            }
            checkText += '}}' + nomination.message;
            let signature = '~' + '~' + '~' + '~';
            checkText += '--' + signature;

            let fulltext = await HKCATool.getFullText(),
                updatedText;
            if (HKCATool.queried.type === 'main' || HKCATool.queried.type === 'extra') {
                let changes = {'核對用': checkText};
                updatedText = HKCATool.updateEntryParameters(fulltext, HKCATool.queried, changes);
            } else if (HKCATool.queried.type === 'hkca1') {
                let changes = {'核對用': checkText};
                updatedText = HKCATool.updateEntryParameters(fulltext, HKCATool.queried, changes);
            }
            if (updatedText === fulltext) {
                mw.notify(HKCATool.convByVar({
                    hant: '核分並未改動!',
                    hans: '核分并未改动!',
                }), {
                    type: 'warn',
                    title: HKCATool.convByVar({
                        hant: '提示',
                        hans: '提示',
                    }),
                });
                return true;
            }

            let response = await HKCATool.api.postWithToken('csrf', {
                action: 'edit',
                title: 'Wikipedia:香港維基人佈告板/維基香港內容獎/登記處',
                text: updatedText,
                summary: '[[User:Royal Sailor/JS/HKCATool.js|核對分數]]',
            });
            if (response.edit.result === 'Success') {
                mw.notify(HKCATool.convByVar({
                    hant: '核分已成功提交!',
                    hans: '核分已成功提交!',
                }), {
                    title: HKCATool.convByVar({
                        hant: '成功',
                        hans: '成功',
                    }),
                    autoHide: false,
                });
                if (reasonScore > 0) {
                    await HKCATool.editHKCAScoreList(nomination.awarder, reasonScore);
                }
                if (!HKCATool.multiNomCheckOngoing) {
                    HKCATool.refreshPage();
                }
                HKCATool.multiNomCheckChanged = true;
                return false;
            } else {
                mw.notify(HKCATool.convByVar({
                    hant: '核分提交失敗:',
                    hans: '核分提交失败:',
                }) + response.edit.result, {
                    type: 'error',
                    title: HKCATool.convByVar({
                        hant: '錯誤',
                        hans: '错误',
                    }),
                });
                return true;
            }
        },

        /**
         * 編輯 Module:HKCA/list。
         * @param {string} awarder 得分者。
         * @param {number} score 得分。
         * @return {Promise<boolean>} 是否成功提交。
         */
        editHKCAScoreList: async function (awarder, score) {
            let scorelistText = await HKCATool.getFullText('Module:HKCA/list');
            let scorelistLines = scorelistText.trim().split('\n').slice(1, -1);  // 去掉 'return {' 和 '}' 行
            let scorelist = {};
            for (let line of scorelistLines) {
                let match = line.match(/^\s*\[?(?:'([^']+)'|"([^"]+)")\]?\s*=\s*([+-]?[\d.]+)\s*,?\s*$/);
                if (match) {
                    let name = match[1] || match[2];  // 使用第一個捕獲組或第二個捕獲組
                    scorelist[name] = parseFloat(match[3]);
                }
            }
            let originalScore = scorelist[awarder] || 0;  // 獲取原始分數
            scorelist[awarder] = originalScore + score;  // 更新分數
            let editSummary = awarder + ' ' + originalScore + '+' + score + '=' + scorelist[awarder];

            let sortedNames = Object.keys(scorelist).sort((a, b) => {
                // 按照名稱排序,忽略大小寫
                return a.toLowerCase().localeCompare(b.toLowerCase(), "en");
            });
            let newScorelistText = 'return {\n';
            for (let name of sortedNames) {
                let nameQuoted = name.includes('"') ? "'" + name + "'" : '"' + name + '"';  // 確保名稱被正確引用
                newScorelistText += '    [' + nameQuoted + '] = ' + scorelist[name].toString() + ',\n';
            }
            newScorelistText += '}';

            // 提交修改
            let response = await HKCATool.api.postWithToken('csrf', {
                action: 'edit',
                title: 'Module:HKCA/list',
                text: newScorelistText,
                summary: editSummary + '([[User:Royal Sailor/JS/HKCATool.js|核對分數]])',
            });
            if (response.edit.result === 'Success') {
                mw.notify(HKCATool.convByVar({
                    hant: 'Module:HKCA/list 已成功更新!',
                    hans: 'Module:HKCA/list 已成功更新!',
                }), {
                    title: HKCATool.convByVar({
                        hant: '成功',
                        hans: '成功',
                    }),
                    autoHide: true,
                });
                return false;  // 成功提交
            } else {
                mw.notify(HKCATool.convByVar({
                    hant: 'Module:HKCA/list 更新失敗:',
                    hans: 'Module:HKCA/list 更新失败:',
                }) + response.edit.result, {
                    type: 'error',
                    title: HKCATool.convByVar({
                        hant: '錯誤',
                        hans: '错误',
                    }),
                });
                return true;  // 提交失敗
            }
        },

        /**
         * 刷新頁面。
         */
        refreshPage: function () {
            // 2秒後刷新頁面
            setTimeout(function () {
                location.reload();
            }, 2000);
        },

        /**
         * 新提名對話框。
         * @param config
         * @constructor
         */
        NewNominationDialog: function (config) {
            HKCATool.NewNominationDialog.super.call(this, config);
        },

        /**
         * 初始化新提名對話框。在腳本啟動時執行一次。
         */
        initNewNominationDialog: function () {
            OO.inheritClass(HKCATool.NewNominationDialog, OO.ui.ProcessDialog);

            HKCATool.NewNominationDialog.static.name = 'NewNominationDialog';
            HKCATool.NewNominationDialog.static.title = HKCATool.convByVar({
                hant: '新提名(維基香港內容獎小工具)',
                hans: '新提名(维基香港内容奖小工具)',
            });
            HKCATool.NewNominationDialog.static.actions = [
                {
                    action: 'save',
                    label: HKCATool.convByVar({
                        hant: '儲存',
                        hans: '储存',
                    }),
                    flags: [
                        'primary',
                        'progressive',
                    ],
                },
                {
                    action: 'cancel',
                    label: HKCATool.convByVar({
                        hant: '取消',
                        hans: '取消',
                    }),
                    flags: 'safe',
                },
                {
                    action: 'add',
                    label: HKCATool.convByVar({
                        hant: '額外提名 + 1',
                        hans: '额外提名 + 1',
                    }),
                },
                {
                    action: 'minus',
                    label: HKCATool.convByVar({
                        hant: '額外提名 − 1',
                        hans: '额外提名 − 1',
                    }),
                },
            ];
            HKCATool.NewNominationDialog.prototype.initialize = function () {
                HKCATool.NewNominationDialog.super.prototype.initialize.call(this);
                this.panel = new OO.ui.PanelLayout({
                    padded: true,
                    expanded: false,
                });
                this.content = new OO.ui.FieldsetLayout();

                // 附加說明
                this.messageInput = new OO.ui.MultilineTextInputWidget({
                    autosize: true,
                    rows: 1,
                });
                this.messageInput.connect(this, {resize: 'onMessageInputResize'});
                this.messageInputField = new OO.ui.FieldLayout(this.messageInput, {
                    label: HKCATool.convByVar({
                        hant: '附加說明',
                        hans: '附加说明',
                    }),
                    align: 'top',
                    help: HKCATool.convByVar({
                        hant: '可不填;無須簽名',
                        hans: '可不填;无须签名',
                    }),
                    helpInline: true,
                });
                this.messageInputFieldSet = new OO.ui.FieldsetLayout({items: [this.messageInputField]});

                this.content.addItems([
                    this.messageInputFieldSet,
                    HKCATool.generateNominationFieldset(),
                ]);

                this.panel.$element.append(this.content.$element);
                this.$body.append(this.panel.$element);
            };

            HKCATool.NewNominationDialog.prototype.onMessageInputResize = function () {
                this.updateSize();
            };

            HKCATool.NewNominationDialog.prototype.getBodyHeight = function () {
                return this.panel.$element.outerHeight(true);
            };

            HKCATool.NewNominationDialog.prototype.getActionProcess = function (action) {
                if (action === 'save') {
                    return new OO.ui.Process(async function () {
                        let response = await HKCATool.saveNewNomination();
                        if (!response) {
                            this.close();
                        }
                    }, this);
                } else if (action === 'add') {
                    return new OO.ui.Process(function () {
                        // 新增一個提名
                        let newFieldset = HKCATool.generateNominationFieldset();
                        this.content.addItems([newFieldset]);
                        this.updateSize();
                    }, this);
                } else if (action === 'minus') {
                    return new OO.ui.Process(function () {
                        if (this.content.items.length <= 2) {
                            mw.notify(HKCATool.convByVar({
                                hant: '至少需要一個提名!',
                                hans: '至少需要一个提名!',
                            }), {
                                type: 'error',
                                title: HKCATool.convByVar({
                                    hant: '錯誤',
                                    hans: '错误',
                                }),
                            });
                            return;
                        }
                        // 移除最後一個提名
                        this.content.removeItems([this.content.items[this.content.items.length - 1]]);
                        HKCATool.nominations.pop();
                        this.updateSize();
                    }, this);
                } else if (action === 'cancel') {
                    return new OO.ui.Process(function () {
                        this.close();
                    }, this);
                }
                return HKCATool.NewNominationDialog.super.prototype.getActionProcess.call(this, action);
            };

            HKCATool.NewNominationDialog.prototype.getTearnDownProcess = function (data) {
                return HKCATool.NewNominationDialog.super.prototype.getTearnDownProcess.call(this, data);
            };
        },

        /**
         * 顯示新提名對話框。
         */
        showNewNominationDialog: function () {
            // 清空原有的items
            HKCATool.nominations.length = 0;

            HKCATool.newNominationDialog = new HKCATool.NewNominationDialog({
                size: 'large',
                padded: true,
                scrollable: true,
            });
            HKCATool.windowManager.addWindows([HKCATool.newNominationDialog]);
            HKCATool.windowManager.openWindow(HKCATool.newNominationDialog);
        },

        /**
         * 編輯提名對話框。
         * @param config
         * @constructor
         */
        EditNominationDialog: function (config) {
            HKCATool.EditNominationDialog.super.call(this, config);
        },

        /**
         * 初始化編輯提名對話框。在腳本啟動時執行一次。
         */
        initEditNominationDialog: function () {
            OO.inheritClass(HKCATool.EditNominationDialog, OO.ui.ProcessDialog);

            HKCATool.EditNominationDialog.static.name = 'EditNominationDialog';
            HKCATool.EditNominationDialog.static.title = HKCATool.convByVar({
                hant: '編輯提名(維基香港內容獎小工具)',
                hans: '编辑提名(维基香港内容奖小工具)',
            });
            HKCATool.EditNominationDialog.static.actions = [
                {
                    action: 'save',
                    label: HKCATool.convByVar({
                        hant: '儲存',
                        hans: '储存',
                    }),
                    flags: [
                        'primary',
                        'progressive',
                    ],
                },
                {
                    action: 'cancel',
                    label: HKCATool.convByVar({
                        hant: '取消',
                        hans: '取消',
                    }),
                    flags: 'safe',
                },
            ];
            HKCATool.EditNominationDialog.prototype.initialize = function () {
                HKCATool.EditNominationDialog.super.prototype.initialize.call(this);
                this.panel = new OO.ui.PanelLayout({
                    padded: true,
                    expanded: false,
                });
                this.panel.$element.append(HKCATool.editNominationDialogContent.$element);
                this.$body.append(this.panel.$element);
            };

            HKCATool.EditNominationDialog.prototype.onMessageInputResize = function () {
                this.updateSize();
            };

            HKCATool.EditNominationDialog.prototype.getBodyHeight = function () {
                return this.panel.$element.outerHeight(true);
            };

            HKCATool.EditNominationDialog.prototype.getActionProcess = function (action) {
                if (action === 'save') {
                    return new OO.ui.Process(async function () {
                        let response = await HKCATool.saveModifiedNomination();
                        if (!response) {
                            this.close();
                        }
                    }, this);
                } else if (action === 'cancel') {
                    return new OO.ui.Process(function () {
                        this.close();
                    }, this);
                }
                return HKCATool.EditNominationDialog.super.prototype.getActionProcess.call(this, action);
            };

            HKCATool.EditNominationDialog.prototype.getTearnDownProcess = function (data) {
                return HKCATool.EditNominationDialog.super.prototype.getTearnDownProcess.call(this, data);
            };
        },

        /**
         * 顯示編輯提名對話框。
         * @param nomData 提名資料。
         */
        showEditNominationDialog: function (nomData) {
            // 清空原有的items
            HKCATool.editNominationDialogContent.clearItems();
            HKCATool.nominations.length = 0;

            HKCATool.editNominationDialog = new HKCATool.EditNominationDialog({
                size: 'large',
                padded: true,
                scrollable: true,
            });

            // 加入新的items
            HKCATool.editNominationDialogContent.addItems([HKCATool.generateNominationFieldset(nomData)]);

            HKCATool.windowManager.addWindows([HKCATool.editNominationDialog]);
            HKCATool.windowManager.openWindow(HKCATool.editNominationDialog);
        },

        /**
         * 核分提名對話框。
         * @param config
         * @constructor
         */
        CheckNominationDialog: function (config) {
            HKCATool.CheckNominationDialog.super.call(this, config);
        },

        /**
         * 初始化核分提名對話框。在腳本啟動時執行一次。
         */
        initCheckNominationDialog: function () {
            OO.inheritClass(HKCATool.CheckNominationDialog, OO.ui.ProcessDialog);

            HKCATool.CheckNominationDialog.static.name = 'CheckNominationDialog';
            HKCATool.CheckNominationDialog.static.title = HKCATool.convByVar({
                hant: '核對分數(維基香港內容獎小工具)',
                hans: '核对分数(维基香港内容奖小工具)',
            });
            HKCATool.CheckNominationDialog.static.actions = [
                {
                    action: 'save',
                    label: HKCATool.convByVar({
                        hant: '儲存',
                        hans: '储存',
                    }),
                    flags: [
                        'primary',
                        'progressive',
                    ],
                },
                {
                    action: 'cancel',
                    label: HKCATool.convByVar({
                        hant: '取消',
                        hans: '取消',
                    }),
                    flags: 'safe',
                },
            ];
            HKCATool.CheckNominationDialog.prototype.initialize = function () {
                HKCATool.CheckNominationDialog.super.prototype.initialize.call(this);
                this._header = this.$body.find('.oo-ui-processDialog-title');
                this.panel = new OO.ui.PanelLayout({
                    padded: true,
                    expanded: false,
                });
                this.panel.$element.append(HKCATool.checkNominationDialogContent.$element);
                this.$body.append(this.panel.$element);
            };

            HKCATool.CheckNominationDialog.prototype.getSetupProcess = function (data) {
                return HKCATool.CheckNominationDialog.super.prototype.getSetupProcess.call(this, data)
                    .next(function () {
                        if (data.title) {
                            this._header.innerText = data.title;
                        }
                    }, this);
            };

            HKCATool.CheckNominationDialog.prototype.onMessageInputResize = function () {
                this.updateSize();
            };

            HKCATool.CheckNominationDialog.prototype.getBodyHeight = function () {
                return this.panel.$element.outerHeight(true);
            };

            HKCATool.CheckNominationDialog.prototype.getActionProcess = function (action) {
                if (action === 'save') {
                    return new OO.ui.Process(async function () {
                        let response = await HKCATool.saveNominationCheck();
                        if (!response) {
                            this.close();
                        }
                    }, this);
                } else if (action === 'cancel') {
                    return new OO.ui.Process(function () {
                        this.close();
                    }, this);
                }
                return HKCATool.CheckNominationDialog.super.prototype.getActionProcess.call(this, action);
            };

            HKCATool.CheckNominationDialog.prototype.getTearnDownProcess = function (data) {
                return HKCATool.CheckNominationDialog.super.prototype.getTearnDownProcess.call(this, data);
            };

            mw.util.addCSS('.checklist-field .oo-ui-fieldLayout-field { width: 80% !important; }');
        },

        /**
         * 顯示核分提名對話框。
         * @param nomData 提名資料。
         * @param multiCheckStatus 多選核對狀態字串。
         */
        showCheckNominationDialog: async function (nomData, multiCheckStatus) {
            // 清空原有的items
            HKCATool.checkNominationDialogContent.clearItems();
            HKCATool.nominations.length = 0;

            HKCATool.checkNominationDialog = new HKCATool.CheckNominationDialog({
                size: 'large',
                padded: true,
                scrollable: true,
            });

            // 加入新的items
            const field = await HKCATool.generateChecklistFieldset(nomData);
            HKCATool.checkNominationDialogContent.addItems([field]);

            HKCATool.windowManager.addWindows([HKCATool.checkNominationDialog]);

            const instance = HKCATool.windowManager.openWindow(HKCATool.checkNominationDialog, multiCheckStatus ? {
                title: HKCATool.convByVar({
                    hant: '核對分數,' + multiCheckStatus + '(維基香港內容獎小工具)',
                    hans: '核对分数,' + multiCheckStatus + '(维基香港内容奖小工具)',
                }),
            } : undefined);
            await instance.closed;
        },

        /**
         * 腳本入口。
         */
        init: function () {
            HKCATool.pageName = mw.config.get('wgPageName');
            if (HKCATool.pageName !== 'Wikipedia:香港維基人佈告板/維基香港內容獎' && HKCATool.pageName !== 'Wikipedia:香港維基人佈告板/維基香港內容獎/登記處') return;  // 非目標頁面,不執行

            mw.loader.using('ext.gadget.HanAssist').then((require) => {
                const {convByVar} = require('ext.gadget.HanAssist');
                HKCATool.convByVar = convByVar;

                // Initialize OOUI custom widgets
                HKCATool.initDynamicWidthTextInputWidget();
                HKCATool.initRuleCheckboxInputWidget();
                // Initialize dialogs
                HKCATool.initNewNominationDialog();
                HKCATool.initEditNominationDialog();
                HKCATool.initCheckNominationDialog();

                // Append the window manager element to the body
                HKCATool.windowManager = new OO.ui.WindowManager();
                $(document.body).append(HKCATool.windowManager.$element);

                // 添加提名按鈕
                HKCATool.addEditButtonsToPage();
            });
        },

        api: new mw.Api({userAgent: 'HKCATool/1.1.0'}),  // MediaWiki API實例
        pageName: '',  // JS運行的當前頁面
        windowManager: null,  // Window manager for OOUI dialogs
        newNominationDialog: null,  // 新提名dialog
        editNominationDialog: null,  // 修改提名dialog
        editNominationDialogContent: new OO.ui.FieldsetLayout(), // 修改提名dialog的內容池
        checkNominationDialog: null,  // 檢查提名dialog
        checkNominationDialogContent: new OO.ui.FieldsetLayout(), // 檢查提名dialog的內容池
        queried: null,  // 查詢到的提名
        nominations: [],  // 提名
        convByVar: null,  // 簡繁轉換
        multiNomCheckOngoing: false,  // 多選核對進度狀態
        multiNomCheckChanged: false,  // 多選核對是否有變更
    };

    $(HKCATool.init);
})();