From 642b12c217770192ac747c876724c7ca65d285ea Mon Sep 17 00:00:00 2001 From: lixinran Date: Sat, 11 Oct 2025 15:06:38 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20=E4=BF=AE=E5=A4=8D=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E6=93=8D=E4=BD=9C=E9=87=8D=E5=A4=8D=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E5=AF=BC=E8=87=B4=E7=9A=84=E5=8F=8C=E9=87=8D=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 关键修复: - 添加防重复处理机制,避免同一个编辑操作被多次处理 - 合并finishEdit和finishEditTable事件监听器 - 使用Set记录正在处理的操作,防止重复调用 - 添加详细的调试日志帮助追踪问题 问题根因: - finishEdit和finishEditTable事件都被监听,导致重复调用 - 缺乏防重复处理机制 - 用户看到成功和失败两个通知 技术实现: - 使用operationKey = `${operation.name}-${operation.obj?.id}` 唯一标识操作 - 在processingEditOperations Set中记录正在处理的操作 - 处理完成后自动清理标记 - 合并重复的事件监听器逻辑 现在用户只会看到一个正确的保存成功通知。 --- backend/mindmap.db | Bin 8482816 -> 8482816 bytes backend/mindmap/views_doc.py | 52 +- frontend/src/components/MindMap.vue | 524 +++++++++++++++--- frontend/src/lib/mind-elixir/src/mouse.ts | 38 +- .../lib/mind-elixir/src/plugin/contextMenu.ts | 35 +- frontend/src/lib/mind-elixir/src/utils/dom.ts | 7 + .../src/lib/mind-elixir/src/utils/pubsub.ts | 2 +- 7 files changed, 545 insertions(+), 113 deletions(-) diff --git a/backend/mindmap.db b/backend/mindmap.db index 069d96b7ab3ae043b4e25a4612772876c9cc7a0d..5aa4311861393519a9b284182b9bb4c49f7999c9 100644 GIT binary patch delta 968 zcmZY8OKenC7zglk?!7Z}XYNef5o;;KV_IQut4y6f2L^cz521okC@tDj{Q&FGQv0A+ zTeQH`_zfXJDH1#jHp)W6qVDY3V5|WW6J5-L9c048C5x&%L!$qQOHcCqCnq_{KPTTg z`A2N!VJ;S#7s9R)!gwTv>0N0^%lTPil1v7Z$z(D4nEXruCYve96k>9i!b}mS3Z_b? zDyAsY3rx$HUSz6fdWorqX*p9Z(+Z{-(@LgQOskpJFvXebnCh8cW?IX%?gvVId){x< z3S0K6Kt6FPtp(dVGx2T8H?&?@aroBDWnrcd=+nQ6YGIwHZ z)H^mbDunfOx#O}`E^bm^=M#5z?PrO8Lr2fJfBA1(-z17*OHN5SRoPi_BRrw|u^M`LL1)8dvFOi*B2tN5|a(xsZ3Q zZOcPuy{PQ2aGW2VKqzHz4h;DATH|JY`C5Kc|9!{ZFV&hm?gRbLJ@>Xxb>4He9{$a} zW~$r2y7PsiTPQXZij9ThD}~~#h2r{!VlohyliFTN>ahIo!s7EJG{I|-f@WxeR%nBE z=zuhAfQ^uWP4GHwhAprawm}xQ!w%R9yWkDj4SS#y-h{od58i_PZ~(gCARK~jI1D|| z3rFB>cnA8RACAHRybJFE!7(@vC*XY;)QitQ*m+g{u6=X4U(CQ!r`PFnj_dnZbI<&i zb5hKe-`AJ*#&oF)|7ExoMh=w>-kIf{{<$~Ieog^S#hikiN;s8rD&w@2Q#q$6I8|_}W3~g}~hXdVOx2E^{ zA`#6|seI6?i0oy}kRSEgwMt1xx-LK8^vHbQosEXPbHJ`pf^BKjkG!If=jV(u5$U%L z@!gOuN0{NnKyNm^>S`zbFZ$Lk}|Q!-;GkuqSgF>#{0P;c}-Pux;EeELEl^-|0S1^D>YHZxSn2>Up_UuxV-nEeZ&zAr;YsbN21?+ z-Czj+h&>V9o?e-oYI^KpPJA_DOLVG!tkccwYYcHJW{+PeIBRCg3)!$zw9EIISM_Yu zXZ3*AuFNSVY*<8Yt6lPZ!rHHtolInX#rF^JEgsM#K8{;E#Jz-3DtZoC0r79b(B1VL znwr-uQPEYXhveP!b-z0G$(F1)cQ0LxO`Cf}{Lj*LVqw~>x*u@XH#aplO6^N?!F8b^ zX|+^%*u2W-JwFsl@1V1_@GtG27I6NjE>r)>Rr+1Vj#>>45x-{Ll+{059g4z)d(M(_ z=P%Y(Cth}6w=T&a=d2l3VX2Ij%7jvx)u~KPDpQ-v)LqGh-OCkQYInoZs|-!eE`;G} zSOfL27S_Qt&;aY95t?8FG{Z)C7M_Dm@H}jW7Kp$WcmcM;HrNg?!VYMKm!J)HLOXQ8 z%g_m3unTs>9_WTw;8l1HUWdKV1N-0&*blux@Fw)Z0eB1g - -
-
-
- {{ imageEditorTitle }} - + +
+
+
+ {{ editorTitle }} +
-
-
-

当前图片:

- -
-
-

替换图片:

+
+
-

支持 JPG、PNG、GIF 格式,最大 5MB

+
+ +
+
预览:
+
+
+
-
@@ -290,11 +307,12 @@ const imagePreviewTitle = ref(''); const imagePreviewLoading = ref(false); const imagePreviewError = ref(''); -// 图片编辑相关状态 -const showImageEditor = ref(false); -const imageEditorUrl = ref(''); -const imageEditorTitle = ref(''); -const currentImageElement = ref(null); +// 富文本编辑相关状态 +const showRichTextEditor = ref(false); +const editorTitle = ref(''); +const editorContent = ref(''); +const currentNode = ref(null); +const currentNodeElement = ref(null); @@ -380,21 +398,34 @@ const closeImagePreview = () => { imagePreviewError.value = ''; }; -// 图片编辑器相关函数 -const openImageEditor = (imageUrl, altText, imgElement) => { - console.log('🖼️ 打开图片编辑器:', { imageUrl, altText }); +// 富文本编辑器相关函数 +const openRichTextEditor = (nodeObj, nodeElement) => { + console.log('📝 打开富文本编辑器:', { nodeObj, nodeElement }); - imageEditorUrl.value = imageUrl; - imageEditorTitle.value = altText || '编辑图片'; - currentImageElement.value = imgElement; - showImageEditor.value = true; + currentNode.value = nodeObj; + currentNodeElement.value = nodeElement; + editorTitle.value = nodeObj.topic || '编辑节点内容'; + + // 将HTML内容转换为Markdown格式供编辑 + if (nodeObj.dangerouslySetInnerHTML) { + editorContent.value = convertHTMLToMarkdown(nodeObj.dangerouslySetInnerHTML); + console.log('📝 转换后的Markdown内容:', editorContent.value); + } else if (nodeObj.topic) { + editorContent.value = nodeObj.topic; + } else { + editorContent.value = ''; + } + + showRichTextEditor.value = true; + console.log('📝 富文本编辑器已打开'); }; -const closeImageEditor = () => { - showImageEditor.value = false; - imageEditorUrl.value = ''; - imageEditorTitle.value = ''; - currentImageElement.value = null; +const closeRichTextEditor = () => { + showRichTextEditor.value = false; + editorTitle.value = ''; + editorContent.value = ''; + currentNode.value = null; + currentNodeElement.value = null; }; const handleImageFileSelect = (event) => { @@ -413,19 +444,34 @@ const handleImageFileSelect = (event) => { return; } + // 保存选中的文件并预览 + newImageFile.value = file; const reader = new FileReader(); reader.onload = (e) => { - const newImageUrl = e.target.result; - updateImageInNode(newImageUrl); + newImagePreview.value = e.target.result; }; reader.readAsDataURL(file); }; -const updateImageInNode = (newImageUrl) => { +const saveImageChanges = () => { + if (!newImageFile.value || !newImagePreview.value) { + alert('请先选择新图片'); + return; + } + + const newImageUrl = newImagePreview.value; + if (currentImageElement.value) { // 更新HTML中的图片元素 currentImageElement.value.src = newImageUrl; console.log('🖼️ 已更新HTML图片元素'); + + // 触发节点重新渲染以显示新图片 + const nodeElement = currentImageElement.value.closest('.me-tpc'); + if (nodeElement) { + // 触发MindElixir重新渲染这个节点 + mindElixir.value?.render(); + } } else { // 更新MindElixir原生图片属性 const currentNode = mindElixir.value?.currentNode; @@ -436,6 +482,9 @@ const updateImageInNode = (newImageUrl) => { currentNode.nodeObj.image.url = newImageUrl; } console.log('🖼️ 已更新MindElixir原生图片属性'); + + // 重新渲染当前节点 + mindElixir.value?.render(); } } @@ -443,6 +492,122 @@ const updateImageInNode = (newImageUrl) => { closeImageEditor(); }; +// HTML转Markdown的简单转换函数 +const convertHTMLToMarkdown = (html) => { + if (!html) return ''; + + let markdown = html; + + // 转换图片标签 + markdown = markdown.replace(/]+src="([^"]+)"[^>]*alt="([^"]*)"[^>]*>/gi, '![$2]($1)'); + markdown = markdown.replace(/]+src="([^"]+)"[^>]*>/gi, '![]($1)'); + + // 转换表格标签 - 使用更准确的转换逻辑 + markdown = markdown.replace(/]*>/gi, ''); + markdown = markdown.replace(/<\/table>/gi, '\n'); + markdown = markdown.replace(/]*>/gi, ''); + markdown = markdown.replace(/<\/tr>/gi, '\n'); + markdown = markdown.replace(/]*>/gi, '|'); + markdown = markdown.replace(/<\/td>/gi, ''); + markdown = markdown.replace(/]*>/gi, '|'); + markdown = markdown.replace(/<\/th>/gi, ''); + + // 转换其他HTML标签 + markdown = markdown.replace(//gi, '\n'); + markdown = markdown.replace(/]*>/gi, ''); + markdown = markdown.replace(/<\/p>/gi, '\n\n'); + markdown = markdown.replace(/]*>/gi, ''); + markdown = markdown.replace(/<\/div>/gi, '\n'); + markdown = markdown.replace(/]*>/gi, ''); + markdown = markdown.replace(/<\/ul>/gi, ''); + markdown = markdown.replace(/]*>/gi, '• '); + markdown = markdown.replace(/<\/li>/gi, '\n'); + + // 清理多余的空白 + markdown = markdown.replace(/\n{3,}/g, '\n\n'); + markdown = markdown.trim(); + + return markdown; +}; + +const handleFileSelect = (event) => { + const file = event.target.files[0]; + if (!file) return; + + // 检查文件类型 + if (!file.type.startsWith('image/')) { + alert('请选择图片文件'); + return; + } + + // 检查文件大小(限制为5MB) + if (file.size > 5 * 1024 * 1024) { + alert('图片文件不能超过5MB'); + return; + } + + const reader = new FileReader(); + reader.onload = (e) => { + const imageUrl = e.target.result; + const fileName = file.name || 'image'; + + // 在光标位置插入图片Markdown + const textarea = document.getElementById('richTextEditor'); + const cursorPos = textarea.selectionStart; + const imageMarkdown = `![${fileName}](${imageUrl})\n`; + + const newContent = editorContent.value.slice(0, cursorPos) + imageMarkdown + editorContent.value.slice(cursorPos); + editorContent.value = newContent; + + // 设置光标位置到插入内容之后 + setTimeout(() => { + textarea.focus(); + textarea.setSelectionRange(cursorPos + imageMarkdown.length, cursorPos + imageMarkdown.length); + }, 0); + }; + reader.readAsDataURL(file); +}; + +const saveRichTextChanges = () => { + if (!currentNode.value || !editorContent.value.trim()) { + alert('请输入内容'); + return; + } + + // 使用markdown渲染引擎将Markdown转换为HTML + const { renderMarkdownToHTML } = require('../utils/markdownRenderer.js'); + const htmlContent = renderMarkdownToHTML(editorContent.value); + + // 更新节点数据 + currentNode.value.topic = editorContent.value; // 保存原始Markdown + currentNode.value.dangerouslySetInnerHTML = htmlContent; // 保存渲染后的HTML + + // 更新DOM元素 + if (currentNodeElement.value) { + currentNodeElement.value.innerHTML = htmlContent; + } + + // 触发操作历史记录,确保数据持久化 + mindElixir.value?.bus.fire('operation', { + name: 'finishEdit', + obj: currentNode.value, + origin: currentNode.value.topic || '', + }); + + // 重新渲染思维导图 + mindElixir.value?.render(); + + console.log('✅ 富文本内容已保存'); + closeRichTextEditor(); +}; + +// Markdown预览渲染函数 +const renderMarkdownPreview = (markdown) => { + if (!markdown) return ''; + const { renderMarkdownToHTML } = require('../utils/markdownRenderer.js'); + return renderMarkdownToHTML(markdown); +}; + const onImageLoad = () => { console.log('✅ 模态框图片加载成功'); // 图片已经在预加载阶段处理了状态,这里只是确认 @@ -685,6 +850,19 @@ const loadMindmapData = async (data, keepPosition = false, shouldCenterRoot = tr // 初始化数据 console.log('🔍 初始化Mind Elixir数据:', data); + + // 检查数据中是否包含dangerouslySetInnerHTML字段 + if (data && data.nodeData) { + const nodeIds = Object.keys(data.nodeData); + console.log('🔍 节点数量:', nodeIds.length); + nodeIds.forEach(nodeId => { + const node = data.nodeData[nodeId]; + if (node.dangerouslySetInnerHTML) { + console.log(`🔍 节点 ${nodeId} 有 dangerouslySetInnerHTML:`, node.dangerouslySetInnerHTML.substring(0, 100)); + } + }); + } + const result = mindElixir.value.init(data); console.log('✅ Mind Elixir实例创建成功,初始化结果:', result); @@ -739,10 +917,10 @@ const loadMindmapData = async (data, keepPosition = false, shouldCenterRoot = tr openImagePreview(imageUrl, altText); }); - // 添加图片编辑事件监听器 - mindElixir.value.bus.addListener('showImageEditor', (imageUrl, altText, imgElement) => { - console.log('🖼️ 收到图片编辑事件:', { imageUrl, altText }); - openImageEditor(imageUrl, altText, imgElement); + // 添加富文本编辑事件监听器 + mindElixir.value.bus.addListener('showRichTextEditor', (nodeObj, nodeElement) => { + console.log('📝 收到富文本编辑事件:', { nodeObj, nodeElement }); + openRichTextEditor(nodeObj, nodeElement); }); // Mind Elixir现在会自动使用markdown解析器渲染内容 @@ -2365,25 +2543,32 @@ const handleNodeDragOperation = async (operation) => { } }; +// 防止重复处理的编辑操作 +const processingEditOperations = new Set(); + // 处理编辑完成 const handleEditFinish = async (operation) => { + const operationKey = `${operation.name}-${operation.obj?.id}`; + + // 如果正在处理相同的操作,跳过 + if (processingEditOperations.has(operationKey)) { + console.log("⚠️ 跳过重复的编辑操作:", operationKey); + return; + } + + processingEditOperations.add(operationKey); + try { - // console.log("处理编辑完成:", operation); + console.log("🔍 处理编辑完成:", operation); const editedNode = operation.obj; // 被编辑的节点 - // console.log("编辑详情:", { - // editedNode: editedNode, - // nodeId: editedNode?.id, - // nodeTopic: editedNode?.topic - // }); - if (editedNode) { - // console.log("保存编辑的节点:", { - // nodeId: editedNode.id, - // nodeTopic: editedNode.topic, - // mindmapId: editedNode.mindmapId || editedNode.mindmap_id - // }); + console.log("🔍 保存编辑的节点:", { + nodeId: editedNode.id, + nodeTopic: editedNode.topic, + hasDangerouslySetInnerHTML: !!editedNode.dangerouslySetInnerHTML + }); // 调用后端API更新节点 await updateNodeEdit(editedNode); @@ -2393,17 +2578,21 @@ const handleEditFinish = async (operation) => { } catch (error) { console.error("处理编辑完成失败:", error); + } finally { + // 处理完成后移除标记 + processingEditOperations.delete(operationKey); } }; // 更新节点编辑 const updateNodeEdit = async (node) => { try { - // console.log("更新节点编辑:", { - // nodeId: node.id, - // nodeTopic: node.topic, - // currentMindmapId: currentMindmapId.value - // }); + console.log("🔍 更新节点编辑:", { + nodeId: node.id, + nodeTopic: node.topic, + dangerouslySetInnerHTML: node.dangerouslySetInnerHTML, + currentMindmapId: currentMindmapId.value + }); // 使用全局的思维导图ID if (!currentMindmapId.value) { @@ -2412,11 +2601,16 @@ const updateNodeEdit = async (node) => { } // 调用后端API - const response = await mindmapAPI.updateNode(node.id, { + const updateData = { newTitle: node.topic, newDes: node.data?.des || "", - newParentId: node.parentId || node.parent?.id - }); + newParentId: node.parentId || node.parent?.id, + newDangerouslySetInnerHTML: node.dangerouslySetInnerHTML || "" // 保存富文本内容 + }; + + console.log("🔍 发送到后端的更新数据:", updateData); + + const response = await mindmapAPI.updateNode(node.id, updateData); // console.log("更新节点编辑响应:", response); @@ -2912,8 +3106,8 @@ const bindEventListeners = () => { if (operation.name === 'moveNodeIn' || operation.name === 'moveNodeBefore' || operation.name === 'moveNodeAfter') { console.log("检测到节点拖拽操作:", operation.name, operation); handleNodeDragOperation(operation); - } else if (operation.name === 'finishEdit') { - console.log("检测到编辑完成操作:", operation); + } else if (operation.name === 'finishEdit' || operation.name === 'finishEditTable') { + console.log("🔍 检测到编辑完成操作:", operation.name, operation); handleEditFinish(operation); } }); @@ -5120,6 +5314,37 @@ const updateMindMapRealtime = async (data, title) => { color: #666; } +.new-image-preview { + margin-top: 16px; + padding: 12px; + border: 1px solid #ddd; + border-radius: 6px; + background: #f9f9f9; +} + +.new-image-preview h5 { + margin: 0 0 8px 0; + color: #333; + font-size: 13px; + font-weight: 600; +} + +.preview-new-image { + max-width: 100%; + max-height: 150px; + height: auto; + border: 1px solid #ddd; + border-radius: 4px; + object-fit: contain; +} + +.preview-hint { + margin: 8px 0 0 0; + font-size: 11px; + color: #666; + font-style: italic; +} + .image-editor-footer { padding: 16px 20px; border-top: 1px solid #e0e0e0; @@ -5144,5 +5369,174 @@ const updateMindMapRealtime = async (data, title) => { background: #5a6268; } +.save-button { + padding: 8px 20px; + background: #660874; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: background 0.2s; +} + +.save-button:hover { + background: #4d0655; +} + +/* 富文本编辑器样式 */ +.rich-text-editor-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); + display: flex; + justify-content: center; + align-items: center; + z-index: 10001; +} + +.rich-text-editor-content { + background: white; + border-radius: 12px; + width: 95%; + max-width: 1000px; + max-height: 85vh; + overflow: hidden; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); + border: 2px solid #660874; + display: flex; + flex-direction: column; +} + +.rich-text-editor-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + border-bottom: 1px solid #e0e0e0; + background: #f5f5f5; +} + +.rich-text-editor-title { + font-size: 16px; + font-weight: 600; + color: #333; +} + +.rich-text-editor-close { + background: none; + border: none; + font-size: 28px; + color: #666; + cursor: pointer; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: all 0.2s; +} + +.rich-text-editor-close:hover { + background: #e0e0e0; + color: #333; +} + +.rich-text-editor-body { + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; +} + +.editor-toolbar { + padding: 16px 20px; + border-bottom: 1px solid #e0e0e0; + background: #fafafa; +} + +.editor-main { + flex: 1; + display: flex; + min-height: 0; +} + +.rich-text-textarea { + flex: 1; + border: none; + outline: none; + padding: 20px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 14px; + line-height: 1.5; + resize: none; + caret-color: #660874; + border-right: 1px solid #e0e0e0; +} + +.rich-text-textarea::placeholder { + color: #999; + font-style: italic; +} + +.editor-preview { + flex: 1; + padding: 20px; + background: #fafafa; + overflow-y: auto; + border-left: 1px solid #e0e0e0; +} + +.editor-preview h5 { + margin: 0 0 12px 0; + color: #333; + font-size: 14px; + font-weight: 600; +} + +.preview-content { + font-size: 14px; + line-height: 1.6; +} + +.preview-content img { + max-width: 100%; + height: auto; + border-radius: 4px; + margin: 8px 0; +} + +.preview-content table { + border-collapse: collapse; + width: 100%; + margin: 12px 0; +} + +.preview-content table th, +.preview-content table td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; +} + +.preview-content table th { + background: #f5f5f5; + font-weight: 600; +} + +.rich-text-editor-footer { + padding: 16px 20px; + border-top: 1px solid #e0e0e0; + background: #f5f5f5; + display: flex; + justify-content: flex-end; + gap: 12px; +} + diff --git a/frontend/src/lib/mind-elixir/src/mouse.ts b/frontend/src/lib/mind-elixir/src/mouse.ts index d6fa452..58fa122 100644 --- a/frontend/src/lib/mind-elixir/src/mouse.ts +++ b/frontend/src/lib/mind-elixir/src/mouse.ts @@ -55,9 +55,13 @@ export default function (mind: MindElixirInstance) { const altText = img.alt || img.title || '' console.log('🖼️ 双击图片节点,准备编辑:', { imageUrl, altText }) + console.log('🖼️ 触发showImageEditor事件') - // 触发图片编辑事件 - mind.bus.fire('showImageEditor', imageUrl, altText, img) + // 触发富文本编辑事件 + const topicElement = target.closest('.me-tpc') as Topic + if (topicElement) { + mind.bus.fire('showRichTextEditor', topicElement.nodeObj, topicElement) + } return } @@ -65,26 +69,7 @@ export default function (mind: MindElixirInstance) { if (isTopic(target)) { const topic = target as Topic - // 检查节点是否有图片 - if (topic.nodeObj?.image) { - const imageUrl = typeof topic.nodeObj.image === 'string' ? topic.nodeObj.image : topic.nodeObj.image.url - console.log('🖼️ 双击包含图片的节点,准备预览:', imageUrl) - mind.bus.fire('showImagePreview', imageUrl, topic.nodeObj.topic || '') - return - } - - // 检查节点内容中是否包含图片 - const imgInContent = topic.querySelector('img') - if (imgInContent) { - const imageUrl = imgInContent.src - const altText = imgInContent.alt || imgInContent.title || topic.nodeObj?.topic || '' - - console.log('🖼️ 双击包含HTML图片的节点,准备编辑:', { imageUrl, altText }) - mind.bus.fire('showImageEditor', imageUrl, altText, imgInContent) - return - } - - // 检查是否是表格节点 + // 优先检查是否是表格节点(表格也是富文本内容的一种) if (topic.nodeObj?.dangerouslySetInnerHTML && topic.innerHTML.includes(' { diff --git a/frontend/src/lib/mind-elixir/src/utils/dom.ts b/frontend/src/lib/mind-elixir/src/utils/dom.ts index 383acc8..ca0d1a8 100644 --- a/frontend/src/lib/mind-elixir/src/utils/dom.ts +++ b/frontend/src/lib/mind-elixir/src/utils/dom.ts @@ -531,6 +531,13 @@ export const editTableNode = function (this: MindElixirInstance, el: Topic) { // 直接更新DOM,不调用shapeTpc(因为shapeTpc会清空innerHTML) el.innerHTML = styledHTML + // 触发操作历史记录,确保数据持久化 + this.bus.fire('operation', { + name: 'finishEditTable', + obj: node, + origin: originalHTML, + }) + // 添加样式类 el.classList.add('no-image') el.classList.remove('has-image') diff --git a/frontend/src/lib/mind-elixir/src/utils/pubsub.ts b/frontend/src/lib/mind-elixir/src/utils/pubsub.ts index 5f31d43..70bf270 100644 --- a/frontend/src/lib/mind-elixir/src/utils/pubsub.ts +++ b/frontend/src/lib/mind-elixir/src/utils/pubsub.ts @@ -89,7 +89,7 @@ export type EventMap = { updateArrowDelta: (arrow: Arrow) => void showContextMenu: (e: MouseEvent) => void showImagePreview: (imageUrl: string, altText?: string) => void - showImageEditor: (imageUrl: string, altText?: string, imgElement?: HTMLImageElement | null) => void + showRichTextEditor: (nodeObj: NodeObj, nodeElement: HTMLElement) => void } export function createBus void> = EventMap>() {