Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta
// @require https://update.greasyforks.org/scripts/548573/1655884/ModernMonkeyConfig%20Edition%20Enhanced%20Mod%20v030.js
// ==UserScript==
// @name ModernMonkeyConfig Edition Enhanced mod2
// @noframes
// @version 0.3.3
// @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.3] ${message}`);
} catch (e) {
console.error(`[MonkeyConfig v0.3.3] 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">
${
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">${MonkeyConfig.res.icons.save} Save Without Reload</button>`;
} else if (btn === "reset") {
html += `<button type="button" id="__MonkeyConfig_button_reset">${MonkeyConfig.res.icons.reset} Reset</button>`;
} else if (btn === "reload") {
html += `<button type="button" id="__MonkeyConfig_button_reload">${MonkeyConfig.res.icons.reload} Save With Reload</button>`;
} else if (btn === "homepage") {
html += `<button type="button" id="__MonkeyConfig_button_homepage">${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, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
} 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"><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"><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"><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"><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"><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: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif !important;
display: block !important;
isolation: isolate !important;
box-sizing: border-box !important;
}
*, *::before, *::after {
box-sizing: border-box !important;
}
.__MonkeyConfig_overlay {
position: fixed !important;
inset: 0 !important;
background-color: rgba(0, 0, 0, 0.5) !important;
backdrop-filter: blur(4px) !important;
-webkit-backdrop-filter: blur(4px) !important;
z-index: 2147483646 !important;
animation: fadeIn 0.15s ease-out !important;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideIn {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.95);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
.__MonkeyConfig_container {
position: fixed !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
z-index: 2147483647 !important;
width: __WIDTH__ !important;
height: __HEIGHT__ !important;
max-width: calc(100vw - 2rem) !important;
max-height: calc(100vh - 2rem) !important;
background-color: white !important;
border-radius: 0.75rem !important; /* rounded-xl */
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25) !important; /* shadow-2xl */
overflow: hidden !important;
display: flex !important;
flex-direction: column !important;
font-size: __FONT_SIZE__ !important;
color: __FONT_COLOR__ !important;
animation: slideIn 0.2s ease-out !important;
}
.__MonkeyConfig_container h1 {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
color: white !important;
font-size: 1.25rem !important; /* text-xl */
font-weight: 600 !important; /* font-semibold */
margin: 0 !important;
padding: 1.5rem 4rem 1.5rem 1.5rem !important; /* p-6 pr-16 */
border-bottom: 1px solid rgba(229, 231, 235, 0.2) !important;
position: relative !important;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) !important;
}
.__MonkeyConfig_container button#__MonkeyConfig_button_close {
position: absolute !important;
top: 1rem !important; /* top-4 */
right: 1rem !important; /* right-4 */
width: 2rem !important; /* w-8 */
height: 2rem !important; /* h-8 */
border: none !important;
border-radius: 0.375rem !important; /* rounded-md */
background-color: rgba(255, 255, 255, 0.2) !important;
color: white !important;
cursor: pointer !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
transition: all 0.15s ease-in-out !important;
backdrop-filter: blur(8px) !important;
-webkit-backdrop-filter: blur(8px) !important;
}
.__MonkeyConfig_container button#__MonkeyConfig_button_close:hover {
background-color: rgba(255, 255, 255, 0.3) !important;
transform: scale(1.05) !important;
}
.__MonkeyConfig_content {
flex: 1 !important;
overflow-y: auto !important;
padding: 1.5rem !important; /* p-6 */
background-color: #f9fafb !important; /* bg-gray-50 */
}
.__MonkeyConfig_content::-webkit-scrollbar {
width: 0.5rem !important; /* w-2 */
}
.__MonkeyConfig_content::-webkit-scrollbar-track {
background-color: #f3f4f6 !important; /* bg-gray-100 */
border-radius: 0.25rem !important; /* rounded */
}
.__MonkeyConfig_content::-webkit-scrollbar-thumb {
background-color: #d1d5db !important; /* bg-gray-300 */
border-radius: 0.25rem !important; /* rounded */
}
.__MonkeyConfig_content::-webkit-scrollbar-thumb:hover {
background-color: #9ca3af !important; /* bg-gray-400 */
}
.__MonkeyConfig_top,
.__MonkeyConfig_bottom {
margin-bottom: 1.5rem !important; /* mb-6 */
}
.__MonkeyConfig_columns,
.__MonkeyConfig_top_columns,
.__MonkeyConfig_bottom_columns {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important; /* grid-cols-2 */
gap: 1.5rem !important; /* gap-6 */
margin-bottom: 1.5rem !important; /* mb-6 */
}
.__MonkeyConfig_left_column,
.__MonkeyConfig_right_column,
.__MonkeyConfig_left_top,
.__MonkeyConfig_right_top,
.__MonkeyConfig_left_bottom,
.__MonkeyConfig_right_bottom {
background-color: white !important;
border-radius: 0.5rem !important; /* rounded-lg */
padding: 1.5rem !important; /* p-6 */
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06) !important; /* shadow-sm */
border: 1px solid #e5e7eb !important; /* border border-gray-200 */
}
.__MonkeyConfig_container table {
width: 100% !important;
border-collapse: separate !important;
border-spacing: 0 !important;
background-color: white !important;
border-radius: 0.5rem !important; /* rounded-lg */
overflow: hidden !important;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06) !important; /* shadow-sm */
border: 1px solid #e5e7eb !important; /* border border-gray-200 */
margin-bottom: 1.5rem !important; /* mb-6 */
}
.__MonkeyConfig_container td {
padding: 1rem 1.5rem !important; /* px-6 py-4 */
border-bottom: 1px solid #f3f4f6 !important; /* border-b border-gray-100 */
vertical-align: middle !important;
}
.__MonkeyConfig_container tr:last-child td {
border-bottom: none !important;
}
.__MonkeyConfig_container td.__MonkeyConfig_inline {
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
gap: 1rem !important; /* gap-4 */
}
.__MonkeyConfig_container td.__MonkeyConfig_inline label {
flex: 1 !important;
font-weight: 500 !important; /* font-medium */
color: #374151 !important; /* text-gray-700 */
margin: 0 !important;
}
.__MonkeyConfig_container label {
font-weight: 500 !important; /* font-medium */
color: #374151 !important; /* text-gray-700 */
line-height: 1.5 !important;
cursor: pointer !important;
display: block !important;
margin-bottom: 0.5rem !important; /* mb-2 */
}
.__MonkeyConfig_container input[type="text"],
.__MonkeyConfig_container input[type="number"],
.__MonkeyConfig_container input[type="color"],
.__MonkeyConfig_container input[type="range"],
.__MonkeyConfig_container select {
border: 1px solid #d1d5db !important; /* border border-gray-300 */
border-radius: 0.375rem !important; /* rounded-md */
padding: 0.5rem 0.75rem !important; /* px-3 py-2 */
font-size: 0.875rem !important; /* text-sm */
line-height: 1.25rem !important;
background-color: white !important;
transition: all 0.15s ease-in-out !important;
outline: none !important;
}
.__MonkeyConfig_container input[type="text"]:focus,
.__MonkeyConfig_container input[type="number"]:focus,
.__MonkeyConfig_container input[type="color"]:focus,
.__MonkeyConfig_container select:focus {
border-color: #3b82f6 !important; /* border-blue-500 */
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; /* ring-blue-500/10 */
}
.__MonkeyConfig_container input[type="number"] {
width: 5rem !important; /* w-20 */
}
.__MonkeyConfig_container input[type="text"] {
min-width: 8rem !important; /* min-w-32 */
}
/* Checkbox */
.__MonkeyConfig_container input[type="checkbox"] {
width: 1.125rem !important; /* w-4.5 */
height: 1.125rem !important; /* h-4.5 */
accent-color: #3b82f6 !important; /* accent-blue-500 */
cursor: pointer !important;
margin: 0 !important;
flex-shrink: 0 !important;
}
/* Textarea */
.__MonkeyConfig_container textarea {
width: 100% !important;
border: 1px solid #d1d5db !important; /* border border-gray-300 */
border-radius: 0.375rem !important; /* rounded-md */
padding: 0.75rem !important; /* p-3 */
font-size: 0.875rem !important; /* text-sm */
line-height: 1.5 !important;
background-color: white !important;
resize: vertical !important;
min-height: 5rem !important; /* min-h-20 */
font-family: inherit !important;
transition: all 0.15s ease-in-out !important;
outline: none !important;
}
.__MonkeyConfig_container textarea:focus {
border-color: #3b82f6 !important; /* border-blue-500 */
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important; /* ring-blue-500/10 */
}
/* Radio buttons */
.__MonkeyConfig_container input[type="radio"] {
width: 1rem !important; /* w-4 */
height: 1rem !important; /* h-4 */
accent-color: #3b82f6 !important; /* accent-blue-500 */
cursor: pointer !important;
margin-right: 0.5rem !important; /* mr-2 */
}
.__MonkeyConfig_container input[type="radio"] + label {
display: inline !important;
margin-bottom: 0 !important;
font-weight: 400 !important; /* font-normal */
cursor: pointer !important;
}
.__MonkeyConfig_buttons_container {
padding: 1.5rem !important; /* p-6 */
background-color: #f9fafb !important; /* bg-gray-50 */
border-top: 1px solid #e5e7eb !important; /* border-t border-gray-200 */
display: flex !important;
justify-content: center !important;
gap: 0.75rem !important; /* gap-3 */
flex-wrap: wrap !important;
}
.__MonkeyConfig_buttons_container table {
background: none !important;
box-shadow: none !important;
border: none !important;
margin: 0 !important;
}
.__MonkeyConfig_buttons_container td {
padding: 0 0.375rem !important; /* px-1.5 */
border: none !important;
}
.__MonkeyConfig_container button {
display: inline-flex !important;
align-items: center !important;
gap: 0.5rem !important; /* gap-2 */
padding: 0.5rem 1rem !important; /* px-4 py-2 */
font-size: 0.875rem !important; /* text-sm */
font-weight: 500 !important; /* font-medium */
border-radius: 0.375rem !important; /* rounded-md */
border: none !important;
cursor: pointer !important;
transition: all 0.15s ease-in-out !important;
text-decoration: none !important;
outline: none !important;
}
.__MonkeyConfig_container button#__MonkeyConfig_button_save {
background-color: #3b82f6 !important; /* bg-blue-500 */
color: white !important;
}
.__MonkeyConfig_container button#__MonkeyConfig_button_save:hover {
background-color: #2563eb !important; /* bg-blue-600 */
transform: translateY(-1px) !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important; /* shadow-md */
}
.__MonkeyConfig_container button#__MonkeyConfig_button_reset {
background-color: #f59e0b !important; /* bg-amber-500 */
color: white !important;
}
.__MonkeyConfig_container button#__MonkeyConfig_button_reset:hover {
background-color: #d97706 !important; /* bg-amber-600 */
transform: translateY(-1px) !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important; /* shadow-md */
}
.__MonkeyConfig_container button#__MonkeyConfig_button_reload {
background-color: #10b981 !important; /* bg-emerald-500 */
color: white !important;
}
.__MonkeyConfig_container button#__MonkeyConfig_button_reload:hover {
background-color: #059669 !important; /* bg-emerald-600 */
transform: translateY(-1px) !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important; /* shadow-md */
}
.__MonkeyConfig_container button#__MonkeyConfig_button_homepage {
background-color: #6b7280 !important; /* bg-gray-500 */
color: white !important;
}
.__MonkeyConfig_container button#__MonkeyConfig_button_homepage:hover {
background-color: #4b5563 !important; /* bg-gray-600 */
transform: translateY(-1px) !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important; /* shadow-md */
}
/* Select dropdown */
.__MonkeyConfig_container select {
appearance: none !important;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e") !important;
background-position: right 0.5rem center !important;
background-repeat: no-repeat !important;
background-size: 1.5em 1.5em !important;
padding-right: 2.5rem !important;
min-width: 8rem !important;
}
/* Fieldset for groups */
.__MonkeyConfig_container fieldset {
border: 1px solid #d1d5db !important; /* border border-gray-300 */
border-radius: 0.5rem !important; /* rounded-lg */
padding: 1rem !important; /* p-4 */
margin-bottom: 1rem !important; /* mb-4 */
background-color: white !important;
}
.__MonkeyConfig_container legend {
font-weight: 600 !important; /* font-semibold */
color: #374151 !important; /* text-gray-700 */
padding: 0 0.5rem !important; /* px-2 */
}
/* Responsive Design */
@media (max-width: 768px) {
.__MonkeyConfig_columns,
.__MonkeyConfig_top_columns,
.__MonkeyConfig_bottom_columns {
grid-template-columns: 1fr !important; /* grid-cols-1 */
gap: 1rem !important; /* gap-4 */
}
.__MonkeyConfig_container {
margin: 1rem !important; /* m-4 */
max-width: calc(100vw - 2rem) !important;
max-height: calc(100vh - 2rem) !important;
}
.__MonkeyConfig_container h1 {
font-size: 1.125rem !important; /* text-lg */
padding: 1rem 3rem 1rem 1rem !important; /* p-4 pr-12 */
}
.__MonkeyConfig_content {
padding: 1rem !important; /* p-4 */
}
.__MonkeyConfig_buttons_container {
padding: 1rem !important; /* p-4 */
flex-direction: column !important;
}
.__MonkeyConfig_container button {
width: 100% !important;
justify-content: center !important;
}
.__MonkeyConfig_container td.__MonkeyConfig_inline {
flex-direction: column !important;
align-items: stretch !important;
gap: 0.5rem !important; /* gap-2 */
}
.__MonkeyConfig_container td.__MonkeyConfig_inline label {
margin-bottom: 0.25rem !important; /* mb-1 */
}
}
@media (max-width: 480px) {
.__MonkeyConfig_container {
border-radius: 0 !important;
max-width: 100vw !important;
max-height: 100vh !important;
margin: 0 !important;
}
.__MonkeyConfig_container h1 {
padding: 0.75rem 2.5rem 0.75rem 0.75rem !important; /* p-3 pr-10 */
}
.__MonkeyConfig_content {
padding: 0.75rem !important; /* p-3 */
}
.__MonkeyConfig_buttons_container {
padding: 0.75rem !important; /* p-3 */
}
}
/* Animation for buttons */
@keyframes buttonPress {
0% { transform: translateY(0) scale(1); }
50% { transform: translateY(1px) scale(0.98); }
100% { transform: translateY(0) scale(1); }
}
.__MonkeyConfig_container button:active {
animation: buttonPress 0.1s ease-in-out !important;
}
/* Focus styles */
.__MonkeyConfig_container button:focus-visible {
outline: 2px solid #3b82f6 !important;
outline-offset: 2px !important;
}
.__MonkeyConfig_container input:focus-visible,
.__MonkeyConfig_container textarea:focus-visible,
.__MonkeyConfig_container select:focus-visible {
outline: 2px solid #3b82f6 !important;
outline-offset: 2px !important;
}
/* Loading state (optional) */
.__MonkeyConfig_container.loading {
opacity: 0.7 !important;
pointer-events: none !important;
}
/* Success message (optional) */
.__MonkeyConfig_success {
position: absolute !important;
top: 1rem !important;
left: 50% !important;
transform: translateX(-50%) !important;
background-color: #10b981 !important; /* bg-emerald-500 */
color: white !important;
padding: 0.5rem 1rem !important; /* px-4 py-2 */
border-radius: 0.375rem !important; /* rounded-md */
font-size: 0.875rem !important; /* text-sm */
z-index: 2147483648 !important;
animation: slideDown 0.3s ease-out !important;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateX(-50%) translateY(-1rem);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
`
},
};