// ==UserScript== // @name JXAU OJ - 代码编辑器 // @namespace http://tampermonkey.net/ // @version 1.5 // @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, .jxau-code-area::-webkit-scrollbar, .jxau-code-editor::-webkit-scrollbar { width: 8px; height: 8px; } .jxau-problem-panel::-webkit-scrollbar-track, .jxau-code-area::-webkit-scrollbar-track, .jxau-code-editor::-webkit-scrollbar-track { background: #1e1e2e; border-radius: 4px; } .jxau-problem-panel::-webkit-scrollbar-thumb, .jxau-code-area::-webkit-scrollbar-thumb, .jxau-code-editor::-webkit-scrollbar-thumb { background: #45475a; border-radius: 4px; border: 2px solid #1e1e2e; } .jxau-problem-panel::-webkit-scrollbar-thumb:hover, .jxau-code-area::-webkit-scrollbar-thumb:hover, .jxau-code-editor::-webkit-scrollbar-thumb:hover { background: #585b70; } .jxau-problem-panel::-webkit-scrollbar-corner, .jxau-code-area::-webkit-scrollbar-corner, .jxau-code-editor::-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; color: #6c7086; overflow: hidden !important; flex-shrink: 0; user-select: none; line-height: 1.5 !important; box-sizing: border-box !important; } .jxau-code-area { flex: 1; position: relative; overflow: hidden !important; background: #0d1117; } .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 !important; box-sizing: border-box !important; } .jxau-code-highlight code { display: block; margin: 0 !important; padding: 0 !important; background: transparent !important; } .jxau-code-editor { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0 !important; padding: 12px !important; background: transparent !important; color: transparent !important; caret-color: #ffffff !important; resize: none; outline: none; z-index: 2; overflow: auto !important; white-space: pre !important; word-wrap: normal !important; box-sizing: border-box !important; font-family: 'Consolas', 'Monaco', monospace !important; line-height: 1.5 !important; } .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 = `
无法获取题目内容
`; } 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 += `无法自动获取题目信息,请返回页面查看题面。
`; } 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) return; let content = editor.value; if (content.endsWith('\n')) { highlightCode.textContent = content + ' '; } else { highlightCode.textContent = content + '\n '; } highlightCode.removeAttribute('data-highlighted'); highlightCode.className = `${langClasses[currentLang]} hljs`; try { hljs.highlightElement(highlightCode); applyBracketColors(highlightCode); highlightVariables(highlightCode); } catch (e) { console.error('[JXAU OJ] 高亮失败:', e); } syncScroll(); } // 彩色括号 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) => `