diff --git a/Update.json b/Update.json index 4eebcd48..ee7882f6 100644 --- a/Update.json +++ b/Update.json @@ -3627,6 +3627,17 @@ } ], "Notes": "No release notes were provided for this release." + }, + "3.5.2": { + "UpdateDate": 1780217926990, + "Prerelease": true, + "UpdateContents": [ + { + "PR": 989, + "Description": "Use Monaco Editor and save code in submitpage.php when edit the content" + } + ], + "Notes": "Use Monaco Editor and save code in submitpage.php when edit the content." } } } \ No newline at end of file diff --git a/XMOJ.user.js b/XMOJ.user.js index 8b13e45a..d5bf7ebd 100644 --- a/XMOJ.user.js +++ b/XMOJ.user.js @@ -1,6 +1,6 @@ // ==UserScript== // @name XMOJ -// @version 3.5.1 +// @version 3.5.2 // @description XMOJ增强脚本 // @author @XMOJ-Script-dev, @langningchen and the community // @namespace https://github/langningchen @@ -240,6 +240,167 @@ let GetUserBadge = async (Username) => { } } }; +async function ensureMonaco() { + if (typeof monaco !== 'undefined') return; + const loaderUrl = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs/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: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs' } }); } catch (e) {} + resolve(); + }; + s.onerror = reject; + document.head.appendChild(s); + }); + } else { + try { require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.55.1/min/vs' } }); } catch (e) {} + } + 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); + } + }); + if (!document.getElementById('monaco-custom-style')) { + const style = document.createElement('style'); + style.id = 'monaco-custom-style'; + style.textContent = ` +/* rounded corners + find/replace/goto UI tweaks */ +.monaco-editor, .monaco-editor .margin, .monaco-editor .overflow-guard, .monaco-editor .monaco-editor-background { + border-radius: 8px !important; +} +.monaco-editor .find-widget, .monaco-editor .replace-widget, .monaco-editor .gotoLine-widget { + border-radius: 8px !important; + overflow: hidden; +} +.monaco-editor .monaco-scrollable-element .monaco-editor-background { + border-radius: 8px !important; +} + `; + document.head.appendChild(style); + } +} + +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); + 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')); + const editor = monaco.editor.create(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 + }); + try { + if (options.restoreOnLoad !== false) { + const saved = localStorage.getItem(key); + if (saved !== null && saved !== 'null') editor.setValue(saved); + } + } catch (e) {} + let saveTimer = null; + const doSave = () => { try { localStorage.setItem(key, editor.getValue()); } 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 = container; if (w) el.style.width = w; if (h) { 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) {} }, + getWrapperElement: () => container, + focus: () => { try { editor.focus(); } catch (e) {} }, + _monacoEditor: editor, + showFind: () => { try { editor.trigger('', 'editor.action.startFindReplaceAction'); } catch (e) {} }, + goToLine: (line) => { try { editor.setPosition({ lineNumber: parseInt(line) || 1, column: 1 }); editor.revealPositionInCenter({ lineNumber: parseInt(line) || 1, column: 1 }); editor.focus(); } catch (e) {} }, + 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) {} }, + saveToLocal: doSave, + localStorageKey: key + }; + 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'); + div.className = 'codemirror-shim-host'; + 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; + let _lastSetSizeArgs = null; + const placeholderAdapter = { + getValue: () => container._cmValue || '', + setValue: (v) => { container._cmValue = v; if (container._cmEditor) try { container._cmEditor.setValue(v); } catch (e) {} }, + 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) {} }, + getWrapperElement: () => container, + focus: () => { if (container._cmEditor) try { container._cmEditor.focus(); } catch (e) {} }, + _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; + if (_lastSetSizeArgs) monacoAdapter.setSize(_lastSetSizeArgs[0], _lastSetSizeArgs[1]); + } catch (e) { console.error(e); } + })(); + return placeholderAdapter; + }; + shim.fromTextArea = function(textarea, options) { return shim(textarea, options); }; + shim.MergeView = function(container, 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); } + const wrapper = { ignoreWhitespace: !!(options && options.ignoreWhitespace), _diffEditor: null, _originalModel: null, _modifiedModel: null }; + (async () => { + try { + await ensureMonaco(); + const diffEditor = monaco.editor.createDiffEditor(el, { readOnly: !!(options && options.readOnly), theme: (typeof UtilityEnabled === 'function' && UtilityEnabled("DarkMode") ? 'vs-dark' : 'vs'), minimap: { enabled: false }, automaticLayout: true, ignoreTrimWhitespace: !!wrapper.ignoreWhitespace }); + const orig = options && options.value ? options.value : ''; + const mod = options && options.orig ? options.orig : ''; + const isCpp = (options && options.mode === 'text/x-c++src') || (options && options.mode && options.mode.indexOf('c++') !== -1); + const lang = isCpp ? 'cpp' : (options && 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; + } catch (e) { console.error(e); } + })(); + return wrapper; + }; + window.CodeMirror = shim; +})(); /** * 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. @@ -3474,8 +3635,9 @@ async function main() { } } else if (location.pathname == "/submitpage.php") { document.title = "提交代码: " + (SearchParams.get("id") != null ? "题目" + Number(SearchParams.get("id")) : "比赛" + Number(SearchParams.get("cid"))); - document.querySelector("body > div > div.mt-3").innerHTML = `