Compare commits
No commits in common. "3693899a2b51baee940e6797ae4ee0da0d33847c" and "e3fedcbf0ff6f3a4a9392ebfd4fce1002bd9a282" have entirely different histories.
3693899a2b
...
e3fedcbf0f
Binary file not shown.
|
|
@ -54,8 +54,8 @@ def convert_to_mindelixir_format(mindmap, nodes):
|
|||
# 添加HTML内容(如果存在)
|
||||
if node.html_content:
|
||||
node_data["dangerouslySetInnerHTML"] = node.html_content
|
||||
# 保留topic内容,因为可能需要markdown原始内容
|
||||
# node_data["topic"] = ""
|
||||
# 如果有HTML内容,清空topic以避免冲突
|
||||
node_data["topic"] = ""
|
||||
|
||||
# 添加图片信息
|
||||
if node.image_url:
|
||||
|
|
@ -359,18 +359,6 @@ def update_node(request):
|
|||
node.desc = body.get('newDes') or ''
|
||||
updated_fields.append('des')
|
||||
|
||||
if 'newDangerouslySetInnerHTML' in body:
|
||||
try:
|
||||
new_html_content = body.get('newDangerouslySetInnerHTML') or ''
|
||||
print(f"🔍 更新节点 {node.id} 的 html_content: {new_html_content[:100]}...")
|
||||
print(f"🔍 html_content 字段类型: {type(node.html_content)}")
|
||||
node.html_content = new_html_content
|
||||
updated_fields.append('html_content')
|
||||
print(f"🔍 html_content 更新成功,长度: {len(new_html_content)}")
|
||||
except Exception as e:
|
||||
print(f"❌ 更新 html_content 时出错: {e}")
|
||||
return Response({'detail': f'Failed to update html_content: {str(e)}'}, status=500)
|
||||
|
||||
if 'newParentId' in body:
|
||||
new_parent_id = body.get('newParentId')
|
||||
old_parent_id = node.parent_id
|
||||
|
|
@ -386,12 +374,7 @@ def update_node(request):
|
|||
|
||||
# 更新新父节点的子节点计数
|
||||
parent_node.children_count += 1
|
||||
try:
|
||||
with transaction.atomic():
|
||||
parent_node.save()
|
||||
except Exception as e:
|
||||
print(f"⚠️ 更新父节点计数时出错: {e}")
|
||||
# 继续执行,不中断主流程
|
||||
parent_node.save()
|
||||
|
||||
except Node.DoesNotExist:
|
||||
return Response({'detail': 'parent node not found'}, status=404)
|
||||
|
|
@ -406,41 +389,14 @@ def update_node(request):
|
|||
try:
|
||||
old_parent = Node.objects.get(id=old_parent_id, deleted=False, mindmap=node.mindmap)
|
||||
old_parent.children_count = max(0, old_parent.children_count - 1)
|
||||
try:
|
||||
with transaction.atomic():
|
||||
old_parent.save()
|
||||
except Exception as e:
|
||||
print(f"⚠️ 更新原父节点计数时出错: {e}")
|
||||
# 继续执行,不中断主流程
|
||||
old_parent.save()
|
||||
except Node.DoesNotExist:
|
||||
pass # 原父节点可能已被删除
|
||||
|
||||
# 只有在有字段更新时才更新时间戳
|
||||
if updated_fields:
|
||||
try:
|
||||
from django.db import transaction
|
||||
import time
|
||||
|
||||
# 使用事务和重试机制处理数据库锁定
|
||||
max_retries = 3
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
node.updated_at = timezone.now()
|
||||
print(f"🔍 准备保存节点 {node.id},更新字段: {updated_fields} (尝试 {attempt + 1}/{max_retries})")
|
||||
node.save()
|
||||
print(f"✅ 节点 {node.id} 保存成功")
|
||||
break # 成功则跳出重试循环
|
||||
except Exception as e:
|
||||
if "database is locked" in str(e) and attempt < max_retries - 1:
|
||||
print(f"⚠️ 数据库被锁定,等待重试... (尝试 {attempt + 1}/{max_retries})")
|
||||
time.sleep(0.1 * (attempt + 1)) # 递增等待时间
|
||||
continue
|
||||
else:
|
||||
raise e
|
||||
except Exception as e:
|
||||
print(f"❌ 保存节点 {node.id} 时出错: {e}")
|
||||
return Response({'detail': f'Failed to save node: {str(e)}'}, status=500)
|
||||
node.updated_at = timezone.now()
|
||||
node.save()
|
||||
|
||||
return Response({
|
||||
'success': True,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -56,14 +56,8 @@ export default function (mind: MindElixirInstance) {
|
|||
|
||||
console.log('🖼️ 双击图片节点,准备编辑:', { imageUrl, altText })
|
||||
|
||||
// 双击图片触发富文本编辑事件(可以编辑图片)
|
||||
const topicElement = target.closest('.me-tpc') as Topic
|
||||
if (topicElement && topicElement.nodeObj) {
|
||||
console.log('🖼️ 找到图片所在的节点:', topicElement.nodeObj)
|
||||
mind.bus.fire('showRichTextEditor', topicElement.nodeObj, topicElement)
|
||||
} else {
|
||||
console.warn('🖼️ 未找到图片所在的节点')
|
||||
}
|
||||
// 触发图片编辑事件
|
||||
mind.bus.fire('showImageEditor', imageUrl, altText, img)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +65,26 @@ 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('<table')) {
|
||||
console.log('📊 双击表格节点,准备编辑:', topic.nodeObj.topic)
|
||||
if (mind.editable) {
|
||||
|
|
@ -80,23 +93,7 @@ export default function (mind: MindElixirInstance) {
|
|||
return
|
||||
}
|
||||
|
||||
// 检查节点是否有MindElixir原生图片
|
||||
if (topic.nodeObj?.image) {
|
||||
const imageUrl = typeof topic.nodeObj.image === 'string' ? topic.nodeObj.image : topic.nodeObj.image.url
|
||||
console.log('🖼️ 双击包含原生图片的节点,准备编辑:', imageUrl)
|
||||
// 双击包含原生图片的节点触发富文本编辑事件
|
||||
mind.bus.fire('showRichTextEditor', topic.nodeObj, topic)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查节点是否有富文本内容(但不包括表格)
|
||||
if (topic.nodeObj?.dangerouslySetInnerHTML && !topic.innerHTML.includes('<table')) {
|
||||
console.log('📝 双击富文本节点,准备编辑:', topic.nodeObj?.topic || '')
|
||||
mind.bus.fire('showRichTextEditor', topic.nodeObj, topic)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果没有图片或富文本内容,则进入普通编辑模式
|
||||
// 如果没有图片或表格,则进入编辑模式
|
||||
if (mind.editable) {
|
||||
mind.beginEdit(target)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +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-rich-text-edit', '编辑内容', '')
|
||||
const imageEdit = createLi('cm-image-edit', '编辑图片', '')
|
||||
|
||||
const menuUl = document.createElement('ul')
|
||||
menuUl.className = 'menu-list'
|
||||
|
|
@ -98,27 +98,18 @@ export default function (mind: MindElixirInstance, option: true | ContextMenuOpt
|
|||
isRoot = false
|
||||
}
|
||||
|
||||
// 检查节点是否有富文本内容,决定是否显示编辑选项
|
||||
// 检查节点是否有图片,决定是否显示预览图片选项
|
||||
const topic = target as Topic
|
||||
const hasRichContent = topic.nodeObj?.image || topic.nodeObj?.dangerouslySetInnerHTML || topic.querySelector('img')
|
||||
const hasImage = topic.nodeObj?.image || topic.querySelector('img')
|
||||
|
||||
console.log('🔍 右键菜单检查富文本内容:', {
|
||||
hasNodeImage: !!topic.nodeObj?.image,
|
||||
hasHTMLContent: !!topic.nodeObj?.dangerouslySetInnerHTML,
|
||||
hasHTMLImage: !!topic.querySelector('img'),
|
||||
hasRichContent: !!hasRichContent
|
||||
})
|
||||
|
||||
if (hasRichContent) {
|
||||
if (hasImage) {
|
||||
imagePreview.style.display = 'block'
|
||||
imagePreview.className = ''
|
||||
imageEdit.style.display = 'block'
|
||||
imageEdit.className = ''
|
||||
console.log('✅ 显示富文本编辑菜单项')
|
||||
} else {
|
||||
imagePreview.style.display = 'none'
|
||||
imageEdit.style.display = 'none'
|
||||
console.log('❌ 隐藏富文本编辑菜单项')
|
||||
}
|
||||
|
||||
if (isRoot) {
|
||||
|
|
@ -266,8 +257,22 @@ export default function (mind: MindElixirInstance, option: true | ContextMenuOpt
|
|||
menuContainer.hidden = true
|
||||
const target = mind.currentNode as Topic
|
||||
if (target) {
|
||||
console.log('📝 右键菜单编辑富文本内容:', target.nodeObj?.topic || '')
|
||||
mind.bus.fire('showRichTextEditor', target.nodeObj, 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 () => {
|
||||
|
|
|
|||
|
|
@ -531,13 +531,6 @@ 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')
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ export type EventMap = {
|
|||
updateArrowDelta: (arrow: Arrow) => void
|
||||
showContextMenu: (e: MouseEvent) => void
|
||||
showImagePreview: (imageUrl: string, altText?: string) => void
|
||||
showRichTextEditor: (nodeObj: NodeObj, nodeElement: HTMLElement) => void
|
||||
showImageEditor: (imageUrl: string, altText?: string, imgElement?: HTMLImageElement | null) => void
|
||||
}
|
||||
|
||||
export function createBus<T extends Record<string, (...args: any[]) => void> = EventMap>() {
|
||||
|
|
|
|||
|
|
@ -19,42 +19,11 @@ const renderer = new marked.Renderer();
|
|||
|
||||
// 自定义图片渲染器
|
||||
renderer.image = function(href, title, text) {
|
||||
console.log('🖼️ 图片渲染器被调用:', { href, title, text, hrefType: typeof href });
|
||||
|
||||
// 确保href是字符串类型,处理各种异常情况
|
||||
let hrefStr = '';
|
||||
|
||||
if (typeof href === 'string') {
|
||||
hrefStr = href;
|
||||
} else if (href === null || href === undefined) {
|
||||
console.warn('图片href为null或undefined');
|
||||
return `<div class="markdown-error">图片链接为空</div>`;
|
||||
} else if (typeof href === 'object') {
|
||||
// 如果是对象,尝试提取URL
|
||||
if (href.href) {
|
||||
hrefStr = String(href.href);
|
||||
} else if (href.url) {
|
||||
hrefStr = String(href.url);
|
||||
} else if (href.src) {
|
||||
hrefStr = String(href.src);
|
||||
} else {
|
||||
console.warn('图片href是对象但没有href、url或src属性:', href);
|
||||
return `<div class="markdown-error">图片链接格式错误</div>`;
|
||||
}
|
||||
} else if (typeof href.toString === 'function') {
|
||||
hrefStr = href.toString();
|
||||
} else {
|
||||
console.warn('图片href无法转换为字符串:', typeof href, href);
|
||||
return `<div class="markdown-error">图片链接格式错误</div>`;
|
||||
}
|
||||
|
||||
console.log('🖼️ 处理后的hrefStr:', hrefStr);
|
||||
|
||||
// 处理图片URL,确保能正确显示
|
||||
let processedUrl = hrefStr;
|
||||
if (hrefStr.includes('cdn-mineru.openxlab.org.cn')) {
|
||||
let processedUrl = href;
|
||||
if (href.includes('cdn-mineru.openxlab.org.cn')) {
|
||||
// 将外部CDN URL转换为代理URL
|
||||
const urlPath = hrefStr.replace('https://cdn-mineru.openxlab.org.cn', '');
|
||||
const urlPath = href.replace('https://cdn-mineru.openxlab.org.cn', '');
|
||||
processedUrl = `/proxy-image${urlPath}`;
|
||||
}
|
||||
|
||||
|
|
@ -62,8 +31,6 @@ renderer.image = function(href, title, text) {
|
|||
const altText = text || '图片';
|
||||
const titleAttr = title ? ` title="${title}"` : '';
|
||||
|
||||
console.log('🖼️ 最终生成的图片HTML:', `<img src="${processedUrl}" alt="${altText}"${titleAttr} class="markdown-image" />`);
|
||||
|
||||
return `<img src="${processedUrl}" alt="${altText}"${titleAttr} class="markdown-image" style="max-width: 200px; max-height: 150px; object-fit: contain; display: block; margin: 4px auto; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);" />`;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue