User:SuperGrey/gadgets/ACGATool/main.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
// Main page: [[User:SuperGrey/gadgets/ACGATool]]
var ACGATool = {
/**
* 維基ACG專題獎提名規則。
* @returns {object} 包含規則組的對象。
*/
NominationRules: function () {
var rules = [
{
group: ACGATool.convByVar({ hant: '(1) 內容擴充', hans: '(1) 内容扩充' }),
rules: [
{
rule: '1a',
desc: ACGATool.convByVar({ hant: '短新增', hans: '短新增' }),
score: 1
},
{
rule: '1b',
desc: ACGATool.convByVar({ hant: '中新增', hans: '中新增' }),
score: 2
},
{
rule: '1c',
desc: ACGATool.convByVar({ hant: '長新增', hans: '长新增' }),
score: 3
}
],
explanation: ACGATool.convByVar({ hant: '鼓勵編者新增或擴充條目內容。新內容達 2 kB 為「短新增」,達 3 kB 為「中新增」,達 5 kB 為「長新增」。', hans: '新增或扩充条目内容。新内容达 2 kB 为「短新增」,达 3 kB 为「中新增」,达 5 kB 为「长新增」。' })
},
{
group: ACGATool.convByVar({ hant: '(2) 品質提升', hans: '(2) 品质提升' }),
rules: [
{
rule: '2-c',
desc: ACGATool.convByVar({ hant: '丙級', hans: '丙级' }),
score: 1
},
{
rule: '2-b',
desc: ACGATool.convByVar({ hant: '乙級', hans: '乙级' }),
score: 3
},
{
rule: '2-ga',
desc: ACGATool.convByVar({ hant: '優良', hans: '优良' }),
score: 5
},
{
rule: '2-fa',
desc: ACGATool.convByVar({ hant: '典特', hans: '典特' }),
score: 10
}
],
explanation: ACGATool.convByVar({ hant: '鼓勵編者以條目品質評級為標準,提升條目品質。由缺失條目、小作品、初級提升至丙級為「丙級」,由丙級提升至乙級為「乙級」,由乙級提升至優良為「優良」,由優良提升至典範(特色)為「典特」。跨級品質提升,則多選所有符合項。', hans: '以条目品质评级为标准,提升条目品质。由缺失条目、小作品、初级提升至丙级为「丙级」,由丙级提升至乙级为「乙级」,由乙级提升至优良为「优良」,由优良提升至典范(特色)为「典特」。跨级品质提升,则多选所有符合项。' })
},
{
group: ACGATool.convByVar({ hant: '(3) 格式', hans: '(3) 格式' }),
rules: [
{
rule: '3',
desc: ACGATool.convByVar({ hant: '格式', hans: '格式' }),
score: 1
}
],
explanation: ACGATool.convByVar({ hant: '鼓勵編者創作整潔的條目。對於未滿 5 級創作獎的編者,條目應採用 ref 格式搭配 Cite 家族模板列出來源,引文沒有格式錯誤;對於已滿 5 級創作獎的有經驗編者,條目須行文通順、符合命名常規、遵循格式手冊。此項得分需依附於項目 1 或 2,不可單獨申請。', hans: '鼓励编辑者创作整洁的条目。对于未满 5 级创作奖的编辑者,条目应采用 ref 格式搭配 Cite 家族模板列出来源,引文没有格式错误;对于已满 5 级创作奖的有经验编辑者,条目须行文通顺、符合命名常规、遵循格式手册。此项得分需依附于项目 1 或 2,不可单独申请。' })
},
{
group: ACGATool.convByVar({ hant: '(4) 編輯活動', hans: '(4) 编辑活动' }),
rules: [
{
rule: '4',
desc: ACGATool.convByVar({ hant: '活動', hans: '活动' }),
score: 1
},
{
rule: '4-req',
desc: ACGATool.convByVar({ hant: '請求', hans: '请求' }),
score: 1
},
{
rule: '4-dyk',
desc: ACGATool.convByVar({ hant: 'DYK', hans: 'DYK' }),
score: 1
},
{
rule: '4-req-game',
desc: ACGATool.convByVar({ hant: '請求·遊戲', hans: '请求·游戏' }),
score: 1
},
{
rule: '4-req-ac',
desc: ACGATool.convByVar({ hant: '請求·動漫', hans: '请求·动漫' }),
score: 1
}
],
explanation: ACGATool.convByVar({ hant: '鼓勵編者參加社群或專題活動。可單獨申請的活動:新條目推薦、拯救典優條目。須依附於項目 1 或 2,不可單獨申請的活動:動漫及電子遊戲條目請求、基礎條目擴充挑戰、編輯松、動員令。提名時請手動編輯規則,註明所參與活動。', hans: '鼓励编辑者参加社群或专题活动。可单独申请的活动:新条目推荐、拯救典优条目。须依附于项目 1 或 2,不可单独申请的活动:动漫及电子游戏条目请求、基础条目扩充挑战、编辑松、动员令。提名时请手动编辑规则,注明所参与活动。' })
},
{
group: ACGATool.convByVar({ hant: '(5) 條目評審', hans: '(5) 条目评审' }),
tabs: [
{
tab: ACGATool.convByVar({ hant: '泛用評審', hans: '泛用评审' }),
rules: [
{
rule: '5',
desc: ACGATool.convByVar({ hant: '評審', hans: '评审' }),
score: 1
},
{
rule: '5-half',
desc: ACGATool.convByVar({ hant: '快評', hans: '快评' }),
score: 0.5
},
{
rule: '5a',
desc: ACGATool.convByVar({ hant: '文筆評審', hans: '文笔评审' }),
score: 1
},
{
rule: '5a-half',
desc: ACGATool.convByVar({ hant: '文筆快評', hans: '文笔快评' }),
score: 0.5
},
{
rule: '5b',
desc: ACGATool.convByVar({ hant: '覆蓋面評審', hans: '覆盖面评审' }),
score: 1
},
{
rule: '5b-half',
desc: ACGATool.convByVar({ hant: '覆蓋面快評', hans: '覆盖面快评' }),
score: 0.5
},
{
rule: '5c',
desc: ACGATool.convByVar({ hant: '來源格式評審', hans: '来源格式评审' }),
score: 1
},
{
rule: '5c-half',
desc: ACGATool.convByVar({ hant: '來源格式快評', hans: '来源格式快评' }),
score: 0.5
},
{
rule: '5x',
desc: ACGATool.convByVar({ hant: '完整評審', hans: '完整评审' }),
score: 3
}
]
},
{
tab: ACGATool.convByVar({ hant: '乙級(乙上級)內容評審', hans: '乙级(乙上级)内容评审' }),
rules: [
{
rule: '5-bcr',
desc: ACGATool.convByVar({ hant: '乙級評審', hans: '乙级评审' }),
score: 1
},
{
rule: '5-bcr-half',
desc: ACGATool.convByVar({ hant: '乙級快評', hans: '乙级快评' }),
score: 0.5
},
{
rule: '5a-bcr',
desc: ACGATool.convByVar({ hant: '乙級文筆評審', hans: '乙级文笔评审' }),
score: 1
},
{
rule: '5a-bcr-half',
desc: ACGATool.convByVar({ hant: '乙級文筆快評', hans: '乙级文笔快评' }),
score: 0.5
},
{
rule: '5b-bcr',
desc: ACGATool.convByVar({ hant: '乙級覆蓋面評審', hans: '乙级覆盖面评审' }),
score: 1
},
{
rule: '5b-bcr-half',
desc: ACGATool.convByVar({ hant: '乙級覆蓋面快評', hans: '乙级覆盖面快评' }),
score: 0.5
},
{
rule: '5c-bcr',
desc: ACGATool.convByVar({ hant: '乙級來源格式評審', hans: '乙级来源格式评审' }),
score: 1
},
{
rule: '5c-bcr-half',
desc: ACGATool.convByVar({ hant: '乙級來源格式快評', hans: '乙级来源格式快评' }),
score: 0.5
},
{
rule: '5x-bcr',
desc: ACGATool.convByVar({ hant: '完整乙級評審', hans: '完整乙级评审' }),
score: 3
}
]
},
{
tab: ACGATool.convByVar({ hant: '優良內容評審', hans: '优良内容评审' }),
rules: [
{
rule: '5-gan',
desc: ACGATool.convByVar({ hant: '優良評審', hans: '优良评审' }),
score: 1
},
{
rule: '5-gan-half',
desc: ACGATool.convByVar({ hant: '優良快評', hans: '优良快评' }),
score: 0.5
},
{
rule: '5a-gan',
desc: ACGATool.convByVar({ hant: '優良文筆評審', hans: '优良文笔评审' }),
score: 1
},
{
rule: '5a-gan-half',
desc: ACGATool.convByVar({ hant: '優良文筆快評', hans: '优良文笔快评' }),
score: 0.5
},
{
rule: '5b-gan',
desc: ACGATool.convByVar({ hant: '優良覆蓋面評審', hans: '优良覆盖面评审' }),
score: 1
},
{
rule: '5b-gan-half',
desc: ACGATool.convByVar({ hant: '優良覆蓋面快評', hans: '优良覆盖面快评' }),
score: 0.5
},
{
rule: '5c-gan',
desc: ACGATool.convByVar({ hant: '優良來源格式評審', hans: '优良来源格式评审' }),
score: 1
},
{
rule: '5c-gan-half',
desc: ACGATool.convByVar({ hant: '優良來源格式快評', hans: '优良来源格式快评' }),
score: 0.5
},
{
rule: '5x-gan',
desc: ACGATool.convByVar({ hant: '完整優良評審', hans: '完整优良评审' }),
score: 3
}
]
},
{
tab: ACGATool.convByVar({ hant: '甲級內容評審', hans: '甲级内容评审' }),
rules: [
{
rule: '5-acr',
desc: ACGATool.convByVar({ hant: '甲級評審', hans: '甲级评审' }),
score: 2
},
{
rule: '5-acr-half',
desc: ACGATool.convByVar({ hant: '甲級快評', hans: '甲级快评' }),
score: 1
},
{
rule: '5a-acr',
desc: ACGATool.convByVar({ hant: '甲級文筆評審', hans: '甲级文笔评审' }),
score: 2
},
{
rule: '5a-acr-half',
desc: ACGATool.convByVar({ hant: '甲級文筆快評', hans: '甲级文笔快评' }),
score: 1
},
{
rule: '5b-acr',
desc: ACGATool.convByVar({ hant: '甲級覆蓋面評審', hans: '甲级覆盖面评审' }),
score: 2
},
{
rule: '5b-acr-half',
desc: ACGATool.convByVar({ hant: '甲級覆蓋面快評', hans: '甲级覆盖面快评' }),
score: 1
},
{
rule: '5c-acr',
desc: ACGATool.convByVar({ hant: '甲級來源格式評審', hans: '甲级来源格式评审' }),
score: 2
},
{
rule: '5c-acr-half',
desc: ACGATool.convByVar({ hant: '甲級來源格式快評', hans: '甲级来源格式快评' }),
score: 1
},
{
rule: '5x-acr',
desc: ACGATool.convByVar({ hant: '完整甲級評審', hans: '完整甲级评审' }),
score: 6
}
]
},
{
tab: ACGATool.convByVar({ hant: '典範(特色)內容評審', hans: '典范(特色)内容评审' }),
rules: [
{
rule: '5-fac',
desc: ACGATool.convByVar({ hant: '典特評審', hans: '典特评审' }),
score: 2
},
{
rule: '5-fac-half',
desc: ACGATool.convByVar({ hant: '典特快評', hans: '典特快评' }),
score: 1
},
{
rule: '5a-fac',
desc: ACGATool.convByVar({ hant: '典特文筆評審', hans: '典特文笔评审' }),
score: 2
},
{
rule: '5a-fac-half',
desc: ACGATool.convByVar({ hant: '典特文筆快評', hans: '典特文笔快评' }),
score: 1
},
{
rule: '5b-fac',
desc: ACGATool.convByVar({ hant: '典特覆蓋面評審', hans: '典特覆盖面评审' }),
score: 2
},
{
rule: '5b-fac-half',
desc: ACGATool.convByVar({ hant: '典特覆蓋面快評', hans: '典特覆盖面快评' }),
score: 1
},
{
rule: '5c-fac',
desc: ACGATool.convByVar({ hant: '典特來源格式評審', hans: '典特来源格式评审' }),
score: 2
},
{
rule: '5c-fac-half',
desc: ACGATool.convByVar({ hant: '典特來源格式快評', hans: '典特来源格式快评' }),
score: 1
},
{
rule: '5x-fac',
desc: ACGATool.convByVar({ hant: '完整典特評審', hans: '完整典特评审' }),
score: 6
}
]
}
],
explanation: ACGATool.convByVar({ hant: '鼓勵維基人評審他人主編的條目。條目評審分為三大層面:文筆、覆蓋面、來源和格式。不完整評審(快評)折半得分。', hans: '鼓励维基人评审他人主编的条目。条目评审分为三大层面:文笔、覆盖面、来源和格式。不完整评审(快评)折半得分。' })
},
{
group: ACGATool.convByVar({ hant: '(6) 媒體', hans: '(6) 媒体' }),
rules: [
{
rule: '6',
desc: ACGATool.convByVar({ hant: '媒體', hans: '媒体' }),
score: 3
},
{
rule: '6-fp',
desc: ACGATool.convByVar({ hant: '特色圖片', hans: '特色图片' }),
score: 5
}
],
explanation: ACGATool.convByVar({ hant: '鼓勵編者上載與 ACG 專題相關的自由版權媒體檔案至 Wikimedia Commons。若檔案當選特色圖片,額外加 5 分。', hans: '鼓励编辑者上传与 ACG 专题相关的自由版权媒体文件至 Wikimedia Commons。若文件当选特色图片,额外加 5 分。' })
},
{
group: ACGATool.convByVar({ hant: '(7) 他薦', hans: '(7) 他荐' }),
rules: [
{
rule: '7',
desc: ACGATool.convByVar({ hant: '他薦', hans: '他荐' }),
score: 0.5
}
],
explanation: ACGATool.convByVar({ hant: '鼓勵維基人提名他人。每有效提名他人 1 次得 0.5 分,每人每月最多得 5 分。若多次得分,請按照規則編輯自定義分數。', hans: '鼓励维基人提名他人。每有效提名他人 1 次得 0.5 分,每人每月最多得 5 分。若多次得分,请按照规则编辑自定义分数。' })
},
{
group: ACGATool.convByVar({ hant: '(8) 其他', hans: '(8) 其他' }),
rules: [
{
rule: '8',
desc: ACGATool.convByVar({ hant: '其他', hans: '其他' }),
score: 0
}
],
explanation: ACGATool.convByVar({ hant: '獎勵維基人做出的其他難以量化的貢獻,如大量整理格式條目、上載作品封面、製作模板、制定寫作方案、維護資料庫等。請手動編輯規則名稱、自定義分數,並附上說明。最終具體得分將由與專題成員共同商討得出。', hans: '奖励维基人做出的其他难以量化的贡献,如大量整理格式条目、上传作品封面、制作模板、制定写作方案、维护数据库等。请手动编辑规则名称、自定义分数,并附上说明。最终具体得分将由与专题成员共同商讨得出。' })
}
];
return rules;
},
/**
* 提名規則別名。
* @returns {object} 包含別名的對象。
*/
NominationRuleAliases: function () {
return {
'2a': '2-c',
'c': '2-c',
'2b': '2-b',
'b': '2-b',
'2c': '2-ga',
'ga': '2-ga',
'2d': '2-fa',
'fa': '2-fa',
'4a': '4-dyk',
'dyk': '4-dyk',
'4b': '4-req',
'4b-game': '4-req-game',
'4b-ac': '4-req-ac',
'fp': '6-fp',
};
},
/**
* 提名規則集合。
* @returns {object} 包含規則名稱和規則字典的對象。
*/
NominationRuleSet: function () {
var ruleNames = [];
var ruleDict = {};
for (var ruleGroup of ACGATool.NominationRules()) {
if (ruleGroup.rules) {
for (var ruleSet of ruleGroup.rules) {
ruleNames.push(ruleSet.rule);
ruleDict[ruleSet.rule] = ruleSet;
}
} else if (ruleGroup.tabs) {
for (var tab of ruleGroup.tabs) {
for (var ruleSet of tab.rules) {
ruleNames.push(ruleSet.rule);
ruleDict[ruleSet.rule] = ruleSet;
}
}
}
}
return { ruleNames: ruleNames, ruleDict: ruleDict };
},
/**
* 獲取完整的wikitext。
* @returns {Promise<string>} 包含完整wikitext的Promise。
*/
getFullText: async function () {
var api = new mw.Api();
var response = await api.get({
action: 'query', titles: 'WikiProject:ACG/維基ACG專題獎/登記處', prop: 'revisions', rvslots: '*', rvprop: 'content', indexpageids: 1
});
var fulltext = response.query.pages[response.query.pageids[0]].revisions[0].slots.main['*'];
return fulltext;
},
/**
* 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 regular {{ACG提名}} templates, parameters are parsed as key=value pairs (with special handling for nested extras).
* For simpler {{ACG提名2}} 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 } = ACGATool.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 = ACGATool.splitParameters(innerContent, innerStart);
// The first token is the template name.
let nameToken = ACGATool.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("ACG提名2")) {
// For ACG提名2, 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 = ACGATool.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;
} else {
// For regular ACG提名, process tokens as key=value pairs (or positional parameters).
for (let j = 1; j < tokens.length; j++) {
let token = tokens[j];
let tokenTrim = ACGATool.trimToken(token);
if (tokenTrim.text === "") continue; // skip empty tokens
const eqIndex = tokenTrim.text.indexOf('=');
if (eqIndex !== -1) {
// Split into key and value without including the "=" in the value.
let rawKey = tokenTrim.text.substring(0, eqIndex);
let rawValue = tokenTrim.text.substring(eqIndex + 1);
let keyText = rawKey.trim();
let valueText = rawValue.trim();
// Compute absolute positions for key and value.
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
};
templateObj.params[keyText] = {
value: valueText, keyLocation: keyLocation, valueLocation: valueLocation, fullLocation: { start: token.start, end: token.end }
};
} else {
// Positional parameter.
templateObj.params[j] = {
value: tokenTrim.text, fullLocation: { start: token.start, end: token.end }
};
}
}
// Special handling for the "額外提名" parameter.
if (templateObj.params['額外提名']) {
const extraParam = templateObj.params['額外提名'];
extraParam.nestedTemplates = ACGATool.parseMultipleTemplates(text, extraParam.valueLocation.start, extraParam.valueLocation.end);
}
}
return { template: templateObj, endIndex: templateEnd };
},
/**
* Parses nested extra templates from the given region of text.
* This function uses a regex to capture any occurrence of "{{ACG提名/extra" that appears at
* the beginning of the region or is preceded by a newline.
* @param {string} text The full wikitext.
* @param {number} regionStart The start index of the region.
* @param {number} regionEnd The end index of the region.
* @returns {Array} An array of parsed extra template objects.
*/
parseMultipleTemplates: function (text, regionStart, regionEnd) {
const templates = [];
const regionText = text.slice(regionStart, regionEnd);
// Regex: match either start of string (^) or a newline, then capture "{{ACG提名/extra"
const regex = /(^|\n)({{ACG提名\/extra)/g;
let match;
while ((match = regex.exec(regionText)) !== null) {
// Calculate the actual absolute start position of the extra template.
let extraStart = regionStart + match.index + match[1].length;
const { template, endIndex } = ACGATool.parseTemplate(text, extraStart);
templates.push(template);
// Advance regex.lastIndex so that we do not match inside the parsed template.
regex.lastIndex = endIndex - regionStart;
}
return templates;
},
/**
* 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 either a main template ({{ACG提名}}), a nested extra template, or an entry from {{ACG提名2}}.
* @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 either {{ACG提名2 or {{ACG提名 (but not /extra)
const regex = /{{(?:ACG提名2|ACG提名(?!\/extra))/g;
let match;
while ((match = regex.exec(sectionText)) !== null) {
let absolutePos = section.start + match.index;
let { template, endIndex } = ACGATool.parseTemplate(text, absolutePos);
if (template.name.startsWith("ACG提名2")) {
// For ACG提名2, 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: 'acg2'
});
}
}
} else {
// For regular ACG提名
entries.push({
template: template, start: template.location.start, end: template.location.end, type: 'main'
});
if (template.params['額外提名'] && template.params['額外提名'].nestedTemplates) {
for (let nested of template.params['額外提名'].nestedTemplates) {
entries.push({
template: nested, start: nested.location.start, end: nested.location.end, type: 'extra'
});
}
}
}
// Advance regex.lastIndex to avoid re-parsing 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 = ACGATool.getDateSections(text);
const targetSection = sections.find(sec => sec.date === date);
if (!targetSection) return null;
const entries = ACGATool.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 ACG提名2 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 === 'acg2') {
// For ACG提名2 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 = ACGATool.removeComments(reason);
var ruleStatus = {};
if (reason.startsWith('{{ACG提名2/request|ver=1|')) {
reason = reason.slice('{{ACG提名2/request|ver=1|'.length, -2);
}
var reasonList = reason.split(/\s+/);
var ruleSet = ACGATool.NominationRuleSet();
var ruleNames = ruleSet.ruleNames;
var ruleDict = ruleSet.ruleDict;
var ruleAliases = ACGATool.NominationRuleAliases();
for (var rule of reasonList) {
if (rule.endsWith('?')) {
// 最後一個字符是?,可直接去掉?
rule = rule.slice(0, -1);
}
// 名稱和分數的自定義
var ruleNameMod = '', ruleScoreMod = '';
while (rule.endsWith(')') || rule.endsWith(']')) {
if (rule.endsWith(')')) {
var lastLeftParen = rule.lastIndexOf('(');
if (lastLeftParen === -1) break;
ruleNameMod = rule.slice(lastLeftParen + 1, -1);
rule = rule.slice(0, lastLeftParen);
}
if (rule.endsWith(']')) {
var 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('分數不是數字', ruleScoreMod);
return null;
}
}
} else {
// 不在提名規則中,報錯
// console.log('不在提名規則中', rule);
return null;
}
}
return ruleStatus;
},
/**
* 將queried(查詢結果)轉換為nomData(提名數據)。
* @param queried {object} 查詢結果。
* @returns {object} 提名數據。
*/
queried2NomData: function (queried) {
var nomData = {};
if (queried.type === 'main' || queried.type === 'extra') {
var params = queried.template.params;
nomData.pageName = params['條目名稱'].value;
nomData.awarder = params['用戶名稱'].value;
var reasonWikitext = params['提名理由'].value;
nomData.ruleStatus = ACGATool.parseUserReason(reasonWikitext);
if (nomData.ruleStatus == null) {
return null;
}
return nomData;
} else if (queried.type === 'acg2') {
var params = queried.template;
nomData.pageName = ACGATool.removeComments(params['條目名稱'].value);
nomData.awarder = ACGATool.removeComments(params['用戶名稱'].value);
var reasonWikitext = params['提名理由'].value;
nomData.ruleStatus = ACGATool.parseUserReason(reasonWikitext);
// console.log('queried2NomData', nomData);
if (nomData.ruleStatus == null) {
return null;
}
return nomData;
} else {
return null;
}
},
/**
* 點擊編輯按鈕時的事件處理。
* @param date 日期(章節標題)
* @param index 該章節下第X個提名
*/
editNonimation: function (date, index) {
// console.log('editNonimation', date, index);
ACGATool.getFullText().then(function (fulltext) {
ACGATool.queried = ACGATool.queryEntry(fulltext, date, index);
// console.log(ACGATool.queried);
var nomData = ACGATool.queried2NomData(ACGATool.queried);
if (nomData == null) {
mw.notify(
ACGATool.convByVar({ hant: '小工具無法讀取該提名,請手動編輯。', hans: '小工具无法读取该提名,请手动编辑。' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
} else {
ACGATool.showEditNominationDialog(nomData);
}
});
},
/**
* 點擊核對按鈕時的事件處理。
* @param date 日期(章節標題)
* @param index 該章節下第X個提名
*/
checkNonimation: function (date, index) {
// console.log('checkNonimation', date, index);
ACGATool.getFullText().then(function (fulltext) {
ACGATool.queried = ACGATool.queryEntry(fulltext, date, index);
// console.log(ACGATool.queried);
var nomData = ACGATool.queried2NomData(ACGATool.queried);
if (nomData == null) {
mw.notify(
ACGATool.convByVar({ hant: '小工具無法讀取該提名,請手動編輯。', hans: '小工具无法读取该提名,请手动编辑。' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
} else {
ACGATool.showCheckNominationDialog(nomData);
}
});
},
/**
* 點擊登記新提名按鈕時的事件處理。
*/
newNomination: function () {
// console.log('newNomination');
ACGATool.showNewNominationDialog();
},
/**
* 點擊歸檔按鈕時的事件處理。
*/
archiveChapter: function (date) {
// console.log('archiveChapter', date);
OO.ui.confirm(ACGATool.convByVar({ hant: '確定要歸檔「', hans: '确定要归档「' }) + date + ACGATool.convByVar({ hant: '」章節嗎?', hans: '」章节吗?' })).done(function (confirmed) {
if (confirmed) {
// console.log('confirmed');
ACGATool.getFullText().then(function (fulltext) {
var sections = ACGATool.getDateSections(fulltext);
var targetSection = sections.find(sec => sec.date === date);
if (!targetSection) {
mw.notify(
ACGATool.convByVar({ hant: '小工具無法讀取該章節,請手動歸檔。', hans: '小工具无法读取该章节,请手动归档。' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
return;
}
var sectionText = fulltext.slice(targetSection.start, targetSection.end);
var fulltextWithoutSection = fulltext.slice(0, targetSection.start) + fulltext.slice(targetSection.end);
// 找到包含年份的UTC字串,例如 2025年2月13日 (四) 20:58 (UTC)
var utcRegex = /提名人:.+?(\d{4})年(\d{1,2})月(\d{1,2})日 \((.*?)\) (\d{1,2}:\d{2}) \(UTC\)/;
var utcMatch = sectionText.match(utcRegex);
if (!utcMatch) {
mw.notify(
ACGATool.convByVar({ hant: '小工具無法讀取該章節的UTC時間,請手動歸檔。', hans: '小工具无法读取该章节的UTC时间,请手动归档。' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
return;
}
// 獲得 X年Y月
var yearMonth = utcMatch[1] + '年' + utcMatch[2] + '月';
var archiveTarget = 'WikiProject:ACG/維基ACG專題獎/存檔/' + yearMonth;
mw.notify(
ACGATool.convByVar({ hant: '小工具正在歸檔中,請耐心等待。', hans: '小工具正在归档中,请耐心等待。' }),
{ type: 'info', title: ACGATool.convByVar({ hant: '提示', hans: '提示' }), autoHide: false }
);
var api = new mw.Api();
// 先檢查新的存檔頁面是否存在
var response = 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|WikiProject:ACG/維基ACG專題獎/登記處}}\n\n' + sectionText;
} else {
// 直接歸檔,補充空行
sectionText = '\n\n' + sectionText;
}
api.postWithToken('csrf', {
action: 'edit', title: archiveTarget, appendtext: sectionText,
summary: '[[User:SuperGrey/gadgets/ACGATool|' + ACGATool.convByVar({ hant: '歸檔', hans: '归档' })
+ ']]「' + date + '」' + ACGATool.convByVar({ hant: '章節', hans: '章节' }),
}).done(function (data) {
api.postWithToken('csrf', {
action: 'edit', title: 'WikiProject:ACG/維基ACG專題獎/登記處', text: fulltextWithoutSection,
summary: '[[User:SuperGrey/gadgets/ACGATool|' + ACGATool.convByVar({ hant: '歸檔', hans: '归档' })
+ ']]「' + date + '」' + ACGATool.convByVar({ hant: '章節至', hans: '章节至' }) + '「[[' + archiveTarget + ']]」',
}).done(function (data) {
mw.notify(
ACGATool.convByVar({ hant: '小工具已歸檔', hans: '小工具已归档' }) + '「' + date + '」' + ACGATool.convByVar({ hant: '章節至', hans: '章节至' }) + '「[[' + archiveTarget + ']]」',
{ type: 'success', title: ACGATool.convByVar({ hant: '成功', hans: '成功' }) }
);
// 刷新頁面
ACGATool.refreshPage();
}).fail(function (error) {
console.log(error);
mw.notify(
ACGATool.convByVar({ hant: '小工具無法歸檔,請手動歸檔。', hans: '小工具无法归档,请手动归档。' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
});
}).fail(function (error) {
console.log(error);
mw.notify(
ACGATool.convByVar({ hant: '小工具無法歸檔,請手動歸檔。', hans: '小工具无法归档,请手动归档。' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
});
});
});
} else {
// console.log('cancelled');
}
});
},
/**
* 在頁面上添加編輯按鈕。
*/
addEditButtonsToPage: function () {
// 找到<span role"button">登記新提名</span>
var newNominationButton = $('span[role="button"]').filter(function () {
return $(this).text() === '登記新提名' || $(this).text() === '登记新提名';
});
if (newNominationButton.length > 0) {
// 修改原本按鈕的文本為「手動登記新提名」
newNominationButton.text(ACGATool.convByVar({ hant: '手動登記新提名', hans: '手动登记新提名' }));
newNominationButton.removeClass('mw-ui-progressive');
// 父節點的父節點是<span>,在後面加入編輯按鈕
var newNominationButtonParent = newNominationButton.parent().parent();
var editUIButton = $('<span>').addClass('mw-ui-button').addClass('mw-ui-progressive').attr('role', 'button').text(ACGATool.convByVar({ hant: '登記新提名', hans: '登记新提名' }));
var editButton = $('<a>').attr('href', 'javascript:void(0)').append(editUIButton).click(ACGATool.newNomination);
newNominationButtonParent.append(' ').append(editButton);
}
// 識別所有h3
$('div.mw-heading3').each(function () {
var h3div = $(this);
var h3 = h3div.find('h3').first();
var date = h3.text().trim();
var index = 0;
// console.log('h3', date);
// 為h3div底下的span.mw-editsection添加歸檔按鈕
var editsection = h3div.find('span.mw-editsection').first();
var editsectionA = editsection.find('a').first();
$('<a>').attr('href', 'javascript:void(0)').click(function () {
ACGATool.archiveChapter(date);
}).append(ACGATool.convByVar({ hant: '歸檔', hans: '归档' })).insertAfter(editsectionA);
$('<span> | </span>').insertAfter(editsectionA);
h3div.nextUntil('div.mw-heading3', 'table.acgnom-table').each(function () {
// console.log('table', date, index);
var table = $(this);
var rows = table.find('tr').slice(1); // 去掉表頭
var title = "";
rows.each(function () {
var row = $(this);
var th = row.find('th');
if (th.length != 0) {
// 提名行
var nomEntry = th.first();
var nomEntryA = nomEntry.find('a');
if (nomEntryA.length != 0) {
title = nomEntry.find('a').first().attr('title');
} else {
title = nomEntry.text().trim();
}
++index;
// console.log('nom', date, index, title);
// 加入編輯按鈕
var 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;
var editButton = $('<a>').attr('href', 'javascript:void(0)').append(editIcon).click(function () {
ACGATool.editNonimation(date, currentIndex);
});
nomEntry.append(' ').append(editButton);
} else {
// 核對行
var td = row.find('td').first();
var mwNoTalk = td.find('.mw-notalk').first();
var checkIcon = $('<img>').attr('src', 'https://upload.wikimedia.org/wikipedia/commons/3/30/OOjs_UI_icon_highlight-progressive.svg').css({ 'width': '12px' });
const currentIndex = index;
var checkButton = $('<a>').attr('href', 'javascript:void(0)').append(checkIcon).click(function () {
ACGATool.checkNonimation(date, currentIndex);
});
mwNoTalk.append(' ').append(checkButton);
}
});
});
});
},
/**
* 動態寬度文本輸入框。
* @param config
* @constructor
*/
DynamicWidthTextInputWidget: function (config) {
ACGATool.DynamicWidthTextInputWidget.parent.call(this, config);
// Create a hidden element for measuring text width.
this.$measure = $('<span>').css({
position: 'absolute', visibility: 'hidden', whiteSpace: 'pre', // It’s important to mirror the font styles of the input.
fontSize: '14px', fontFamily: 'sans-serif'
}).appendTo(document.body);
// Bind the adjustWidth function to the 'input' event.
this.$input.on('input', this.adjustWidth.bind(this));
},
/**
* 初始化動態寬度文本輸入框。在腳本啟動時執行一次。
*/
initDynamicWidthTextInputWidget: function () {
OO.inheritClass(ACGATool.DynamicWidthTextInputWidget, OO.ui.TextInputWidget);
// add stylesheet for the widget
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; }');
ACGATool.DynamicWidthTextInputWidget.prototype.adjustWidth = function () {
// Get the current value; use placeholder if empty.
var text = this.getValue() || '';
// Update the measurement element.
this.$measure.text(text);
var newWidth = this.$measure.width() + 5; // Add a bit of padding.
// Apply the new width to the input element.
this.$input.css('width', newWidth + 'px');
};
},
/**
* 規則選框,附帶規則名和分數輸入框。
* @param config
* @constructor
*/
RuleCheckboxInputWidget: function (config) {
ACGATool.RuleCheckboxInputWidget.parent.call(this, config);
this.ruleset = config.ruleset;
this.nomidx = config.nomidx;
this.check = config.check || false;
if (!ACGATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule]) {
ACGATool.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(ACGATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].selected);
if (!ACGATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].desc) {
ACGATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].desc = this.ruleset.desc;
}
ACGATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].ogDesc = this.ruleset.desc;
if (!ACGATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].score) {
ACGATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].score = this.ruleset.score;
}
ACGATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].maxScore = this.ruleset.score;
}
this.ruleInputWidget = new ACGATool.DynamicWidthTextInputWidget({
value: ACGATool.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 ACGATool.DynamicWidthTextInputWidget({
value: ACGATool.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(ACGATool.RuleCheckboxInputWidget, OO.ui.CheckboxInputWidget);
ACGATool.RuleCheckboxInputWidget.prototype.handleCheckboxChange = function (isChecked) {
ACGATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].selected = isChecked;
var 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();
}
}
ACGATool.RuleCheckboxInputWidget.prototype.handleRuleInputChange = function (newValue) {
ACGATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].desc = newValue;
// console.log(ACGATool.nominations[this.nomidx].ruleStatus);
}
ACGATool.RuleCheckboxInputWidget.prototype.handleScoreInputChange = function (newValue) {
ACGATool.nominations[this.nomidx].ruleStatus[this.ruleset.rule].score = parseFloat(newValue);
// console.log(ACGATool.nominations[this.nomidx].ruleStatus);
}
},
/**
* 生成提名表單。
* @param {object} nomData 提名資料(非必須)。無資料時為新提名。
* @returns {OO.ui.FieldsetLayout} 提名表單。
*/
generateNominationFieldset: function (nomData) {
var awarder, pageName, ruleStatus;
if (nomData) {
awarder = nomData.awarder;
pageName = nomData.pageName;
ruleStatus = nomData.ruleStatus;
} else {
awarder = mw.config.get('wgUserName');
pageName = '';
ruleStatus = {};
}
const currentNomidx = ACGATool.nominations.length;
ACGATool.nominations.push({
awarder: awarder, pageName: pageName, ruleStatus: ruleStatus
});
// console.log('generateNominationFieldset', ACGATool.nominations);
// 得分者
var userNameInput = new OO.ui.TextInputWidget({ value: ACGATool.nominations[currentNomidx].awarder });
userNameInput.on('change', function (newValue) {
ACGATool.nominations[currentNomidx].awarder = newValue;
});
var userNameField = new OO.ui.FieldLayout(userNameInput, {
label: ACGATool.convByVar({ hant: '得分者', hans: '得分者' }),
align: 'left'
});
// 條目名稱
var pageNameInput = new OO.ui.TextInputWidget({ value: ACGATool.nominations[currentNomidx].pageName });
pageNameInput.on('change', function (newValue) {
ACGATool.nominations[currentNomidx].pageName = newValue;
});
var pageNameField = new OO.ui.FieldLayout(pageNameInput, {
label: ACGATool.convByVar({ hant: '得分條目', hans: '得分条目' }),
align: 'left',
help: ACGATool.convByVar({ hant: '可填「他薦」', hans: '可填「他荐」' }),
helpInline: true
});
// 提名理由
var reasonFieldsetLayout = new OO.ui.FieldsetLayout();
reasonFieldsetLayout.$element.css({
'padding-top': '15px' // 讓理由區域與上方的輸入框有點距離
});
var NominationRules = ACGATool.NominationRules();
for (var i = 0; i < NominationRules.length; i++) {
var ruleGroup = NominationRules[i];
var ruleGroupFieldset = new OO.ui.FieldsetLayout({
label: ruleGroup.group, align: 'top',
help: ruleGroup.explanation, helpInline: true
});
if (ruleGroup.rules) {
var ruleItems = [];
for (var j = 0; j < ruleGroup.rules.length; j++) {
var rule = ruleGroup.rules[j];
var ruleCheckbox = new ACGATool.RuleCheckboxInputWidget({
value: rule.rule, ruleset: rule, nomidx: currentNomidx
});
ruleItems.push(ruleCheckbox);
}
var horizontalLayout = new OO.ui.HorizontalLayout({
items: ruleItems
});
ruleGroupFieldset.addItems(horizontalLayout);
} else {
// 處理 (5) 條目評審
for (var j = 0; j < ruleGroup.tabs.length; j++) {
var tab = ruleGroup.tabs[j];
var tabFieldset = new OO.ui.FieldsetLayout({
label: tab.tab
});
tabFieldset.$element.find('legend > span').css({ 'font-size': '1.05em' });
var ruleItems = [];
for (var k = 0; k < tab.rules.length; k++) {
var rule = tab.rules[k];
var ruleCheckbox = new ACGATool.RuleCheckboxInputWidget({
value: rule.rule, ruleset: rule, nomidx: currentNomidx
});
ruleItems.push(ruleCheckbox);
}
var horizontalLayout = new OO.ui.HorizontalLayout({
items: ruleItems
});
tabFieldset.addItems(horizontalLayout);
ruleGroupFieldset.addItems([tabFieldset]);
}
}
reasonFieldsetLayout.addItems([ruleGroupFieldset]);
}
var nominationFieldset = new OO.ui.FieldsetLayout({
items: [userNameField, pageNameField, reasonFieldsetLayout]
});
nominationFieldset.$element.css({
'margin-top': 0
});
if (!nomData) {
// 頂部的 <hr>
var hr = $('<hr>').css({
'margin-top': '15px', 'margin-bottom': '15px'
});
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) {
var awarder = nomData.awarder;
var pageName = nomData.pageName;
var ruleStatus = nomData.ruleStatus;
ACGATool.nominations.push({
awarder: awarder, pageName: pageName, ruleStatus: ruleStatus, invalid: false, message: ''
});
// 得分者
var userNameLabelWidget = new OO.ui.LabelWidget({
label: $('<a>').attr('href', mw.util.getUrl('User:' + awarder)).text(awarder),
});
var userTalkLabelWidget = new OO.ui.LabelWidget({
label: $('<a>').attr('href', mw.util.getUrl('User talk:' + awarder)).text(ACGATool.convByVar({ hant: '討論', hans: '讨论' })),
});
var userContribsLabelWidget = new OO.ui.LabelWidget({
label: $('<a>').attr('href', mw.util.getUrl('Special:用户贡献/' + awarder)).text(ACGATool.convByVar({ hant: '貢獻', hans: '贡献' })),
});
var userNameHorizontalLayout = new OO.ui.HorizontalLayout({
items: [userNameLabelWidget, new OO.ui.LabelWidget({ label: '(' }), userTalkLabelWidget, new OO.ui.LabelWidget({ label: '·' }), userContribsLabelWidget, new OO.ui.LabelWidget({ label: ')' })]
});
userNameHorizontalLayout.$element.css({
'gap': '4px', 'width': '80%', 'flex-shrink': '0', 'flex-wrap': 'wrap',
});
var userNameLabelWidget = new OO.ui.LabelWidget({
label: ACGATool.convByVar({ hant: '得分者', hans: '得分者' }),
});
userNameLabelWidget.$element.css({
'flex-grow': 1, 'align-self': 'stretch'
});
var userNameField = new OO.ui.HorizontalLayout({
items: [userNameLabelWidget, userNameHorizontalLayout]
});
// 條目名稱
var pageNameHorizontalLayout, pageNameLabelWidget;
if (pageName === '他薦' || pageName === '他荐') {
pageNameLabelWidget = new OO.ui.LabelWidget({
label: ACGATool.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),
});
var pageTalkLabelWidget = new OO.ui.LabelWidget({
label: $('<a>').attr('href', mw.util.getUrl('Talk:' + pageName)).text(ACGATool.convByVar({ hant: '討論', hans: '讨论' })),
});
var pageHistoryLabelWidget = new OO.ui.LabelWidget({
label: $('<a>').attr('href', mw.util.getUrl(pageName, { action: 'history' })).text(ACGATool.convByVar({ hant: '歷史', hans: '历史' })),
});
var pagesLinkedToPageLabelWidget = new OO.ui.LabelWidget({
label: $('<a>').attr('href', mw.util.getUrl('Special:链入页面/' + pageName)).text(ACGATool.convByVar({ hant: '連入', hans: '链入' })),
});
// 更多頁面資訊 from XTools
var xtoolsData = await ACGATool.getXToolsInfo(pageName);
var 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',
});
var pageLabelLabelWidget = new OO.ui.LabelWidget({
label: ACGATool.convByVar({ hant: '得分條目', hans: '得分条目' }),
});
pageLabelLabelWidget.$element.css({
'flex-grow': 1, 'align-self': 'stretch'
});
var pageNameField = new OO.ui.HorizontalLayout({
items: [pageLabelLabelWidget, pageNameHorizontalLayout]
});
pageNameField.$element.css({
'margin-top': '15px'
});
// 提名無效
var invalidToggleSwitchWidget = new OO.ui.ToggleSwitchWidget({
value: false,
});
invalidToggleSwitchWidget.on('change', function (isChecked) {
ACGATool.nominations[0].invalid = isChecked;
});
var invalidField = new OO.ui.FieldLayout(invalidToggleSwitchWidget, {
label: ACGATool.convByVar({ hant: '提名無效', hans: '提名无效' }),
align: 'left'
});
invalidField.$element.css({
'margin-top': '15px'
});
invalidField.$element.addClass('checklist-field');
// 提名理由
var reasonField = new OO.ui.HorizontalLayout();
reasonField.$element.css({
'margin-top': '15px'
});
var reasonLabelWidget = new OO.ui.LabelWidget({
label: ACGATool.convByVar({ hant: '提名理由', hans: '提名理由' }),
});
reasonLabelWidget.$element.css({
'flex-grow': 1, 'align-self': 'stretch'
});
var ruleItems = [];
var ruleDict = ACGATool.NominationRuleSet().ruleDict;
for (var ruleName in ruleStatus) {
var rule = ruleDict[ruleName];
var ruleCheckbox = new ACGATool.RuleCheckboxInputWidget({
value: rule.rule, ruleset: rule, nomidx: 0, check: true
});
ruleItems.push(ruleCheckbox);
}
var horizontalLayout = new OO.ui.HorizontalLayout({
items: ruleItems
});
horizontalLayout.$element.css({
'width': '80%', 'flex-shrink': '0', 'flex-wrap': 'wrap',
});
reasonField.addItems([reasonLabelWidget, horizontalLayout]);
// 附加說明
var messageInput = new OO.ui.MultilineTextInputWidget({
autosize: true, rows: 1
});
messageInput.on('change', function (newValue) {
ACGATool.nominations[0].message = newValue;
});
messageInput.on('resize', function () {
ACGATool.checkNominationDialog.updateSize();
});
var messageField = new OO.ui.FieldLayout(messageInput, {
label: ACGATool.convByVar({ hant: '附加說明', hans: '附加说明' }),
align: 'left',
help: ACGATool.convByVar({ hant: '可不填;無須簽名', hans: '可不填;无须签名' }),
helpInline: true
});
messageField.$element.css({
'margin-top': '15px'
});
messageField.$element.addClass('checklist-field');
var 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) {
// 拼湊提名理由
var reasonText = '', unselectedReasonText = '';
var reasonScore = 0;
for (var rule in ruleStatus) {
if (check ? true : ruleStatus[rule].selected) {
if (isNaN(ruleStatus[rule].score) || ruleStatus[rule].score < 0) {
mw.notify(
ACGATool.convByVar({ hant: '規則', hans: '规则' }) + '「' + rule + '」' + ACGATool.convByVar({ hant: '的分數不合法,請檢查!', hans: '的分数不合法,请检查!' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
return null;
}
// if (ruleStatus[rule].score > ruleStatus[rule].maxScore) {
// mw.notify(
// ACGATool.convByVar({ hant: '規則', hans: '规则' }) + '「' + rule + '」' + ACGATool.convByVar({ hant: '的分數超過最大值', hans: '的分数超过最大值' }) + '「' + ruleStatus[rule].maxScore + '」' + ACGATool.convByVar({ hant: ',請檢查!', hans: ',请检查!' }),
// { type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
// );
// return null;
// }
if (ruleStatus[rule].selected) {
reasonText += ' ' + rule;
if (ruleStatus[rule].desc !== ruleStatus[rule].ogDesc) {
// console.log('desc', ruleStatus[rule].desc, ruleStatus[rule].ogDesc);
reasonText += '(' + ruleStatus[rule].desc + ')';
}
if (ruleStatus[rule].score !== ruleStatus[rule].maxScore) {
reasonText += '[' + ruleStatus[rule].score + ']';
}
reasonScore += ruleStatus[rule].score;
} else if (check) {
unselectedReasonText += ' ' + rule;
if (ruleStatus[rule].desc !== ruleStatus[rule].ogDesc) {
// console.log('desc', ruleStatus[rule].desc, ruleStatus[rule].ogDesc);
unselectedReasonText += '(' + ruleStatus[rule].desc + ')';
}
if (ruleStatus[rule].score !== ruleStatus[rule].maxScore) {
unselectedReasonText += '[' + ruleStatus[rule].score + ']';
}
}
}
}
reasonText = reasonText.trim();
unselectedReasonText = unselectedReasonText.trim();
if (check) {
return {
reasonText: reasonText,
unselectedReasonText: unselectedReasonText,
reasonScore: reasonScore
};
}
return reasonText;
},
/**
* 保存新提名。
* @returns {Promise<boolean>} 是否成功提交。
*/
saveNewNomination: async function () {
var proposedWikitext = '{{ACG提名2';
for (var i = 0; i < ACGATool.nominations.length; i++) {
var nomination = ACGATool.nominations[i];
if (nomination.awarder === '' || nomination.pageName === '') {
mw.notify(
ACGATool.convByVar({ hant: '得分者或得分條目未填寫,請檢查!', hans: '得分者或得分条目未填写,请检查!' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
return true;
}
var reasonText = ACGATool.generateReason(nomination.ruleStatus);
if (reasonText == null) {
return true;
}
if (reasonText === '') {
mw.notify(
ACGATool.convByVar({ hant: '未選擇任何評審規則,請檢查!', hans: '未选择任何评审规则,请检查!' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
return true;
}
proposedWikitext += '\n|條目名稱' + (i + 1) + ' = ' + nomination.pageName.trim();
proposedWikitext += '\n|用戶名稱' + (i + 1) + ' = ' + nomination.awarder.trim();
proposedWikitext += '\n|提名理由' + (i + 1) + ' = {{ACG提名2/request|ver=1|' + reasonText + '}}';
proposedWikitext += '\n|核對用' + (i + 1) + ' = {{ACG提名2/check|ver=1|}}';
}
const signature = '~' + '~' + '~' + '~';
proposedWikitext += "\n}}\n'''提名人:'''" + signature;
// 附加說明
var message = ACGATool.newNominationDialog.messageInput.getValue().trim();
if (message !== '') {
proposedWikitext += "\n: {{說明}}:" + message + '--' + signature;
}
// 是否已有今日的date
var today = new Date();
var todayDate = (today.getMonth() + 1) + '月' + today.getDate() + '日';
var fulltext = await ACGATool.getFullText();
if (!fulltext.includes('=== ' + todayDate + ' ===')) {
// 沒有今日的date,先新增一個
proposedWikitext = '=== ' + todayDate + ' ===\n' + proposedWikitext;
}
// 提交
var api = new mw.Api();
var response = await api.postWithToken('csrf', {
action: 'edit', title: 'WikiProject:ACG/維基ACG專題獎/登記處', appendtext: '\n' + proposedWikitext, summary: '[[User:SuperGrey/gadgets/ACGATool|新提名]]',
});
if (response.edit.result === 'Success') {
mw.notify(
ACGATool.convByVar({ hant: '新提名已成功提交!', hans: '新提名已成功提交!' }),
{ title: ACGATool.convByVar({ hant: '成功', hans: '成功' }), autoHide: true }
);
ACGATool.refreshPage();
return false;
} else {
mw.notify(
ACGATool.convByVar({ hant: '新提名提交失敗:', hans: '新提名提交失败:' }) + response.edit.result,
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
return true;
}
},
/**
* 保存修改提名。
* @returns {Promise<boolean>} 是否成功提交。
*/
saveModifiedNomination: async function () {
var nomination = ACGATool.nominations[0];
if (nomination.awarder === '' || nomination.pageName === '') {
mw.notify(
ACGATool.convByVar({ hant: '得分者或得分條目未填寫,請檢查!', hans: '得分者或得分条目未填写,请检查!' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
return true;
}
var reasonText = ACGATool.generateReason(nomination.ruleStatus);
if (reasonText == null) {
return true;
}
if (reasonText === '') {
mw.notify(
ACGATool.convByVar({ hant: '未選擇任何評審規則,請檢查!', hans: '未选择任何评审规则,请检查!' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
return true;
}
var fulltext = await ACGATool.getFullText(), updatedText;
if (ACGATool.queried.type === 'main' || ACGATool.queried.type === 'extra') {
var changes = {
'條目名稱': nomination.pageName, '用戶名稱': nomination.awarder, '提名理由': '{{ACG提名2/request|ver=1|' + reasonText + '}}'
};
updatedText = ACGATool.updateEntryParameters(fulltext, ACGATool.queried, changes);
} else if (ACGATool.queried.type === 'acg2') {
var changes = {
'條目名稱': nomination.pageName, '用戶名稱': nomination.awarder, '提名理由': '{{ACG提名2/request|ver=1|' + reasonText + '}}'
};
updatedText = ACGATool.updateEntryParameters(fulltext, ACGATool.queried, changes);
}
if (updatedText === fulltext) {
mw.notify(
ACGATool.convByVar({ hant: '提名並未改動!', hans: '提名并未改动!' }),
{ type: 'warn', title: ACGATool.convByVar({ hant: '提示', hans: '提示' }) }
);
return true;
}
var api = new mw.Api();
var response = await api.postWithToken('csrf', {
action: 'edit', title: 'WikiProject:ACG/維基ACG專題獎/登記處', text: updatedText, summary: '[[User:SuperGrey/gadgets/ACGATool|編輯提名]]',
});
if (response.edit.result === 'Success') {
mw.notify(
ACGATool.convByVar({ hant: '提名已成功修改!', hans: '提名已成功修改!' }),
{ title: ACGATool.convByVar({ hant: '成功', hans: '成功' }), autoHide: true }
);
ACGATool.refreshPage();
return false;
} else {
mw.notify(
ACGATool.convByVar({ hant: '提名修改失敗:', hans: '提名修改失败:' }) + response.edit.result,
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
return true;
}
},
/**
* 保存核分。
* @returns {Promise<boolean>} 是否成功提交。
*/
saveNominationCheck: async function () {
var nomination = ACGATool.nominations[0];
// console.log('nomination', nomination);
var checkText = '{{ACG提名2/check|ver=1|';
// 是否選擇「提名無效」
if (nomination.invalid) {
checkText += '0';
var reasonScore = 0;
} else {
var reasonObject = ACGATool.generateReason(nomination.ruleStatus, true);
if (reasonObject == null) {
return true;
}
var reasonText = reasonObject.reasonText;
var unselectedReasonText = reasonObject.unselectedReasonText;
var reasonScore = reasonObject.reasonScore;
checkText += reasonText;
if (unselectedReasonText !== '') {
checkText += '|no=' + unselectedReasonText;
}
}
checkText += '}}' + nomination.message;
var signature = '~' + '~' + '~' + '~';
checkText += '--' + signature;
// console.log(checkText);
var fulltext = await ACGATool.getFullText(), updatedText;
if (ACGATool.queried.type === 'main' || ACGATool.queried.type === 'extra') {
var changes = {
'核對用': checkText
};
updatedText = ACGATool.updateEntryParameters(fulltext, ACGATool.queried, changes);
} else if (ACGATool.queried.type === 'acg2') {
var changes = {
'核對用': checkText
};
updatedText = ACGATool.updateEntryParameters(fulltext, ACGATool.queried, changes);
}
if (updatedText === fulltext) {
mw.notify(
ACGATool.convByVar({ hant: '核分並未改動!', hans: '核分并未改动!' }),
{ type: 'warn', title: ACGATool.convByVar({ hant: '提示', hans: '提示' }) }
);
return true;
}
var api = new mw.Api();
var response = await api.postWithToken('csrf', {
action: 'edit', title: 'WikiProject:ACG/維基ACG專題獎/登記處', text: updatedText, summary: '[[User:SuperGrey/gadgets/ACGATool|核對分數]]',
});
if (response.edit.result === 'Success') {
mw.notify(
ACGATool.convByVar({ hant: '核分已成功提交!即將自動打開 Module:ACGaward/list,請在其中手動更新得分排行榜。', hans: '核分已成功提交!即将自动打开 Module:ACGaward/list,请在其中手动更新得分排行榜。' }),
{ title: ACGATool.convByVar({ hant: '成功', hans: '成功' }), autoHide: false }
);
// 立即打開 Module:ACGaward/list
if (reasonScore > 0) {
window.open(mw.util.getUrl('Module:ACGaward/list') + '?action=edit&summary=' + nomination.awarder + "+X%2B" + reasonScore.toString() + "%3DY");
}
// 2秒後刷新頁面
ACGATool.refreshPage();
return false;
} else {
mw.notify(
ACGATool.convByVar({ hant: '核分提交失敗:', hans: '核分提交失败:' }) + response.edit.result,
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
return true;
}
},
/**
* 刷新頁面。
*/
refreshPage: function () {
// 2秒後刷新頁面
setTimeout(function () {
location.reload();
}, 2000);
},
/**
* 新提名對話框。
* @param config
* @constructor
*/
NewNominationDialog: function (config) {
ACGATool.NewNominationDialog.super.call(this, config);
},
/**
* 初始化新提名對話框。在腳本啟動時執行一次。
*/
initNewNominationDialog: function () {
OO.inheritClass(ACGATool.NewNominationDialog, OO.ui.ProcessDialog);
ACGATool.NewNominationDialog.static.name = 'NewNominationDialog';
ACGATool.NewNominationDialog.static.title = ACGATool.convByVar({ hant: '新提名(維基ACG專題獎小工具)', hans: '新提名(维基ACG专题奖小工具)' });
ACGATool.NewNominationDialog.static.actions = [
{
action: 'save',
label: ACGATool.convByVar({ hant: '儲存', hans: '储存' }),
flags: ['primary', 'progressive']
},
{
action: 'cancel',
label: ACGATool.convByVar({ hant: '取消', hans: '取消' }),
flags: 'safe'
},
{
action: 'add',
label: ACGATool.convByVar({ hant: '額外提名 + 1', hans: '额外提名 + 1' })
},
{
action: 'minus',
label: ACGATool.convByVar({ hant: '額外提名 − 1', hans: '额外提名 − 1' })
}
];
ACGATool.NewNominationDialog.prototype.initialize = function () {
ACGATool.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: ACGATool.convByVar({ hant: '附加說明', hans: '附加说明' }),
align: 'top',
help: ACGATool.convByVar({ hant: '可不填;無須簽名', hans: '可不填;无须签名' }),
helpInline: true
});
this.messageInputFieldSet = new OO.ui.FieldsetLayout({
items: [this.messageInputField]
});
this.content.addItems([this.messageInputFieldSet, ACGATool.generateNominationFieldset()]);
this.panel.$element.append(this.content.$element);
this.$body.append(this.panel.$element);
};
ACGATool.NewNominationDialog.prototype.onMessageInputResize = function () {
this.updateSize();
};
ACGATool.NewNominationDialog.prototype.getBodyHeight = function () {
return this.panel.$element.outerHeight(true);
};
ACGATool.NewNominationDialog.prototype.getActionProcess = function (action) {
if (action === 'save') {
return new OO.ui.Process(async function () {
let response = await ACGATool.saveNewNomination();
if (!response) {
this.close();
}
}, this);
} else if (action === 'add') {
return new OO.ui.Process(function () {
// 新增一個提名
var newFieldset = ACGATool.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(
ACGATool.convByVar({ hant: '至少需要一個提名!', hans: '至少需要一个提名!' }),
{ type: 'error', title: ACGATool.convByVar({ hant: '錯誤', hans: '错误' }) }
);
return;
}
// 移除最後一個提名
this.content.removeItems([this.content.items[this.content.items.length - 1]]);
ACGATool.nominations.pop();
this.updateSize();
}, this);
} else if (action === 'cancel') {
return new OO.ui.Process(function () {
this.close();
}, this);
}
return ACGATool.NewNominationDialog.super.prototype.getActionProcess.call(this, action);
};
ACGATool.NewNominationDialog.prototype.getTearnDownProcess = function (data) {
return ACGATool.NewNominationDialog.super.prototype.getTearnDownProcess.call(this, data);
};
},
/**
* 顯示新提名對話框。
*/
showNewNominationDialog: function () {
// 清空原有的items
ACGATool.nominations.length = 0;
ACGATool.newNominationDialog = new ACGATool.NewNominationDialog({ size: 'large', padded: true, scrollable: true });
ACGATool.windowManager.addWindows([ACGATool.newNominationDialog]);
ACGATool.windowManager.openWindow(ACGATool.newNominationDialog);
},
/**
* 編輯提名對話框。
* @param config
* @constructor
*/
EditNominationDialog: function (config) {
ACGATool.EditNominationDialog.super.call(this, config);
},
/**
* 初始化編輯提名對話框。在腳本啟動時執行一次。
*/
initEditNominationDialog: function () {
OO.inheritClass(ACGATool.EditNominationDialog, OO.ui.ProcessDialog);
ACGATool.EditNominationDialog.static.name = 'EditNominationDialog';
ACGATool.EditNominationDialog.static.title = ACGATool.convByVar({ hant: '編輯提名(維基ACG專題獎小工具)', hans: '编辑提名(维基ACG专题奖小工具)' });
ACGATool.EditNominationDialog.static.actions = [
{
action: 'save',
label: ACGATool.convByVar({ hant: '儲存', hans: '储存' }),
flags: ['primary', 'progressive']
},
{
action: 'cancel',
label: ACGATool.convByVar({ hant: '取消', hans: '取消' }),
flags: 'safe'
}
];
ACGATool.EditNominationDialog.prototype.initialize = function () {
ACGATool.EditNominationDialog.super.prototype.initialize.call(this);
this.panel = new OO.ui.PanelLayout({
padded: true, expanded: false
});
this.panel.$element.append(ACGATool.editNominationDialogContent.$element);
this.$body.append(this.panel.$element);
};
ACGATool.EditNominationDialog.prototype.onMessageInputResize = function () {
this.updateSize();
};
ACGATool.EditNominationDialog.prototype.getBodyHeight = function () {
return this.panel.$element.outerHeight(true);
};
ACGATool.EditNominationDialog.prototype.getActionProcess = function (action) {
if (action === 'save') {
return new OO.ui.Process(async function () {
let response = await ACGATool.saveModifiedNomination();
if (!response) {
this.close();
}
}, this);
} else if (action === 'cancel') {
return new OO.ui.Process(function () {
this.close();
}, this);
}
return ACGATool.EditNominationDialog.super.prototype.getActionProcess.call(this, action);
};
ACGATool.EditNominationDialog.prototype.getTearnDownProcess = function (data) {
return ACGATool.EditNominationDialog.super.prototype.getTearnDownProcess.call(this, data);
};
},
/**
* 顯示編輯提名對話框。
* @param nomData 提名資料。
*/
showEditNominationDialog: function (nomData) {
// 清空原有的items
ACGATool.editNominationDialogContent.clearItems();
ACGATool.nominations.length = 0;
ACGATool.editNominationDialog = new ACGATool.EditNominationDialog({ size: 'large', padded: true, scrollable: true });
// 加入新的items
ACGATool.editNominationDialogContent.addItems([ACGATool.generateNominationFieldset(nomData)]);
ACGATool.windowManager.addWindows([ACGATool.editNominationDialog]);
ACGATool.windowManager.openWindow(ACGATool.editNominationDialog);
},
/**
* 核分提名對話框。
* @param config
* @constructor
*/
CheckNominationDialog: function (config) {
ACGATool.CheckNominationDialog.super.call(this, config);
},
/**
* 初始化核分提名對話框。在腳本啟動時執行一次。
*/
initCheckNominationDialog: function () {
OO.inheritClass(ACGATool.CheckNominationDialog, OO.ui.ProcessDialog);
ACGATool.CheckNominationDialog.static.name = 'CheckNominationDialog';
ACGATool.CheckNominationDialog.static.title = ACGATool.convByVar({ hant: '核對分數(維基ACG專題獎小工具)', hans: '核对分数(维基ACG专题奖小工具)' });
ACGATool.CheckNominationDialog.static.actions = [
{
action: 'save',
label: ACGATool.convByVar({ hant: '儲存', hans: '储存' }),
flags: ['primary', 'progressive']
},
{
action: 'cancel',
label: ACGATool.convByVar({ hant: '取消', hans: '取消' }),
flags: 'safe'
}
];
ACGATool.CheckNominationDialog.prototype.initialize = function () {
ACGATool.CheckNominationDialog.super.prototype.initialize.call(this);
this.panel = new OO.ui.PanelLayout({
padded: true, expanded: false
});
this.panel.$element.append(ACGATool.checkNominationDialogContent.$element);
this.$body.append(this.panel.$element);
};
ACGATool.CheckNominationDialog.prototype.onMessageInputResize = function () {
this.updateSize();
};
ACGATool.CheckNominationDialog.prototype.getBodyHeight = function () {
return this.panel.$element.outerHeight(true);
};
ACGATool.CheckNominationDialog.prototype.getActionProcess = function (action) {
if (action === 'save') {
return new OO.ui.Process(async function () {
let response = await ACGATool.saveNominationCheck();
if (!response) {
this.close();
}
}, this);
} else if (action === 'cancel') {
return new OO.ui.Process(function () {
this.close();
}, this);
}
return ACGATool.CheckNominationDialog.super.prototype.getActionProcess.call(this, action);
};
ACGATool.CheckNominationDialog.prototype.getTearnDownProcess = function (data) {
return ACGATool.CheckNominationDialog.super.prototype.getTearnDownProcess.call(this, data);
};
mw.util.addCSS('.checklist-field .oo-ui-fieldLayout-field { width: 80% !important; }');
},
/**
* 顯示核分提名對話框。
* @param nomData 提名資料。
*/
showCheckNominationDialog: function (nomData) {
// 清空原有的items
ACGATool.checkNominationDialogContent.clearItems();
ACGATool.nominations.length = 0;
ACGATool.checkNominationDialog = new ACGATool.CheckNominationDialog({ size: 'large', padded: true, scrollable: true });
// 加入新的items
ACGATool.generateChecklistFieldset(nomData).then(function (field) {
ACGATool.checkNominationDialogContent.addItems([field]);
ACGATool.windowManager.addWindows([ACGATool.checkNominationDialog]);
ACGATool.windowManager.openWindow(ACGATool.checkNominationDialog);
});
},
/**
* 腳本入口。
*/
init: function () {
ACGATool.pageName = mw.config.get('wgPageName');
if (ACGATool.pageName !== 'WikiProject:ACG/維基ACG專題獎' && ACGATool.pageName !== 'WikiProject:ACG/維基ACG專題獎/登記處') {
// 非目標頁面,不執行
return;
}
mw.loader.using('ext.gadget.HanAssist').then((require) => {
const { convByVar } = require('ext.gadget.HanAssist');
ACGATool.convByVar = convByVar;
// Initialize OOUI custom widgets
ACGATool.initDynamicWidthTextInputWidget();
ACGATool.initRuleCheckboxInputWidget();
// Initialize dialogs
ACGATool.initNewNominationDialog();
ACGATool.initEditNominationDialog();
ACGATool.initCheckNominationDialog();
// Append the window manager element to the body
ACGATool.windowManager = new OO.ui.WindowManager();
$(document.body).append(ACGATool.windowManager.$element);
// 添加提名按鈕
ACGATool.addEditButtonsToPage();
});
},
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 // 簡繁轉換
};
ACGATool.init();