MediaWiki:AxUserMsg.js:修订间差异

H萌娘,万物皆可H的百科全书!
跳到导航 跳到搜索
imported>=海豚=
无编辑摘要
imported>=海豚=
无编辑摘要
第782行: 第782行:
        umSelectTag: "警告类型:",
        umSelectTag: "警告类型:",
        umFillInMedia: "有关文件",
        umFillInMedia: "有关文件",
        umFillInAdditional: " 所选模板 额外参数 :",
        umFillInAdditional: " 用户违规 页面 :",
        umFillInRelUser: "被警告的相关用户:",
        umFillInRelUser: "被警告的相关用户:",
        umFillInSummary: "编辑摘要:",
        umFillInSummary: "编辑摘要:",
第1,025行: 第1,025行:
        ["模板:预加载警告/繁简输入", "错误的繁体输入", "用户在简体模式下输入繁体编辑条目", umsg.umFlagUM, "用户违反编辑规则警告", 0],
        ["模板:预加载警告/繁简输入", "错误的繁体输入", "用户在简体模式下输入繁体编辑条目", umsg.umFlagUM, "用户违反编辑规则警告", 0],
        ["模板:预加载警告/忍耐是有限的", "我们的忍耐是有限的", "用户进行了相当数量的违反编辑规范的操作", umsg.umFlagUM, "用户违反编辑规范警告", 0],
        ["模板:预加载警告/忍耐是有限的", "我们的忍耐是有限的", "用户进行了相当数量的违反编辑规范的操作", umsg.umFlagUM, "用户违反编辑规范警告", 0],
        ["Sign", "Sign your postings", "Please sign your postings with four tildes (~~~~) at the end of your comments", 0],
        [" 模板:预加载警告/针对条目的评论违规", "Bloc111ked", "Your account has been blocked", umsg.umFlagP2],
       ["Blocked", "Blocked", "Your account has been blocked", umsg.umFlagP2 + umsg.umFlagP3],
        [" 模板:预加载警告/针对用户的评论违规", "Bloc11111ked", "Your account has been blocked", umsg.umFlagP2],
        ["Anonblock", "Anon edit blocked", "Anonymous editing is not allowed from your IP address. Create an account to contribute", umsg.umFlagIP],
       ["Blocked proxy subst", "Blocked proxy", "This IP address has been blocked because it is believed to be an open proxy or zombie computer", umsg.umFlagP2 + umsg.umFlagIP],
       ["IPsock", "IP Sock", "An editor has expressed concern that this IP address has been used by a registered user", umsg.umFlagUQ + umsg.umFlagIP],
      ];
      ];
      const placeholderTemplate = ["", "请选择警告类型", "", 0];
      const placeholderTemplate = ["", "请选择警告类型", "", 0];

2021年9月13日 (一) 18:24的版本

//<nowiki>
/**
 * https://commons.wikimedia.org/w/index.php?oldid=553484026
 */
"use strict";
(async () => {
    if (window.AxUserMsg || !mw.config.get("wgUserGroups").includes("patroller") && !mw.config.get("wgUserGroups").includes("sysop")) {
        return;
    }
    class ClsUPC {
        constructor($userInputField, $outputField, stCallBack, callingObject, CBValidChange) {
            this.$userInputField = $userInputField;
            this.$outputField = $outputField;
            this.callingObject = callingObject;
            this.stCallBack = stCallBack;
            this.CBValidChange = CBValidChange;
            this.pendingCalls = 0;
            this.pendingSetTimeouts = 0;
            this.oldValue = "";
            this.userNameExists = 2;
            const o = this;
            $userInputField.on("keyup", ( /* e*/) => {
                const tmpUNE = o.userNameExists;
                if (o.isValidIP($userInputField.val())) {
                    o.setToIP(tmpUNE);
                } else {
                    o.execApiCall(false, $userInputField.val());
                }
            });
            $userInputField.on("input", () => {
                $userInputField.keyup();
            });
            $userInputField.on("selected", () => {
                $userInputField.keyup();
            });
        }
        isValidIP(username) {
            if (mw.util.isIPv4Address(username)) {
                return true;
            } //IP v.4
            return mw.util.isIPv6Address(username); //IP v.6
        }
        setToIP(tmpUNE) {
            const o = this;
            o.userNameExists = -1;
            if ("function" === typeof o.CBValidChange && tmpUNE !== o.userNameExists) {
                o.$outputField.attr("src", o.callingObject.umImgUserIsIP);
                o.$outputField.attr("alt", "IP");
                o.oldValue = o.callingObject.umCleanFileAndUser(o.$userInputField.val());
                o.CBValidChange(o, o.callingObject);
            }
        }
        execApiCall(isTimeout, val) {
            if (isTimeout) {
                this.pendingSetTimeouts--;
            }
            if (this.oldValue !== this.callingObject.umCleanFileAndUser(val)) {
                if (this.pendingCalls > 0) {
                    if (!this.pendingSetTimeouts) {
                        this.pendingSetTimeouts++;
                        const o = this;
                        setTimeout(() => {
                            o.execApiCall(true, o.$userInputField.val());
                        }, 1000); // do not use the old value, use the current instead
                    }
                    return;
                }
                const User = this.oldValue = this.callingObject.umCleanFileAndUser(val);
                const query = {
                    action: "query",
                    list: "allusers",
                    aufrom: User,
                    auto: User,
                };
                this.callingObject.umCurrentUserQuery = this;
                this.pendingCalls++;
                this.callingObject.doAPICall(query, this.stCallBack);
            }
        }
        evalAPICall(result) {
            this.pendingCalls--;
            const uifval = this.$userInputField.val();
            if (this.oldValue !== this.callingObject.umCleanFileAndUser(uifval)) {
                // Don't do anything if user updated the field in-between
                return;
            }
            if (this.isValidIP(uifval)) {
                return;
            }
            if ("object" === typeof result.query && "object" === typeof result.query.allusers) {
                const tmpUNE = this.userNameExists;
                if (!result.query.allusers.length) {
                    this.$outputField.attr("src", this.callingObject.umImgUserNotExists);
                    this.$outputField.attr("alt", "!! invalid !!");
                    this.userNameExists = false;
                } else {
                    if (this.callingObject.umCleanFileAndUser(this.$userInputField.val()) === result.query.allusers[0].name) {
                        this.$outputField.attr("src", this.callingObject.umImgUserExists);
                        this.$outputField.attr("alt", "OK");
                        this.userNameExists = true;
                    } else {
                        if (!this.pendingSetTimeouts) {
                            // Only overwrite if there is nothing pending
                            this.$outputField.attr("src", this.callingObject.umImgUserUndefined);
                            this.$outputField.attr("alt", "?");
                            this.userNameExists = 2;
                        }
                    }
                }
                if ("function" === typeof this.CBValidChange && tmpUNE !== this.userNameExists) {
                    this.CBValidChange(this, this.callingObject);
                }
            }
        }
    }
    const umsg = window.AxUserMsg = {
        umInstall: () => {
            if ($("#t-AjaxUserMessage").length === 0) {
                const $LODLinkNode = $("#t-AjaxUserMessageLOD");
                const $Href = $LODLinkNode.length ? $LODLinkNode.eq(0) : $(mw.util.addPortletLink("p-tb", "#", "警告用户", "t-AjaxUserMessage", "加载 UserMessages 小工具"));
                $Href.on("click", (e) => {
                    e.preventDefault();
                    umsg.fireImmediately = false;
                    umsg.umNotifyUser();
                });
            }
        },
        umInstallOldLinks: () => {
            // written for Herby, who needs this for working on Commons
            umsg.umTemplate.forEach((ti, id) => {
                // Create portlet link
                const portletLink = mw.util.addPortletLink("p-tb", `#${id}`, ti[1], `t-gadgetUserMessage${id}`, ti[2]);
                // Bind click handler
                $(portletLink).on("click", (e) => {
                    e.preventDefault();
                    window.AxUserMsgPreSelect = $(portletLink).find("a").attr("href").split("#")[1];
                    if (window.AxUserMsgFireAsYouClick) {
                        const umType = umsg.umTemplate[umsg.umSelect][3];
                        if (!(umType & umsg.umFlagUQ) && !(umType & umsg.umFlagReq)) {
                            umsg.fireImmediately = true;
                        }
                        else {
                            umsg.fireImmediately = false;
                        }
                    }
                    umsg.umNotifyUser();
                });
            });
        },
        umNotifyUser: () => {
            // mw.util.addCSS('a.new{color:#B00 !important;}'); ??
            umsg.umUser = "";
            umsg.editToken = "";
            umsg.umDlgPresent = false;
            umsg.umExecuting = false;
            umsg.umPendingParser = 0;
            umsg.umPendingParserTimeouts = 0;
            umsg.umParserTimerID = 0;
            umsg.umDelay = window.AxUserMsgDelay || 100;
            umsg.umUserCache = {};
            umsg.umFileCache = {};
            umsg.umParserCache = {};
            umsg.focusOwner = "";
            umsg.umObtainEditToken();
            umsg.umUser = mw.libs.commons.guessUser();
            umsg.umDlg();
        },
        umFillSelect: (caller, o) => {
            const userstate = caller.userNameExists;
            o.umTemplate.forEach((currentTag, id) => {
                if ($(`#umOpt${id}`, o.$tagSelect).length === 0) {
                    // check wether to add
                    if (-1 === userstate && !(currentTag[3] & o.umFlagUM) || true === userstate && !(currentTag[3] & o.umFlagIP)) {
                        o.$tagSelect.append(`<option id="umOpt${id}" value="${id}">${mw.html.escape(`${currentTag[1]} - ${currentTag[2]}`)}</option>`);
                        return;
                    }
                } else {
                    // check wether to remove
                    if (-1 === userstate && currentTag[3] & o.umFlagUM || true === userstate && currentTag[3] & o.umFlagIP) {
                        $(`#umOpt${id}`, o.$tagSelect).remove();
                        return;
                    }
                }
            });
            if (-1 === userstate) {
                if (window.AxUserMsgPreSelectIP) {
                    o.$tagSelect.val(window.AxUserMsgPreSelectIP);
                }
            } else {
                o.$tagSelect.val(umsg.umSelect);
            }
            o.umValidateInput(o);
            o.$tagSelect.change();
        },
        umDlg: () => {
            const $win = $(window);
            umsg.dlg = $("<div>").html('<div id="AjaxUmContainer"></div>').dialog({
                modal: true,
                closeOnEscape: true,
                position: [Math.round(($win.width() - Math.min($win.width(), 850)) / 2), Math.round(($win.height() - Math.min($win.height(), 800)) / 2)],
                title: '警告用户 - <span class="mw-parser-output"><a href="//commons.wikimedia.org/wiki/MediaWiki_talk:Gadget-UserMessages.js" target="_blank" rel="nofollow noreferrer noopener" class="external text">去维基共享报告错误和建议</a> | <a href="https://zh.moegirl.org.cn/Help:UserMessages%E5%B0%8F%E5%B7%A5%E5%85%B7" target="_blank" class="external text">使用帮助</a></span>',
                height: Math.min($win.height(), 800),
                width: Math.min($win.width(), 850),
                buttons: {
                    [umsg.i18n.submitButtonLabel]: () => {
                        try {
                            if (umsg.umIsValid) {
                                umsg.umNotifyUserExecute();
                            }
                        } catch (ex) {
                            umsg.fail(ex);
                        }
                    },
                    [umsg.i18n.cancelButtonLabel]: function () {
                        $(this).dialog("close");
                        console.info("[this.i18n.cancelButtonLabel]", this, umsg.dlg);
                    },
                },
                close: function () {
                    console.info("close", this, umsg.dlg);
                    $(this).dialog("destroy");
                    $(this).remove();
                    umsg.umDlgPresent = false;
                },
                open: function () {
                    const $dlg = $(this); // 不可去除
                    $dlg.parents(".ui-dialog").css({
                        position: "fixed", top: `${Math.round(($win.height() - Math.min($win.height(), 800)) / 2)}px`,
                    });
                },
            });
            umsg.umDlgPresent = true;
            if (umsg.dlg) {
                const $AjaxUmContainer = $("#AjaxUmContainer");
                $AjaxUmContainer.append(`<label for="umUser">${mw.html.escape(umsg.i18n.umFillInUser)}</label><br><input type="text" id="umUser" style="width: 95%;" value="${mw.html.escape(umsg.umUser)}"/>${umsg.umInitImgUserExists.replace("%ID%", "umUserExists")}<br><br>`);
                umsg.$tagSelect = $("<select>", {
                    size: "1",
                    id: "umTagToInsert",
                    style: "width: 99%;",
                });
                $AjaxUmContainer.append([
                    `<label for="umTagToInsert">${mw.html.escape(umsg.i18n.umSelectTag)}</label><br>`,
                    umsg.$tagSelect, "<br><br>",
                    `<span id="umMediaWrapper"><label for="umMedia">${mw.html.escape(umsg.i18n.umFillInMedia)}</label><br><input type="text" id="umMedia" style="width: 95%;" value="File:"/><br><br></span>`,
                    `<span id="umP2Wrapper"><label for="umP2">${mw.html.escape(umsg.i18n.umFillInAdditional)}</label><br><input type="text" id="umP2" style="width: 95%;"/><br><br></span>`,
                    `<span id="umP3Wrapper"><label for="umP3">${mw.html.escape(umsg.i18n.umFillInAdditional)}</label><br><input type="text" id="umP3" style="width: 95%;"/><br><br></span>`,
                    `<span id="umRelatedUserWrapper"><label for="umRelatedUser">${mw.html.escape(umsg.i18n.umFillInRelUser)}</label><br><input type="text" id="umRelatedUser" style="width: 95%;" value="User:"/>${umsg.umInitImgUserExists.replace("%ID%", "umRelatedUserExists")}<br><br></span>`,
                    `<span id="umSummaryWrapper"><label for="umSummary">${mw.html.escape(umsg.i18n.umFillInSummary)}</label><br><input type="text" id="umSummary" style="width: 95%;" value="摘要"/><br><br></span>`,
                    `<label for="umAddText">${mw.html.escape(umsg.i18n.umAddText)}</label><br><textarea id="umAddText" style="width: 95%; height: 5em;">${mw.html.escape(window.AxUserMsgCustomText || "")}</textarea><br><br>`,
                ]);
                umsg.talkTag = "";
                const $umMedia = $("#umMedia"),
                    $umP2 = $("#umP2"),
                    $umP3 = $("#umP3"),
                    $umUser = $("#umUser"),
                    $umRelatedUser = $("#umRelatedUser"),
                    $umSummary = $("#umSummary"),
                    $umAddText = $("#umAddText");
                umsg.uUPC = new ClsUPC($umUser, $("#umUserExists"), "umUserExistsCB", umsg, umsg.umFillSelect);
                umsg.ouUPC = new ClsUPC($umRelatedUser, $("#umRelatedUserExists"), "umUserExistsCB", umsg, umsg.umUserValidChange);
                const submitButton = $(".ui-dialog-buttonpane button:first");
                // guessing the related file thanks User:Platonides
                const guessFile = () => {
                    let f = mw.util.getParamValue("title", document.referrer);
                    if (f && /File:/.test(f)) {
                        return f;
                    }
                    f = mw.util.getParamValue("page", document.referrer);
                    if (f && /File:/.test(f)) {
                        return f;
                    }
                    f = mw.util.getParamValue("target", document.referrer);
                    if (f && /File:/.test(f)) {
                        return f;
                    }
                    const m = document.referrer.match(/File:(.+)/);
                    try {
                        if (m) {
                            if (/&.+=/.test(m[1])) {
                                return `File:${decodeURI(m[1]).match(/^(.+)&/)[1]}`;
                            } return `File:${m[1]}`;
                        }
                    } catch {
                    }
                };
                const umFile = guessFile();
                if (umFile) {
                    $umMedia.val(decodeURIComponent(umFile).replace(/_/g, " "));
                }
                $umUser.on("keyup", (event) => {
                    $umUser.val($umUser.val().replace(/<|>|\^/g, ""));
                });
                $umUser.autocomplete({
                    minLength: 1,
                    source: (request, callback) => {
                        umsg.umSeekUsers(request, callback);
                    },
                    close: () => {
                        $umUser.triggerHandler("selected");
                    },
                });
                $umMedia.on("change", () => {
                    umsg.umValidateInput(umsg);
                });
                $umMedia.on("input", () => {
                    $umMedia.val($umMedia.val().replace(/<|>|\^/g, ""));
                    umsg.umValidateInput(umsg);
                });
                $umMedia.on("keyup", (e) => {
                    $umMedia.val($umMedia.val().replace(/<|>|\^/g, ""));
                });
                $umMedia.autocomplete({
                    minLength: 1,
                    source: (request, callback) => {
                        umsg.umSeekFiles(request, callback);
                    },
                    close: () => {
                        $umMedia.triggerHandler("input");
                    },
                });
                $umRelatedUser.on("keyup", (event) => {
                    $umRelatedUser.val($umRelatedUser.val().replace(/<|>|\^/g, ""));
                    umsg.umValidateInput(umsg);
                });
                $umRelatedUser.autocomplete({
                    minLength: 1,
                    source: (request, callback) => {
                        umsg.umSeekUsers(request, callback);
                    },
                    close: () => {
                        $umRelatedUser.triggerHandler("selected");
                    },
                });
                $umP2.on("change", () => {
                    umsg.umValidateInput(umsg);
                });
                $umP2.on("input", () => {
                    umsg.umValidateInput(umsg);
                });
                $umP3.on("change", () => {
                    umsg.umValidateInput(umsg);
                });
                $umP3.on("input", () => {
                    umsg.umValidateInput(umsg);
                });
                $umAddText.on("change", () => {
                    umsg.umValidateInput(umsg);
                });
                $umAddText.on("input", () => {
                    umsg.umValidateInput(umsg);
                });
                submitButton.focus();
                $AjaxUmContainer.append(umsg.umInstPrevContainer.clone().text("即时预览为空"));
                umsg.$tagSelect.on("change", () => {
                    const umType = umsg.umTemplate[$(umsg.$tagSelect).val()][3];
                    $umSummary.val(umsg.umTemplate[$("#umTagToInsert").val()][4] ? umsg.umTemplate[$("#umTagToInsert").val()][4] : `${umsg.umTemplate[$("#umTagToInsert").val()][2]}.`);
                    umsg.umValidateInput(umsg);
                    umsg.$tagSelect.combobox({
                        displaytext: umsg.$tagSelect.val() ? umsg.$tagSelect.children(":selected").text() : "",
                    });
                    if (umType & umsg.umFlagP2) {
                        $("#umP2Wrapper").show();
                        if (document.activeElement && $umUser.attr("id") !== document.activeElement.id) {
                            $("#umP2").select();
                        }
                    } else {
                        $("#umP2Wrapper").hide();
                    }
                    if (umType & umsg.umFlagP3) {
                        $("#umP3Wrapper").show();
                        if (document.activeElement && $umUser.attr("id") !== document.activeElement.id) {
                            $("#umP3").select();
                        }
                    } else {
                        $("#umP3Wrapper").hide();
                    }
                    if (umType & umsg.umFlagMQ) {
                        $("#umMediaWrapper").show();
                        if (document.activeElement && $umUser.attr("id") !== document.activeElement.id) {
                            $("#umMedia").select();
                        }
                    } else {
                        $("#umMediaWrapper").hide();
                    }
                    if (umType & umsg.umFlagUQ) {
                        $("#umRelatedUserWrapper").show();
                        if (document.activeElement && $umUser.attr("id") !== document.activeElement.id) {
                            $("#umMedia").select();
                        }
                    } else {
                        $("#umRelatedUserWrapper").hide();
                    }
                });
                umsg.$tagSelect.val(umsg.umSelect);
                $("#umUser").keyup();
                $("#umTagToInsert").combobox();
            }
        },
        umSeekUsers: (request, pCallback) => {
            const query = {
                action: "query",
                list: "allusers",
                auprefix: request.term.replace(/^(?:User):/, ""),
            };
            umsg.doGetApiCall(query, "umSeekUsersCB", pCallback);
        },
        umSeekUsersCB: (result, pCallback) => {
            const searchTerms = [];
            result.forEach((usi) => {
                searchTerms.push({
                    id: usi.userid,
                    value: usi.name,
                });
            });
            if ("function" === typeof pCallback) {
                pCallback(searchTerms);
            }
        },
        umSeekFiles: (request, pCallback) => {
            const query = {
                action: "query",
                list: "allimages",
                aiprefix: request.term.replace(/^(?:File|Image):/, ""),
            };
            umsg.doGetApiCall(query, "umSeekFilesCB", pCallback);
        },
        umSeekFilesCB: (result, pCallback) => {
            const searchTerms = [];
            result.forEach((fii) => {
                searchTerms.push({
                    id: fii.timestamp, value: `File:${fii.name}`,
                });
            });
            if ("function" === typeof pCallback) {
                pCallback(searchTerms);
            }
        },
        umUserExistsCB: (result) => {
            umsg.umCurrentUserQuery.evalAPICall(result);
        },
        umShowInfo: (info, o) => {
            $("#umInstantPreviewContainer").empty().html(`<p class="center"><img src="${o.umImgInfo}" width="64" height="64"/><br>${info}</p>`);
        },
        umValidateInput: (o) => {
            umsg.umIsValid = true;
            const umType = umsg.umTemplate[$("#umTagToInsert").val()][3];
            const submitButton = $(".ui-dialog-buttonpane button:first");
            const validRelatedUser = () => {
                if (umType & o.umFlagUQ) {
                    if (o.umCleanFileAndUser($("#umRelatedUser").val()).length < 1) {
                        o.umShowInfo("未指定相关用户", o);
                        return false;
                    }
                    if (!o.ouUPC.userNameExists) {
                        o.umShowInfo("所指定的相关用户不存在", o);
                        return false;
                    }
                }
                return true;
            };
            const validMedia = () => {
                if (umType & o.umFlagMQ) {
                    if (o.umCleanFileAndUser($("#umMedia").val()).length < 5 && umType & o.umFlagReq) {
                        o.umShowInfo("未指定文件,且强制检查已开启", o);
                        return false;
                    }
                }
                return true;
            };
            const validUser = () => {
                if (o.umCleanFileAndUser($("#umUser").val()).length < 1) {
                    o.umShowInfo("未指定用户", o);
                    return false;
                }
                if (!o.uUPC.userNameExists) {
                    o.umShowInfo("所指定的用户不存在", o);
                    return false;
                }
                return true;
            };
            umsg.umIsValid = umsg.umIsValid && validRelatedUser() && validMedia() && validUser();
            if (umsg.umIsValid) {
                submitButton.removeClass("ui-state-disabled");
                if (umType & umsg.umFlagMQ) {
                    umsg.talkTag = `\n{{${umsg.umTemplate[$("#umTagToInsert").val()][0]}${umsg.umCleanFileAndUser($("#umMedia").val()) ? `|1=${umType & umsg.umFlagNS ? `File:${umsg.umCleanFileAndUser($("#umMedia").val())}` : $("#umMedia").val()}` : ""}`;
                } else if (umType & umsg.umFlagUQ) {
                    umsg.talkTag = `\n{{${umsg.umTemplate[$("#umTagToInsert").val()][0]}|1=${umsg.umCleanFileAndUser($("#umRelatedUser").val())}`;
                }
                else {
                    umsg.talkTag = `\n{{${umsg.umTemplate[$("#umTagToInsert").val()][0]}`;
                }
                let paramCount = (umType & umsg.umFlagUQ ? 1 : 0) + (umType & umsg.umFlagMQ ? 1 : 0);
                // cut of extra white space
                const sigText = "——~~~~";
                if (umType & umsg.umFlagP2) {
                    paramCount++;
                    umsg.talkTag += `|${paramCount}=${$("#umP2").val()}`;
                }
                if (umType & umsg.umFlagP3) {
                    paramCount++;
                    umsg.talkTag += `|${paramCount}=${$("#umP3").val()}`;
                }
                umsg.talkTag += "}}";
                if ("\n{{}}" === umsg.talkTag) {
                    umsg.talkTag = "\n";
                }
                umsg.talkTag += `\n${$("#umAddText").val().replace(/~{3,5}$/, "")}${sigText}\n`;
                umsg.umParseTemplate(false);
                // If the user wants the old behaviour back, we fire immediately
                if (umsg.fireImmediately) {
                    umsg.umNotifyUserExecute();
                }
            } else {
                submitButton.addClass("ui-state-disabled");
            }
        },
        umUserValidChange: (_, o) => {
            o.umValidateInput(o);
        },
        umCleanFileAndUser: (input) => {
            let output = "";
            if (input) {
                output = input.replace(/_/g, " ").replace(/File:/g, "").replace(/Image:/g, "").replace(/User:/g, "").replace(/^\s+|\s+$/g, "");
                output = output.substring(0, 1).toUpperCase() + output.substring(1);
            }
            return output;
        },
        umParseTemplate: (viaSetTimeout) => {
            if (window.AxUserMsgNoParse) {
                return;
            }
            if (viaSetTimeout) {
                umsg.umPendingParserTimeouts--;
            }
            if (umsg.umPendingParser > 0) {
                if (!umsg.umPendingParserTimeouts) {
                    // call me later
                    const o = umsg;
                    umsg.umPendingParserTimeouts++;
                    setTimeout(() => {
                        o.umParseTemplate(true);
                    }, 300);
                }
                return;
            }
            const maybeParse = () => {
                umsg.umPendingParser++;
                const action = {
                    action: "parse",
                    uselang: mw.config.get("wgUserLanguage"),
                    redirects: true,
                    prop: "text",
                    pst: true,
                    title: umsg.umUserTalkPrefix + $("#umUser").val(),
                    text: umsg.talkTag,
                };
                umsg.umDelay = Math.min(umsg.umDelay + 30, 1500); // Save server resources.
                umsg.doAPICall(action, "umParsedTemplate");
            };
            if (umsg.umParserTimerID) {
                clearTimeout(umsg.umParserTimerID);
            }
            umsg.umParserTimerID = setTimeout(maybeParse, umsg.umDelay);
        },
        umParsedTemplate: (result) => {
            umsg.umPendingParser--;
            if ("object" === typeof result.parse && "object" === typeof result.parse.text && umsg.umDlgPresent && !umsg.umExecuting && umsg.umIsValid) {
                let $containerText = result.parse.text["*"].replace(" API", ` ${umsg.umCleanFileAndUser($("#umUser").val())}`).replace(">API", `>${umsg.umCleanFileAndUser($("#umUser").val())}`);
                $containerText = $($containerText);
                $(".editsection", $containerText).remove();
                $("a", $containerText).attr("target", "_blank");
                $("#umInstantPreviewContainer").empty().append($containerText).resizable({
                    alsoResize: "#AjaxUmContainer",
                });
            }
        },
        umObtainEditToken: () => {
            if (mw.user && mw.user.tokens) {
                umsg.editToken = mw.user.tokens.get("csrfToken");
            }
            umsg.editToken = umsg.editToken || (mw.user.isAnon() ? "+\\" : "");
            if (umsg.editToken) {
                return;
            }
            const query = {
                action: "query",
                prop: "info",
                intoken: "edit",
                titles: "FAQ", // Random title
            };
            umsg.doAPICall(query, "umObtainEditTokenCB");
        },
        umObtainEditTokenCB: (result) => {
            const pages = result.query.pages;
            for (const id in pages) {
                // there should be only one, but we don't know its ID
                if (Object.hasOwnProperty.bind(pages)(id)) {
                    umsg.editToken = pages[id].edittoken;
                }
            }
        },
        umNotifyUserExecute: () => {
            if (umsg.umExecuting) {
                return;
            }
            umsg.pageName = umsg.umUserTalkPrefix + $("#umUser").val();
            umsg.talkSummary = $("#umSummary").val();
            umsg.appendTemplate();
        },
        appendTemplate: () => {
            const page = [];
            page.title = umsg.pageName;
            page.text = umsg.talkTag;
            page.editType = "appendtext";
            page.redirect = true;
            if (window.AjaxDeleteWatchFile) {
                page.watchlist = "watch";
            }
            umsg.umExecuting = true;
            $("#umInstantPreviewContainer").empty().html('<p class="center"><img src="https://img.moegirl.org.cn/common/d/d1/Windows_10_loading.gif"/></p>');
            umsg.savePage(page, umsg.talkSummary, "umNotifyUserExecuteCB");
        },
        savePage: (page, summary, callback) => {
            const edit = {
                action: "edit",
                summary: summary,
                watchlist: page.watchlist || "preferences",
                title: page.title,
            };
            if (page.redirect) {
                edit.redirect = "";
            }
            edit[page.editType] = page.text;
            edit.token = umsg.editToken;
            umsg.doAPICall(edit, callback);
        },
        umNotifyUserExecuteCB: (/* result*/) => {
            let encTitle = umsg.umUserTalkPrefix + $("#umUser").val();
            encTitle = encodeURIComponent(encTitle.replace(/ /g, "_")).replace(/%2F/ig, "/").replace(/%3A/ig, ":");
            const newLoc = mw.config.get("wgServer") + mw.config.get("wgArticlePath").replace("$1", encTitle);
            if (window.location.pathname === mw.config.get("wgArticlePath").replace("$1", encTitle)) {
                window.location.hash = "#footer";
                window.location.reload();
            } else {
                window.location.href = `${newLoc}#footer`;
            }
            umsg.umExecuting = false;
        },
        doGetApiCall: (params, callback, pCallback) => {
            // Local Cache
            if (params.list) {
                if ("allusers" === params.list) {
                    if (params.auprefix in umsg.umUserCache) {
                        umsg[callback](umsg.umUserCache[params.auprefix], pCallback);
                        return;
                    }
                } else if ("allimages" === params.list) {
                    if (params.aiprefix in umsg.umFileCache) {
                        umsg[callback](umsg.umFileCache[params.aiprefix], pCallback);
                        return;
                    }
                }
            }
            params.format = "json";
            $.ajax({
                url: umsg.apiURL,
                cache: true,
                dataType: "json",
                data: params,
                type: "GET",
                async: true,
                success: (result) => {
                    if (!result) {
                        if ("function" === typeof pCallback) {
                            pCallback();
                        } return;
                    }
                    try {
                        if (params.list) {
                            if ("allusers" === params.list) {
                                // cache the result
                                umsg.umUserCache[params.auprefix] = result.query.allusers;
                                umsg[callback](result.query.allusers, pCallback);
                                return;
                            }
                        }
                        if (params.list) {
                            if ("allimages" === params.list) {
                                // cache the result
                                umsg.umFileCache[params.aiprefix] = result.query.allimages;
                                umsg[callback](result.query.allimages, pCallback);
                                return;
                            }
                        }
                        // This is a "must", the doc sais
                        if ("function" === typeof pCallback) {
                            pCallback();
                        }
                        umsg[callback](result);
                    } catch (e) {
                        return umsg.fail(e);
                    }
                },
                error: () => {
                    // This is a "must", the doc sais
                    if ("function" === typeof pCallback) {
                        pCallback();
                    }
                },
            });
        },
        doAPICall: (params, callback) => {
            if (params.action) {
                if ("parse" === params.action) {
                    if (params.text in umsg.umParserCache) {
                        umsg[callback](umsg.umParserCache[params.text]);
                        return;
                    }
                }
            }
            params.format = "json";
            $.ajax({
                url: umsg.apiURL,
                cache: false,
                dataType: "json",
                data: params,
                type: "POST",
                success: (result, _, x) => {
                    if (!result) {
                        return umsg.fail(`API 响应为空:\n${x.responseText}`);
                    }
                    // In case we get the mysterious 231 unknown error, just try again
                    if (result.error && result.error.info.includes("231")) {
                        return setTimeout(() => {
                            umsg.doAPICall(params, callback);
                        }, 500);
                    }
                    if (result.error) {
                        return umsg.fail(`API 请求返回失败结果 (${result.error.code}):${result.error.info}`);
                    }
                    if (result.edit && result.edit.spamblacklist) {
                        return umsg.fail(`此次编辑因 ${result.edit.spamblacklist} 被列入黑名单而失败`);
                    }
                    if (params.action) {
                        if ("parse" === params.action) {
                            umsg.umParserCache[params.text] = result;
                        }
                    }
                    try {
                        umsg[callback](result);
                    } catch (e) {
                        return umsg.fail(e);
                    }
                },
                error: (x, status, error) => {
                    return umsg.fail(`API 请求遭遇网络错误:状态码为 【${x.status}】,状态文本为 【${status}】,错误信息为 【${error}】`);
                },
            });
        },
        fail: (_err) => {
            let err;
            if ("object" === typeof _err) {
                let stErr = `${mw.html.escape(_err.message)}<br>${_err.name}`;
                if (_err.lineNumber) {
                    stErr += ` @line${_err.lineNumber}`;
                }
                if (_err.line) {
                    stErr += ` @line${_err.line}`;
                }
                err = stErr;
            } else {
                err = mw.html.escape(_err.toString());
            }
            if (umsg.umDlgPresent) {
                $("#umInstantPreviewContainer").empty().html(`<p class="center"><img src="${umsg.umImgErr}" width="64" height="64"/></p><br>小工具发生错误:<br>${mw.html.escape(err)}`);
            } else {
                mw.notify(`小工具发生错误:${err}`);
            }
        },
        i18n: {
            umFillInUser: "被警告的用户:",
            umSelectTag: "警告类型:",
            umFillInMedia: "有关文件",
            umFillInAdditional: "用户违规的页面:",
            umFillInRelUser: "被警告的相关用户:",
            umFillInSummary: "编辑摘要:",
            umAddText: "额外信息:",
            submitButtonLabel: "留言",
            cancelButtonLabel: "取消",
        },
        umInstPrevContainer: $("<div>", {
            id: "umInstantPreviewContainer", style: "background-color:#EFD;min-height:300px;vertical-align:middle;",
        }),
        umInitImgUserExists: '<img id="%ID%" src="https://img.moegirl.org.cn/common/thumb/4/42/P_no.svg/20px-P_no.svg.png" alt="?"/>',
        umImgUserUndefined: "https://img.moegirl.org.cn/common/thumb/4/42/P_no.svg/20px-P_no.svg.png",
        umImgUserNotExists: "https://img.moegirl.org.cn/common/thumb/4/42/P_no.svg/20px-P_no.svg.png",
        umImgUserExists: "https://img.moegirl.org.cn/common/thumb/b/be/P_yes.svg/20px-P_yes.svg.png",
        umImgUserIsIP: "https://img.moegirl.org.cn/common/thumb/7/7e/OOjs_UI_icon_userAnonymous.svg/20px-OOjs_UI_icon_userAnonymous.svg.png",
        umImgErr: "https://img.moegirl.org.cn/common/7/7f/Dialog-error.svg",
        umImgWarn: "https://img.moegirl.org.cn/common/b/bc/Commons-emblem-issue.svg",
        umImgInfo: "https://img.moegirl.org.cn/common/2/28/Commons-emblem-notice.svg",
        umFlagMQ: 1, // Media Query
        umFlagUQ: 2, // Username Query
        umFlagReq: 4, // Required - must filled in
        umFlagNS: 8, // Add Namespace
        umFlagP2: 16, // add a universal parameter
        umFlagP3: 32, // add a universal parameter
        umFlagIP: 64, // Message for IP only
        umFlagUM: 128, // User message only
        umFlagReqMqNs: 13, // Combination of (umFlagReq | umFlagMQ | umFlagNS)
        umUserTalkPrefix: `${mw.config.get("wgFormattedNamespaces")[3]}:`,
        apiURL: mw.util.wikiScript("api"),
    };
    /**
     * A custom widget built by composition of Autocomplete and Button.
     * You can either type something into the field to get filtered suggestions based on your input,
     * or use the button to get the full list of selections.
     *
     * The input is read from an existing select-element for progressive enhancement,
     * passed to Autocomplete with a customized source-option.
     *
     * Autor: someone from the jQuery UI-Team?
     * slightly altered
    **/
    const initCombobox = ($) => {
        $.widget("ui.combobox", {
            // These options will be used as defaults
            options: {
                displaytext: "",
                emptyMessage: 42,
                passEnter: true,
                shutOff: window.AxUserMsgUseSelect,
            },
            // Use the _setOption method to respond to changes to options
            _setOption: function (key, value, ...args) {
                if (this.options.shutOff) {
                    return;
                }
                switch (key) {
                    case "displaytext":
                        this.input.val(value);
                        break;
                    case "passEnter":
                        this.options.passEnter = value;
                        break;
                }
                // In jQuery UI 1.8, you have to manually invoke the _setOption method from the base widget
                $.Widget.prototype._setOption.apply(this, [key, value, ...args]);
            },
            _create: function () {
                if (this.options.shutOff) {
                    return;
                }
                const self = this,
                    select = this.element.hide(),
                    selectWidth = select.width(),
                    selectId = select.attr("id"),
                    selected = select.children(":selected"),
                    value = selected.val() ? selected.text() : "",
                    ownId = `j${Math.floor(Math.random() * 10000000000)}`;
                let selectLabels,
                    isOpen = false,
                    valid = true;
                if (selectId) {
                    selectLabels = $(`label[for="${selectId}"]`);
                }
                const portMessure = this.portMessure = $("<div>", {
                    id: `${ownId}vp`,
                }).css({
                    position: "fixed", top: "0", height: "0",
                });
                $("body").append(portMessure);
                const input = this.input = $("<input>", {
                    id: ownId,
                }).insertAfter(select).val(value).autocomplete({
                    delay: 0,
                    minLength: 0,
                    source: (request, response) => {
                        let i = 0;
                        const matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i");
                        response(select.children("option").map((_, ele) => {
                            if (i > (window.AxUserMsgMaxSelect || 20) && request.term) {
                                return;
                            }
                            const text = $(ele).text();
                            if (ele.value && (!request.term || matcher.test(text))) {
                                i++;
                                return {
                                    label: text.replace(
                                        new RegExp(
                                            `(?![^&;]+;)(?!<[^<>]*)(${$.ui.autocomplete.escapeRegex(request.term)
                                            })(?![^<>]*>)(?![^&;]+;)`, "gi",
                                        ), "<b>$1</b>"),
                                    value: text,
                                    option: ele,
                                };
                            }
                        }));
                    },
                    select: (event, ui) => {
                        ui.item.option.selected = true;
                        self._trigger("selected", event, {
                            item: ui.item.option,
                        });
                        select.triggerHandler("change");
                    },
                    change: function (_, ui) {
                        setTimeout(() => {
                            console.info("change", input, this, ui);
                        }, 16);
                        if (!ui.item) {
                            const matcher = new RegExp(`^${$.ui.autocomplete.escapeRegex($(this).val())}$`, "i");
                            valid = false;
                            select.children("option").each((_, ele) => {
                                if ($(ele).text().match(matcher)) {
                                    ele.selected = valid = true;
                                    return false;
                                }
                            });
                            if (!valid) {
                                // remove invalid value, as it didn't match anything
                                $(this).val("");
                                input.data("autocomplete").term = "";
                                select.val(self.options.emptyMessage);
                                select.triggerHandler("change");
                                return false;
                            }
                            select.triggerHandler("change");
                        }
                    },
                    create: function () {
                        const $this = $(this); // 不可去除
                        const t_top = $this.offset().top - portMessure.offset().top;
                        $(".ui-autocomplete.ui-menu").css({
                            position: "fixed",
                            overflow: "auto",
                            "max-height": `${Math.round($(window).height() - t_top - $this.height() - 20)}px`,
                        });
                    },
                    close: () => {
                        setTimeout(() => {
                            isOpen = false;
                        }, 1);
                    },
                    open: function () {
                        setTimeout(() => {
                            console.info("open", input, this);
                        }, 16);
                        isOpen = true;
                        const _t = $(this),
                            t_top = _t.offset().top - portMessure.offset().top;
                        $(".ui-autocomplete.ui-menu").css({
                            position: "fixed",
                            "max-height": `${Math.round($(window).height() - t_top - _t.height() - 20)}px`,
                        });
                    },
                }).addClass("ui-widget ui-widget-content ui-corner-left").css("width", `${selectWidth - 70}px`).on("click", () => {
                    $(input).select();
                }).on("keydown", (e) => {
                    if (self.options.passEnter && 13 === e.which && !isOpen && valid) {
                        const kup = $.Event("keyup");
                        kup.ctrlKey = false;
                        kup.keyCode = kup.which = 13;
                        select.triggerHandler(kup);
                    }
                });
                if (selectLabels) {
                    selectLabels.attr("for", ownId);
                }
                input.data("autocomplete")._renderItem = (ul, item) => {
                    return $("<li>").data("item.autocomplete", item).append(`<a>${item.label}</a>`).appendTo(ul);
                };
                this.button = $("<button>", {
                    tabIndex: -1,
                    type: "button",
                    text: "&nbsp;",
                    title: "Show All Items",
                    style: "height:1.5em;padding:0!important;width:20px;margin:0!important;position:relative;top:-2px;",
                }).insertAfter(input).button({
                    icons: {
                        primary: "ui-icon-triangle-1-s",
                    },
                    text: false,
                }).removeClass("ui-corner-all").addClass("ui-corner-right ui-button-icon").on("click", () => {
                    // close if already visible
                    if (input.autocomplete("widget").is(":visible")) {
                        input.autocomplete("close");
                        return;
                    }
                    // work around a bug (likely same cause as #5265)
                    this.button.blur();
                    // pass empty string as value to search for, displaying all results
                    input.autocomplete("search", "");
                    input.focus();
                });
            },
            destroy: function () {
                if (this.options.shutOff) {
                    return;
                }
                this.input.remove();
                this.portMessure.remove();
                this.button.remove();
                this.element.show();
                $.Widget.prototype.destroy.call(this);
            },
        });
    };
    if ([-1, 2, 3].includes(mw.config.get("wgNamespaceNumber"))) {
        // alternative for jQuery UI autocomplete: jquery.suggestions
        // http://jqueryui.com/demos/autocomplete/ http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/resources/jquery/jquery.suggestions.js?view=markup
        await mw.loader.using([
            "ext.gadget.jquery.ui",
            "mediawiki.util",
            "mediawiki.user",
            "user.options", // for sig
            "ext.gadget.libUtil",
            "ext.gadget.libJQuery",
        ]);
        const builtinTemplate = [
            /*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
             * Append new messages at the bottom. Otherwise pre-selection for users will break.
             !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*/
            //  ['Template name',             "Name in Sidebar", "Detailed text",                                                                          Type/Prompt statement,           'Talk summary'];
            ["模板:预加载警告/繁简输入", "错误的繁体输入", "用户在简体模式下输入繁体编辑条目", umsg.umFlagUM, "用户违反编辑规则警告", 0],
            ["模板:预加载警告/忍耐是有限的", "我们的忍耐是有限的", "用户进行了相当数量的违反编辑规范的操作", umsg.umFlagUM, "用户违反编辑规范警告", 0],
            ["模板:预加载警告/针对条目的评论违规", "Bloc111ked", "Your account has been blocked", umsg.umFlagP2],
            ["模板:预加载警告/针对用户的评论违规", "Bloc11111ked", "Your account has been blocked", umsg.umFlagP2],
        ];
        const placeholderTemplate = ["", "请选择警告类型", "", 0];
        Object.defineProperties(umsg, {
            umTemplate: {
                get: () => {
                    if (!Array.isArray(window.AxUserMsgCustomTemplate)) {
                        window.AxUserMsgCustomTemplate = [];
                    }
                    const result = [
                        ...window.AxUserMsgUseBuiltinTemplate !== false ? builtinTemplate : [],
                        ...window.AxUserMsgCustomTemplate,
                        placeholderTemplate,
                    ];
                    result.push = (...args) => window.AxUserMsgCustomTemplate.push(...args);
                    return result;
                },
                configurable: false,
                enumerable: true,
            },
            umSelect: {
                get: () => {
                    if (Number.isSafeInteger(window.AxUserMsgPreSelect) && window.AxUserMsgPreSelect >= 0 && window.AxUserMsgPreSelect < umsg.umTemplate.length) {
                        return window.AxUserMsgPreSelect;
                    }
                    return umsg.umTemplate.indexOf(placeholderTemplate);
                },
                configurable: false,
                enumerable: true,
            },
        });
        $(() => {
            initCombobox($);
            umsg.umInstall();
            if (window.installOldLinks) {
                umsg.umInstallOldLinks();
            }
            $(document).triggerHandler("scriptLoaded", ["AxUserMsg", umsg]);
        });
    }
})();
//</nowiki>