打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

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

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
/* Original author:https://mzh.moegirl.org.cn/User:AnnAngela  Copied with Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International license. */

/* eslint-disable no-magic-numbers */
/* global mw */
/* jshint ignore:start */
// 
"use strict";
try {
    const statesMap = {
        0: "尚未发出请求",
        1: "已发出请求但未收到数据",
        2: "已收到 header",
        3: "接收数据中",
        4: "数据接收完成",
    };
    (async () => {
        const userId = mw.config.get("wgUserId");
        if (typeof userId !== "number" || !/^\d+$/.test("" + userId) || userId <= 0) {
            return;
        }
        if (!["edit", "submit"].includes(mw.config.get("wgAction"))) {
            return;
        }
        let style;
        try {
            style = await $.ajax({
                url: `${mw.config.get("wgServer")}${mw.config.get("wgScriptPath")}/User:G/draft.css?action=raw&ctype=text/css`,
                type: "GET",
                cache: true,
            });
        } catch (e) {
            throw new Error(`无法加载 CSS:\n    加载进度:${statesMap[e.readyState]}\n    服务器返回代码:${e.status}${e.statusText === "error" ? "" : `(${e.statusText})`}`);
        }
        class DraftStorage {
            constructor(userId) {
                const legalKeys = ["timerInterval", "drafts"];
                this.userId = userId;
                let data;
                try {
                    data = JSON.parse(localStorage.getItem("AnnTools-QuickSaveDraft"));
                } catch (_) {
                    console.debug(_);
                }
                let setBackFlag = false;
                if (!$.isPlainObject(data)) {
                    data = {};
                    setBackFlag = true;
                }
                if (!data[userId]) {
                    data[userId] = {};
                    setBackFlag = true;
                }
                Object.keys(data).forEach((uId) => {
                    const uData = data[uId];
                    if (typeof uData.timerInterval !== "number" || !/^\d+$/.test("" + uData.timerInterval) || uData.timerInterval <= 0) {
                        uData.timerInterval = 3;
                        setBackFlag = true;
                    }
                    if (!$.isPlainObject(uData.drafts)) {
                        uData.drafts = {};
                        setBackFlag = true;
                    }
                    Object.keys(uData.drafts).forEach((pagename) => {
                        if (!$.isPlainObject(uData.drafts[pagename])) {
                            delete uData.drafts[pagename];
                            setBackFlag = true;
                            return;
                        }
                        Object.keys(uData.drafts[pagename]).forEach((section) => {
                            if (!$.isPlainObject(uData.drafts[pagename][section])
                                || typeof uData.drafts[pagename][section].timestamp !== "number" || !/^\d+$/.test("" + uData.drafts[pagename][section].timestamp) || uData.drafts[pagename][section].timestamp <= 0
                                || typeof uData.drafts[pagename][section].draft !== "string") {
                                delete uData.drafts[pagename][section];
                                setBackFlag = true;
                                return;
                            }
                        });
                    });
                    Object.keys(uData).forEach((key) => {
                        if (!legalKeys.includes(key)) {
                            delete uData[key];
                            setBackFlag = true;
                        }
                    });
                });
                this.data = data;
                if (setBackFlag) {
                    this.setData();
                }
            }
            setData() {
                localStorage.setItem("AnnTools-QuickSaveDraft", JSON.stringify(this.data));
            }
            getTimerInterval() {
                return this.data[this.userId].timerInterval;
            }
            setTimerInterval(interval) {
                if (this.data[this.userId].timerInterval !== interval) {
                    this.data[this.userId].timerInterval = interval;
                    this.setData();
                }
            }
            getDraft(pagename, section) {
                return $.isPlainObject(this.data[this.userId].drafts[pagename]) && $.isPlainObject(this.data[this.userId].drafts[pagename][section]) ? this.data[this.userId].drafts[pagename][section].draft : null;
            }
            setDraft(pagename, section, text) {
                if (!$.isPlainObject(this.data[this.userId].drafts[pagename])) {
                    this.data[this.userId].drafts[pagename] = {};
                }
                if (!$.isPlainObject(this.data[this.userId].drafts[pagename][section])) {
                    this.data[this.userId].drafts[pagename][section] = {};
                }
                this.data[this.userId].drafts[pagename][section].timestamp = new Date().getTime();
                this.data[this.userId].drafts[pagename][section].draft = text;
                this.setData();
            }
            getTimestamp(pagename, section) {
                return $.isPlainObject(this.data[this.userId].drafts[pagename]) && $.isPlainObject(this.data[this.userId].drafts[pagename][section]) ? this.data[this.userId].drafts[pagename][section].timestamp : null;
            }
        }
        class IntervalRegistry {
            constructor() {
                this.intervalRegistry = {};
                const date = new Date();
                const now = date.getTime();
                date.setMilliseconds(0);
                date.setSeconds(date.getSeconds() + 1);
                setTimeout(() => {
                    this.runInterval();
                    setInterval(() => {
                        this.runInterval();
                    }, 1000);
                }, date.getTime() - now);
            }
            register(denominator, ...callbacks) {
                const key = denominator + "";
                const index = +(Math.random() + "").replace(/^.*?(\d+)$/, "$1");
                if (!this.intervalRegistry[key]) {
                    this.intervalRegistry[key] = [];
                }
                const callback1 = callbacks[0];
                let callback;
                if (Array.isArray(callback1)) {
                    callback = callback1;
                }
                else {
                    callback = callbacks;
                }
                callback.forEach((cb) => {
                    this.intervalRegistry[key].push({ func: cb, index });
                });
                return index;
            }
            unregister(denominator, index) {
                let result = false;
                if (!index) {
                    const idx = denominator;
                    Object.keys(this.intervalRegistry).forEach((key) => {
                        if (this.deleteFunction(this.intervalRegistry[key], idx)) {
                            result = true;
                        }
                    });
                }
                else if (this.deleteFunction(this.intervalRegistry[denominator], index)) {
                    result = true;
                }
                return result;
            }
            deleteFunction(registry, index) {
                for (const reg of registry) {
                    if (reg.index === index || reg.func === index) {
                        registry.splice(registry.indexOf(reg), 1);
                        return true;
                    }
                }
                return false;
            }
            runInterval() {
                const now = Math.round(Date.now() / 1000);
                Object.keys(this.intervalRegistry).forEach((key) => {
                    if (now % +key === 0) {
                        this.intervalRegistry[key].forEach((reg) => {
                            reg.func();
                        });
                    }
                });
            }
        }
        jQuery.fn.extend({
            disable() {
                return this.each((_, ele) => {
                    if (!$(ele).is("input,button")) { return; }
                    if ($(ele).parent().hasClass("oo-ui-widget-enabled")) { $(ele).parent().toggleClass("oo-ui-widget-enabled oo-ui-widget-disabled"); }
                    ele.disabled = true;
                });
            },
            enable() {
                return this.each((_, ele) => {
                    if (!$(ele).is("input,button")) { return; }
                    if ($(ele).parent().hasClass("oo-ui-widget-disabled")) { $(ele).parent().toggleClass("oo-ui-widget-enabled oo-ui-widget-disabled"); }
                    ele.disabled = false;
                });
            },
        });
        const draftStorage = new DraftStorage(userId);
        const pagename = mw.config.get("wgPageName");
        const section = $('[name="wpSection"]').val() || "(-1)全文";
        const textarea = $("#wpTextbox1");
        const buttonArea = $("
", { css: { "margin-top": ".5em", }, }).appendTo("#editform .editButtons"); const saveButton = inputConstruct({ "class": "QuickSaveDraftSaveButton", val: "立即保存草稿", attr: { type: "button", }, }); const recoverButton = inputConstruct({ "class": "QuickSaveDraftRecoverButton", val: "暂无草稿", attr: { type: "button", }, }); const rollbackButton = inputConstruct({ "class": "QuickSaveDraftRollbackButton", val: "回退还原", attr: { type: "button", }, }); const timerInput = inputConstruct({ "class": "QuickSaveDraftTimerInput", val: draftStorage.getTimerInterval(), //默认值 maxlength: 2, }); const timerSave = $("", { id: "QuickSaveDraftTimerSave", }); const lastRun = $("
", { "class": "QuickSaveDraftLastRunDiv", text: "上次草稿保存于", }).append($("", { "class": "QuickSaveDraftLastRun", })).append($("", { "class": "QuickSaveDraftSame", text: ",草稿内容与当前编辑内容一致", })); const pasueButton = inputConstruct({ "class": "QuickSaveDraftPauseButton", val: "点击暂停自动保存", attr: { type: "button", }, }); function inputConstruct(opt) { const r = $(""); const input = $("", opt); if (opt.attr && opt.attr.type === "button") { r.addClass("oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-buttonElement oo-ui-buttonElement-framed oo-ui-labelElement oo-ui-buttonInputWidget"); input.addClass("oo-ui-inputWidget-input oo-ui-buttonElement-button"); } else { r.addClass("oo-ui-widget oo-ui-widget-enabled oo-ui-inputWidget oo-ui-textInputWidget oo-ui-textInputWidget-type-text QuickSaveDraftTimer"); input.addClass("oo-ui-inputWidget-input"); } r.append(input); ["on", "val", "disable", "enable"].forEach((key) => { r[key] = input[key].bind(input); }); return r; } function complement(_n, l) { const n = _n + ""; if (n.length >= l) { return n; } const b = ''; let c = ""; while (l > (n + c).length) { c += "0"; } return `${b + c}${n}`; } function check() { if (draftStorage.getDraft(pagename, section) === undefined) { return; } const draft = draftStorage.getDraft(pagename, section); if (draft === null) //检测是否同一章节 { return recoverButton.disable().val("暂无草稿"); } if (draft !== textarea.val()) { //检测草稿是否一致 recoverButton.enable().val("立即还原草稿"); lastRun.find(".QuickSaveDraftSame").fadeOut(); } else { recoverButton.disable().val("草稿内容一致"); lastRun.find(".QuickSaveDraftSame").fadeIn(); } } function save() { const date = new Date(), value = textarea.val(); draftStorage.setDraft(pagename, section, value); check(); lastRun.fadeIn().find(".QuickSaveDraftLastRun").html(`今日${complement(date.getHours(), 2)}时${complement(date.getMinutes(), 2)}分,草稿长度为 ${draftStorage.getDraft(pagename, section).length || "-1(暂无)"} 字符`); } let originText = null; let pause = false; let pastTime = 0; let timerSaveCode; recoverButton.disable(); rollbackButton.disable(); buttonArea.append(saveButton).append(recoverButton).append(rollbackButton).append("每隔").append(timerInput).append("分钟保存一次").append(timerSave).append(pasueButton).append(lastRun); const timestamp = draftStorage.getTimestamp(pagename, section); const date = new Date(timestamp); if (timestamp) { lastRun.fadeIn().find(".QuickSaveDraftLastRun").html(`${date.getDate() === new Date().getDate() ? "今" : complement(date.getDate(), 2)}日${complement(date.getHours(), 2)}时${complement(date.getMinutes(), 2)}分,草稿长度为 ${draftStorage.getDraft(pagename, section).length || "-1(暂无)"} 字符`); } textarea.on("input change", function () { check(); }); timerInput.on("input", () => { let flag = false; const input = timerInput.val(); if (/^\d+$/.test(input) && +input > 0) { flag = true; draftStorage.setTimerInterval(+input); } const result = flag ? ",保存成功!" : ",保存失败"; timerSave.text(result); timerSave.removeClass("disapper"); if (timerSaveCode) { window.clearTimeout(timerSaveCode); } timerSaveCode = window.setTimeout(() => { timerSave.addClass("disapper"); }, 3000); }); rollbackButton.on("click", () => { if (rollbackButton.is(":disabled") || originText === null) { return; } textarea.val(originText); originText = null; rollbackButton.disable(); check(); return false; }); recoverButton.on("click", () => { if (recoverButton.is(":disabled")) { return; } const draft = draftStorage.getDraft(pagename, section); if (draft === null) { return; } originText = textarea.val(); textarea.val(draft); rollbackButton.enable(); check(); return false; }); saveButton.on("click", () => { if (saveButton.is(":disabled")) { return; } save(false); return false; }); pasueButton.on("click", () => { if (pause) { pasueButton.val("点击暂停自动保存"); pause = false; } else { pasueButton.val("点击继续自动保存"); pause = true; } }); $("