MediaWiki:ToggleContent.js:修订间差异
外观
Willson-v-(留言 | 贡献) 建立内容为“// jshint maxerr:1000 // WARNING: This page will be included in Common.js, and could affect THE WHOLE // SITE if poorly handled. CONTACT willson-v- BEFORE EDITIN…”的新页面 |
Willson-v-(留言 | 贡献) 小 已保护“MediaWiki:ToggleContent.js”([编辑=仅允许管理员](无限期)[移动=仅允许管理员](无限期)) |
(没有差异)
|
2022年5月4日 (三) 12:22的最新版本
// jshint maxerr:1000 // WARNING: This page will be included in Common.js, and could affect THE WHOLE // SITE if poorly handled. CONTACT willson-v- BEFORE EDITING! // 警告:本代码将会被Common.js引用, // 故而可能影响到全站页面。请在作出修改前联系willson-v-。 // Make all local var inside a function to prevent interference 'use strict' $(function () { const cssSanitizer = (cssStr) => (typeof cssStr === "string" ? cssStr : "").replace(RegExp(decodeURIComponent("%3C!--"), "g"), "").replace(RegExp(decodeURIComponent("-%3E"), "g"), "").replace(/\/\//g, "").replace(/url/g, "").replace(/&/g, "").replace(/pointer-event/g, ";").replace(/display\s*:\s*none\s*!\s*important/g, "display: none"); const uuidv4 = () => { let result = ""; while (result === "" || $(`#${result}, [name="${result}"]`).length > 0) { result = ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) ); } return result; }; const datasetParser = (ele) => { const result = { config: {}, data: {}, }; Object.entries(ele.dataset).filter(([n, m]) => n.startsWith("key-") && !/['"<>\\/]/.test(m)).forEach(([__key, _key]) => { const value = ele.dataset[`value-${__key.substring(4)}`]; if (_key.length > 0 && value.length > 0) { const isConfig = _key.startsWith("@"); const key = isConfig ? _key.substring(1) : _key; const base = result[isConfig ? "config" : "data"]; base[key] = value; if (/@o(?:n|ff)$/.test(_key)) { const id = _key.replace(/@o(?:n|ff)$/, ""); if (!(id in base)) { base[id] = value; } } else if (_key.endsWith("@input")) { base[_key] = value; const id = _key.replace(/@input$/, ""); if (!(id in base)) { base[id] = ""; } } else { if (!(`${key}@on` in base)) { base[`${key}@on`] = value; } if (!(`${key}@off` in base)) { base[`${key}@off`] = value; } if (!(`${key}@input` in base)) { base[`${key}@input`] = ""; } } } }); return result; }; const getEntries = (obj) => Object.entries(obj).filter(([k, n]) => !/@(?:on|off|input)$/.test(k) && !/['"<>\\/]/.test(n)); const triggerChangeHandler = ($ele) => { $ele.each((_, ele) => { $(ele).triggerHandler("change", [true]); }); }; const textToggleDisplayStyle = {}; $(".textToggleDisplayStyle").each((_, ele) => { const dataset = datasetParser(ele).data; getEntries(dataset).forEach(([id, style]) => { textToggleDisplayStyle[id] = style; }); }); $(".textToggleDisplay").each((_, ele) => { if (ele.dataset.id in textToggleDisplayStyle) { $(ele).attr("style", cssSanitizer(textToggleDisplayStyle[ele.dataset.id])); } }).addClass("hidden"); $(".textToggleDisplayButtons").each((_, ele) => { const self = $(ele); const parsedDataset = datasetParser(ele); const ifRadio = "radio" in parsedDataset.config && parsedDataset.config.radio.length > 0; const ifForceNoCancel = "forceNoCancel" in parsedDataset.config && parsedDataset.config.forceNoCancel.length > 0; const ifReverse = "reverse" in parsedDataset.config && parsedDataset.config.reverse.length > 0; const name = uuidv4(); let hasDefault = false; self.children("span[data-key]").each((_, e) => { if (/['"<>\\/]/.test(e.dataset.key)) { e.remove(); return; } const $e = $(e); const input = $("<input/>"); input.attr({ "data-id": e.dataset.key, type: ifRadio ? "radio" : "checkbox", }).addClass(ifReverse ? "textToggleDisplayButtonInputReverse" : "textToggleDisplayButtonInput"); if (ifRadio) { input.attr("name", name); } const label = $("<label/>"); $e.children().appendTo(label); label.prepend(input).appendTo(e).attr("data-id", e.dataset.key).addClass("textToggleDisplayButtonLabel"); $e.find("*").each((_, e) => { e.addEventListener("click", (evt) => { evt.stopImmediatePropagation(); evt.stopPropagation(); if (!$(evt.target).is(input)) { evt.preventDefault(); if (ifRadio ? !input.prop("checked") : true) { input.prop("checked", !input.prop("checked")).trigger("change"); } } }, { capture: true, }); }); if (e.dataset.key === parsedDataset.config.default) { input.prop("checked", true); hasDefault = true; } }); if (ifRadio && (!hasDefault || !ifForceNoCancel)) { const input = $("<input/>"); const inputId = uuidv4(); input.attr({ "data-id": "", "data-radio-cancel": "true", id: inputId, name, type: "radio", }).addClass(ifReverse ? "textToggleDisplayButtonInputReverse" : "textToggleDisplayButtonInput").css({ "margin-left": "0", "margin-right": "0", width: "0", }); const label = $("<label/>"); const span = $("<span/>"); span.text("取消选择"); label.attr({ "data-id": "", "for": inputId, }).addClass("textToggleDisplayButtonLabel cancelButton"); self.append(label.append(input).append(span)); if (!hasDefault) { input.prop("checked", true); } } }); $(".textToggleDisplayButtonsStyle").each((_, ele) => { const parsedDataset = datasetParser(ele); $(".textToggleDisplayButtonLabel.cancelButton").attr({ "data-style-off": parsedDataset.config["cancel@off"] || "", "data-style-on": parsedDataset.config["cancel@on"] || "", }); $(".textToggleDisplayButtonLabel.cancelButton > input").attr("style", cssSanitizer(parsedDataset.config["cancel@input"])); getEntries(parsedDataset.data).forEach(([id]) => { $(`.textToggleDisplayButtonLabel[data-id="${id}"]`).attr({ "data-style-off": parsedDataset.data[`${id}@off`] || "", "data-style-on": parsedDataset.data[`${id}@on`] || "", }).children("input").attr("style", cssSanitizer(parsedDataset.data[`${id}@input`])); }); }); triggerChangeHandler($(".textToggleDisplayButtonInput").on("change", ({ target, }, fromTriggerHandler = false) => { const { dataset: { id, }, checked, } = target; const textToggleDisplayTarget = $(`.textToggleDisplay[data-id="${id}"]`); const { config: { toggleClass } } = datasetParser($(target).closest(".textToggleDisplayButtons")[0]); const toggleClassName = typeof toggleClass === "string" && toggleClass.length > 0 ? toggleClass : "hidden"; if (toggleClassName !== "hidden") { textToggleDisplayTarget.removeClass("hidden"); } textToggleDisplayTarget[checked ? "removeClass" : "addClass"](toggleClassName)[checked ? "addClass" : "removeClass"]("textToggleDisplay-on"); textToggleDisplayTarget.prev(".textToggleDisplay")[checked ? "addClass" : "removeClass"]("textToggleDisplay-before-on"); textToggleDisplayTarget.next(".textToggleDisplay")[checked ? "addClass" : "removeClass"]("textToggleDisplay-after-on"); const self = $(target); const label = self.closest(".textToggleDisplayButtonLabel"); const span = label.children(".on"); label[checked ? "addClass" : "removeClass"]("on").attr("style", cssSanitizer(label.attr(checked ? "data-style-on" : "data-style-off"))); if (checked) { span.attr("style", cssSanitizer(textToggleDisplayStyle[id])); } else { span.removeAttr("style"); } const labelContainer = label.closest("span[data-order][data-key]"); labelContainer.prev("span[data-order][data-key]").children(".textToggleDisplayButtonLabel").last()[checked ? "addClass" : "removeClass"]("before-on"); labelContainer.next("span[data-order][data-key]").children(".textToggleDisplayButtonLabel").first()[checked ? "addClass" : "removeClass"]("after-on"); if (!fromTriggerHandler) { triggerChangeHandler(self.closest(".textToggleDisplayButtons").find("input").not(target)); } })); triggerChangeHandler($(".textToggleDisplayButtonInputReverse").on("change", ({ target, }, fromTriggerHandler = false) => { const { dataset: { id, radioCancel, }, checked, } = target; const isRadioCancel = (radioCancel || "").length > 0; const self = $(target); const label = self.closest(".textToggleDisplayButtonLabel"); if (isRadioCancel) { label[checked ? "addClass" : "removeClass"]("on").attr("style", cssSanitizer(label.attr(checked ? "data-style-on" : "data-style-off"))); } else { const textToggleDisplayTarget = $(`.textToggleDisplay[data-id="${id}"]`); const { config: { toggleClass } } = datasetParser($(target).closest(".textToggleDisplayButtons")[0]); const toggleClassName = typeof toggleClass === "string" && toggleClass.length > 0 ? toggleClass : "hidden"; if (toggleClassName !== "hidden") { textToggleDisplayTarget.removeClass("hidden"); } textToggleDisplayTarget[checked ? "addClass" : "removeClass"](toggleClassName)[checked ? "removeClass" : "addClass"]("textToggleDisplay-on"); textToggleDisplayTarget.prev(".textToggleDisplay")[checked ? "removeClass" : "addClass"]("textToggleDisplay-before-on"); textToggleDisplayTarget.next(".textToggleDisplay")[checked ? "removeClass" : "addClass"]("textToggleDisplay-after-on"); const span = label.children("span"); label[checked ? "removeClass" : "addClass"]("on").attr("style", cssSanitizer(label.attr(checked ? "data-style-off" : "data-style-on"))); if (checked) { span.removeAttr(".on"); } else { span.attr("style", cssSanitizer(textToggleDisplayStyle[id])); } } const labelContainer = label.closest("span[data-order][data-key]"); labelContainer.prev("span[data-order][data-key]").children(".textToggleDisplayButtonLabel").last()[checked ? "addClass" : "removeClass"]("before-on"); labelContainer.next("span[data-order][data-key]").children(".textToggleDisplayButtonLabel").first()[checked ? "addClass" : "removeClass"]("after-on"); if (!fromTriggerHandler) { triggerChangeHandler(self.closest(".textToggleDisplayButtons").find("input").not(target)); } })); $(window).on("beforeunload", () => { $(".textToggleDisplayButtons input[type=checkbox]").prop("checked", false); }); }());