diff --git a/frontend/src/components/MindMap.vue b/frontend/src/components/MindMap.vue
index 1aad384..615c4e6 100644
--- a/frontend/src/components/MindMap.vue
+++ b/frontend/src/components/MindMap.vue
@@ -164,6 +164,40 @@
+
+
+
+
+
+
+
当前图片:
+
![]()
+
+
+
替换图片:
+
+
+
+
+
支持 JPG、PNG、GIF 格式,最大 5MB
+
+
+
+
+
@@ -256,6 +290,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);
+
@@ -340,6 +380,69 @@ const closeImagePreview = () => {
imagePreviewError.value = '';
};
+// 图片编辑器相关函数
+const openImageEditor = (imageUrl, altText, imgElement) => {
+ console.log('🖼️ 打开图片编辑器:', { imageUrl, altText });
+
+ imageEditorUrl.value = imageUrl;
+ imageEditorTitle.value = altText || '编辑图片';
+ currentImageElement.value = imgElement;
+ showImageEditor.value = true;
+};
+
+const closeImageEditor = () => {
+ showImageEditor.value = false;
+ imageEditorUrl.value = '';
+ imageEditorTitle.value = '';
+ currentImageElement.value = null;
+};
+
+const handleImageFileSelect = (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 newImageUrl = e.target.result;
+ updateImageInNode(newImageUrl);
+ };
+ reader.readAsDataURL(file);
+};
+
+const updateImageInNode = (newImageUrl) => {
+ if (currentImageElement.value) {
+ // 更新HTML中的图片元素
+ currentImageElement.value.src = newImageUrl;
+ console.log('🖼️ 已更新HTML图片元素');
+ } else {
+ // 更新MindElixir原生图片属性
+ const currentNode = mindElixir.value?.currentNode;
+ if (currentNode && currentNode.nodeObj) {
+ if (typeof currentNode.nodeObj.image === 'string') {
+ currentNode.nodeObj.image = newImageUrl;
+ } else if (currentNode.nodeObj.image && typeof currentNode.nodeObj.image === 'object') {
+ currentNode.nodeObj.image.url = newImageUrl;
+ }
+ console.log('🖼️ 已更新MindElixir原生图片属性');
+ }
+ }
+
+ // 关闭编辑器
+ closeImageEditor();
+};
+
const onImageLoad = () => {
console.log('✅ 模态框图片加载成功');
// 图片已经在预加载阶段处理了状态,这里只是确认
@@ -636,6 +739,12 @@ 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);
+ });
+
// Mind Elixir现在会自动使用markdown解析器渲染内容
} else {
@@ -724,6 +833,12 @@ 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);
+ });
+
// 延迟执行后续操作
setTimeout(() => {
// Mind Elixir现在会自动使用markdown解析器渲染内容
@@ -4881,5 +4996,153 @@ const updateMindMapRealtime = async (data, title) => {
background: #7d0a8e;
}
+/* 图片编辑器模态框样式 */
+.image-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;
+}
+
+.image-editor-content {
+ background: white;
+ border-radius: 12px;
+ width: 600px;
+ max-width: 90vw;
+ max-height: 80vh;
+ overflow: hidden;
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
+ border: 2px solid #660874;
+}
+
+.image-editor-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 16px 20px;
+ border-bottom: 1px solid #e0e0e0;
+ background: #f5f5f5;
+}
+
+.image-editor-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #333;
+}
+
+.image-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;
+}
+
+.image-editor-close:hover {
+ background: #e0e0e0;
+ color: #333;
+}
+
+.image-editor-body {
+ padding: 20px;
+ max-height: 60vh;
+ overflow-y: auto;
+}
+
+.current-image-section {
+ margin-bottom: 24px;
+}
+
+.current-image-section h4 {
+ margin: 0 0 12px 0;
+ color: #333;
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.current-image {
+ max-width: 100%;
+ max-height: 200px;
+ height: auto;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ object-fit: contain;
+}
+
+.replace-image-section h4 {
+ margin: 0 0 12px 0;
+ color: #333;
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.file-input-wrapper {
+ margin-bottom: 8px;
+}
+
+.file-input {
+ display: none;
+}
+
+.file-input-label {
+ display: inline-block;
+ padding: 10px 20px;
+ background: #660874;
+ color: white;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+ font-weight: 500;
+ transition: background 0.2s;
+ border: none;
+}
+
+.file-input-label:hover {
+ background: #4d0655;
+}
+
+.file-hint {
+ margin: 0;
+ font-size: 12px;
+ color: #666;
+}
+
+.image-editor-footer {
+ padding: 16px 20px;
+ border-top: 1px solid #e0e0e0;
+ background: #f9f9f9;
+ display: flex;
+ justify-content: flex-end;
+ gap: 12px;
+}
+
+.cancel-button {
+ padding: 8px 20px;
+ background: #6c757d;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 14px;
+ transition: background 0.2s;
+}
+
+.cancel-button:hover {
+ background: #5a6268;
+}
+
diff --git a/frontend/src/lib/mind-elixir/src/mouse.ts b/frontend/src/lib/mind-elixir/src/mouse.ts
index 52662a0..d6fa452 100644
--- a/frontend/src/lib/mind-elixir/src/mouse.ts
+++ b/frontend/src/lib/mind-elixir/src/mouse.ts
@@ -1,212 +1,212 @@
-import { handleZoom } from './plugin/keypress'
-import type { SummarySvgGroup } from './summary'
-import type { Expander, CustomSvg, Topic } from './types/dom'
-import type { MindElixirInstance } from './types/index'
-import { isTopic, on } from './utils'
-
-export default function (mind: MindElixirInstance) {
- const { dragMoveHelper } = mind
-
- const handleClick = (e: MouseEvent) => {
- console.log('handleClick', e)
- // Only handle primary button clicks
- if (e.button !== 0) return
- if (mind.helper1?.moved) {
- mind.helper1.clear()
- return
- }
- if (mind.helper2?.moved) {
- mind.helper2.clear()
- return
- }
- if (dragMoveHelper.moved) {
- dragMoveHelper.clear()
- return
- }
- const target = e.target as HTMLElement
- if (target.tagName === 'ME-EPD') {
- if (e.ctrlKey || e.metaKey) {
- mind.expandNodeAll((target as Expander).previousSibling)
- } else {
- mind.expandNode((target as Expander).previousSibling)
- }
- } else if (target.tagName === 'ME-TPC' && mind.currentNodes.length > 1) {
- // This is a bit complex, intertwined with selection and nodeDraggable
- // The main conflict is between multi-node dragging and selecting a single node when multiple nodes are already selected
- mind.selectNode(target as Topic)
- } else if (!mind.editable) {
- return
- }
- const trySvg = target.parentElement?.parentElement as unknown as SVGElement
- if (trySvg.getAttribute('class') === 'topiclinks') {
- mind.selectArrow(target.parentElement as unknown as CustomSvg)
- } else if (trySvg.getAttribute('class') === 'summary') {
- mind.selectSummary(target.parentElement as unknown as SummarySvgGroup)
- }
- }
-
- const handleDblClick = (e: MouseEvent) => {
- const target = e.target as HTMLElement
-
- // 检查是否双击了图片
- if (target.tagName === 'IMG') {
- const img = target as HTMLImageElement
- const imageUrl = img.src
- const altText = img.alt || img.title || ''
-
- console.log('🖼️ 双击图片节点,准备预览:', { imageUrl, altText })
-
- // 触发图片预览事件
- mind.bus.fire('showImagePreview', imageUrl, altText)
- return
- }
-
- // 检查是否双击了包含图片的节点
- 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('showImagePreview', imageUrl, altText)
- return
- }
-
- // 检查是否是表格节点
- if (topic.nodeObj?.dangerouslySetInnerHTML && topic.innerHTML.includes(' {
- if (e.pointerType === 'mouse') return
- const currentTime = new Date().getTime()
- const tapLength = currentTime - lastTap
- console.log('tapLength', tapLength)
- if (tapLength < 300 && tapLength > 0) {
- handleDblClick(e)
- }
-
- lastTap = currentTime
- }
-
- const handlePointerDown = (e: PointerEvent) => {
- dragMoveHelper.moved = false
- const mouseMoveButton = mind.mouseSelectionButton === 0 ? 2 : 0
- if (e.button !== mouseMoveButton && e.pointerType === 'mouse') return
-
- // Store initial position for movement calculation
- dragMoveHelper.x = e.clientX
- dragMoveHelper.y = e.clientY
-
- const target = e.target as HTMLElement
- if (target.className === 'circle') return
- if (target.contentEditable !== 'plaintext-only') {
- dragMoveHelper.mousedown = true
- // Capture pointer to ensure we receive all pointer events even if pointer moves outside the element
- target.setPointerCapture(e.pointerId)
- }
- }
-
- const handlePointerMove = (e: PointerEvent) => {
- // click trigger pointermove in windows chrome
- if ((e.target as HTMLElement).contentEditable !== 'plaintext-only') {
- // drag and move the map
- // Calculate movement delta manually since pointer events don't have movementX/Y
- const movementX = e.clientX - dragMoveHelper.x
- const movementY = e.clientY - dragMoveHelper.y
-
- dragMoveHelper.onMove(movementX, movementY)
- }
-
- dragMoveHelper.x = e.clientX
- dragMoveHelper.y = e.clientY
- }
-
- const handlePointerUp = (e: PointerEvent) => {
- const mouseMoveButton = mind.mouseSelectionButton === 0 ? 2 : 0
- if (e.button !== mouseMoveButton && e.pointerType === 'mouse') return
- const target = e.target as HTMLElement
- // Release pointer capture
- if (target.hasPointerCapture && target.hasPointerCapture(e.pointerId)) {
- target.releasePointerCapture(e.pointerId)
- }
- dragMoveHelper.clear()
- }
-
- const handleContextMenu = (e: MouseEvent) => {
- console.log('handleContextMenu', e)
- e.preventDefault()
- // Only handle right-click for context menu
- if (e.button !== 2) return
- if (!mind.editable) return
- const target = e.target as HTMLElement
- if (isTopic(target) && !target.classList.contains('selected')) {
- mind.selectNode(target)
- }
- setTimeout(() => {
- // delay to avoid conflict with click event on Mac
- if (mind.dragMoveHelper.moved) return
- mind.bus.fire('showContextMenu', e)
- }, 200)
- }
-
- const handleWheel = (e: WheelEvent) => {
- e.stopPropagation()
- e.preventDefault()
- if (e.ctrlKey || e.metaKey) {
- if (e.deltaY < 0) handleZoom(mind, 'in', mind.dragMoveHelper)
- else if (mind.scaleVal - mind.scaleSensitivity > 0) handleZoom(mind, 'out', mind.dragMoveHelper)
- } else if (e.shiftKey) {
- mind.move(-e.deltaY, 0)
- } else {
- mind.move(-e.deltaX, -e.deltaY)
- }
- }
-
- const { container } = mind
- const off = on([
- { dom: container, evt: 'pointerdown', func: handlePointerDown },
- { dom: container, evt: 'pointermove', func: handlePointerMove },
- { dom: container, evt: 'pointerup', func: handlePointerUp },
- { dom: container, evt: 'pointerup', func: handleTouchDblClick },
- { dom: container, evt: 'click', func: handleClick },
- { dom: container, evt: 'dblclick', func: handleDblClick },
- { dom: container, evt: 'contextmenu', func: handleContextMenu },
- { dom: container, evt: 'wheel', func: typeof mind.handleWheel === 'function' ? mind.handleWheel : handleWheel },
- ])
- return off
-}
+import { handleZoom } from './plugin/keypress'
+import type { SummarySvgGroup } from './summary'
+import type { Expander, CustomSvg, Topic } from './types/dom'
+import type { MindElixirInstance } from './types/index'
+import { isTopic, on } from './utils'
+
+export default function (mind: MindElixirInstance) {
+ const { dragMoveHelper } = mind
+
+ const handleClick = (e: MouseEvent) => {
+ console.log('handleClick', e)
+ // Only handle primary button clicks
+ if (e.button !== 0) return
+ if (mind.helper1?.moved) {
+ mind.helper1.clear()
+ return
+ }
+ if (mind.helper2?.moved) {
+ mind.helper2.clear()
+ return
+ }
+ if (dragMoveHelper.moved) {
+ dragMoveHelper.clear()
+ return
+ }
+ const target = e.target as HTMLElement
+ if (target.tagName === 'ME-EPD') {
+ if (e.ctrlKey || e.metaKey) {
+ mind.expandNodeAll((target as Expander).previousSibling)
+ } else {
+ mind.expandNode((target as Expander).previousSibling)
+ }
+ } else if (target.tagName === 'ME-TPC' && mind.currentNodes.length > 1) {
+ // This is a bit complex, intertwined with selection and nodeDraggable
+ // The main conflict is between multi-node dragging and selecting a single node when multiple nodes are already selected
+ mind.selectNode(target as Topic)
+ } else if (!mind.editable) {
+ return
+ }
+ const trySvg = target.parentElement?.parentElement as unknown as SVGElement
+ if (trySvg.getAttribute('class') === 'topiclinks') {
+ mind.selectArrow(target.parentElement as unknown as CustomSvg)
+ } else if (trySvg.getAttribute('class') === 'summary') {
+ mind.selectSummary(target.parentElement as unknown as SummarySvgGroup)
+ }
+ }
+
+ const handleDblClick = (e: MouseEvent) => {
+ const target = e.target as HTMLElement
+
+ // 检查是否双击了图片
+ if (target.tagName === 'IMG') {
+ const img = target as HTMLImageElement
+ const imageUrl = img.src
+ const altText = img.alt || img.title || ''
+
+ console.log('🖼️ 双击图片节点,准备编辑:', { imageUrl, altText })
+
+ // 触发图片编辑事件
+ mind.bus.fire('showImageEditor', imageUrl, altText, img)
+ return
+ }
+
+ // 检查是否双击了包含图片的节点
+ 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(' {
+ if (e.pointerType === 'mouse') return
+ const currentTime = new Date().getTime()
+ const tapLength = currentTime - lastTap
+ console.log('tapLength', tapLength)
+ if (tapLength < 300 && tapLength > 0) {
+ handleDblClick(e)
+ }
+
+ lastTap = currentTime
+ }
+
+ const handlePointerDown = (e: PointerEvent) => {
+ dragMoveHelper.moved = false
+ const mouseMoveButton = mind.mouseSelectionButton === 0 ? 2 : 0
+ if (e.button !== mouseMoveButton && e.pointerType === 'mouse') return
+
+ // Store initial position for movement calculation
+ dragMoveHelper.x = e.clientX
+ dragMoveHelper.y = e.clientY
+
+ const target = e.target as HTMLElement
+ if (target.className === 'circle') return
+ if (target.contentEditable !== 'plaintext-only') {
+ dragMoveHelper.mousedown = true
+ // Capture pointer to ensure we receive all pointer events even if pointer moves outside the element
+ target.setPointerCapture(e.pointerId)
+ }
+ }
+
+ const handlePointerMove = (e: PointerEvent) => {
+ // click trigger pointermove in windows chrome
+ if ((e.target as HTMLElement).contentEditable !== 'plaintext-only') {
+ // drag and move the map
+ // Calculate movement delta manually since pointer events don't have movementX/Y
+ const movementX = e.clientX - dragMoveHelper.x
+ const movementY = e.clientY - dragMoveHelper.y
+
+ dragMoveHelper.onMove(movementX, movementY)
+ }
+
+ dragMoveHelper.x = e.clientX
+ dragMoveHelper.y = e.clientY
+ }
+
+ const handlePointerUp = (e: PointerEvent) => {
+ const mouseMoveButton = mind.mouseSelectionButton === 0 ? 2 : 0
+ if (e.button !== mouseMoveButton && e.pointerType === 'mouse') return
+ const target = e.target as HTMLElement
+ // Release pointer capture
+ if (target.hasPointerCapture && target.hasPointerCapture(e.pointerId)) {
+ target.releasePointerCapture(e.pointerId)
+ }
+ dragMoveHelper.clear()
+ }
+
+ const handleContextMenu = (e: MouseEvent) => {
+ console.log('handleContextMenu', e)
+ e.preventDefault()
+ // Only handle right-click for context menu
+ if (e.button !== 2) return
+ if (!mind.editable) return
+ const target = e.target as HTMLElement
+ if (isTopic(target) && !target.classList.contains('selected')) {
+ mind.selectNode(target)
+ }
+ setTimeout(() => {
+ // delay to avoid conflict with click event on Mac
+ if (mind.dragMoveHelper.moved) return
+ mind.bus.fire('showContextMenu', e)
+ }, 200)
+ }
+
+ const handleWheel = (e: WheelEvent) => {
+ e.stopPropagation()
+ e.preventDefault()
+ if (e.ctrlKey || e.metaKey) {
+ if (e.deltaY < 0) handleZoom(mind, 'in', mind.dragMoveHelper)
+ else if (mind.scaleVal - mind.scaleSensitivity > 0) handleZoom(mind, 'out', mind.dragMoveHelper)
+ } else if (e.shiftKey) {
+ mind.move(-e.deltaY, 0)
+ } else {
+ mind.move(-e.deltaX, -e.deltaY)
+ }
+ }
+
+ const { container } = mind
+ const off = on([
+ { dom: container, evt: 'pointerdown', func: handlePointerDown },
+ { dom: container, evt: 'pointermove', func: handlePointerMove },
+ { dom: container, evt: 'pointerup', func: handlePointerUp },
+ { dom: container, evt: 'pointerup', func: handleTouchDblClick },
+ { dom: container, evt: 'click', func: handleClick },
+ { dom: container, evt: 'dblclick', func: handleDblClick },
+ { dom: container, evt: 'contextmenu', func: handleContextMenu },
+ { dom: container, evt: 'wheel', func: typeof mind.handleWheel === 'function' ? mind.handleWheel : handleWheel },
+ ])
+ return off
+}
diff --git a/frontend/src/lib/mind-elixir/src/plugin/contextMenu.ts b/frontend/src/lib/mind-elixir/src/plugin/contextMenu.ts
index e9f0779..efcfb61 100644
--- a/frontend/src/lib/mind-elixir/src/plugin/contextMenu.ts
+++ b/frontend/src/lib/mind-elixir/src/plugin/contextMenu.ts
@@ -49,6 +49,7 @@ export default function (mind: MindElixirInstance, option: true | ContextMenuOpt
const linkBidirectional = createLi('cm-link-bidirectional', lang.linkBidirectional, '')
const summary = createLi('cm-summary', lang.summary, '')
const imagePreview = createLi('cm-image-preview', '预览图片', '')
+ const imageEdit = createLi('cm-image-edit', '编辑图片', '')
const menuUl = document.createElement('ul')
menuUl.className = 'menu-list'
@@ -63,6 +64,8 @@ export default function (mind: MindElixirInstance, option: true | ContextMenuOpt
menuUl.appendChild(up)
menuUl.appendChild(down)
menuUl.appendChild(summary)
+ menuUl.appendChild(imagePreview)
+ menuUl.appendChild(imageEdit)
if (option.link) {
menuUl.appendChild(link)
menuUl.appendChild(linkBidirectional)
@@ -102,8 +105,11 @@ export default function (mind: MindElixirInstance, option: true | ContextMenuOpt
if (hasImage) {
imagePreview.style.display = 'block'
imagePreview.className = ''
+ imageEdit.style.display = 'block'
+ imageEdit.className = ''
} else {
imagePreview.style.display = 'none'
+ imageEdit.style.display = 'none'
}
if (isRoot) {
@@ -246,6 +252,29 @@ export default function (mind: MindElixirInstance, option: true | ContextMenuOpt
}
}
}
+
+ imageEdit.onclick = () => {
+ menuContainer.hidden = true
+ const target = mind.currentNode as Topic
+ if (target) {
+ // 检查节点是否有图片
+ if (target.nodeObj?.image) {
+ const imageUrl = typeof target.nodeObj.image === 'string' ? target.nodeObj.image : target.nodeObj.image.url
+ console.log('🖼️ 右键菜单编辑图片:', imageUrl)
+ mind.bus.fire('showImageEditor', imageUrl, target.nodeObj.topic || '', null)
+ } else {
+ // 检查节点内容中是否包含图片
+ const imgInContent = target.querySelector('img')
+ if (imgInContent) {
+ const imageUrl = imgInContent.src
+ const altText = imgInContent.alt || imgInContent.title || target.nodeObj?.topic || ''
+
+ console.log('🖼️ 右键菜单编辑HTML图片:', { imageUrl, altText })
+ mind.bus.fire('showImageEditor', imageUrl, altText, imgInContent)
+ }
+ }
+ }
+ }
return () => {
// maybe useful?
add_child.onclick = null
diff --git a/frontend/src/lib/mind-elixir/src/utils/pubsub.ts b/frontend/src/lib/mind-elixir/src/utils/pubsub.ts
index 7652a22..5f31d43 100644
--- a/frontend/src/lib/mind-elixir/src/utils/pubsub.ts
+++ b/frontend/src/lib/mind-elixir/src/utils/pubsub.ts
@@ -89,6 +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
}
export function createBus void> = EventMap>() {