From e0c2693d2a97b24040f11b4e2c6ac95da1581d49 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 07:38:23 +0000 Subject: [PATCH 01/15] fix: eliminate dark mode flash in MonochromeUI / NewBootstrap Bootstrap 5.3.3 CSS was previously fetched from CDN at document-idle, causing a flash of the old light-themed page before dark mode took effect. Changes: - Add @run-at document-start so an early shim can run before first paint - Add @resource BootstrapCSS + @require Bootstrap JS bundle so both are cached by the userscript manager (no CDN round-trip on page load) - Add @grant GM_getResourceText - Early synchronous IIFE: reads saved theme/settings from localStorage, sets data-bs-theme on immediately, injects Bootstrap CSS and the MonochromeUI or default skin CSS before the browser paints anything, and uses a MutationObserver to drop the page's own old Bootstrap/theme link tags before they are ever fetched - Extract MonochromeSkinCSS and NewBootstrapSkinCSS to top-level consts so they are available both to the early block and to the late Style element (which still applies them at document-end for any dynamic page additions) - Remove Bootstrap CSS + JS from the runtime CDN resources array - Remove the now-redundant post-load link-removal loop, data-bs-theme assignment, and earlyStyle CSS-variable injection from the NewBootstrap block - Add DOMContentLoaded wait at the start of the main async IIFE so all existing DOM-dependent code continues to work correctly under document-start https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb --- XMOJ.user.js | 4140 +++++++++++++++++++++++++------------------------- 1 file changed, 2065 insertions(+), 2075 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 8b0b2ea0..5076e557 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -14,6 +14,8 @@ // @require https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.0.2/purify.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js +// @require https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.js +// @resource BootstrapCSS https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @grant GM_setClipboard @@ -21,6 +23,7 @@ // @grant GM_setValue // @grant GM_getValue // @grant GM_cookie +// @grant GM_getResourceText // @homepage https://www.xmoj-script.uk/ // @supportURL https://github.com/XMOJ-Script-dev/XMOJ-Script/issues // @connect api.xmoj-bbs.tech @@ -31,6 +34,7 @@ // @connect cdnjs.cloudflare.com // @connect 127.0.0.1 // @license GPL +// @run-at document-start // @icon data:image/png;base64,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Uk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAJcEhZcwAACxMAAAsTAQCanBgAAAPSaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA3LjItYzAwMCA3OS4xYjY1YTc5LCAyMDIyLzA2LzEzLTE3OjQ2OjE0ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6ZTIyMGE0MzYtMWFhYi01MjRjLTg1ZjQtNDUyYjdkYTE4ZjdhIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjlEQTA5MUE5OTM0NEYxNEM5Q0RFMEVFREY2MzA4QThEIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjk1RkQ1QzI3QzBFN0I2NDdCMTBGMzU5NjU0RUI1NjQ2IiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCAyMy41IChXaW5kb3dzKSIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ZTIyMGE0MzYtMWFhYi01MjRjLTg1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 // ==/UserScript== @@ -40,726 +44,634 @@ * You should have received a copy of the GNU General Public License along with this program. If not, see . */ -const CaptchaSiteKey = "0x4AAAAAAALBT58IhyDViNmv"; -const MonacoCDN = "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.53.0/min/vs"; -const AdminUserList = ["zhuchenrui2", "shanwenxiao", "chenlangning", "admin"]; +const MonochromeSkinCSS = ` + /* Fonts loaded via to avoid layout shift */ -// Pre-declared so that closures defined before the async init block can reference them -let CurrentUsername; -let initTheme; -let SearchParams; + :root { + --mono-black: #000; + --mono-white: #fff; + --mono-gray-100: #f5f5f5; + --mono-gray-200: #e5e5e5; + --mono-gray-300: #d4d4d4; + --mono-gray-400: #a3a3a3; + --mono-gray-500: #737373; + --mono-border: 2px solid var(--mono-black); + --mono-border-thin: 1px solid var(--mono-gray-300); + --mono-font-heading: 'Playfair Display', Georgia, serif; + --mono-font-body: 'Source Serif 4', 'Source Serif Pro', Georgia, serif; + --mono-font-mono: 'JetBrains Mono', 'Consolas', monospace; + --mono-transition: 100ms ease; + } -let escapeHTML = (str) => { - return str.replace(/[&<>"']/g, function (match) { - const escape = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - return escape[match]; - }); -}; + [data-bs-theme='dark'] { + --mono-black: #e5e5e5; + --mono-white: #1a1a1a; + --mono-gray-100: #222; + --mono-gray-200: #2a2a2a; + --mono-gray-300: #404040; + --mono-gray-400: #737373; + --mono-gray-500: #a3a3a3; + } -let PurifyHTML = (Input) => { - try { - return DOMPurify.sanitize(Input, { - "ALLOWED_TAGS": ["a", "b", "big", "blockquote", "br", "code", "dd", "del", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "hr", "i", "img", "ins", "kbd", "li", "ol", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "tt", "ul", "var"], - "ALLOWED_ATTR": ["abbr", "accept", "accept-charset", "accesskey", "action", "align", "alt", "axis", "border", "cellpadding", "cellspacing", "char", "charoff", "charset", "checked", "cite", "clear", "color", "cols", "colspan", "compact", "coords", "datetime", "dir", "disabled", "enctype", "for", "frame", "headers", "height", "href", "hreflang", "hspace", "ismap", "itemprop", "label", "lang", "longdesc", "maxlength", "media", "method", "multiple", "name", "nohref", "noshade", "nowrap", "prompt", "readonly", "rel", "rev", "rows", "rowspan", "rules", "scope", "selected", "shape", "size", "span", "src", "start", "summary", "tabindex", "target", "title", "type", "usemap", "valign", "value", "vspace", "width"] - }); - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } -} -let SmartAlert = (Message) => { - if (localStorage.getItem("UserScript-Alert") !== Message) { - alert(Message); - } - localStorage.setItem("UserScript-Alert", Message); -} -/** - * Calculates the relative time based on the input date. - * @param {string|Date} Input - The input date. - * @returns {string} The relative time in a formatted string. - */ -let GetRelativeTime = (Input) => { - try { - Input = new Date(parseInt(Input)); - let Now = new Date().getTime(); - let Delta = Now - Input.getTime(); - let RelativeName = ""; - if (Delta < 0) { - RelativeName = "未来"; - } else if (Delta <= 1000 * 60) { - RelativeName = "刚刚"; - } else if (Delta <= 1000 * 60 * 60) { - RelativeName = Math.floor((Now - Input) / 1000 / 60) + "分钟前"; - } else if (Delta <= 1000 * 60 * 60 * 24) { - RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60) + "小时前"; - } else if (Delta <= 1000 * 60 * 60 * 24 * 31) { - RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60 / 24) + "天前"; - } else if (Delta <= 1000 * 60 * 60 * 24 * 365) { - RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60 / 24 / 31) + "个月前"; - } else { - RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60 / 24 / 365) + "年前"; - } - return "" + RelativeName + ""; - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } -}; + * { + border-radius: 0 !important; + box-shadow: none !important; + } -function compareVersions(currVer, remoteVer) { - const currParts = currVer.split('.').map(Number); - const remoteParts = remoteVer.split('.').map(Number); + body { + font-family: var(--mono-font-body) !important; + color: var(--mono-black) !important; + background-color: var(--mono-white) !important; + } - const maxLen = Math.max(currParts.length, remoteParts.length); - for (let i = 0; i < maxLen; i++) { - const curr = currParts[i] !== undefined ? currParts[i] : 0; - const remote = remoteParts[i] !== undefined ? remoteParts[i] : 0; - if (remote > curr) { - return true; // update needed - } else if (remote < curr) { - return false; // no update needed - } - } - return false; // versions are equal -} + h1, h2, h3, h4, h5, h6 { + font-family: var(--mono-font-heading) !important; + font-weight: 700 !important; + } -let RenderMathJax = async () => { - try { - if (document.getElementById("MathJax-script") === null) { - var ScriptElement = document.createElement("script"); - ScriptElement.id = "MathJax-script"; - ScriptElement.type = "text/javascript"; - ScriptElement.src = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.0.5/es5/tex-chtml.js"; - document.body.appendChild(ScriptElement); - await new Promise((Resolve) => { - ScriptElement.onload = () => { - Resolve(); - }; - }); - } - if (MathJax !== undefined) { //If there is a Math expression - MathJax.startup.input[0].findTeX.options.inlineMath.push(["$", "$"]); - MathJax.startup.input[0].findTeX.getPatterns(); - MathJax.typeset(); - } - } catch (e) { - console.error(e); - } -}; -let GetUserInfo = async (Username) => { - try { - if (localStorage.getItem("UserScript-User-" + Username + "-UserRating") != null && new Date().getTime() - parseInt(localStorage.getItem("UserScript-User-" + Username + "-LastUpdateTime")) < 1000 * 60 * 60 * 24) { - return { - "Rating": localStorage.getItem("UserScript-User-" + Username + "-UserRating"), - "EmailHash": localStorage.getItem("UserScript-User-" + Username + "-EmailHash") - } - } - return await fetch("https://www.xmoj.tech/userinfo.php?user=" + Username).then((Response) => { - return Response.text(); - }).then((Response) => { - if (Response.indexOf("No such User!") !== -1) { - return null; - } - const ParsedDocument = new DOMParser().parseFromString(Response, "text/html"); - let Rating = (parseInt(ParsedDocument.querySelector("#statics > tbody > tr:nth-child(4) > td:nth-child(2)").innerText.trim()) / parseInt(ParsedDocument.querySelector("#statics > tbody > tr:nth-child(3) > td:nth-child(2)").innerText.trim())).toFixed(3) * 1000; - let Temp = ParsedDocument.querySelector("#statics > tbody").children; - let Email = Temp[Temp.length - 1].children[1].innerText.trim(); - let EmailHash = CryptoJS.MD5(Email).toString(); - localStorage.setItem("UserScript-User-" + Username + "-UserRating", Rating); - if (Email == "") { - EmailHash = undefined; - } else { - localStorage.setItem("UserScript-User-" + Username + "-EmailHash", EmailHash); - } - localStorage.setItem("UserScript-User-" + Username + "-LastUpdateTime", new Date().getTime()); - return { - "Rating": Rating, "EmailHash": EmailHash - } - }); - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } -}; -/** - * Retrieves the badge information for a given user. - * - * @param {string} Username - The username of the user. - * @returns {Promise} - A promise that resolves to an object containing the badge information. - * @property {string} BackgroundColor - The background color of the badge. - * @property {string} Color - The color of the badge. - * @property {string} Content - The content of the badge. - */ -let GetUserBadge = async (Username) => { - try { - if (localStorage.getItem("UserScript-User-" + Username + "-Badge-LastUpdateTime") != null && new Date().getTime() - parseInt(localStorage.getItem("UserScript-User-" + Username + "-Badge-LastUpdateTime")) < 1000 * 60 * 60 * 24) { - return { - "BackgroundColor": localStorage.getItem("UserScript-User-" + Username + "-Badge-BackgroundColor"), - "Color": localStorage.getItem("UserScript-User-" + Username + "-Badge-Color"), - "Content": localStorage.getItem("UserScript-User-" + Username + "-Badge-Content") - } - } else { - let BackgroundColor = ""; - let Color = ""; - let Content = ""; - await new Promise((Resolve) => { - RequestAPI("GetBadge", { - "UserID": String(Username) - }, (Response) => { - if (Response.Success) { - BackgroundColor = Response.Data.BackgroundColor; - Color = Response.Data.Color; - Content = Response.Data.Content; - } - Resolve(); - }); - }); - localStorage.setItem("UserScript-User-" + Username + "-Badge-BackgroundColor", BackgroundColor); - localStorage.setItem("UserScript-User-" + Username + "-Badge-Color", Color); - localStorage.setItem("UserScript-User-" + Username + "-Badge-Content", Content); - localStorage.setItem("UserScript-User-" + Username + "-Badge-LastUpdateTime", String(new Date().getTime())); - return { - "BackgroundColor": BackgroundColor, "Color": Color, "Content": Content - } - } - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } -}; -async function ensureMonaco() { - if (typeof monaco !== 'undefined') return; - const loaderUrl = MonacoCDN + '/loader.js'; - if (typeof require === 'undefined' || typeof require.config === 'undefined') { - await new Promise((resolve, reject) => { - const s = document.createElement('script'); - s.src = loaderUrl; - s.onload = () => { - try { require.config({ paths: { vs: MonacoCDN } }); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } + code, pre, .CodeMirror, kbd, samp { + font-family: var(--mono-font-mono) !important; } - resolve(); - }; - s.onerror = reject; - document.head.appendChild(s); - }); - } else { - try { require.config({ paths: { vs: MonacoCDN } }); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } - } - await new Promise((resolve, reject) => { - let check = null; - const done = () => { if (check) clearInterval(check); clearTimeout(timeout); resolve(); }; - const timeout = setTimeout(() => { if (check) clearInterval(check); reject(new Error('Monaco load timeout')); }, 30000); - try { - require(['vs/editor/editor.main'], function() { done(); }); - } catch (e) { - check = setInterval(() => { if (typeof monaco !== 'undefined') done(); }, 50); - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } - }); -} -async function createMonacoEditor(containerOrId, options = {}) { - await ensureMonaco(); - let container = null; - if (typeof containerOrId === 'string') container = document.getElementById(containerOrId); - else container = containerOrId; - if (!container) throw new Error('Monaco container not found'); - if (!container.id) container.id = 'monaco-' + Math.random().toString(36).slice(2,9); - // Auto-fit settings: when true Monaco will fill width and height from the page header to window bottom - let autoFitEnabled = !!options.fitToViewport; - let _autoFitHandler = null; - let innerHost = null; - const computeAvailableHeight = () => { - try { - const header = document.querySelector('nav') || document.querySelector('#navbar') || document.querySelector('.navbar') || document.querySelector('header'); - const top = header ? header.getBoundingClientRect().bottom : 0; - const bottomOffset = Number(options.bottomOffset || 0); - const available = Math.max(80, window.innerHeight - top - bottomOffset); - return available; - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - return null; - } - }; - const applyAutoFit = (callLayout, ed) => { - try { - if (!autoFitEnabled) return; - const available = computeAvailableHeight(); - if (available != null) { - try { - container.style.width = options.width || '100%'; - container.style.display = 'flex'; - container.style.alignItems = 'center'; - container.style.justifyContent = 'center'; - container.style.boxSizing = 'border-box'; - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } + a { + color: var(--mono-black) !important; + text-decoration: none !important; + transition: var(--mono-transition) !important; } - try { container.style.height = available + 'px'; } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } + .container a:not(.nav-link):not(.btn):not(.dropdown-item):not(.list-group-item):not(.page-link) { + border-bottom: 1px solid var(--mono-gray-400) !important; + padding-bottom: 1px !important; } - if (innerHost) { - try { - const innerRatio = (typeof options.innerRatio !== 'undefined') ? Number(options.innerRatio) : 0.95; - const wrapperHeight = Math.max(200, Math.min(Math.floor(available * innerRatio), available)); - innerHost.style.height = wrapperHeight + 'px'; - innerHost.style.width = options.width || '95%'; - if (options.maxWidth) innerHost.style.maxWidth = String(options.maxWidth); - innerHost.style.boxSizing = 'border-box'; - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } + .container a:not(.nav-link):not(.btn):not(.dropdown-item):not(.list-group-item):not(.page-link):hover { + border-bottom-color: var(--mono-black) !important; } - if (callLayout) try { if (ed && ed.layout) ed.layout(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } + + blockquote { + border-left: 4px solid var(--mono-black) !important; + padding: 0.5em 1em; } - } - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } - }; - const key = options.localStorageKey || ('XMOJ-Monaco-' + location.pathname + ':' + container.id); - const theme = options.theme || (typeof UtilityEnabled === 'function' && UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs'); - const readOnly = !!options.readOnly; - const language = options.language || (options.mode === 'text/x-c++src' ? 'cpp' : (options.mode || 'cpp')); - // create an inner host so we can center the editor both horizontally and vertically - try { - innerHost = document.createElement('div'); - innerHost.className = 'monaco-editor-host'; - innerHost.style.boxSizing = 'border-box'; - // default sizing; will be recalculated by applyAutoFit when enabled - innerHost.style.width = options.width || (autoFitEnabled ? '95%' : '100%'); - if (!autoFitEnabled) { - if (options.height) innerHost.style.height = (typeof options.height === 'number' ? options.height + 'px' : options.height); - } - container.appendChild(innerHost); - } catch (e) { console.error(e); } - const editor = monaco.editor.create(innerHost || container, { - value: options.value || '', - language: language, - automaticLayout: !!options.automaticLayout, - theme: theme, - minimap: (typeof options.minimap !== 'undefined' ? options.minimap : { enabled: false }), - readOnly: readOnly, - lineNumbers: typeof options.lineNumbers !== 'undefined' ? options.lineNumbers : 'on', - tabSize: options.tabSize || 4 - }); - // apply initial auto-fit (no layout call yet because editor is not fully initialized until after creation) - try { applyAutoFit(false, editor); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } - // after creation, ensure Monaco layout matches the computed size and listen for viewport changes - try { - applyAutoFit(true, editor); - if (autoFitEnabled && typeof window !== 'undefined') { - _autoFitHandler = () => applyAutoFit(true, editor); - window.addEventListener('resize', _autoFitHandler); - window.addEventListener('orientationchange', _autoFitHandler); - window.addEventListener('scroll', _autoFitHandler); - try { editor.onDidDispose(() => { window.removeEventListener('resize', _autoFitHandler); window.removeEventListener('orientationchange', _autoFitHandler); window.removeEventListener('scroll', _autoFitHandler); }); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + /* Navbar */ + .navbar, nav.navbar { + border-bottom: 4px solid var(--mono-black) !important; + background-color: var(--mono-white) !important; + opacity: 1 !important; } - } - } - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } - try { - if (options.restoreOnLoad !== false) { - const saved = localStorage.getItem(key); - if (saved !== null && saved !== 'null') editor.setValue(saved); - } - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } - let saveTimer = null; - const doSave = () => { try { localStorage.setItem(key, editor.getValue()); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } }; - editor.onDidChangeModelContent(() => { if (saveTimer) clearTimeout(saveTimer); saveTimer = setTimeout(doSave, options.saveDebounce || 500); }); - const adapter = { - getValue: () => editor.getValue(), - setValue: (v) => { editor.setValue(v); }, - setSize: (w, h) => { const el = innerHost || container; if (w) el.style.width = w; if (h) { try { if (h !== 'auto') autoFitEnabled = false; } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } if (h === 'auto') { try { const lines = editor.getModel().getLineCount(); el.style.height = Math.max(80, Math.min(1200, lines * 18)) + 'px'; } catch (e) { el.style.height = '80px'; } } else el.style.height = h; } try { editor.layout(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } }, - getWrapperElement: () => innerHost || container, - focus: () => { try { editor.focus(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } }, - _monacoEditor: editor, - showFind: () => { try { editor.trigger('', 'editor.action.startFindReplaceAction'); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } }, - goToLine: (line) => { try { editor.setPosition({ lineNumber: parseInt(line) || 1, column: 1 }); editor.revealPositionInCenter({ lineNumber: parseInt(line) || 1, column: 1 }); editor.focus(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } }, - selectRange: (sLine, sCol, eLine, eCol) => { try { editor.setSelection({ startLineNumber: sLine, startColumn: sCol, endLineNumber: eLine, endColumn: eCol }); editor.revealRangeInCenter({ startLineNumber: sLine, startColumn: sCol, endLineNumber: eLine, endColumn: eCol }); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } }, - saveToLocal: doSave, - localStorageKey: key - , - dispose: () => { - try { - if (_autoFitHandler && typeof window !== 'undefined') { - window.removeEventListener('resize', _autoFitHandler); - window.removeEventListener('orientationchange', _autoFitHandler); - window.removeEventListener('scroll', _autoFitHandler); + .navbar .nav-link { + color: var(--mono-black) !important; + text-decoration: none !important; + font-family: var(--mono-font-body) !important; + text-transform: uppercase !important; + letter-spacing: 0.05em !important; + font-size: 0.85rem !important; } - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + .navbar .nav-link:hover, .navbar .nav-link.active { + background-color: var(--mono-black) !important; + color: var(--mono-white) !important; } - } - try { - if (editor) { - try { - const model = (editor.getModel && editor.getModel()) || null; - if (model && model.dispose) model.dispose(); - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } - try { editor.dispose(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } + + /* Buttons */ + .btn { + border: 2px solid var(--mono-black) !important; + background-color: var(--mono-white) !important; + color: var(--mono-black) !important; + text-transform: uppercase !important; + letter-spacing: 0.1em !important; + font-family: var(--mono-font-body) !important; + font-weight: 600 !important; + transition: var(--mono-transition) !important; } - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + .btn:hover, .btn:focus, .btn:active, .btn.active { + background-color: var(--mono-black) !important; + color: var(--mono-white) !important; + border-color: var(--mono-black) !important; } - } - } - }; - return adapter; -} - -(function() { - const shim = function(containerOrTextArea, options) { - let container = containerOrTextArea; - let initialValue = ''; - if (container && container.tagName && container.tagName.toLowerCase() === 'textarea') { - initialValue = container.value || container.textContent || ''; - const div = document.createElement('div'); - // copy most attributes from the original textarea to the replacement div (except value) - try { - for (let i = 0; i < container.attributes.length; i++) { - const a = container.attributes[i]; - if (!a) continue; - if (a.name === 'value') continue; - try { div.setAttribute(a.name, a.value); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } + .btn-primary { + border: 2px solid var(--mono-black) !important; + background-color: var(--mono-black) !important; + color: var(--mono-white) !important; } - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + .btn-primary:hover { + background-color: var(--mono-white) !important; + color: var(--mono-black) !important; } - } - div.classList.add('codemirror-shim-host'); - // preserve inline styles if any - try { if (container.style && container.style.cssText) div.style.cssText = container.style.cssText; } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + .btn-secondary { + border: 2px solid var(--mono-black) !important; + background-color: var(--mono-white) !important; + color: var(--mono-black) !important; } - } - container.parentNode.replaceChild(div, container); - container = div; - } else if (typeof containerOrTextArea === 'string') { - container = document.querySelector(containerOrTextArea) || document.getElementById(containerOrTextArea); - } - if (!container) { container = document.createElement('div'); document.body.appendChild(container); } - container._cmValue = (options && options.value) ? options.value : initialValue; - // show a centered Loading... placeholder while Monaco initializes - try { - const _monacoLoadingEl = document.createElement('div'); - _monacoLoadingEl.className = 'monaco-loading'; - _monacoLoadingEl.style.width = '100%'; - _monacoLoadingEl.style.height = '100%'; - _monacoLoadingEl.style.display = 'flex'; - _monacoLoadingEl.style.alignItems = 'center'; - _monacoLoadingEl.style.justifyContent = 'center'; - _monacoLoadingEl.textContent = 'Loading...'; - container.appendChild(_monacoLoadingEl); - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } - let _lastSetSizeArgs = null; - const placeholderAdapter = { - getValue: () => container._cmValue || '', - setValue: (v) => { container._cmValue = v; if (container._cmEditor) try { container._cmEditor.setValue(v); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + .btn-secondary:hover { + background-color: var(--mono-black) !important; + color: var(--mono-white) !important; } - } }, - setSize: (w, h) => { _lastSetSizeArgs = [w, h]; if (w) container.style.width = w; if (h) { if (h === 'auto') container.style.height = 'auto'; else container.style.height = h; } if (container._cmEditor) try { container._cmEditor.layout(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + .btn-success { + background-color: var(--mono-white) !important; + border: 2px solid #52c41a !important; + color: #52c41a !important; } - } }, - getWrapperElement: () => container, - focus: () => { if (container._cmEditor) try { container._cmEditor.focus(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + .btn-danger { + background-color: var(--mono-white) !important; + border: 2px solid #fe4c61 !important; + color: #fe4c61 !important; } - } }, - _monacoEditor: null - }; - (async () => { - try { - const opts = options || {}; - await ensureMonaco(); - const lang = opts.mode === 'text/x-c++src' || (opts.mode && opts.mode.indexOf('c++') !== -1) ? 'cpp' : (opts.language || 'cpp'); - const monacoAdapter = await createMonacoEditor(container, Object.assign({ language: lang, value: container._cmValue || '', readOnly: !!opts.readOnly, theme: (typeof UtilityEnabled === 'function' && UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs') }, opts)); - container._cmEditor = monacoAdapter._monacoEditor; - placeholderAdapter.getValue = monacoAdapter.getValue; - placeholderAdapter.setValue = monacoAdapter.setValue; - placeholderAdapter.setSize = monacoAdapter.setSize; - placeholderAdapter.getWrapperElement = monacoAdapter.getWrapperElement; - placeholderAdapter.focus = monacoAdapter.focus; - placeholderAdapter._monacoEditor = monacoAdapter._monacoEditor; - try { const _l = container.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } + .btn-warning { + background-color: var(--mono-white) !important; + border: 2px solid #ffa900 !important; + color: #ffa900 !important; + } + .btn-info { + background-color: var(--mono-white) !important; + border: 2px solid #0dcaf0 !important; + color: #0dcaf0 !important; } - if (_lastSetSizeArgs) monacoAdapter.setSize(_lastSetSizeArgs[0], _lastSetSizeArgs[1]); - } catch (e) { console.error(e); try { const _l = container.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (_) {} } - })(); - return placeholderAdapter; - }; - shim.fromTextArea = function(textarea, options) { return shim(textarea, options); }; - shim.MergeView = function(container, options) { - options = options || {}; - let el = container; - if (typeof container === 'string') el = document.getElementById(container) || document.querySelector(container); - if (!el) { el = document.createElement('div'); document.body.appendChild(el); } - // show a centered Loading... placeholder while Monaco initializes - try { - const _mergeLoadingEl = document.createElement('div'); - _mergeLoadingEl.className = 'monaco-loading'; - _mergeLoadingEl.style.width = '100%'; - _mergeLoadingEl.style.height = '100%'; - _mergeLoadingEl.style.display = 'flex'; - _mergeLoadingEl.style.alignItems = 'center'; - _mergeLoadingEl.style.justifyContent = 'center'; - _mergeLoadingEl.textContent = 'Loading...'; - el.appendChild(_mergeLoadingEl); - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } - const wrapper = { ignoreWhitespace: !!options.ignoreWhitespace, _diffEditor: null, _originalModel: null, _modifiedModel: null }; - (async () => { - try { - await ensureMonaco(); - // create an inner host so the diff editor can be centered if requested - let mvInner = null; - try { - mvInner = document.createElement('div'); - mvInner.className = 'monaco-merge-host'; - mvInner.style.boxSizing = 'border-box'; - mvInner.style.width = options.width || '95%'; - if (options.fitToViewport) { - try { el.style.display = 'flex'; el.style.alignItems = 'center'; el.style.justifyContent = 'center'; } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } - const header = document.querySelector('nav') || document.querySelector('#navbar') || document.querySelector('.navbar') || document.querySelector('header'); - const top = header ? header.getBoundingClientRect().bottom : 0; - const bottomOffset = Number(options.bottomOffset || 0); - const available = Math.max(80, window.innerHeight - top - bottomOffset); - const wrapperHeight = Math.max(200, Math.min(Math.floor(available * 0.95), available)); - mvInner.style.height = wrapperHeight + 'px'; - } else { - if (options.height) mvInner.style.height = (typeof options.height === 'number' ? options.height + 'px' : options.height); - } - el.appendChild(mvInner); - } catch (e) { mvInner = null; } - const diffEditor = monaco.editor.createDiffEditor(mvInner || el, { readOnly: !!options.readOnly, theme: (typeof UtilityEnabled === 'function' && UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs'), minimap: { enabled: false }, automaticLayout: true, ignoreTrimWhitespace: !!wrapper.ignoreWhitespace }); - const orig = options.value || ''; - const mod = options.orig || ''; - const isCpp = options.mode === 'text/x-c++src' || (typeof options.mode === 'string' && options.mode.indexOf('c++') !== -1); - const lang = isCpp ? 'cpp' : (options.language || 'cpp'); - const originalModel = monaco.editor.createModel(orig, lang); - const modifiedModel = monaco.editor.createModel(mod, lang); - diffEditor.setModel({ original: originalModel, modified: modifiedModel }); - wrapper._diffEditor = diffEditor; - wrapper._originalModel = originalModel; - wrapper._modifiedModel = modifiedModel; - try { const _l = el.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } + /* Cards */ + .card { + border: 2px solid var(--mono-black) !important; + background-color: var(--mono-white) !important; } - } catch (e) { console.error(e); try { const _l = el.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (_) {} } - })(); - return wrapper; - }; - window.CodeMirror = shim; -})(); -/** - * Dispose any transient Monaco editors created inside the ErrorMessage area (freopen snippets). - */ -function _xmoj_disposeErrorMessageEditors() { - try { - const arr = window._xmoj_temp_error_editors || []; - if (arr.length) { - arr.forEach((ed) => { - try { - const m = (ed && ed.getModel) ? ed.getModel() : null; - if (m && m.dispose) try { m.dispose(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } + .card-header { + background-color: var(--mono-black) !important; + color: var(--mono-white) !important; + border-bottom: none !important; + font-family: var(--mono-font-heading) !important; } - try { if (ed && ed.dispose) ed.dispose(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + .card-header * { + color: var(--mono-white) !important; + } + .card-body { + background-color: var(--mono-white) !important; + color: var(--mono-black) !important; + } + .card-footer { + border-top: 1px solid var(--mono-gray-300) !important; + background-color: var(--mono-white) !important; + } + + /* Modals */ + .modal-content { + border: 4px solid var(--mono-black) !important; + background-color: var(--mono-white) !important; + } + .modal-header { + border-bottom: 1px solid var(--mono-gray-300) !important; + background-color: var(--mono-white) !important; + } + .modal-footer { + border-top: 1px solid var(--mono-gray-300) !important; + background-color: var(--mono-white) !important; + } + .modal-title { + font-family: var(--mono-font-heading) !important; + } + + /* Toasts */ + .toast { + border: 2px solid var(--mono-black) !important; + background-color: var(--mono-white) !important; + } + .toast-header { + background-color: var(--mono-gray-100) !important; + color: var(--mono-black) !important; + border-bottom: 1px solid var(--mono-gray-300) !important; + } + + /* Tables */ + .table { + border-color: var(--mono-gray-300) !important; + } + thead th, th.header, th.headerSortUp, th.headerSortDown { + background-color: var(--mono-black) !important; + background-image: none !important; + color: var(--mono-white) !important; + border-bottom: none !important; + font-family: var(--mono-font-heading) !important; + text-transform: uppercase !important; + letter-spacing: 0.05em !important; + font-size: 0.85rem !important; + } + td, th { + border-color: var(--mono-gray-300) !important; + text-align: center !important; + } + .table-striped > tbody > tr:nth-of-type(odd) > * { + background-color: var(--mono-gray-100) !important; + } + table { + margin-top: 16px !important; + } + + /* List groups */ + .list-group-item { + border: none !important; + border-bottom: 1px solid var(--mono-gray-300) !important; + background-color: var(--mono-white) !important; + color: var(--mono-black) !important; + } + .list-group-item-success { + border-left: 4px solid #52c41a !important; + } + .list-group-item-warning { + border-left: 4px solid #ffa900 !important; + } + .list-group-item-danger { + border-left: 4px solid #fe4c61 !important; + } + + /* Dropdowns */ + .dropdown-menu { + border: 2px solid var(--mono-black) !important; + padding: 0 !important; + background-color: var(--mono-white) !important; + } + .dropdown-item { + border-bottom: 1px solid var(--mono-gray-200) !important; + color: var(--mono-black) !important; + transition: var(--mono-transition) !important; + text-decoration: none !important; + } + .dropdown-item:last-child { + border-bottom: none !important; + } + .dropdown-item:hover, .dropdown-item:focus { + background-color: var(--mono-black) !important; + color: var(--mono-white) !important; + } + + /* Forms */ + .form-control, .form-select { + border: 2px solid var(--mono-black) !important; + background-color: var(--mono-white) !important; + color: var(--mono-black) !important; + font-family: var(--mono-font-body) !important; + } + .form-control:focus, .form-select:focus { + outline: 2px solid var(--mono-black) !important; + outline-offset: 2px !important; + border-color: var(--mono-black) !important; + } + + /* Alerts */ + .alert { + border: 2px solid var(--mono-black) !important; + background-color: var(--mono-white) !important; + color: var(--mono-black) !important; + } + .alert-primary { + border-left: 8px solid var(--mono-black) !important; + } + + /* Status indicators */ + .status_y { + background-color: #52c41a !important; + color: #fff !important; + border-color: #52c41a !important; + } + .status_n { + background-color: #fe4c61 !important; + color: #fff !important; + border-color: #fe4c61 !important; + } + .status_w { + background-color: #ffa900 !important; + color: #fff !important; + border-color: #ffa900 !important; + } + + .test-case:hover { + border: 2px solid var(--mono-black) !important; + } + + .software_list { + width: unset !important; + } + .software_item { + margin: 5px 10px !important; + background-color: var(--mono-gray-100) !important; + border: 1px solid var(--mono-gray-300) !important; + } + .software_item img { + width: 50px !important; + height: 50px !important; + object-fit: contain !important; + } + .item-txt { + color: var(--mono-black) !important; + } + .cnt-row { + justify-content: inherit; + align-items: stretch; + width: 100% !important; + padding: 1rem 0; + } + .cnt-row-head { + padding: 0.8em 1em; + background-color: var(--mono-black); + color: var(--mono-white); + width: 100%; + font-family: var(--mono-font-heading); + } + .cnt-row-head * { + color: var(--mono-white) !important; + } + .cnt-row-body { + padding: 1em; + border: 2px solid var(--mono-black); + border-top: none; + } + + /* Scrollbar */ + ::-webkit-scrollbar { + width: 8px; + height: 8px; + } + ::-webkit-scrollbar-track { + background: var(--mono-white); + } + ::-webkit-scrollbar-thumb { + background: var(--mono-black); + } + + /* Copy button in inverted headers */ + .cnt-row-head .copy-btn, .card-header .copy-btn { + border-color: var(--mono-white) !important; + color: var(--mono-white) !important; + background-color: transparent !important; + } + .cnt-row-head .copy-btn:hover, .card-header .copy-btn:hover { + background-color: var(--mono-white) !important; + color: var(--mono-black) !important; + } + + /* Problem switcher responsive */ + @media (max-width: 768px) { + .problem-switcher-container { + display: none !important; } } - }); + .refreshList { + cursor: pointer; + } + + /* Contain images */ + img { + max-width: 100% !important; + height: auto !important; + } + + /* Hide blur overlay */ + #blur-overlay { display: none !important; }`; +const NewBootstrapSkinCSS = ` + nav { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + } + blockquote { + border-left: 5px solid var(--bs-secondary-bg); + padding: 0.5em 1em; + } + .status_y:hover { + box-shadow: #52c41a 1px 1px 10px 0px !important; + } + .status_n:hover { + box-shadow: #fe4c61 1px 1px 10px 0px !important; + } + .status_w:hover { + box-shadow: #ffa900 1px 1px 10px 0px !important; + } + .test-case { + border-radius: 5px !important; + } + .test-case:hover { + box-shadow: rgba(0, 0, 0, 0.3) 0px 10px 20px 3px !important; + } + .data[result-item] { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + } + .software_list { + width: unset !important; + } + .software_item { + margin: 5px 10px !important; + background-color: var(--bs-secondary-bg) !important; + } + .item-txt { + color: var(--bs-emphasis-color) !important; + } + .cnt-row { + justify-content: inherit; + align-items: stretch; + width: 100% !important; + padding: 1rem 0; + } + .cnt-row-head { + padding: 0.8em 1em; + background-color: var(--bs-secondary-bg); + border-radius: 0.3rem 0.3rem 0 0; + width: 100%; + } + .cnt-row-body { + padding: 1em; + border: 1px solid var(--bs-secondary-bg); + border-top: none; + border-radius: 0 0 0.3rem 0.3rem; + } + .refreshList { + cursor: pointer; + color: #6c757d; + text-decoration: none; + }`; + +// Runs synchronously at document-start. When NewBootstrap is enabled, we apply +// the saved theme and inject Bootstrap CSS + the skin CSS before the first paint, +// and we block the page's own old stylesheets from loading at all. +(() => { + try { + let get = (k) => { + let v = localStorage.getItem("UserScript-Setting-" + k); + return v === null ? !["DebugMode", "SuperDebug", "ReplaceXM"].includes(k) : v === "true"; + }; + if (!get("NewBootstrap")) return; + + let savedTheme = localStorage.getItem("UserScript-Setting-Theme") || "auto"; + let dark = savedTheme === "auto" + ? window.matchMedia("(prefers-color-scheme: dark)").matches + : savedTheme === "dark"; + document.documentElement.setAttribute("data-bs-theme", dark ? "dark" : "light"); + + let head = document.head || document.documentElement; + + let bsStyle = document.createElement("style"); + bsStyle.textContent = GM_getResourceText("BootstrapCSS"); + head.appendChild(bsStyle); + + let isMono = get("MonochromeUI"); + let skinCSS = isMono ? MonochromeSkinCSS : NewBootstrapSkinCSS; + if (get("AddAnimation")) skinCSS += `.status, .test-case { transition: ${isMono ? "100ms ease" : "0.5s"} !important; }`; + if (get("AddColorText")) skinCSS += `.red { color: red !important; } .green { color: green !important; } .blue { color: blue !important; }`; + let skinStyle = document.createElement("style"); + skinStyle.textContent = skinCSS; + head.appendChild(skinStyle); + + let blocked = ["bootstrap.min.css", "white.css", "semantic.min.css", "bootstrap-theme.min.css", "problem.css"]; + let obs = new MutationObserver(mutations => { + for (let m of mutations) + for (let node of m.addedNodes) + if (node.tagName === "LINK" && blocked.some(h => node.href && node.href.indexOf(h) !== -1)) + node.remove(); + }); + obs.observe(document.documentElement, { childList: true, subtree: true }); + document.addEventListener("DOMContentLoaded", () => obs.disconnect(), { once: true }); + } catch (e) { + console.error("[XMOJ-Script] early init error:", e); + } +})(); + +const CaptchaSiteKey = "0x4AAAAAAALBT58IhyDViNmv"; +const MonacoCDN = "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.53.0/min/vs"; +const AdminUserList = ["zhuchenrui2", "shanwenxiao", "chenlangning", "admin"]; + +// Pre-declared so that closures defined before the async init block can reference them +let CurrentUsername; +let initTheme; +let SearchParams; + +let escapeHTML = (str) => { + return str.replace(/[&<>"']/g, function (match) { + const escape = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + return escape[match]; + }); +}; + +let PurifyHTML = (Input) => { + try { + return DOMPurify.sanitize(Input, { + "ALLOWED_TAGS": ["a", "b", "big", "blockquote", "br", "code", "dd", "del", "div", "dl", "dt", "em", "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "hr", "i", "img", "ins", "kbd", "li", "ol", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "strike", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "tt", "ul", "var"], + "ALLOWED_ATTR": ["abbr", "accept", "accept-charset", "accesskey", "action", "align", "alt", "axis", "border", "cellpadding", "cellspacing", "char", "charoff", "charset", "checked", "cite", "clear", "color", "cols", "colspan", "compact", "coords", "datetime", "dir", "disabled", "enctype", "for", "frame", "headers", "height", "href", "hreflang", "hspace", "ismap", "itemprop", "label", "lang", "longdesc", "maxlength", "media", "method", "multiple", "name", "nohref", "noshade", "nowrap", "prompt", "readonly", "rel", "rev", "rows", "rowspan", "rules", "scope", "selected", "shape", "size", "span", "src", "start", "summary", "tabindex", "target", "title", "type", "usemap", "valign", "value", "vspace", "width"] + }); + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } - window._xmoj_temp_error_editors = []; + } +} +let SmartAlert = (Message) => { + if (localStorage.getItem("UserScript-Alert") !== Message) { + alert(Message); + } + localStorage.setItem("UserScript-Alert", Message); +} +/** + * Calculates the relative time based on the input date. + * @param {string|Date} Input - The input date. + * @returns {string} The relative time in a formatted string. + */ +let GetRelativeTime = (Input) => { + try { + Input = new Date(parseInt(Input)); + let Now = new Date().getTime(); + let Delta = Now - Input.getTime(); + let RelativeName = ""; + if (Delta < 0) { + RelativeName = "未来"; + } else if (Delta <= 1000 * 60) { + RelativeName = "刚刚"; + } else if (Delta <= 1000 * 60 * 60) { + RelativeName = Math.floor((Now - Input) / 1000 / 60) + "分钟前"; + } else if (Delta <= 1000 * 60 * 60 * 24) { + RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60) + "小时前"; + } else if (Delta <= 1000 * 60 * 60 * 24 * 31) { + RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60 / 24) + "天前"; + } else if (Delta <= 1000 * 60 * 60 * 24 * 365) { + RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60 / 24 / 31) + "个月前"; + } else { + RelativeName = Math.floor((Now - Input) / 1000 / 60 / 60 / 24 / 365) + "年前"; + } + return "" + RelativeName + ""; } catch (e) { console.error(e); if (UtilityEnabled("DebugMode")) { SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } +}; + +function compareVersions(currVer, remoteVer) { + const currParts = currVer.split('.').map(Number); + const remoteParts = remoteVer.split('.').map(Number); + + const maxLen = Math.max(currParts.length, remoteParts.length); + for (let i = 0; i < maxLen; i++) { + const curr = currParts[i] !== undefined ? currParts[i] : 0; + const remote = remoteParts[i] !== undefined ? remoteParts[i] : 0; + if (remote > curr) { + return true; // update needed + } else if (remote < curr) { + return false; // no update needed + } + } + return false; // versions are equal +} + +let RenderMathJax = async () => { try { - const hosts = document.querySelectorAll('[data-xmoj-error-editor]'); - hosts.forEach((h) => { - try { if (h._monacoEditor && h._monacoEditor.dispose) h._monacoEditor.dispose(); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } + if (document.getElementById("MathJax-script") === null) { + var ScriptElement = document.createElement("script"); + ScriptElement.id = "MathJax-script"; + ScriptElement.type = "text/javascript"; + ScriptElement.src = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.0.5/es5/tex-chtml.js"; + document.body.appendChild(ScriptElement); + await new Promise((Resolve) => { + ScriptElement.onload = () => { + Resolve(); + }; + }); + } + if (MathJax !== undefined) { //If there is a Math expression + MathJax.startup.input[0].findTeX.options.inlineMath.push(["$", "$"]); + MathJax.startup.input[0].findTeX.getPatterns(); + MathJax.typeset(); + } + } catch (e) { + console.error(e); + } +}; +let GetUserInfo = async (Username) => { + try { + if (localStorage.getItem("UserScript-User-" + Username + "-UserRating") != null && new Date().getTime() - parseInt(localStorage.getItem("UserScript-User-" + Username + "-LastUpdateTime")) < 1000 * 60 * 60 * 24) { + return { + "Rating": localStorage.getItem("UserScript-User-" + Username + "-UserRating"), + "EmailHash": localStorage.getItem("UserScript-User-" + Username + "-EmailHash") } - try { delete h._monacoEditor; } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } + } + return await fetch("https://www.xmoj.tech/userinfo.php?user=" + Username).then((Response) => { + return Response.text(); + }).then((Response) => { + if (Response.indexOf("No such User!") !== -1) { + return null; } - try { h.removeAttribute('data-xmoj-error-editor'); } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } + const ParsedDocument = new DOMParser().parseFromString(Response, "text/html"); + let Rating = (parseInt(ParsedDocument.querySelector("#statics > tbody > tr:nth-child(4) > td:nth-child(2)").innerText.trim()) / parseInt(ParsedDocument.querySelector("#statics > tbody > tr:nth-child(3) > td:nth-child(2)").innerText.trim())).toFixed(3) * 1000; + let Temp = ParsedDocument.querySelector("#statics > tbody").children; + let Email = Temp[Temp.length - 1].children[1].innerText.trim(); + let EmailHash = CryptoJS.MD5(Email).toString(); + localStorage.setItem("UserScript-User-" + Username + "-UserRating", Rating); + if (Email == "") { + EmailHash = undefined; + } else { + localStorage.setItem("UserScript-User-" + Username + "-EmailHash", EmailHash); + } + localStorage.setItem("UserScript-User-" + Username + "-LastUpdateTime", new Date().getTime()); + return { + "Rating": Rating, "EmailHash": EmailHash } }); } catch (e) { @@ -768,71 +680,48 @@ function _xmoj_disposeErrorMessageEditors() { SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } -} +}; /** - * Sets the HTML content of an element to display a username with optional additional information. - * @param {HTMLElement} Element - The element to set the HTML content. - * @param {string} Username - The username to display. - * @param {boolean} [Simple=false] - Indicates whether to display additional information or not. - * @param {string} [Href="https://www.xmoj.tech/userinfo.php?user="] - The URL to link the username to. - * @returns {Promise} - A promise that resolves when the HTML content is set. + * Retrieves the badge information for a given user. + * + * @param {string} Username - The username of the user. + * @returns {Promise} - A promise that resolves to an object containing the badge information. + * @property {string} BackgroundColor - The background color of the badge. + * @property {string} Color - The color of the badge. + * @property {string} Content - The content of the badge. */ -let GetUsernameHTML = async (Element, Username, Simple = false, Href = "https://www.xmoj.tech/userinfo.php?user=") => { +let GetUserBadge = async (Username) => { try { - //Username = Username.replaceAll(/[^a-zA-Z0-9]/g, ""); - let ID = "Username-" + Username + "-" + Math.random(); - Element.id = ID; - Element.innerHTML = `
`; - Element.appendChild(document.createTextNode(Username)); - let UserInfo = await GetUserInfo(Username); - if (UserInfo === null) { - document.getElementById(ID).innerHTML = ""; - document.getElementById(ID).appendChild(document.createTextNode(Username)); - return; - } - let HTMLData = ""; - if (!Simple) { - HTMLData += ``; - } - HTMLData += ` 500) { - HTMLData += "link-danger"; - } else if (Rating >= 400) { - HTMLData += "link-warning"; - } else if (Rating >= 300) { - HTMLData += "link-success"; - } else { - HTMLData += "link-info"; + if (localStorage.getItem("UserScript-User-" + Username + "-Badge-LastUpdateTime") != null && new Date().getTime() - parseInt(localStorage.getItem("UserScript-User-" + Username + "-Badge-LastUpdateTime")) < 1000 * 60 * 60 * 24) { + return { + "BackgroundColor": localStorage.getItem("UserScript-User-" + Username + "-Badge-BackgroundColor"), + "Color": localStorage.getItem("UserScript-User-" + Username + "-Badge-Color"), + "Content": localStorage.getItem("UserScript-User-" + Username + "-Badge-Content") } } else { - HTMLData += "link-info"; - } - HTMLData += `\";">`; - if (!Simple) { - if (AdminUserList.includes(Username)) { - HTMLData += `脚本管理员`; - } - let BadgeInfo = await GetUserBadge(Username); - if (BadgeInfo.Content != "") { - HTMLData += `${BadgeInfo.Content}`; + let BackgroundColor = ""; + let Color = ""; + let Content = ""; + await new Promise((Resolve) => { + RequestAPI("GetBadge", { + "UserID": String(Username) + }, (Response) => { + if (Response.Success) { + BackgroundColor = Response.Data.BackgroundColor; + Color = Response.Data.Color; + Content = Response.Data.Content; + } + Resolve(); + }); + }); + localStorage.setItem("UserScript-User-" + Username + "-Badge-BackgroundColor", BackgroundColor); + localStorage.setItem("UserScript-User-" + Username + "-Badge-Color", Color); + localStorage.setItem("UserScript-User-" + Username + "-Badge-Content", Content); + localStorage.setItem("UserScript-User-" + Username + "-Badge-LastUpdateTime", String(new Date().getTime())); + return { + "BackgroundColor": BackgroundColor, "Color": Color, "Content": Content } } - if (document.getElementById(ID) !== null) { - document.getElementById(ID).innerHTML = HTMLData; - document.getElementById(ID).getElementsByTagName("a")[0].appendChild(document.createTextNode(Username)); - } } catch (e) { console.error(e); if (UtilityEnabled("DebugMode")) { @@ -840,104 +729,175 @@ let GetUsernameHTML = async (Element, Username, Simple = false, Href = "https:// } } }; -/** - * Converts the given number of seconds to a formatted string representation of hours, minutes, and seconds. - * @param {number} InputSeconds - The number of seconds to convert. - * @returns {string} The formatted string representation of the input seconds. - */ -let SecondsToString = (InputSeconds) => { - try { - let Hours = Math.floor(InputSeconds / 3600); - let Minutes = Math.floor((InputSeconds % 3600) / 60); - let Seconds = InputSeconds % 60; - return (Hours < 10 ? "0" : "") + Hours + ":" + (Minutes < 10 ? "0" : "") + Minutes + ":" + (Seconds < 10 ? "0" : "") + Seconds; - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); +async function ensureMonaco() { + if (typeof monaco !== 'undefined') return; + const loaderUrl = MonacoCDN + '/loader.js'; + if (typeof require === 'undefined' || typeof require.config === 'undefined') { + await new Promise((resolve, reject) => { + const s = document.createElement('script'); + s.src = loaderUrl; + s.onload = () => { + try { require.config({ paths: { vs: MonacoCDN } }); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + resolve(); + }; + s.onerror = reject; + document.head.appendChild(s); + }); + } else { + try { require.config({ paths: { vs: MonacoCDN } }); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } } } -} -/** - * Converts a string in the format "hh:mm:ss" to the equivalent number of seconds. - * @param {string} InputString - The input string to convert. - * @returns {number} The number of seconds equivalent to the input string. - */ -let StringToSeconds = (InputString) => { - try { - let SplittedString = InputString.split(":"); - return parseInt(SplittedString[0]) * 60 * 60 + parseInt(SplittedString[1]) * 60 + parseInt(SplittedString[2]); - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + await new Promise((resolve, reject) => { + let check = null; + const done = () => { if (check) clearInterval(check); clearTimeout(timeout); resolve(); }; + const timeout = setTimeout(() => { if (check) clearInterval(check); reject(new Error('Monaco load timeout')); }, 30000); + try { + require(['vs/editor/editor.main'], function() { done(); }); + } catch (e) { + check = setInterval(() => { if (typeof monaco !== 'undefined') done(); }, 50); + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } } - } + }); } -/** - * Converts a memory size in bytes to a human-readable string representation. - * @param {number} Memory - The memory size in bytes. - * @returns {string} The human-readable string representation of the memory size. - */ -let SizeToStringSize = (Memory) => { - try { - if (UtilityEnabled("AddUnits")) { - if (Memory < 1024) { - return Memory + "KB"; - } else if (Memory < 1024 * 1024) { - return (Memory / 1024).toFixed(2) + "MB"; - } else if (Memory < 1024 * 1024 * 1024) { - return (Memory / 1024 / 1024).toFixed(2) + "GB"; - } else { - return (Memory / 1024 / 1024 / 1024).toFixed(2) + "TB"; + +async function createMonacoEditor(containerOrId, options = {}) { + await ensureMonaco(); + let container = null; + if (typeof containerOrId === 'string') container = document.getElementById(containerOrId); + else container = containerOrId; + if (!container) throw new Error('Monaco container not found'); + if (!container.id) container.id = 'monaco-' + Math.random().toString(36).slice(2,9); + // Auto-fit settings: when true Monaco will fill width and height from the page header to window bottom + let autoFitEnabled = !!options.fitToViewport; + let _autoFitHandler = null; + let innerHost = null; + const computeAvailableHeight = () => { + try { + const header = document.querySelector('nav') || document.querySelector('#navbar') || document.querySelector('.navbar') || document.querySelector('header'); + const top = header ? header.getBoundingClientRect().bottom : 0; + const bottomOffset = Number(options.bottomOffset || 0); + const available = Math.max(80, window.innerHeight - top - bottomOffset); + return available; + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } - } else { - return Memory; + return null; } - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + }; + const applyAutoFit = (callLayout, ed) => { + try { + if (!autoFitEnabled) return; + const available = computeAvailableHeight(); + if (available != null) { + try { + container.style.width = options.width || '100%'; + container.style.display = 'flex'; + container.style.alignItems = 'center'; + container.style.justifyContent = 'center'; + container.style.boxSizing = 'border-box'; + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + try { container.style.height = available + 'px'; } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + if (innerHost) { + try { + const innerRatio = (typeof options.innerRatio !== 'undefined') ? Number(options.innerRatio) : 0.95; + const wrapperHeight = Math.max(200, Math.min(Math.floor(available * innerRatio), available)); + innerHost.style.height = wrapperHeight + 'px'; + innerHost.style.width = options.width || '95%'; + if (options.maxWidth) innerHost.style.maxWidth = String(options.maxWidth); + innerHost.style.boxSizing = 'border-box'; + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + } + if (callLayout) try { if (ed && ed.layout) ed.layout(); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } } - } -}; -let CodeSizeToStringSize = (Memory) => { + }; + const key = options.localStorageKey || ('XMOJ-Monaco-' + location.pathname + ':' + container.id); + const theme = options.theme || (typeof UtilityEnabled === 'function' && UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs'); + const readOnly = !!options.readOnly; + const language = options.language || (options.mode === 'text/x-c++src' ? 'cpp' : (options.mode || 'cpp')); + // create an inner host so we can center the editor both horizontally and vertically try { - if (UtilityEnabled("AddUnits")) { - if (Memory < 1024) { - return Memory + "B"; - } else if (Memory < 1024 * 1024) { - return (Memory / 1024).toFixed(2) + "KB"; - } else if (Memory < 1024 * 1024 * 1024) { - return (Memory / 1024 / 1024).toFixed(2) + "MB"; - } else { - return (Memory / 1024 / 1024 / 1024).toFixed(2) + "GB"; - } - } else { - return Memory; + innerHost = document.createElement('div'); + innerHost.className = 'monaco-editor-host'; + innerHost.style.boxSizing = 'border-box'; + // default sizing; will be recalculated by applyAutoFit when enabled + innerHost.style.width = options.width || (autoFitEnabled ? '95%' : '100%'); + if (!autoFitEnabled) { + if (options.height) innerHost.style.height = (typeof options.height === 'number' ? options.height + 'px' : options.height); } - } catch (e) { + container.appendChild(innerHost); + } catch (e) { console.error(e); } + + const editor = monaco.editor.create(innerHost || container, { + value: options.value || '', + language: language, + automaticLayout: !!options.automaticLayout, + theme: theme, + minimap: (typeof options.minimap !== 'undefined' ? options.minimap : { enabled: false }), + readOnly: readOnly, + lineNumbers: typeof options.lineNumbers !== 'undefined' ? options.lineNumbers : 'on', + tabSize: options.tabSize || 4 + }); + // apply initial auto-fit (no layout call yet because editor is not fully initialized until after creation) + try { applyAutoFit(false, editor); } catch (e) { console.error(e); if (UtilityEnabled("DebugMode")) { SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } -}; -/** - * Converts a time value to a string representation. - * @param {number} Time - The time value to convert. - * @returns {string|number} - The converted time value as a string, or the original value if UtilityEnabled("AddUnits") is false. - */ -let TimeToStringTime = (Time) => { + // after creation, ensure Monaco layout matches the computed size and listen for viewport changes try { - if (UtilityEnabled("AddUnits")) { - if (Time < 1000) { - return Time + "ms"; - } else if (Time < 1000 * 60) { - return (Time / 1000).toFixed(2) + "s"; + applyAutoFit(true, editor); + if (autoFitEnabled && typeof window !== 'undefined') { + _autoFitHandler = () => applyAutoFit(true, editor); + window.addEventListener('resize', _autoFitHandler); + window.addEventListener('orientationchange', _autoFitHandler); + window.addEventListener('scroll', _autoFitHandler); + try { editor.onDidDispose(() => { window.removeEventListener('resize', _autoFitHandler); window.removeEventListener('orientationchange', _autoFitHandler); window.removeEventListener('scroll', _autoFitHandler); }); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } } - } else { - return Time; } } catch (e) { console.error(e); @@ -945,16 +905,10 @@ let TimeToStringTime = (Time) => { SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } -}; -/** - * Tidies up the given table by applying Bootstrap styling and removing unnecessary attributes. - * - * @param {HTMLElement} Table - The table element to be tidied up. - */ -let TidyTable = (Table) => { try { - if (UtilityEnabled("NewBootstrap") && Table != null) { - Table.className = "table table-hover"; + if (options.restoreOnLoad !== false) { + const saved = localStorage.getItem(key); + if (saved !== null && saved !== 'null') editor.setValue(saved); } } catch (e) { console.error(e); @@ -962,1383 +916,1419 @@ let TidyTable = (Table) => { SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } -}; -let UtilityEnabled = (Name) => { - try { - if (localStorage.getItem("UserScript-Setting-" + Name) == null) { - const defaultOffItems = ["DebugMode", "SuperDebug", "ReplaceXM"]; - localStorage.setItem("UserScript-Setting-" + Name, defaultOffItems.includes(Name) ? "false" : "true"); - } - return localStorage.getItem("UserScript-Setting-" + Name) == "true"; - } catch (e) { + let saveTimer = null; + const doSave = () => { try { localStorage.setItem(key, editor.getValue()); } catch (e) { console.error(e); if (UtilityEnabled("DebugMode")) { SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } - } -}; -let storeCredential = async (username, password) => { - if ('credentials' in navigator && window.PasswordCredential) { - try { - const credential = new PasswordCredential({id: username, password: password}); - await navigator.credentials.store(credential); - } catch (e) { + } }; + editor.onDidChangeModelContent(() => { if (saveTimer) clearTimeout(saveTimer); saveTimer = setTimeout(doSave, options.saveDebounce || 500); }); + const adapter = { + getValue: () => editor.getValue(), + setValue: (v) => { editor.setValue(v); }, + setSize: (w, h) => { const el = innerHost || container; if (w) el.style.width = w; if (h) { try { if (h !== 'auto') autoFitEnabled = false; } catch (e) { console.error(e); - } - } -}; -let getCredential = async () => { - if ('credentials' in navigator && window.PasswordCredential) { - try { - return await navigator.credentials.get({password: true, mediation: 'optional'}); - } catch (e) { + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } if (h === 'auto') { try { const lines = editor.getModel().getLineCount(); el.style.height = Math.max(80, Math.min(1200, lines * 18)) + 'px'; } catch (e) { el.style.height = '80px'; } } else el.style.height = h; } try { editor.layout(); } catch (e) { console.error(e); - } - } - return null; -}; -let clearCredential = async () => { - if ('credentials' in navigator && window.PasswordCredential) { - try { - await navigator.credentials.preventSilentAccess(); - } catch (e) { + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } }, + getWrapperElement: () => innerHost || container, + focus: () => { try { editor.focus(); } catch (e) { console.error(e); - } - } -}; -let RequestAPI = (Action, Data, CallBack) => { - try { - let Session = ""; - let Temp = document.cookie.split(";"); - for (let i = 0; i < Temp.length; i++) { - if (Temp[i].includes("PHPSESSID")) { - Session = Temp[i].split("=")[1]; + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } - } - if (Session === "") { //The cookie is httpOnly - GM.cookie.set({ - name: 'PHPSESSID', - value: (Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)).substring(0, 28), - path: "/" - }) - .then(() => { - console.log('Reset PHPSESSID successfully.'); - location.reload(); //Refresh the page to auth with the new PHPSESSID - }) - .catch((error) => { - console.error(error); - }); - } - let PostData = { - "Authentication": { - "SessionID": Session, "Username": CurrentUsername, - }, "Data": Data, "Version": GM_info.script.version, "DebugMode": UtilityEnabled("DebugMode") - }; - let DataString = JSON.stringify(PostData); - if (UtilityEnabled("DebugMode")) { - console.log("Sent for", Action + ":", DataString); - } - GM_xmlhttpRequest({ - method: "POST", - url: (UtilityEnabled("SuperDebug") ? "http://127.0.0.1:8787/" : "https://api.xmoj-script.uk/") + Action, - headers: { - "Content-Type": "application/json", - "Cache-Control": "no-cache", - "XMOJ-UserID": CurrentUsername, - "XMOJ-Script-Version": GM_info.script.version, - "DebugMode": UtilityEnabled("DebugMode") - }, - data: DataString, - onload: (Response) => { - if (UtilityEnabled("DebugMode")) { - console.log("Received for", Action + ":", Response.responseText); - } - try { - CallBack(JSON.parse(Response.responseText)); - } catch (Error) { - console.log(Response.responseText); - } + } }, + _monacoEditor: editor, + showFind: () => { try { editor.trigger('', 'editor.action.startFindReplaceAction'); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } - }); - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } - } -}; -let SyncSettingsToCloud = (CallBack) => { - if (!CurrentUsername) { - if (CallBack) CallBack({ Success: false, Message: "用户未登录" }); - return; - } - if (!UtilityEnabled("CloudSync")) { - if (CallBack) CallBack({ Success: false, Message: "云同步已禁用" }); - return; - } - let Settings = {}; - for (let i = 0; i < localStorage.length; i++) { - let key = localStorage.key(i); - if (key && key.startsWith("UserScript-Setting-")) { - Settings[key.replace("UserScript-Setting-", "")] = localStorage.getItem(key); - } - } - RequestAPI("SetUserSettings", {"Settings": JSON.stringify(Settings)}, (Response) => { - if (UtilityEnabled("DebugMode")) { - if (Response.Success) { - console.log("设置已同步到云端"); - } else { - console.error("设置云端同步失败:", Response.Message); + } }, + goToLine: (line) => { try { editor.setPosition({ lineNumber: parseInt(line) || 1, column: 1 }); editor.revealPositionInCenter({ lineNumber: parseInt(line) || 1, column: 1 }); editor.focus(); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } }, + selectRange: (sLine, sCol, eLine, eCol) => { try { editor.setSelection({ startLineNumber: sLine, startColumn: sCol, endLineNumber: eLine, endColumn: eCol }); editor.revealRangeInCenter({ startLineNumber: sLine, startColumn: sCol, endLineNumber: eLine, endColumn: eCol }); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } }, + saveToLocal: doSave, + localStorageKey: key + , + dispose: () => { + try { + if (_autoFitHandler && typeof window !== 'undefined') { + window.removeEventListener('resize', _autoFitHandler); + window.removeEventListener('orientationchange', _autoFitHandler); + window.removeEventListener('scroll', _autoFitHandler); + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } } - } - if (CallBack) CallBack(Response); - }); -}; - -let PeriodicCloudSync = () => { - if (!CurrentUsername || !UtilityEnabled("CloudSync")) return; - const lastSync = parseInt(localStorage.getItem("UserScript-CloudSync-LastSync") || "0"); - if (Date.now() - lastSync < 60 * 60 * 1000) return; - RequestAPI("GetUserSettings", {}, (Response) => { - if (Response.Success) { - localStorage.setItem("UserScript-CloudSync-LastSync", String(Date.now())); - const cloudSettings = (Response.Data && Response.Data.Settings) || {}; - if (Object.keys(cloudSettings).length > 0) { - let themeChanged = false; - for (let key in cloudSettings) { - const rawValue = String(cloudSettings[key]); - const localKey = "UserScript-Setting-" + key; - if (localStorage.getItem(localKey) !== rawValue) { - localStorage.setItem(localKey, rawValue); - if (key === "Theme") themeChanged = true; + try { + if (editor) { + try { + const model = (editor.getModel && editor.getModel()) || null; + if (model && model.dispose) model.dispose(); + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + try { editor.dispose(); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } } } - if (themeChanged) initTheme(); - } - SyncSettingsToCloud(); - } - }); -}; - -unsafeWindow.GetContestProblemList = async function(RefreshList) { - try { - const contestReq = await fetch("https://www.xmoj.tech/contest.php?cid=" + SearchParams.get("cid")); - const res = await contestReq.text(); - if (contestReq.status === 200 && res.indexOf("比赛尚未开始或私有,不能查看题目。") === -1) { - const parser = new DOMParser(); - const dom = parser.parseFromString(res, "text/html"); - const rows = (dom.querySelector("#problemset > tbody")).rows; - let problemList = []; - for (let i = 0; i < rows.length; i++) { - problemList.push({ - "title": rows[i].children[2].innerText, - "url": rows[i].children[2].children[0].href - }); + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } } - localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList)); - if (RefreshList) location.reload(); - } - } catch (e) { - console.error(e); - } -} - -// WebSocket Notification System -let NotificationSocket = null; -let NotificationSocketReconnectAttempts = 0; -let NotificationSocketReconnectDelay = 1000; -let NotificationSocketPingInterval = null; -let NotificationSocketReconnectTimer = null; - -function GetPHPSESSID() { - let Session = ""; - let Temp = document.cookie.split(";"); - for (let i = 0; i < Temp.length; i++) { - if (Temp[i].includes("PHPSESSID")) { - Session = Temp[i].split("=")[1]; - break; } - } - return Session; + }; + return adapter; } -function ConnectNotificationSocket() { - try { - // Clear any pending reconnection timer to prevent duplicate connections - if (NotificationSocketReconnectTimer) { - clearTimeout(NotificationSocketReconnectTimer); - NotificationSocketReconnectTimer = null; +(function() { + const shim = function(containerOrTextArea, options) { + let container = containerOrTextArea; + let initialValue = ''; + if (container && container.tagName && container.tagName.toLowerCase() === 'textarea') { + initialValue = container.value || container.textContent || ''; + const div = document.createElement('div'); + // copy most attributes from the original textarea to the replacement div (except value) + try { + for (let i = 0; i < container.attributes.length; i++) { + const a = container.attributes[i]; + if (!a) continue; + if (a.name === 'value') continue; + try { div.setAttribute(a.name, a.value); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + div.classList.add('codemirror-shim-host'); + // preserve inline styles if any + try { if (container.style && container.style.cssText) div.style.cssText = container.style.cssText; } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + container.parentNode.replaceChild(div, container); + container = div; + } else if (typeof containerOrTextArea === 'string') { + container = document.querySelector(containerOrTextArea) || document.getElementById(containerOrTextArea); } - - let Session = GetPHPSESSID(); - if (Session === "") { + if (!container) { container = document.createElement('div'); document.body.appendChild(container); } + container._cmValue = (options && options.value) ? options.value : initialValue; + // show a centered Loading... placeholder while Monaco initializes + try { + const _monacoLoadingEl = document.createElement('div'); + _monacoLoadingEl.className = 'monaco-loading'; + _monacoLoadingEl.style.width = '100%'; + _monacoLoadingEl.style.height = '100%'; + _monacoLoadingEl.style.display = 'flex'; + _monacoLoadingEl.style.alignItems = 'center'; + _monacoLoadingEl.style.justifyContent = 'center'; + _monacoLoadingEl.textContent = 'Loading...'; + container.appendChild(_monacoLoadingEl); + } catch (e) { + console.error(e); if (UtilityEnabled("DebugMode")) { - console.log("WebSocket: PHPSESSID not available, skipping connection"); + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } - return; - } - - let wsUrl = (UtilityEnabled("SuperDebug") ? "ws://127.0.0.1:8787" : "wss://api.xmoj-script.uk") + "/ws/notifications?SessionID=" + Session; - - if (UtilityEnabled("DebugMode")) { - console.log("WebSocket: Connecting to", wsUrl); } - - NotificationSocket = new WebSocket(wsUrl); - - NotificationSocket.onopen = () => { + let _lastSetSizeArgs = null; + const placeholderAdapter = { + getValue: () => container._cmValue || '', + setValue: (v) => { container._cmValue = v; if (container._cmEditor) try { container._cmEditor.setValue(v); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } }, + setSize: (w, h) => { _lastSetSizeArgs = [w, h]; if (w) container.style.width = w; if (h) { if (h === 'auto') container.style.height = 'auto'; else container.style.height = h; } if (container._cmEditor) try { container._cmEditor.layout(); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } }, + getWrapperElement: () => container, + focus: () => { if (container._cmEditor) try { container._cmEditor.focus(); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } }, + _monacoEditor: null + }; + (async () => { + try { + const opts = options || {}; + await ensureMonaco(); + const lang = opts.mode === 'text/x-c++src' || (opts.mode && opts.mode.indexOf('c++') !== -1) ? 'cpp' : (opts.language || 'cpp'); + const monacoAdapter = await createMonacoEditor(container, Object.assign({ language: lang, value: container._cmValue || '', readOnly: !!opts.readOnly, theme: (typeof UtilityEnabled === 'function' && UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs') }, opts)); + container._cmEditor = monacoAdapter._monacoEditor; + placeholderAdapter.getValue = monacoAdapter.getValue; + placeholderAdapter.setValue = monacoAdapter.setValue; + placeholderAdapter.setSize = monacoAdapter.setSize; + placeholderAdapter.getWrapperElement = monacoAdapter.getWrapperElement; + placeholderAdapter.focus = monacoAdapter.focus; + placeholderAdapter._monacoEditor = monacoAdapter._monacoEditor; + try { const _l = container.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + if (_lastSetSizeArgs) monacoAdapter.setSize(_lastSetSizeArgs[0], _lastSetSizeArgs[1]); + } catch (e) { console.error(e); try { const _l = container.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (_) {} } + })(); + return placeholderAdapter; + }; + shim.fromTextArea = function(textarea, options) { return shim(textarea, options); }; + shim.MergeView = function(container, options) { + options = options || {}; + let el = container; + if (typeof container === 'string') el = document.getElementById(container) || document.querySelector(container); + if (!el) { el = document.createElement('div'); document.body.appendChild(el); } + // show a centered Loading... placeholder while Monaco initializes + try { + const _mergeLoadingEl = document.createElement('div'); + _mergeLoadingEl.className = 'monaco-loading'; + _mergeLoadingEl.style.width = '100%'; + _mergeLoadingEl.style.height = '100%'; + _mergeLoadingEl.style.display = 'flex'; + _mergeLoadingEl.style.alignItems = 'center'; + _mergeLoadingEl.style.justifyContent = 'center'; + _mergeLoadingEl.textContent = 'Loading...'; + el.appendChild(_mergeLoadingEl); + } catch (e) { + console.error(e); if (UtilityEnabled("DebugMode")) { - console.log("WebSocket: Connected successfully"); + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } - NotificationSocketReconnectAttempts = 0; - NotificationSocketReconnectDelay = 1000; + } + const wrapper = { ignoreWhitespace: !!options.ignoreWhitespace, _diffEditor: null, _originalModel: null, _modifiedModel: null }; + (async () => { + try { + await ensureMonaco(); + // create an inner host so the diff editor can be centered if requested + let mvInner = null; + try { + mvInner = document.createElement('div'); + mvInner.className = 'monaco-merge-host'; + mvInner.style.boxSizing = 'border-box'; + mvInner.style.width = options.width || '95%'; + if (options.fitToViewport) { + try { el.style.display = 'flex'; el.style.alignItems = 'center'; el.style.justifyContent = 'center'; } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + const header = document.querySelector('nav') || document.querySelector('#navbar') || document.querySelector('.navbar') || document.querySelector('header'); + const top = header ? header.getBoundingClientRect().bottom : 0; + const bottomOffset = Number(options.bottomOffset || 0); + const available = Math.max(80, window.innerHeight - top - bottomOffset); + const wrapperHeight = Math.max(200, Math.min(Math.floor(available * 0.95), available)); + mvInner.style.height = wrapperHeight + 'px'; + } else { + if (options.height) mvInner.style.height = (typeof options.height === 'number' ? options.height + 'px' : options.height); + } + el.appendChild(mvInner); + } catch (e) { mvInner = null; } - // Start ping keepalive - if (NotificationSocketPingInterval) { - clearInterval(NotificationSocketPingInterval); - } - NotificationSocketPingInterval = setInterval(() => { - if (NotificationSocket && NotificationSocket.readyState === WebSocket.OPEN) { - NotificationSocket.send(JSON.stringify({ type: 'ping' })); + const diffEditor = monaco.editor.createDiffEditor(mvInner || el, { readOnly: !!options.readOnly, theme: (typeof UtilityEnabled === 'function' && UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs'), minimap: { enabled: false }, automaticLayout: true, ignoreTrimWhitespace: !!wrapper.ignoreWhitespace }); + const orig = options.value || ''; + const mod = options.orig || ''; + const isCpp = options.mode === 'text/x-c++src' || (typeof options.mode === 'string' && options.mode.indexOf('c++') !== -1); + const lang = isCpp ? 'cpp' : (options.language || 'cpp'); + const originalModel = monaco.editor.createModel(orig, lang); + const modifiedModel = monaco.editor.createModel(mod, lang); + diffEditor.setModel({ original: originalModel, modified: modifiedModel }); + wrapper._diffEditor = diffEditor; + wrapper._originalModel = originalModel; + wrapper._modifiedModel = modifiedModel; + try { const _l = el.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (e) { + console.error(e); if (UtilityEnabled("DebugMode")) { - console.log("WebSocket: Sent ping"); + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } - } else { - clearInterval(NotificationSocketPingInterval); } - }, 30000); - }; - - NotificationSocket.onmessage = (event) => { - HandleNotificationMessage(event); - }; - - NotificationSocket.onerror = (error) => { - if (UtilityEnabled("DebugMode")) { - console.error("WebSocket: Error", error); - } - }; - - NotificationSocket.onclose = (event) => { - if (UtilityEnabled("DebugMode")) { - console.log("WebSocket: Connection closed", event.code, event.reason); - } - if (NotificationSocketPingInterval) { - clearInterval(NotificationSocketPingInterval); - } - ReconnectNotificationSocket(); - }; - } catch (e) { - console.error("WebSocket: Failed to connect", e); - ReconnectNotificationSocket(); - } -} - -function ReconnectNotificationSocket() { - const delay = Math.min(NotificationSocketReconnectDelay * Math.pow(2, NotificationSocketReconnectAttempts), 30000); - NotificationSocketReconnectAttempts++; - - if (UtilityEnabled("DebugMode")) { - console.log(`WebSocket: Reconnecting in ${delay}ms (attempt ${NotificationSocketReconnectAttempts})`); - } - - NotificationSocketReconnectTimer = setTimeout(() => { - ConnectNotificationSocket(); - }, delay); -} - -function HandleNotificationMessage(event) { + } catch (e) { console.error(e); try { const _l = el.querySelector('.monaco-loading'); if (_l) _l.remove(); } catch (_) {} } + })(); + return wrapper; + }; + window.CodeMirror = shim; +})(); +/** + * Dispose any transient Monaco editors created inside the ErrorMessage area (freopen snippets). + */ +function _xmoj_disposeErrorMessageEditors() { try { - const notification = JSON.parse(event.data); - - if (UtilityEnabled("DebugMode")) { - console.log("WebSocket: Received message", notification); - } - - if (notification.type === 'connected') { - if (UtilityEnabled("DebugMode")) { - console.log("WebSocket: Server confirmed connection at timestamp", notification.timestamp); - } - } else if (notification.type === 'bbs_mention') { - if (UtilityEnabled("BBSPopup")) { - CreateAndShowBBSMentionToast(notification.data); - } - } else if (notification.type === 'mail_mention') { - if (UtilityEnabled("MessagePopup")) { - CreateAndShowMailMentionToast(notification.data); - } - } else if (notification.type === 'pong') { - if (UtilityEnabled("DebugMode")) { - console.log("WebSocket: Received pong"); - } - } - } catch (e) { - console.error("WebSocket: Failed to handle message", e); - } -} - -function CreateAndShowBBSMentionToast(mention) { - let ToastContainer = document.querySelector(".toast-container"); - if (!ToastContainer) return; - - let Toast = document.createElement("div"); - Toast.classList.add("toast"); - Toast.setAttribute("role", "alert"); - let ToastHeader = document.createElement("div"); - ToastHeader.classList.add("toast-header"); - let ToastTitle = document.createElement("strong"); - ToastTitle.classList.add("me-auto"); - ToastTitle.innerHTML = "提醒:有人@你"; - ToastHeader.appendChild(ToastTitle); - let ToastTime = document.createElement("small"); - ToastTime.classList.add("text-body-secondary"); - ToastTime.innerHTML = GetRelativeTime(mention.MentionTime); - ToastHeader.appendChild(ToastTime); - let ToastCloseButton = document.createElement("button"); - ToastCloseButton.type = "button"; - ToastCloseButton.classList.add("btn-close"); - ToastCloseButton.setAttribute("data-bs-dismiss", "toast"); - ToastHeader.appendChild(ToastCloseButton); - Toast.appendChild(ToastHeader); - let ToastBody = document.createElement("div"); - ToastBody.classList.add("toast-body"); - ToastBody.innerHTML = "讨论" + escapeHTML(mention.PostTitle) + "有新回复"; - let ToastFooter = document.createElement("div"); - ToastFooter.classList.add("mt-2", "pt-2", "border-top"); - let ToastDismissButton = document.createElement("button"); - ToastDismissButton.type = "button"; - ToastDismissButton.classList.add("btn", "btn-secondary", "btn-sm", "me-2"); - ToastDismissButton.innerText = "忽略"; - ToastDismissButton.addEventListener("click", () => { - RequestAPI("ReadBBSMention", { - "MentionID": Number(mention.MentionID) - }, () => { - }); - Toast.remove(); - }); - ToastFooter.appendChild(ToastDismissButton); - let ToastViewButton = document.createElement("button"); - ToastViewButton.type = "button"; - ToastViewButton.classList.add("btn", "btn-primary", "btn-sm"); - ToastViewButton.innerText = "查看"; - ToastViewButton.addEventListener("click", () => { - open("https://www.xmoj.tech/discuss3/thread.php?tid=" + mention.PostID + '&page=' + mention.PageNumber, "_blank"); - RequestAPI("ReadBBSMention", { - "MentionID": Number(mention.MentionID) - }, () => { - }); - }); - ToastFooter.appendChild(ToastViewButton); - ToastBody.appendChild(ToastFooter); - Toast.appendChild(ToastBody); - ToastContainer.appendChild(Toast); - new bootstrap.Toast(Toast).show(); -} - -function CreateAndShowMailMentionToast(mention) { - let ToastContainer = document.querySelector(".toast-container"); - if (!ToastContainer) return; - - let Toast = document.createElement("div"); - Toast.classList.add("toast"); - Toast.setAttribute("role", "alert"); - let ToastHeader = document.createElement("div"); - ToastHeader.classList.add("toast-header"); - let ToastTitle = document.createElement("strong"); - ToastTitle.classList.add("me-auto"); - ToastTitle.innerHTML = "提醒:有新消息"; - ToastHeader.appendChild(ToastTitle); - let ToastTime = document.createElement("small"); - ToastTime.classList.add("text-body-secondary"); - ToastTime.innerHTML = GetRelativeTime(mention.MentionTime); - ToastHeader.appendChild(ToastTime); - let ToastCloseButton = document.createElement("button"); - ToastCloseButton.type = "button"; - ToastCloseButton.classList.add("btn-close"); - ToastCloseButton.setAttribute("data-bs-dismiss", "toast"); - ToastHeader.appendChild(ToastCloseButton); - Toast.appendChild(ToastHeader); - let ToastBody = document.createElement("div"); - ToastBody.classList.add("toast-body"); - let ToastUser = document.createElement("span"); - GetUsernameHTML(ToastUser, mention.FromUserID); - ToastBody.appendChild(ToastUser); - ToastBody.appendChild(document.createTextNode(" 给你发了一封短消息")); - let ToastFooter = document.createElement("div"); - ToastFooter.classList.add("mt-2", "pt-2", "border-top"); - let ToastDismissButton = document.createElement("button"); - ToastDismissButton.type = "button"; - ToastDismissButton.classList.add("btn", "btn-secondary", "btn-sm", "me-2"); - ToastDismissButton.setAttribute("data-bs-dismiss", "toast"); - ToastDismissButton.innerText = "忽略"; - ToastDismissButton.addEventListener("click", () => { - RequestAPI("ReadMailMention", { - "MentionID": Number(mention.MentionID) - }, () => { - }); - }); - ToastFooter.appendChild(ToastDismissButton); - let ToastViewButton = document.createElement("button"); - ToastViewButton.type = "button"; - ToastViewButton.classList.add("btn", "btn-primary", "btn-sm"); - ToastViewButton.innerText = "查看"; - ToastViewButton.addEventListener("click", () => { - open("https://www.xmoj.tech/mail.php?to_user=" + mention.FromUserID, "_blank"); - RequestAPI("ReadMailMention", { - "MentionID": Number(mention.MentionID) - }, () => { - }); - }); - ToastFooter.appendChild(ToastViewButton); - ToastBody.appendChild(ToastFooter); - Toast.appendChild(ToastBody); - ToastContainer.appendChild(Toast); - new bootstrap.Toast(Toast).show(); -} - -function PollNotifications() { - // Clear toast container once before fetching to prevent race condition - if (UtilityEnabled("BBSPopup") || UtilityEnabled("MessagePopup")) { - let ToastContainer = document.querySelector(".toast-container"); - if (ToastContainer) { - ToastContainer.innerHTML = ""; + const arr = window._xmoj_temp_error_editors || []; + if (arr.length) { + arr.forEach((ed) => { + try { + const m = (ed && ed.getModel) ? ed.getModel() : null; + if (m && m.dispose) try { m.dispose(); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + try { if (ed && ed.dispose) ed.dispose(); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + }); + } + window._xmoj_temp_error_editors = []; + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } - if (UtilityEnabled("BBSPopup")) { - RequestAPI("GetBBSMentionList", {}, (Response) => { - if (Response.Success) { - let MentionList = Response.Data.MentionList; - for (let i = 0; i < MentionList.length; i++) { - CreateAndShowBBSMentionToast(MentionList[i]); + try { + const hosts = document.querySelectorAll('[data-xmoj-error-editor]'); + hosts.forEach((h) => { + try { if (h._monacoEditor && h._monacoEditor.dispose) h._monacoEditor.dispose(); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } - }); - } - if (UtilityEnabled("MessagePopup")) { - RequestAPI("GetMailMentionList", {}, (Response) => { - if (Response.Success) { - let MentionList = Response.Data.MentionList; - for (let i = 0; i < MentionList.length; i++) { - CreateAndShowMailMentionToast(MentionList[i]); + try { delete h._monacoEditor; } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + try { h.removeAttribute('data-xmoj-error-editor'); } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } }); + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } } } - -GM_registerMenuCommand("清除缓存", () => { - let Temp = []; - for (let i = 0; i < localStorage.length; i++) { - if (localStorage.key(i).startsWith("UserScript-User-")) { - Temp.push(localStorage.key(i)); +/** + * Sets the HTML content of an element to display a username with optional additional information. + * @param {HTMLElement} Element - The element to set the HTML content. + * @param {string} Username - The username to display. + * @param {boolean} [Simple=false] - Indicates whether to display additional information or not. + * @param {string} [Href="https://www.xmoj.tech/userinfo.php?user="] - The URL to link the username to. + * @returns {Promise} - A promise that resolves when the HTML content is set. + */ +let GetUsernameHTML = async (Element, Username, Simple = false, Href = "https://www.xmoj.tech/userinfo.php?user=") => { + try { + //Username = Username.replaceAll(/[^a-zA-Z0-9]/g, ""); + let ID = "Username-" + Username + "-" + Math.random(); + Element.id = ID; + Element.innerHTML = `
`; + Element.appendChild(document.createTextNode(Username)); + let UserInfo = await GetUserInfo(Username); + if (UserInfo === null) { + document.getElementById(ID).innerHTML = ""; + document.getElementById(ID).appendChild(document.createTextNode(Username)); + return; + } + let HTMLData = ""; + if (!Simple) { + HTMLData += ``; + } + HTMLData += ` 500) { + HTMLData += "link-danger"; + } else if (Rating >= 400) { + HTMLData += "link-warning"; + } else if (Rating >= 300) { + HTMLData += "link-success"; + } else { + HTMLData += "link-info"; + } + } else { + HTMLData += "link-info"; + } + HTMLData += `\";">`; + if (!Simple) { + if (AdminUserList.includes(Username)) { + HTMLData += `脚本管理员`; + } + let BadgeInfo = await GetUserBadge(Username); + if (BadgeInfo.Content != "") { + HTMLData += `${BadgeInfo.Content}`; + } + } + if (document.getElementById(ID) !== null) { + document.getElementById(ID).innerHTML = HTMLData; + document.getElementById(ID).getElementsByTagName("a")[0].appendChild(document.createTextNode(Username)); + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } - for (let i = 0; i < Temp.length; i++) { - localStorage.removeItem(Temp[i]); - } - location.reload(); -}); -GM_registerMenuCommand("重置数据", () => { - if (confirm("确定要重置数据吗?")) { - localStorage.clear(); - location.reload(); +}; +/** + * Converts the given number of seconds to a formatted string representation of hours, minutes, and seconds. + * @param {number} InputSeconds - The number of seconds to convert. + * @returns {string} The formatted string representation of the input seconds. + */ +let SecondsToString = (InputSeconds) => { + try { + let Hours = Math.floor(InputSeconds / 3600); + let Minutes = Math.floor((InputSeconds % 3600) / 60); + let Seconds = InputSeconds % 60; + return (Hours < 10 ? "0" : "") + Hours + ":" + (Minutes < 10 ? "0" : "") + Minutes + ":" + (Seconds < 10 ? "0" : "") + Seconds; + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } } -}); - -// Wrapped in an async IIFE so that `await` is valid in Violentmonkey, -// which executes userscripts as classic scripts (not ES modules). -(async () => { -//otherwise CurrentUsername might be undefined -let loginStatus; -await fetch("https://www.xmoj.tech/loginpage.php") - .then((response) => response.text()) - .then((data) => (loginStatus = data)); -const logined = loginStatus == "Please logout First!"; -if (UtilityEnabled("AutoLogin") && document.querySelector("body > a:nth-child(1)") != null && document.querySelector("body > a:nth-child(1)").innerText == "请登录后继续操作") { - localStorage.setItem("UserScript-LastPage", location.pathname + location.search); - location.href = "https://www.xmoj.tech/loginpage.php"; - return; } - -SearchParams = new URLSearchParams(location.search); -let ServerURL = (UtilityEnabled("DebugMode") ? "https://ghpages.xmoj-script.uk/" : "https://www.xmoj-script.uk") -const profileElement = document.querySelector("#profile"); -if (profileElement === null) { - if (!logined) { - location.href = "https://www.xmoj.tech/loginpage.php"; +/** + * Converts a string in the format "hh:mm:ss" to the equivalent number of seconds. + * @param {string} InputString - The input string to convert. + * @returns {number} The number of seconds equivalent to the input string. + */ +let StringToSeconds = (InputString) => { + try { + let SplittedString = InputString.split(":"); + return parseInt(SplittedString[0]) * 60 * 60 + parseInt(SplittedString[1]) * 60 + parseInt(SplittedString[2]); + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } } - return; } -CurrentUsername = profileElement.innerText; -CurrentUsername = CurrentUsername.replaceAll(/[^a-zA-Z0-9]/g, ""); -let IsAdmin = AdminUserList.indexOf(CurrentUsername) !== -1; - -const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); -const applyTheme = (theme) => { - document.querySelector("html").setAttribute("data-bs-theme", theme); - localStorage.setItem("UserScript-Setting-DarkMode", theme === "dark" ? "true" : "false"); -}; -const applySystemTheme = (e) => applyTheme(e.matches ? "dark" : "light"); -initTheme = () => { - const saved = localStorage.getItem("UserScript-Setting-Theme") || "auto"; - if (saved === "auto") { - applyTheme(prefersDark.matches ? "dark" : "light"); - prefersDark.addEventListener("change", applySystemTheme); - } else { - applyTheme(saved); - prefersDark.removeEventListener("change", applySystemTheme); +/** + * Converts a memory size in bytes to a human-readable string representation. + * @param {number} Memory - The memory size in bytes. + * @returns {string} The human-readable string representation of the memory size. + */ +let SizeToStringSize = (Memory) => { + try { + if (UtilityEnabled("AddUnits")) { + if (Memory < 1024) { + return Memory + "KB"; + } else if (Memory < 1024 * 1024) { + return (Memory / 1024).toFixed(2) + "MB"; + } else if (Memory < 1024 * 1024 * 1024) { + return (Memory / 1024 / 1024).toFixed(2) + "GB"; + } else { + return (Memory / 1024 / 1024 / 1024).toFixed(2) + "TB"; + } + } else { + return Memory; + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } } }; -initTheme(); -PeriodicCloudSync(); -setInterval(PeriodicCloudSync, 60 * 60 * 1000); - - -class NavbarStyler { - constructor() { - try { - this.navbar = document.querySelector('.navbar.navbar-expand-lg.bg-body-tertiary'); - if (this.navbar && UtilityEnabled("NewTopBar")) { - this.init(); - } - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); +let CodeSizeToStringSize = (Memory) => { + try { + if (UtilityEnabled("AddUnits")) { + if (Memory < 1024) { + return Memory + "B"; + } else if (Memory < 1024 * 1024) { + return (Memory / 1024).toFixed(2) + "KB"; + } else if (Memory < 1024 * 1024 * 1024) { + return (Memory / 1024 / 1024).toFixed(2) + "MB"; + } else { + return (Memory / 1024 / 1024 / 1024).toFixed(2) + "GB"; } + } else { + return Memory; + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } - - init() { - try { - this.applyStyles(); - this.createOverlay(); - this.createSpacer(); - window.addEventListener('resize', () => this.updateBlurOverlay()); - this.updateBlurOverlay(); - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); +}; +/** + * Converts a time value to a string representation. + * @param {number} Time - The time value to convert. + * @returns {string|number} - The converted time value as a string, or the original value if UtilityEnabled("AddUnits") is false. + */ +let TimeToStringTime = (Time) => { + try { + if (UtilityEnabled("AddUnits")) { + if (Time < 1000) { + return Time + "ms"; + } else if (Time < 1000 * 60) { + return (Time / 1000).toFixed(2) + "s"; } + } else { + return Time; + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } - - applyStyles() { - try { - let n = this.navbar; - n.classList.add('fixed-top', 'container', 'ml-auto'); - if (UtilityEnabled("MonochromeUI")) { - Object.assign(n.style, { - position: 'fixed', - borderRadius: '0', - boxShadow: '0 4px 8px rgba(0, 0, 0, 0.5)', - margin: '0', - maxWidth: '100%', - backgroundColor: 'rgba(255, 255, 255, 0)', - opacity: '0.75', - zIndex: '1000' - }); - } else { - Object.assign(n.style, { - position: 'fixed', - borderRadius: '28px', - boxShadow: '0 4px 8px rgba(0, 0, 0, 0.5)', - margin: '16px auto', - backgroundColor: 'rgba(255, 255, 255, 0)', - opacity: '0.75', - zIndex: '1000' - }); - } - } catch (e) { - console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } +}; +/** + * Tidies up the given table by applying Bootstrap styling and removing unnecessary attributes. + * + * @param {HTMLElement} Table - The table element to be tidied up. + */ +let TidyTable = (Table) => { + try { + if (UtilityEnabled("NewBootstrap") && Table != null) { + Table.className = "table table-hover"; + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } } - - createOverlay() { +}; +let UtilityEnabled = (Name) => { + try { + if (localStorage.getItem("UserScript-Setting-" + Name) == null) { + const defaultOffItems = ["DebugMode", "SuperDebug", "ReplaceXM"]; + localStorage.setItem("UserScript-Setting-" + Name, defaultOffItems.includes(Name) ? "false" : "true"); + } + return localStorage.getItem("UserScript-Setting-" + Name) == "true"; + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } +}; +let storeCredential = async (username, password) => { + if ('credentials' in navigator && window.PasswordCredential) { try { - if (!document.getElementById('blur-overlay')) { - let overlay = document.createElement('div'); - overlay.id = 'blur-overlay'; - document.body.appendChild(overlay); - - let style = document.createElement('style'); - style.textContent = UtilityEnabled("MonochromeUI") ? ` - #blur-overlay { - display: none !important; - } - ` : ` - #blur-overlay { - position: fixed; - backdrop-filter: blur(4px); - z-index: 999; - pointer-events: none; - border-radius: 28px; - } - `; - document.head.appendChild(style); - } + const credential = new PasswordCredential({id: username, password: password}); + await navigator.credentials.store(credential); } catch (e) { console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } } } - - updateBlurOverlay() { +}; +let getCredential = async () => { + if ('credentials' in navigator && window.PasswordCredential) { try { - let overlay = document.getElementById('blur-overlay'); - let n = this.navbar; - Object.assign(overlay.style, { - top: `${n.offsetTop}px`, - left: `${n.offsetLeft}px`, - width: `${n.offsetWidth}px`, - height: `${n.offsetHeight}px` - }); + return await navigator.credentials.get({password: true, mediation: 'optional'}); } catch (e) { console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } } } - - createSpacer() { + return null; +}; +let clearCredential = async () => { + if ('credentials' in navigator && window.PasswordCredential) { try { - let spacer = document.getElementById('navbar-spacer'); - let newHeight = this.navbar.offsetHeight + 24; - if (!spacer) { - spacer = document.createElement('div'); - spacer.id = 'navbar-spacer'; - spacer.style.height = `${newHeight}px`; - spacer.style.width = '100%'; - document.body.insertBefore(spacer, document.body.firstChild); - } else { - let currentHeight = parseInt(spacer.style.height, 10); - if (currentHeight !== newHeight) { - document.body.removeChild(spacer); - spacer = document.createElement('div'); - spacer.id = 'navbar-spacer'; - spacer.style.height = `${newHeight}px`; - spacer.style.width = '100%'; - document.body.insertBefore(spacer, document.body.firstChild); - } - } + await navigator.credentials.preventSilentAccess(); } catch (e) { console.error(e); - if (UtilityEnabled("DebugMode")) { - SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); - } } } -} - -function replaceMarkdownImages(text, string) { - return text.replace(/!\[.*?\]\(.*?\)/g, string); -} - -function GetMDText(element) { - let result = ''; - const blockTags = new Set([ - 'P', 'DIV', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER', 'NAV', - 'UL', 'OL', 'LI', 'PRE', 'BLOCKQUOTE', - 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', - 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR' - ]); - const cellTags = new Set(['TD', 'TH']); - - function traverse(node) { - if (node.nodeType === Node.TEXT_NODE) { - result += node.textContent; - return; +}; +let RequestAPI = (Action, Data, CallBack) => { + try { + let Session = ""; + let Temp = document.cookie.split(";"); + for (let i = 0; i < Temp.length; i++) { + if (Temp[i].includes("PHPSESSID")) { + Session = Temp[i].split("=")[1]; + } } - - if (node.nodeType !== Node.ELEMENT_NODE) { - return; + if (Session === "") { //The cookie is httpOnly + GM.cookie.set({ + name: 'PHPSESSID', + value: (Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)).substring(0, 28), + path: "/" + }) + .then(() => { + console.log('Reset PHPSESSID successfully.'); + location.reload(); //Refresh the page to auth with the new PHPSESSID + }) + .catch((error) => { + console.error(error); + }); } - - const tag = node.nodeName.toUpperCase(); - - // Preserve line breaks for
- if (tag === 'BR') { - result += '\n'; - return; + let PostData = { + "Authentication": { + "SessionID": Session, "Username": CurrentUsername, + }, "Data": Data, "Version": GM_info.script.version, "DebugMode": UtilityEnabled("DebugMode") + }; + let DataString = JSON.stringify(PostData); + if (UtilityEnabled("DebugMode")) { + console.log("Sent for", Action + ":", DataString); } - - // Convert images to Markdown - if (tag === 'IMG') { - const src = node.getAttribute('src'); - if (src) { - let resolvedSrc = src; + GM_xmlhttpRequest({ + method: "POST", + url: (UtilityEnabled("SuperDebug") ? "http://127.0.0.1:8787/" : "https://api.xmoj-script.uk/") + Action, + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache", + "XMOJ-UserID": CurrentUsername, + "XMOJ-Script-Version": GM_info.script.version, + "DebugMode": UtilityEnabled("DebugMode") + }, + data: DataString, + onload: (Response) => { + if (UtilityEnabled("DebugMode")) { + console.log("Received for", Action + ":", Response.responseText); + } try { - resolvedSrc = new URL(src, location.href).href; - } catch (e) { - // Fallback to the raw src if URL construction fails + CallBack(JSON.parse(Response.responseText)); + } catch (Error) { + console.log(Response.responseText); } - result += `![](${resolvedSrc})`; } - return; + }); + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); } - - const isBlock = blockTags.has(tag); - const isCell = cellTags.has(tag); - - if (isBlock && !result.endsWith('\n')) { - result += '\n'; + } +}; +let SyncSettingsToCloud = (CallBack) => { + if (!CurrentUsername) { + if (CallBack) CallBack({ Success: false, Message: "用户未登录" }); + return; + } + if (!UtilityEnabled("CloudSync")) { + if (CallBack) CallBack({ Success: false, Message: "云同步已禁用" }); + return; + } + let Settings = {}; + for (let i = 0; i < localStorage.length; i++) { + let key = localStorage.key(i); + if (key && key.startsWith("UserScript-Setting-")) { + Settings[key.replace("UserScript-Setting-", "")] = localStorage.getItem(key); } - - // Keep table cells visually separated when copied as plain text. - if (isCell && result.length > 0 && !result.endsWith('\n') && !result.endsWith('\t') && !result.endsWith(' ')) { - result += '\t'; + } + RequestAPI("SetUserSettings", {"Settings": JSON.stringify(Settings)}, (Response) => { + if (UtilityEnabled("DebugMode")) { + if (Response.Success) { + console.log("设置已同步到云端"); + } else { + console.error("设置云端同步失败:", Response.Message); + } } + if (CallBack) CallBack(Response); + }); +}; - for (let child of node.childNodes) { - traverse(child); +let PeriodicCloudSync = () => { + if (!CurrentUsername || !UtilityEnabled("CloudSync")) return; + const lastSync = parseInt(localStorage.getItem("UserScript-CloudSync-LastSync") || "0"); + if (Date.now() - lastSync < 60 * 60 * 1000) return; + RequestAPI("GetUserSettings", {}, (Response) => { + if (Response.Success) { + localStorage.setItem("UserScript-CloudSync-LastSync", String(Date.now())); + const cloudSettings = (Response.Data && Response.Data.Settings) || {}; + if (Object.keys(cloudSettings).length > 0) { + let themeChanged = false; + for (let key in cloudSettings) { + const rawValue = String(cloudSettings[key]); + const localKey = "UserScript-Setting-" + key; + if (localStorage.getItem(localKey) !== rawValue) { + localStorage.setItem(localKey, rawValue); + if (key === "Theme") themeChanged = true; + } + } + if (themeChanged) initTheme(); + } + SyncSettingsToCloud(); } + }); +}; - if (isCell && !result.endsWith('\n') && !result.endsWith('\t')) { - result += '\t'; +unsafeWindow.GetContestProblemList = async function(RefreshList) { + try { + const contestReq = await fetch("https://www.xmoj.tech/contest.php?cid=" + SearchParams.get("cid")); + const res = await contestReq.text(); + if (contestReq.status === 200 && res.indexOf("比赛尚未开始或私有,不能查看题目。") === -1) { + const parser = new DOMParser(); + const dom = parser.parseFromString(res, "text/html"); + const rows = (dom.querySelector("#problemset > tbody")).rows; + let problemList = []; + for (let i = 0; i < rows.length; i++) { + problemList.push({ + "title": rows[i].children[2].innerText, + "url": rows[i].children[2].children[0].href + }); + } + localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList)); + if (RefreshList) location.reload(); } + } catch (e) { + console.error(e); + } +} - if (isBlock && !result.endsWith('\n')) { - result += '\n'; +// WebSocket Notification System +let NotificationSocket = null; +let NotificationSocketReconnectAttempts = 0; +let NotificationSocketReconnectDelay = 1000; +let NotificationSocketPingInterval = null; +let NotificationSocketReconnectTimer = null; + +function GetPHPSESSID() { + let Session = ""; + let Temp = document.cookie.split(";"); + for (let i = 0; i < Temp.length; i++) { + if (Temp[i].includes("PHPSESSID")) { + Session = Temp[i].split("=")[1]; + break; } } - - traverse(element); - return result; + return Session; } -async function main() { +function ConnectNotificationSocket() { try { - if (location.href.startsWith('http://')) { - //use https - location.href = location.href.replace('http://', 'https://'); + // Clear any pending reconnection timer to prevent duplicate connections + if (NotificationSocketReconnectTimer) { + clearTimeout(NotificationSocketReconnectTimer); + NotificationSocketReconnectTimer = null; } - if (location.host != "www.xmoj.tech") { - location.host = "www.xmoj.tech"; - } else { - if (location.href === 'https://www.xmoj.tech/open_contest_sign_up.php') { - return; - } - document.body.classList.add("placeholder-glow"); - if (document.querySelector("#navbar") != null) { - if (document.querySelector("body > div > div.jumbotron") != null) { - document.querySelector("body > div > div.jumbotron").className = "mt-3"; - } - - if (UtilityEnabled("AutoLogin") && document.querySelector("#profile") != null && document.querySelector("#profile").innerHTML == "登录" && location.pathname != "/login.php" && location.pathname != "/loginpage.php" && location.pathname != "/lostpassword.php" && !logined) { - localStorage.setItem("UserScript-LastPage", location.pathname + location.search); - location.href = "https://www.xmoj.tech/loginpage.php"; - } - let Discussion = null; - if (UtilityEnabled("Discussion")) { - Discussion = document.createElement("li"); - document.querySelector("#navbar > ul:nth-child(1)").appendChild(Discussion); - Discussion.innerHTML = "讨论"; - } - if (UtilityEnabled("Translate")) { - document.querySelector("#navbar > ul:nth-child(1) > li:nth-child(2) > a").innerText = "题库"; - } - //send analytics - RequestAPI("SendData", {}); - if (UtilityEnabled("ReplaceLinks")) { - document.body.innerHTML = String(document.body.innerHTML).replaceAll(/\[([^<]*)<\/a>\]/g, ""); - } - if (UtilityEnabled("ReplaceXM")) { - document.body.innerHTML = String(document.body.innerHTML).replaceAll("我", "高老师"); - document.body.innerHTML = String(document.body.innerHTML).replaceAll("小明", "高老师"); - document.body.innerHTML = String(document.body.innerHTML).replaceAll("下海", "上海"); - document.body.innerHTML = String(document.body.innerHTML).replaceAll("海上", "上海"); - document.body.innerHTML = String(document.body.innerHTML).replaceAll("小红", "徐师娘"); - document.body.innerHTML = String(document.body.innerHTML).replaceAll("小粉", "彩虹"); - document.body.innerHTML = String(document.body.innerHTML).replaceAll("提交上节课的代码", "自动提交当年代码"); - document.body.innerHTML = String(document.body.innerHTML).replaceAll("高老师们", "我们"); - document.body.innerHTML = String(document.body.innerHTML).replaceAll("自高老师", "自我"); - document.title = String(document.title).replaceAll("小明", "高老师"); - } + let Session = GetPHPSESSID(); + if (Session === "") { + if (UtilityEnabled("DebugMode")) { + console.log("WebSocket: PHPSESSID not available, skipping connection"); + } + return; + } - if (UtilityEnabled("NewBootstrap")) { - let Temp = document.querySelectorAll("link"); - for (var i = 0; i < Temp.length; i++) { - if (Temp[i].href.indexOf("bootstrap.min.css") != -1) { - Temp[i].remove(); - } else if (Temp[i].href.indexOf("white.css") != -1) { - Temp[i].remove(); - } else if (Temp[i].href.indexOf("semantic.min.css") != -1) { - Temp[i].remove(); - } else if (Temp[i].href.indexOf("bootstrap-theme.min.css") != -1) { - Temp[i].remove(); - } else if (Temp[i].href.indexOf("problem.css") != -1) { - Temp[i].remove(); - } - } - if (UtilityEnabled("DarkMode")) { - document.querySelector("html").setAttribute("data-bs-theme", "dark"); - } else { - document.querySelector("html").setAttribute("data-bs-theme", "light"); - } - if (UtilityEnabled("MonochromeUI")) { - let fontLink = document.createElement("link"); - fontLink.rel = "stylesheet"; - fontLink.href = "https://fonts.loli.net/css2?family=Playfair+Display:wght@400;700&family=Source+Serif+4:wght@400;600;700&family=JetBrains+Mono:wght@400;500&display=swap"; - document.head.appendChild(fontLink); - let earlyStyle = document.createElement("style"); - earlyStyle.textContent = ` - :root { - --mono-black: #000; --mono-white: #fff; - --mono-gray-100: #f5f5f5; --mono-gray-300: #d4d4d4; - --mono-font-heading: 'Playfair Display', Georgia, serif; - --mono-font-body: 'Source Serif 4', 'Source Serif Pro', Georgia, serif; - } - [data-bs-theme='dark'] { - --mono-black: #e5e5e5; --mono-white: #1a1a1a; - --mono-gray-100: #222; --mono-gray-300: #404040; - } - * { border-radius: 0 !important; box-shadow: none !important; } - body { font-family: var(--mono-font-body) !important; background-color: var(--mono-white) !important; color: var(--mono-black) !important; } - h1,h2,h3,h4,h5,h6 { font-family: var(--mono-font-heading) !important; } - .table thead th { background-color: var(--mono-black) !important; color: var(--mono-white) !important; } - .card { border: 2px solid var(--mono-black) !important; } - .card-header { background-color: var(--mono-black) !important; color: var(--mono-white) !important; } - `; - document.head.appendChild(earlyStyle); - } - var resources = [{ - type: 'link', - href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css', - rel: 'stylesheet' - }, { - type: 'link', - href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/theme/darcula.min.css', - rel: 'stylesheet' - }, { - type: 'link', - href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/addon/merge/merge.min.css', - rel: 'stylesheet' - }, { - type: 'link', - href: 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css', - rel: 'stylesheet' - }, { - type: 'script', - src: 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.js', - isModule: true - }]; - let loadResources = async () => { - let promises = resources.map(resource => { - return new Promise((resolve, reject) => { - let element; - if (resource.type === 'script') { - element = document.createElement('script'); - element.src = resource.src; - if (resource.isModule) { - element.type = 'module'; - } - element.onload = resolve; - element.onerror = reject; - } else if (resource.type === 'link') { - element = document.createElement('link'); - element.href = resource.href; - element.rel = resource.rel; - resolve(); // Stylesheets don't have an onload event - } - document.head.appendChild(element); - }); - }); + let wsUrl = (UtilityEnabled("SuperDebug") ? "ws://127.0.0.1:8787" : "wss://api.xmoj-script.uk") + "/ws/notifications?SessionID=" + Session; - await Promise.all(promises); - }; - if (location.pathname == "/submitpage.php") { - await loadResources(); - } else { - loadResources(); - } - document.querySelector("nav").className = "navbar navbar-expand-lg bg-body-tertiary"; - document.querySelector("#navbar > ul:nth-child(1)").classList = "navbar-nav me-auto mb-2 mb-lg-0"; - document.querySelector("body > div > nav > div > div.navbar-header").outerHTML = `${UtilityEnabled("ReplaceXM") ? "高老师" : "小明"}的OJ`; - document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li").classList = "nav-item dropdown"; - document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").className = "nav-link dropdown-toggle"; - document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a > span.caret").remove(); - Temp = document.querySelector("#navbar > ul:nth-child(1)").children; - for (var i = 0; i < Temp.length; i++) { - if (Temp[i].classList.contains("active")) { - Temp[i].classList.remove("active"); - Temp[i].children[0].classList.add("active"); - } - Temp[i].classList.add("nav-item"); - Temp[i].children[0].classList.add("nav-link"); + if (UtilityEnabled("DebugMode")) { + console.log("WebSocket: Connecting to", wsUrl); + } + + NotificationSocket = new WebSocket(wsUrl); + + NotificationSocket.onopen = () => { + if (UtilityEnabled("DebugMode")) { + console.log("WebSocket: Connected successfully"); + } + NotificationSocketReconnectAttempts = 0; + NotificationSocketReconnectDelay = 1000; + + // Start ping keepalive + if (NotificationSocketPingInterval) { + clearInterval(NotificationSocketPingInterval); + } + NotificationSocketPingInterval = setInterval(() => { + if (NotificationSocket && NotificationSocket.readyState === WebSocket.OPEN) { + NotificationSocket.send(JSON.stringify({ type: 'ping' })); + if (UtilityEnabled("DebugMode")) { + console.log("WebSocket: Sent ping"); } - document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").setAttribute("data-bs-toggle", "dropdown"); - document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").removeAttribute("data-toggle"); - } - if (UtilityEnabled("RemoveUseless") && document.getElementsByTagName("marquee")[0] != undefined) { - document.getElementsByTagName("marquee")[0].remove(); + } else { + clearInterval(NotificationSocketPingInterval); } - let Style = document.createElement("style"); - document.body.appendChild(Style); - if (UtilityEnabled("MonochromeUI")) { - Style.innerHTML = ` - /* Fonts loaded via to avoid layout shift */ + }, 30000); + }; - :root { - --mono-black: #000; - --mono-white: #fff; - --mono-gray-100: #f5f5f5; - --mono-gray-200: #e5e5e5; - --mono-gray-300: #d4d4d4; - --mono-gray-400: #a3a3a3; - --mono-gray-500: #737373; - --mono-border: 2px solid var(--mono-black); - --mono-border-thin: 1px solid var(--mono-gray-300); - --mono-font-heading: 'Playfair Display', Georgia, serif; - --mono-font-body: 'Source Serif 4', 'Source Serif Pro', Georgia, serif; - --mono-font-mono: 'JetBrains Mono', 'Consolas', monospace; - --mono-transition: 100ms ease; - } + NotificationSocket.onmessage = (event) => { + HandleNotificationMessage(event); + }; - [data-bs-theme='dark'] { - --mono-black: #e5e5e5; - --mono-white: #1a1a1a; - --mono-gray-100: #222; - --mono-gray-200: #2a2a2a; - --mono-gray-300: #404040; - --mono-gray-400: #737373; - --mono-gray-500: #a3a3a3; - } + NotificationSocket.onerror = (error) => { + if (UtilityEnabled("DebugMode")) { + console.error("WebSocket: Error", error); + } + }; - * { - border-radius: 0 !important; - box-shadow: none !important; + NotificationSocket.onclose = (event) => { + if (UtilityEnabled("DebugMode")) { + console.log("WebSocket: Connection closed", event.code, event.reason); + } + if (NotificationSocketPingInterval) { + clearInterval(NotificationSocketPingInterval); + } + ReconnectNotificationSocket(); + }; + } catch (e) { + console.error("WebSocket: Failed to connect", e); + ReconnectNotificationSocket(); + } +} + +function ReconnectNotificationSocket() { + const delay = Math.min(NotificationSocketReconnectDelay * Math.pow(2, NotificationSocketReconnectAttempts), 30000); + NotificationSocketReconnectAttempts++; + + if (UtilityEnabled("DebugMode")) { + console.log(`WebSocket: Reconnecting in ${delay}ms (attempt ${NotificationSocketReconnectAttempts})`); + } + + NotificationSocketReconnectTimer = setTimeout(() => { + ConnectNotificationSocket(); + }, delay); +} + +function HandleNotificationMessage(event) { + try { + const notification = JSON.parse(event.data); + + if (UtilityEnabled("DebugMode")) { + console.log("WebSocket: Received message", notification); + } + + if (notification.type === 'connected') { + if (UtilityEnabled("DebugMode")) { + console.log("WebSocket: Server confirmed connection at timestamp", notification.timestamp); + } + } else if (notification.type === 'bbs_mention') { + if (UtilityEnabled("BBSPopup")) { + CreateAndShowBBSMentionToast(notification.data); + } + } else if (notification.type === 'mail_mention') { + if (UtilityEnabled("MessagePopup")) { + CreateAndShowMailMentionToast(notification.data); + } + } else if (notification.type === 'pong') { + if (UtilityEnabled("DebugMode")) { + console.log("WebSocket: Received pong"); + } + } + } catch (e) { + console.error("WebSocket: Failed to handle message", e); + } +} + +function CreateAndShowBBSMentionToast(mention) { + let ToastContainer = document.querySelector(".toast-container"); + if (!ToastContainer) return; + + let Toast = document.createElement("div"); + Toast.classList.add("toast"); + Toast.setAttribute("role", "alert"); + let ToastHeader = document.createElement("div"); + ToastHeader.classList.add("toast-header"); + let ToastTitle = document.createElement("strong"); + ToastTitle.classList.add("me-auto"); + ToastTitle.innerHTML = "提醒:有人@你"; + ToastHeader.appendChild(ToastTitle); + let ToastTime = document.createElement("small"); + ToastTime.classList.add("text-body-secondary"); + ToastTime.innerHTML = GetRelativeTime(mention.MentionTime); + ToastHeader.appendChild(ToastTime); + let ToastCloseButton = document.createElement("button"); + ToastCloseButton.type = "button"; + ToastCloseButton.classList.add("btn-close"); + ToastCloseButton.setAttribute("data-bs-dismiss", "toast"); + ToastHeader.appendChild(ToastCloseButton); + Toast.appendChild(ToastHeader); + let ToastBody = document.createElement("div"); + ToastBody.classList.add("toast-body"); + ToastBody.innerHTML = "讨论" + escapeHTML(mention.PostTitle) + "有新回复"; + let ToastFooter = document.createElement("div"); + ToastFooter.classList.add("mt-2", "pt-2", "border-top"); + let ToastDismissButton = document.createElement("button"); + ToastDismissButton.type = "button"; + ToastDismissButton.classList.add("btn", "btn-secondary", "btn-sm", "me-2"); + ToastDismissButton.innerText = "忽略"; + ToastDismissButton.addEventListener("click", () => { + RequestAPI("ReadBBSMention", { + "MentionID": Number(mention.MentionID) + }, () => { + }); + Toast.remove(); + }); + ToastFooter.appendChild(ToastDismissButton); + let ToastViewButton = document.createElement("button"); + ToastViewButton.type = "button"; + ToastViewButton.classList.add("btn", "btn-primary", "btn-sm"); + ToastViewButton.innerText = "查看"; + ToastViewButton.addEventListener("click", () => { + open("https://www.xmoj.tech/discuss3/thread.php?tid=" + mention.PostID + '&page=' + mention.PageNumber, "_blank"); + RequestAPI("ReadBBSMention", { + "MentionID": Number(mention.MentionID) + }, () => { + }); + }); + ToastFooter.appendChild(ToastViewButton); + ToastBody.appendChild(ToastFooter); + Toast.appendChild(ToastBody); + ToastContainer.appendChild(Toast); + new bootstrap.Toast(Toast).show(); +} + +function CreateAndShowMailMentionToast(mention) { + let ToastContainer = document.querySelector(".toast-container"); + if (!ToastContainer) return; + + let Toast = document.createElement("div"); + Toast.classList.add("toast"); + Toast.setAttribute("role", "alert"); + let ToastHeader = document.createElement("div"); + ToastHeader.classList.add("toast-header"); + let ToastTitle = document.createElement("strong"); + ToastTitle.classList.add("me-auto"); + ToastTitle.innerHTML = "提醒:有新消息"; + ToastHeader.appendChild(ToastTitle); + let ToastTime = document.createElement("small"); + ToastTime.classList.add("text-body-secondary"); + ToastTime.innerHTML = GetRelativeTime(mention.MentionTime); + ToastHeader.appendChild(ToastTime); + let ToastCloseButton = document.createElement("button"); + ToastCloseButton.type = "button"; + ToastCloseButton.classList.add("btn-close"); + ToastCloseButton.setAttribute("data-bs-dismiss", "toast"); + ToastHeader.appendChild(ToastCloseButton); + Toast.appendChild(ToastHeader); + let ToastBody = document.createElement("div"); + ToastBody.classList.add("toast-body"); + let ToastUser = document.createElement("span"); + GetUsernameHTML(ToastUser, mention.FromUserID); + ToastBody.appendChild(ToastUser); + ToastBody.appendChild(document.createTextNode(" 给你发了一封短消息")); + let ToastFooter = document.createElement("div"); + ToastFooter.classList.add("mt-2", "pt-2", "border-top"); + let ToastDismissButton = document.createElement("button"); + ToastDismissButton.type = "button"; + ToastDismissButton.classList.add("btn", "btn-secondary", "btn-sm", "me-2"); + ToastDismissButton.setAttribute("data-bs-dismiss", "toast"); + ToastDismissButton.innerText = "忽略"; + ToastDismissButton.addEventListener("click", () => { + RequestAPI("ReadMailMention", { + "MentionID": Number(mention.MentionID) + }, () => { + }); + }); + ToastFooter.appendChild(ToastDismissButton); + let ToastViewButton = document.createElement("button"); + ToastViewButton.type = "button"; + ToastViewButton.classList.add("btn", "btn-primary", "btn-sm"); + ToastViewButton.innerText = "查看"; + ToastViewButton.addEventListener("click", () => { + open("https://www.xmoj.tech/mail.php?to_user=" + mention.FromUserID, "_blank"); + RequestAPI("ReadMailMention", { + "MentionID": Number(mention.MentionID) + }, () => { + }); + }); + ToastFooter.appendChild(ToastViewButton); + ToastBody.appendChild(ToastFooter); + Toast.appendChild(ToastBody); + ToastContainer.appendChild(Toast); + new bootstrap.Toast(Toast).show(); +} + +function PollNotifications() { + // Clear toast container once before fetching to prevent race condition + if (UtilityEnabled("BBSPopup") || UtilityEnabled("MessagePopup")) { + let ToastContainer = document.querySelector(".toast-container"); + if (ToastContainer) { + ToastContainer.innerHTML = ""; + } + } + if (UtilityEnabled("BBSPopup")) { + RequestAPI("GetBBSMentionList", {}, (Response) => { + if (Response.Success) { + let MentionList = Response.Data.MentionList; + for (let i = 0; i < MentionList.length; i++) { + CreateAndShowBBSMentionToast(MentionList[i]); } - - body { - font-family: var(--mono-font-body) !important; - color: var(--mono-black) !important; - background-color: var(--mono-white) !important; + } + }); + } + if (UtilityEnabled("MessagePopup")) { + RequestAPI("GetMailMentionList", {}, (Response) => { + if (Response.Success) { + let MentionList = Response.Data.MentionList; + for (let i = 0; i < MentionList.length; i++) { + CreateAndShowMailMentionToast(MentionList[i]); } + } + }); + } +} - h1, h2, h3, h4, h5, h6 { - font-family: var(--mono-font-heading) !important; - font-weight: 700 !important; - } +GM_registerMenuCommand("清除缓存", () => { + let Temp = []; + for (let i = 0; i < localStorage.length; i++) { + if (localStorage.key(i).startsWith("UserScript-User-")) { + Temp.push(localStorage.key(i)); + } + } + for (let i = 0; i < Temp.length; i++) { + localStorage.removeItem(Temp[i]); + } + location.reload(); +}); +GM_registerMenuCommand("重置数据", () => { + if (confirm("确定要重置数据吗?")) { + localStorage.clear(); + location.reload(); + } +}); - code, pre, .CodeMirror, kbd, samp { - font-family: var(--mono-font-mono) !important; - } +// Wrapped in an async IIFE so that `await` is valid in Violentmonkey, +// which executes userscripts as classic scripts (not ES modules). +(async () => { +if (document.readyState === "loading") { + await new Promise(r => document.addEventListener("DOMContentLoaded", r, { once: true })); +} +//otherwise CurrentUsername might be undefined +let loginStatus; +await fetch("https://www.xmoj.tech/loginpage.php") + .then((response) => response.text()) + .then((data) => (loginStatus = data)); +const logined = loginStatus == "Please logout First!"; +if (UtilityEnabled("AutoLogin") && document.querySelector("body > a:nth-child(1)") != null && document.querySelector("body > a:nth-child(1)").innerText == "请登录后继续操作") { + localStorage.setItem("UserScript-LastPage", location.pathname + location.search); + location.href = "https://www.xmoj.tech/loginpage.php"; + return; +} - a { - color: var(--mono-black) !important; - text-decoration: none !important; - transition: var(--mono-transition) !important; - } - .container a:not(.nav-link):not(.btn):not(.dropdown-item):not(.list-group-item):not(.page-link) { - border-bottom: 1px solid var(--mono-gray-400) !important; - padding-bottom: 1px !important; - } - .container a:not(.nav-link):not(.btn):not(.dropdown-item):not(.list-group-item):not(.page-link):hover { - border-bottom-color: var(--mono-black) !important; - } +SearchParams = new URLSearchParams(location.search); +let ServerURL = (UtilityEnabled("DebugMode") ? "https://ghpages.xmoj-script.uk/" : "https://www.xmoj-script.uk") +const profileElement = document.querySelector("#profile"); +if (profileElement === null) { + if (!logined) { + location.href = "https://www.xmoj.tech/loginpage.php"; + } + return; +} +CurrentUsername = profileElement.innerText; +CurrentUsername = CurrentUsername.replaceAll(/[^a-zA-Z0-9]/g, ""); +let IsAdmin = AdminUserList.indexOf(CurrentUsername) !== -1; - blockquote { - border-left: 4px solid var(--mono-black) !important; - padding: 0.5em 1em; - } +const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); +const applyTheme = (theme) => { + document.querySelector("html").setAttribute("data-bs-theme", theme); + localStorage.setItem("UserScript-Setting-DarkMode", theme === "dark" ? "true" : "false"); +}; +const applySystemTheme = (e) => applyTheme(e.matches ? "dark" : "light"); +initTheme = () => { + const saved = localStorage.getItem("UserScript-Setting-Theme") || "auto"; + if (saved === "auto") { + applyTheme(prefersDark.matches ? "dark" : "light"); + prefersDark.addEventListener("change", applySystemTheme); + } else { + applyTheme(saved); + prefersDark.removeEventListener("change", applySystemTheme); + } +}; +initTheme(); +PeriodicCloudSync(); +setInterval(PeriodicCloudSync, 60 * 60 * 1000); - /* Navbar */ - .navbar, nav.navbar { - border-bottom: 4px solid var(--mono-black) !important; - background-color: var(--mono-white) !important; - opacity: 1 !important; - } - .navbar .nav-link { - color: var(--mono-black) !important; - text-decoration: none !important; - font-family: var(--mono-font-body) !important; - text-transform: uppercase !important; - letter-spacing: 0.05em !important; - font-size: 0.85rem !important; - } - .navbar .nav-link:hover, .navbar .nav-link.active { - background-color: var(--mono-black) !important; - color: var(--mono-white) !important; - } - /* Buttons */ - .btn { - border: 2px solid var(--mono-black) !important; - background-color: var(--mono-white) !important; - color: var(--mono-black) !important; - text-transform: uppercase !important; - letter-spacing: 0.1em !important; - font-family: var(--mono-font-body) !important; - font-weight: 600 !important; - transition: var(--mono-transition) !important; - } - .btn:hover, .btn:focus, .btn:active, .btn.active { - background-color: var(--mono-black) !important; - color: var(--mono-white) !important; - border-color: var(--mono-black) !important; - } - .btn-primary { - border: 2px solid var(--mono-black) !important; - background-color: var(--mono-black) !important; - color: var(--mono-white) !important; - } - .btn-primary:hover { - background-color: var(--mono-white) !important; - color: var(--mono-black) !important; - } - .btn-secondary { - border: 2px solid var(--mono-black) !important; - background-color: var(--mono-white) !important; - color: var(--mono-black) !important; - } - .btn-secondary:hover { - background-color: var(--mono-black) !important; - color: var(--mono-white) !important; - } - .btn-success { - background-color: var(--mono-white) !important; - border: 2px solid #52c41a !important; - color: #52c41a !important; - } - .btn-danger { - background-color: var(--mono-white) !important; - border: 2px solid #fe4c61 !important; - color: #fe4c61 !important; - } - .btn-warning { - background-color: var(--mono-white) !important; - border: 2px solid #ffa900 !important; - color: #ffa900 !important; - } - .btn-info { - background-color: var(--mono-white) !important; - border: 2px solid #0dcaf0 !important; - color: #0dcaf0 !important; - } +class NavbarStyler { + constructor() { + try { + this.navbar = document.querySelector('.navbar.navbar-expand-lg.bg-body-tertiary'); + if (this.navbar && UtilityEnabled("NewTopBar")) { + this.init(); + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + } - /* Cards */ - .card { - border: 2px solid var(--mono-black) !important; - background-color: var(--mono-white) !important; - } - .card-header { - background-color: var(--mono-black) !important; - color: var(--mono-white) !important; - border-bottom: none !important; - font-family: var(--mono-font-heading) !important; - } - .card-header * { - color: var(--mono-white) !important; - } - .card-body { - background-color: var(--mono-white) !important; - color: var(--mono-black) !important; - } - .card-footer { - border-top: 1px solid var(--mono-gray-300) !important; - background-color: var(--mono-white) !important; - } + init() { + try { + this.applyStyles(); + this.createOverlay(); + this.createSpacer(); + window.addEventListener('resize', () => this.updateBlurOverlay()); + this.updateBlurOverlay(); + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + } - /* Modals */ - .modal-content { - border: 4px solid var(--mono-black) !important; - background-color: var(--mono-white) !important; - } - .modal-header { - border-bottom: 1px solid var(--mono-gray-300) !important; - background-color: var(--mono-white) !important; - } - .modal-footer { - border-top: 1px solid var(--mono-gray-300) !important; - background-color: var(--mono-white) !important; - } - .modal-title { - font-family: var(--mono-font-heading) !important; - } + applyStyles() { + try { + let n = this.navbar; + n.classList.add('fixed-top', 'container', 'ml-auto'); + if (UtilityEnabled("MonochromeUI")) { + Object.assign(n.style, { + position: 'fixed', + borderRadius: '0', + boxShadow: '0 4px 8px rgba(0, 0, 0, 0.5)', + margin: '0', + maxWidth: '100%', + backgroundColor: 'rgba(255, 255, 255, 0)', + opacity: '0.75', + zIndex: '1000' + }); + } else { + Object.assign(n.style, { + position: 'fixed', + borderRadius: '28px', + boxShadow: '0 4px 8px rgba(0, 0, 0, 0.5)', + margin: '16px auto', + backgroundColor: 'rgba(255, 255, 255, 0)', + opacity: '0.75', + zIndex: '1000' + }); + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + } - /* Toasts */ - .toast { - border: 2px solid var(--mono-black) !important; - background-color: var(--mono-white) !important; - } - .toast-header { - background-color: var(--mono-gray-100) !important; - color: var(--mono-black) !important; - border-bottom: 1px solid var(--mono-gray-300) !important; - } + createOverlay() { + try { + if (!document.getElementById('blur-overlay')) { + let overlay = document.createElement('div'); + overlay.id = 'blur-overlay'; + document.body.appendChild(overlay); - /* Tables */ - .table { - border-color: var(--mono-gray-300) !important; - } - thead th, th.header, th.headerSortUp, th.headerSortDown { - background-color: var(--mono-black) !important; - background-image: none !important; - color: var(--mono-white) !important; - border-bottom: none !important; - font-family: var(--mono-font-heading) !important; - text-transform: uppercase !important; - letter-spacing: 0.05em !important; - font-size: 0.85rem !important; - } - td, th { - border-color: var(--mono-gray-300) !important; - text-align: center !important; - } - .table-striped > tbody > tr:nth-of-type(odd) > * { - background-color: var(--mono-gray-100) !important; + let style = document.createElement('style'); + style.textContent = UtilityEnabled("MonochromeUI") ? ` + #blur-overlay { + display: none !important; } - table { - margin-top: 16px !important; + ` : ` + #blur-overlay { + position: fixed; + backdrop-filter: blur(4px); + z-index: 999; + pointer-events: none; + border-radius: 28px; } + `; + document.head.appendChild(style); + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + } - /* List groups */ - .list-group-item { - border: none !important; - border-bottom: 1px solid var(--mono-gray-300) !important; - background-color: var(--mono-white) !important; - color: var(--mono-black) !important; - } - .list-group-item-success { - border-left: 4px solid #52c41a !important; - } - .list-group-item-warning { - border-left: 4px solid #ffa900 !important; - } - .list-group-item-danger { - border-left: 4px solid #fe4c61 !important; - } + updateBlurOverlay() { + try { + let overlay = document.getElementById('blur-overlay'); + let n = this.navbar; + Object.assign(overlay.style, { + top: `${n.offsetTop}px`, + left: `${n.offsetLeft}px`, + width: `${n.offsetWidth}px`, + height: `${n.offsetHeight}px` + }); + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + } - /* Dropdowns */ - .dropdown-menu { - border: 2px solid var(--mono-black) !important; - padding: 0 !important; - background-color: var(--mono-white) !important; - } - .dropdown-item { - border-bottom: 1px solid var(--mono-gray-200) !important; - color: var(--mono-black) !important; - transition: var(--mono-transition) !important; - text-decoration: none !important; - } - .dropdown-item:last-child { - border-bottom: none !important; - } - .dropdown-item:hover, .dropdown-item:focus { - background-color: var(--mono-black) !important; - color: var(--mono-white) !important; + createSpacer() { + try { + let spacer = document.getElementById('navbar-spacer'); + let newHeight = this.navbar.offsetHeight + 24; + if (!spacer) { + spacer = document.createElement('div'); + spacer.id = 'navbar-spacer'; + spacer.style.height = `${newHeight}px`; + spacer.style.width = '100%'; + document.body.insertBefore(spacer, document.body.firstChild); + } else { + let currentHeight = parseInt(spacer.style.height, 10); + if (currentHeight !== newHeight) { + document.body.removeChild(spacer); + spacer = document.createElement('div'); + spacer.id = 'navbar-spacer'; + spacer.style.height = `${newHeight}px`; + spacer.style.width = '100%'; + document.body.insertBefore(spacer, document.body.firstChild); } + } + } catch (e) { + console.error(e); + if (UtilityEnabled("DebugMode")) { + SmartAlert("XMOJ-Script internal error!\n\n" + e + "\n\n" + "If you see this message, please report it to the developer.\nDon't forget to include console logs and a way to reproduce the error!\n\nDon't want to see this message? Disable DebugMode."); + } + } + } +} - /* Forms */ - .form-control, .form-select { - border: 2px solid var(--mono-black) !important; - background-color: var(--mono-white) !important; - color: var(--mono-black) !important; - font-family: var(--mono-font-body) !important; - } - .form-control:focus, .form-select:focus { - outline: 2px solid var(--mono-black) !important; - outline-offset: 2px !important; - border-color: var(--mono-black) !important; - } +function replaceMarkdownImages(text, string) { + return text.replace(/!\[.*?\]\(.*?\)/g, string); +} - /* Alerts */ - .alert { - border: 2px solid var(--mono-black) !important; - background-color: var(--mono-white) !important; - color: var(--mono-black) !important; - } - .alert-primary { - border-left: 8px solid var(--mono-black) !important; - } +function GetMDText(element) { + let result = ''; + const blockTags = new Set([ + 'P', 'DIV', 'SECTION', 'ARTICLE', 'HEADER', 'FOOTER', 'NAV', + 'UL', 'OL', 'LI', 'PRE', 'BLOCKQUOTE', + 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', + 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TR' + ]); + const cellTags = new Set(['TD', 'TH']); - /* Status indicators */ - .status_y { - background-color: #52c41a !important; - color: #fff !important; - border-color: #52c41a !important; - } - .status_n { - background-color: #fe4c61 !important; - color: #fff !important; - border-color: #fe4c61 !important; - } - .status_w { - background-color: #ffa900 !important; - color: #fff !important; - border-color: #ffa900 !important; - } + function traverse(node) { + if (node.nodeType === Node.TEXT_NODE) { + result += node.textContent; + return; + } - .test-case:hover { - border: 2px solid var(--mono-black) !important; - } + if (node.nodeType !== Node.ELEMENT_NODE) { + return; + } - .software_list { - width: unset !important; - } - .software_item { - margin: 5px 10px !important; - background-color: var(--mono-gray-100) !important; - border: 1px solid var(--mono-gray-300) !important; - } - .software_item img { - width: 50px !important; - height: 50px !important; - object-fit: contain !important; - } - .item-txt { - color: var(--mono-black) !important; - } - .cnt-row { - justify-content: inherit; - align-items: stretch; - width: 100% !important; - padding: 1rem 0; - } - .cnt-row-head { - padding: 0.8em 1em; - background-color: var(--mono-black); - color: var(--mono-white); - width: 100%; - font-family: var(--mono-font-heading); - } - .cnt-row-head * { - color: var(--mono-white) !important; - } - .cnt-row-body { - padding: 1em; - border: 2px solid var(--mono-black); - border-top: none; - } + const tag = node.nodeName.toUpperCase(); - /* Scrollbar */ - ::-webkit-scrollbar { - width: 8px; - height: 8px; - } - ::-webkit-scrollbar-track { - background: var(--mono-white); - } - ::-webkit-scrollbar-thumb { - background: var(--mono-black); - } + // Preserve line breaks for
+ if (tag === 'BR') { + result += '\n'; + return; + } - /* Copy button in inverted headers */ - .cnt-row-head .copy-btn, .card-header .copy-btn { - border-color: var(--mono-white) !important; - color: var(--mono-white) !important; - background-color: transparent !important; - } - .cnt-row-head .copy-btn:hover, .card-header .copy-btn:hover { - background-color: var(--mono-white) !important; - color: var(--mono-black) !important; + // Convert images to Markdown + if (tag === 'IMG') { + const src = node.getAttribute('src'); + if (src) { + let resolvedSrc = src; + try { + resolvedSrc = new URL(src, location.href).href; + } catch (e) { + // Fallback to the raw src if URL construction fails } + result += `![](${resolvedSrc})`; + } + return; + } - /* Problem switcher responsive */ - @media (max-width: 768px) { - .problem-switcher-container { - display: none !important; - } - } - .refreshList { - cursor: pointer; - } + const isBlock = blockTags.has(tag); + const isCell = cellTags.has(tag); - /* Contain images */ - img { - max-width: 100% !important; - height: auto !important; - } + if (isBlock && !result.endsWith('\n')) { + result += '\n'; + } - /* Hide blur overlay */ - #blur-overlay { display: none !important; }`; - } else { - Style.innerHTML = ` - nav { - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; - } - blockquote { - border-left: 5px solid var(--bs-secondary-bg); - padding: 0.5em 1em; - } - .status_y:hover { - box-shadow: #52c41a 1px 1px 10px 0px !important; - } - .status_n:hover { - box-shadow: #fe4c61 1px 1px 10px 0px !important; - } - .status_w:hover { - box-shadow: #ffa900 1px 1px 10px 0px !important; - } - .test-case { - border-radius: 5px !important; - } - .test-case:hover { - box-shadow: rgba(0, 0, 0, 0.3) 0px 10px 20px 3px !important; - } - .data[result-item] { - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; - } - .software_list { - width: unset !important; - } - .software_item { - margin: 5px 10px !important; - background-color: var(--bs-secondary-bg) !important; - } - .item-txt { - color: var(--bs-emphasis-color) !important; - } - .cnt-row { - justify-content: inherit; - align-items: stretch; - width: 100% !important; - padding: 1rem 0; + // Keep table cells visually separated when copied as plain text. + if (isCell && result.length > 0 && !result.endsWith('\n') && !result.endsWith('\t') && !result.endsWith(' ')) { + result += '\t'; + } + + for (let child of node.childNodes) { + traverse(child); + } + + if (isCell && !result.endsWith('\n') && !result.endsWith('\t')) { + result += '\t'; + } + + if (isBlock && !result.endsWith('\n')) { + result += '\n'; + } + } + + traverse(element); + return result; +} + +async function main() { + try { + if (location.href.startsWith('http://')) { + //use https + location.href = location.href.replace('http://', 'https://'); + } + if (location.host != "www.xmoj.tech") { + location.host = "www.xmoj.tech"; + } else { + if (location.href === 'https://www.xmoj.tech/open_contest_sign_up.php') { + return; + } + document.body.classList.add("placeholder-glow"); + if (document.querySelector("#navbar") != null) { + if (document.querySelector("body > div > div.jumbotron") != null) { + document.querySelector("body > div > div.jumbotron").className = "mt-3"; } - .cnt-row-head { - padding: 0.8em 1em; - background-color: var(--bs-secondary-bg); - border-radius: 0.3rem 0.3rem 0 0; - width: 100%; + + if (UtilityEnabled("AutoLogin") && document.querySelector("#profile") != null && document.querySelector("#profile").innerHTML == "登录" && location.pathname != "/login.php" && location.pathname != "/loginpage.php" && location.pathname != "/lostpassword.php" && !logined) { + localStorage.setItem("UserScript-LastPage", location.pathname + location.search); + location.href = "https://www.xmoj.tech/loginpage.php"; } - .cnt-row-body { - padding: 1em; - border: 1px solid var(--bs-secondary-bg); - border-top: none; - border-radius: 0 0 0.3rem 0.3rem; + + let Discussion = null; + if (UtilityEnabled("Discussion")) { + Discussion = document.createElement("li"); + document.querySelector("#navbar > ul:nth-child(1)").appendChild(Discussion); + Discussion.innerHTML = "讨论"; } - .refreshList { - cursor: pointer; - color: #6c757d; - text-decoration: none; - }`; + if (UtilityEnabled("Translate")) { + document.querySelector("#navbar > ul:nth-child(1) > li:nth-child(2) > a").innerText = "题库"; } - if (UtilityEnabled("AddAnimation")) { - Style.innerHTML += `.status, .test-case { - transition: ${UtilityEnabled("MonochromeUI") ? "100ms ease" : "0.5s"} !important; - }`; + //send analytics + RequestAPI("SendData", {}); + if (UtilityEnabled("ReplaceLinks")) { + document.body.innerHTML = String(document.body.innerHTML).replaceAll(/\[([^<]*)<\/a>\]/g, ""); } - if (UtilityEnabled("AddColorText")) { - Style.innerHTML += `.red { - color: red !important; + if (UtilityEnabled("ReplaceXM")) { + document.body.innerHTML = String(document.body.innerHTML).replaceAll("我", "高老师"); + document.body.innerHTML = String(document.body.innerHTML).replaceAll("小明", "高老师"); + document.body.innerHTML = String(document.body.innerHTML).replaceAll("下海", "上海"); + document.body.innerHTML = String(document.body.innerHTML).replaceAll("海上", "上海"); + document.body.innerHTML = String(document.body.innerHTML).replaceAll("小红", "徐师娘"); + document.body.innerHTML = String(document.body.innerHTML).replaceAll("小粉", "彩虹"); + document.body.innerHTML = String(document.body.innerHTML).replaceAll("提交上节课的代码", "自动提交当年代码"); + document.body.innerHTML = String(document.body.innerHTML).replaceAll("高老师们", "我们"); + document.body.innerHTML = String(document.body.innerHTML).replaceAll("自高老师", "自我"); + document.title = String(document.title).replaceAll("小明", "高老师"); } - .green { - color: green !important; + + if (UtilityEnabled("NewBootstrap")) { + if (UtilityEnabled("MonochromeUI")) { + let fontLink = document.createElement("link"); + fontLink.rel = "stylesheet"; + fontLink.href = "https://fonts.loli.net/css2?family=Playfair+Display:wght@400;700&family=Source+Serif+4:wght@400;600;700&family=JetBrains+Mono:wght@400;500&display=swap"; + document.head.appendChild(fontLink); + } + var resources = [{ + type: 'link', + href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/codemirror.min.css', + rel: 'stylesheet' + }, { + type: 'link', + href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/theme/darcula.min.css', + rel: 'stylesheet' + }, { + type: 'link', + href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/addon/merge/merge.min.css', + rel: 'stylesheet' + }]; + let loadResources = async () => { + let promises = resources.map(resource => { + return new Promise((resolve, reject) => { + let element; + if (resource.type === 'script') { + element = document.createElement('script'); + element.src = resource.src; + if (resource.isModule) { + element.type = 'module'; + } + element.onload = resolve; + element.onerror = reject; + } else if (resource.type === 'link') { + element = document.createElement('link'); + element.href = resource.href; + element.rel = resource.rel; + resolve(); // Stylesheets don't have an onload event + } + document.head.appendChild(element); + }); + }); + + await Promise.all(promises); + }; + if (location.pathname == "/submitpage.php") { + await loadResources(); + } else { + loadResources(); + } + document.querySelector("nav").className = "navbar navbar-expand-lg bg-body-tertiary"; + document.querySelector("#navbar > ul:nth-child(1)").classList = "navbar-nav me-auto mb-2 mb-lg-0"; + document.querySelector("body > div > nav > div > div.navbar-header").outerHTML = `${UtilityEnabled("ReplaceXM") ? "高老师" : "小明"}的OJ`; + document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li").classList = "nav-item dropdown"; + document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").className = "nav-link dropdown-toggle"; + document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a > span.caret").remove(); + Temp = document.querySelector("#navbar > ul:nth-child(1)").children; + for (var i = 0; i < Temp.length; i++) { + if (Temp[i].classList.contains("active")) { + Temp[i].classList.remove("active"); + Temp[i].children[0].classList.add("active"); + } + Temp[i].classList.add("nav-item"); + Temp[i].children[0].classList.add("nav-link"); + } + document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").setAttribute("data-bs-toggle", "dropdown"); + document.querySelector("#navbar > ul.nav.navbar-nav.navbar-right > li > a").removeAttribute("data-toggle"); } - .blue { - color: blue !important; - }`; + if (UtilityEnabled("RemoveUseless") && document.getElementsByTagName("marquee")[0] != undefined) { + document.getElementsByTagName("marquee")[0].remove(); } + let Style = document.createElement("style"); + document.body.appendChild(Style); + Style.innerHTML = UtilityEnabled("MonochromeUI") ? MonochromeSkinCSS : NewBootstrapSkinCSS; + if (UtilityEnabled("AddAnimation")) Style.innerHTML += `.status, .test-case { transition: ${UtilityEnabled("MonochromeUI") ? "100ms ease" : "0.5s"} !important; }`; + if (UtilityEnabled("AddColorText")) Style.innerHTML += `.red { color: red !important; } .green { color: green !important; } .blue { color: blue !important; }`; if (UtilityEnabled("RemoveUseless")) { if (document.getElementsByClassName("footer")[0] != null) { From 8a064f5811c05a528bd4526c3733ccfb2f372bba Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 07:40:33 +0000 Subject: [PATCH 02/15] refactor: remove redundant late skin CSS injection from Style element The early block now owns the MonochromeSkinCSS / NewBootstrapSkinCSS injection. The Style element created at document-end is kept (other code appends to it further down the IIFE), but it no longer re-sets the skin CSS that was already applied synchronously at document-start. https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb --- XMOJ.user.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 5076e557..7fbdfe63 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -2326,9 +2326,6 @@ async function main() { } let Style = document.createElement("style"); document.body.appendChild(Style); - Style.innerHTML = UtilityEnabled("MonochromeUI") ? MonochromeSkinCSS : NewBootstrapSkinCSS; - if (UtilityEnabled("AddAnimation")) Style.innerHTML += `.status, .test-case { transition: ${UtilityEnabled("MonochromeUI") ? "100ms ease" : "0.5s"} !important; }`; - if (UtilityEnabled("AddColorText")) Style.innerHTML += `.red { color: red !important; } .green { color: green !important; } .blue { color: blue !important; }`; if (UtilityEnabled("RemoveUseless")) { if (document.getElementsByClassName("footer")[0] != null) { From f38b4aa3d7b14905e628c146aea87295a7288d89 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 07:50:38 +0000 Subject: [PATCH 03/15] fix: handle @resource cache miss to prevent broken page on first install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If GM_getResourceText("BootstrapCSS") returns null (the @resource has not been downloaded yet on first install or update), the early block was still setting up the MutationObserver that blocked the page's original Bootstrap CSS — leaving the page with no Bootstrap CSS at all. Fix: bail out of the early block before the observer is set up when the resource is unavailable, so the page's original stylesheets load as a fallback (flash still happens, but the page renders correctly). Also add a CDN fallback entry to the runtime resources array: on the rare load where the @resource wasn't ready, Bootstrap 5.3.3 CSS is fetched from CDN instead, maintaining the same behaviour as before this PR. Subsequent page loads use the cached @resource and no CDN request. https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb --- XMOJ.user.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 7fbdfe63..4cd43e48 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -483,6 +483,10 @@ const NewBootstrapSkinCSS = ` text-decoration: none; }`; +// Set to true by the early block if Bootstrap CSS was injected from the @resource +// cache. Checked in the IIFE to decide whether a CDN fallback is needed. +let _earlyBootstrapInjected = false; + // Runs synchronously at document-start. When NewBootstrap is enabled, we apply // the saved theme and inject Bootstrap CSS + the skin CSS before the first paint, // and we block the page's own old stylesheets from loading at all. @@ -502,8 +506,15 @@ const NewBootstrapSkinCSS = ` let head = document.head || document.documentElement; + let bootstrapCSS = GM_getResourceText("BootstrapCSS"); + if (!bootstrapCSS) { + // @resource not cached yet (first install/update). Let the old stylesheets + // load normally and let the late CDN fallback in the IIFE handle Bootstrap. + return; + } + _earlyBootstrapInjected = true; let bsStyle = document.createElement("style"); - bsStyle.textContent = GM_getResourceText("BootstrapCSS"); + bsStyle.textContent = bootstrapCSS; head.appendChild(bsStyle); let isMono = get("MonochromeUI"); @@ -2274,6 +2285,15 @@ async function main() { href: 'https://cdnjs.cloudflare.com/ajax/libs/codemirror/6.65.7/addon/merge/merge.min.css', rel: 'stylesheet' }]; + // If the @resource wasn't cached yet (first install/update), the early + // block bailed out and Bootstrap CSS still needs to load from CDN. + if (!_earlyBootstrapInjected) { + resources.push({ + type: 'link', + href: 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css', + rel: 'stylesheet' + }); + } let loadResources = async () => { let promises = resources.map(resource => { return new Promise((resolve, reject) => { From 3c9fbfcd9864db6d257ece577acd10128e8d27b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 07:57:38 +0000 Subject: [PATCH 04/15] fix: restore link removal loop to guarantee old CSS is un-applied MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The browser preload scanner starts fetching old Bootstrap/theme stylesheets before the document-start MutationObserver can remove the link elements. Once fetched, those stylesheets are applied and override our early-injected Bootstrap 5 + skin CSS — causing the new UI to render for a moment and then immediately revert. Restoring the IIFE-time link removal loop fixes this: removing a from the DOM un-applies its stylesheet from the CSSOM immediately, regardless of whether the preload scanner had already fetched it. The two layers now work together: 1. MutationObserver (document-start): best-effort early block that prevents old CSS from ever loading when the timing works out. 2. Link removal loop (document-idle): guaranteed cleanup that removes any preload-scanner-fetched stylesheets after the DOM is ready, eliminating the revert. https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb --- XMOJ.user.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/XMOJ.user.js b/XMOJ.user.js index 4cd43e48..05844ce8 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -2266,6 +2266,24 @@ async function main() { } if (UtilityEnabled("NewBootstrap")) { + // Remove any old Bootstrap/theme stylesheets that the browser's preload + // scanner may have fetched and applied before the document-start + // MutationObserver could intercept them. Removing a from the DOM + // un-applies its stylesheet from the CSSOM immediately. + let Temp = document.querySelectorAll("link"); + for (var i = 0; i < Temp.length; i++) { + if (Temp[i].href.indexOf("bootstrap.min.css") != -1) { + Temp[i].remove(); + } else if (Temp[i].href.indexOf("white.css") != -1) { + Temp[i].remove(); + } else if (Temp[i].href.indexOf("semantic.min.css") != -1) { + Temp[i].remove(); + } else if (Temp[i].href.indexOf("bootstrap-theme.min.css") != -1) { + Temp[i].remove(); + } else if (Temp[i].href.indexOf("problem.css") != -1) { + Temp[i].remove(); + } + } if (UtilityEnabled("MonochromeUI")) { let fontLink = document.createElement("link"); fontLink.rel = "stylesheet"; From 9b227de725462ed1790158e704d22e9852214644 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 08:06:01 +0000 Subject: [PATCH 05/15] fix: evict preload-scanned old CSS at DOMContentLoaded, not at IIFE time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The visible flash was caused by old Bootstrap 3 CSS sitting in the CSSOM from the moment DOMContentLoaded fired until the IIFE's link removal loop ran — a window that includes a full loginpage.php network round-trip. Move the definitive link removal into the early block's DOMContentLoaded handler so it fires at the earliest possible moment after the DOM is ready, before any async work in the main IIFE. The observer is also disconnected at the same time. The IIFE-level link removal loop is kept as a belt-and-suspenders fallback for edge cases (e.g. early block disabled due to cache miss). https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb --- XMOJ.user.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 05844ce8..825be77a 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -533,7 +533,19 @@ let _earlyBootstrapInjected = false; node.remove(); }); obs.observe(document.documentElement, { childList: true, subtree: true }); - document.addEventListener("DOMContentLoaded", () => obs.disconnect(), { once: true }); + // At DOMContentLoaded, immediately evict any old stylesheets the browser's + // preload scanner fetched before the observer could intercept them. This + // fires before the IIFE's await fetch() completes, so the flash window is + // as short as possible. + document.addEventListener("DOMContentLoaded", () => { + obs.disconnect(); + let links = document.querySelectorAll("link"); + for (let link of links) { + if (blocked.some(h => link.href && link.href.indexOf(h) !== -1)) { + link.remove(); + } + } + }, { once: true }); } catch (e) { console.error("[XMOJ-Script] early init error:", e); } From d554e7de7ee1cec6a4484337d7d8e0016514d32b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 08:10:00 +0000 Subject: [PATCH 06/15] fix: hide page at document-start to eliminate CLS from preload scanner No JavaScript approach can reliably prevent the browser preload scanner from fetching and visibly applying old Bootstrap CSS before DOMContentLoaded. Attempting to evict stylesheets at DOMContentLoaded still leaves a window where old CSS is visible, causing CLS. Instead, hide the page immediately at document-start with html { opacity: 0 !important; } so the user never sees the intermediate broken state. The page is revealed in the DOMContentLoaded handler, after old stylesheet links have been removed and the correct Bootstrap 5 + skin CSS is already in place. A 3-second safety-net timeout ensures the page is always revealed even if something unexpected prevents DOMContentLoaded from firing. This is the standard FOUC prevention pattern. The hidden window is just HTML parse time (~50-150 ms for a server-rendered page), after which the user sees the correct final state with zero CLS. The hide style is only injected when the @resource is available (_earlyBootstrapInjected = true), so the fallback path (cache miss on first install) continues to show the page immediately. https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb --- XMOJ.user.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 825be77a..7244b6d7 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -525,6 +525,16 @@ let _earlyBootstrapInjected = false; skinStyle.textContent = skinCSS; head.appendChild(skinStyle); + // Hide the page until old stylesheets are evicted and our CSS is in place. + // This eliminates CLS from the preload scanner loading old Bootstrap CSS. + // The page is revealed in the DOMContentLoaded handler below. + let foucStyle = document.createElement("style"); + foucStyle.id = "xmoj-fouc-prevent"; + foucStyle.textContent = "html { opacity: 0 !important; }"; + head.appendChild(foucStyle); + // Safety net: reveal after 3 s in case DOMContentLoaded misfires. + let foucTimeout = setTimeout(() => { foucStyle.remove(); }, 3000); + let blocked = ["bootstrap.min.css", "white.css", "semantic.min.css", "bootstrap-theme.min.css", "problem.css"]; let obs = new MutationObserver(mutations => { for (let m of mutations) @@ -533,18 +543,18 @@ let _earlyBootstrapInjected = false; node.remove(); }); obs.observe(document.documentElement, { childList: true, subtree: true }); - // At DOMContentLoaded, immediately evict any old stylesheets the browser's - // preload scanner fetched before the observer could intercept them. This - // fires before the IIFE's await fetch() completes, so the flash window is - // as short as possible. document.addEventListener("DOMContentLoaded", () => { obs.disconnect(); + // Evict any stylesheets the preload scanner fetched before the observer + // could intercept them, then reveal the page in the correct final state. let links = document.querySelectorAll("link"); for (let link of links) { if (blocked.some(h => link.href && link.href.indexOf(h) !== -1)) { link.remove(); } } + clearTimeout(foucTimeout); + foucStyle.remove(); }, { once: true }); } catch (e) { console.error("[XMOJ-Script] early init error:", e); From da0075c70e79ec6103e7d1e9cae3613dbdc0f4ff Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 08:31:20 +0000 Subject: [PATCH 07/15] fix: move FOUC reveal into IIFE to fix mandatory 3s wait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DOMContentLoaded listener registered inside the early IIFE's sandbox context was not firing reliably, causing the opacity:0 FOUC prevention style to only be removed by the 3-second safety-net timeout — resulting in a mandatory 3 s blank page on every load. Fix: expose the FOUC style element and MutationObserver via top-level variables (_foucStyle, _earlyObs) and perform the reveal + link cleanup at the top of the main async IIFE, immediately after its DOMContentLoaded wait. The IIFE's own DOMContentLoaded mechanism is proven to work, so the reveal is now reliable. The early block still hides the page at document-start and arms the MutationObserver for best-effort link interception; the IIFE takes care of teardown and the final CSSOM cleanup at DOMContentLoaded time, before the loginpage.php fetch begins. https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb --- XMOJ.user.js | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/XMOJ.user.js b/XMOJ.user.js index 7244b6d7..02d951eb 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -486,6 +486,11 @@ const NewBootstrapSkinCSS = ` // Set to true by the early block if Bootstrap CSS was injected from the @resource // cache. Checked in the IIFE to decide whether a CDN fallback is needed. let _earlyBootstrapInjected = false; +// Shared between the early block and the IIFE so the IIFE can perform the +// reveal and observer teardown at DOMContentLoaded time (more reliable than +// adding a DOMContentLoaded listener inside the sandboxed early block). +let _foucStyle = null; +let _earlyObs = null; // Runs synchronously at document-start. When NewBootstrap is enabled, we apply // the saved theme and inject Bootstrap CSS + the skin CSS before the first paint, @@ -526,36 +531,20 @@ let _earlyBootstrapInjected = false; head.appendChild(skinStyle); // Hide the page until old stylesheets are evicted and our CSS is in place. - // This eliminates CLS from the preload scanner loading old Bootstrap CSS. - // The page is revealed in the DOMContentLoaded handler below. - let foucStyle = document.createElement("style"); - foucStyle.id = "xmoj-fouc-prevent"; - foucStyle.textContent = "html { opacity: 0 !important; }"; - head.appendChild(foucStyle); - // Safety net: reveal after 3 s in case DOMContentLoaded misfires. - let foucTimeout = setTimeout(() => { foucStyle.remove(); }, 3000); + // Revealed by the IIFE right after its DOMContentLoaded wait (more reliable + // than a DOMContentLoaded listener here due to sandbox context differences). + _foucStyle = document.createElement("style"); + _foucStyle.textContent = "html { opacity: 0 !important; }"; + head.appendChild(_foucStyle); let blocked = ["bootstrap.min.css", "white.css", "semantic.min.css", "bootstrap-theme.min.css", "problem.css"]; - let obs = new MutationObserver(mutations => { + _earlyObs = new MutationObserver(mutations => { for (let m of mutations) for (let node of m.addedNodes) if (node.tagName === "LINK" && blocked.some(h => node.href && node.href.indexOf(h) !== -1)) node.remove(); }); - obs.observe(document.documentElement, { childList: true, subtree: true }); - document.addEventListener("DOMContentLoaded", () => { - obs.disconnect(); - // Evict any stylesheets the preload scanner fetched before the observer - // could intercept them, then reveal the page in the correct final state. - let links = document.querySelectorAll("link"); - for (let link of links) { - if (blocked.some(h => link.href && link.href.indexOf(h) !== -1)) { - link.remove(); - } - } - clearTimeout(foucTimeout); - foucStyle.remove(); - }, { once: true }); + _earlyObs.observe(document.documentElement, { childList: true, subtree: true }); } catch (e) { console.error("[XMOJ-Script] early init error:", e); } @@ -1972,6 +1961,17 @@ GM_registerMenuCommand("重置数据", () => { if (document.readyState === "loading") { await new Promise(r => document.addEventListener("DOMContentLoaded", r, { once: true })); } +// Reveal the page now that DOMContentLoaded has fired. Remove any old Bootstrap +// stylesheets the preload scanner fetched (un-applies them from the CSSOM), then +// remove the FOUC hide so the user sees the correct final state immediately. +if (_earlyObs) { _earlyObs.disconnect(); _earlyObs = null; } +if (_foucStyle) { + let _blocked = ["bootstrap.min.css", "white.css", "semantic.min.css", "bootstrap-theme.min.css", "problem.css"]; + for (let _link of document.querySelectorAll("link")) { + if (_blocked.some(h => _link.href && _link.href.indexOf(h) !== -1)) _link.remove(); + } + _foucStyle.remove(); _foucStyle = null; +} //otherwise CurrentUsername might be undefined let loginStatus; await fetch("https://www.xmoj.tech/loginpage.php") From 258a31d414178728ba0628b89a6db2fb0d0996ad Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 14 Jun 2026 08:32:50 +0000 Subject: [PATCH 08/15] 3.5.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b1f5181a..d94bfece 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xmoj-script", - "version": "3.5.2", + "version": "3.5.3", "description": "an improvement script for xmoj.tech", "main": "AddonScript.js", "scripts": { From 8ba07e3ee7e8fc080a9c3e615ae5f13665825f5e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 14 Jun 2026 08:32:56 +0000 Subject: [PATCH 09/15] Update version info to 3.5.3 --- Update.json | 11 +++++++++++ XMOJ.user.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Update.json b/Update.json index 4f2f2901..a35b6e7b 100644 --- a/Update.json +++ b/Update.json @@ -3638,6 +3638,17 @@ } ], "Notes": "Use Monaco Editor and save code in submitpage.php when edit the content." + }, + "3.5.3": { + "UpdateDate": 1781425971151, + "Prerelease": true, + "UpdateContents": [ + { + "PR": 992, + "Description": "Refactor and enhance XMOJ.user.js with improved features" + } + ], + "Notes": "Major refactoring and enhancement of XMOJ.user.js with improved code organization and feature implementations." } } } \ No newline at end of file diff --git a/XMOJ.user.js b/XMOJ.user.js index 02d951eb..5276cc56 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.5.2 +// @version 3.5.3 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen From 366829b2d5c212a030a968ee8c046a3312e75a99 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 14 Jun 2026 08:34:13 +0000 Subject: [PATCH 10/15] Update time and description of 3.5.3 --- Update.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Update.json b/Update.json index a35b6e7b..8f391100 100644 --- a/Update.json +++ b/Update.json @@ -3640,12 +3640,12 @@ "Notes": "Use Monaco Editor and save code in submitpage.php when edit the content." }, "3.5.3": { - "UpdateDate": 1781425971151, + "UpdateDate": 1781426046724, "Prerelease": true, "UpdateContents": [ { "PR": 992, - "Description": "Refactor and enhance XMOJ.user.js with improved features" + "Description": "fix page flashes in dark mode" } ], "Notes": "Major refactoring and enhancement of XMOJ.user.js with improved code organization and feature implementations." From bd585277a6139ef4aa5b37f58fd496d430cf40f3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 14 Jun 2026 08:36:35 +0000 Subject: [PATCH 11/15] Update time and description of 3.5.3 --- Update.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Update.json b/Update.json index 8f391100..d47f2119 100644 --- a/Update.json +++ b/Update.json @@ -3640,15 +3640,15 @@ "Notes": "Use Monaco Editor and save code in submitpage.php when edit the content." }, "3.5.3": { - "UpdateDate": 1781426046724, + "UpdateDate": 1781426190003, "Prerelease": true, "UpdateContents": [ { "PR": 992, - "Description": "fix page flashes in dark mode" + "Description": "fix: eliminate dark-mode flash in MonochromeUI/NewBootstrap" } ], - "Notes": "Major refactoring and enhancement of XMOJ.user.js with improved code organization and feature implementations." + "Notes": "修复了 MonochromeUI / NewBootstrap 新界面在深色模式下加载时出现的白色闪烁(FOUC/CLS)问题。" } } } \ No newline at end of file From 6c5bbbc79cda7ef668ab26a56557fbde5cac5849 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 14 Jun 2026 08:37:31 +0000 Subject: [PATCH 12/15] Update time and description of 3.5.3 --- Update.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Update.json b/Update.json index d47f2119..b7c126dd 100644 --- a/Update.json +++ b/Update.json @@ -3640,7 +3640,7 @@ "Notes": "Use Monaco Editor and save code in submitpage.php when edit the content." }, "3.5.3": { - "UpdateDate": 1781426190003, + "UpdateDate": 1781426245318, "Prerelease": true, "UpdateContents": [ { From 63b2e69c48dcb6ccad049d4c71bdbb1430ad7bc7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 14 Jun 2026 08:39:08 +0000 Subject: [PATCH 13/15] Update time and description of 3.5.3 --- Update.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Update.json b/Update.json index b7c126dd..c67b851f 100644 --- a/Update.json +++ b/Update.json @@ -3640,7 +3640,7 @@ "Notes": "Use Monaco Editor and save code in submitpage.php when edit the content." }, "3.5.3": { - "UpdateDate": 1781426245318, + "UpdateDate": 1781426342433, "Prerelease": true, "UpdateContents": [ { From 74a6c074e967b98f284e9e3c9445c0cbafc088ff Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 14 Jun 2026 08:50:07 +0000 Subject: [PATCH 14/15] fix: inject skin CSS on @resource cache miss When GM_getResourceText("BootstrapCSS") returns null (first install or @resource not yet cached), the early block exits before injecting MonochromeSkinCSS/NewBootstrapSkinCSS. The Style element in the IIFE now injects the skin CSS (plus AddAnimation/AddColorText overrides) whenever _earlyBootstrapInjected is false, so the skin is always applied regardless of cache state. https://claude.ai/code/session_01B1RgyUvtsWWhS2hdNiUhZb --- XMOJ.user.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/XMOJ.user.js b/XMOJ.user.js index 5276cc56..9ab9ad5c 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -2386,6 +2386,12 @@ async function main() { } let Style = document.createElement("style"); document.body.appendChild(Style); + if (!_earlyBootstrapInjected) { + let _isMono = UtilityEnabled("MonochromeUI"); + Style.innerHTML = _isMono ? MonochromeSkinCSS : NewBootstrapSkinCSS; + if (UtilityEnabled("AddAnimation")) Style.innerHTML += `.status, .test-case { transition: ${_isMono ? "100ms ease" : "0.5s"} !important; }`; + if (UtilityEnabled("AddColorText")) Style.innerHTML += `.red { color: red !important; } .green { color: green !important; } .blue { color: blue !important; }`; + } if (UtilityEnabled("RemoveUseless")) { if (document.getElementsByClassName("footer")[0] != null) { From 69a65f58fe376c12c9078e9b5b5759cf56e2a19b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 14 Jun 2026 08:50:44 +0000 Subject: [PATCH 15/15] Update time and description of 3.5.3 --- Update.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Update.json b/Update.json index c67b851f..3cc9edb0 100644 --- a/Update.json +++ b/Update.json @@ -3640,7 +3640,7 @@ "Notes": "Use Monaco Editor and save code in submitpage.php when edit the content." }, "3.5.3": { - "UpdateDate": 1781426342433, + "UpdateDate": 1781427038989, "Prerelease": true, "UpdateContents": [ {