بۇ قوليازمىنى بىۋاسىتە قاچىلاشقا بولمايدۇ. بۇ باشقا قوليازمىلارنىڭ ئىشلىتىشى ئۈچۈن تەمىنلەنگەن ئامبار بولۇپ، ئىشلىتىش ئۈچۈن مېتا كۆرسەتمىسىگە قىستۇرىدىغان كود: // @require https://update.greasyforks.org/scripts/549278/1666310/ModernMonkeyConfig%20Enhanced%20Security%20Edition%20v040.js
// ==UserScript==
// @name ModernMonkeyConfig Enhanced Security Edition v0.4.2
// @noframes
// @version 0.4.2
// @namespace http://odyniec.net/
// @include *
// @description Enhanced Security Configuration Dialog - Complete Organized Version
// @require https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js#sha384-qSFej5dZNviyoPgYJ5+Xk4bEbX8AYddxAHPuzs1aSgRiXxJ3qmyWNaPsRkpv/+x5
// ==/UserScript==
/**
* ModernMonkeyConfig - Enhanced Security Configuration Dialog
* Complete organized version with improved maintainability and performance
*/
class ModernMonkeyConfig {
constructor(data) {
this.version = '0.4.2';
this.initializeProperties();
this.validateAndInitialize(data);
}
// ===========================================
// INITIALIZATION METHODS
// ===========================================
/**
* Initialize all class properties
*/
initializeProperties() {
this.data = null;
this.params = {};
this.values = {};
this.storageKey = '';
// UI State
this.displayed = false;
this.openLayer = null;
this.shadowRoot = null;
this.container = null;
this.iframeFallback = null;
// Caching and performance
this.elementCache = new Map();
this.eventListeners = new Map();
this.validationRules = new Map();
// Security
this.trustedPolicy = null;
}
/**
* Validate configuration data and initialize the system
*/
validateAndInitialize(data) {
try {
this.data = this.validateAndSanitizeConfig(data);
this.params = this.data.parameters || {};
this.setupSecurity();
this.setupValidationRules();
this.setupStorage();
this.loadStoredValues();
if (this.data.menuCommand) {
this.registerMenuCommand();
}
this.setupPublicMethods();
this.log('ModernMonkeyConfig initialized successfully');
} catch (error) {
this.log(`Initialization failed: ${error.message}`, 'error');
throw error;
}
}
/**
* Setup security measures
*/
setupSecurity() {
this.createTrustedPolicy();
}
/**
* Setup validation rules
*/
setupValidationRules() {
this.validationRules.set('email', /^[^\s@]+@[^\s@]+\.[^\s@]+$/);
this.validationRules.set('url', /^https?:\/\/.+/);
this.validationRules.set('number', /^\d+$/);
this.validationRules.set('float', /^\d*\.?\d+$/);
}
/**
* Setup storage configuration
*/
setupStorage() {
this.storageKey = `_ModernMonkeyConfig_${this.data.title.replace(/[^a-zA-Z0-9]/g, '_')}_cfg`;
}
/**
* Setup public methods with proper binding
*/
setupPublicMethods() {
this.open = this.open.bind(this);
this.close = this.close.bind(this);
this.get = this.get.bind(this);
this.set = this.set.bind(this);
this.validate = this.validate.bind(this);
this.reset = this.reset.bind(this);
}
// ===========================================
// VALIDATION AND SANITIZATION
// ===========================================
/**
* Validate and sanitize the main configuration object
*/
validateAndSanitizeConfig(data) {
if (!data || typeof data !== 'object') {
throw new Error('Configuration data must be an object');
}
const sanitized = {
title: this.sanitizeString(data.title) || 'Configuration',
buttons: this.sanitizeButtons(data.buttons),
menuCommand: Boolean(data.menuCommand),
parameters: {},
...this.sanitizeDimensions(data),
onSave: typeof data.onSave === 'function' ? data.onSave : null
};
// Sanitize parameters
if (data.parameters && typeof data.parameters === 'object') {
sanitized.parameters = this.sanitizeParameters(data.parameters);
}
return sanitized;
}
/**
* Sanitize button configuration
*/
sanitizeButtons(buttons) {
const validButtons = ['save', 'reset', 'close', 'reload', 'homepage'];
return Array.isArray(buttons)
? buttons.filter(btn => validButtons.includes(btn))
: ['save', 'reset', 'close', 'reload', 'homepage'];
}
/**
* Sanitize dimension and styling properties
*/
sanitizeDimensions(data) {
return {
shadowWidth: this.validateDimension(data.shadowWidth) || '600px',
shadowHeight: this.validateDimension(data.shadowHeight) || '400px',
iframeWidth: this.validateDimension(data.iframeWidth) || '600px',
iframeHeight: this.validateDimension(data.iframeHeight) || '400px',
shadowFontSize: this.validateFontSize(data.shadowFontSize) || '14px',
shadowFontColor: this.validateColor(data.shadowFontColor) || '#000000',
iframeFontSize: this.validateFontSize(data.iframeFontSize) || '14px',
iframeFontColor: this.validateColor(data.iframeFontColor) || '#000000'
};
}
/**
* Sanitize all parameters
*/
sanitizeParameters(parameters) {
const sanitized = {};
for (const [key, param] of Object.entries(parameters)) {
if (this.isValidParameterKey(key) && this.isValidParameter(param)) {
sanitized[key] = this.sanitizeParameter(param);
} else {
this.log(`Invalid parameter skipped: ${key}`, 'warn');
}
}
return sanitized;
}
/**
* Sanitize individual parameter
*/
sanitizeParameter(param) {
const sanitized = {
type: param.type,
label: this.sanitizeString(param.label),
default: this.sanitizeValue(param.default, param.type),
column: this.validateColumn(param.column)
};
// Type-specific sanitization
switch (param.type) {
case 'number':
case 'range':
Object.assign(sanitized, this.sanitizeNumericParam(param));
break;
case 'textarea':
Object.assign(sanitized, this.sanitizeTextareaParam(param));
break;
case 'radio':
case 'select':
Object.assign(sanitized, this.sanitizeChoiceParam(param));
break;
case 'file':
sanitized.accept = this.sanitizeString(param.accept) || '*/*';
break;
case 'custom':
Object.assign(sanitized, this.sanitizeCustomParam(param));
break;
}
// Common styling properties
this.sanitizeStyleProperties(param, sanitized);
return sanitized;
}
/**
* Sanitize numeric parameter properties
*/
sanitizeNumericParam(param) {
return {
min: this.sanitizeNumber(param.min),
max: this.sanitizeNumber(param.max),
step: this.sanitizeNumber(param.step) || 1
};
}
/**
* Sanitize textarea parameter properties
*/
sanitizeTextareaParam(param) {
return {
rows: Math.max(1, Math.min(20, parseInt(param.rows) || 4)),
cols: Math.max(10, Math.min(100, parseInt(param.cols) || 20))
};
}
/**
* Sanitize choice-based parameter properties
*/
sanitizeChoiceParam(param) {
const result = {
multiple: Boolean(param.multiple)
};
if (param.choices && typeof param.choices === 'object') {
result.choices = this.sanitizeChoices(param.choices);
}
return result;
}
/**
* Sanitize custom parameter properties
*/
sanitizeCustomParam(param) {
const result = {
html: this.sanitizeString(param.html)
};
if (typeof param.get === 'function') result.get = param.get;
if (typeof param.set === 'function') result.set = param.set;
return result;
}
/**
* Sanitize styling properties
*/
sanitizeStyleProperties(param, sanitized) {
const styleProps = ['fontSize', 'fontColor', 'inputWidth', 'inputHeight',
'checkboxWidth', 'checkboxHeight'];
styleProps.forEach(prop => {
if (param[prop]) {
sanitized[prop] = this.sanitizeString(param[prop]);
}
});
}
// ===========================================
// BASIC SANITIZATION UTILITIES
// ===========================================
/**
* Sanitize string input
*/
sanitizeString(str) {
if (typeof str !== 'string') return '';
return str.trim().substring(0, 1000);
}
/**
* Sanitize numeric input
*/
sanitizeNumber(num) {
const parsed = parseFloat(num);
return isNaN(parsed) ? undefined : parsed;
}
/**
* Sanitize value based on parameter type
*/
sanitizeValue(value, type) {
const sanitizers = {
'number': () => this.sanitizeNumber(value),
'range': () => this.sanitizeNumber(value),
'checkbox': () => Boolean(value),
'text': () => this.sanitizeString(value),
'color': () => this.sanitizeString(value),
'textarea': () => this.sanitizeString(value),
'select': () => Array.isArray(value)
? value.map(v => this.sanitizeString(v))
: this.sanitizeString(value)
};
return sanitizers[type] ? sanitizers[type]() : value;
}
/**
* Sanitize choice options
*/
sanitizeChoices(choices) {
const sanitized = {};
for (const [key, value] of Object.entries(choices)) {
const cleanKey = this.sanitizeString(key);
const cleanValue = this.sanitizeString(value);
if (cleanKey && cleanValue) {
sanitized[cleanKey] = cleanValue;
}
}
return sanitized;
}
// ===========================================
// VALIDATION UTILITIES
// ===========================================
/**
* Validate parameter key format
*/
isValidParameterKey(key) {
return typeof key === 'string' &&
key.length > 0 &&
key.length <= 50 &&
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key);
}
/**
* Validate parameter structure
*/
isValidParameter(param) {
if (!param || typeof param !== 'object') return false;
const validTypes = ['checkbox', 'number', 'text', 'color', 'textarea',
'range', 'radio', 'file', 'button', 'select', 'group', 'custom'];
return validTypes.includes(param.type);
}
/**
* Validate column placement
*/
validateColumn(column) {
const validColumns = ['left', 'right', 'top', 'bottom',
'left&top', 'right&top', 'left&bottom', 'right&bottom'];
return validColumns.includes(column) ? column : null;
}
/**
* Validate dimension strings
*/
validateDimension(dimension) {
if (typeof dimension !== 'string') return null;
return /^\d+(px|em|rem|%|vh|vw)$/.test(dimension) ? dimension : null;
}
/**
* Validate font size strings
*/
validateFontSize(fontSize) {
if (typeof fontSize !== 'string') return null;
return /^\d+(px|em|rem)$/.test(fontSize) ? fontSize : null;
}
/**
* Validate color strings
*/
validateColor(color) {
if (typeof color !== 'string') return null;
return /^#([0-9A-Fa-f]{3}){1,2}$/.test(color) ? color : null;
}
// ===========================================
// SECURITY AND TRUSTED TYPES
// ===========================================
/**
* Create Trusted Types policy for secure HTML handling
*/
createTrustedPolicy() {
try {
if (window.trustedTypes && window.trustedTypes.createPolicy) {
this.trustedPolicy = window.trustedTypes.createPolicy(`monkeyConfig-${Date.now()}`, {
createHTML: (input) => this.sanitizeHTML(input)
});
} else {
this.trustedPolicy = {
createHTML: (input) => this.fallbackSanitize(input)
};
}
} catch (error) {
this.log(`Failed to create Trusted Types policy: ${error.message}`, 'error');
this.trustedPolicy = {
createHTML: (input) => this.fallbackSanitize(input)
};
}
}
/**
* Sanitize HTML using DOMPurify or fallback
*/
sanitizeHTML(input) {
if (typeof DOMPurify !== 'undefined') {
return DOMPurify.sanitize(input, {
ALLOWED_TAGS: ['div', 'span', 'table', 'tr', 'td', 'input', 'textarea',
'button', 'label', 'select', 'option', 'fieldset', 'legend',
'h1', 'br', 'svg', 'path', 'style'],
ALLOWED_ATTR: ['type', 'name', 'id', 'class', 'style', 'for', 'value',
'min', 'max', 'step', 'rows', 'cols', 'multiple', 'accept',
'width', 'height', 'viewBox', 'fill', 'stroke', 'stroke-width',
'stroke-linecap', 'stroke-linejoin', 'd', 'colspan', 'checked'],
ALLOW_DATA_ATTR: false,
RETURN_TRUSTED_TYPE: true
});
} else {
this.log('DOMPurify not available, using fallback sanitization', 'warn');
return this.fallbackSanitize(input);
}
}
/**
* Fallback HTML sanitization
*/
fallbackSanitize(input) {
return String(input)
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/on\w+\s*=\s*"[^"]*"/gi, '')
.replace(/on\w+\s*=\s*'[^']*'/gi, '')
.replace(/javascript:/gi, '')
.replace(/vbscript:/gi, '')
.replace(/data:/gi, '');
}
/**
* Create trusted HTML content
*/
createTrustedHTML(htmlString) {
try {
return this.trustedPolicy.createHTML(htmlString);
} catch (error) {
this.log(`Failed to create TrustedHTML: ${error.message}`, 'error');
return '';
}
}
// ===========================================
// STORAGE AND VALUES MANAGEMENT
// ===========================================
/**
* Load stored configuration values
*/
loadStoredValues() {
try {
let storedValues = this.getStoredData();
// Load UI settings
this.loadUISettings(storedValues);
// Load parameter values
this.loadParameterValues(storedValues);
} catch (error) {
this.log(`Failed to load stored values: ${error.message}`, 'error');
this.loadDefaultValues();
}
}
/**
* Get stored data from appropriate storage
*/
getStoredData() {
try {
if (typeof GM_getValue !== 'undefined') {
const stored = GM_getValue(this.storageKey);
return stored ? JSON.parse(stored) : {};
}
return {};
} catch {
return {};
}
}
/**
* Load UI-related settings
*/
loadUISettings(storedValues) {
this.shadowWidth = this.validateDimension(storedValues.shadowWidth) || this.data.shadowWidth;
this.shadowHeight = this.validateDimension(storedValues.shadowHeight) || this.data.shadowHeight;
this.iframeWidth = this.validateDimension(storedValues.iframeWidth) || this.data.iframeWidth;
this.iframeHeight = this.validateDimension(storedValues.iframeHeight) || this.data.iframeHeight;
this.shadowFontSize = this.validateFontSize(storedValues.shadowFontSize) || this.data.shadowFontSize;
this.shadowFontColor = this.validateColor(storedValues.shadowFontColor) || this.data.shadowFontColor;
this.iframeFontSize = this.validateFontSize(storedValues.iframeFontSize) || this.data.iframeFontSize;
this.iframeFontColor = this.validateColor(storedValues.iframeFontColor) || this.data.iframeFontColor;
}
/**
* Load parameter values
*/
loadParameterValues(storedValues) {
for (const [key, param] of Object.entries(this.params)) {
this.values[key] = storedValues[key] !== undefined
? this.sanitizeValue(storedValues[key], param.type)
: param.default;
}
}
/**
* Load default values for all parameters
*/
loadDefaultValues() {
for (const [key, param] of Object.entries(this.params)) {
this.values[key] = param.default;
}
}
/**
* Save current configuration to storage
*/
save() {
try {
const errors = this.validate();
if (errors.length > 0) {
alert('Validation errors:\n' + errors.join('\n'));
return false;
}
const dataToSave = this.prepareDataForSaving();
if (typeof GM_setValue !== 'undefined') {
GM_setValue(this.storageKey, JSON.stringify(dataToSave));
this.log('Settings saved successfully');
} else {
this.log('GM_setValue not available, cannot save settings', 'warn');
return false;
}
this.executeOnSaveCallback();
return true;
} catch (error) {
this.log(`Failed to save settings: ${error.message}`, 'error');
alert('Failed to save settings. Please check console for details.');
return false;
}
}
/**
* Prepare data structure for saving
*/
prepareDataForSaving() {
return {
...this.values,
shadowWidth: this.shadowWidth,
shadowHeight: this.shadowHeight,
iframeWidth: this.iframeWidth,
iframeHeight: this.iframeHeight,
shadowFontSize: this.shadowFontSize,
shadowFontColor: this.shadowFontColor,
iframeFontSize: this.iframeFontSize,
iframeFontColor: this.iframeFontColor
};
}
/**
* Execute onSave callback if provided
*/
executeOnSaveCallback() {
if (this.data.onSave && typeof this.data.onSave === 'function') {
try {
this.data.onSave(this.values);
} catch (error) {
this.log(`Error in onSave callback: ${error.message}`, 'error');
}
}
}
// ===========================================
// PUBLIC API METHODS
// ===========================================
/**
* Get parameter value
*/
get(name) {
if (!this.params[name]) {
this.log(`Parameter '${name}' does not exist`, 'warn');
return undefined;
}
return this.values[name];
}
/**
* Set parameter value
*/
set(name, value) {
try {
if (!this.params[name]) {
this.log(`Parameter '${name}' does not exist`, 'warn');
return false;
}
const sanitizedValue = this.sanitizeValue(value, this.params[name].type);
if (this.validateValue(name, sanitizedValue)) {
this.values[name] = sanitizedValue;
this.updateUI();
return true;
}
return false;
} catch (error) {
this.log(`Failed to set value for ${name}: ${error.message}`, 'error');
return false;
}
}
/**
* Reset all values to defaults
*/
reset() {
try {
for (const [key, param] of Object.entries(this.params)) {
this.values[key] = param.default;
}
this.updateUI();
this.log('Configuration reset to defaults');
} catch (error) {
this.log(`Failed to reset configuration: ${error.message}`, 'error');
}
}
/**
* Validate all current values
*/
validate() {
const errors = [];
for (const [name, value] of Object.entries(this.values)) {
if (!this.validateValue(name, value)) {
errors.push(`Invalid value for parameter '${name}': ${value}`);
}
}
return errors;
}
/**
* Validate individual value
*/
validateValue(name, value) {
const param = this.params[name];
if (!param) return false;
const validators = {
'number': (val, p) => this.validateNumericValue(val, p),
'range': (val, p) => this.validateNumericValue(val, p),
'text': (val, p) => this.validateTextValue(val, p),
'checkbox': (val) => typeof val === 'boolean'
};
const validator = validators[param.type];
return validator ? validator(value, param) : true;
}
/**
* Validate numeric values
*/
validateNumericValue(value, param) {
const num = parseFloat(value);
if (isNaN(num)) return false;
if (param.min !== undefined && num < param.min) return false;
if (param.max !== undefined && num > param.max) return false;
return true;
}
/**
* Validate text values
*/
validateTextValue(value, param) {
if (typeof value !== 'string') return false;
if (value.length > 1000) return false;
if (param.validation && this.validationRules.has(param.validation)) {
return this.validationRules.get(param.validation).test(value);
}
return true;
}
// ===========================================
// UTILITY METHODS
// ===========================================
/**
* Register menu command with userscript manager
*/
registerMenuCommand() {
try {
if (typeof GM_registerMenuCommand !== 'undefined') {
const commandText = this.data.menuCommand === true
? this.data.title
: String(this.data.menuCommand);
GM_registerMenuCommand(commandText, () => this.open());
}
} catch (error) {
this.log(`Failed to register menu command: ${error.message}`, 'error');
}
}
/**
* Log messages with timestamp and context
*/
log(message, level = 'info') {
try {
const timestamp = new Date().toISOString();
const formattedMessage = `[ModernMonkeyConfig v${this.version}] ${timestamp}: ${message}`;
if (console[level]) {
console[level](formattedMessage);
} else {
console.log(`[${level.toUpperCase()}] ${formattedMessage}`);
}
} catch (error) {
console.error(`[ModernMonkeyConfig v${this.version}] Logging failed: ${error.message}`);
}
}
/**
* Escape HTML for safe display
*/
escapeHtml(string) {
if (string == null) return '';
return String(string)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// ===========================================
// UI RENDERING AND MANAGEMENT
// ===========================================
/**
* Open the configuration dialog
*/
open() {
if (this.displayed) {
this.log('Configuration dialog is already open', 'warn');
return;
}
try {
this.createShadowDOM();
this.displayed = true;
this.log('Configuration dialog opened');
} catch (error) {
this.log(`Failed to open configuration dialog: ${error.message}`, 'error');
this.createIframeFallback();
}
}
/**
* Close the configuration dialog
*/
close() {
try {
if (!this.displayed) return;
this.removeEventListeners();
this.removeDOMElements();
this.clearReferences();
this.displayed = false;
this.log('Configuration dialog closed');
} catch (error) {
this.log(`Error closing dialog: ${error.message}`, 'error');
}
}
/**
* Remove all event listeners
*/
removeEventListeners() {
for (const [event, handler] of this.eventListeners) {
document.removeEventListener(event, handler);
}
this.eventListeners.clear();
}
/**
* Remove DOM elements
*/
removeDOMElements() {
if (this.openLayer && this.openLayer.parentNode) {
this.openLayer.parentNode.removeChild(this.openLayer);
}
}
/**
* Clear object references
*/
clearReferences() {
this.openLayer = null;
this.shadowRoot = null;
this.container = null;
this.iframeFallback = null;
this.elementCache.clear();
}
/**
* Enhanced Shadow DOM creation with proper event isolation
*/
createShadowDOM() {
try {
this.createOverlay();
if (this.openLayer.attachShadow) {
this.shadowRoot = this.openLayer.attachShadow({ mode: 'closed' });
this.populateShadowDOM();
// منع انتشار أحداث النقر من Shadow DOM إلى الخلفية
const container = this.shadowRoot.querySelector('.__MonkeyConfig_container');
if (container) {
container.addEventListener('click', (e) => {
e.stopPropagation();
});
}
} else {
throw new Error('Shadow DOM not supported');
}
document.body.appendChild(this.openLayer);
this.attachEventListeners();
this.focusFirstElement();
} catch (error) {
this.log(`Failed to create Shadow DOM: ${error.message}`, 'error');
throw error;
}
}
/**
* Create overlay element
*/
createOverlay() {
this.openLayer = document.createElement('div');
this.openLayer.style.cssText = `
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
background: rgba(0, 0, 0, 0.5) !important;
z-index: 2147483646 !important;
backdrop-filter: blur(2px) !important;
`;
}
/**
* Populate Shadow DOM with content
*/
populateShadowDOM() {
const container = document.createElement('div');
container.innerHTML = this.getCSS() + this.render();
this.shadowRoot.appendChild(container);
this.container = this.shadowRoot.querySelector('.__MonkeyConfig_container');
}
/**
* Focus first interactive element
*/
focusFirstElement() {
setTimeout(() => {
const firstInput = this.shadowRoot.querySelector('input, textarea, select, button');
if (firstInput) firstInput.focus();
}, 100);
}
/**
* Create iframe fallback when Shadow DOM fails
*/
createIframeFallback() {
try {
this.log('Using iframe fallback', 'info');
this.createOverlay();
this.createIframe();
this.populateIframe();
document.body.appendChild(this.openLayer);
// منع إغلاق النافذة عند النقر داخل الإطار
this.preventIframeBackdropClose();
this.attachEventListeners(this.iframeFallback.contentDocument);
this.displayed = true;
} catch (error) {
this.log(`Failed to create iframe fallback: ${error.message}`, 'error');
alert('Failed to open configuration dialog. Please check console for details.');
}
}
/**
* Create iframe element
*/
createIframe() {
this.iframeFallback = document.createElement('iframe');
this.iframeFallback.style.cssText = `
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) !important;
width: ${this.iframeWidth} !important;
height: ${this.iframeHeight} !important;
border: none !important;
border-radius: 8px !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important;
`;
this.openLayer.appendChild(this.iframeFallback);
}
/**
* Populate iframe with content
*/
populateIframe() {
const iframeDoc = this.iframeFallback.contentDocument || this.iframeFallback.contentWindow.document;
const html = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${this.escapeHtml(this.data.title)}</title>
${this.getIframeCSS()}
</head>
<body style="margin:0;padding:0;overflow:hidden;">
${this.render()}
</body>
</html>
`;
iframeDoc.open();
iframeDoc.write(html);
iframeDoc.close();
this.container = iframeDoc.querySelector('.__MonkeyConfig_container');
}
/**
* Get CSS adapted for iframe
*/
getIframeCSS() {
return this.getCSS()
.replace(new RegExp(this.shadowFontSize, 'g'), this.iframeFontSize)
.replace(new RegExp(this.shadowFontColor, 'g'), this.iframeFontColor);
}
/**
* Prevent iframe content clicks from bubbling to backdrop
*/
preventIframeBackdropClose() {
if (this.iframeFallback) {
// منع انتشار أحداث النقر من داخل الإطار إلى الخلفية
this.iframeFallback.addEventListener('click', (e) => {
e.stopPropagation();
});
// منع انتشار الأحداث من محتوى الإطار
const iframeDoc = this.iframeFallback.contentDocument || this.iframeFallback.contentWindow.document;
if (iframeDoc) {
iframeDoc.addEventListener('click', (e) => {
e.stopPropagation();
});
}
}
}
/**
* Attach event listeners to the dialog
*/
attachEventListeners(doc = null) {
try {
const context = doc || this.shadowRoot;
if (!context) return;
const container = context.querySelector('.__MonkeyConfig_container');
if (!container) return;
// Single event listener with delegation for better performance
const handleEvent = (e) => {
this.handleDialogEvent(e);
};
// Attach delegated event listeners
container.addEventListener('click', handleEvent);
container.addEventListener('change', handleEvent);
container.addEventListener('input', handleEvent);
// Backdrop click to close
this.attachBackdropListener();
// Keyboard shortcuts
this.attachKeyboardListeners(doc);
this.log('Event listeners attached');
} catch (error) {
this.log(`Failed to attach event listeners: ${error.message}`, 'error');
}
}
/**
* Handle dialog events with delegation
*/
handleDialogEvent(e) {
const target = e.target;
const targetId = target.id;
const targetName = target.name;
// Button clicks
if (targetId && targetId.startsWith('__MonkeyConfig_button_')) {
e.preventDefault();
const buttonType = targetId.replace('__MonkeyConfig_button_', '');
this.handleButtonClick(buttonType);
return;
}
// Form field changes
if (targetName && this.params[targetName]) {
this.handleFieldChange(targetName, target);
return;
}
}
/**
* Attach backdrop click listener with improved logic
*/
attachBackdropListener() {
if (this.openLayer) {
this.openLayer.addEventListener('click', (e) => {
// التحقق من أن النقر كان على الخلفية المظلمة وليس على أي محتوى داخلي
const clickTarget = e.target;
const container = this.shadowRoot ?
this.shadowRoot.querySelector('.__MonkeyConfig_container') :
(this.iframeFallback ? this.iframeFallback : null);
// إغلاق فقط عند النقر على الـ overlay نفسه، وليس على المحتوى
if (clickTarget === this.openLayer) {
this.close();
}
});
}
}
/**
* Attach keyboard event listeners
*/
attachKeyboardListeners(doc) {
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
this.close();
} else if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.handleButtonClick('save');
}
};
(doc || document).addEventListener('keydown', handleKeyDown);
this.eventListeners.set('keydown', handleKeyDown);
}
/**
* Handle button click events
*/
handleButtonClick(buttonType) {
try {
const handlers = {
'save': () => this.save(),
'reset': () => this.handleReset(),
'close': () => this.close(),
'reload': () => this.handleReload(),
'homepage': () => this.handleHomepage()
};
const handler = handlers[buttonType];
if (handler) {
handler();
} else {
this.log(`Unknown button type: ${buttonType}`, 'warn');
}
} catch (error) {
this.log(`Error handling button click (${buttonType}): ${error.message}`, 'error');
}
}
/**
* Handle reset button click
*/
handleReset() {
if (confirm('Are you sure you want to reset all settings to default values?')) {
this.reset();
}
}
/**
* Handle reload button click
*/
handleReload() {
this.save();
if (typeof GM_getValue !== 'undefined') {
location.reload();
} else {
alert('Settings saved. Please refresh the page manually.');
}
}
/**
* Handle homepage button click
*/
handleHomepage() {
if (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.homepage) {
window.open(GM_info.script.homepage, '_blank');
} else {
alert('Homepage not available');
}
}
/**
* Handle form field changes
*/
handleFieldChange(name, element) {
try {
const param = this.params[name];
if (!param) return;
const value = this.extractFieldValue(param, element);
if (this.validateValue(name, value)) {
this.values[name] = value;
// Update UI for range sliders
if (param.type === 'range') {
this.updateRangeDisplay(element, value);
}
this.log(`Field '${name}' updated to: ${value}`);
} else {
this.log(`Invalid value for field '${name}': ${value}`, 'warn');
this.revertFieldValue(element, name);
}
} catch (error) {
this.log(`Error handling field change (${name}): ${error.message}`, 'error');
}
}
/**
* Extract value from form field based on type
*/
extractFieldValue(param, element) {
switch (param.type) {
case 'checkbox':
return element.checked;
case 'number':
case 'range':
const num = parseFloat(element.value);
return isNaN(num) ? param.default : num;
case 'select':
return param.multiple
? Array.from(element.selectedOptions).map(option => option.value)
: element.value;
case 'custom':
return param.get && typeof param.get === 'function'
? param.get(element)
: element.value;
default:
return element.value;
}
}
/**
* Update range slider display
*/
updateRangeDisplay(element, value) {
const rangeValue = element.parentNode.querySelector('.__MonkeyConfig_range_value');
if (rangeValue) {
rangeValue.textContent = value;
}
}
/**
* Revert field to previous valid value
*/
revertFieldValue(element, name) {
const previousValue = this.values[name];
const param = this.params[name];
switch (param.type) {
case 'checkbox':
element.checked = Boolean(previousValue);
break;
case 'select':
element.value = previousValue;
break;
default:
element.value = previousValue || '';
}
}
/**
* Update UI elements with current values
*/
updateUI() {
try {
const context = this.shadowRoot || (this.iframeFallback && this.iframeFallback.contentDocument);
if (!context) return;
for (const [name, value] of Object.entries(this.values)) {
const param = this.params[name];
if (!param) continue;
this.updateFieldValue(context, name, param, value);
}
} catch (error) {
this.log(`Error updating UI: ${error.message}`, 'error');
}
}
/**
* Update individual field value in UI
*/
updateFieldValue(context, name, param, value) {
const element = context.querySelector(`[name="${name}"]`);
if (!element) return;
switch (param.type) {
case 'checkbox':
element.checked = Boolean(value);
break;
case 'select':
this.updateSelectValue(element, param, value);
break;
case 'radio':
this.updateRadioValue(context, name, value);
break;
case 'range':
this.updateRangeValue(element, value);
break;
case 'custom':
this.updateCustomValue(element, param, value);
break;
default:
element.value = value || '';
}
}
/**
* Update select field value
*/
updateSelectValue(element, param, value) {
if (param.multiple && Array.isArray(value)) {
Array.from(element.options).forEach(option => {
option.selected = value.includes(option.value);
});
} else {
element.value = value;
}
}
/**
* Update radio button value
*/
updateRadioValue(context, name, value) {
const radioButton = context.querySelector(`[name="${name}"][value="${value}"]`);
if (radioButton) radioButton.checked = true;
}
/**
* Update range slider value and display
*/
updateRangeValue(element, value) {
element.value = value;
const rangeValue = element.parentNode.querySelector('.__MonkeyConfig_range_value');
if (rangeValue) rangeValue.textContent = value;
}
/**
* Update custom field value
*/
updateCustomValue(element, param, value) {
if (param.set && typeof param.set === 'function') {
param.set(element, value);
} else {
element.value = value;
}
}
// ===========================================
// UI RENDERING IMPLEMENTATION
// ===========================================
/**
* Render main HTML content
*/
render() {
try {
const title = this.escapeHtml(this.data.title);
let html = `
<div class="__MonkeyConfig_container">
${this.renderHeader(title)}
${this.renderContent()}
${this.renderFooter()}
</div>`;
return this.createTrustedHTML(html);
} catch (error) {
this.log(`Failed to render HTML: ${error.message}`, 'error');
return this.createTrustedHTML('<div class="__MonkeyConfig_error">Error rendering configuration dialog</div>');
}
}
/**
* Render dialog header
*/
renderHeader(title) {
return `
<div class="__MonkeyConfig_header">
<h1>${title}</h1>
<button type="button" id="__MonkeyConfig_button_close" class="__MonkeyConfig_close_btn" aria-label="Close">
${this.getIcon('close')}
</button>
</div>`;
}
/**
* Render main content area
*/
renderContent() {
return `
<div class="__MonkeyConfig_content">
<div class="__MonkeyConfig_sections">
${this.renderSections()}
</div>
</div>`;
}
/**
* Render all sections
*/
renderSections() {
let html = '';
html += this.renderSection('top');
html += this.renderColumns('top');
html += this.renderColumns('middle');
html += this.renderDefaultSection();
html += this.renderColumns('bottom');
html += this.renderSection('bottom');
return html;
}
/**
* Render positioned section
*/
renderSection(position) {
const items = Object.entries(this.params)
.filter(([, param]) => param.column === position)
.map(([key, param]) => this.renderField(key, param))
.join('');
return items ? `<div class="__MonkeyConfig_section_${position}">${items}</div>` : '';
}
/**
* Render column layout
*/
renderColumns(position) {
const leftColumn = position !== 'middle' ? `left&${position}` : 'left';
const rightColumn = position !== 'middle' ? `right&${position}` : 'right';
const leftItems = this.getColumnItems(leftColumn);
const rightItems = this.getColumnItems(rightColumn);
if (!leftItems && !rightItems) return '';
return `
<div class="__MonkeyConfig_columns">
<div class="__MonkeyConfig_left_column">${leftItems}</div>
<div class="__MonkeyConfig_right_column">${rightItems}</div>
</div>`;
}
/**
* Get items for specific column
*/
getColumnItems(column) {
return Object.entries(this.params)
.filter(([, param]) => param.column === column)
.map(([key, param]) => this.renderField(key, param))
.join('');
}
/**
* Render default section (table layout)
*/
renderDefaultSection() {
const items = Object.entries(this.params)
.filter(([, param]) => !param.column)
.map(([key, param]) => this.renderField(key, param))
.join('');
return items ? `<table class="__MonkeyConfig_default_table">${items}</table>` : '';
}
/**
* Render individual field
*/
renderField(name, param) {
const fieldId = `__MonkeyConfig_field_${this.escapeHtml(name)}`;
const parentId = `__MonkeyConfig_parent_${this.escapeHtml(name)}`;
const label = this.renderLabel(name, param, fieldId);
const field = this.renderInput(name, param, fieldId);
if (param.type === 'group') {
return `<tr><td colspan="2">${field}</td></tr>`;
}
const isInline = ['checkbox', 'number', 'text'].includes(param.type);
if (isInline) {
return `
<tr>
<td id="${parentId}" colspan="2" class="__MonkeyConfig_inline">
${label}${field}
</td>
</tr>`;
}
return `
<tr>
<td class="__MonkeyConfig_label_cell">${label}</td>
<td id="${parentId}" class="__MonkeyConfig_field_cell">${field}</td>
</tr>`;
}
/**
* Render field label
*/
renderLabel(name, param, fieldId) {
const labelText = param.label ||
name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, ' ');
const styles = this.getLabelStyles(param);
const styleAttr = styles.length ? ` style="${styles.join(';')}"` : '';
return `<label for="${fieldId}"${styleAttr}>${this.escapeHtml(labelText)}</label>`;
}
/**
* Get label styling
*/
getLabelStyles(param) {
const styles = [];
if (param.fontSize) styles.push(`font-size:${this.escapeHtml(param.fontSize)}`);
if (param.fontColor) styles.push(`color:${this.escapeHtml(param.fontColor)}`);
if (param.labelAlign) styles.push(`text-align:${this.escapeHtml(param.labelAlign)}`);
return styles;
}
/**
* Render input field based on type
*/
renderInput(name, param, fieldId) {
const inputName = this.escapeHtml(name);
const value = this.values[name];
const renderers = {
'checkbox': () => this.renderCheckbox(fieldId, inputName, value),
'number': () => this.renderNumber(fieldId, inputName, param, value),
'text': () => this.renderText(fieldId, inputName, value),
'color': () => this.renderColor(fieldId, inputName, value),
'textarea': () => this.renderTextarea(fieldId, inputName, param, value),
'range': () => this.renderRange(fieldId, inputName, param, value),
'select': () => this.renderSelect(fieldId, inputName, param, value),
'radio': () => this.renderRadio(fieldId, inputName, param, value),
'file': () => this.renderFile(fieldId, inputName, param),
'button': () => this.renderButton(fieldId, inputName, param),
'group': () => this.renderGroup(fieldId, inputName, param),
'custom': () => this.renderCustom(fieldId, inputName, param)
};
const renderer = renderers[param.type];
return renderer ? renderer() : this.renderError(param.type);
}
/**
* Render checkbox input
*/
renderCheckbox(fieldId, inputName, value) {
return `<input type="checkbox" id="${fieldId}" name="${inputName}" ${value ? 'checked' : ''}>`;
}
/**
* Render number input
*/
renderNumber(fieldId, inputName, param, value) {
const attributes = [];
if (param.min !== undefined) attributes.push(`min="${param.min}"`);
if (param.max !== undefined) attributes.push(`max="${param.max}"`);
if (param.step !== undefined) attributes.push(`step="${param.step}"`);
return `<input type="number" id="${fieldId}" name="${inputName}"
value="${this.escapeHtml(value || '')}" ${attributes.join(' ')}>`;
}
/**
* Render text input
*/
renderText(fieldId, inputName, value) {
return `<input type="text" id="${fieldId}" name="${inputName}"
value="${this.escapeHtml(value || '')}">`;
}
/**
* Render color input
*/
renderColor(fieldId, inputName, value) {
return `<input type="color" id="${fieldId}" name="${inputName}"
value="${this.escapeHtml(value || '#000000')}">`;
}
/**
* Render textarea
*/
renderTextarea(fieldId, inputName, param, value) {
return `<textarea id="${fieldId}" name="${inputName}"
rows="${param.rows || 4}" cols="${param.cols || 20}">${this.escapeHtml(value || '')}</textarea>`;
}
/**
* Render range input
*/
renderRange(fieldId, inputName, param, value) {
const currentValue = value || param.min || 0;
return `<input type="range" id="${fieldId}" name="${inputName}"
value="${currentValue}" min="${param.min || 0}" max="${param.max || 100}"
step="${param.step || 1}">
<span class="__MonkeyConfig_range_value">${currentValue}</span>`;
}
/**
* Render select dropdown
*/
renderSelect(fieldId, inputName, param, value) {
const multipleAttr = param.multiple ? ' multiple' : '';
const selectedValues = Array.isArray(value) ? value : [value];
let options = '';
if (param.choices) {
for (const [key, label] of Object.entries(param.choices)) {
const selected = selectedValues.includes(key) ? ' selected' : '';
options += `<option value="${this.escapeHtml(key)}"${selected}>${this.escapeHtml(label)}</option>`;
}
}
return `<select id="${fieldId}" name="${inputName}"${multipleAttr}>${options}</select>`;
}
/**
* Render radio buttons
*/
renderRadio(fieldId, inputName, param, value) {
let radioHtml = '<div class="__MonkeyConfig_radio_group">';
if (param.choices) {
for (const [key, label] of Object.entries(param.choices)) {
const checked = value === key ? ' checked' : '';
const radioId = `${fieldId}_${key}`;
radioHtml += `
<label class="__MonkeyConfig_radio_label">
<input type="radio" id="${radioId}" name="${inputName}"
value="${this.escapeHtml(key)}"${checked}>
${this.escapeHtml(label)}
</label>`;
}
}
radioHtml += '</div>';
return radioHtml;
}
/**
* Render file input
*/
renderFile(fieldId, inputName, param) {
return `<input type="file" id="${fieldId}" name="${inputName}"
accept="${this.escapeHtml(param.accept || '*/*')}">`;
}
/**
* Render button
*/
renderButton(fieldId, inputName, param) {
return `<button type="button" id="${fieldId}" name="${inputName}"
class="__MonkeyConfig_custom_button">${this.escapeHtml(param.label || 'Button')}</button>`;
}
/**
* Render group fieldset
*/
renderGroup(fieldId, inputName, param) {
const legend = param.label ? `<legend>${this.escapeHtml(param.label)}</legend>` : '';
return `<fieldset id="${fieldId}" class="__MonkeyConfig_group">${legend}</fieldset>`;
}
/**
* Render custom field
*/
renderCustom(fieldId, inputName, param) {
const customHtml = param.html || '';
return `<div id="${fieldId}" class="__MonkeyConfig_custom" data-name="${inputName}">${customHtml}</div>`;
}
/**
* Render error message
*/
renderError(type) {
return `<span class="__MonkeyConfig_error">Unknown field type: ${this.escapeHtml(type)}</span>`;
}
/**
* Render footer with buttons
*/
renderFooter() {
return `
<div class="__MonkeyConfig_footer">
${this.renderButtons()}
</div>`;
}
/**
* Render action buttons
*/
renderButtons() {
let buttonsHtml = '';
for (const buttonType of this.data.buttons) {
if (buttonType === 'close') continue; // Close button is in header
const buttonId = `__MonkeyConfig_button_${buttonType}`;
const buttonText = this.getButtonText(buttonType);
const icon = this.getIcon(buttonType);
buttonsHtml += `
<button type="button" id="${buttonId}" class="__MonkeyConfig_btn __MonkeyConfig_btn_${buttonType}">
${icon}
<span>${buttonText}</span>
</button>`;
}
return buttonsHtml;
}
/**
* Get icon SVG for button type
*/
getIcon(type) {
const icons = {
save: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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"><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"><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"><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>',
homepage: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>'
};
return icons[type] || '';
}
/**
* Get button text
*/
getButtonText(buttonType) {
const texts = {
save: 'Save Without Reload',
reset: 'Reset',
reload: 'Save With Reload',
homepage: 'Homepage'
};
return texts[buttonType] || buttonType;
}
/**
* Get complete CSS styles
*/
getCSS() {
return `
<style>
.__MonkeyConfig_container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: ${this.shadowWidth};
max-width: 90vw;
height: ${this.shadowHeight};
max-height: 90vh;
background: #ffffff;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: ${this.shadowFontSize};
color: ${this.shadowFontColor};
z-index: 2147483647;
display: flex;
flex-direction: column;
overflow: hidden;
animation: __MonkeyConfig_fadeIn 0.3s ease-out;
}
.__MonkeyConfig_header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 8px 8px 0 0;
}
.__MonkeyConfig_header h1 {
margin: 0;
font-size: 18px;
font-weight: 600;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.__MonkeyConfig_close_btn {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
width: 32px;
height: 32px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.__MonkeyConfig_close_btn:hover {
background: rgba(255, 255, 255, 0.2);
transform: scale(1.1);
}
.__MonkeyConfig_content {
flex: 1;
padding: 20px;
overflow-y: auto;
overflow-x: hidden;
}
.__MonkeyConfig_sections {
display: flex;
flex-direction: column;
gap: 20px;
}
.__MonkeyConfig_columns {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.__MonkeyConfig_default_table {
width: 100%;
border-collapse: collapse;
}
.__MonkeyConfig_default_table tr {
border-bottom: 1px solid #f0f0f0;
}
.__MonkeyConfig_default_table td {
padding: 12px 8px;
vertical-align: top;
}
.__MonkeyConfig_label_cell {
width: 30%;
font-weight: 500;
color: #333;
}
.__MonkeyConfig_field_cell {
width: 70%;
}
.__MonkeyConfig_inline {
display: flex;
align-items: center;
gap: 12px;
}
.__MonkeyConfig_inline label {
margin: 0;
font-weight: 500;
}
/* Form Controls */
.__MonkeyConfig_container input,
.__MonkeyConfig_container textarea,
.__MonkeyConfig_container select {
border: 2px solid #e1e5e9;
border-radius: 6px;
padding: 8px 12px;
font-size: 14px;
transition: all 0.2s ease;
background: white;
}
.__MonkeyConfig_container input:focus,
.__MonkeyConfig_container textarea:focus,
.__MonkeyConfig_container select:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.__MonkeyConfig_container input[type="checkbox"] {
width: 18px;
height: 18px;
margin: 0;
cursor: pointer;
}
.__MonkeyConfig_container input[type="color"] {
width: 50px;
height: 40px;
padding: 2px;
cursor: pointer;
}
.__MonkeyConfig_container input[type="range"] {
width: 150px;
margin-right: 10px;
}
.__MonkeyConfig_range_value {
display: inline-block;
min-width: 40px;
text-align: center;
font-weight: 500;
color: #667eea;
}
.__MonkeyConfig_radio_group {
display: flex;
flex-direction: column;
gap: 8px;
}
.__MonkeyConfig_radio_label {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 4px 0;
}
.__MonkeyConfig_radio_label input[type="radio"] {
margin: 0;
}
.__MonkeyConfig_group {
border: 2px solid #e1e5e9;
border-radius: 6px;
padding: 16px;
margin: 8px 0;
}
.__MonkeyConfig_group legend {
font-weight: 600;
color: #333;
padding: 0 8px;
}
.__MonkeyConfig_custom {
padding: 8px 0;
}
.__MonkeyConfig_custom_button {
background: #f8f9fa;
border: 2px solid #e1e5e9;
color: #333;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.__MonkeyConfig_custom_button:hover {
background: #e9ecef;
border-color: #667eea;
}
/* Footer */
.__MonkeyConfig_footer {
padding: 16px 20px;
background: #f8f9fa;
border-top: 1px solid #e1e5e9;
display: flex;
gap: 12px;
justify-content: flex-end;
flex-wrap: wrap;
}
.__MonkeyConfig_btn {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
border: 2px solid transparent;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
}
.__MonkeyConfig_btn_save {
background: #28a745;
color: white;
border-color: #28a745;
}
.__MonkeyConfig_btn_save:hover {
background: #218838;
transform: translateY(-1px);
}
.__MonkeyConfig_btn_reset {
background: #dc3545;
color: white;
border-color: #dc3545;
}
.__MonkeyConfig_btn_reset:hover {
background: #c82333;
transform: translateY(-1px);
}
.__MonkeyConfig_btn_reload {
background: #007bff;
color: white;
border-color: #007bff;
}
.__MonkeyConfig_btn_reload:hover {
background: #0056b3;
transform: translateY(-1px);
}
.__MonkeyConfig_btn_homepage {
background: #6c757d;
color: white;
border-color: #6c757d;
}
.__MonkeyConfig_btn_homepage:hover {
background: #545b62;
transform: translateY(-1px);
}
.__MonkeyConfig_error {
color: #dc3545;
font-weight: 500;
padding: 8px;
background: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 4px;
}
/* Responsive Design */
@media (max-width: 768px) {
.__MonkeyConfig_container {
width: 95vw;
height: 95vh;
margin: 0;
transform: translate(-50%, -50%);
}
.__MonkeyConfig_columns {
grid-template-columns: 1fr;
}
.__MonkeyConfig_footer {
flex-direction: column;
}
.__MonkeyConfig_btn {
justify-content: center;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
.__MonkeyConfig_container {
background: #1a1a1a;
border-color: #444;
color: #e0e0e0;
}
.__MonkeyConfig_default_table tr {
border-color: #333;
}
.__MonkeyConfig_container input,
.__MonkeyConfig_container textarea,
.__MonkeyConfig_container select {
background: #2a2a2a;
border-color: #444;
color: #e0e0e0;
}
.__MonkeyConfig_footer {
background: #2a2a2a;
border-color: #444;
}
.__MonkeyConfig_group {
border-color: #444;
}
.__MonkeyConfig_custom_button {
background: #2a2a2a;
border-color: #444;
color: #e0e0e0;
}
}
/* Animation */
@keyframes __MonkeyConfig_fadeIn {
from {
opacity: 0;
transform: translate(-50%, -50%) scale(0.9);
}
to {
opacity: 1;
transform: translate(-50%, -50%) scale(1);
}
}
</style>`;
}
/**
* Clean up all resources
*/
destroy() {
try {
this.close();
this.data = null;
this.params = null;
this.values = null;
this.validationRules.clear();
this.trustedPolicy = null;
this.log('ModernMonkeyConfig destroyed');
} catch (error) {
this.log(`Error during cleanup: ${error.message}`, 'error');
}
}
}
// ===========================================
// FACTORY FUNCTION AND EXPORTS
// ===========================================
/**
* Factory function for creating configuration dialogs
*/
function createMonkeyConfig(configData) {
try {
return new ModernMonkeyConfig(configData);
} catch (error) {
console.error(`[ModernMonkeyConfig] Failed to create configuration: ${error.message}`);
return null;
}
}
// Export for different environments
if (typeof module !== 'undefined' && module.exports) {
module.exports = ModernMonkeyConfig;
} else if (typeof window !== 'undefined') {
window.ModernMonkeyConfig = ModernMonkeyConfig;
window.createMonkeyConfig = createMonkeyConfig;
}
/**
* Example usage:
*
* const config = createMonkeyConfig({
* title: 'My Script Configuration',
* menuCommand: true,
* buttons: ['save', 'reset', 'close', 'reload'],
* parameters: {
* enabled: {
* type: 'checkbox',
* label: 'Enable Script',
* default: true
* },
* maxItems: {
* type: 'number',
* label: 'Maximum Items',
* default: 10,
* min: 1,
* max: 100
* },
* theme: {
* type: 'select',
* label: 'Theme',
* choices: {
* 'light': 'Light Theme',
* 'dark': 'Dark Theme',
* 'auto': 'Auto Detect'
* },
* default: 'auto'
* },
* opacity: {
* type: 'range',
* label: 'Opacity',
* min: 0.1,
* max: 1.0,
* step: 0.1,
* default: 0.8
* },
* description: {
* type: 'textarea',
* label: 'Description',
* rows: 4,
* cols: 40,
* default: ''
* }
* },
* onSave: (values) => {
* console.log('Settings saved:', values);
* // Apply your settings here
* }
* });
*
* // Usage:
* config.open(); // Open configuration dialog
* const enabled = config.get('enabled'); // Get parameter value
* config.set('enabled', false); // Set parameter value
* config.reset(); // Reset to defaults
* config.destroy(); // Clean up resources
*/