paper-burner/views/mindmap/mindmap.html

784 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>思维导图 - Markmap</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="icon" type="image/svg+xml" href="../../public/pure.svg">
<!-- Markmap 依赖 -->
<script src="https://gcore.jsdelivr.net/npm/d3@7"></script>
<script src="https://gcore.jsdelivr.net/npm/markmap-lib"></script>
<script src="https://gcore.jsdelivr.net/npm/markmap-view"></script>
<!-- Monaco Editor CDN -->
<script src="https://gcore.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs/loader.js"></script>
<!-- 导出PNG需要的库 -->
<script src="https://registry.npmmirror.com/html2canvas/1.4.1/files/dist/html2canvas.min.js"></script>
<style>
/* 谷歌字体 Nunito */
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700&display=swap');
body {
margin:0;
background: linear-gradient(135deg, #f3f6fa 0%, #e4edf9 100%);
font-family: 'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
overflow: hidden;
user-select: none;
}
/* 顶部栏样式 */
.topbar {
position: fixed; top: 0; left: 0; width: 100vw; height: 54px;
background: #fff; border-bottom: 1px solid #e2e8f0; z-index: 100; /* Increased z-index */
display: flex; align-items: center; justify-content: space-between; padding: 0 20px;
box-sizing: border-box;
box-shadow: 0 4px 24px 0 rgba(59,130,246,0.08), 0 1.5px 6px 0 rgba(59,130,246,0.04);
border-radius: 0 0 18px 18px;
}
.topbar-title {
display: flex;
align-items: center;
font-weight: 600;
font-size: 1.1em;
color: #2563eb;
}
.topbar-title svg {
width: 24px;
height: 24px;
margin-right: 8px;
fill: #2563eb;
}
/* 主容器与面板样式 */
.container {
position: fixed;
top: 54px;
bottom: 0;
left: 0;
right: 0;
display: flex;
transition: all 0.3s ease;
border-radius: 18px;
box-shadow: 0 8px 32px 0 rgba(31, 41, 55, 0.10), 0 1.5px 6px 0 rgba(59,130,246,0.06);
background: #f8fafc;
}
.editor-container {
width: 25%; /* 默认左:右 = 1:3编辑器占 25% */
min-width: 200px; /* 最小宽度 */
height: 100%;
background: #fff;
display: flex;
flex-direction: column;
overflow: hidden;
border-right: 1px solid #e0e7ef;
box-shadow: 1px 0 5px rgba(0,0,0,0.03);
border-radius: 18px;
box-shadow: 0 2px 8px 0 rgba(59,130,246,0.06);
margin: 12px;
background: linear-gradient(135deg, #fff 60%, #f3f6fa 100%);
}
.editor-header {
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
background: #f9fafb; /* Slightly lighter header */
padding: 0 16px;
font-size: 0.9em;
font-weight: 600;
color: #334155;
border-bottom: 1px dashed #e2e8f0; /* 虚线边框 */
flex-shrink: 0;
border-radius: 18px 18px 0 0;
background: linear-gradient(90deg, #f9fafb 80%, #e0e7ef 100%);
box-shadow: 0 2px 8px 0 rgba(59,130,246,0.04);
}
#monaco-container {
flex: 1;
overflow: hidden;
}
.splitter {
width: 6px;
background: repeating-linear-gradient(90deg, #e0e7ef, #e0e7ef 4px, #f3f6fa 4px, #f3f6fa 8px);
cursor: ew-resize;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 50; /* Ensure splitter is above editor/map content during drag */
border-radius: 8px;
}
.splitter:hover {
background: #cbd5e1;
}
.splitter::before { /* 更改为竖向虚线 */
content: "";
height: 20px;
border-left: 2px dashed #b0bfd0;
}
.markmap-container {
flex: 1;
min-width: 200px; /* 最小宽度 */
position: relative;
overflow: hidden;
background: #fdfdff; /* Slightly different background for contrast */
border-radius: 18px;
box-shadow: 0 2px 8px 0 rgba(59,130,246,0.06);
margin: 12px;
background: linear-gradient(135deg, #fff 60%, #f3f6fa 100%);
border: 1.5px dashed #e0e7ef;
}
.markmap-header {
height: 40px;
display: flex;
align-items: center;
justify-content: flex-end; /* 将按钮放在右侧 */
background: #f9fafb;
padding: 0 16px;
border-bottom: 1px dashed #e2e8f0; /* 虚线边框 */
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 10;
border-radius: 18px 18px 0 0;
background: linear-gradient(90deg, #f9fafb 80%, #e0e7ef 100%);
box-shadow: 0 2px 8px 0 rgba(59,130,246,0.04);
}
#markmap {
width: 100%;
height: 100%;
}
/* 按钮通用样式 */
.button {
background: linear-gradient(90deg, #3b82f6 60%, #60a5fa 100%);
color: #fff;
border: none;
padding: 6px 12px;
border-radius: 8px; /* 更大的圆角 */
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 6px;
box-shadow: 0 2px 8px 0 rgba(59,130,246,0.08);
}
.button svg {
width: 14px; height: 14px;
}
.button:hover {
background: linear-gradient(90deg, #2563eb 60%, #3b82f6 100%);
color: #fff;
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(37,99,235,0.2);
}
.button.outline {
background: #f8fafc;
color: #2563eb;
border: 1.5px dashed #2563eb; /* 虚线边框 */
}
.button.outline:hover {
background: #e0e7ef;
color: #1d4ed8;
border-color: #1d4ed8;
}
.button.small {
padding: 4px 8px;
font-size: 12px;
}
.button.icon-only {
padding: 6px;
}
.action-buttons {
display: flex;
gap: 8px;
}
/* 面板控制按钮样式 */
.panel-control {
background: transparent;
color: #64748b;
border: 1.5px dashed #cbd5e1;
padding: 4px 8px;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
display: inline-flex;
align-items: center;
border-radius: 10px !important;
font-weight: 600;
letter-spacing: 0.02em;
box-shadow: 0 2px 8px 0 rgba(59,130,246,0.08);
border-width: 1.5px;
}
.panel-control:hover {
color: #3b82f6;
border-color: #3b82f6;
background: rgba(59, 130, 246, 0.05);
}
.panel-control svg {
width: 14px;
height: 14px;
margin-right: 4px;
}
/* 布局控制(隐藏面板) */
.hide-editor .editor-container, .hide-editor .splitter {
width: 0 !important; /* Important to override inline style from resize */
min-width: 0 !important;
overflow: hidden;
border-right: none;
}
.hide-editor .splitter { display: none; }
/* 隐藏思维导图时,彻底收起右侧区域并让编辑器占满 */
.hide-mindmap .markmap-container {
display: none !important;
width: 0 !important;
min-width: 0 !important;
flex: 0 0 0 !important; /* 彻底不占据弹性空间 */
overflow: hidden !important;
}
.hide-mindmap .splitter { display: none !important; }
.hide-mindmap .editor-container {
width: 100% !important;
flex: 1 1 auto !important;
}
/* 响应式布局 */
@media (max-width: 768px) {
.container {
flex-direction: column;
}
.editor-container, .markmap-container {
width: 100% !important; /* Override inline style from resize */
height: 50%;
}
.splitter {
width: 100%;
height: 6px;
cursor: ns-resize;
}
.splitter::before {
height: auto;
width: 20px;
border-left: none;
border-top: 2px dashed #b0bfd0;
}
.hide-editor .editor-container, .hide-mindmap .markmap-container {
height: 0 !important; /* Important to override inline style from resize */
}
.hide-editor .splitter, .hide-mindmap .splitter { display: none !important; }
.topbar { padding: 0 10px; }
.topbar-title span { display: none; } /* Hide text on small screens */
.container, .editor-container, .markmap-container {
border-radius: 0;
margin: 0;
box-shadow: none;
}
.topbar {
border-radius: 0;
}
}
/* 打印样式 */
@media print {
body { overflow: visible !important; }
.topbar, .editor-container, .splitter, .action-buttons, .editor-header, .markmap-header {
display: none !important;
}
.container {
position: static !important;
top: 0;
display: block !important;
}
.markmap-container {
width: 100% !important;
height: auto !important;
flex: none !important;
overflow: visible !important;
border: none !important;
padding-top: 0 !important; /* 防止顶部空白 */
}
#markmap {
width: 100% !important;
height: auto !important;
page-break-inside: avoid;
}
svg { /* Ensure SVG scales well for printing */
max-width: 100%;
height: auto;
}
}
</style>
</head>
<body>
<div class="topbar">
<div class="topbar-title" style="gap:12px;">
<!-- Font Awesome diagram-project 现成思维导图风格 logo -->
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="6" height="6" rx="1"/>
<rect x="15" y="3" width="6" height="6" rx="1"/>
<rect x="9" y="15" width="6" height="6" rx="1"/>
<path d="M6 9v2a2 2 0 0 0 2 2h2m4 0h2a2 2 0 0 0 2-2V9"/>
<path d="M12 15V13"/>
</svg>
<span style="font-size:1.2em;font-weight:700;letter-spacing:0.02em;">思维导图</span>
<button id="auto-save-btn" class="button small outline" style="margin-left:16px;min-width:90px;">自动保存: 开</button>
<button id="toggle-layout-btn" class="button small outline" style="margin-left:8px;" title="切换编辑器左右布局">⇄ 切换布局</button>
</div>
<div class="action-buttons">
<button id="export-svg-btn" class="button outline" title="导出为 SVG">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 8L21 12L17 16"/><line x1="3" y1="12" x2="21" y2="12"/><path d="M12 19v-4M12 9V5"/><path d="M5 19v-4M5 9V5"/></svg>
SVG
</button>
<button id="export-png-btn" class="button outline" title="导出为 PNG">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
PNG
</button>
<button id="save-btn" class="button" title="保存到本地存储">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
保存
</button>
</div>
</div>
<div class="container" id="main-container">
<div class="editor-container" id="editor-area">
<div class="editor-header">
<div class="flex items-center"><!-- 左侧保留占位,避免布局抖动 --></div>
<span>Markdown</span>
<div class="flex items-center">
<small id="save-status" style="color:#64748b; margin-right: 10px;">未修改</small>
<button id="toggle-editor-btn" class="panel-control" title="隐藏编辑器">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 6l-6 6 6 6"></path></svg>
隐藏
</button>
</div>
</div>
<div id="monaco-container"></div>
</div>
<div class="splitter" id="splitter"></div>
<div class="markmap-container" id="markmap-area">
<div class="markmap-header">
<button id="toggle-mindmap-btn" class="panel-control" title="隐藏思维导图">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"></path></svg>
隐藏
</button>
</div>
<svg id="markmap"></svg>
</div>
</div>
<script>
// 获取 Markdown 数据(兼容原有 docId 方案)
function getQueryParam(name) {
const url = window.location.search;
const params = new URLSearchParams(url);
return params.get(name);
}
const docId = getQueryParam('docId');
let md = '';
if (docId) {
md = window.localStorage.getItem('mindmapData_' + docId) || '';
}
if (!md) {
md = '# 思维导图示例\n\n## 分支 1\n- 条目 1.1\n- 条目 1.2\n\n## 分支 2\n- 条目 2.1\n - 子条目 2.1.1\n- 条目 2.2';
}
// 全局变量
let editor;
let markmapInstance;
let autoSaveEnabled = true; // 默认开启自动保存
let autoSaveInterval;
let isDirty = false;
let renderTimeout;
// 渲染 Markmap
function renderMarkmap(mdContent) {
if (markmapInstance) {
markmapInstance.setData(transformer.transform(mdContent).root);
markmapInstance.fit(); // Ensure it fits after update
} else {
document.getElementById('markmap').innerHTML = ''; // Clear previous before new instance
const { root, features } = transformer.transform(mdContent);
const assets = transformer.getUsedAssets(features);
if (assets.styles) loadCSS(assets.styles);
if (assets.scripts) loadJS(assets.scripts, { getMarkmap: () => window.markmap });
markmapInstance = Markmap.create('#markmap', { autoFit: true }, root);
}
}
// 保存数据到 localStorage
function saveToLocalStorage() {
if (!editor) return;
const content = editor.getValue();
if (docId) {
window.localStorage.setItem('mindmapData_' + docId, content);
showSaveStatus('已保存');
isDirty = false;
} else {
showSaveStatus('无法保存 (无 docId)', true);
}
}
// 显示保存状态
function showSaveStatus(message, isError = false) {
const saveStatus = document.getElementById('save-status');
if (saveStatus) {
saveStatus.textContent = message;
saveStatus.style.color = isError ? '#ef4444' : '#64748b';
setTimeout(() => {
saveStatus.textContent = isDirty ? '未保存' : '已保存';
saveStatus.style.color = isDirty ? '#ef4444' : '#64748b';
}, 2500);
}
}
// 设置自动保存
function toggleAutoSave() {
autoSaveEnabled = !autoSaveEnabled;
const autoSaveBtn = document.getElementById('auto-save-btn');
if (autoSaveEnabled) {
autoSaveBtn.textContent = '自动保存: 开';
autoSaveBtn.classList.remove('outline');
saveToLocalStorage(); // Save once when turning on
autoSaveInterval = setInterval(saveToLocalStorage, 10000); // 10秒自动保存
} else {
autoSaveBtn.textContent = '自动保存: 关';
autoSaveBtn.classList.add('outline');
if (autoSaveInterval) clearInterval(autoSaveInterval);
}
}
// 导出SVG
function exportSvg() {
let svgEl = document.querySelector('#markmap svg');
if (!svgEl) {
const markmapNode = document.getElementById('markmap');
if (markmapNode && markmapNode.tagName.toLowerCase() === 'svg') {
svgEl = markmapNode;
}
}
if (!svgEl) {
showSaveStatus('未找到SVG元素', true);
return;
}
// 确保有xmlns属性
if (!svgEl.getAttribute('xmlns')) {
svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
}
let serializer = new XMLSerializer();
let svgData = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(svgEl);
let blob = new Blob([svgData], {type: 'image/svg+xml;charset=utf-8'});
let url = URL.createObjectURL(blob);
let link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', (docId || 'markmap') + '.svg');
link.click();
URL.revokeObjectURL(url);
showSaveStatus('SVG已导出');
}
// 导出PNG
function exportPng() {
let svgEl = document.querySelector('#markmap svg');
if (!svgEl) {
const markmapNode = document.getElementById('markmap');
if (markmapNode && markmapNode.tagName.toLowerCase() === 'svg') {
svgEl = markmapNode;
}
}
if (!svgEl) {
showSaveStatus('未找到SVG元素', true);
return;
}
// 获取内容实际边界
let bbox = svgEl.getBBox();
let width = bbox.width;
let height = bbox.height;
let x = bbox.x;
let y = bbox.y;
// 4倍高清
let scale = 4;
let canvas = document.createElement('canvas');
canvas.width = width * scale;
canvas.height = height * scale;
let context = canvas.getContext('2d');
context.fillStyle = '#fff';
context.fillRect(0, 0, canvas.width, canvas.height);
// 复制SVG并设置viewBox和宽高
let serializer = new XMLSerializer();
let clonedSvg = svgEl.cloneNode(true);
clonedSvg.setAttribute('viewBox', `${x} ${y} ${width} ${height}`);
clonedSvg.setAttribute('width', width);
clonedSvg.setAttribute('height', height);
if (!clonedSvg.getAttribute('xmlns')) {
clonedSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
}
let source = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(clonedSvg);
let image = new Image();
image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source);
image.onload = function () {
// 居中绘制
context.setTransform(scale, 0, 0, scale, 0, 0);
context.drawImage(image, 0, 0, width, height, 0, 0, width, height);
let a = document.createElement('a');
a.download = (docId || 'markmap') + '.png';
a.href = canvas.toDataURL('image/png');
a.click();
showSaveStatus('PNG已导出');
};
image.onerror = function () {
showSaveStatus('导出PNG失败: 无法加载SVG图像', true);
};
}
// 初始化 Monaco Editor
let transformer, loadCSS, loadJS, Markmap;
function initMonacoEditor() {
({ loadCSS, loadJS, Markmap } = window.markmap);
transformer = new window.markmap.Transformer();
require.config({ paths: { 'vs': 'https://gcore.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs' } });
require(['vs/editor/editor.main'], function() {
editor = monaco.editor.create(document.getElementById('monaco-container'), {
value: md,
language: 'markdown',
theme: 'vs', // 'vs-dark' for dark theme
automaticLayout: true,
wordWrap: 'on',
lineNumbers: 'on',
minimap: { enabled: false }, // Minimalist: disable minimap
scrollBeyondLastLine: false,
renderWhitespace: 'none', // Minimalist: hide whitespace chars
fontFamily: 'Menlo, Monaco, Consolas, "Courier New", monospace', // Common monospaced fonts
fontSize: 14,
padding: { top: 10, bottom: 10 }
});
editor.onDidChangeModelContent(function() {
const content = editor.getValue();
isDirty = true;
showSaveStatus('未保存', true);
clearTimeout(renderTimeout);
renderTimeout = setTimeout(() => {
renderMarkmap(content);
}, 1000); // 1秒后刷新
});
// 初始渲染
renderMarkmap(md);
showSaveStatus(isDirty ? '未保存' : '已同步', isDirty); // Initial status based on localStorage
});
}
// 初始化界面交互和可调节面板
function initUI() {
document.getElementById('toggle-editor-btn').addEventListener('click', function() {
const container = document.getElementById('main-container');
const isHidden = container.classList.toggle('hide-editor');
this.innerHTML = isHidden ?
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"></path></svg> 显示' :
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 6l-6 6 6 6"></path></svg> 隐藏';
if(editor) editor.layout(); // Monaco needs relayout
if(markmapInstance) markmapInstance.fit();
});
// 修复思维导图隐藏逻辑
document.getElementById('toggle-mindmap-btn').addEventListener('click', function() {
const container = document.getElementById('main-container');
const isHidden = container.classList.toggle('hide-mindmap');
if (isHidden) {
this.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 6l-6 6 6 6"></path></svg> 显示';
} else {
this.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"></path></svg> 隐藏';
}
if(editor) editor.layout();
if(markmapInstance) markmapInstance.fit();
});
// 自动保存按钮逻辑放到logo区
const autoSaveBtn = document.getElementById('auto-save-btn');
autoSaveBtn.onclick = function() {
toggleAutoSave();
autoSaveBtn.textContent = autoSaveEnabled ? '自动保存: 开' : '自动保存: 关';
};
autoSaveBtn.textContent = autoSaveEnabled ? '自动保存: 开' : '自动保存: 关';
// 编辑器左右切换
const toggleLayoutBtn = document.getElementById('toggle-layout-btn');
toggleLayoutBtn.onclick = function() {
const container = document.getElementById('main-container');
if (container.style.flexDirection === 'row-reverse') {
container.style.flexDirection = '';
} else {
container.style.flexDirection = 'row-reverse';
}
if(editor) editor.layout();
if(markmapInstance) markmapInstance.fit();
};
document.getElementById('save-btn').addEventListener('click', saveToLocalStorage);
// 导出功能
document.getElementById('export-svg-btn').addEventListener('click', exportSvg);
document.getElementById('export-png-btn').addEventListener('click', exportPng);
// 面板拖拽调整逻辑
const splitter = document.getElementById('splitter');
const editorArea = document.getElementById('editor-area');
const markmapArea = document.getElementById('markmap-area');
const container = document.getElementById('main-container');
let isDragging = false;
let startX, startY, startEditorWidth, startMarkmapWidth, startEditorHeight, startMarkmapHeight;
splitter.addEventListener('mousedown', function(e) {
e.preventDefault(); // Prevent text selection
isDragging = true;
startX = e.clientX;
startY = e.clientY;
startEditorWidth = editorArea.offsetWidth;
startMarkmapWidth = markmapArea.offsetWidth;
startEditorHeight = editorArea.offsetHeight;
startMarkmapHeight = markmapArea.offsetHeight;
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
function onMouseMove(e) {
if (!isDragging) return;
// Horizontal drag for desktop
if (window.innerWidth > 768) { // Check if not in mobile layout
const dx = e.clientX - startX;
let newEditorWidth = startEditorWidth + dx;
let newMarkmapWidth = startMarkmapWidth - dx;
// Enforce min width
const minWidth = 100; // px
if (newEditorWidth < minWidth) {
newEditorWidth = minWidth;
newMarkmapWidth = container.offsetWidth - minWidth - splitter.offsetWidth;
}
if (newMarkmapWidth < minWidth) {
newMarkmapWidth = minWidth;
newEditorWidth = container.offsetWidth - minWidth - splitter.offsetWidth;
}
editorArea.style.width = newEditorWidth + 'px';
markmapArea.style.width = newMarkmapWidth + 'px';
} else { // Vertical drag for mobile
const dy = e.clientY - startY;
let newEditorHeight = startEditorHeight + dy;
let newMarkmapHeight = startMarkmapHeight - dy;
const minHeight = 100; // px
if (newEditorHeight < minHeight) {
newEditorHeight = minHeight;
newMarkmapHeight = container.offsetHeight - minHeight - splitter.offsetHeight;
}
if (newMarkmapHeight < minHeight) {
newMarkmapHeight = minHeight;
newEditorHeight = container.offsetHeight - minHeight - splitter.offsetHeight;
}
editorArea.style.height = newEditorHeight + 'px';
markmapArea.style.height = newMarkmapHeight + 'px';
}
if (editor) editor.layout(); // Trigger Monaco relayout
if (markmapInstance) markmapInstance.fit(); // Trigger Markmap refit
}
function onMouseUp() {
if (!isDragging) return;
isDragging = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
if (editor) editor.layout();
if (markmapInstance) markmapInstance.fit();
}
// 悬浮显示按钮逻辑
const showEditorBtn = document.getElementById('show-editor-btn');
const showMindmapBtn = document.getElementById('show-mindmap-btn');
function updateShowPanelButtons() {
const container = document.getElementById('main-container');
showEditorBtn.style.display = container.classList.contains('hide-editor') ? 'flex' : 'none';
showMindmapBtn.style.display = container.classList.contains('hide-mindmap') ? 'flex' : 'none';
}
showEditorBtn.onclick = function() {
document.getElementById('main-container').classList.remove('hide-editor');
updateShowPanelButtons();
if(editor) editor.layout();
if(markmapInstance) markmapInstance.fit();
};
showMindmapBtn.onclick = function() {
document.getElementById('main-container').classList.remove('hide-mindmap');
updateShowPanelButtons();
if(editor) editor.layout();
if(markmapInstance) markmapInstance.fit();
};
// 在切换隐藏时也更新
document.getElementById('toggle-editor-btn').addEventListener('click', updateShowPanelButtons);
document.getElementById('toggle-mindmap-btn').addEventListener('click', updateShowPanelButtons);
// 初始化时也调用
updateShowPanelButtons();
}
// 页面加载后初始化
window.addEventListener('DOMContentLoaded', function() {
initMonacoEditor();
initUI();
// 默认开启自动保存
if (autoSaveEnabled) {
toggleAutoSave();
}
});
</script>
<!-- 悬浮显示按钮 -->
<button id="show-editor-btn" style="display:none;position:fixed;left:12px;top:50%;transform:translateY(-50%);z-index:2000;box-shadow:0 2px 8px rgba(37,99,235,0.15);background:#fff;color:#2563eb;border:1.5px dashed #2563eb;border-radius:50%;width:48px;height:48px;align-items:center;justify-content:center;cursor:pointer;outline:none;transition:background 0.2s;" title="显示编辑器">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg>
</button>
<button id="show-mindmap-btn" style="display:none;position:fixed;right:12px;top:50%;transform:translateY(-50%);z-index:2000;box-shadow:0 2px 8px rgba(37,99,235,0.15);background:#fff;color:#2563eb;border:1.5px dashed #2563eb;border-radius:50%;width:48px;height:48px;align-items:center;justify-content:center;cursor:pointer;outline:none;transition:background 0.2s;" title="显示思维导图">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#2563eb" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 6l-6 6 6 6"/></svg>
</button>
</body>
</html>