oimp
Version:
A CLI tool for generating OI problem and packages
275 lines (253 loc) • 12.1 kB
JavaScript
// 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();
}