跳转到内容
打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
登录后可编辑和发表评论。

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

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl + F5Ctrl + R(Mac为 R
  • Google Chrome:Ctrl + Shift + R(Mac为 Shift R
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl + F5
/*!
 * 分类页面小工具
 * ------------------------------------------------------------------
 * 功能:在分类页面标题旁添加一个“分类工具”按钮,
 *       点击后弹出弹窗,用于快速完成分类页的编辑工作:
 *
 *   标签页一“建立作者分类”:选择/填写作者国籍,自动生成并写入 Catnav 模板;可勾选“存在对应条目”并填写条目名(勾选后自动填入标题中“作者:”之后的字段),以追加 Catmore 行。国籍留空(无法确认)时,直接归入“按作者分类的图片”。
 *
 *   标签页二“建立分类重定向”:填写重定向目标,自动生成并写入 分类重定向 模板。若当前分类名以“作者:”开头输入框会自动预填“作者:”。
 *
 * 使用:
 *     在个人 JS(User:你的用户名/common.js)中加入一行引用:
 *         importScript('User:Wecury/js/cat-tool.js');
 */
"use strict";

// 防止这个页面被错误加分类
var OPEN_LINK = "[" + "[";
var CLOSE_LINK = "]" + "]";
var OPEN_TPL = "{" + "{";
var CLOSE_TPL = "}" + "}";
var TOOL_LINK = OPEN_LINK + "User:Wecury/js#分类页小工具|分类工具" + CLOSE_LINK;

$(function () {
    // 防止脚本在同页被重复执行时重复添加按钮/弹窗
    if (window.CATEGORY_TOOL_LOADED) {
        return;
    }
    window.CATEGORY_TOOL_LOADED = true;

    if (mw.config.get("wgNamespaceNumber") !== 14) {
        return;
    }
    const currentPage = mw.config.get("wgPageName") || "";
    if (!currentPage) {
        return;
    }

    const citizenHeadingContainer = document.querySelector(".firstHeading-container");
    const heading = document.getElementById("firstHeading");
    if (!citizenHeadingContainer && !heading) {
        return;
    }

    // ---- 样式 ----
    if (!document.getElementById("category-tool-styles")) {
        const style = document.createElement("style");
        style.id = "category-tool-styles";
        style.textContent = [
            ".category-tool-trigger { margin-left: 12px; }",
            "#firstHeading > .category-tool-trigger { vertical-align: 4px; }",
            ".category-tool-dialog { border-radius: var(--border-radius-medium, 2px); }",
            ".category-tool-dialog .cdx-tabs > .cdx-tabs__header { margin-bottom: 16px; }",
            ".category-tool-dialog .cdx-tabs__prev-scroller, .category-tool-dialog .cdx-tabs__next-scroller, .category-tool-dialog .cdx-tabs__scroll-button { display: none !important; }",
            ".category-tool-dialog .cdx-field { margin-bottom: 8px; }",
            ".category-tool-article { display: flex; align-items: center; gap: 8px; margin: 12px 0 4px; }",
            ".category-tool-article .cdx-checkbox { margin-bottom: 0; }",
            ".category-tool-article .cdx-text-input { flex: 1; min-width: 0; }",
            ".category-tool-preview-label { margin: 16px 0 4px; font-size: 0.875rem; color: var(--color-subtle, #54595d); }",
            ".category-tool-preview { margin: 0; padding: 8px 12px; background-color: var(--background-color-neutral-subtle, #f8f9fa); border: 1px solid var(--border-color-subtle, #c8ccd1); border-radius: var(--border-radius-medium, 2px); font-size: 0.8125rem; white-space: pre-wrap; word-break: break-all; }",
        ].join("\n");
        document.head.appendChild(style);
    }

    // ---- 添加「分类工具」入口按钮 ----
    const trigger = document.createElement("button");
    trigger.type = "button";
    trigger.className =
        "cdx-button cdx-button--action-default cdx-button--weight-normal cdx-button--size-medium cdx-button--framed cdx-dialog__footer__default-action category-tool-trigger";
    trigger.textContent = "分类工具";
    trigger.title = "打开分类页小工具";
    if (citizenHeadingContainer) {
        citizenHeadingContainer.insertBefore(
            trigger,
            citizenHeadingContainer.querySelector(".mw-indicators") || null
        );
    } else if (heading) {
        heading.appendChild(trigger);
    }

    // ---- 常用作者国籍 ----
    const nationalities = [
        "日本", "中国", "美国", "韩国", "英国", "法国", "德国",
        "俄罗斯", "意大利", "西班牙", "荷兰", "瑞典", "芬兰", "丹麦",
        "挪威", "波兰", "加拿大", "澳大利亚", "新西兰", "巴西", "墨西哥",
        "阿根廷", "泰国", "越南", "新加坡", "马来西亚", "印度尼西亚",
        "菲律宾", "印度", "土耳其", "乌克兰", "比利时", "奥地利", "瑞士",
        "葡萄牙", "希腊",
    ];

    // ---- 重定向目标预填 ----
    const canonicalNs = mw.config.get("wgCanonicalNamespace") || "";
    const titleNoNs = canonicalNs && currentPage.startsWith(canonicalNs + ":")
        ? currentPage.slice(canonicalNs.length + 1)
        : currentPage;
    const defaultTarget = titleNoNs.indexOf("作者:") === 0 ? "作者:" : "";
    // 条目名自动填充:标题含“作者:”时取其后字段(如 Category:作者:Iyowa → Iyowa),否则取整个标题
    const articleDefault = titleNoNs.indexOf("作者:") === 0 ? titleNoNs.slice("作者:".length) : titleNoNs;

    // ---- 加载Codex并挂载弹窗 ----
    mw.loader.using(["@wikimedia/codex", "mediawiki.api", "mediawiki.notification"]).then(function (require) {
        const Vue = require("vue");
        const Codex = require("@wikimedia/codex");
        const mountPoint = document.body.appendChild(document.createElement("div"));

        Vue.createMwApp({
            data() {
                return {
                    open: false,
                    activeTab: "author",
                    nationality: "",
                    articleExists: false,
                    articleName: "",
                    redirectTarget: defaultTarget,
                    busy: false,
                };
            },
            computed: {
                nationalityMenu() {
                    return nationalities.map(function (n) {
                        return { label: n, value: n };
                    });
                },
                authorWikitext() {
                    const n = (this.nationality || "").trim();
                    const article = this.articleExists ? (this.articleName || "").trim() : "";
                    const catnav =
                        OPEN_TPL + "Catnav|图片|按作者分类的图片" +
                        (n ? "|" + n + "作者作品" : "") + CLOSE_TPL;
                    const catmore = article
                        ? "\n" + OPEN_TPL + "Catmore|" + article + CLOSE_TPL
                        : "";
                    const catlink =
                        OPEN_LINK + "Category:" +
                        (n ? n + "作者作品" : "按作者分类的图片") + CLOSE_LINK;
                    return catnav + catmore + "\n" + catlink;
                },
                redirectWikitext() {
                    const t = (this.redirectTarget || "").trim();
                    return (
                        "#REDIRECT " + OPEN_LINK + "Category:" + t + CLOSE_LINK + "\n" +
                        OPEN_TPL + "分类重定向|" + t + CLOSE_TPL
                    );
                },
                canSubmit() {
                    if (this.activeTab === "redirect") {
                        return !!(this.redirectTarget || "").trim();
                    }
                    if (this.articleExists && !(this.articleName || "").trim()) {
                        return false;
                    }
                    return true;
                },
            },
            watch: {
                articleExists(checked) {
                    // 勾选“存在对应条目”后,若条目名未填,自动填充“作者:”后面的字段
                    if (checked && !(this.articleName || "").trim() && articleDefault) {
                        this.articleName = articleDefault;
                    }
                },
            },
            methods: {
                openDialog() {
                    this.open = true;
                    const harden = () => {
                        document
                            .querySelectorAll(".category-tool-dialog .cdx-tabs__scroll-button")
                            .forEach((btn) => btn.setAttribute("inert", ""));
                        document
                            .querySelectorAll(".category-tool-dialog .cdx-combobox__expand-button")
                            .forEach((btn) => {
                                if (!btn.dataset.hardened) {
                                    btn.dataset.hardened = "1";
                                    btn.addEventListener("mousedown", (e) => e.preventDefault());
                                    btn.addEventListener("focus", () => btn.blur());
                                }
                            });
                    };
                    setTimeout(harden, 0);
                    setTimeout(harden, 200);
                },
                closeDialog() {
                    if (!this.busy) {
                        this.open = false;
                    }
                },
                onPrimary() {
                    if (this.activeTab === "redirect") {
                        this.submitRedirect();
                    } else {
                        this.submitAuthor();
                    }
                },
                notify(message, type) {
                    mw.notify(message, { type: type || "notice" });
                },
                submitAuthor() {
                    if (this.articleExists && !(this.articleName || "").trim()) {
                        this.notify("请填写对应条目名称。", "error");
                        return;
                    }
                    this.doEdit(this.authorWikitext, TOOL_LINK + ":快速建立作者分类");
                },
                submitRedirect() {
                    if (!(this.redirectTarget || "").trim()) {
                        this.notify("请填写重定向目标。", "error");
                        return;
                    }
                    this.doEdit(this.redirectWikitext, TOOL_LINK + ":快速建立分类重定向");
                },
                doEdit(text, summary) {
                    this.busy = true;
                    const api = new mw.Api();
                    const that = this;
                    api.postWithToken("csrf", {
                        action: "edit",
                        title: currentPage,
                        text: text,
                        summary: summary,
                        tags: "Automation tool",
                    })
                        .done(function (data) {
                            that.busy = false;
                            if (data && data.edit && data.edit.result === "Success") {
                                that.open = false;
                                that.notify("编辑成功", "success");
                                setTimeout(function () {
                                    location.reload();
                                }, 800);
                            } else {
                                that.notify("编辑失败:" + JSON.stringify(data), "error");
                            }
                        })
                        .fail(function (code, data) {
                            that.busy = false;
                            that.notify(
                                "编辑失败:" + code +
                                (data && data.error && data.error.info
                                    ? ":" + data.error.info
                                    : ""),
                                "error"
                            );
                        });
                },
            },
            mounted() {
                // 入口按钮点击打开弹窗
                trigger.addEventListener("click", this.openDialog);
            },
            template: `
                <cdx-dialog v-model:open="open"
                    class="category-tool-dialog"
                    title="分类页小工具"
                    :use-close-button="true"
                    close-button-label="关闭"
                >
                    <template #footer>
                        <div class="cdx-dialog__footer__actions">
                            <cdx-button
                                action="progressive"
                                weight="primary"
                                :disabled="busy || !canSubmit"
                                @click="onPrimary"
                            >
                                {{ busy ? '处理中…' : '确定' }}
                            </cdx-button>
                            <cdx-button action="destructive" weight="quiet" :disabled="busy" @click="closeDialog">取消</cdx-button>
                        </div>
                    </template>
                    <cdx-tabs v-model:active="activeTab">
                        <cdx-tab name="author" label="建立作者分类">
                            <cdx-field
                                label="作者国籍"
                                description="选择或填写作者国籍;留空(无法确认)时,直接归入“按作者分类的图片”。"
                            >
                                <cdx-combobox
                                    v-model:input-value="nationality"
                                    v-model:selected="nationality"
                                    :menu-items="nationalityMenu"
                                    placeholder="未选择国籍"
                                />
                            </cdx-field>
                            <div class="category-tool-article">
                                <cdx-checkbox v-model="articleExists">
                                    存在对应条目
                                </cdx-checkbox>
                                <cdx-text-input
                                    v-model="articleName"
                                    :disabled="!articleExists"
                                    placeholder="条目名"
                                />
                            </div>
                            <p class="category-tool-preview-label">将写入的文本:</p>
                            <pre class="category-tool-preview">{{ authorWikitext }}</pre>
                        </cdx-tab>
                        <cdx-tab name="redirect" label="建立分类重定向">
                            <cdx-field
                                label="重定向目标"
                                description="将当前分类页重定向到目标页面(无需带“Category:”前缀)。"
                            >
                                <cdx-text-input
                                    v-model="redirectTarget"
                                    placeholder="目标分类名称"
                                />
                            </cdx-field>
                            <p class="category-tool-preview-label">将写入的文本:</p>
                            <pre class="category-tool-preview">{{ redirectWikitext }}</pre>
                        </cdx-tab>
                    </cdx-tabs>
                </cdx-dialog>
            `,
        })
            .component("cdx-dialog", Codex.CdxDialog)
            .component("cdx-tabs", Codex.CdxTabs)
            .component("cdx-tab", Codex.CdxTab)
            .component("cdx-button", Codex.CdxButton)
            .component("cdx-checkbox", Codex.CdxCheckbox)
            .component("cdx-text-input", Codex.CdxTextInput)
            .component("cdx-combobox", Codex.CdxCombobox)
            .component("cdx-field", Codex.CdxField)
            .mount(mountPoint);
    });
});