|
| 1 | +"use strict"; |
| 2 | +$(() => { |
| 3 | + try { |
| 4 | + const { wgArticleId, wgPageName, wgUserName, wgUserGroups, wgNamespaceNumber, wgNamespaceIds } = mw.config.get([ |
| 5 | + "wgArticleId", |
| 6 | + "wgPageName", |
| 7 | + "wgUserName", |
| 8 | + "wgUserGroups", |
| 9 | + "wgNamespaceNumber", |
| 10 | + "wgNamespaceIds", |
| 11 | + ]); |
| 12 | + |
| 13 | + if ( |
| 14 | + wgArticleId === 0 |
| 15 | + || wgNamespaceNumber < 0 |
| 16 | + || wgPageName.startsWith("萌娘百科_talk:讨论版") |
| 17 | + || $(".will2Be2Deleted")[0] |
| 18 | + || (wgUserGroups.includes("patroller") || wgUserGroups.includes("sysop")) && !(new URL(location.href).searchParams.get("AnnTools-debug") || "").split("|").includes("requestDeletion") |
| 19 | + ) { |
| 20 | + return; |
| 21 | + } |
| 22 | + |
| 23 | + const isZh = /^m?zh\.moegirl\.org\.cn$/.test(location.hostname); |
| 24 | + const api = new mw.Api(), zhAPI = isZh ? api : new mw.ForeignApi("https://mzh.moegirl.org.cn/api.php"); |
| 25 | + |
| 26 | + const BOARD_PAGE = "萌娘百科_talk:讨论版/操作申请"; |
| 27 | + const boardUrl = `${isZh ? "" : "https://mzh.moegirl.org.cn"}${mw.util.getUrl(BOARD_PAGE)}`; |
| 28 | + const linkedPageName = isZh ? wgPageName : `cm:${wgPageName}`; |
| 29 | + |
| 30 | + const $body = $("body"); |
| 31 | + $("#mw-notification-area").appendTo($body); |
| 32 | + |
| 33 | + const getRequestTitleSuffix = (userName, now) => ` - ${userName} - ${now.getFullYear()}.${libPrefixNumber(now.getMonth() + 1)}.${libPrefixNumber(now.getDate())}`; |
| 34 | + |
| 35 | + const buildSectionTitle = () => `请求删除页面:[[${linkedPageName}]]${getRequestTitleSuffix(wgUserName, new Date())}`; |
| 36 | + |
| 37 | + /** |
| 38 | + * 新讨论串的正文 |
| 39 | + * |
| 40 | + * @param {string} reason 理由 |
| 41 | + * @param {string} detail 详情;为空时整行不输出 |
| 42 | + * @returns {string} 正文 wikitext |
| 43 | + */ |
| 44 | + const buildWikitext = (reason, detail) => { |
| 45 | + const lines = [ |
| 46 | + `* '''${wgULS("页面", "頁面")}''':[[${linkedPageName}]]`, |
| 47 | + `* '''${wgULS("理由", "緣由")}''':${reason}`, |
| 48 | + ]; |
| 49 | + if (detail) { |
| 50 | + lines.push(`* '''${wgULS("详情", "詳情")}''':${detail}`); |
| 51 | + } |
| 52 | + lines.push("~~~~"); |
| 53 | + return lines.join("\n"); |
| 54 | + }; |
| 55 | + |
| 56 | + const fetchNewSectionAnchor = async (revid) => { |
| 57 | + if (!revid) { |
| 58 | + return ""; |
| 59 | + } |
| 60 | + try { |
| 61 | + const res = await zhAPI.get({ |
| 62 | + action: "parse", |
| 63 | + oldid: revid, |
| 64 | + prop: "sections", |
| 65 | + formatversion: 2, |
| 66 | + }); |
| 67 | + return res?.parse?.sections?.at(-1)?.anchor ?? ""; |
| 68 | + } catch (e) { |
| 69 | + console.warn("[RequestDeletion] 读取新讨论串锚点失败", e); |
| 70 | + return ""; |
| 71 | + } |
| 72 | + }; |
| 73 | + |
| 74 | + class RDWindow extends OO.ui.ProcessDialog { |
| 75 | + static static = { |
| 76 | + ...super.static, |
| 77 | + tagName: "div", |
| 78 | + name: "lr-rd", |
| 79 | + title: wgULS("提删", "提刪"), |
| 80 | + actions: [ |
| 81 | + { |
| 82 | + action: "cancel", |
| 83 | + label: "取消", |
| 84 | + flags: ["safe", "close", "destructive"], |
| 85 | + }, |
| 86 | + { |
| 87 | + action: "submit", |
| 88 | + label: wgULS("确认", "確認"), |
| 89 | + flags: ["primary", "progressive"], |
| 90 | + }, |
| 91 | + ], |
| 92 | + }; |
| 93 | + constructor(config) { |
| 94 | + // Parent constructor |
| 95 | + super(config); |
| 96 | + |
| 97 | + this.storage = config.data.storage; |
| 98 | + } |
| 99 | + initialize() { |
| 100 | + // Parent method |
| 101 | + super.initialize(); |
| 102 | + |
| 103 | + this.panelLayout = new OO.ui.PanelLayout({ |
| 104 | + scrollable: false, |
| 105 | + expanded: false, |
| 106 | + padded: true, |
| 107 | + }); |
| 108 | + this.reasonsDropdown = new OO.ui.DropdownInputWidget({ |
| 109 | + options: [{ |
| 110 | + data: "", |
| 111 | + label: wgULS("其他(请自行说明理由)", "其他(請自行說明緣由)"), |
| 112 | + }, { |
| 113 | + data: "", |
| 114 | + disabled: true, |
| 115 | + label: wgULS("加载中……", "載入中……"), |
| 116 | + }], |
| 117 | + }); |
| 118 | + this.detailsText = new OO.ui.TextInputWidget(); |
| 119 | + this.enterCheckbox = new OO.ui.CheckboxInputWidget({ |
| 120 | + selected: this.storage.getItem("enterToSubmit"), |
| 121 | + }); |
| 122 | + |
| 123 | + this.reasonsDropdownMenu = this.reasonsDropdown.dropdownWidget.getMenu(); |
| 124 | + |
| 125 | + const reasonsField = new OO.ui.FieldLayout(this.reasonsDropdown, { |
| 126 | + label: wgULS("提删理由", "提刪緣由"), |
| 127 | + align: "top", |
| 128 | + }); |
| 129 | + const detailsField = new OO.ui.FieldLayout(this.detailsText, { |
| 130 | + label: wgULS("详情", "詳情"), |
| 131 | + align: "top", |
| 132 | + }); |
| 133 | + const enterField = new OO.ui.FieldLayout(this.enterCheckbox, { |
| 134 | + label: wgULS("理由详情按回车提交(浏览器级设置)", "緣由詳情按回車鍵提交(瀏覽器級設置)"), |
| 135 | + align: "inline", |
| 136 | + }); |
| 137 | + |
| 138 | + this.panelLayout.$element.append( |
| 139 | + reasonsField.$element, |
| 140 | + detailsField.$element, |
| 141 | + enterField.$element, |
| 142 | + ); |
| 143 | + |
| 144 | + this.reasonsDropdownMenu.connect(this, { toggle: "dropdownToggle" }); |
| 145 | + this.detailsText.connect(this, { enter: "onEnter" }); |
| 146 | + this.enterCheckbox.connect(this, { change: "setStorage" }); |
| 147 | + |
| 148 | + this.$body.append(this.panelLayout.$element); |
| 149 | + } |
| 150 | + updateReasons(reasons) { |
| 151 | + this.reasonsDropdown.setOptions([{ |
| 152 | + data: "", |
| 153 | + label: wgULS("其他(请自行说明理由)", "其他(請自行說明緣由)"), |
| 154 | + }, ...reasons]); |
| 155 | + } |
| 156 | + setStorage(selected) { |
| 157 | + this.storage.setItem("enterToSubmit", selected); |
| 158 | + } |
| 159 | + onEnter() { |
| 160 | + if (this.enterCheckbox.isSelected()) { |
| 161 | + this.executeAction("submit"); |
| 162 | + } |
| 163 | + } |
| 164 | + dropdownToggle(visible) { |
| 165 | + if (visible) { |
| 166 | + // Manually resize |
| 167 | + const newHeight = parseFloat(this.$frame.css("height")) + 300; |
| 168 | + this.withoutSizeTransitions(() => { |
| 169 | + this.$frame.css("height", newHeight); |
| 170 | + this.reasonsDropdownMenu.clip(); |
| 171 | + $(this.$frame).animate({ |
| 172 | + height: newHeight, |
| 173 | + }, 700); |
| 174 | + }); |
| 175 | + } else { |
| 176 | + this.updateSize(); |
| 177 | + } |
| 178 | + } |
| 179 | + getBodyHeight() { |
| 180 | + return this.panelLayout.$element.outerHeight(true); |
| 181 | + } |
| 182 | + getReadyProcess(data) { |
| 183 | + return super.getReadyProcess(data).next(() => { |
| 184 | + this.reasonsDropdown.focus(); |
| 185 | + }, this); |
| 186 | + } |
| 187 | + getActionProcess(action) { |
| 188 | + if (action === "cancel") { |
| 189 | + return new OO.ui.Process(() => { |
| 190 | + this.close({ action }); |
| 191 | + }, this); |
| 192 | + } else if (action === "submit") { |
| 193 | + return new OO.ui.Process($.when((async () => { |
| 194 | + const reason = this.reasonsDropdown.getValue(); |
| 195 | + const detail = this.detailsText.getValue(); |
| 196 | + if (!reason && !detail) { |
| 197 | + throw new OO.ui.Error(wgULS("请填写理由或详情", "請填寫緣由或詳情")); |
| 198 | + } |
| 199 | + try { |
| 200 | + const anchor = await this.postRequest(buildWikitext(reason || detail, reason ? detail : "")); |
| 201 | + this.close({ action }); |
| 202 | + mw.notify(wgULS("提删请求已发出,3 秒后跳转到讨论串", "提刪請求已發出,3 秒後跳轉到討論串"), { |
| 203 | + title: wgULS("提删成功", "提刪成功"), |
| 204 | + type: "success", |
| 205 | + tag: "lr-rd", |
| 206 | + }); |
| 207 | + setTimeout(() => { |
| 208 | + location.assign(anchor === "" ? boardUrl : `${boardUrl}#${encodeURIComponent(anchor)}`); |
| 209 | + }, 3000); |
| 210 | + } catch (e) { |
| 211 | + console.error("[RequestDeletion] Error:", e); |
| 212 | + throw new OO.ui.Error(e); |
| 213 | + } |
| 214 | + })()).promise(), this); |
| 215 | + } |
| 216 | + // Fallback to parent handler |
| 217 | + return super.getActionProcess(action); |
| 218 | + } |
| 219 | + |
| 220 | + /** |
| 221 | + * 往讨论版发一个新话题,并返回该讨论串的锚点。 |
| 222 | + * |
| 223 | + * @param {string} wikitext 正文 wikitext |
| 224 | + * @returns {Promise<string>} 讨论串锚点(不含 `#`);取不到时为空串 |
| 225 | + */ |
| 226 | + postRequest = async (wikitext) => { |
| 227 | + const res = await zhAPI.postWithToken("csrf", { |
| 228 | + action: "discussiontoolsedit", |
| 229 | + paction: "addtopic", |
| 230 | + page: BOARD_PAGE, |
| 231 | + sectiontitle: buildSectionTitle(), |
| 232 | + wikitext, |
| 233 | + assertuser: wgUserName, |
| 234 | + nocontent: "", |
| 235 | + watchlist: "nochange", |
| 236 | + tags: "Automation tool", |
| 237 | + }); |
| 238 | + if (res.error) { |
| 239 | + throw res.error.code; |
| 240 | + } |
| 241 | + return fetchNewSectionAnchor(res?.discussiontoolsedit?.newrevid); |
| 242 | + }; |
| 243 | + } |
| 244 | + |
| 245 | + const storage = new LocalObjectStorage("AnnTools-requestDeletion"); |
| 246 | + |
| 247 | + const windowManager = new OO.ui.WindowManager(); |
| 248 | + $body.append(windowManager.$element); |
| 249 | + const rdDialog = new RDWindow({ |
| 250 | + size: "medium", |
| 251 | + data: { storage }, |
| 252 | + }); |
| 253 | + windowManager.addWindows([rdDialog]); |
| 254 | + |
| 255 | + let loadReason = false; |
| 256 | + |
| 257 | + $(mw.util.addPortletLink("p-cactions", "#", wgULS("提删", "提刪"), "ca-lr-rd", wgULS("向讨论版提出删除请求", "向討論版提出刪除請求"))).on("click", async (e) => { |
| 258 | + e.preventDefault(); |
| 259 | + windowManager.openWindow(rdDialog); |
| 260 | + $body.css("overflow", "auto"); |
| 261 | + if (loadReason === false) { |
| 262 | + loadReason = true; |
| 263 | + const { parse: { text: { "*": html } } } = await api.post({ |
| 264 | + action: "parse", |
| 265 | + assertuser: wgUserName, |
| 266 | + page: wgNamespaceNumber === wgNamespaceIds.file ? "MediaWiki:Filedelete-reason-dropdown" : "MediaWiki:Deletereason-dropdown", |
| 267 | + prop: "text", |
| 268 | + }); |
| 269 | + const container = $("<div>"); |
| 270 | + container.html(html); |
| 271 | + const result = []; |
| 272 | + const getReason = (ele) => { |
| 273 | + result.push({ |
| 274 | + data: ele.innerText.trim(), |
| 275 | + label: ele.innerText.trim(), |
| 276 | + }); |
| 277 | + }; |
| 278 | + container.children(".mw-parser-output").children("ul").children("li").each((_, ele) => { |
| 279 | + const $ele = $(ele); |
| 280 | + if ($ele.children("ul").length > 0) { |
| 281 | + result.push({ |
| 282 | + optgroup: $ele.clone().find("*").remove().end().text().trim(), |
| 283 | + }); |
| 284 | + $ele.children("ul").children("li").each((_, e) => getReason(e)); |
| 285 | + } else { |
| 286 | + getReason(ele); |
| 287 | + } |
| 288 | + }); |
| 289 | + rdDialog.updateReasons(result); |
| 290 | + } |
| 291 | + }); |
| 292 | + } catch (e) { |
| 293 | + const parseError = (errLike, _space) => { |
| 294 | + let space = _space; |
| 295 | + if (_space === void 0) { |
| 296 | + space = 4; |
| 297 | + } |
| 298 | + return JSON.stringify(errLike, (_, v) => { |
| 299 | + if (v instanceof Error) { |
| 300 | + const stack = []; |
| 301 | + if (v.stack) { |
| 302 | + Reflect.apply(stack.push, stack, v.stack.split("\n").map((n) => n.trim()).filter((n) => { |
| 303 | + let _a; |
| 304 | + return ((_a = n === null || n === void 0 ? void 0 : n.length) !== null && _a !== void 0 ? _a : -1) > 0; |
| 305 | + })); |
| 306 | + } |
| 307 | + const keys = Object.keys(v).filter((key) => !Reflect.has(Error.prototype, key)); |
| 308 | + if (keys.length) { |
| 309 | + stack.push(JSON.stringify(Object.fromEntries(keys.map((key) => [key, v[key]])), null, space)); |
| 310 | + } |
| 311 | + return stack.join("\n").trim() || ""; |
| 312 | + } |
| 313 | + return v; |
| 314 | + }, space).replace(/^"(.*)"$/, "$1"); |
| 315 | + }; |
| 316 | + oouiDialog.alert(`错误信息:<br>${oouiDialog.sanitize(parseError(e))}`, { |
| 317 | + title: "提删工具发生错误", |
| 318 | + }); |
| 319 | + console.error("[RequestDeletion] Setup error:", e); |
| 320 | + } |
| 321 | +}); |
0 commit comments