MediaWiki:AxUserMsg.js

H萌娘,万物皆可H的百科全书!
imported>=海豚=2021年9月12日 (日) 23:51的版本 (创建页面,内容为“ //<nowiki> →‎* * https://commons.wikimedia.org/w/index.php?oldid=553484026:​ "use strict"; (async () => { if (window.AxUserMsg || !mw.config.get("wgUserGroup…”
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

注意:这类代码页面在保存之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。

  • 按住CTRL+SHIFT+DEL 或 ⌘-Shift-R来清除缓存!
  • 或尝试在地址栏的地址最后添加代码?_=1来访问最新页面。
    添加代码后的本页地址如下:-{R|https://hmoegirl.com/MediaWiki:AxUserMsg.js?_=1}-
  • 你还可以在设置中勾选小工具在页面右上角添加清除缓存按钮!
//<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{{subst:${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{{subst:${umsg.umTemplate[$("#umTagToInsert").val()][0]}|1=${umsg.umCleanFileAndUser($("#umRelatedUser").val())}`;
                }
                else {
                    umsg.talkTag = `\n{{subst:${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{{subst:}}" === 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;height:380px;overflow:scroll;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'];
            ["Please link images", "Please link", "Request user to please link their images through categories or galleries", umsg.umFlagUM, "Please link images."],
            ["Copyvionote", "Copyvionote", "Inform user about speedy deletion of uploaded media", umsg.umFlagUM + umsg.umFlagReqMqNs, "Please upload [[Commons:Copyright|free content]] only."],
            ["Derivativenote", "Derivativenote", "Inform user about speedy deletion of uploaded derivative media", umsg.umFlagUM + umsg.umFlagReqMqNs, "Even [[COM:DW|derivative works]] of copyrighted material is protected."],
            ["No fair use", "No fair use", "Inform user that Commons does not accept fair use", umsg.umFlagUM + umsg.umFlagReqMqNs, "On Commons, we do not accept [[COM:FU|fair-use-media]]. Please stop uploading those."],
            ["Please name images", "Please name", "Request user to please name their images correctly", umsg.umFlagUM + umsg.umFlagReqMqNs, "Please [[Commons:File naming|name images]] correctly."],
            ["Please tag images", "Please tag", "Request user to please tag their images", umsg.umFlagUM, "The description of one of your uploads is lacking in information about reusing. Please check the whole description-page."],
            ["Please describe images", "Please describe", "Request user to please describe their images", umsg.umFlagUM + umsg.umFlagReqMqNs, "What is it? Could you please describe it in more detail?"],
            ["Project scope", "Project scope", "Inform user on project scope after deleting out of scope contributions", umsg.umFlagMQ, "Please do not create [[COM:PS|out of scope media and pages.]]"],
            ["No comments", "Use talk pages", "Inform user to use talk pages", 0, "Please put comments to [[COM:TALK|the appropriate place]]."],
            ["Welcome", "Welcome", "Welcome a new user or a user who has not yet received a welcome message", 0, "Hello, [[Commons:Welcome|Welcome to Wikimedia Commons! -A database of freely usable media files]]."],
            ["End of copyvios", "End copyvio", "Give user a final warning because of previous copyright violations", umsg.umFlagUM, "Immediately stop uploading copyright violations, please."],
            ["Fcs", "Stop copyvio", "Give user a polite warning because of copyright violations", umsg.umFlagUM, "Stop uploading copyright violations, please."],
            ["Off topic", "Off topic", "Please stay on topic in Commons", 0, "Commons is not Wikipedia. Wikipedia is for articles; Commons for media."],
            ["No re-uploading", "No re-uploading", "Please do not re-upload", umsg.umFlagUM],
            ["Test", "Test/Sandbox", "Referral to sandbox for conducting experiments", umsg.umFlagP2 + umsg.umFlagP3, "We have a [[COM:SAND|sandbox]] for test edits; or use the preview function if you want to test."],
            ["Test2", "Vandalism", "Warning or vandalism and request to cease", umsg.umFlagP2 + umsg.umFlagP3, "Please do not contribute nonsense-edits."],
            ["Test3", "Vandalism 2", "Second warning for vandalism and announcement of block if it continues", umsg.umFlagP2 + umsg.umFlagP3, "Please stop making nonsense-edits."],
            ["Test4", "Vandalism 3", "Last warning for vandalism and announcement of block on next violation", 0, "Last warning: Stop producing nonsense!"],
            ["Inappropriate imagenotes", "Imagenotes", "Tell user not to add inappropriate imagenotes", umsg.umFlagUM, "We have a [[COM:ANN|guideline for imagenotes]]."],
            ["Dont remove delete", "Rem.Delete", "Please do not remove deletion-templates from pages nominated for deletion", 0],
            ["Dont remove nsd or nld", "Rem.n(slp)d", "Please do not remove valid warning tags from file-description pages", 0],
            ["Dont remove speedy", "Rem.speedy", "Please do not remove speedy-deletion tags", 0],
            ["Dont remove warnings", "Rem.warning", "Please do not remove valid warning templates from your talk page, except while archiving", 0],
            ["Be civil", "Be civil", "Please remain civil, even if your contributions are being attacked", 0],
            ["Be civil final", "Be civil final", "Remain civil! You will become blocked next time", 0],
            ["Blocked user with header", "Blocked user with header", "Your account has been blocked", umsg.umFlagP2 + umsg.umFlagP3],
            ["Inappropriate username", "Inapp.username", "Your username is considered being inappropriate and therefore your account has been blocked", umsg.umFlagUM],
            ["Copyviouploadindefblock", "Blocked indefinite (copyvio)", "User blocked because they did not stop uploading copyvios after warnings", umsg.umFlagUM, "You did not stop uploading copyright violations and therefore we had to block you."],
            ["Indefblockeduser", "Blocked indef", "Your account has been blocked indefinitely", umsg.umFlagP2],
            ["Imposter", "Imposter", "Mark account as blocked for impersonation or attack", umsg.umFlagUQ],
            ["Please register", "Please register", "Please create an account. Your contributions are numerous and quite good", umsg.umFlagIP],
            ["Provide better quality", "Better quality", "Do you have a better version of this media?", umsg.umFlagUM + umsg.umFlagReqMqNs],
            ["Please use SVG", "Use SVG, please", "SVG (Scalable Vector Graphics) have a few advantages, I'll tell you some of them", umsg.umFlagUM],
            ["No scaled down dupes", "Scaled down", "Please do not upload scaled down duplicates. MediaWiki can change the size for you", umsg.umFlagUM + umsg.umFlagMQ + umsg.umFlagNS],
            ["Unfree", "Unfree", "Image deletion notification", umsg.umFlagUM + umsg.umFlagMQ + umsg.umFlagNS],
            ["Attackimage", "Attackimage", "Please do not upload attack images", umsg.umFlagUM + umsg.umFlagReqMqNs],
            ["Attackpage", "Attackpage", "Please do not create attack pages", umsg.umFlagP2 + umsg.umFlagReq],
            ["Dont overwrite", "Dont overwrite", "Please do not overwrite images", umsg.umFlagUM + umsg.umFlagMQ + umsg.umFlagNS],
            ["Dont recreate", "Dont recreate", "Please do not recreate deleted images", umsg.umFlagUM + umsg.umFlagReqMqNs],
            ["Speedywhat", "Speedy warn", "One of your uploads has been speedy-deleted", umsg.umFlagUM + umsg.umFlagReqMqNs],
            ["No advertising", "Dont advertise", "Please do not advertise on Commons. We have the goal of an educational image collection", 0],
            ["Sign", "Sign your postings", "Please sign your postings with four tildes (~~~~) at the end of your comments", 0],
            ["Blocked", "Blocked", "Your account has been blocked", umsg.umFlagP2 + umsg.umFlagP3],
            ["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],
            ["Geocoding", "Geocoding", "Geo-Coding: Maybe you could consider adding coordinates to some of your images", umsg.umFlagUM],
            ["Sourcefield", "Sourcefield", "Please properly use the source field", umsg.umFlagUM + umsg.umFlagReqMqNs + umsg.umFlagP2],
            ["Dateformat", "Dateformat", "Please use the correct date format", umsg.umFlagUM + umsg.umFlagReqMqNs + umsg.umFlagP2],
            ["Do not upload thumbnails", "No thumbs", "Please always upload the biggest resolution image, you can obtain. Thanks", umsg.umFlagUM + umsg.umFlagReqMqNs],
            ["Flickrvionote", "Flickrvionote", "Please do not upload questionable Flickr-Files to Commons", umsg.umFlagUM + umsg.umFlagReqMqNs],
            ["Speedynote", "Speedynote", "Some of your contributed contents will be possibly speedy-deleted", umsg.umFlagReqMqNs + umsg.umFlagP2],
            ["Talkback", "Talkback", "Talkback would be appreciated", umsg.umFlagUQ + umsg.umFlagP2],
            ["You've got mail", "E-Mailed", "I sent an e-Mail to you using the e-Mail-this-user feature", umsg.umFlagUM],
            ["Commons is not for articles", "About articles", "Wikimedia Commons is for media files. Wikipedia is for articles", umsg.umFlagUM],
            ["Nopenis", "Nude photos", "Please consider reading [[COM:Project scope]] and [[COM:Nudity]]", umsg.umFlagUM + umsg.umFlagReqMqNs],
            ["Please use TeX", "Use TeX, please", "TeX markup have a few advantages, I'll tell you some of them", umsg.umFlagUM],
            ["Wrong license note", "Wrong license", "Please be careful to select the correct license", umsg.umFlagUM + umsg.umFlagReqMqNs, "Please pay attention to copyright"],
            ["How to transfer", "Please use CommonsHelper", "You've made an error when transferring. Please consider using CommonsHelper", umsg.umFlagUM + umsg.umFlagReqMqNs, "Asking user to consider using CommonsHelper"],
            ["Unfreeflickrnote", "Unfree flickr note", "Please do not upload unfree files from Flickr", umsg.umFlagUM + umsg.umFlagReqMqNs],
            ["Dont upload Wikipedia thumbnails", "No Wikipedia thumbs", "Please always upload the biggest resolution image, you can obtain (Wikipedia specific). Thanks", umsg.umFlagUM + umsg.umFlagReqMqNs, "Please always upload the biggest resolution image, you can obtain. Thanks"],
            ["No selfies", "No selfies", "Inform the user that pictures of self and family/friends are out of often scope", umsg.umFlagUM, "Please do not create [[COM:PS|out of scope media and pages.]]"],
            ["Dont editwar", "Don't edit war", "Ask the user to refrain from edit warring", 0],
        ];
        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>