// ==UserScript== // @name JXAU OJ - 代码编辑器 // @namespace http://tampermonkey.net/ // @version 1.4 // @description 在题目页面添加代码编辑器 // @author Sunse666 // @match *://*/problem/* // @match *://*/contest/*/problem/* // @grant GM_addStyle // @require https://cdn.bootcdn.net/ajax/libs/highlight.js/11.11.1/highlight.min.js // @require https://cdn.bootcdn.net/ajax/libs/highlight.js/11.11.1/languages/go.min.js // @run-at document-end // ==/UserScript== (function() { 'use strict'; console.log('[JXAU OJ] 脚本启动, hljs 状态:', typeof hljs); GM_addStyle(` .hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c} `); // 编辑器样式 GM_addStyle(` .jxau-editor-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); display: none; justify-content: center; align-items: center; z-index: 9999; } .jxau-editor-overlay.active { display: flex; } .jxau-editor-container { width: 95%; max-width: 1200px; height: 90vh; background: #1e1e2e; border: 1px solid #45475a; border-radius: 12px; display: flex; flex-direction: column; } .jxau-editor-header { padding: 14px 18px; border-bottom: 1px solid #45475a; display: flex; justify-content: space-between; align-items: center; background: #313244; border-radius: 12px 12px 0 0; } .jxau-editor-header h3 { color: #89b4fa; font-size: 15px; margin: 0; } .jxau-editor-controls { display: flex; gap: 10px; align-items: center; } .jxau-lang-select { padding: 8px 14px; border: 1px solid #45475a; border-radius: 6px; background: #1e1e2e; color: #cdd6f4; font-size: 13px; cursor: pointer; } .jxau-close-btn { width: 32px; height: 32px; border: 1px solid #45475a; border-radius: 6px; background: #313244; color: #a6adc8; font-size: 20px; cursor: pointer; } .jxau-close-btn:hover { border-color: #f38ba8; color: #f38ba8; } .jxau-editor-body { flex: 1; display: flex; overflow: hidden; padding: 15px; gap: 15px; } .jxau-problem-panel { width: 40%; background: #313244; border-radius: 8px; padding: 15px; overflow: auto; color: #cdd6f4; font-size: 13px; line-height: 1.8; } .jxau-problem-panel::-webkit-scrollbar { width: 8px; height: 8px; } .jxau-problem-panel::-webkit-scrollbar-track { background: #1e1e2e; border-radius: 4px; } .jxau-problem-panel::-webkit-scrollbar-thumb { background: #45475a; border-radius: 4px; border: 2px solid #1e1e2e; } .jxau-problem-panel::-webkit-scrollbar-thumb:hover { background: #585b70; } .jxau-problem-panel::-webkit-scrollbar-corner { background: #1e1e2e; } .jxau-problem-panel h4 { color: #89b4fa; font-size: 14px; margin: 0 0 10px 0; padding-bottom: 8px; border-bottom: 1px solid #45475a; } .jxau-problem-panel h5 { color: #cba6f7; font-size: 13px; margin: 16px 0 8px 0; padding-bottom: 4px; border-bottom: 1px solid #45475a; } .jxau-problem-panel pre { background: #11111b; padding: 10px; border-radius: 6px; overflow: auto; font-family: 'Consolas', 'Monaco', monospace; font-size: 12px; color: #a6e3a1; margin: 8px 0; } .jxau-problem-panel .problem-markdown-content { color: #cdd6f4; } .jxau-problem-panel .problem-markdown-content p { margin: 8px 0; line-height: 1.8; } .jxau-problem-panel .problem-markdown-content h1, .jxau-problem-panel .problem-markdown-content h2, .jxau-problem-panel .problem-markdown-content h3, .jxau-problem-panel .problem-markdown-content h4, .jxau-problem-panel .problem-markdown-content h5, .jxau-problem-panel .problem-markdown-content h6 { color: #cba6f7; margin: 16px 0 8px 0; padding-bottom: 4px; border-bottom: 1px solid #45475a; font-size: 14px; } .jxau-problem-panel .problem-markdown-content h1 { font-size: 16px; } .jxau-problem-panel .problem-markdown-content h2 { font-size: 15px; } .jxau-problem-panel .problem-markdown-content h3, .jxau-problem-panel .problem-markdown-content h4, .jxau-problem-panel .problem-markdown-content h5, .jxau-problem-panel .problem-markdown-content h6 { font-size: 14px; } .jxau-problem-panel .problem-markdown-content code { background: #11111b; padding: 2px 6px; border-radius: 4px; font-family: 'Consolas', 'Monaco', monospace; font-size: 12px; color: #f38ba8; } .jxau-problem-panel .problem-markdown-content pre { background: #11111b; padding: 12px; border-radius: 6px; overflow: auto; margin: 10px 0; } .jxau-problem-panel .problem-markdown-content pre code { background: transparent; padding: 0; color: #a6e3a1; } .jxau-problem-panel .problem-markdown-content ul, .jxau-problem-panel .problem-markdown-content ol { margin: 8px 0; padding-left: 20px; } .jxau-problem-panel .problem-markdown-content li { margin: 4px 0; } .jxau-problem-panel .problem-markdown-content table { width: 100%; border-collapse: collapse; margin: 10px 0; background: #1e1e2e; border-radius: 6px; overflow: hidden; } .jxau-problem-panel .problem-markdown-content th, .jxau-problem-panel .problem-markdown-content td { padding: 8px 12px; border: 1px solid #45475a; text-align: left; } .jxau-problem-panel .problem-markdown-content th { background: #313244; color: #89b4fa; font-weight: bold; } .jxau-problem-panel .problem-markdown-content blockquote { border-left: 4px solid #89b4fa; margin: 10px 0; padding: 8px 16px; background: #1e1e2e; border-radius: 0 6px 6px 0; } .jxau-problem-panel .problem-markdown-content a { color: #89b4fa; text-decoration: none; } .jxau-problem-panel .problem-markdown-content a:hover { text-decoration: underline; } .jxau-problem-panel .problem-markdown-content img { max-width: 100%; border-radius: 6px; margin: 10px 0; } .jxau-problem-panel .problem-markdown-content hr { border: none; border-top: 1px solid #45475a; margin: 16px 0; } .jxau-problem-panel .sample-container, .jxau-problem-panel [class*="sample"] { display: flex !important; flex-direction: column !important; gap: 15px !important; width: 100% !important; } .jxau-problem-panel .sample-container > *, .jxau-problem-panel [class*="sample"] > * { width: 100% !important; max-width: 100% !important; flex: none !important; } .jxau-problem-panel .problem-markdown-content h5, .jxau-problem-panel .problem-markdown-content h6, .jxau-problem-panel .problem-markdown-content strong { color: #cba6f7; font-weight: bold; } .jxau-code-panel { flex: 1; display: flex; flex-direction: column; background: #313244; border-radius: 8px; padding: 15px; } .jxau-editor-wrapper { flex: 1; display: flex; overflow: hidden; border: 1px solid #45475a; border-radius: 8px; background: #0d1117; margin-bottom: 12px; position: relative; } .jxau-line-numbers { width: 50px; background: #1e1e2e; border-right: 1px solid #45475a; padding: 12px 8px; text-align: right; font-family: 'Consolas', 'Monaco', monospace; font-size: 14px; line-height: 1.5; color: #6c7086; overflow: hidden; flex-shrink: 0; user-select: none; } .jxau-code-area { flex: 1; position: relative; overflow: auto; } .jxau-code-highlight, .jxau-code-editor { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0 !important; border: 0 !important; padding: 12px !important; font-family: 'Consolas', 'Monaco', 'Courier New', monospace !important; font-size: 14px !important; line-height: 1.5 !important; white-space: pre-wrap !important; word-wrap: break-word !important; tab-size: 4 !important; box-sizing: border-box !important; } .jxau-code-highlight { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0 !important; padding: 12px !important; background: transparent !important; pointer-events: none; z-index: 1; overflow: hidden; } .jxau-code-highlight code { display: block; margin: 0 !important; padding: 0 !important; background: transparent !important; } .jxau-code-editor { background: transparent !important; color: transparent !important; caret-color: #ffffff !important; resize: none; outline: none; z-index: 2; overflow-y: auto; overflow-x: auto; } .jxau-code-editor::selection { background: rgba(137, 180, 250, 0.3); } .jxau-editor-footer { display: flex; justify-content: space-between; align-items: center; } .jxau-editor-info { font-size: 12px; color: #a6adc8; } .jxau-btn { padding: 10px 20px; border-radius: 6px; font-size: 13px; cursor: pointer; border: none; background: #45475a; color: #cdd6f4; } .jxau-btn:hover { background: #89b4fa; color: #1e1e2e; } .jxau-open-btn { display: inline-flex; align-items: center; gap: 6px; padding: 10px 20px; border: 1px solid #a6e3a1; border-radius: 4px; background: transparent; color: #a6e3a1; font-size: 14px; cursor: pointer; margin-left: 10px; } .jxau-open-btn:hover { background: #a6e3a1; color: #1e1e2e; } .problem-markdown-content .title { color: #cba6f7 !important; font-weight: bold !important; font-size: 15px !important; margin-top: 20px !important; margin-bottom: 10px !important; padding-bottom: 5px; border-bottom: 1px solid #45475a; display: block; } .problem-markdown-content .content { color: #cdd6f4 !important; line-height: 1.8 !important; margin-bottom: 15px !important; display: block; } .jxau-problem-panel .sample .title { color: #f9e2af !important; border-bottom: none !important; margin-top: 5px !important; font-size: 13px !important; } .bracket-level-0 { color: #FFD700 !important; font-weight: bold; } .bracket-level-1 { color: #DA70D6 !important; font-weight: bold; } .bracket-level-2 { color: #87CEEB !important; font-weight: bold; } .bracket-level-3 { color: #98FB98 !important; font-weight: bold; } .bracket-level-4 { color: #FFA07A !important; font-weight: bold; } .bracket-level-5 { color: #F0E68C !important; font-weight: bold; } .hljs-variable-enhanced { color: #79c0ff !important; font-weight: 500 !important; } `); const codeTemplates = { c: ``, cpp: ``, go: ``, java: ``, javascript: ``, python: `` }; const langNames = { c: 'C', cpp: 'C++', go: 'Go', java: 'Java', javascript: 'JavaScript', python: 'Python 3' }; const langClasses = { c: 'language-c', cpp: 'language-cpp', go: 'language-go', java: 'language-java', javascript: 'language-javascript', python: 'language-python' }; let currentLang = 'cpp'; let problemId = null; // 创建编辑器 function createLightbox() { if (document.getElementById('jxauEditorOverlay')) return; const div = document.createElement('div'); div.className = 'jxau-editor-overlay'; div.id = 'jxauEditorOverlay'; div.innerHTML = `

✏️ 代码编辑器

📋 题目信息

1
`; document.body.appendChild(div); // 事件绑定 div.addEventListener('click', (e) => { if (e.target.dataset.action === 'close' || e.target === div) { closeEditor(); } }); document.getElementById('jxauLangSelect').addEventListener('change', changeLanguage); document.getElementById('jxauConfirmBtn').addEventListener('click', handleConfirm); const editor = document.getElementById('jxauCodeEditor'); editor.addEventListener('input', updateCode); editor.addEventListener('scroll', syncScroll); editor.addEventListener('keydown', handleKey); editor.addEventListener('click', updateCursorInfo); editor.addEventListener('keyup', updateCursorInfo); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeEditor(); }); console.log('[JXAU OJ] 编辑器创建成功'); } // 获取题目信息 function getProblemInfo() { try { let result = ''; const titleElement = document.querySelector('.ivu-card-head .panel-title div[data-v-6e5e6c6e]') || document.querySelector('.panel-title') || document.querySelector('.ivu-card-head span'); if (titleElement) { const title = titleElement.textContent.trim(); result += `

${escapeHtml(title)}

`; } const problemContent = document.querySelector('#problem-content.markdown-body') || document.querySelector('.markdown-body') || document.querySelector('.ivu-card-body .panel-body'); if (problemContent) { const clonedContent = problemContent.cloneNode(true); clonedContent.querySelectorAll('button, input, textarea, select, .submit-btn, .ivu-btn, .copy, .simditor-caret-start, .simditor-caret-end').forEach(el => el.remove()); clonedContent.querySelectorAll('p.title').forEach(el => { el.style.color = '#cba6f7'; el.style.fontWeight = 'bold'; el.style.fontSize = '14px'; el.style.marginTop = '15px'; el.style.marginBottom = '8px'; el.style.paddingBottom = '4px'; el.style.borderBottom = '1px solid #45475a'; }); clonedContent.querySelectorAll('p.content').forEach(el => { el.style.color = '#cdd6f4'; el.style.lineHeight = '1.8'; el.style.marginBottom = '10px'; const firstChild = el.querySelector('p:first-child'); if (firstChild) { let text = firstChild.textContent.trim(); text = text.replace(/^(输入|输出|Input|Output)\s*/i, ''); if (text) { firstChild.textContent = text; } } }); clonedContent.querySelectorAll('code').forEach(code => { if (!code.closest('pre')) { const originalText = code.textContent; code.style.background = '#11111b'; code.style.padding = '2px 6px'; code.style.borderRadius = '4px'; code.style.color = '#f38ba8'; code.style.fontSize = '12px'; code.style.fontFamily = "'Consolas', 'Monaco', monospace"; code.style.whiteSpace = 'pre-wrap'; code.style.wordBreak = 'keep-all'; code.textContent = originalText; } }); clonedContent.querySelectorAll('.flex-container.sample, .sample').forEach(container => { container.style.display = 'flex'; container.style.flexDirection = 'column'; container.style.gap = '12px'; container.style.marginTop = '15px'; }); clonedContent.querySelectorAll('.sample-input, .sample-output').forEach(el => { el.style.width = '100%'; el.style.marginBottom = '10px'; const titleEl = el.querySelector('p.title'); if (titleEl) { titleEl.style.color = '#f9e2af'; titleEl.style.fontWeight = 'bold'; titleEl.style.fontSize = '13px'; titleEl.style.marginBottom = '6px'; } const preEl = el.querySelector('pre'); if (preEl) { preEl.style.background = '#11111b'; preEl.style.padding = '10px'; preEl.style.borderRadius = '6px'; preEl.style.color = '#a6e3a1'; preEl.style.fontSize = '12px'; preEl.style.fontFamily = "'Consolas', 'Monaco', monospace"; preEl.style.overflowX = 'auto'; preEl.style.whiteSpace = 'pre'; } }); clonedContent.querySelectorAll('pre').forEach(pre => { if (!pre.closest('.sample-input') && !pre.closest('.sample-output')) { pre.style.background = '#11111b'; pre.style.padding = '12px'; pre.style.borderRadius = '6px'; pre.style.color = '#a6e3a1'; pre.style.fontSize = '12px'; pre.style.fontFamily = "'Consolas', 'Monaco', monospace"; pre.style.overflowX = 'auto'; pre.style.margin = '10px 0'; pre.style.whiteSpace = 'pre'; } }); clonedContent.querySelectorAll('span[style*="color"]').forEach(span => { const currentColor = span.style.color; if (currentColor === 'rgb(64, 64, 64)' || currentColor === '#404040') { span.style.color = '#cdd6f4'; } }); result += `
${clonedContent.innerHTML}
`; } else { result += `

无法获取题目内容

`; } return result || getProblemInfoFallback(); } catch (e) { console.error('[JXAU OJ] 获取题目信息失败:', e); return getProblemInfoFallback(); } } function syncToOriginalEditor() { const editor = document.getElementById('jxauCodeEditor'); const code = editor.value; const cmElement = document.querySelector('.CodeMirror'); if (cmElement && cmElement.CodeMirror) { cmElement.CodeMirror.setValue(code); console.log('[JXAU OJ] 已通过 CodeMirror API 同步代码'); } else { const textarea = document.querySelector('.CodeMirror textarea') || document.querySelector('textarea[name="code"]') || document.querySelector('#editor textarea'); if (textarea) { textarea.value = code; textarea.dispatchEvent(new Event('input', { bubbles: true })); textarea.dispatchEvent(new Event('change', { bubbles: true })); console.log('[JXAU OJ] 已通过 Textarea 同步代码'); } else { alert('未找到原网站编辑器,请手动复制粘贴。'); } } } function handleConfirm() { syncToOriginalEditor(); const btn = document.getElementById('jxauConfirmBtn'); const originalText = btn.innerHTML; btn.innerHTML = '✅ 已载入'; setTimeout(() => { btn.innerHTML = originalText; closeEditor(); }, 800); } function processSampleLayout(container) { const allElements = Array.from(container.querySelectorAll('*')); const sampleInputElements = allElements.filter(el => { const text = el.textContent.trim(); return (text.match(/^\s*(Sample Input|样例输入)\s*\d*\s*$/i)) && (el.children.length === 0 || el.querySelectorAll('*').length <= 2); }); const sampleOutputElements = allElements.filter(el => { const text = el.textContent.trim(); return (text.match(/^\s*(Sample Output|样例输出)\s*\d*\s*$/i)) && (el.children.length === 0 || el.querySelectorAll('*').length <= 2); }); sampleInputElements.forEach(el => { el.style.color = '#f9e2af'; el.style.fontWeight = 'bold'; el.style.marginTop = '15px'; el.style.display = 'block'; }); sampleOutputElements.forEach(el => { el.style.color = '#f9e2af'; el.style.fontWeight = 'bold'; el.style.marginTop = '15px'; el.style.display = 'block'; }); const possibleContainers = container.querySelectorAll( '.ivu-row, .ivu-col-span-12, .ivu-col-span-11, [class*="ivu-col"], ' + '.row, .col, .col-md-6, .col-sm-6, [class*="col-"], ' + '[class*="sample"], [class*="example"]' ); possibleContainers.forEach(el => { const html = el.innerHTML.toLowerCase(); const hasInput = html.includes('sample input') || html.includes('样例输入'); const hasOutput = html.includes('sample output') || html.includes('样例输出'); if (hasInput || hasOutput) { el.style.display = 'flex'; el.style.flexDirection = 'column'; el.style.gap = '10px'; el.style.width = '100%'; const children = Array.from(el.children); children.forEach(child => { child.style.width = '100%'; child.style.maxWidth = '100%'; child.style.flex = 'none'; }); } }); sampleInputElements.forEach(inputEl => { let parent = inputEl.parentElement; let depth = 0; const maxDepth = 5; while (parent && parent !== container && depth < maxDepth) { const siblings = Array.from(parent.children); const hasOutput = siblings.some(sib => { const sibText = sib.textContent.trim().toLowerCase(); return sibText.includes('sample output') || sibText.includes('样例输出'); }); if (hasOutput) { parent.style.display = 'flex'; parent.style.flexDirection = 'column'; parent.style.gap = '15px'; parent.style.width = '100%'; siblings.forEach(sib => { sib.style.width = '100%'; sib.style.maxWidth = '100%'; sib.style.flex = 'none'; }); break; } parent = parent.parentElement; depth++; } }); } function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function getProblemInfoFallback() { let result = ''; const titleEl = document.querySelector('h1, h2, .problem-title, [class*="problem"] h1, [class*="problem"] h2'); if (titleEl) { result += `
题目

${escapeHtml(titleEl.textContent.trim())}

`; } const descEl = document.querySelector('.markdown-body, .problem-description'); if (descEl) { const cloned = descEl.cloneNode(true); cloned.querySelectorAll('script, style').forEach(el => el.remove()); result += `
${cloned.innerHTML}
`; } if (!result) { return `
题目信息

无法自动获取题目信息,请返回页面查看题面。

`; } return result; } // 打开编辑器 function openEditor() { problemId = window.location.pathname.match(/\/problem\/(\d+)/)?.[1] || '1'; createLightbox(); const overlay = document.getElementById('jxauEditorOverlay'); overlay.classList.add('active'); document.body.style.overflow = 'hidden'; document.getElementById('jxauProblemContent').innerHTML = getProblemInfo(); const saved = localStorage.getItem(`jxau_code_${problemId}_${currentLang}`); const editor = document.getElementById('jxauCodeEditor'); editor.value = saved || codeTemplates[currentLang]; updateLineNumbers(); updateHighlight(); updateCursorInfo(); setTimeout(() => editor.focus(), 100); console.log('[JXAU OJ] 编辑器已打开'); } // 关闭编辑器 function closeEditor() { const overlay = document.getElementById('jxauEditorOverlay'); if (overlay) { overlay.classList.remove('active'); document.body.style.overflow = ''; } } // 切换语言 function changeLanguage() { const editor = document.getElementById('jxauCodeEditor'); const select = document.getElementById('jxauLangSelect'); localStorage.setItem(`jxau_code_${problemId}_${currentLang}`, editor.value); currentLang = select.value; const saved = localStorage.getItem(`jxau_code_${problemId}_${currentLang}`); editor.value = saved || codeTemplates[currentLang]; updateLineNumbers(); updateHighlight(); updateCursorInfo(); } // 更新代码 function updateCode() { updateLineNumbers(); updateHighlight(); updateCursorInfo(); processSampleLayout(); const editor = document.getElementById('jxauCodeEditor'); if (editor) { localStorage.setItem(`jxau_code_${problemId}_${currentLang}`, editor.value); } } // 更新高亮 function updateHighlight() { const editor = document.getElementById('jxauCodeEditor'); const highlightCode = document.getElementById('jxauHighlightCode'); if (!editor || !highlightCode) { console.error('[JXAU OJ] 元素未找到'); return; } if (typeof hljs === 'undefined') { console.error('[JXAU OJ] hljs 未定义'); return; } let content = editor.value; if (content.endsWith('\n')) content += ' '; highlightCode.textContent = content; highlightCode.removeAttribute('data-highlighted'); highlightCode.className = `${langClasses[currentLang]} hljs`; try { hljs.highlightElement(highlightCode); applyBracketColors(highlightCode); highlightVariables(highlightCode); } catch (e) { console.error('[JXAU OJ] 高亮失败:', e); } } // 彩色括号 function applyBracketColors(element) { const brackets = ['{', '}', '[', ']', '(', ')']; const colors = 6; let stack = []; function processNode(node) { if (node.nodeType === Node.TEXT_NODE) { const text = node.textContent; if (!/[(){}\[\]]/.test(text)) return; const fragment = document.createDocumentFragment(); let lastIdx = 0; for (let i = 0; i < text.length; i++) { const char = text[i]; if (brackets.includes(char)) { if (i > lastIdx) { fragment.appendChild(document.createTextNode(text.substring(lastIdx, i))); } const span = document.createElement('span'); let level = 0; if (['{', '[', '('].includes(char)) { level = stack.length % colors; stack.push({ char, level }); } else { const pair = { '}': '{', ']': '[', ')': '(' }; if (stack.length > 0 && stack[stack.length - 1].char === pair[char]) { level = stack.pop().level; } else { level = stack.length % colors; } } span.className = `bracket-level-${level}`; span.textContent = char; fragment.appendChild(span); lastIdx = i + 1; } } if (lastIdx < text.length) { fragment.appendChild(document.createTextNode(text.substring(lastIdx))); } node.parentNode.replaceChild(fragment, node); } else if (node.nodeType === Node.ELEMENT_NODE) { if (node.classList.contains('bracket-level-0')) return; const children = Array.from(node.childNodes); children.forEach(processNode); } } processNode(element); } // 变量高亮 function highlightVariables(element) { const variablePatterns = { cpp: /\b(?:int|long|float|double|char|bool|string|auto|void|short|unsigned)\s+(\w+)/g, c: /\b(?:int|long|float|double|char|void|short|unsigned)\s+(\w+)/g, python: /^(\w+)\s*=/gm, java: /\b(?:int|long|float|double|char|boolean|String|byte|short)\s+(\w+)/g, javascript: /\b(?:var|let|const)\s+(\w+)/g, go: /(\w+)\s*:=/g }; const pattern = variablePatterns[currentLang]; if (!pattern) return; function processTextNode(node) { if (node.nodeType !== Node.TEXT_NODE) { Array.from(node.childNodes).forEach(processTextNode); return; } const text = node.textContent; const matches = []; let match; pattern.lastIndex = 0; while ((match = pattern.exec(text)) !== null) { if (match[1]) { matches.push({ start: match.index + match[0].indexOf(match[1]), length: match[1].length, name: match[1] }); } } if (matches.length === 0) return; const fragment = document.createDocumentFragment(); let lastIndex = 0; matches.forEach(m => { if (m.start > lastIndex) { fragment.appendChild(document.createTextNode(text.substring(lastIndex, m.start))); } const span = document.createElement('span'); span.style.color = '#79c0ff'; span.style.fontWeight = '500'; span.textContent = m.name; fragment.appendChild(span); lastIndex = m.start + m.length; }); if (lastIndex < text.length) { fragment.appendChild(document.createTextNode(text.substring(lastIndex))); } node.parentNode.replaceChild(fragment, node); } const codeElements = element.querySelectorAll('.hljs-keyword, .hljs-type'); codeElements.forEach(el => { if (el.nextSibling && el.nextSibling.nodeType === Node.TEXT_NODE) { processTextNode(el.nextSibling); } }); } // 更新行号 function updateLineNumbers() { const editor = document.getElementById('jxauCodeEditor'); const lineNumbers = document.getElementById('jxauLineNumbers'); if (!editor || !lineNumbers) return; const lines = editor.value.split('\n').length; lineNumbers.innerHTML = Array(lines).fill(0).map((_, i) => `
${i + 1}
`).join(''); } // 同步滚动 function syncScroll() { const editor = document.getElementById('jxauCodeEditor'); const lineNumbers = document.getElementById('jxauLineNumbers'); const codeArea = document.querySelector('.jxau-code-area'); if (editor && lineNumbers && codeArea) { lineNumbers.scrollTop = editor.scrollTop; codeArea.scrollTop = editor.scrollTop; codeArea.scrollLeft = editor.scrollLeft; } } // 更新光标信息 function updateCursorInfo() { const editor = document.getElementById('jxauCodeEditor'); const info = document.getElementById('jxauEditorInfo'); if (!editor || !info) return; const value = editor.value; const pos = editor.selectionStart; const lines = value.substring(0, pos).split('\n'); const line = lines.length; const col = lines[lines.length - 1].length + 1; info.textContent = `行 ${line}, 列 ${col} | ${langNames[currentLang]}`; } // 键盘处理 function handleKey(e) { const editor = document.getElementById('jxauCodeEditor'); if (!editor) return; const start = editor.selectionStart; const end = editor.selectionEnd; const value = editor.value; const before = value.substring(0, start); const after = value.substring(end); const pairs = { '(': ')', '[': ']', '{': '}', '"': '"', "'": "'" }; const closingChars = [')', ']', '}', '"', "'"]; if (closingChars.includes(e.key)) { const nextChar = value.charAt(start); if (nextChar === e.key) { e.preventDefault(); editor.selectionStart = editor.selectionEnd = start + 1; return; } } if (pairs[e.key]) { e.preventDefault(); const pair = pairs[e.key]; if (start !== end) { const selectedText = value.substring(start, end); editor.value = before + e.key + selectedText + pair + after; editor.selectionStart = start + 1; editor.selectionEnd = start + 1 + selectedText.length; } else { editor.value = before + e.key + pair + after; editor.selectionStart = editor.selectionEnd = start + 1; } updateCode(); } else if (e.key === 'Enter') { e.preventDefault(); const lines = before.split('\n'); const currentLine = lines[lines.length - 1]; const indentMatch = currentLine.match(/^\s*/); const indent = indentMatch ? indentMatch[0] : ""; let extraIndent = ""; let closingBrace = ""; if (currentLine.trim().endsWith('{')) { extraIndent = " "; if (after.trim().startsWith('}')) { closingBrace = "\n" + indent; } } const insertText = "\n" + indent + extraIndent; editor.value = before + insertText + closingBrace + after; editor.selectionStart = editor.selectionEnd = start + insertText.length; updateCode(); } else if (e.key === 'Backspace' && start === end) { const charBefore = value.charAt(start - 1); const charAfter = value.charAt(start); if (pairs[charBefore] === charAfter) { e.preventDefault(); editor.value = value.substring(0, start - 1) + value.substring(start + 1); editor.selectionStart = editor.selectionEnd = start - 1; updateCode(); } } else if (e.key === 'Tab') { e.preventDefault(); editor.value = before + " " + after; editor.selectionStart = editor.selectionEnd = start + 4; updateCode(); } setTimeout(updateCursorInfo, 0); } // 添加打开按钮 function addOpenButton() { if (document.getElementById('jxauOpenBtn')) return true; const buttons = document.querySelectorAll('button'); let submitBtn = null; for (const btn of buttons) { const text = btn.textContent.toLowerCase(); if (text.includes('提交') || text.includes('submit')) { submitBtn = btn; break; } } if (!submitBtn) return false; const btn = document.createElement('button'); btn.id = 'jxauOpenBtn'; btn.className = 'jxau-open-btn'; btn.innerHTML = '✏️ 高级编辑器'; btn.addEventListener('click', openEditor); submitBtn.parentNode.insertBefore(btn, submitBtn.nextSibling); console.log('[JXAU OJ] 按钮添加成功'); return true; } // 初始化 function init() { console.log('[JXAU OJ] 初始化...'); setTimeout(() => { if (addOpenButton()) return; let attempts = 0; const interval = setInterval(() => { if (addOpenButton() || attempts >= 15) { clearInterval(interval); } attempts++; }, 1500); }, 2000); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();