Greasy Fork

Greasy Fork is available in English.

ModernMonkeyConfig Edition Enhanced mod2

Enhanced Configuration Dialog Builder with column layout, custom styling, additional input types, and scrollable labels

当前为 2025-09-06 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/548573/1655870/ModernMonkeyConfig%20Edition%20Enhanced%20mod2.js

// ==UserScript==
// @name            ModernMonkeyConfig Edition Enhanced mod2
// @noframes
// @version         0.3.2
// @namespace       http://odyniec.net/
// @include        *
// @description     Enhanced Configuration Dialog Builder with column layout, custom styling, additional input types, and scrollable labels
// @require         https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js#sha384-qSFej5dZNviyoPgYJ5+Xk4bEbX8AYddxAHPuzs1aSgRiXxJ3qmyWNaPsRkpv/+x5
// ==/UserScript==
/* eslint-disable no-undef */
/*
 * ModernMonkeyConfig Edition Enhanced mod2
 * Based on version 0.1.4 by Michal Wojciechowski (odyniec.net)
 * v0.1.4 - January 2020 - David Hosier (https://github.com/david-hosier/MonkeyConfig)
 * Enhanced by Scorpion - March 2025
 * Additions: Column layout, font size/color customization, new input types (textarea, range, radio, file, button, group)
 * Modified: Checkbox, number, and text inputs aligned inline with labels - March 2025
 * Fixed: Improved Shadow DOM and Optimized Iframe for consistent styling across sites - March 2025
 * Enhanced: Scrollable labels, customizable checkbox/number sizes, new column options (left&top, right&top, left&bottom, right&bottom) - April 2025
 * Security: Added Trusted Types support, DOMPurify sanitization, and robust error handling - May 2025
 */

function MonkeyConfig(data) {
  let cfg = this,
    params = data.parameters || data.params,
    values = {},
    storageKey,
    displayed,
    openLayer,
    shadowRoot,
    container,
    iframeFallback;
  function log(message, level = "info") {
    try {
      console[level](`[MonkeyConfig v0.3.2] ${message}`);
    } catch (e) {
      console.error(`[MonkeyConfig v0.3.2] Logging failed: ${e.message}`);
    }
  }
  let trustedPolicy;
  try {
    trustedPolicy = window.trustedTypes?.createPolicy("monkeyConfigPolicy", {
      createHTML: (input) => {
        if (typeof DOMPurify === "undefined") {
          log(
            "DOMPurify not available, falling back to basic sanitization",
            "warn"
          );
          return input
            .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
            .replace(/on\w+\s*=\s*".*?"/gi, "");
        }
        return DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: true });
      },
    }) || { createHTML: (input) => input };
  } catch (e) {
    log(`Failed to create Trusted Types policy: ${e.message}`, "error");
    trustedPolicy = {
      createHTML: (input) =>
        input
          .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
          .replace(/on\w+\s*=\s*".*?"/gi, ""),
    };
  }
  function createTrustedHTML(htmlString) {
    try {
      return trustedPolicy.createHTML(htmlString);
    } catch (e) {
      log(`Failed to create TrustedHTML: ${e.message}`, "error");
      return "";
    }
  }
  function init() {
    try {
      data.buttons = data.buttons || [
        "save",
        "reset",
        "close",
        "reload",
        "homepage",
      ];
      storageKey =
        "_MonkeyConfig_" +
        (data.title || "Configuration").replace(/[^a-zA-Z0-9]/g, "_") +
        "_cfg";
      let storedValues;
      try {
        storedValues = GM_getValue(storageKey)
          ? JSON.parse(GM_getValue(storageKey))
          : {};
      } catch (e) {
        log(`Failed to parse stored values: ${e.message}`, "error");
        storedValues = {};
      }
      cfg.shadowWidth = data.shadowWidth || storedValues.shadowWidth || "600px";
      cfg.shadowHeight =
        data.shadowHeight || storedValues.shadowHeight || "400px";
      cfg.iframeWidth = data.iframeWidth || storedValues.iframeWidth || "600px";
      cfg.iframeHeight =
        data.iframeHeight || storedValues.iframeHeight || "400px";
      cfg.shadowFontSize =
        data.shadowFontSize || storedValues.shadowFontSize || "14px";
      cfg.shadowFontColor =
        data.shadowFontColor || storedValues.shadowFontColor || "#000000";
      cfg.iframeFontSize =
        data.iframeFontSize || storedValues.iframeFontSize || "14px";
      cfg.iframeFontColor =
        data.iframeFontColor || storedValues.iframeFontColor || "#000000";
      cfg.title =
        data.title ||
        (typeof GM_getMetadata === "function"
          ? GM_getMetadata("name") + " Configuration"
          : "Configuration");
      for (let key in params) {
        const param = params[key];
        values[key] = storedValues[key] ?? param.default ?? "";
      }
      if (data.menuCommand) {
        try {
          GM_registerMenuCommand(
            data.menuCommand === true ? cfg.title : data.menuCommand,
            () => cfg.open()
          );
        } catch (e) {
          log(`Failed to register menu command: ${e.message}`, "error");
        }
      }
      cfg.open = open;
      cfg.close = close;
      cfg.get = (name) => values[name];
      cfg.set = (name, value) => {
        try {
          values[name] = value;
          update();
        } catch (e) {
          log(`Failed to set value for ${name}: ${e.message}`, "error");
        }
      };
    } catch (e) {
      log(`Initialization failed: ${e.message}`, "error");
    }
  }
  function setDefaults() {
    try {
      for (let key in params) {
        if (params[key].default !== undefined) {
          values[key] = params[key].default;
        }
      }
      update();
    } catch (e) {
      log(`Failed to set defaults: ${e.message}`, "error");
    }
  }
  function render() {
    try {
      let html = `<div class="__MonkeyConfig_container">
        <div style="position: absolute; top: 5px; right: 5px;">
          <button type="button" id="__MonkeyConfig_button_close" style="background: none !important; border: none !important; padding: 5px !important; display: flex !important; align-items: center !important; justify-content: center !important;">
            ${
              MonkeyConfig.res.icons.close
            }
          </button>
        </div>
        <h1>${MonkeyConfig.esc(
          cfg.title
        )}</h1><div class="__MonkeyConfig_content"><div class="__MonkeyConfig_top">`;
      for (let key in params) {
        if (params[key].column === "top") {
          html += MonkeyConfig.formatters.tr(key, params[key]);
        }
      }
      html += `<div class="__MonkeyConfig_top_columns"><div class="__MonkeyConfig_left_top">`;
      for (let key in params) {
        if (params[key].column === "left&top") {
          html += MonkeyConfig.formatters.tr(key, params[key]);
        }
      }
      html += `</div><div class="__MonkeyConfig_right_top">`;
      for (let key in params) {
        if (params[key].column === "right&top") {
          html += MonkeyConfig.formatters.tr(key, params[key]);
        }
      }
      html += `</div></div>`;
      html += `</div><div class="__MonkeyConfig_columns"><div class="__MonkeyConfig_left_column">`;
      for (let key in params) {
        if (params[key].column === "left") {
          html += MonkeyConfig.formatters.tr(key, params[key]);
        }
      }
      html += `</div><div class="__MonkeyConfig_right_column">`;
      for (let key in params) {
        if (params[key].column === "right") {
          html += MonkeyConfig.formatters.tr(key, params[key]);
        }
      }
      html += `</div></div><table class="__MonkeyConfig_default">`;
      for (let key in params) {
        if (!params[key].column) {
          html += MonkeyConfig.formatters.tr(key, params[key]);
        }
      }
      html += `</table><div class="__MonkeyConfig_bottom">`;
      for (let key in params) {
        if (params[key].column === "bottom") {
          html += MonkeyConfig.formatters.tr(key, params[key]);
        }
      }
      html += `<div class="__MonkeyConfig_bottom_columns"><div class="__MonkeyConfig_left_bottom">`;
      for (let key in params) {
        if (params[key].column === "left&bottom") {
          html += MonkeyConfig.formatters.tr(key, params[key]);
        }
      }
      html += `</div><div class="__MonkeyConfig_right_bottom">`;
      for (let key in params) {
        if (params[key].column === "right&bottom") {
          html += MonkeyConfig.formatters.tr(key, params[key]);
        }
      }
      html += `</div></div>`;
      html += `</div></div><div class="__MonkeyConfig_buttons_container"><table><tr>`;
      data.buttons.forEach((btn) => {
        if (btn === "close") return;
        html += "<td>";
        if (btn === "save") {
          html += `<button type="button" id="__MonkeyConfig_button_save" style="display: flex !important; align-items: center !important; justify-content: center !important; padding: 4px 8px !important; border-radius: 0.5em !important; border: 1px solid #999 !important; background: #ccc linear-gradient(180deg, #ddd 0, #ccc 45%, #bbb 50%, #aaa 100%) !important; line-height: 1.2 !important;">${MonkeyConfig.res.icons.save} Save Without Reload</button>`;
        } else if (btn === "reset") {
          html += `<button type="button" id="__MonkeyConfig_button_reset" style="display: flex !important; align-items: center !important; justify-content: center !important; padding: 4px 8px !important; border-radius: 0.5em !important; border: 1px solid #999 !important; background: #ccc linear-gradient(180deg, #ddd 0, #ccc 45%, #bbb 50%, #aaa 100%) !important; line-height: 1.2 !important;">${MonkeyConfig.res.icons.reset} Reset</button>`;
        } else if (btn === "reload") {
          html += `<button type="button" id="__MonkeyConfig_button_reload" style="display: flex !important; align-items: center !important; justify-content: center !important; padding: 4px 8px !important; border-radius: 0.5em !important; border: 1px solid #999 !important; background: #ccc linear-gradient(180deg, #ddd 0, #ccc 45%, #bbb 50%, #aaa 100%) !important; line-height: 1.2 !important;">${MonkeyConfig.res.icons.reload} Save With Reload</button>`;
        } else if (btn === "homepage") {
          html += `<button type="button" id="__MonkeyConfig_button_homepage" style="display: flex !important; align-items: center !important; justify-content: center !important; padding: 4px 8px !important; border-radius: 0.5em !important; border: 1px solid #999 !important; background: #ccc linear-gradient(180deg, #ddd 0, #ccc 45%, #bbb 50%, #aaa 100%) !important; line-height: 1.2 !important;">${MonkeyConfig.res.icons.home} Homepage</button>`;
        }
        html += "</td>";
      });
      html += "</tr></table></div></div>";
      return createTrustedHTML(html);
    } catch (e) {
      log(`Failed to render HTML: ${e.message}`, "error");
      return createTrustedHTML(
        "<div>Error rendering configuration dialog</div>"
      );
    }
  }
  function update() {
    try {
      if (!displayed) return;
      const root =
        shadowRoot || (iframeFallback && iframeFallback.contentDocument);
      if (!root) {
        log("Root element not found for update", "error");
        return;
      }
      for (let key in params) {
        const elem = root.querySelector(`[name="${MonkeyConfig.esc(key)}"]`),
          param = params[key];
        if (!elem) {
          log(`Element for ${key} not found`, "warn");
          continue;
        }
        if (param.type === "checkbox") {
          elem.checked = !!values[key];
          elem.style.width = param.checkboxWidth || "11px";
          elem.style.height = param.checkboxHeight || "11px";
        } else if (param.type === "number") {
          elem.value = values[key] || param.default;
          elem.style.width = param.inputWidth || "50px";
          elem.style.height = param.inputHeight || "15px";
        } else if (param.type === "text") {
          elem.value = values[key] || param.default;
          elem.style.width = param.inputWidth || "100px";
          elem.style.height = param.inputHeight || "15px";
        } else if (param.type === "custom" && param.set) {
          try {
            param.set(
              values[key],
              root.querySelector(
                `#__MonkeyConfig_parent_${MonkeyConfig.esc(key)}`
              )
            );
          } catch (e) {
            log(`Failed to set custom param ${key}: ${e.message}`, "error");
          }
        } else if (
          ["text", "color", "textarea", "range"].includes(param.type)
        ) {
          elem.value = values[key] || param.default;
        } else if (param.type === "radio") {
          const radio = root.querySelector(
            `[name="${MonkeyConfig.esc(key)}"][value="${MonkeyConfig.esc(
              values[key]
            )}"]`
          );
          if (radio) radio.checked = true;
        } else if (param.type === "file") {
          elem.value = "";
        } else if (param.type === "select") {
          const currentValue = values[key];
          if (elem.type === "checkbox") {
            const checkboxes = root.querySelectorAll(
              `input[name="${MonkeyConfig.esc(key)}"]`
            );
            checkboxes.forEach((cb) => {
              cb.checked = currentValue.includes(cb.value);
            });
          } else if (elem.multiple) {
            const options = root.querySelectorAll(
              `select[name="${MonkeyConfig.esc(key)}"] option`
            );
            options.forEach((opt) => {
              opt.selected = currentValue.includes(opt.value);
            });
          } else {
            elem.value = currentValue;
          }
        }
        const fontSize = shadowRoot ? cfg.shadowFontSize : cfg.iframeFontSize;
        const defaultFontColor = shadowRoot
          ? cfg.shadowFontColor
          : cfg.iframeFontColor;
        const labelFontColor = param.fontColor || defaultFontColor;
        elem.style.fontSize = fontSize;
        elem.style.color = labelFontColor;
        if (param.type === "checkbox" || param.type === "textarea") {
          elem.style.backgroundColor = "inherit";
          elem.style.color = labelFontColor;
        }
        const label = root.querySelector(
          `label[for="__MonkeyConfig_field_${MonkeyConfig.esc(key)}"]`
        );
        if (label) {
          label.style.fontSize = fontSize;
          label.style.color = labelFontColor;
          label.style.cssText +=
            param.type === "textarea"
              ? "text-align:center;display:block;width:100%"
              : "text-align:left;display:inline-block;width:auto";
        }
      }
    } catch (e) {
      log(`Failed to update UI: ${e.message}`, "error");
    }
  }
  function saveClick() {
    try {
      const root =
        shadowRoot || (iframeFallback && iframeFallback.contentDocument);
      if (!root) {
        log("Root element not found for save", "error");
        return;
      }
      for (let key in params) {
        const elem = root.querySelector(`[name="${MonkeyConfig.esc(key)}"]`),
          param = params[key];
        if (!elem) {
          log(`Element for ${key} not found during save`, "warn");
          continue;
        }
        if (param.type === "checkbox") {
          values[key] = elem.checked;
        } else if (param.type === "custom" && param.get) {
          try {
            values[key] = param.get(
              root.querySelector(
                `#__MonkeyConfig_parent_${MonkeyConfig.esc(key)}`
              )
            );
          } catch (e) {
            log(`Failed to get custom param ${key}: ${e.message}`, "error");
          }
        } else if (
          ["number", "text", "color", "textarea", "range"].includes(param.type)
        ) {
          values[key] = elem.value;
        } else if (param.type === "radio") {
          values[key] =
            root.querySelector(`[name="${MonkeyConfig.esc(key)}"]:checked`)
              ?.value || "";
        } else if (param.type === "file") {
          values[key] = elem.dataset.value || values[key];
        } else if (param.type === "select") {
          if (elem.type === "checkbox") {
            values[key] = Array.from(
              root.querySelectorAll(
                `input[name="${MonkeyConfig.esc(key)}"]:checked`
              )
            ).map((input) => input.value);
          } else if (elem.multiple) {
            values[key] = Array.from(
              root.querySelectorAll(
                `select[name="${MonkeyConfig.esc(key)}"] option:selected`
              )
            ).map((opt) => opt.value);
          } else {
            values[key] = elem.value;
          }
        }
      }
      const allValues = {
        ...values,
        shadowWidth: cfg.shadowWidth,
        shadowHeight: cfg.shadowHeight,
        iframeWidth: cfg.iframeWidth,
        iframeHeight: cfg.iframeHeight,
        shadowFontSize: cfg.shadowFontSize,
        shadowFontColor: cfg.shadowFontColor,
        iframeFontSize: cfg.iframeFontSize,
        iframeFontColor: cfg.iframeFontColor,
      };

      try {
        GM_setValue(storageKey, JSON.stringify(allValues));
      } catch (e) {
        log(`Failed to save values: ${e.message}`, "error");
      }
      close();
      if (data.onSave) {
        try {
          data.onSave(values);
        } catch (e) {
          log(`onSave callback failed: ${e.message}`, "error");
        }
      }
    } catch (e) {
      log(`Save operation failed: ${e.message}`, "error");
    }
  }
  function open() {
    if (window.self !== window.top) {
      log("Cannot open dialog in iframe", "warn");
      return;
    }
    function openDone(root) {
      try {
        const saveBtn = root.querySelector("#__MonkeyConfig_button_save");
        if (saveBtn) saveBtn.addEventListener("click", saveClick, false);
        const resetBtn = root.querySelector("#__MonkeyConfig_button_reset");
        if (resetBtn) resetBtn.addEventListener("click", setDefaults, false);
        const closeBtn = root.querySelector("#__MonkeyConfig_button_close");
        if (closeBtn) closeBtn.addEventListener("click", close, false);
        const reloadBtn = root.querySelector("#__MonkeyConfig_button_reload");
        if (reloadBtn)
          reloadBtn.addEventListener(
            "click",
            () => {
              saveClick();
              location.reload();
            },
            false
          );
        const homepageBtn = root.querySelector(
          "#__MonkeyConfig_button_homepage"
        );
        if (homepageBtn)
          homepageBtn.addEventListener(
            "click",
            () =>
              window.open(
                "https://openai.com/",
                "_blank"
              ),
            false
          );
        displayed = true;
        const checkboxes = root.querySelectorAll('input[type="checkbox"]');
        checkboxes.forEach((cb) => {
          cb.style.width = cb.style.width || "11px";
          cb.style.height = cb.style.height || "11px";
        });
        const numbers = root.querySelectorAll('input[type="number"]');
        numbers.forEach((num) => {
          num.style.width = num.style.width || "40px";
          num.style.height = num.style.height || "20px";
        });
        update();
      } catch (e) {
        log(`Failed to initialize dialog: ${e.message}`, "error");
      }
    }
    const body = document.querySelector("body") || document.documentElement;
    if (!body) {
      log("Body not found, cannot open dialog", "error");
      return;
    }
    openLayer = document.createElement("div");
    openLayer.className = "__MonkeyConfig_layer";
    try {
      shadowRoot = openLayer.attachShadow({
        mode: "open",
      });
    } catch (e) {
      log(`Failed to attach Shadow DOM: ${e.message}`, "error");
      shadowRoot = null;
    }
    const shadowWidth = cfg.shadowWidth || "600px";
    const shadowHeight = cfg.shadowHeight || "300px";
    log(
      `Preparing Shadow DOM with title: ${MonkeyConfig.esc(
        cfg.title
      )}, dimensions - Width: ${shadowWidth}, Height: ${shadowHeight}`
    );
    const heightStyle = shadowHeight === "auto" ? "auto" : shadowHeight;
    if (shadowRoot) {
      try {
        shadowRoot.innerHTML = createTrustedHTML(`
            <style>
              :host { all: initial; display: block !important; font-family: Arial, sans-serif !important; isolation: isolate; z-index: 2147483647 !important; font-size: ${MonkeyConfig.esc(
                cfg.shadowFontSize
              )} !important; color: ${MonkeyConfig.esc(
          cfg.shadowFontColor
        )} !important; }
              h1 { font-size: 120% !important; font-weight: normal !important; margin: 0 !important; padding: 0 !important; }
              ${MonkeyConfig.res.stylesheets.main
                .replace(/__FONT_SIZE__/g, MonkeyConfig.esc(cfg.shadowFontSize))
                .replace(
                  /__FONT_COLOR__/g,
                  MonkeyConfig.esc(cfg.shadowFontColor)
                )}
              .__MonkeyConfig_overlay { position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; background-color: rgba(0, 0, 0, 0.6) !important; z-index: 2147483646 !important; }
              .__MonkeyConfig_container { position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; z-index: 2147483647 !important; width: ${MonkeyConfig.esc(
                shadowWidth
              )} !important; height: ${MonkeyConfig.esc(
          heightStyle
        )} !important; max-width: 90vw !important; max-height: 80vh !important; overflow-y: auto !important; box-sizing: border-box !important; }
            </style>
            <div class="__MonkeyConfig_overlay"></div>
            ${render()}`);
        container = shadowRoot.querySelector(".__MonkeyConfig_container");
        openLayer.style.cssText =
          "position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; z-index: 2147483647 !important;";
        body.appendChild(openLayer);
        log("Dialog appended to body via Shadow DOM");
        const appliedWidth = container?.offsetWidth || "unknown";
        const appliedHeight = container?.offsetHeight || "unknown";
        log(
          `Actual applied dimensions - Width: ${appliedWidth}px, Height: ${appliedHeight}px`
        );
        if (
          !container ||
          shadowRoot.querySelector(".__MonkeyConfig_overlay")?.offsetHeight ===
            0
        ) {
          throw new Error("Shadow DOM rendering failed");
        }
        openDone(shadowRoot);
      } catch (e) {
        log(
          `Shadow DOM failed: ${e.message}, switching to iframe fallback`,
          "warn"
        );
        body.removeChild(openLayer);
        shadowRoot = null;
      }
    }
    if (!shadowRoot) {
      iframeFallback = document.createElement("iframe");
      const iframeWidth = cfg.iframeWidth || "600px";
      const iframeHeight = cfg.iframeHeight || "300px";
      log(
        `Switching to iframe with dimensions - Width: ${iframeWidth}, Height: ${iframeHeight}`
      );
      iframeFallback.style.cssText = `position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: ${MonkeyConfig.esc(
        iframeWidth
      )} !important; height: ${MonkeyConfig.esc(
        iframeHeight
      )} !important; max-width: 90vw !important; max-height: 80vh !important; z-index: 2147483647 !important; border: none !important; background: #eee !important; box-shadow: 2px 2px 16px #000 !important; border-radius: 0.5em !important;`;
      body.appendChild(iframeFallback);
      const iframeDoc = iframeFallback.contentDocument;
      try {
        iframeDoc.open();
        const iframeHTML = createTrustedHTML(`<!DOCTYPE html><html><head><style>
            html, body, * { all: initial !important; margin: 0 !important; padding: 0 !important; font-family: Arial, sans-serif !important; font-size: ${MonkeyConfig.esc(
              cfg.iframeFontSize
            )} !important; color: ${MonkeyConfig.esc(
          cfg.iframeFontColor
        )} !important; height: 100% !important; width: 100% !important; box-sizing: border-box !important; }
            html, body { background: #eee !important; display: block !important; isolation: isolate !important; }
            input, textarea, button, label, table, td, div, span { all: unset !important; }
            ${MonkeyConfig.res.stylesheets.main
              .replace(/__FONT_SIZE__/g, MonkeyConfig.esc(cfg.iframeFontSize))
              .replace(
                /__FONT_COLOR__/g,
                MonkeyConfig.esc(cfg.iframeFontColor)
              )}
            .__MonkeyConfig_overlay { position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; background-color: rgba(0, 0, 0, 0.6) !important; z-index: 2147483646 !important; }
            .__MonkeyConfig_container { position: relative !important; width: 100% !important; height: 100% !important; padding: 1em !important; box-sizing: border-box !important; overflow-y: auto !important; border-radius: 0.5em !important; font-size: ${MonkeyConfig.esc(
              cfg.iframeFontSize
            )} !important; isolation: isolate !important; background: #eee linear-gradient(180deg, #f8f8f8 0, #ddd 100%) !important; }
            .__MonkeyConfig_container h1 { font-size: 120% !important; font-weight: normal !important; margin: 0 !important; padding: 0 !important; display: block !important; }
            .__MonkeyConfig_container td.__MonkeyConfig_inline input[type="checkbox"] { width: 11px !important; height: 11px !important; margin: 0 0.5em 0 0 !important; vertical-align: middle !important; accent-color: #007bff !important; display: inline-block !important; }
            .__MonkeyConfig_container td.__MonkeyConfig_inline input[type="number"] { width: 40px !important; height: 20px !important; margin: 0 0.5em 0 0 !important; vertical-align: middle !important; display: inline-block !important; }
            .__MonkeyConfig_container textarea { width: 100% !important; padding: 1.2em !important; border: 1px solid #ccc !important; border-radius: 0.3em !important; box-sizing: border-box !important; font-size: 20px !important; color: ${MonkeyConfig.esc(
              cfg.iframeFontColor
            )} !important; resize: vertical !important; min-height: 140px !important; white-space: pre-wrap !important; display: block !important; }
            .__MonkeyConfig_container button { background: #ccc linear-gradient(180deg, #ddd 0, #ccc 45%, #bbb 50%, #aaa 100%) !important; border: 1px solid #999 !important; border-radius: 0.5em !important; box-shadow: 0 0 1px #000 !important; padding: 12px 16px 12px 48px !important; white-space: nowrap !important; font-size: 20px !important; color: ${MonkeyConfig.esc(
              cfg.iframeFontColor
            )} !important; cursor: pointer !important; display: inline-block !important; }
            .__MonkeyConfig_container button:hover { background: #d2d2d2 linear-gradient(180deg, #e2e2e2 0, #d2d2d2 45%, #c2c2c2 50%, #b2b2b2 100%) !important; }
            .__MonkeyConfig_container label { display: inline-block !important; line-height: 120% !important; vertical-align: middle !important; }
            .__MonkeyConfig_container table { border-spacing: 0 !important; margin: 0 !important; width: 100% !important; display: table !important; }
            .__MonkeyConfig_container td { border: none !important; line-height: 100% !important; padding: 0.3em !important; text-align: left !important; vertical-align: middle !important; white-space: normal !important; display: table-cell !important; }
          </style></head><body><div class="__MonkeyConfig_overlay"></div>${render()}</body></html>`);
        iframeDoc.write(iframeHTML);
        iframeDoc.close();
        openLayer = iframeFallback;
        openDone(iframeDoc);
        const iframeAppliedWidth = iframeFallback.offsetWidth || "unknown";
        const iframeAppliedHeight = iframeFallback.offsetHeight || "unknown";
        log(
          `Iframe actual applied dimensions - Width: ${iframeAppliedWidth}px, Height: ${iframeAppliedHeight}px`
        );
      } catch (e) {
        log(`Iframe rendering failed: ${e.message}`, "error");
        body.removeChild(iframeFallback);
        iframeFallback = null;
      }
    }
  }
  function close() {
    try {
      if (openLayer && openLayer.parentNode) {
        openLayer.parentNode.removeChild(openLayer);
      }
      openLayer = shadowRoot = iframeFallback = undefined;
      displayed = false;
    } catch (e) {
      log(`Failed to close dialog: ${e.message}`, "error");
    }
  }
  init();
}
MonkeyConfig.esc = (string) => {
  try {
    return String(string)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#39;");
  } catch (e) {
    log(`Failed to escape string: ${e.message}`, "error");
    return "";
  }
};
MonkeyConfig.HTML = {
  _field: (name, opt) => {
    try {
      return opt.type && MonkeyConfig.HTML[opt.type]
        ? opt.html
          ? opt.html.replace(
              /\[FIELD\]/,
              MonkeyConfig.HTML[opt.type](name, opt)
            )
          : MonkeyConfig.HTML[opt.type](name, opt)
        : "";
    } catch (e) {
      log(`Failed to render field ${name}: ${e.message}`, "error");
      return "";
    }
  },
  _label: (name, opt) => {
    try {
      return `<label for="__MonkeyConfig_field_${MonkeyConfig.esc(name)}"${
        opt.labelAlign || opt.fontSize || opt.fontColor
          ? ` style="${[
              opt.labelAlign &&
                `text-align:${MonkeyConfig.esc(opt.labelAlign)}`,
              opt.fontSize && `font-size:${MonkeyConfig.esc(opt.fontSize)}`,
              opt.fontColor && `color:${MonkeyConfig.esc(opt.fontColor)}`,
            ]
              .filter(Boolean)
              .join(";")};"`
          : ""
      }>${MonkeyConfig.esc(
        opt.label ||
          name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, " ")
      )}</label>`;
    } catch (e) {
      log(`Failed to render label for ${name}: ${e.message}`, "error");
      return "";
    }
  },
  checkbox: (name) =>
    `<input id="__MonkeyConfig_field_${MonkeyConfig.esc(
      name
    )}" type="checkbox" name="${MonkeyConfig.esc(name)}" />`,
  custom: (name, opt) => opt.html || "",
  number: (name, opt) =>
    `<input id="__MonkeyConfig_field_${MonkeyConfig.esc(
      name
    )}" type="number" class="__MonkeyConfig_field_number" name="${MonkeyConfig.esc(
      name
    )}" min="${MonkeyConfig.esc(opt.min || "")}" max="${MonkeyConfig.esc(
      opt.max || ""
    )}" step="${MonkeyConfig.esc(opt.step || "1")}" />`,
  text: (name) =>
    `<input id="__MonkeyConfig_field_${MonkeyConfig.esc(
      name
    )}" type="text" class="__MonkeyConfig_field_text" name="${MonkeyConfig.esc(
      name
    )}" />`,
  color: (name) =>
    `<input id="__MonkeyConfig_field_${MonkeyConfig.esc(
      name
    )}" type="color" class="__MonkeyConfig_field_text" name="${MonkeyConfig.esc(
      name
    )}" />`,
  textarea: (name, opt) =>
    `<textarea id="__MonkeyConfig_field_${MonkeyConfig.esc(
      name
    )}" class="__MonkeyConfig_field_text" name="${MonkeyConfig.esc(
      name
    )}" rows="${MonkeyConfig.esc(opt.rows || 4)}" cols="${MonkeyConfig.esc(
      opt.cols || 20
    )}"></textarea>`,
  range: (name, opt) =>
    `<input id="__MonkeyConfig_field_${MonkeyConfig.esc(
      name
    )}" type="range" name="${MonkeyConfig.esc(name)}" min="${MonkeyConfig.esc(
      opt.min || 0
    )}" max="${MonkeyConfig.esc(opt.max || 100)}" step="${MonkeyConfig.esc(
      opt.step || 1
    )}" />`,
  radio: (name, opt) => {
    try {
      return Object.entries(opt.choices)
        .map(
          ([val, text]) =>
            `<label><input type="radio" name="${MonkeyConfig.esc(
              name
            )}" value="${MonkeyConfig.esc(val)}" /> ${MonkeyConfig.esc(
              text
            )}</label><br/>`
        )
        .join("");
    } catch (e) {
      log(`Failed to render radio for ${name}: ${e.message}`, "error");
      return "";
    }
  },
  file: (name, opt) =>
    `<input id="__MonkeyConfig_field_${MonkeyConfig.esc(
      name
    )}" type="file" name="${MonkeyConfig.esc(name)}" accept="${MonkeyConfig.esc(
      opt.accept || "*/*"
    )}" />`,
  button: (name, opt) =>
    `<button type="button" id="__MonkeyConfig_field_${MonkeyConfig.esc(
      name
    )}" name="${MonkeyConfig.esc(name)}">${MonkeyConfig.esc(
      opt.label || "Click"
    )}</button>`,
  group: (name, opt) => {
    try {
      return `<fieldset><legend>${MonkeyConfig.esc(
        opt.label || name
      )}</legend>${Object.entries(opt.params)
        .map(([subName, subOpt]) => MonkeyConfig.formatters.tr(subName, subOpt))
        .join("")}</fieldset>`;
    } catch (e) {
      log(`Failed to render group for ${name}: ${e.message}`, "error");
      return "";
    }
  },
  select: (name, opt) => {
    try {
      const choices = Array.isArray(opt.choices)
        ? Object.fromEntries(opt.choices.map((val) => [val, val]))
        : opt.choices;
      return `<select id="__MonkeyConfig_field_${MonkeyConfig.esc(
        name
      )}" class="__MonkeyConfig_field_select" name="${MonkeyConfig.esc(name)}"${
        opt.multiple ? ' multiple="multiple"' : ""
      }>${Object.entries(choices)
        .map(
          ([val, text]) =>
            `<option value="${MonkeyConfig.esc(val)}">${MonkeyConfig.esc(
              text
            )}</option>`
        )
        .join("")}</select>`;
    } catch (e) {
      log(`Failed to render select for ${name}: ${e.message}`, "error");
      return "";
    }
  },
};
MonkeyConfig.formatters = {
  tr: (name, opt) => {
    try {
      return `<tr>${
        ["checkbox", "number", "text"].includes(opt.type)
          ? `<td id="__MonkeyConfig_parent_${MonkeyConfig.esc(
              name
            )}" colspan="2" class="__MonkeyConfig_inline">${MonkeyConfig.HTML._label(
              name,
              opt
            )} ${MonkeyConfig.HTML._field(name, opt)}</td>`
          : opt.type === "group"
          ? `<td colspan="2">${MonkeyConfig.HTML._field(name, opt)}</td>`
          : `<td>${MonkeyConfig.HTML._label(
              name,
              opt
            )}</td><td id="__MonkeyConfig_parent_${MonkeyConfig.esc(
              name
            )}">${MonkeyConfig.HTML._field(name, opt)}</td>`
      }</tr>`;
    } catch (e) {
      log(`Failed to format table row for ${name}: ${e.message}`, "error");
      return "";
    }
  },
};
MonkeyConfig.res = {
  icons: {
    save:  '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg>',
    reset: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-ccw-icon lucide-rotate-ccw"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>',    
    close: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',
    reload:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-refresh-ccw-icon lucide-refresh-ccw"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></svg>',
    home:  '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-house-icon lucide-house"><path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>',
  },
  stylesheets: {
    main: `:host, body { all: initial; font-family: Arial, sans-serif !important; display: block !important; isolation: isolate; }
.__MonkeyConfig_container { display: flex !important; flex-direction: column !important; padding: 1em !important; font-size: __FONT_SIZE__ !important; color: __FONT_COLOR__ !important; background: #eee linear-gradient(180deg, #f8f8f8 0, #ddd 100%) !important; border-radius: 0.5em !important; box-shadow: 2px 2px 16px #000 !important; box-sizing: border-box !important; }
.__MonkeyConfig_container h1 { border-bottom: solid 1px #999 !important; font-size: 120% !important; font-weight: normal !important; margin: 0 0 0.5em 0 !important; padding: 0 0 0.3em 0 !important; text-align: center !important; }
.__MonkeyConfig_content { flex: 1 !important; overflow-y: auto !important; max-height: 60vh !important; }
.__MonkeyConfig_top, .__MonkeyConfig_bottom { margin-bottom: 1em !important; }
.__MonkeyConfig_top_columns, .__MonkeyConfig_bottom_columns { display: flex !important; justify-content: space-between !important; margin-bottom: 1em !important;}
.__MonkeyConfig_left_top, .__MonkeyConfig_right_top, .__MonkeyConfig_left_bottom, .__MonkeyConfig_right_bottom { width: 48% !important; }
.__MonkeyConfig_columns { display: flex !important; justify-content: space-between !important; margin-bottom: 1em !important; }
.__MonkeyConfig_left_column, .__MonkeyConfig_right_column { width: 48% !important; }
.__MonkeyConfig_container table { border-spacing: 0 !important; margin: 0 !important; width: 100% !important; }
.__MonkeyConfig_container td { border: none !important; line-height: 100% !important; padding: 0.3em !important; text-align: left !important; vertical-align: middle !important; white-space: normal !important; }
.__MonkeyConfig_container td.__MonkeyConfig_inline { display: flex !important; align-items: center !important; white-space: nowrap !important; }
.__MonkeyConfig_container td.__MonkeyConfig_inline label { margin-right: 0.5em !important; flex-shrink: 0 !important; display: block !important; max-width: 100% !important; overflow-x: auto !important; white-space: nowrap !important; scrollbar-width: thin !important; }
.__MonkeyConfig_container td.__MonkeyConfig_inline input[type="checkbox"] { flex-grow: 0 !important; margin: 0 0.3em 0 0 !important; display: inline-block !important; width: 11px !important; height: 11px !important; }
.__MonkeyConfig_container td.__MonkeyConfig_inline input[type="number"] { flex-grow: 0 !important; width: 40px !important; height: 20px !important; }
.__MonkeyConfig_buttons_container { margin-top: 1em !important; border-top: solid 1px #999 !important; padding-top: 0.6em !important; text-align: center !important; }
.__MonkeyConfig_buttons_container table { width: auto !important; margin: 0 auto !important; }
.__MonkeyConfig_buttons_container td { padding: 0.3em !important; }
.__MonkeyConfig_container button { background: #ccc linear-gradient(180deg, #ddd 0, #ccc 45%, #bbb 50%, #aaa 100%) !important; border: solid 1px !important; border-radius: 0.5em !important; box-shadow: 0 0 1px #000 !important; padding: 3px 8px 3px 24px !important; white-space: nowrap !important; }
.__MonkeyConfig_container button img { vertical-align: middle !important; }
.__MonkeyConfig_container label { line-height: 120% !important; vertical-align: middle !important; display: inline-block !important; max-width: 100% !important; overflow-x: auto !important; white-space: nowrap !important; scrollbar-width: thin !important; }
.__MonkeyConfig_container textarea { vertical-align: text-top !important; width: 100% !important; white-space: pre-wrap !important; resize: vertical !important; text-align: left !important; }
.__MonkeyConfig_container input[type="text"], .__MonkeyConfig_container input[type="number"], .__MonkeyConfig_container input[type="color"] { background: #fff !important; }
.__MonkeyConfig_container button:hover { background: #d2d2d2 linear-gradient(180deg, #e2e2e2 0, #d2d2d2 45%, #c2c2c2 50%, #b2b2b2 100%) !important; }
@media (max-width: 500px) {
    .__MonkeyConfig_columns, .__MonkeyConfig_top_columns, .__MonkeyConfig_bottom_columns { flex-direction: column !important; }
    .__MonkeyConfig_left_column, .__MonkeyConfig_right_column, .__MonkeyConfig_left_top, .__MonkeyConfig_right_top, .__MonkeyConfig_left_bottom, .__MonkeyConfig_right_bottom { width: 100% !important; }
    .__MonkeyConfig_container label { animation: scroll-text 10s linear infinite; }
    @keyframes scroll-text { 0% { transform: translateX(0); } 100% { transform: translateX(-100%); } }}`,
  },
};