MediaWiki:Gadget-interfaceVariantConverter.js:修订间差异
外观
AnnAngela-dbot(留言 | 贡献) 代码变动:a157f65a - chore by U:星海子 标签:由机器人或全自动脚本执行的操作 |
AnnAngela-dbot(留言 | 贡献) 代码变动:a157f65a - chore by U:星海子 标签:由机器人或全自动脚本执行的操作 |
||
| 第8行: | 第8行: | ||
/* <pre> */ | /* <pre> */ | ||
"use strict"; | "use strict"; | ||
$(() => (async () => { | |||
var _a, _b; | |||
const pagename = mw.config.get("wgPageName"); | |||
const username = mw.config.get("wgUserName"); | |||
const pageid = mw.config.get("wgArticleId"); | |||
const basepage = pagename.replace(/\/.*?$/, ""); | |||
const api = new mw.Api(), zhAPI = /m?zh\.moegirl\.org\.cn/.test(location.hostname) ? api : new mw.ForeignApi("https://mzh.moegirl.org.cn/api.php", { anonymous: true }); | |||
const lrAivc = $.extend({ | |||
main: ["zh-cn", "zh-tw", "zh-hk"], | |||
dependent: { | |||
"(main)": "zh-cn", | |||
"zh-hans": "zh-cn", | |||
"zh-hant": "zh-tw", | |||
}, | |||
noteTA: { | |||
G1: "MediaWiki", | |||
}, | |||
autoPopulate: true, | |||
useOpenCC: true, | |||
manualAction: { | |||
"zh-hk": (t) => t.replaceAll("户", "戶"), | |||
"zh-tw": (t) => t.replaceAll("名稱空間", "命名空間").replaceAll("記憶體", "內存"), | |||
}, | |||
watchlist: "nochange", | |||
}, window.lr_aivc); | |||
let prepopContent = ""; | |||
try { | try { | ||
prepopContent = lrAivc.autoPopulate | |||
? (await api.get({ | |||
action: "parse", | |||
assertuser: username, | |||
pageid, | |||
prop: "wikitext", | |||
})).parse.wikitext["*"] | |||
: ""; | |||
} | } | ||
catch { } | |||
const toParams = (obj) => Object.entries(obj).map(([k, v]) => `${k}=${v}`).join("|"); | |||
} | const variantPage = (variant) => variant === "(main)" ? `${basepage}` : `${basepage}/${variant}`; | ||
const REGEXP = { | |||
lcMarker: /-{([\s\S]*?)}-/g, | |||
lcMarkerEsc: /-\\{([\s\S]*?)}\\-/g, | |||
nowiki: /<nowiki>([\s\S]*?)<\/nowiki>/g, | |||
link: /\[\[([\s\S]*?)(?:#([\s\S]*?))?(\|[\s\S]*?)?\]\]/g, | |||
extLink: /([^[])\[([^[]+?)( [\s\S]+?)?\]([^\]])/g, | |||
template: /\{\{([\s\S]*?)\}\}/g, | |||
htmlEntity: /&([a-zA-Z0-9#]+);/g, | |||
noOCC: /<!--noOCC-->([\s\S]*?)<!--\/noOCC-->/gi, | |||
}; | |||
const escapeWikitext = (original) => { | |||
const nowikis = [], mappings = []; | |||
let replaced = original; | |||
replaced = replaced.replace(REGEXP.lcMarker, (_, content) => `-\\{${content}}\\-`); | |||
replaced = replaced.replace(REGEXP.nowiki, (_, content) => `<nowiki>${nowikis.push(content) - 1}</nowiki>`); | |||
replaced = replaced.replace(REGEXP.link, (match, target, anchor, text) => anchor || text ? `[[${text ? mappings.push(target) - 1 : target}${anchor ? `#${mappings.push(anchor) - 1}` : ""}${text || ""}]]` : match); | |||
replaced = replaced.replace(REGEXP.extLink, (_, before, target, text, after) => `${before}[${mappings.push(target) - 1}${text || ""}]${after}`); | |||
replaced = replaced.replace(REGEXP.template, (_, params) => { | |||
const paramList = params.split("|"); | |||
mappings.push(paramList[0]); | |||
paramList.shift(); | |||
return `${paramList.reduce((acc, param) => { | |||
const [first, ...rest] = param.split("="); | |||
return rest.length ? `${acc}|${mappings.push(first) - 1}=${rest.join("=")}` : `${acc}|${param}`; | |||
}, `{{${mappings.length - 1}`)}}}`; | |||
}); | }); | ||
replaced = replaced.replace(REGEXP.lcMarkerEsc, (_, content) => `-\\{${mappings.push(content || "") - 1}}\\-`); | |||
replaced = replaced.replace(REGEXP.htmlEntity, (_, entity) => `&${mappings.push(entity) - 1};`); | |||
replaced = replaced.replace(REGEXP.nowiki, (_, index) => `<nowiki><nowiki>${nowikis[index]}</nowiki></nowiki>`); | |||
return { replaced, mappings }; | |||
}; | }; | ||
const restoreWikitext = (original, mappings) => { | |||
const nowikis = []; | |||
let replaced = original; | |||
replaced = replaced.replace(REGEXP.nowiki, (_, content) => `<nowiki>${nowikis.push(content) - 1}</nowiki>`); | |||
replaced = replaced.replace(REGEXP.htmlEntity, (_, index) => `&${mappings[index]};`); | |||
replaced = replaced.replace(REGEXP.template, (_, params) => { | |||
const paramList = params.split("|"); | |||
const name = mappings[paramList[0]]; | |||
paramList.shift(); | |||
return `${paramList.reduce((acc, param) => { | |||
const [first, ...rest] = param.split("="); | |||
return rest.length ? `${acc}|${mappings[first]}=${rest.join("=")}` : `${acc}|${param}`; | |||
}, `{{${name}`)}}}`; | |||
} | |||
}); | }); | ||
replaced = replaced.replace(REGEXP.extLink, (_, before, target, text, after) => `${before}[${mappings[target]}${text || ""}]${after}`); | |||
replaced = replaced.replace(REGEXP.link, (match, target, anchor, text) => anchor || text ? `[[${text ? mappings[target] : target}${mappings[anchor] ? `#${mappings[anchor]}` : ""}${text || ""}]]` : match); | |||
replaced = replaced.replace(REGEXP.nowiki, (_, index) => `<nowiki>${nowikis[index]}</nowiki>`); | |||
replaced = replaced.replace(REGEXP.lcMarkerEsc, (_, index) => `-{${mappings[index] ?? index}}-`); | |||
return replaced; | |||
return | |||
}; | }; | ||
class AIVCWindow extends (_b = OO.ui.ProcessDialog) { | |||
constructor(config) { | |||
super(config); | |||
this.config = config.data.config; | |||
this.prepopContent = config.data.prepopContent; | |||
} | } | ||
initialize() { | |||
super.initialize(); | |||
} | this.configPanel = new OO.ui.PanelLayout({ | ||
scrollable: false, | |||
expanded: false, | |||
} | padded: true, | ||
}); | |||
this.confirmPanel = new OO.ui.PanelLayout({ | |||
scrollable: false, | |||
expanded: false, | |||
padded: true, | |||
}); | |||
this.ogText = new OO.ui.MultilineTextInputWidget({ | |||
value: this.prepopContent, | |||
autosize: true, | |||
}); | |||
const textField = new OO.ui.FieldLayout(this.ogText, { | |||
label: wgULS("原始内容", "原始內容"), | |||
align: "top", | |||
}); | |||
this.mainVariants = new OO.ui.TextInputWidget({ | |||
value: this.config.main.join(";"), | |||
}); | |||
const mainField = new OO.ui.FieldLayout(this.mainVariants, { | |||
label: wgULS("主要变体", "主要變体"), | |||
align: "top", | |||
}); | |||
this.depVariants = new OO.ui.TextInputWidget({ | |||
value: Object.entries(this.config.dependent).map(([k, v]) => `${k}:${v}`).join(";"), | |||
}); | |||
const depField = new OO.ui.FieldLayout(this.depVariants, { | |||
label: wgULS("依赖变体", "依賴變体"), | |||
align: "top", | |||
}); | |||
this.noteTAParams = new OO.ui.TextInputWidget({ | |||
value: toParams(this.config.noteTA), | |||
}); | |||
const noteTAField = new OO.ui.FieldLayout(this.noteTAParams, { | |||
label: wgULS("NoteTA参数", "NoteTA參數"), | |||
align: "top", | |||
}); | |||
this.occCheckbox = new OO.ui.CheckboxInputWidget({ | |||
selected: this.config.useOpenCC, | |||
}); | |||
const occField = new OO.ui.FieldLayout(this.occCheckbox, { | |||
label: "使用OpenCC", | |||
align: "inline", | |||
}); | |||
this.configPanel.$element.append(textField.$element, mainField.$element, depField.$element, noteTAField.$element, occField.$element); | |||
this.stackLayout = new OO.ui.StackLayout({ | |||
items: [this.configPanel, this.confirmPanel], | |||
}); | |||
this.ogText.connect(this, { resize: "updateSize" }); | |||
this.$body.append(this.stackLayout.$element); | |||
} | } | ||
getBodyHeight() { | |||
return this.stackLayout.getCurrentItem().$element.outerHeight(true); | |||
} | } | ||
getSetupProcess(data) { | |||
return super.getSetupProcess(data).next(() => { | |||
this.actions.setMode("config"); | |||
this.stackLayout.setItem(this.configPanel); | |||
}, this); | |||
} | |||
} | } | ||
getReadyProcess(data) { | |||
return super.getReadyProcess(data) | |||
.next(() => { | |||
this.ogText.focus(); | |||
}, this); | |||
}); | |||
} | } | ||
getActionProcess(action) { | |||
if (action === "cancel") { | |||
return new OO.ui.Process(() => { | |||
this.close({ action: action }); | |||
}, this); | |||
} | |||
else if (action === "continue") { | |||
return new OO.ui.Process($.when((async () => { | |||
this.config = $.extend(this.config, { | |||
main: this.mainVariants.getValue().split(";"), | |||
dependent: Object.fromEntries(this.depVariants.getValue().split(";").map((v) => v.split(":"))), | |||
noteTAStr: this.noteTAParams.getValue(), | |||
useOpenCC: this.occCheckbox.isSelected(), | |||
dependentInv: {}, | |||
}); | |||
if (this.config.main.includes("(main)")) { | |||
throw new OO.ui.Error(wgULS("主页面不得作为主要变体", "主頁面不得作為主要變体")); | |||
} | |||
this.config.main.forEach((v) => this.config.dependentInv[v] = [v]); | |||
try { | |||
Object.entries(this.config.dependent).forEach(([k, v]) => this.config.dependentInv[v].push(k)); | |||
} | |||
catch { | |||
console.error("[VariantConverter] Error: Key not found in dependentInv. Config dump:", this.config); | |||
throw new OO.ui.Error(wgULS("依赖变体格式错误,请检查控制台", "依賴變体格式錯誤,請檢查控制臺")); | |||
} | |||
this.textInputs = {}; | |||
} | this.confirmPanel.$element.empty(); | ||
try { | |||
await this.getVariants(this.ogText.getValue()); | |||
} | this.actions.setMode("confirm"); | ||
this.stackLayout.setItem(this.confirmPanel); | |||
this.updateSize(); | |||
} | |||
} | |||
} | } | ||
catch (e) { | |||
console.error("[VariantConverter] Error:", e); | |||
throw new OO.ui.Error(e); | |||
} | } | ||
})()).promise(), this); | |||
} | |||
else if (action === "back") { | |||
this.actions.setMode("config"); | |||
this.stackLayout.setItem(this.configPanel); | |||
this.updateSize(); | |||
} | |||
else if (action === "submit") { | |||
return new OO.ui.Process($.when((async () => { | |||
try { | |||
await this.saveChanges(); | |||
this.close({ action: action }); | |||
mw.notify("保存成功!", { | |||
title: wgULS("自动繁简转换工具", "自動繁簡轉換工具"), | |||
type: "success", | |||
tag: "lr-aivc", | |||
}); | |||
setTimeout(() => location.reload(), 730); | |||
} | } | ||
catch (e) { | |||
console.error("[VariantConverter] Error:", e); | |||
throw new OO.ui.Error(e); | |||
} | } | ||
})()).promise(), this); | |||
} | |||
return super.getActionProcess(action); | |||
} | |||
addVariant(variant, text) { | |||
this.textInputs[variant] = new OO.ui.MultilineTextInputWidget({ | |||
value: text, | |||
autosize: true, | |||
}); | |||
const field = new OO.ui.FieldLayout(this.textInputs[variant], { | |||
label: variant, | |||
align: "top", | |||
}); | |||
this.textInputs[variant].connect(this, { resize: "updateSize" }); | |||
this.confirmPanel.$element.append(field.$element); | |||
if (window?.InPageEdit?.quickDiff) { | |||
this.confirmPanel.$element.append(new OO.ui.FieldLayout(new OO.ui.Widget({ | |||
content: [ | |||
new OO.ui.HorizontalLayout({ | |||
items: this.config.dependentInv[variant].map((v) => new OO.ui.ButtonWidget({ | |||
label: `${wgULS("对比", "對比")}${v === "(main)" ? wgULS("主页面", "主頁面") : v}`, | |||
}).on("click", () => window.InPageEdit.quickDiff({ | |||
fromtitle: variantPage(v), | |||
totext: this.textInputs[variant].getValue(), | |||
pageName: variantPage(v), | |||
isPreview: true, | |||
}))), | |||
}), | |||
], | |||
})).$element); | |||
} | |||
} | |||
async getVariants(original) { | |||
this.confirmPanel.$element.append(`<p>${wgULS("请确认以下转换是否正确", "請確認以下轉換是否正確")}:</p>`); | |||
if (!window.OpenCC && this.config.useOpenCC) { | |||
await libCachedCode.injectCachedCode("https://npm.elemecdn.com/opencc-js@latest", "script"); | |||
} | |||
const { replaced, mappings } = escapeWikitext(original); | |||
for (const variant of this.config.main) { | |||
if (variant === "(main)") { | |||
continue; | continue; | ||
} | |||
const text = `{{NoteTA|${this.config.noteTAStr}}}<pre id="converted">-{}-${replaced}</pre>`; | |||
const parsed = $($.parseHTML((await zhAPI.post({ | |||
action: "parse", | |||
assertuser: username, | |||
text, | |||
contentmodel: "wikitext", | |||
prop: "text", | |||
uselang: variant, | |||
disablelimitreport: true, | |||
pst: true, | |||
})).parse.text["*"])); | |||
let converted = parsed.find("#converted").text(); | |||
if (this.config.useOpenCC) { | |||
const occMappings = []; | |||
converted = converted.replace(REGEXP.noOCC, (_, content) => `<!--noOCC-->${occMappings.push(content) - 1}<!--/noOCC-->`); | |||
const occVariant = variant.replace("hans", "cn").replace(/zh-(?:han)?/, "").replace("tw", "twp"); | |||
const converter = OpenCC.Converter({ from: occVariant, to: occVariant }); | |||
converted = converter(converted); | |||
converted = converted.replace(REGEXP.noOCC, (_, index) => `<!--noOCC-->${occMappings[index]}<!--/noOCC-->`); | |||
} | |||
const final = this.config.manualAction[variant]?.(converted) || converted; | |||
const restored = restoreWikitext(final, mappings); | |||
this.addVariant(variant, restored); | |||
} | } | ||
} | } | ||
async saveChanges() { | |||
console.log("[VariantConverter] Saved changes", await Promise.allSettled(this.config.main.map((variant) => { | |||
const text = this.textInputs[variant].getValue(); | |||
return api.postWithToken("csrf", { | |||
} | action: "edit", | ||
assertuser: username, | |||
title: variantPage(variant), | |||
text, | |||
summary: `自动转换自[[${pagename}]]`, | |||
tags: "VariantConverter|Automation tool", | |||
watchlist: this.config.watchlist, | |||
}); | |||
}).concat(Object.entries(this.config.dependent).map(([variant, parent]) => { | |||
const text = this.textInputs[parent].getValue(); | |||
return api.postWithToken("csrf", { | |||
action: "edit", | |||
assertuser: username, | |||
title: variantPage(variant), | |||
text, | |||
summary: `自动转换自[[${pagename}]](同步${parent})`, | |||
tags: "VariantConverter|Automation tool", | |||
watchlist: this.config.watchlist, | |||
}); | |||
})))); | |||
} | |||
} | } | ||
_a = AIVCWindow; | |||
AIVCWindow.static = { | |||
...Reflect.get(_b, "static", _a), | |||
tagName: "div", | |||
name: "lr-aivc", | |||
title: wgULS("自动繁简转换工具", "自動繁簡轉換工具"), | |||
actions: [ | |||
{ | |||
action: "cancel", | |||
label: "取消", | |||
} | flags: ["safe", "close", "destructive"], | ||
modes: "config", | |||
}, | |||
{ | |||
action: "continue", | |||
label: wgULS("继续", "繼續"), | |||
flags: ["primary", "progressive"], | |||
modes: "config", | |||
}, | |||
{ | |||
action: "back", | |||
label: "返回", | |||
flags: ["safe", "back"], | |||
modes: "confirm", | |||
}, | |||
{ | |||
action: "submit", | |||
label: wgULS("确认", "確認"), | |||
flags: ["primary", "progressive"], | |||
modes: "confirm", | |||
}, | |||
], | |||
}; | }; | ||
const $body = $(document.body); | |||
const windowManager = new OO.ui.WindowManager(); | |||
$body.append(windowManager.$element); | |||
const aivcDialog = new AIVCWindow({ | |||
size: "large", | |||
data: { | |||
config: lrAivc, | |||
prepopContent, | |||
}, | |||
}); | |||
windowManager.addWindows([aivcDialog]); | |||
$(mw.util.addPortletLink("p-cactions", "#", wgULS("自动繁简转换", "自動繁簡轉換"), "ca-VariantConverter", wgULS("自动同步界面消息的繁简版本", "自動同步介面消息的繁簡版本"))).on("click", (e) => { | |||
e.preventDefault(); | |||
$("#mw-notification-area").appendTo($body); | |||
windowManager.openWindow(aivcDialog); | |||
}); | |||
})()); | |||
/* </pre> */ | /* </pre> */ | ||
2025年7月24日 (四) 20:53的版本
/**
* -------------------------------------------------------------------------
* !!! DON'T MODIFY THIS PAGE MANUALLY, YOUR CHANGES WILL BE OVERWRITTEN !!!
* -------------------------------------------------------------------------
*/
var _addText = '{{GHIACode|page=GHIA:MoegirlPediaInterfaceCodes/blob/master/src/gadgets/interfaceVariantConverter/Gadget-interfaceVariantConverter.js|user=[[U:星海子]]|co-authors=|longId=a157f65a20db84d70f63275717dff38cbffb9db9|shortId=a157f65a|summary=chore}}';
/* <pre> */
"use strict";
$(() => (async () => {
var _a, _b;
const pagename = mw.config.get("wgPageName");
const username = mw.config.get("wgUserName");
const pageid = mw.config.get("wgArticleId");
const basepage = pagename.replace(/\/.*?$/, "");
const api = new mw.Api(), zhAPI = /m?zh\.moegirl\.org\.cn/.test(location.hostname) ? api : new mw.ForeignApi("https://mzh.moegirl.org.cn/api.php", { anonymous: true });
const lrAivc = $.extend({
main: ["zh-cn", "zh-tw", "zh-hk"],
dependent: {
"(main)": "zh-cn",
"zh-hans": "zh-cn",
"zh-hant": "zh-tw",
},
noteTA: {
G1: "MediaWiki",
},
autoPopulate: true,
useOpenCC: true,
manualAction: {
"zh-hk": (t) => t.replaceAll("户", "戶"),
"zh-tw": (t) => t.replaceAll("名稱空間", "命名空間").replaceAll("記憶體", "內存"),
},
watchlist: "nochange",
}, window.lr_aivc);
let prepopContent = "";
try {
prepopContent = lrAivc.autoPopulate
? (await api.get({
action: "parse",
assertuser: username,
pageid,
prop: "wikitext",
})).parse.wikitext["*"]
: "";
}
catch { }
const toParams = (obj) => Object.entries(obj).map(([k, v]) => `${k}=${v}`).join("|");
const variantPage = (variant) => variant === "(main)" ? `${basepage}` : `${basepage}/${variant}`;
const REGEXP = {
lcMarker: /-{([\s\S]*?)}-/g,
lcMarkerEsc: /-\\{([\s\S]*?)}\\-/g,
nowiki: /<nowiki>([\s\S]*?)<\/nowiki>/g,
link: /\[\[([\s\S]*?)(?:#([\s\S]*?))?(\|[\s\S]*?)?\]\]/g,
extLink: /([^[])\[([^[]+?)( [\s\S]+?)?\]([^\]])/g,
template: /\{\{([\s\S]*?)\}\}/g,
htmlEntity: /&([a-zA-Z0-9#]+);/g,
noOCC: /<!--noOCC-->([\s\S]*?)<!--\/noOCC-->/gi,
};
const escapeWikitext = (original) => {
const nowikis = [], mappings = [];
let replaced = original;
replaced = replaced.replace(REGEXP.lcMarker, (_, content) => `-\\{${content}}\\-`);
replaced = replaced.replace(REGEXP.nowiki, (_, content) => `<nowiki>${nowikis.push(content) - 1}</nowiki>`);
replaced = replaced.replace(REGEXP.link, (match, target, anchor, text) => anchor || text ? `[[${text ? mappings.push(target) - 1 : target}${anchor ? `#${mappings.push(anchor) - 1}` : ""}${text || ""}]]` : match);
replaced = replaced.replace(REGEXP.extLink, (_, before, target, text, after) => `${before}[${mappings.push(target) - 1}${text || ""}]${after}`);
replaced = replaced.replace(REGEXP.template, (_, params) => {
const paramList = params.split("|");
mappings.push(paramList[0]);
paramList.shift();
return `${paramList.reduce((acc, param) => {
const [first, ...rest] = param.split("=");
return rest.length ? `${acc}|${mappings.push(first) - 1}=${rest.join("=")}` : `${acc}|${param}`;
}, `{{${mappings.length - 1}`)}}}`;
});
replaced = replaced.replace(REGEXP.lcMarkerEsc, (_, content) => `-\\{${mappings.push(content || "") - 1}}\\-`);
replaced = replaced.replace(REGEXP.htmlEntity, (_, entity) => `&${mappings.push(entity) - 1};`);
replaced = replaced.replace(REGEXP.nowiki, (_, index) => `<nowiki><nowiki>${nowikis[index]}</nowiki></nowiki>`);
return { replaced, mappings };
};
const restoreWikitext = (original, mappings) => {
const nowikis = [];
let replaced = original;
replaced = replaced.replace(REGEXP.nowiki, (_, content) => `<nowiki>${nowikis.push(content) - 1}</nowiki>`);
replaced = replaced.replace(REGEXP.htmlEntity, (_, index) => `&${mappings[index]};`);
replaced = replaced.replace(REGEXP.template, (_, params) => {
const paramList = params.split("|");
const name = mappings[paramList[0]];
paramList.shift();
return `${paramList.reduce((acc, param) => {
const [first, ...rest] = param.split("=");
return rest.length ? `${acc}|${mappings[first]}=${rest.join("=")}` : `${acc}|${param}`;
}, `{{${name}`)}}}`;
});
replaced = replaced.replace(REGEXP.extLink, (_, before, target, text, after) => `${before}[${mappings[target]}${text || ""}]${after}`);
replaced = replaced.replace(REGEXP.link, (match, target, anchor, text) => anchor || text ? `[[${text ? mappings[target] : target}${mappings[anchor] ? `#${mappings[anchor]}` : ""}${text || ""}]]` : match);
replaced = replaced.replace(REGEXP.nowiki, (_, index) => `<nowiki>${nowikis[index]}</nowiki>`);
replaced = replaced.replace(REGEXP.lcMarkerEsc, (_, index) => `-{${mappings[index] ?? index}}-`);
return replaced;
};
class AIVCWindow extends (_b = OO.ui.ProcessDialog) {
constructor(config) {
super(config);
this.config = config.data.config;
this.prepopContent = config.data.prepopContent;
}
initialize() {
super.initialize();
this.configPanel = new OO.ui.PanelLayout({
scrollable: false,
expanded: false,
padded: true,
});
this.confirmPanel = new OO.ui.PanelLayout({
scrollable: false,
expanded: false,
padded: true,
});
this.ogText = new OO.ui.MultilineTextInputWidget({
value: this.prepopContent,
autosize: true,
});
const textField = new OO.ui.FieldLayout(this.ogText, {
label: wgULS("原始内容", "原始內容"),
align: "top",
});
this.mainVariants = new OO.ui.TextInputWidget({
value: this.config.main.join(";"),
});
const mainField = new OO.ui.FieldLayout(this.mainVariants, {
label: wgULS("主要变体", "主要變体"),
align: "top",
});
this.depVariants = new OO.ui.TextInputWidget({
value: Object.entries(this.config.dependent).map(([k, v]) => `${k}:${v}`).join(";"),
});
const depField = new OO.ui.FieldLayout(this.depVariants, {
label: wgULS("依赖变体", "依賴變体"),
align: "top",
});
this.noteTAParams = new OO.ui.TextInputWidget({
value: toParams(this.config.noteTA),
});
const noteTAField = new OO.ui.FieldLayout(this.noteTAParams, {
label: wgULS("NoteTA参数", "NoteTA參數"),
align: "top",
});
this.occCheckbox = new OO.ui.CheckboxInputWidget({
selected: this.config.useOpenCC,
});
const occField = new OO.ui.FieldLayout(this.occCheckbox, {
label: "使用OpenCC",
align: "inline",
});
this.configPanel.$element.append(textField.$element, mainField.$element, depField.$element, noteTAField.$element, occField.$element);
this.stackLayout = new OO.ui.StackLayout({
items: [this.configPanel, this.confirmPanel],
});
this.ogText.connect(this, { resize: "updateSize" });
this.$body.append(this.stackLayout.$element);
}
getBodyHeight() {
return this.stackLayout.getCurrentItem().$element.outerHeight(true);
}
getSetupProcess(data) {
return super.getSetupProcess(data).next(() => {
this.actions.setMode("config");
this.stackLayout.setItem(this.configPanel);
}, this);
}
getReadyProcess(data) {
return super.getReadyProcess(data)
.next(() => {
this.ogText.focus();
}, this);
}
getActionProcess(action) {
if (action === "cancel") {
return new OO.ui.Process(() => {
this.close({ action: action });
}, this);
}
else if (action === "continue") {
return new OO.ui.Process($.when((async () => {
this.config = $.extend(this.config, {
main: this.mainVariants.getValue().split(";"),
dependent: Object.fromEntries(this.depVariants.getValue().split(";").map((v) => v.split(":"))),
noteTAStr: this.noteTAParams.getValue(),
useOpenCC: this.occCheckbox.isSelected(),
dependentInv: {},
});
if (this.config.main.includes("(main)")) {
throw new OO.ui.Error(wgULS("主页面不得作为主要变体", "主頁面不得作為主要變体"));
}
this.config.main.forEach((v) => this.config.dependentInv[v] = [v]);
try {
Object.entries(this.config.dependent).forEach(([k, v]) => this.config.dependentInv[v].push(k));
}
catch {
console.error("[VariantConverter] Error: Key not found in dependentInv. Config dump:", this.config);
throw new OO.ui.Error(wgULS("依赖变体格式错误,请检查控制台", "依賴變体格式錯誤,請檢查控制臺"));
}
this.textInputs = {};
this.confirmPanel.$element.empty();
try {
await this.getVariants(this.ogText.getValue());
this.actions.setMode("confirm");
this.stackLayout.setItem(this.confirmPanel);
this.updateSize();
}
catch (e) {
console.error("[VariantConverter] Error:", e);
throw new OO.ui.Error(e);
}
})()).promise(), this);
}
else if (action === "back") {
this.actions.setMode("config");
this.stackLayout.setItem(this.configPanel);
this.updateSize();
}
else if (action === "submit") {
return new OO.ui.Process($.when((async () => {
try {
await this.saveChanges();
this.close({ action: action });
mw.notify("保存成功!", {
title: wgULS("自动繁简转换工具", "自動繁簡轉換工具"),
type: "success",
tag: "lr-aivc",
});
setTimeout(() => location.reload(), 730);
}
catch (e) {
console.error("[VariantConverter] Error:", e);
throw new OO.ui.Error(e);
}
})()).promise(), this);
}
return super.getActionProcess(action);
}
addVariant(variant, text) {
this.textInputs[variant] = new OO.ui.MultilineTextInputWidget({
value: text,
autosize: true,
});
const field = new OO.ui.FieldLayout(this.textInputs[variant], {
label: variant,
align: "top",
});
this.textInputs[variant].connect(this, { resize: "updateSize" });
this.confirmPanel.$element.append(field.$element);
if (window?.InPageEdit?.quickDiff) {
this.confirmPanel.$element.append(new OO.ui.FieldLayout(new OO.ui.Widget({
content: [
new OO.ui.HorizontalLayout({
items: this.config.dependentInv[variant].map((v) => new OO.ui.ButtonWidget({
label: `${wgULS("对比", "對比")}${v === "(main)" ? wgULS("主页面", "主頁面") : v}`,
}).on("click", () => window.InPageEdit.quickDiff({
fromtitle: variantPage(v),
totext: this.textInputs[variant].getValue(),
pageName: variantPage(v),
isPreview: true,
}))),
}),
],
})).$element);
}
}
async getVariants(original) {
this.confirmPanel.$element.append(`<p>${wgULS("请确认以下转换是否正确", "請確認以下轉換是否正確")}:</p>`);
if (!window.OpenCC && this.config.useOpenCC) {
await libCachedCode.injectCachedCode("https://npm.elemecdn.com/opencc-js@latest", "script");
}
const { replaced, mappings } = escapeWikitext(original);
for (const variant of this.config.main) {
if (variant === "(main)") {
continue;
}
const text = `{{NoteTA|${this.config.noteTAStr}}}<pre id="converted">-{}-${replaced}</pre>`;
const parsed = $($.parseHTML((await zhAPI.post({
action: "parse",
assertuser: username,
text,
contentmodel: "wikitext",
prop: "text",
uselang: variant,
disablelimitreport: true,
pst: true,
})).parse.text["*"]));
let converted = parsed.find("#converted").text();
if (this.config.useOpenCC) {
const occMappings = [];
converted = converted.replace(REGEXP.noOCC, (_, content) => `<!--noOCC-->${occMappings.push(content) - 1}<!--/noOCC-->`);
const occVariant = variant.replace("hans", "cn").replace(/zh-(?:han)?/, "").replace("tw", "twp");
const converter = OpenCC.Converter({ from: occVariant, to: occVariant });
converted = converter(converted);
converted = converted.replace(REGEXP.noOCC, (_, index) => `<!--noOCC-->${occMappings[index]}<!--/noOCC-->`);
}
const final = this.config.manualAction[variant]?.(converted) || converted;
const restored = restoreWikitext(final, mappings);
this.addVariant(variant, restored);
}
}
async saveChanges() {
console.log("[VariantConverter] Saved changes", await Promise.allSettled(this.config.main.map((variant) => {
const text = this.textInputs[variant].getValue();
return api.postWithToken("csrf", {
action: "edit",
assertuser: username,
title: variantPage(variant),
text,
summary: `自动转换自[[${pagename}]]`,
tags: "VariantConverter|Automation tool",
watchlist: this.config.watchlist,
});
}).concat(Object.entries(this.config.dependent).map(([variant, parent]) => {
const text = this.textInputs[parent].getValue();
return api.postWithToken("csrf", {
action: "edit",
assertuser: username,
title: variantPage(variant),
text,
summary: `自动转换自[[${pagename}]](同步${parent})`,
tags: "VariantConverter|Automation tool",
watchlist: this.config.watchlist,
});
}))));
}
}
_a = AIVCWindow;
AIVCWindow.static = {
...Reflect.get(_b, "static", _a),
tagName: "div",
name: "lr-aivc",
title: wgULS("自动繁简转换工具", "自動繁簡轉換工具"),
actions: [
{
action: "cancel",
label: "取消",
flags: ["safe", "close", "destructive"],
modes: "config",
},
{
action: "continue",
label: wgULS("继续", "繼續"),
flags: ["primary", "progressive"],
modes: "config",
},
{
action: "back",
label: "返回",
flags: ["safe", "back"],
modes: "confirm",
},
{
action: "submit",
label: wgULS("确认", "確認"),
flags: ["primary", "progressive"],
modes: "confirm",
},
],
};
const $body = $(document.body);
const windowManager = new OO.ui.WindowManager();
$body.append(windowManager.$element);
const aivcDialog = new AIVCWindow({
size: "large",
data: {
config: lrAivc,
prepopContent,
},
});
windowManager.addWindows([aivcDialog]);
$(mw.util.addPortletLink("p-cactions", "#", wgULS("自动繁简转换", "自動繁簡轉換"), "ca-VariantConverter", wgULS("自动同步界面消息的繁简版本", "自動同步介面消息的繁簡版本"))).on("click", (e) => {
e.preventDefault();
$("#mw-notification-area").appendTo($body);
windowManager.openWindow(aivcDialog);
});
})());
/* </pre> */