Use Monaco Editor and save code in submitpage.php when edit the content.#989
Use Monaco Editor and save code in submitpage.php when edit the content.#989def-WA2025 wants to merge 3 commits into
Conversation
Reviewer's GuideReplaces the existing CodeMirror-based editor integration with a Monaco-based implementation, including a dynamic loader/shim and localStorage-backed persistence for submitpage.php, and adjusts related code display snippets to use Monaco or a simple fallback where appropriate. Sequence diagram for Monaco editor initialization and autosave on submitpage.phpsequenceDiagram
actor User
participant SubmitPage
participant ensureMonaco
participant createMonacoEditor
participant MonacoEditor
participant localStorage
User->>SubmitPage: Open /submitpage.php
SubmitPage->>createMonacoEditor: createMonacoEditor('MonacoEditor', options)
createMonacoEditor->>ensureMonaco: ensureMonaco()
ensureMonaco->>ensureMonaco: Load loader.js via script
ensureMonaco->>ensureMonaco: require(['vs/editor/editor.main'])
ensureMonaco-->>createMonacoEditor: monaco ready
createMonacoEditor->>MonacoEditor: monaco.editor.create(container, config)
createMonacoEditor->>localStorage: getItem(localStorageKey)
localStorage-->>createMonacoEditor: saved code (if any)
createMonacoEditor->>MonacoEditor: editor.setValue(saved)
createMonacoEditor-->>SubmitPage: adapter (CodeMirrorElement)
User->>MonacoEditor: Edit code
MonacoEditor-->>localStorage: setItem(localStorageKey, value) (debounced)
User->>MonacoEditor: Press Ctrl+Enter
MonacoEditor->>SubmitPage: Submit.click()
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Deploying xmoj-script-dev-channel with
|
| Latest commit: |
6f7cc75
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://75217859.xmoj-script-dev-channel.pages.dev |
| Branch Preview URL: | https://def-wa2025-monaco-editor-and.xmoj-script-dev-channel.pages.dev |
There was a problem hiding this comment.
Hey - I've found 2 security issues, 1 other issue, and left some high level feedback:
Security issues:
- User controlled data in methods like
innerHTML,outerHTMLordocument.writeis an anti-pattern that can lead to XSS vulnerabilities (link) - User controlled data in a
document.querySelector("body > div > div.mt-3").innerHTMLis an anti-pattern that can lead to XSS vulnerabilities (link)
General comments:
- On submitpage.php the hidden #CodeInput textarea is no longer bound to the editor as it was with CodeMirror.fromTextArea; make sure you copy the Monaco editor content back into #CodeInput (e.g., in the submit handler) so the backend still receives the code.
- In the freopen snippet error block you always append an empty div (codeHost) to #ErrorMessage and, in the fallback path, also append a
directly to #ErrorMessage; consider either rendering the fallback
inside codeHost or skipping codeHost creation when Monaco is unavailable to avoid redundant markup.
- The CodeMirror shim exposes _monacoEditor and callers reach into that to add commands; consider exposing a small public API (e.g., addCommand/triggerSuggest) on the adapter instead of using the private _monacoEditor field to keep the abstraction boundary cleaner.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- On submitpage.php the hidden #CodeInput textarea is no longer bound to the editor as it was with CodeMirror.fromTextArea; make sure you copy the Monaco editor content back into #CodeInput (e.g., in the submit handler) so the backend still receives the code.
- In the freopen snippet error block you always append an empty div (codeHost) to #ErrorMessage and, in the fallback path, also append a <pre> directly to #ErrorMessage; consider either rendering the fallback <pre> inside codeHost or skipping codeHost creation when Monaco is unavailable to avoid redundant markup.
- The CodeMirror shim exposes _monacoEditor and callers reach into that to add commands; consider exposing a small public API (e.g., addCommand/triggerSuggest) on the adapter instead of using the private _monacoEditor field to keep the abstraction boundary cleaner.
## Individual Comments
### Comment 1
<location path="XMOJ.user.js" line_range="374-383" />
<code_context>
+ shim.MergeView = function(container, options) {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** MergeView hardcodes the language to C++, ignoring any mode/language passed in options.
The shim currently instantiates both models with language `'cpp'` and ignores `options.mode` (or any language option), which changes behavior compared to the CodeMirror-based MergeView for non-C++ content. Please derive the Monaco language from the provided mode (similar to `shim`/`createMonacoEditor`, mapping known CodeMirror modes and defaulting to C++) so other languages still get appropriate syntax handling.
</issue_to_address>
### Comment 2
<location path="XMOJ.user.js" line_range="3633-3638" />
<code_context>
document.querySelector("body > div > div.mt-3").innerHTML = `<center class="mb-3">` + `<h3>提交代码</h3>` + (SearchParams.get("id") != null ? `题目<span class="blue">${Number(SearchParams.get("id"))}</span>` : `比赛<span class="blue">${Number(SearchParams.get("cid")) + `</span> 题目<span class="blue">` + String.fromCharCode(65 + parseInt(SearchParams.get("pid")))}</span>`) + `</center>
<div id="MonacoEditor" style="width:100%; height:400px;"></div>
<textarea id="CodeInput" style="display:none"></textarea>
<center class="mt-3">
<input id="enable_O2" name="enable_O2" type="checkbox"><label for="enable_O2">打开O2开关</label>
<br>
<input id="Submit" class="btn btn-info mt-2" type="button" value="提交">
<div id="ErrorElement" class="mt-2" style="display: none; text-align: left; padding: 10px;">
<div id="ErrorMessage" style="white-space: pre; background-color: rgba(0, 0, 0, 0.1); padding: 10px; border-radius: 5px;"></div>
<button id="PassCheck" class="btn btn-outline-secondary mt-2" style="display: none">强制提交</button>
</div>
</center>`;
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>
### Comment 3
<location path="XMOJ.user.js" line_range="3633-3638" />
<code_context>
document.querySelector("body > div > div.mt-3").innerHTML = `<center class="mb-3">` + `<h3>提交代码</h3>` + (SearchParams.get("id") != null ? `题目<span class="blue">${Number(SearchParams.get("id"))}</span>` : `比赛<span class="blue">${Number(SearchParams.get("cid")) + `</span> 题目<span class="blue">` + String.fromCharCode(65 + parseInt(SearchParams.get("pid")))}</span>`) + `</center>
<div id="MonacoEditor" style="width:100%; height:400px;"></div>
<textarea id="CodeInput" style="display:none"></textarea>
<center class="mt-3">
<input id="enable_O2" name="enable_O2" type="checkbox"><label for="enable_O2">打开O2开关</label>
<br>
<input id="Submit" class="btn btn-info mt-2" type="button" value="提交">
<div id="ErrorElement" class="mt-2" style="display: none; text-align: left; padding: 10px;">
<div id="ErrorMessage" style="white-space: pre; background-color: rgba(0, 0, 0, 0.1); padding: 10px; border-radius: 5px;"></div>
<button id="PassCheck" class="btn btn-outline-secondary mt-2" style="display: none">强制提交</button>
</div>
</center>`;
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `document.querySelector("body > div > div.mt-3").innerHTML` is an anti-pattern that can lead to XSS vulnerabilities
*Source: opengrep*
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| 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 }); | ||
| const orig = options && options.value ? options.value : ''; |
There was a problem hiding this comment.
suggestion (bug_risk): MergeView hardcodes the language to C++, ignoring any mode/language passed in options.
The shim currently instantiates both models with language 'cpp' and ignores options.mode (or any language option), which changes behavior compared to the CodeMirror-based MergeView for non-C++ content. Please derive the Monaco language from the provided mode (similar to shim/createMonacoEditor, mapping known CodeMirror modes and defaulting to C++) so other languages still get appropriate syntax handling.
| document.querySelector("body > div > div.mt-3").innerHTML = `<center class="mb-3">` + `<h3>提交代码</h3>` + (SearchParams.get("id") != null ? `题目<span class="blue">${Number(SearchParams.get("id"))}</span>` : `比赛<span class="blue">${Number(SearchParams.get("cid")) + `</span> 题目<span class="blue">` + String.fromCharCode(65 + parseInt(SearchParams.get("pid")))}</span>`) + `</center> | ||
| <textarea id="CodeInput"></textarea> | ||
| <div id="MonacoEditor" style="width:100%; height:400px;"></div> | ||
| <textarea id="CodeInput" style="display:none"></textarea> | ||
| <center class="mt-3"> | ||
| <input id="enable_O2" name="enable_O2" type="checkbox"><label for="enable_O2">打开O2开关</label> | ||
| <br> |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
| document.querySelector("body > div > div.mt-3").innerHTML = `<center class="mb-3">` + `<h3>提交代码</h3>` + (SearchParams.get("id") != null ? `题目<span class="blue">${Number(SearchParams.get("id"))}</span>` : `比赛<span class="blue">${Number(SearchParams.get("cid")) + `</span> 题目<span class="blue">` + String.fromCharCode(65 + parseInt(SearchParams.get("pid")))}</span>`) + `</center> | ||
| <textarea id="CodeInput"></textarea> | ||
| <div id="MonacoEditor" style="width:100%; height:400px;"></div> | ||
| <textarea id="CodeInput" style="display:none"></textarea> | ||
| <center class="mt-3"> | ||
| <input id="enable_O2" name="enable_O2" type="checkbox"><label for="enable_O2">打开O2开关</label> | ||
| <br> |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-innerhtml): User controlled data in a document.querySelector("body > div > div.mt-3").innerHTML is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
| document.querySelector("body > div > div.mt-3").innerHTML = `<center class="mb-3">` + `<h3>提交代码</h3>` + (SearchParams.get("id") != null ? `题目<span class="blue">${Number(SearchParams.get("id"))}</span>` : `比赛<span class="blue">${Number(SearchParams.get("cid")) + `</span> 题目<span class="blue">` + String.fromCharCode(65 + parseInt(SearchParams.get("pid")))}</span>`) + `</center> | ||
| <textarea id="CodeInput"></textarea> | ||
| <div id="MonacoEditor" style="width:100%; height:400px;"></div> | ||
| <textarea id="CodeInput" style="display:none"></textarea> | ||
| <center class="mt-3"> | ||
| <input id="enable_O2" name="enable_O2" type="checkbox"><label for="enable_O2">打开O2开关</label> | ||
| <br> |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
| document.querySelector("body > div > div.mt-3").innerHTML = `<center class="mb-3">` + `<h3>提交代码</h3>` + (SearchParams.get("id") != null ? `题目<span class="blue">${Number(SearchParams.get("id"))}</span>` : `比赛<span class="blue">${Number(SearchParams.get("cid")) + `</span> 题目<span class="blue">` + String.fromCharCode(65 + parseInt(SearchParams.get("pid")))}</span>`) + `</center> | ||
| <textarea id="CodeInput"></textarea> | ||
| <div id="MonacoEditor" style="width:100%; height:400px;"></div> | ||
| <textarea id="CodeInput" style="display:none"></textarea> | ||
| <center class="mt-3"> | ||
| <input id="enable_O2" name="enable_O2" type="checkbox"><label for="enable_O2">打开O2开关</label> | ||
| <br> |
There was a problem hiding this comment.
security (javascript.browser.security.insecure-innerhtml): User controlled data in a document.querySelector("body > div > div.mt-3").innerHTML is an anti-pattern that can lead to XSS vulnerabilities
Source: opengrep
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="package.json">
<violation number="1" location="package.json:3">
P2: Manual version bump in `package.json` violates the project's automated version sync contract. Project conventions state version numbers must never be edited manually — version bumps are handled automatically by GitHub Actions when PRs modify `XMOJ.user.js`. A manual edit here may desync `package.json` from `XMOJ.user.js` and `Update.json` after the automated workflow runs.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| { | ||
| "name": "xmoj-script", | ||
| "version": "3.5.1", | ||
| "version": "3.5.2", |
There was a problem hiding this comment.
P2: Manual version bump in package.json violates the project's automated version sync contract. Project conventions state version numbers must never be edited manually — version bumps are handled automatically by GitHub Actions when PRs modify XMOJ.user.js. A manual edit here may desync package.json from XMOJ.user.js and Update.json after the automated workflow runs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At package.json, line 3:
<comment>Manual version bump in `package.json` violates the project's automated version sync contract. Project conventions state version numbers must never be edited manually — version bumps are handled automatically by GitHub Actions when PRs modify `XMOJ.user.js`. A manual edit here may desync `package.json` from `XMOJ.user.js` and `Update.json` after the automated workflow runs.</comment>
<file context>
@@ -1,6 +1,6 @@
{
"name": "xmoj-script",
- "version": "3.5.1",
+ "version": "3.5.2",
"description": "an improvement script for xmoj.tech",
"main": "AddonScript.js",
</file context>
| "version": "3.5.2", | |
| "version": "3.5.1", |
There was a problem hiding this comment.
3 issues found across 1 file
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="XMOJ.user.js">
<violation number="1" location="XMOJ.user.js:264">
P2: Monaco bootstrap fallback can hang forever because the polling Promise has no timeout/reject path.</violation>
<violation number="2" location="XMOJ.user.js:387">
P2: MergeView hardcodes `'cpp'` as the language for both models, ignoring any `options.mode` or `options.language` that may be passed in. This diverges from the behavior of the regular shim/`createMonacoEditor` which maps `options.mode` to a Monaco language. Derive the language from options (e.g., `const lang = options && options.mode === 'text/x-c++src' ? 'cpp' : (options && options.language || 'cpp');`) so non-C++ content receives appropriate syntax highlighting.</violation>
<violation number="3" location="XMOJ.user.js:3649">
P1: Submit page has no fallback when Monaco initialization fails, which can break code submission entirely on loader/CDN errors.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| }) | ||
| })(); | ||
| CodeMirrorElement.setSize("100%", "auto"); | ||
| CodeMirrorElement = await createMonacoEditor('MonacoEditor', { |
There was a problem hiding this comment.
P1: Submit page has no fallback when Monaco initialization fails, which can break code submission entirely on loader/CDN errors.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At XMOJ.user.js, line 3649:
<comment>Submit page has no fallback when Monaco initialization fails, which can break code submission entirely on loader/CDN errors.</comment>
<file context>
@@ -3489,24 +3646,21 @@ async function main() {
- })
- })();
- CodeMirrorElement.setSize("100%", "auto");
+ CodeMirrorElement = await createMonacoEditor('MonacoEditor', {
+ language: 'cpp',
+ value: '',
</file context>
| const mod = options && options.orig ? options.orig : ''; | ||
| const origVal = wrapper.ignoreWhitespace ? orig.replace(/\s+/g,' ') : orig; | ||
| const modVal = wrapper.ignoreWhitespace ? mod.replace(/\s+/g,' ') : mod; | ||
| const originalModel = monaco.editor.createModel(origVal, 'cpp'); |
There was a problem hiding this comment.
P2: MergeView hardcodes 'cpp' as the language for both models, ignoring any options.mode or options.language that may be passed in. This diverges from the behavior of the regular shim/createMonacoEditor which maps options.mode to a Monaco language. Derive the language from options (e.g., const lang = options && options.mode === 'text/x-c++src' ? 'cpp' : (options && options.language || 'cpp');) so non-C++ content receives appropriate syntax highlighting.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At XMOJ.user.js, line 387:
<comment>MergeView hardcodes `'cpp'` as the language for both models, ignoring any `options.mode` or `options.language` that may be passed in. This diverges from the behavior of the regular shim/`createMonacoEditor` which maps `options.mode` to a Monaco language. Derive the language from options (e.g., `const lang = options && options.mode === 'text/x-c++src' ? 'cpp' : (options && options.language || 'cpp');`) so non-C++ content receives appropriate syntax highlighting.</comment>
<file context>
@@ -240,6 +240,162 @@ let GetUserBadge = async (Username) => {
+ const mod = options && options.orig ? options.orig : '';
+ const origVal = wrapper.ignoreWhitespace ? orig.replace(/\s+/g,' ') : orig;
+ const modVal = wrapper.ignoreWhitespace ? mod.replace(/\s+/g,' ') : mod;
+ const originalModel = monaco.editor.createModel(origVal, 'cpp');
+ const modifiedModel = monaco.editor.createModel(modVal, 'cpp');
+ diffEditor.setModel({ original: originalModel, modified: modifiedModel });
</file context>
| try { | ||
| require(['vs/editor/editor.main'], function() { resolve(); }); | ||
| } catch (e) { | ||
| const check = setInterval(() => { if (typeof monaco !== 'undefined') { clearInterval(check); resolve(); } }, 50); |
There was a problem hiding this comment.
P2: Monaco bootstrap fallback can hang forever because the polling Promise has no timeout/reject path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At XMOJ.user.js, line 264:
<comment>Monaco bootstrap fallback can hang forever because the polling Promise has no timeout/reject path.</comment>
<file context>
@@ -240,6 +240,162 @@ let GetUserBadge = async (Username) => {
+ try {
+ require(['vs/editor/editor.main'], function() { resolve(); });
+ } catch (e) {
+ const check = setInterval(() => { if (typeof monaco !== 'undefined') { clearInterval(check); resolve(); } }, 50);
+ }
+ });
</file context>
What does this PR aim to accomplish?:
Use Monaco Editor and save code in submitpage.php when edit the content.
How does this PR accomplish the above?:
Editor -> Monaco
Save code in localStorage.
By submitting this pull request, I confirm the following:
git rebase)Summary by Sourcery
Switch the submit page code editor to Monaco and integrate it with existing scripting utilities while preserving compatibility via a CodeMirror shim.
New Features:
Enhancements:
Summary by cubic
Replaced the submit page editor with
monaco-editorand added automatic local save/restore of code. This improves the editing experience and helps prevent code loss on refresh.New Features
monaco-editorfrom CDN and add a small shim to keep existing CodeMirror calls working.localStorageper problem/contest and restore it on load.<pre>fallback.Dependencies
xmoj-scriptto 3.5.2 and add a prerelease entry inUpdate.json.Written for commit 6f7cc75. Summary will update on new commits.