您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
密码输入框内容可聚焦即显 / 悬浮即览 / 双击切换 / 始终可见 | 通过菜单或快捷键(Meta/Ctrl+Alt+P)切换显示模式
// ==UserScript== // @name Password Revealer // @name:zh-CN 密码显示助手 // @name:zh-TW 密碼顯示助手 // @description Password Field Content Via - Reveal On Focus / Preview On Hover / Toggle On Double-Click / Always Visible | Switch Display Mode Via Menu Or Shortcut (Meta/Ctrl+Alt+P) // @description:zh-CN 密码输入框内容可聚焦即显 / 悬浮即览 / 双击切换 / 始终可见 | 通过菜单或快捷键(Meta/Ctrl+Alt+P)切换显示模式 // @description:zh-TW 密碼輸入框內容可聚焦即顯 / 懸停即覽 / 雙擊切換 / 始終可見 | 透過選單或快速鍵(Meta/Ctrl+Alt+P)切換顯示模式 // @version 1.5.0 // @icon https://raw.githubusercontent.com/MiPoNianYou/UserScripts/main/Icons/Password-Revealer-Icon.svg // @author 念柚 // @namespace https://github.com/MiPoNianYou/UserScripts // @supportURL https://github.com/MiPoNianYou/UserScripts/issues // @license GPL-3.0 // @match *://*/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_addStyle // @run-at document-idle // ==/UserScript== (function () { "use strict"; const Config = { SCRIPT_SETTINGS: { UI_FONT_STACK: "-apple-system, BlinkMacSystemFont, system-ui, sans-serif", ANIMATION_DURATION_MS: 300, NOTIFICATION_VISIBILITY_DURATION_MS: 2000, }, MODES: { FOCUS: "Focus", HOVER: "Hover", DBLCLICK: "DoubleClick", ALWAYS_SHOW: "AlwaysShow", }, get VALID_MODES() { return [ this.MODES.FOCUS, this.MODES.HOVER, this.MODES.DBLCLICK, this.MODES.ALWAYS_SHOW, ]; }, ELEMENT_IDS: { MODE_NOTIFICATION: "PasswordRevealerModeNotification", }, CSS_CLASSES: { MODE_NOTIFICATION_VISIBLE: "pr-mode-notification--visible", BREATHING_DOT: "pr-breathing-dot", MODE_NOTIFICATION_MESSAGE: "pr-mode-notification-message", }, ATTRIBUTES: { PROCESSED: "data-password-revealer-processed", }, UI_TEXTS: { "zh-CN": { SCRIPT_TITLE: "密码显示助手", MENU_CMD_FOCUS: "「聚焦即显」模式", MENU_CMD_HOVER: "「悬浮即览」模式", MENU_CMD_DBLCLICK: "「双击切换」模式", MENU_CMD_ALWAYS_SHOW: "「始终可见」模式", ALERT_MSG_FOCUS: "模式已切换为「聚焦即显」", ALERT_MSG_HOVER: "模式已切换为「悬浮即览」", ALERT_MSG_DBLCLICK: "模式已切换为「双击切换」", ALERT_MSG_ALWAYS_SHOW: "模式已切换为「始终可见」", }, "zh-TW": { SCRIPT_TITLE: "密碼顯示助手", MENU_CMD_FOCUS: "「聚焦即顯」模式", MENU_CMD_HOVER: "「懸停即覽」模式", MENU_CMD_DBLCLICK: "「雙擊切換」模式", MENU_CMD_ALWAYS_SHOW: "「始終可見」模式", ALERT_MSG_FOCUS: "模式已切換為「聚焦即顯」", ALERT_MSG_HOVER: "模式已切換為「懸停即覽」", ALERT_MSG_DBLCLICK: "模式已切換為「雙擊切換」", ALERT_MSG_ALWAYS_SHOW: "模式已切換為「始終可見」", }, "en-US": { SCRIPT_TITLE: "Password Revealer", MENU_CMD_FOCUS: "「Reveal On Focus」Mode", MENU_CMD_HOVER: "「Preview On Hover」Mode", MENU_CMD_DBLCLICK: "「Toggle On Double-Click」Mode", MENU_CMD_ALWAYS_SHOW: "「Always Visible」Mode", ALERT_MSG_FOCUS: "Mode Switched To 「Reveal On Focus」", ALERT_MSG_HOVER: "Mode Switched To 「Preview On Hover」", ALERT_MSG_DBLCLICK: "Mode Switched To 「Toggle On Double-Click」", ALERT_MSG_ALWAYS_SHOW: "Mode Switched To 「Always Visible」", }, }, MODE_TO_MENU_TEXT_KEY_MAP: { ["Focus"]: "MENU_CMD_FOCUS", ["Hover"]: "MENU_CMD_HOVER", ["DoubleClick"]: "MENU_CMD_DBLCLICK", ["AlwaysShow"]: "MENU_CMD_ALWAYS_SHOW", }, STORAGE_KEYS: { MODE_KEY: "PasswordDisplayMode", }, MODE_TO_ALERT_MESSAGE_KEY_MAP: { ["Focus"]: "ALERT_MSG_FOCUS", ["Hover"]: "ALERT_MSG_HOVER", ["DoubleClick"]: "ALERT_MSG_DBLCLICK", ["AlwaysShow"]: "ALERT_MSG_ALWAYS_SHOW", }, }; const State = { currentMode: Config.MODES.FOCUS, currentLocale: "en-US", localizedStrings: Config.UI_TEXTS["en-US"], loadAndSetInitialState() { this.currentLocale = this.detectUserLanguage(); this.localizedStrings = Config.UI_TEXTS[this.currentLocale] || Config.UI_TEXTS["en-US"]; this.loadDisplayMode(); }, detectUserLanguage() { const languages = navigator.languages || [navigator.language]; for (const lang of languages) { const langLower = lang.toLowerCase(); if (langLower === "zh-cn") return "zh-CN"; if ( langLower === "zh-tw" || langLower === "zh-hk" || langLower === "zh-mo" || langLower === "zh-hant" ) return "zh-TW"; if (langLower === "en-us") return "en-US"; if (langLower.startsWith("zh-")) return "zh-CN"; if (langLower.startsWith("en-")) return "en-US"; } for (const lang of languages) { const langLower = lang.toLowerCase(); if (langLower.startsWith("zh")) return "zh-CN"; if (langLower.startsWith("en")) return "en-US"; } return "en-US"; }, getLocalizedString(key, fallbackLang = "en-US") { const primaryLangData = this.localizedStrings || Config.UI_TEXTS[fallbackLang]; const fallbackLangData = Config.UI_TEXTS[fallbackLang]; return primaryLangData[key] ?? fallbackLangData[key] ?? `${key}?`; }, loadDisplayMode() { let storedValue; try { storedValue = GM_getValue( Config.STORAGE_KEYS.MODE_KEY, Config.MODES.FOCUS ); } catch (e) { storedValue = Config.MODES.FOCUS; } if (!Config.VALID_MODES.includes(storedValue)) { storedValue = Config.MODES.FOCUS; } this.currentMode = storedValue; }, saveDisplayMode() { try { GM_setValue(Config.STORAGE_KEYS.MODE_KEY, this.currentMode); } catch (e) {} }, setMode(newMode) { if ( this.currentMode === newMode || !Config.VALID_MODES.includes(newMode) ) { return false; } this.currentMode = newMode; this.saveDisplayMode(); return true; }, }; const UserInterface = { notificationTimer: null, notificationRemovalTimer: null, registeredMenuCommandIds: [], injectCoreStyles() { const easeOutQuint = "cubic-bezier(0.23, 1, 0.32, 1)"; const animationDuration = Config.SCRIPT_SETTINGS.ANIMATION_DURATION_MS; const baseCSS = ` :root { --ctp-frappe-rosewater: rgb(242, 213, 207); --ctp-frappe-flamingo: rgb(238, 190, 190); --ctp-frappe-pink: rgb(244, 184, 228); --ctp-frappe-mauve: rgb(202, 158, 230); --ctp-frappe-red: rgb(231, 130, 132); --ctp-frappe-maroon: rgb(234, 153, 156); --ctp-frappe-peach: rgb(239, 159, 118); --ctp-frappe-yellow: rgb(229, 200, 144); --ctp-frappe-green: rgb(166, 209, 137); --ctp-frappe-teal: rgb(129, 200, 190); --ctp-frappe-sky: rgb(153, 209, 219); --ctp-frappe-sapphire: rgb(133, 193, 220); --ctp-frappe-blue: rgb(140, 170, 238); --ctp-frappe-lavender: rgb(186, 187, 241); --ctp-frappe-text: rgb(198, 208, 245); --ctp-frappe-subtext1: rgb(181, 191, 226); --ctp-frappe-subtext0: rgb(165, 173, 206); --ctp-frappe-overlay2: rgb(148, 156, 187); --ctp-frappe-overlay1: rgb(131, 139, 167); --ctp-frappe-overlay0: rgb(115, 121, 148); --ctp-frappe-surface2: rgb(98, 104, 128); --ctp-frappe-surface1: rgb(81, 87, 109); --ctp-frappe-surface0: rgb(65, 69, 89); --ctp-frappe-base: rgb(48, 52, 70); --ctp-frappe-mantle: rgb(41, 44, 60); --ctp-frappe-crust: rgb(35, 38, 52); --ctp-latte-rosewater: rgb(220, 138, 120); --ctp-latte-flamingo: rgb(221, 120, 120); --ctp-latte-pink: rgb(234, 118, 203); --ctp-latte-mauve: rgb(136, 57, 239); --ctp-latte-red: rgb(210, 15, 57); --ctp-latte-maroon: rgb(230, 69, 83); --ctp-latte-peach: rgb(254, 100, 11); --ctp-latte-yellow: rgb(223, 142, 29); --ctp-latte-green: rgb(64, 160, 43); --ctp-latte-teal: rgb(23, 146, 153); --ctp-latte-sky: rgb(4, 165, 229); --ctp-latte-sapphire: rgb(32, 159, 181); --ctp-latte-blue: rgb(30, 102, 245); --ctp-latte-lavender: rgb(114, 135, 253); --ctp-latte-text: rgb(76, 79, 105); --ctp-latte-subtext1: rgb(92, 95, 119); --ctp-latte-subtext0: rgb(108, 111, 133); --ctp-latte-overlay2: rgb(124, 127, 147); --ctp-latte-overlay1: rgb(140, 143, 161); --ctp-latte-overlay0: rgb(156, 160, 176); --ctp-latte-surface2: rgb(172, 176, 190); --ctp-latte-surface1: rgb(188, 192, 204); --ctp-latte-surface0: rgb(204, 208, 218); --ctp-latte-base: rgb(239, 241, 245); --ctp-latte-mantle: rgb(230, 233, 239); --ctp-latte-crust: rgb(220, 224, 232); --pr-notify-bg-dark: rgb(from var(--ctp-frappe-base) r g b / 0.85); --pr-notify-text-dark: var(--ctp-frappe-text); --pr-notify-border-dark: rgb(from var(--ctp-frappe-surface2) r g b / 0.25); --pr-notify-dot-color-dark: var(--ctp-frappe-green); /* Renamed from --ctp-frappe-green for clarity */ --pr-notify-dot-glow-dark: rgb(from var(--ctp-frappe-green) r g b / 0.35); /* Glow for dot */ --pr-notify-bg-light: rgb(from var(--ctp-latte-base) r g b / 0.85); --pr-notify-text-light: var(--ctp-latte-text); --pr-notify-border-light: rgb(from var(--ctp-latte-surface2) r g b / 0.25); --pr-notify-dot-color-light: var(--ctp-latte-green); /* Renamed from --ctp-latte-green for clarity */ --pr-notify-dot-glow-light: rgb(from var(--ctp-latte-green) r g b / 0.35); /* Glow for dot */ --pr-shadow-dark: 0 1px 2px rgba(0, 0, 0, 0.1), 0 6px 12px rgba(0, 0, 0, 0.2); --pr-shadow-light: 0 1px 2px rgba(90, 90, 90, 0.06), 0 6px 12px rgba(90, 90, 90, 0.12); } @keyframes pr-breathing-animation { 0%, 100% { transform: scale(0.85); opacity: 0.7; } 50% { transform: scale(1); opacity: 1; } } #${Config.ELEMENT_IDS.MODE_NOTIFICATION} { position: fixed; bottom: 20px; left: 50%; z-index: 2147483646; display: flex; align-items: center; padding: 10px 16px; border: 1px solid var(--pr-notify-border-dark); border-radius: 20px; background-color: var(--pr-notify-bg-dark); color: var(--pr-notify-text-dark); box-shadow: var(--pr-shadow-dark); box-sizing: border-box; opacity: 0; font-family: ${Config.SCRIPT_SETTINGS.UI_FONT_STACK}; text-align: left; backdrop-filter: blur(16px) saturate(180%); -webkit-backdrop-filter: blur(16px) saturate(180%); transform: translate(-50%, calc(100% + 40px)); transition: transform ${animationDuration}ms ${easeOutQuint}, opacity ${animationDuration * 0.8}ms ${easeOutQuint}; } #${Config.ELEMENT_IDS.MODE_NOTIFICATION}.${ Config.CSS_CLASSES.MODE_NOTIFICATION_VISIBLE } { transform: translate(-50%, 0); opacity: 1; } #${Config.ELEMENT_IDS.MODE_NOTIFICATION} .${ Config.CSS_CLASSES.BREATHING_DOT } { width: 8px; height: 8px; margin-right: 10px; border-radius: 50%; background-color: var(--pr-notify-dot-color-dark); box-shadow: 0 0 8px 3px var(--pr-notify-dot-glow-dark); /* Added glow */ flex-shrink: 0; animation: pr-breathing-animation 2000ms ease-in-out infinite; /* No transition needed here as dot color doesn't change based on state for PR */ } #${Config.ELEMENT_IDS.MODE_NOTIFICATION} .${ Config.CSS_CLASSES.MODE_NOTIFICATION_MESSAGE } { color: var(--pr-notify-text-dark); font-size: 13px; font-weight: 500; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } @media (prefers-color-scheme: light) { #${Config.ELEMENT_IDS.MODE_NOTIFICATION} { border: 1px solid var(--pr-notify-border-light); background-color: var(--pr-notify-bg-light); color: var(--pr-notify-text-light); box-shadow: var(--pr-shadow-light); } #${Config.ELEMENT_IDS.MODE_NOTIFICATION} .${ Config.CSS_CLASSES.BREATHING_DOT } { background-color: var(--pr-notify-dot-color-light); box-shadow: 0 0 8px 3px var(--pr-notify-dot-glow-light); /* Added glow for light mode */ } #${Config.ELEMENT_IDS.MODE_NOTIFICATION} .${ Config.CSS_CLASSES.MODE_NOTIFICATION_MESSAGE } { color: var(--pr-notify-text-light); } } `; try { GM_addStyle(baseCSS); } catch (e) {} }, displayModeNotification(messageKey) { if (this.notificationTimer) clearTimeout(this.notificationTimer); if (this.notificationRemovalTimer) clearTimeout(this.notificationRemovalTimer); this.notificationTimer = null; this.notificationRemovalTimer = null; const message = State.getLocalizedString(messageKey) || messageKey; const renderNotification = () => { let notificationElement = document.getElementById( Config.ELEMENT_IDS.MODE_NOTIFICATION ); if (!notificationElement && document.body) { notificationElement = document.createElement("div"); notificationElement.id = Config.ELEMENT_IDS.MODE_NOTIFICATION; notificationElement.innerHTML = ` <div class="${Config.CSS_CLASSES.BREATHING_DOT}"></div> <div class="${Config.CSS_CLASSES.MODE_NOTIFICATION_MESSAGE}"></div> `.trim(); document.body.appendChild(notificationElement); } else if (!notificationElement) { return; } const messageElement = notificationElement.querySelector( `.${Config.CSS_CLASSES.MODE_NOTIFICATION_MESSAGE}` ); if (messageElement) messageElement.textContent = message; // PR script's dot color does not change based on a disabled class, // so no need to add/remove a .disabled class here for the dot. // The glow is always based on the primary dot color (green). notificationElement.classList.remove( Config.CSS_CLASSES.MODE_NOTIFICATION_VISIBLE ); void notificationElement.offsetWidth; requestAnimationFrame(() => { const currentElement = document.getElementById( Config.ELEMENT_IDS.MODE_NOTIFICATION ); if (currentElement) { currentElement.classList.add( Config.CSS_CLASSES.MODE_NOTIFICATION_VISIBLE ); } }); this.notificationTimer = setTimeout(() => { const currentElement = document.getElementById( Config.ELEMENT_IDS.MODE_NOTIFICATION ); if (currentElement) { currentElement.classList.remove( Config.CSS_CLASSES.MODE_NOTIFICATION_VISIBLE ); this.notificationRemovalTimer = setTimeout(() => { document .getElementById(Config.ELEMENT_IDS.MODE_NOTIFICATION) ?.remove(); this.notificationTimer = null; this.notificationRemovalTimer = null; }, Config.SCRIPT_SETTINGS.ANIMATION_DURATION_MS); } else { this.notificationTimer = null; this.notificationRemovalTimer = null; } }, Config.SCRIPT_SETTINGS.NOTIFICATION_VISIBILITY_DURATION_MS); }; renderNotification(); }, updateUserScriptMenuCommands() { this.registeredMenuCommandIds.forEach((id) => { try { GM_unregisterMenuCommand(id); } catch (e) {} }); this.registeredMenuCommandIds = []; Config.VALID_MODES.forEach((mode) => { const menuKey = Config.MODE_TO_MENU_TEXT_KEY_MAP[mode]; const baseText = State.getLocalizedString(menuKey); const commandText = baseText + (mode === State.currentMode ? " ✅" : ""); try { const commandId = GM_registerMenuCommand(commandText, () => ScriptManager.setModeAndUpdate(mode) ); this.registeredMenuCommandIds.push(commandId); } catch (e) {} }); }, }; const InputManager = { processPasswordInput(input, mode) { if ( !(input instanceof HTMLInputElement) || input.type === "hidden" || input.getAttribute(Config.ATTRIBUTES.PROCESSED) === mode ) { return; } if (mode === Config.MODES.ALWAYS_SHOW) { input.type = "text"; } else { if (input.type !== "password") { input.type = "password"; } } input.setAttribute(Config.ATTRIBUTES.PROCESSED, mode); }, findAndProcessNewInputs(rootNode, mode) { if (!rootNode || typeof rootNode.querySelectorAll !== "function") return; try { const query = `input[type="password"]:not([${Config.ATTRIBUTES.PROCESSED}="${mode}"]), input[${Config.ATTRIBUTES.PROCESSED}]:not([${Config.ATTRIBUTES.PROCESSED}="${mode}"])`; rootNode.querySelectorAll(query).forEach((input) => { this.processPasswordInput(input, mode); }); const elementsToCheckForShadow = rootNode === document || rootNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE ? rootNode.querySelectorAll("*") : [rootNode]; elementsToCheckForShadow.forEach((el) => { if ( el.shadowRoot && typeof el.shadowRoot.querySelectorAll === "function" ) { this.findAndProcessNewInputs(el.shadowRoot, mode); } }); } catch (e) {} }, applyCurrentModeToAllInputs() { try { this.findAndProcessNewInputs(document, State.currentMode); document.querySelectorAll("*").forEach((el) => { if (el.shadowRoot) { this.findAndProcessNewInputs(el.shadowRoot, State.currentMode); } }); } catch (e) {} }, }; const EventManager = { domMutationObserver: null, handleShowPasswordOnHover(event) { const input = event.target; if ( State.currentMode === Config.MODES.HOVER && input instanceof HTMLInputElement && input.matches( `input[type="password"][${Config.ATTRIBUTES.PROCESSED}="${Config.MODES.HOVER}"]` ) ) { input.type = "text"; } }, handleHidePasswordOnLeave(event) { const input = event.target; if ( State.currentMode === Config.MODES.HOVER && input instanceof HTMLInputElement && input.matches( `input[type="text"][${Config.ATTRIBUTES.PROCESSED}="${Config.MODES.HOVER}"]` ) ) { input.type = "password"; } }, handleTogglePasswordOnDoubleClick(event) { const input = event.target; if ( State.currentMode === Config.MODES.DBLCLICK && input instanceof HTMLInputElement && input.matches( `input[${Config.ATTRIBUTES.PROCESSED}="${Config.MODES.DBLCLICK}"]` ) ) { input.type = input.type === "password" ? "text" : "password"; } }, handleFocusIn(event) { const input = event.target; if ( State.currentMode === Config.MODES.FOCUS && input instanceof HTMLInputElement && input.matches( `input[type="password"][${Config.ATTRIBUTES.PROCESSED}="${Config.MODES.FOCUS}"]` ) ) { input.type = "text"; } }, handleFocusOut(event) { const input = event.target; if ( State.currentMode === Config.MODES.FOCUS && input instanceof HTMLInputElement && input.matches( `input[type="text"][${Config.ATTRIBUTES.PROCESSED}="${Config.MODES.FOCUS}"]` ) ) { input.type = "password"; } }, handleKeyboardShortcut(event) { if ( (event.ctrlKey || event.metaKey) && event.altKey && !event.shiftKey && event.code === "KeyP" ) { event.preventDefault(); event.stopPropagation(); const currentIndex = Config.VALID_MODES.indexOf(State.currentMode); const nextIndex = (currentIndex + 1) % Config.VALID_MODES.length; const nextMode = Config.VALID_MODES[nextIndex]; ScriptManager.setModeAndUpdate(nextMode); } }, handleDOMMutation(mutationsList) { for (const mutation of mutationsList) { if (mutation.type === "childList") { mutation.addedNodes.forEach((node) => { if (node.nodeType !== Node.ELEMENT_NODE) return; try { InputManager.findAndProcessNewInputs(node, State.currentMode); } catch (e) {} }); } else if ( mutation.type === "attributes" && mutation.attributeName === "type" ) { const targetInput = mutation.target; if ( targetInput.nodeType === Node.ELEMENT_NODE && targetInput.matches && targetInput.matches('input[type="password"]') && targetInput.getAttribute(Config.ATTRIBUTES.PROCESSED) !== State.currentMode ) { try { InputManager.processPasswordInput(targetInput, State.currentMode); } catch (e) {} } } } }, initializeDOMObserver() { if (this.domMutationObserver) return; const observerOptions = { childList: true, subtree: true, attributes: true, attributeFilter: ["type"], }; this.domMutationObserver = new MutationObserver( this.handleDOMMutation.bind(this) ); if (document.body) { try { this.domMutationObserver.observe(document.body, observerOptions); } catch (error) { this.domMutationObserver = null; } } else { document.addEventListener( "DOMContentLoaded", () => { if (document.body) { try { this.domMutationObserver.observe( document.body, observerOptions ); } catch (error) { this.domMutationObserver = null; } } }, { once: true } ); } }, initializeGlobalEventListeners() { document.body.addEventListener( "mouseenter", this.handleShowPasswordOnHover.bind(this), true ); document.body.addEventListener( "mouseleave", this.handleHidePasswordOnLeave.bind(this), true ); document.body.addEventListener( "dblclick", this.handleTogglePasswordOnDoubleClick.bind(this) ); document.addEventListener("focus", this.handleFocusIn.bind(this), true); document.addEventListener("blur", this.handleFocusOut.bind(this), true); document.addEventListener( "keydown", this.handleKeyboardShortcut.bind(this), true ); }, init() { this.initializeGlobalEventListeners(); this.initializeDOMObserver(); }, }; const ScriptManager = { init() { try { UserInterface.injectCoreStyles(); State.loadAndSetInitialState(); UserInterface.updateUserScriptMenuCommands(); InputManager.applyCurrentModeToAllInputs(); EventManager.init(); } catch (error) {} }, setModeAndUpdate(newMode) { if (State.setMode(newMode)) { const alertMessageKey = Config.MODE_TO_ALERT_MESSAGE_KEY_MAP[State.currentMode]; UserInterface.displayModeNotification(alertMessageKey); InputManager.applyCurrentModeToAllInputs(); UserInterface.updateUserScriptMenuCommands(); } }, }; ScriptManager.init(); })();