跳转到内容

MediaWiki:Gadget-interfaceVariantConverter.js

来自萌娘共享
AnnAngela-dbot留言 | 贡献2025年7月24日 (四) 20:53的版本 (代码变动:a157f65a - chore by U:星海子

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
/**
 * -------------------------------------------------------------------------
 * !!! 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> */