|
|
(未显示同一用户的6个中间版本) |
第109行: |
第109行: |
| }); | | }); |
|
| |
|
| /////// Toggle Content
| |
| function cssSanitizer(cssStr) {
| |
| let result = "";
| |
| if (typeof(cssStr) === "string") {
| |
| result = cssStr;
| |
| }
| |
| result = result.replace(RegExp(decodeURIComponent("%3C!--"), "g"), "");
| |
| result = result.replace(RegExp(decodeURIComponent("-%3E"), "g"), "");
| |
| result = result.replace(/\/\//g, "");
| |
| result = result.replace(/url/g, "");
| |
| result = result.replace(/&/g, "");
| |
| result = result.replace(/pointer-event/g, ";");
| |
| result = result.replace(
| |
| /display\s*:\s*none\s*!\s*important/g,
| |
| "display: none"
| |
| );
| |
| return result;
| |
| }
| |
| function 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;
| |
| }
| |
| function 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;
| |
| }
| |
| function getEntries(obj) {
| |
| return Object.entries(obj).filter(
| |
| ([k, n]) => !/@(?:on|off|input)$/.test(k) && !/['"<>\\/]/.test(n)
| |
| );
| |
| }
| |
| function 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);
| |
| });
| |
| ///////结束//////// | | ///////结束//////// |