UNPKG

oimp

Version:

A CLI tool for generating OI problem and packages

275 lines (253 loc) 12.1 kB
"use strict" // js tree 相关 import { showSaveMsg } from "./ide-message.js"; import { addBtnTooltip, showSaveConfirmModal, addCppToolbar, removeCppToolbar, updateCurrentFileDisplay,getCurrentProblemId,saveCurrentFile } from "./ide-utility.js"; export async function initFileTree() { // 文件树懒加载 $('#file-tree').jstree({ 'core': { 'themes': { 'name': 'default-dark', 'dots': true, 'icons': true }, 'data': function (obj, cb) { if (obj.id === '#') { fetch('/api/tree').then(r => r.json()).then(data => cb(data)); } else { // 用真实目录名(data.path)作为 path 参数 const path = obj.data && typeof obj.data.path === 'string' ? obj.data.path : obj.id; fetch('/api/tree?path=' + encodeURIComponent(path)).then(r => r.json()).then(data => cb(data)); } }, 'check_callback': true }, 'plugins': ['wholerow'] }); console.log('jstree init loaded'); // jstree节点渲染时,灰色不可编辑文件 $('#file-tree').on('after_open.jstree refresh.jstree', function (e, data) { const tree = $('#file-tree').jstree(true); tree.get_json(data.node || '#', { flat: true }).forEach(function (n) { if (n.data && n.data.type === 'file') { const ext = n.text.split('.').pop().toLowerCase(); // id 已为安全 id const anchorId = '#' + n.id + '_anchor'; if (['md', 'cpp', 'cc', 'in', 'out', 'ans', 'json', 'yaml','h'].indexOf(ext) === -1) { $(anchorId).css({ 'color': '#1f1f1f', 'pointer-events': 'none', 'cursor': 'not-allowed' }); } else { $(anchorId).css({ 'color': '', 'pointer-events': '', 'cursor': '' }); } } }); }); // 只在 editor 初始化后绑定 select_node 事件 $('#file-tree').off('select_node.jstree'); $('#file-tree').on('select_node.jstree', function (e, data) { if (isRestoringTreeSelection) { // console.log('跳过 select_node 事件(正在恢复选中)'); return; } if (data.node && data.node.data && data.node.data.type === 'file') { const path = data.node.data.path; const ext = path.split('.').pop().toLowerCase(); // 只允许特定后缀可编辑 if (["md", "in", "ans", "out", "txt", "cpp", "cc", "cxx", "json", "yaml", "yml", "js",'h'].indexOf(ext) !== -1) { loadFile(path); } else { // 取消选中 setTimeout(() => $('#file-tree').jstree('deselect_node', data.node), 0); } } else if (data.node && data.node.data && data.node.data.type === 'dir') { $('#file-tree').jstree('toggle_node', data.node); } }); // 自动展开题目ID目录并自动打开第一个md文件 $('#file-tree').on('ready.jstree', function (e, data) { const tree = $('#file-tree').jstree(true); const root = tree.get_node('#').children[0]; console.log('root', root); tree.open_node(root, function () { // 查找第一个md文件 const allNodes = tree.get_json(root, { flat: true }); const firstMd = allNodes.find(n => n.data && n.data.type === 'file' && n.text.toLowerCase().endsWith('.md')); if (firstMd) { setTimeout(function () { tree.deselect_all(); tree.select_node(firstMd.id); // 强制加载 loadFile(firstMd.data.path); }, 100); } }); }); fileTreeToggle(); // 顶部button tooltip addBtnTooltip(document.getElementById('btn-new-file'), '新建文件'); addBtnTooltip(document.getElementById('btn-new-folder'), '新建文件夹'); return $('#file-tree'); } // 合并唯一 loadFile 逻辑,增加调试日志和错误处理 export async function loadFile(path, skipTreeSelect) { // console.log('loadFile', path); if (window.isDirty || window.isMdDirty) { // 将原来的 confirm 替换为自定义模态对话框 const saveResult = await showSaveConfirmModal(); if (saveResult === 'save') { saveCurrentFile(); } else if (saveResult === 'cancel') { // 用户取消操作,保持当前文件不变 // 恢复文件树的选中状态为当前文件 if (currentFile && !skipTreeSelect) { // 使用 isRestoringTreeSelection 标志防止触发新的 loadFile 调用 window.isRestoringTreeSelection = true; const tree = $('#file-tree').jstree(true); tree.deselect_all(); // 查找当前文件对应的节点并选中 const nodes = tree.get_json('#', { flat: true }); const currentNode = nodes.find(node => node.data && node.data.path === currentFile); if (currentNode) { tree.select_node(currentNode.id, false, false); } // 延迟重置标志,确保操作完成 setTimeout(() => { isRestoringTreeSelection = false; }, 0); } return; } // 如果是 'nosave',则继续执行加载新文件 } try { const res = await fetch('/api/file?path=' + encodeURIComponent(path)); if (!res.ok) throw new Error('文件加载失败: ' + res.status); const text = await res.text(); // 先设置当前文件,这样addCppToolbar()就能获取到正确的文件名 window.currentFile = path; const ext = path.split('.').pop(); let lang = 'plaintext'; let mdContent = ''; if (ext === 'md') { switchToMarkdownEditor(text); removeCppToolbar(); // 移除C++工具栏 mdContent = window.mdTextarea.value; } else { const isCpp = ext === 'cpp' || ext === 'cc' || ext === 'cxx'; switchToMonacoEditor(text, isCpp ? 'cpp' : ext === 'js' ? 'javascript' : ext === 'json' ? 'json' : (ext === 'yaml' || ext === 'yml') ? 'yaml' : 'plaintext'); // 如果是C++文件,添加工具栏 if (isCpp) { addCppToolbar(); if (window.problemFile=== '' || window.problemFileContent === "") { const tree = $('#file-tree').jstree(true); tree.deselect_all(); // 查找problem文件 const nodes = tree.get_json('#', { flat: true }); //console.log('nodes', nodes); const node = nodes.find(n => { return n.data && n.data.type === 'file' && n.text.toLowerCase().startsWith('problem') && n.text.toLowerCase().endsWith('.md') }); window.problemFile = node.data.path; const res = await fetch('/api/file?path=' + encodeURIComponent(window.problemFile)); if (!res.ok) throw new Error('文件加载失败: ' + res.status); const text = await res.text(); //console.log('text', text); window.problemFileContent = text } mdContent = window.problemFileContent; } else { removeCppToolbar(); // 移除C++工具栏 } } window.updatePreviewFromMdTextarea(mdContent); window.lastSavedContent = text; window.isDirty = false; window.isMdDirty = false; updateCurrentFileDisplay(); // 预览 } catch (err) { showSaveMsg('加载文件失败: ' + err.message, true); console.error('加载文件失败', err); } } // 切换到markdown编辑器 function switchToMarkdownEditor(content) { // 隐藏 Monaco Editor domNode,仅显示 textarea if (window.codeEditor && window.codeEditor.getDomNode()) window.codeEditor.getDomNode().style.display = 'none'; window.mdTextarea.style.display = 'block'; window.mdTextarea.value = content; window.currentIsMarkdown = true; window.isMdDirty = false; updateCurrentFileDisplay(); // 显示预览区 document.getElementById('preview').style.display = ''; // 重新绑定光标和滚动同步 const mdTextarea = window.mdTextarea; mdTextarea.removeEventListener('keyup', syncPreviewToMdTextareaBlock); mdTextarea.removeEventListener('click', syncPreviewToMdTextareaBlock); mdTextarea.removeEventListener('scroll', window.syncPreviewToMdTextareaScroll); mdTextarea.addEventListener('keyup', syncPreviewToMdTextareaBlock); mdTextarea.addEventListener('click', syncPreviewToMdTextareaBlock); mdTextarea.addEventListener('scroll', window.syncPreviewToMdTextareaScroll); } // 切换到Monaco编辑器 async function switchToMonacoEditor(content, lang) { window.mdTextarea.style.display = 'none'; // 移除textarea的滚动同步 window.mdTextarea.removeEventListener('keyup', syncPreviewToMdTextareaBlock); window.mdTextarea.removeEventListener('click', syncPreviewToMdTextareaBlock); window.mdTextarea.removeEventListener('scroll', window.syncPreviewToMdTextareaScroll); if (window.codeEditor && window.codeEditor.getDomNode()) window.codeEditor.getDomNode().style.display = ''; window.codeEditor.setValue(content); if (lang) monaco.editor.setModelLanguage(window.codeEditor.getModel(), lang); window.currentIsMarkdown = false; window.isDirty = false; updateCurrentFileDisplay(); // 不隐藏预览区,让其显示题目内容 // document.getElementById('preview').style.display = 'none'; // 为C++文件添加右侧功能按钮 if (lang === 'cpp') { addCppToolbar(); } else { removeCppToolbar(); } } // 文件树显示/隐藏按钮逻辑 function fileTreeToggle() { const btn = document.getElementById('toggle-tree-btn'); const fileTreeWrap = document.getElementById('file-tree-wrap'); const ideMain = document.getElementById('ide-main'); let treeVisible = true; function updateBtn() { btn.title = treeVisible ? '隐藏文件树' : '显示文件树'; btn.innerHTML = treeVisible ? '<svg class="tree-arrow-svg" width="12" height="12" viewBox="0 0 18 18" style="display:block;transition:transform 0.08s;"><polyline points="12,4 6,9 12,14" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>' : '<svg class="tree-arrow-svg" width="12" height="12" viewBox="0 0 18 18" style="display:block;transition:transform 0.08s;"><polyline points="6,4 12,9 6,14" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>'; btn.style.background = treeVisible ? 'transparent' : '#e0e7ef'; } btn.onclick = function (e) { e.stopPropagation(); treeVisible = !treeVisible; if (treeVisible) { fileTreeWrap.classList.remove('hide-anim'); fileTreeWrap.classList.add('show-anim'); fileTreeWrap.style.display = ''; setTimeout(() => { fileTreeWrap.style.width = ''; ideMain.style.width = ''; }, 220); } else { fileTreeWrap.classList.remove('show-anim'); fileTreeWrap.classList.add('hide-anim'); setTimeout(() => { fileTreeWrap.style.display = 'none'; ideMain.style.width = '100%'; }, 220); } updateBtn(); if (window.editor && window.editor.layout) window.editor.layout(); }; updateBtn(); }