-
Notifications
You must be signed in to change notification settings - Fork 63
feat: add flowchart and mindmap #360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
* feat: add insert flowChart and mindChart * fix: update mindmap style * fix: update mindmap ability * fix: update mindmap node error * fix:flow content delete and edit * fix: add mindmap adjust size * fix:node content delete * fix(mind): 调整思维导图文件结构 * fix:删除防抖函数 * fix:更新mind * fix:添加右键删除节点内容 * fix:快捷键添加思维导图 * fix:删除工具栏 * fix(mind):更新快捷键插入思维导图图标 * fix(mind):取消硬编码 * fix:更新依赖包 * fix(mind):完善国际化 * fix(mind-map):更新模块名称与将国际化和样式放入mind-map文件夹中 * fix(mind-map):规范思维导图相关变量名称 * fix(mind-map):删除思维导图多余样式文件 * fix(mind-map):规范思维导图相关变量名称 * fix(mind-map):规范思维导图相关类名称 * fix(mind-map):删除打印 * fix(mind-map):规范思维导图样式文件名称 * fix(mind-map):调整思维导图控制面板图标与样式
* feat(flow-chart):为编辑器添加插入流程图与调整大小布局功能 * fix(flow-chart):修正中文的国际化 * fix(flow-chart):规范样式的类名 * fix(flow-chart):修改图标格式 * fix(flow-chart):规范元素的类名 * fix(flow-chart):修复国际化问题
* feat(mind-map):为思维导图添加调整大小与布局功能 * fix(mind-map):改变思维导图右键菜单dom节点位置 * fix(mind-map):优化思维导图编辑节点内容方式 * fix(mind-map):设置输入框样式 * fix(mind-map):优化输入框 * fix(mind-map):删除多余导入的模块文件 * fix(mind-map):规范元素样式的类名
* feat(mind-map):新增插入图表改变结构控制面板显隐功能 * fix(mind-map):更新样式 * fix(mind-map):优化全屏状态 * fix(mind-mapt):优化样式代码减少重复样式 * fix(mind-map):规范类名 * fix(mind-map):规范元素样式 * fix(mind-map):减少硬编码 * fix(mind-map):封装相同代码 * fix(mind-map):优化对齐方式 * fix(mind-map):修复对齐方式 * fix(mind-map):修复国际化
* fix(flow-chart):优化样式 * fix(flow-chart):删除多余代码 * feat(flow-chart):为流程图添加设置边类型功能 * fix(flow-chart):更换图标 * fix(flow-chart):更改导出类型 * fix(flow-chart):删除左下控制面板 * fix(flow-chart):优化控制面板出现方式 * fix(flow-chart):增加右上控制面板 * feat(flow-chart):增加全面显示功能 * fix(flow-chart):规范变量名 * fix(flow-chart):解决光标问题 * fix(flow-chart):优化控制面板的显隐功能 * fix(flow-chart):优化全屏功能 * fix(flow-chart):改变最小高度 * fix(flow-chart):优化控制面板显隐功能 * fix(flow-chart):优化全屏功能 * fix(flow-chart):修复撤回功能 * fix(flow-chart):封装重复代码 * fix(flow-chart):简化样式代码 * fix(flow-chart):规范类名 * fix(flow-chart):优化样式代码减少重复样式 * fix(flow-chart):规范类名 * fix(flow-chart):优化对齐方式 * fix(flow-chart):优化国际化
* docs(flow-chart):更新文档 * docs(flow-chart):更新文档 * docs(flow-chart):更新文档 * docs(flow-chart):更新文档 * docs(flow-chart):更新文档 * fix(flow-chart):更改初始化流程图调用位置 * docs(flow-chart):更新文档
* docs(mind-map):更新文档 * fix(mind-map):修复警告 * docs(mind-map):更新文档 * fix(mind-map):解决菜单问题 * fix(mind-map):修复全屏功能 * docs(mind-map):更新文档 * fix(mind-map):更改初始化思维导图 * fix(mind-map):优化初始思维导图 * docs(flow-chart):更新文档
* test(mind-map):为思维导图添加自动化测试用例 * fix(mind-map):优化思维导图以及样例工具栏 * fix(mind-map):删除多余注释
* test(flow-chart):为流程图添加自动化测试用例 * fix(flow-chart):更新编辑器工具栏并更新流程图容器类名
* feat(mind-map):删除多余代码 * 为projects扩展功能
WalkthroughThis PR introduces two major new rich-editing features to FluentEditor: Mind Map and Flow Chart modules. It adds complete implementations including interactive blots, configuration utilities, i18n translations, control panels, context menus, resizing handlers, Vue demo components, and comprehensive documentation with four new runtime dependencies. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Toolbar
participant Module as MindMapModule/<br/>FlowChartModule
participant Blot as MindMapPlaceholderBlot/<br/>FlowChartPlaceholderBlot
participant Editor as SimpleMindMap/<br/>LogicFlow
participant UI as ControlPanel/<br/>ContextMenu
User->>Toolbar: Click mind-map/flow-chart button
Toolbar->>Module: insertMindMapEditor()/insertFlowChartEditor()
Module->>Blot: Create and embed blot instance
Blot->>Blot: Mount & initialize editor
Blot->>Editor: Initialize with config & data
Blot->>UI: Create control panel & context menu
Editor->>UI: Render UI elements
User->>UI: Interact (zoom, edit, resize)
UI->>Editor: Apply changes
Editor->>Blot: Update graph/tree state
Blot->>Blot: Sync data to DOM data-* attribute
sequenceDiagram
participant Init as Initialization
participant Config as Config Utils
participant Quill
participant Module as Module Instance
Init->>Module: Create with options
Module->>Quill: Store Quill reference
Module->>Config: getBackgroundConfig(quill)
Module->>Config: getGridConfig(quill) / getLineConfig(quill)
Module->>Config: getResizeConfig(quill)
Config->>Config: Extract from module options
Config->>Module: Return normalized config
Module->>Quill: Register blot class
Module->>Toolbar: Wire toolbar handler
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas requiring extra attention:
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 35
🧹 Nitpick comments (55)
packages/docs/package.json (1)
31-32: Inconsistent version pinning strategy between simple-mind-map and its plugin.
simple-mind-mapis pinned to exact version0.14.0-fix.1(per best practices for reproducible builds), butsimple-mind-map-plugin-themesuses caret^1.0.1, which allows minor/patch version drift. This creates inconsistent reproducibility guarantees.Consider pinning
simple-mind-map-plugin-themesto a specific version to match the reproducibility discipline applied tosimple-mind-map:- "simple-mind-map-plugin-themes": "^1.0.1", + "simple-mind-map-plugin-themes": "1.0.1",Alternatively, if allowing minor/patch updates for the plugin is intentional (e.g., to receive backwards-compatible bug fixes), document why the strategies differ. Based on learnings, please verify: (1) whether simple-mind-map-plugin-themes has a stable release matching 1.x that is compatible with [email protected], and (2) whether the caret range is necessary or if an exact version provides better stability.
packages/fluent-editor/src/modules/mind-map/style/mind-map.scss (2)
169-171: Extract hardcoded hover color to a variable for maintainability.The color
#efefefis hardcoded inline. Define it as a CSS variable or SCSS variable to enable consistent theming and reduce duplication if this color is used elsewhere.+$hover-background-color: #efefef; + .ql-mind-map-control-item:hover { - background-color: #efefef; + background-color: $hover-background-color; }
92-111: Consider adding responsive breakpoints for mobile/tablet layouts.The styling uses fixed dimensions (e.g.,
width: 200pxon line 23,width: 80pxon line 130) without media queries. Mind-map controls may overflow or become unusable on smaller viewports. Consider adding responsive breakpoints to ensure usability across device sizes.Would you like me to suggest responsive breakpoints (e.g., for tablets and mobile devices) to ensure the mind-map UI adapts gracefully across screen sizes?
Also applies to: 113-145
packages/fluent-editor/src/modules/flow-chart/types/images.d.ts (1)
1-4: Move this PNG type declaration to a shared location for better code organization.The ambient module declaration currently works across the project—it's already being imported by the mind-map module (12+ PNG imports across 2 files). However, keeping module-specific type declarations in a deeply nested module directory contradicts the global scope they provide. Move this to a shared location like
packages/fluent-editor/types/images.d.tsor include it in a root-level types directory for clarity and maintainability. This prevents future developers from assuming the declaration is only for flow-chart and improves discoverability.Additionally, verify that your bundler (Webpack/Vite/etc.) is configured to process PNG imports and return them as string URLs, as this declaration assumes that behavior.
packages/fluent-editor/src/modules/mind-map/modules/custom-resize-action.ts (5)
16-16: Remove dead code: unused targetRatio and rect.These aren’t used; keep code lean and avoid lint warnings.
- targetRatio: number = 0 @@ - const rect = container.getBoundingClientRect() @@ - this.targetRatio = rect.height / rect.width + // (ratio no longer tracked)Also applies to: 64-65, 102-103
84-90: Guard non‑left clicks and non‑HTMLElement targets.Prevents unexpected drags with right/middle click or when event target is not the handle.
- onMouseDown = (event: MouseEvent) => { + onMouseDown = (event: MouseEvent) => { if (this.isFullscreen()) { return } - if (!(event.target instanceof HTMLElement)) { + if (event.button !== 0 || !(event.target instanceof HTMLElement)) { return }
84-107: Optional: Prefer Pointer Events for unified mouse/touch/pen support.Switch to pointerdown/pointermove/pointerup with setPointerCapture for better device coverage.
High-level sketch:
box.addEventListener('pointerdown', this.onPointerDown) // in handler: (el as HTMLElement).setPointerCapture(event.pointerId) document.addEventListener('pointermove', this.onPointerMove) document.addEventListener('pointerup', this.onPointerUp)Keep current mouse listeners if you need legacy fallback.
Also applies to: 120-159
168-174: Ensure destroy() also detaches any document listeners and resets cursor.Prevents lingering handlers if destroy is called mid‑drag.
destroy() { const container = this.blot.domNode + document.body.style.cursor = '' + document.removeEventListener('mousemove', this.onDrag) + document.removeEventListener('mouseup', this.onMouseUp) container.removeChild(this.topLeftHandle) container.removeChild(this.topRightHandle) container.removeChild(this.bottomRightHandle) container.removeChild(this.bottomLeftHandle) }
158-159: Storing full JSON in a data‑attribute can bloat DOM and trigger heavy mutations.Consider a lightweight marker (id) and keep the data in JS state.
Do consumers read
data-mind-mapfrom DOM? If not, drop or replace with a small identifier and use a WeakMap to associate state.packages/fluent-editor/src/modules/mind-map/i18n/index.ts (1)
4-9: Consider strengthening the type annotation.The
I18Nparameter is typed asany, which reduces type safety. Consider importing theI18Nclass type from the i18n module to provide better type checking and IDE support.Apply this diff to improve type safety:
+import type { I18N } from '../../i18n' import { MIND_MAP_EN_US } from './en-us' import { MIND_MAP_ZH_CN } from './zh-cn' -export function registerMindMapI18N(I18N: any) { +export function registerMindMapI18N(I18N: typeof I18N) { I18N.register({ 'en-US': MIND_MAP_EN_US, 'zh-CN': MIND_MAP_ZH_CN, }, false) }packages/docs/fluent-editor/demos/flow-chart-background.vue (1)
35-36: Consider extracting the inline ops array to improve readability.The ops array on line 35 is very long and contains nested data structures, making it difficult to read and maintain. Consider extracting it to a named constant above the
onMountedblock.Apply this refactor:
const TOOLBAR_CONFIG = [ [{ header: [] }], ['bold', 'italic', 'underline', 'link'], [{ align: [] }, { list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], ['clean'], ['flow-chart'], ] + +const DEFAULT_FLOW_CHART_DATA = [ + { insert: '\n' }, + { + insert: { + 'flow-chart': { + nodes: [ + { id: 'node1', type: 'rect', x: 100, y: 150, text: '开始' }, + { id: 'node2', type: 'rect', x: 300, y: 150, text: '结束' }, + ], + edges: [ + { id: 'edge1', sourceNodeId: 'node1', targetNodeId: 'node2', type: 'polyline' }, + ], + }, + }, + }, + { insert: '\n\n' }, +] onMounted(() => { import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { if (!editorRef.value) return editor = new FluentEditor(editorRef.value, { theme: 'snow', modules: { 'toolbar': TOOLBAR_CONFIG, 'flow-chart': { background: { color: '#98FB98', // image: 'url(path/to/image.png)', repeat: 'repeat', position: 'center', size: 'auto', opacity: 0.1, }, }, }, }) - const ops = [{ insert: '\n' }, { insert: { 'flow-chart': { nodes: [{ id: 'node1', type: 'rect', x: 100, y: 150, text: '开始' }, { id: 'node2', type: 'rect', x: 300, y: 150, text: '结束' }], edges: [{ id: 'edge1', sourceNodeId: 'node1', targetNodeId: 'node2', type: 'polyline' }] } } }, { insert: '\n\n' }] - editor.setContents(ops) + editor.setContents(DEFAULT_FLOW_CHART_DATA) }) })packages/docs/fluent-editor/demos/mind-map-resize.vue (1)
28-29: Consider extracting the inline ops array to improve readability.Similar to the flow-chart demo, the ops array on line 28 is extremely long with deeply nested data structures, making it difficult to read and maintain. Consider extracting it to a named constant above the
onMountedblock.Apply this refactor:
const TOOLBAR_CONFIG = [ [{ header: [] }], ['bold', 'italic', 'underline', 'link'], [{ align: [] }, { list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], ['clean'], ['mind-map'], ] + +const DEFAULT_MIND_MAP_DATA = [ + { insert: '\n' }, + { + insert: { + 'mind-map': { + layout: 'logicalStructure', + root: { + data: { + text: '根节点', + expand: true, + uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', + isActive: false, + }, + children: [ + { + data: { + text: '二级节点', + uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', + expand: true, + richText: false, + isActive: false, + }, + children: [], + }, + ], + smmVersion: '0.14.0-fix.1', + }, + theme: { template: 'default', config: {} }, + view: { + transform: { + scaleX: 1, + scaleY: 1, + shear: 0, + rotate: 0, + translateX: 0, + translateY: 0, + originX: 0, + originY: 0, + a: 1, + b: 0, + c: 0, + d: 1, + e: 0, + f: 0, + }, + state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 }, + }, + }, + }, + }, + { insert: '\n\n' }, +] onMounted(() => { import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { if (!editorRef.value) return editor = new FluentEditor(editorRef.value, { theme: 'snow', modules: { 'toolbar': TOOLBAR_CONFIG, 'mind-map': { resize: true, }, }, }) - const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] - editor.setContents(ops) + editor.setContents(DEFAULT_MIND_MAP_DATA) }) })packages/fluent-editor/src/modules/flow-chart/i18n/index.ts (1)
4-9: Consider improving type safety for the I18N parameter.The
anytype for theI18Nparameter reduces type safety. Based on the I18N class structure shown in the relevant snippets, you could define a more specific type or interface.Example refactor:
-export function registerFlowChartI18N(I18N: any) { +export function registerFlowChartI18N(I18N: { register: (langs: Record<string, Record<string, string>>, isCover?: boolean) => void }) { I18N.register({ 'en-US': FLOW_CHART_EN_US, 'zh-CN': FLOW_CHART_ZH_CN, }, false) }packages/docs/fluent-editor/demos/mind-map.vue (2)
26-27: Extract inline mind-map data to improve readability.The mind-map initialization data is a large, complex object literal that spans the entire line, making the code difficult to read and maintain.
Apply this diff:
+const INITIAL_MIND_MAP_DATA = { + layout: 'logicalStructure', + root: { + data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, + children: [{ + data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, + children: [] + }], + smmVersion: '0.14.0-fix.1' + }, + theme: { template: 'default', config: {} }, + view: { + transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, + state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } + } +} + onMounted(() => { import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { if (!editorRef.value) return editor = new FluentEditor(editorRef.value, { theme: 'snow', modules: { 'toolbar': TOOLBAR_CONFIG, 'mind-map': true, }, }) - const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] + const ops = [{ insert: '\n' }, { insert: { 'mind-map': INITIAL_MIND_MAP_DATA } }, { insert: '\n\n' }] editor.setContents(ops) }) })
16-29: Add error handling for the dynamic import.The dynamic import lacks error handling, which could result in silent failures or unhandled promise rejections.
Apply this diff:
onMounted(() => { - import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { + import('@opentiny/fluent-editor') + .then(({ default: FluentEditor }) => { if (!editorRef.value) return editor = new FluentEditor(editorRef.value, { theme: 'snow', modules: { 'toolbar': TOOLBAR_CONFIG, 'mind-map': true, }, }) const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] editor.setContents(ops) }) + .catch((error) => { + console.error('Failed to load FluentEditor:', error) + }) })packages/docs/fluent-editor/demos/flow-chart-resize.vue (2)
28-29: Extract inline flow-chart data to improve readability.Similar to the mind-map demo, the flow-chart initialization data is inlined, reducing readability.
Apply this diff:
+const INITIAL_FLOW_CHART_DATA = { + nodes: [ + { id: 'node1', type: 'rect', x: 100, y: 150, text: '开始' }, + { id: 'node2', type: 'rect', x: 300, y: 150, text: '结束' } + ], + edges: [ + { id: 'edge1', sourceNodeId: 'node1', targetNodeId: 'node2', type: 'polyline' } + ] +} + onMounted(() => { import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { if (!editorRef.value) return editor = new FluentEditor(editorRef.value, { theme: 'snow', modules: { 'toolbar': TOOLBAR_CONFIG, 'flow-chart': { resize: true, }, }, }) - const ops = [{ insert: '\n' }, { insert: { 'flow-chart': { nodes: [{ id: 'node1', type: 'rect', x: 100, y: 150, text: '开始' }, { id: 'node2', type: 'rect', x: 300, y: 150, text: '结束' }], edges: [{ id: 'edge1', sourceNodeId: 'node1', targetNodeId: 'node2', type: 'polyline' }] } } }, { insert: '\n\n' }] + const ops = [{ insert: '\n' }, { insert: { 'flow-chart': INITIAL_FLOW_CHART_DATA } }, { insert: '\n\n' }] editor.setContents(ops) }) })
16-31: Add error handling for the dynamic import.The dynamic import lacks error handling.
Apply this diff:
onMounted(() => { - import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { + import('@opentiny/fluent-editor') + .then(({ default: FluentEditor }) => { if (!editorRef.value) return editor = new FluentEditor(editorRef.value, { theme: 'snow', modules: { 'toolbar': TOOLBAR_CONFIG, 'flow-chart': { resize: true, }, }, }) const ops = [{ insert: '\n' }, { insert: { 'flow-chart': { nodes: [{ id: 'node1', type: 'rect', x: 100, y: 150, text: '开始' }, { id: 'node2', type: 'rect', x: 300, y: 150, text: '结束' }], edges: [{ id: 'edge1', sourceNodeId: 'node1', targetNodeId: 'node2', type: 'polyline' }] } } }, { insert: '\n\n' }] editor.setContents(ops) }) + .catch((error) => { + console.error('Failed to load FluentEditor:', error) + }) })packages/docs/fluent-editor/demos/mind-map-line.vue (2)
33-34: Extract inline mind-map data to improve readability.The inline mind-map data with line configuration is extremely long and difficult to read.
Apply this diff:
+const INITIAL_MIND_MAP_DATA = { + layout: 'logicalStructure', + root: { + data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, + children: [{ + data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, + children: [] + }], + smmVersion: '0.14.0-fix.1' + }, + theme: { + template: 'default', + config: { + lineColor: '#009CE0', + lineWidth: 3, + lineDasharray: '15, 10, 5, 10, 15', + lineStyle: 'curve' + } + }, + view: { + transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, + state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } + } +} + onMounted(() => { import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { if (!editorRef.value) return editor = new FluentEditor(editorRef.value, { theme: 'snow', modules: { 'toolbar': TOOLBAR_CONFIG, 'mind-map': { line: { color: '#009CE0', width: 3, dasharray: '15, 10, 5, 10, 15', style: 'curve', }, }, }, }) - const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: { lineColor: '#009CE0', lineWidth: 3, lineDasharray: '15, 10, 5, 10, 15', lineStyle: 'curve' } }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] + const ops = [{ insert: '\n' }, { insert: { 'mind-map': INITIAL_MIND_MAP_DATA } }, { insert: '\n\n' }] editor.setContents(ops) }) })
16-36: Add error handling for the dynamic import.The dynamic import lacks error handling.
Apply this diff:
onMounted(() => { - import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { + import('@opentiny/fluent-editor') + .then(({ default: FluentEditor }) => { if (!editorRef.value) return editor = new FluentEditor(editorRef.value, { theme: 'snow', modules: { 'toolbar': TOOLBAR_CONFIG, 'mind-map': { line: { color: '#009CE0', width: 3, dasharray: '15, 10, 5, 10, 15', style: 'curve', }, }, }, }) const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: { lineColor: '#009CE0', lineWidth: 3, lineDasharray: '15, 10, 5, 10, 15', lineStyle: 'curve' } }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] editor.setContents(ops) }) + .catch((error) => { + console.error('Failed to load FluentEditor:', error) + }) })packages/docs/fluent-editor/demos/flow-chart.vue (2)
26-27: Extract inline flow-chart data to improve readability.The flow-chart initialization data should be extracted to a constant for better readability and maintainability.
Apply this diff:
+const INITIAL_FLOW_CHART_DATA = { + nodes: [ + { id: 'node1', type: 'rect', x: 100, y: 150, text: '开始' }, + { id: 'node2', type: 'rect', x: 300, y: 150, text: '结束' } + ], + edges: [ + { id: 'edge1', sourceNodeId: 'node1', targetNodeId: 'node2', type: 'polyline' } + ] +} + onMounted(() => { import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { if (!editorRef.value) return editor = new FluentEditor(editorRef.value, { theme: 'snow', modules: { 'toolbar': TOOLBAR_CONFIG, 'flow-chart': true, }, }) - const ops = [{ insert: '\n' }, { insert: { 'flow-chart': { nodes: [{ id: 'node1', type: 'rect', x: 100, y: 150, text: '开始' }, { id: 'node2', type: 'rect', x: 300, y: 150, text: '结束' }], edges: [{ id: 'edge1', sourceNodeId: 'node1', targetNodeId: 'node2', type: 'polyline' }] } } }, { insert: '\n\n' }] + const ops = [{ insert: '\n' }, { insert: { 'flow-chart': INITIAL_FLOW_CHART_DATA } }, { insert: '\n\n' }] editor.setContents(ops) }) })
16-29: Add error handling for the dynamic import.The dynamic import lacks error handling, which could result in silent failures.
Apply this diff:
onMounted(() => { - import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { + import('@opentiny/fluent-editor') + .then(({ default: FluentEditor }) => { if (!editorRef.value) return editor = new FluentEditor(editorRef.value, { theme: 'snow', modules: { 'toolbar': TOOLBAR_CONFIG, 'flow-chart': true, }, }) const ops = [{ insert: '\n' }, { insert: { 'flow-chart': { nodes: [{ id: 'node1', type: 'rect', x: 100, y: 150, text: '开始' }, { id: 'node2', type: 'rect', x: 300, y: 150, text: '结束' }], edges: [{ id: 'edge1', sourceNodeId: 'node1', targetNodeId: 'node2', type: 'polyline' }] } } }, { insert: '\n\n' }] editor.setContents(ops) }) + .catch((error) => { + console.error('Failed to load FluentEditor:', error) + }) })packages/fluent-editor/src/ui/icons.config.ts (1)
362-363: Normalize icon sizing and theming to match existing set.Both new icons hardcode width/height=200 and fill colors. For consistent toolbar rendering and theming, prefer using only viewBox, remove fixed sizes, and use
currentColorfills.Example adjustment:
-export const MIND_MAP_ICON = `<svg ... width="200" height="200"><path ... fill="#2c2c2c" .../></svg>` +export const MIND_MAP_ICON = `<svg viewBox="0 0 1024 1024"><path fill="currentColor" d="..."/></svg>` -export const FLOW_CHART_ICON = `<svg ... width="200" height="200"><path ... fill="#2c2c2c" .../></svg>` +export const FLOW_CHART_ICON = `<svg viewBox="0 0 1024 1024"><path fill="currentColor" d="..."/></svg>`Confirm icons.ts registers these two exports in the UI registry so toolbar buttons render correctly.
packages/docs/fluent-editor/demos/mind-map-background.vue (1)
16-37: Add cleanup on unmount to avoid leaks.Destroy the editor instance when the component unmounts.
-import { onMounted, ref } from 'vue' +import { onMounted, onBeforeUnmount, ref } from 'vue' @@ onMounted(() => { import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { @@ }) }) + +onBeforeUnmount(() => { + // destroy/dispose if available + (editor as any)?.destroy?.() +})Optional: add
data-testid="mind-map-background-demo"to the root div to stabilize e2e.packages/docs/fluent-editor/demos/flow-chart-grid.vue (1)
16-37: Add cleanup on unmount to avoid leaks.-import { onMounted, ref } from 'vue' +import { onMounted, onBeforeUnmount, ref } from 'vue' @@ onMounted(() => { import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { @@ }) }) + +onBeforeUnmount(() => { + (editor as any)?.destroy?.() +})Optional: add
data-testid="flow-chart-grid-demo"on the container for robust e2e scoping.packages/fluent-editor/src/modules/mind-map/index.ts (1)
27-54: Embed insertion offsets are fine; consider minimal data for better defaults.If the blot expects
layout/theme/view/smmVersion, align default data with demos to avoid render differences.- const defaultData = { - data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, - children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], - } + const defaultData = { + layout: 'logicalStructure', + root: { + data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, + children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], + smmVersion: '0.14.0-fix.1' + }, + theme: { template: 'default', config: {} }, + view: { transform: { a:1,b:0,c:0,d:1,e:0,f:0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } + }packages/docs/fluent-editor/demos/mind-map-theme.vue (1)
16-37: Add cleanup on unmount to avoid leaks.-import { onMounted, ref } from 'vue' +import { onMounted, onBeforeUnmount, ref } from 'vue' @@ onMounted(() => { import('@opentiny/fluent-editor').then(({ default: FluentEditor }) => { @@ }) }) + +onBeforeUnmount(() => { + (editor as any)?.destroy?.() +})Optional: add
data-testid="mind-map-theme-demo"to stabilize e2e.packages/fluent-editor/src/modules/flow-chart/modules/context-menu.ts (3)
14-24: Avoid repeated i18n registration per blot.
registerFlowChartI18N(I18N)is called for every context menu; move to module scope with a guard to prevent redundant registrations.+let __flowChartI18nRegistered = false @@ - registerFlowChartI18N(I18N) + if (!__flowChartI18nRegistered) { + registerFlowChartI18N(I18N) + __flowChartI18nRegistered = true + }
125-129: Avoid innerHTML mutation; clear children safely.While
innerHTML = ''is safe here, prefer node removal to dodge XSS scanners and microtask reflows.- if (blot.contextMenu) { - blot.contextMenu.innerHTML = '' - } + if (blot.contextMenu) { + while (blot.contextMenu.firstChild) { + blot.contextMenu.removeChild(blot.contextMenu.firstChild) + } + }
139-155: De-dupe menu item styles via CSS class.Inline styles make theme overrides hard. Add a CSS class (e.g.,
.ql-flow-chart-context-menu-item) to your stylesheet and drop JS-assigned styles, keeping only behavior wiring here.packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts (3)
164-181: Accessibility: use button semantics + keyboard activationControl items are
with mouse-only interaction. Add ARIA role, focus, and keyboard handling for Enter/Space. Also reflect disabled state.function createControlItem(iconClass: string, title: string, onClick: () => void, disabled = false) { const controlItem = document.createElement('div') controlItem.className = 'ql-mind-map-control-item' controlItem.title = title controlItem.dataset.controlType = iconClass - controlItem.style.cursor = disabled ? 'not-allowed' : 'pointer' - controlItem.style.opacity = disabled ? DISABLED_OPACITY : ENABLED_OPACITY + controlItem.style.cursor = disabled ? 'not-allowed' : 'pointer' + controlItem.style.opacity = disabled ? DISABLED_OPACITY : ENABLED_OPACITY + controlItem.setAttribute('role', 'button') + controlItem.setAttribute('aria-disabled', disabled ? 'true' : 'false') + controlItem.tabIndex = disabled ? -1 : 0 @@ - if (!disabled) { - controlItem.addEventListener('click', onClick) - } + if (!disabled) { + controlItem.addEventListener('click', onClick) + controlItem.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + onClick() + } + }) + }
3-3: Avoid deep import from simple-mind-map/srcImporting from
simple-mind-map/src/svg/iconsrelies on internal paths that may change. Prefer the package’s public ESM/UMD exports or vendor the assets you need.Can you confirm a stable public export for icons in your current pinned version (e.g.,
0.14.0-fix.1)? If not, consider copying the minimal set you need into this repo.
30-35: Language-change listener needs cleanupThe handler is registered on
quill.emitterbut never removed. Over time, detached blots can leak listeners.Store the callback and remove it when blot is destroyed/unmounted:
class MindMapControlPanelHandler { private onLangChange = (lang: string) => { /* update */ } constructor(private quill: FluentEditor, private blot: MindMapPlaceholderBlot) { // ... this.quill.emitter.on(CHANGE_LANGUAGE_EVENT, this.onLangChange) } dispose() { this.quill.emitter.off(CHANGE_LANGUAGE_EVENT, this.onLangChange) } }Hook
dispose()from the blot’s teardown (if available). Please confirm if there is a blot/module lifecycle hook we can use.packages/fluent-editor/src/modules/mind-map/options.ts (1)
1-20: Types look good; consider documenting value formatsInterfaces are clear. Optionally, document expected units/ranges:
- BackgroundConfig.size/position: CSS background-size/position strings.
- LineConfig.dasharray: SVG stroke-dasharray string.
- LineConfig.style: verify against SimpleMindMap supported values.
If the library uses different naming (e.g., 'curve' vs 'bezier'), align the union type accordingly.
packages/fluent-editor/src/modules/flow-chart/index.ts (2)
17-27: Strongly type options; use Quill sources constantMinor polish:
- Type the constructor options as FlowChartOptions.
- Use
Quills.sources.USERinstead of string literal 'user'.- constructor(quill: Quill, options: any) { + constructor(quill: Quill, options: FlowChartOptions) { @@ - this.quill.insertText(range.index, '\n', 'user') - this.quill.insertEmbed(range.index + 1, 'flow-chart', defaultData, 'user') - this.quill.insertText(range.index + 2, '\n', 'user') + this.quill.insertText(range.index, '\n', Quills.sources.USER) + this.quill.insertEmbed(range.index + 1, 'flow-chart', defaultData, Quills.sources.USER) + this.quill.insertText(range.index + 2, '\n', Quills.sources.USER)
33-40: Default node text and i18nThe default labels are Chinese (“开始/结束”). If the editor language differs, consider mapping to i18n strings.
packages/fluent-editor/src/modules/mind-map/modules/context-menu.ts (2)
19-23: Language-change listener needs cleanupSame pattern as the control panel: store the handler and call
offon teardown to avoid leaks.
50-54: Add ARIA roles for better accessibilityAssign roles to the menu container and items for screen readers.
- blot.contextMenu = document.createElement('div') + blot.contextMenu = document.createElement('div') blot.contextMenu.className = 'ql-mind-map-context-menu' + blot.contextMenu.setAttribute('role', 'menu') @@ const item = document.createElement('div') item.className = 'ql-mind-map-context-menu-item' item.textContent = text + item.setAttribute('role', 'menuitem')Also applies to: 95-110
packages/fluent-editor/src/modules/flow-chart/options.ts (1)
1-23: Looks good; minor docs on numeric rangesConsider documenting:
- GridOptions.size/thickness units (px)
- BackgroundConfig.opacity range (0–1)
packages/fluent-editor/src/modules/flow-chart/modules/custom-resize-action.ts (3)
14-15: Remove unused fields to reduce noise.
targetRatiois assigned but never used; consider removing the field and assignment.- targetRatio: number = 0 @@ - this.targetRatio = rect.height / rect.widthAlso applies to: 96-101
60-63: Minor: unused local.
rectis computed but not used inrepositionHandles(). Remove it.- const rect = container.getBoundingClientRect()
1-2: Consider making min sizes configurable.Hardcoded
MIN_WIDTH/MIN_HEIGHTcould be read from module options for flexibility.packages/fluent-editor/src/modules/mind-map/config-utils.ts (4)
55-60: Preserve valid zero values.
if (typedLine.width)drops0. Check againstundefinedinstead.- if (typedLine.width) { + if (typedLine.width !== undefined) { lineConfig.lineWidth = typedLine.width }
61-66: Allow empty string dash patterns.An empty
dasharraystring is valid to mean solid; current truthy check drops it.- if (typedLine.dasharray) { + if (typedLine.dasharray !== undefined) { lineConfig.lineDasharray = typedLine.dasharray }
19-35: Return a typed config instead ofobject.Replace
any/objectwith a typed shape to improve DX and catch mistakes.
4-8: Optional: treat booleantrueas “use defaults”.Currently only objects are honored;
trueyieldsfalse. Consider returning a sensible default whenbackground/lineistrue.Also applies to: 40-44
packages/fluent-editor/src/modules/flow-chart/config-utils.ts (3)
29-35: Use nullish coalescing to keep valid zeros.
||overwrites0. Switch to??and explicit checks.- size: typedGrid.size || defaultGrid.size, + size: typedGrid.size ?? defaultGrid.size, visible: typedGrid.visible !== undefined ? typedGrid.visible : defaultGrid.visible, - type: typedGrid.type || defaultGrid.type, + type: typedGrid.type ?? defaultGrid.type, config: { - color: typedGrid.config?.color || defaultGrid.config.color, - thickness: typedGrid.config?.thickness || defaultGrid.config.thickness, + color: typedGrid.config?.color ?? defaultGrid.config.color, + thickness: typedGrid.config?.thickness ?? defaultGrid.config.thickness, },
75-77: Preserve opacity=0.Truthiness check drops zero; use
!== undefined.- if (typedBackground.opacity) { + if (typedBackground.opacity !== undefined) { backgroundConfig.opacity = typedBackground.opacity }
4-13: Type the return values.Avoid
any/false | objectwhere possible; export explicit types forGridConfig,BackgroundStyleConfig, and the aggregate.Also applies to: 96-105
packages/fluent-editor/src/modules/mind-map/formats/mind-map-blot.ts (4)
48-58: Guard against malformed JSON.
JSON.parsecan throw whendata-mind-mapis missing/corrupt.- const dataStr = JSON.parse(domNode.getAttribute('data-mind-map')) - const value = dataStr + const raw = domNode.getAttribute('data-mind-map') + let value: any = {} + try { + value = raw ? JSON.parse(raw) : {} + } catch { + value = {} + } @@ - return dataStr + return value
371-384: Scope DOM queries to this blot.
document.querySelectorAll('.smm-node-edit-wrap')risks touching other editors. Limit to the blot container if possible.- const editWraps = document.querySelectorAll('.smm-node-edit-wrap') + const root: ParentNode = this.domNode ?? document + const editWraps = root.querySelectorAll('.smm-node-edit-wrap')
146-148: Manage resize action lifecycle.You instantiate
MindMapResizeActionbut don’t retain or destroy it.- if (resizeConfig) { - new MindMapResizeAction(this) - } + if (resizeConfig) { + this._resizeAction = new MindMapResizeAction(this) + }And in class fields and
remove():+ _resizeAction: MindMapResizeAction | null = null @@ remove() { + this._resizeAction?.destroy() + this._resizeAction = null this.mindMap.destroy() ... }Also applies to: 390-397
275-286: Avoid mutating parent element id.Setting an auto-generated
idon the parent can conflict with host app. PreferisConnectedchecks or track the element via a WeakRef without touching attributes.packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts (1)
21-31: Cleanup i18n listener on dispose.
emitter.onis never unsubscribed; add adispose()or return cleanup fromcreateControlPanelto prevent leaks when blots are removed.packages/fluent-editor/src/modules/flow-chart/formats/flow-chart-blot.ts (2)
93-106: Avoid observing the entire document just to detect connection.Observing document.body subtree is heavy. Consider deferring editor init to attach() (when quill is available) and use requestAnimationFrame or a short microtask instead of a global MutationObserver.
137-156: Localize shape labels; don’t hardcode Chinese strings.Replace '矩形/圆形/椭圆/菱形' with i18n lookups to match the rest of the module’s i18n approach.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (39)
packages/fluent-editor/src/modules/flow-chart/icons/backIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/bezierIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/circleIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/contractIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/diamondIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/ellipseIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/expandIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/fitIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/forwardIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/fullScreenIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/lineIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/polyLineIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/rectangleIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/selectRegionIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/smallScreenIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/zoomInIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/flow-chart/icons/zoomOutIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/backIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/catalogOrganizationIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/childNodeIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/contractIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/deleteNodeIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/expandIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/fishboneIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/fitIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/forwardIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/fullScreenIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/insertIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/levelNodeIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/logicalStructureIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/mindMapIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/organizationStructureIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/parentNodeIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/setLayoutIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/smallScreenIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/timelineIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/zoomInIcon.pngis excluded by!**/*.pngpackages/fluent-editor/src/modules/mind-map/icons/zoomOutIcon.pngis excluded by!**/*.pngpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (50)
README.md(1 hunks)README.zh-CN.md(1 hunks)packages/docs/fluent-editor/.vitepress/sidebar.ts(1 hunks)packages/docs/fluent-editor/demos/flow-chart-background.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart-grid.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart-resize.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart.spec.ts(1 hunks)packages/docs/fluent-editor/demos/flow-chart.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-background.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-line.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-resize.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-theme.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map.spec.ts(1 hunks)packages/docs/fluent-editor/demos/mind-map.vue(1 hunks)packages/docs/fluent-editor/docs/demo/flow-chart.md(1 hunks)packages/docs/fluent-editor/docs/demo/mind-map.md(1 hunks)packages/docs/package.json(2 hunks)packages/fluent-editor/package.json(2 hunks)packages/fluent-editor/src/config/i18n/en-us.ts(1 hunks)packages/fluent-editor/src/config/i18n/zh-cn.ts(1 hunks)packages/fluent-editor/src/config/types/editor-modules.interface.ts(2 hunks)packages/fluent-editor/src/fluent-editor.ts(2 hunks)packages/fluent-editor/src/modules/flow-chart/config-utils.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/formats/flow-chart-blot.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/i18n/en-us.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/i18n/index.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/i18n/zh-cn.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/index.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/modules/context-menu.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/modules/custom-resize-action.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/options.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/style/flow-chart.scss(1 hunks)packages/fluent-editor/src/modules/flow-chart/types/images.d.ts(1 hunks)packages/fluent-editor/src/modules/index.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/config-utils.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/formats/mind-map-blot.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/i18n/en-us.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/i18n/index.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/i18n/zh-cn.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/index.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/modules/context-menu.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/modules/custom-resize-action.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/options.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/style/mind-map.scss(1 hunks)packages/fluent-editor/src/modules/shortcut-key/index.ts(1 hunks)packages/fluent-editor/src/ui/icons.config.ts(1 hunks)packages/fluent-editor/src/ui/icons.ts(3 hunks)packages/projects/src/views/yuque/YuQue.vue(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (15)
packages/fluent-editor/src/modules/mind-map/i18n/index.ts (3)
packages/fluent-editor/src/modules/i18n.ts (1)
I18N(10-64)packages/fluent-editor/src/modules/mind-map/i18n/en-us.ts (1)
MIND_MAP_EN_US(1-25)packages/fluent-editor/src/modules/mind-map/i18n/zh-cn.ts (1)
MIND_MAP_ZH_CN(1-25)
packages/fluent-editor/src/ui/icons.ts (1)
packages/fluent-editor/src/ui/icons.config.ts (2)
MIND_MAP_ICON(362-362)FLOW_CHART_ICON(363-363)
packages/fluent-editor/src/config/types/editor-modules.interface.ts (2)
packages/fluent-editor/src/modules/mind-map/options.ts (1)
MindMapOptions(15-20)packages/fluent-editor/src/modules/flow-chart/options.ts (1)
FlowChartOptions(19-23)
packages/fluent-editor/src/modules/flow-chart/i18n/index.ts (3)
packages/fluent-editor/src/modules/i18n.ts (1)
I18N(10-64)packages/fluent-editor/src/modules/flow-chart/i18n/en-us.ts (1)
FLOW_CHART_EN_US(1-26)packages/fluent-editor/src/modules/flow-chart/i18n/zh-cn.ts (1)
FLOW_CHART_ZH_CN(1-26)
packages/fluent-editor/src/modules/flow-chart/modules/context-menu.ts (4)
packages/fluent-editor/src/modules/i18n.ts (1)
I18N(10-64)packages/fluent-editor/src/modules/flow-chart/i18n/index.ts (1)
registerFlowChartI18N(4-9)packages/fluent-editor/src/config/editor.config.ts (1)
CHANGE_LANGUAGE_EVENT(4-4)packages/fluent-editor/src/core/fluent-editor.ts (1)
lang(17-20)
packages/fluent-editor/src/modules/mind-map/index.ts (1)
packages/fluent-editor/src/modules/mind-map/options.ts (1)
MindMapOptions(15-20)
packages/fluent-editor/src/fluent-editor.ts (2)
packages/fluent-editor/src/modules/mind-map/index.ts (1)
MindMapModule(6-55)packages/fluent-editor/src/modules/flow-chart/index.ts (1)
FlowChartModule(8-46)
packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts (5)
packages/fluent-editor/src/modules/i18n.ts (1)
I18N(10-64)packages/fluent-editor/src/modules/mind-map/i18n/index.ts (1)
registerMindMapI18N(4-9)packages/fluent-editor/src/config/editor.config.ts (1)
CHANGE_LANGUAGE_EVENT(4-4)packages/fluent-editor/src/core/fluent-editor.ts (1)
lang(17-20)packages/fluent-editor/src/modules/mind-map/modules/custom-resize-action.ts (1)
isFullscreen(28-31)
packages/fluent-editor/src/modules/mind-map/modules/context-menu.ts (4)
packages/fluent-editor/src/modules/i18n.ts (1)
I18N(10-64)packages/fluent-editor/src/modules/mind-map/i18n/index.ts (1)
registerMindMapI18N(4-9)packages/fluent-editor/src/config/editor.config.ts (1)
CHANGE_LANGUAGE_EVENT(4-4)packages/fluent-editor/src/core/fluent-editor.ts (1)
lang(17-20)
packages/fluent-editor/src/modules/mind-map/config-utils.ts (1)
packages/fluent-editor/src/modules/mind-map/options.ts (2)
BackgroundConfig(1-7)LineConfig(9-14)
packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts (5)
packages/fluent-editor/src/modules/i18n.ts (1)
I18N(10-64)packages/fluent-editor/src/modules/flow-chart/i18n/index.ts (1)
registerFlowChartI18N(4-9)packages/fluent-editor/src/config/editor.config.ts (1)
CHANGE_LANGUAGE_EVENT(4-4)packages/fluent-editor/src/core/fluent-editor.ts (1)
lang(17-20)packages/fluent-editor/src/modules/flow-chart/modules/custom-resize-action.ts (1)
isFullscreen(26-29)
packages/fluent-editor/src/modules/flow-chart/formats/flow-chart-blot.ts (4)
packages/fluent-editor/src/modules/flow-chart/config-utils.ts (1)
getAllConfigs(96-106)packages/fluent-editor/src/modules/flow-chart/modules/custom-resize-action.ts (1)
FlowChartResizeAction(4-164)packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts (1)
createControlPanel(74-127)packages/fluent-editor/src/modules/flow-chart/modules/context-menu.ts (1)
initContextMenu(54-104)
packages/fluent-editor/src/modules/mind-map/formats/mind-map-blot.ts (4)
packages/fluent-editor/src/modules/mind-map/config-utils.ts (1)
getAllConfigs(97-109)packages/fluent-editor/src/modules/mind-map/modules/custom-resize-action.ts (1)
MindMapResizeAction(6-175)packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts (1)
createControlPanel(103-162)packages/fluent-editor/src/modules/mind-map/modules/context-menu.ts (1)
initContextMenu(50-93)
packages/fluent-editor/src/modules/flow-chart/config-utils.ts (1)
packages/fluent-editor/src/modules/flow-chart/options.ts (2)
GridOptions(1-9)BackgroundConfig(10-17)
packages/fluent-editor/src/modules/flow-chart/index.ts (1)
packages/fluent-editor/src/modules/flow-chart/options.ts (1)
FlowChartOptions(19-23)
🪛 ast-grep (0.39.6)
packages/fluent-editor/src/modules/flow-chart/modules/context-menu.ts
[warning] 126-126: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: blot.contextMenu.innerHTML = ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts
[warning] 256-256: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconItem.innerHTML = icon.icon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 256-256: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconItem.innerHTML = icon.icon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
🪛 GitHub Actions: Playwright Tests
packages/docs/fluent-editor/demos/mind-map.spec.ts
[error] 9-12: Playwright test failure: strict mode violation. locator('.ql-editor') resolved to multiple elements; expected a single editor for render check.
[error] 14-17: Playwright test failure: strict mode violation. locator('.ql-toolbar') resolved to multiple toolbars; expected a single toolbar for visibility check.
[error] 22-25: Playwright test failure: strict mode violation. locator('.ql-editor') resolved to multiple elements for initialize content check.
[error] 32-35: Playwright test failure: strict mode violation. locator('.ql-toolbar .ql-mind-map') resolved to multiple buttons; expected a single button for visibility check.
packages/docs/fluent-editor/demos/flow-chart.spec.ts
[error] 8-10: Playwright test failure: strict mode violation. locator('.ql-editor') resolved to multiple elements; expected a single element for visibility check.
[error] 13-16: Playwright test failure: strict mode violation.locator('.ql-toolbar') resolved to multiple toolbars; expected a single element for visibility check.
[error] 21-24: Playwright test failure: strict mode violation. locator('.ql-editor') resolved to multiple editor instances for initial content check.
[error] 33-36: Playwright test failure: strict mode violation. locator('.ql-flow-chart-item') resolved to multiple items; expected a single item for visibility check.
[error] 44-49: Playwright test failure: strict mode violation. locator('.ql-toolbar .ql-flow-chart') resolved to multiple buttons; expected a single button for visibility check.
🪛 LanguageTool
packages/docs/fluent-editor/docs/demo/flow-chart.md
[uncategorized] ~13-~13: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...标悬停在节点上出现在四个角的手柄可以调整节点大小,右上方的手柄可以旋转节点,上下左右方向上的的手柄可以添加连线。 4. 该流程图模块支持配置网格样式,背景样式,调整大小...
(wa5)
| test('should render the editor', async ({ page }) => { | ||
| const editor = page.locator('.ql-editor') | ||
| await expect(editor).toBeVisible() | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix strict-mode locator conflicts; scope to one editor container and remove arbitrary waits.
Multiple editors/toolbars exist on the docs page, so generic class selectors break in Playwright strict mode. Scope all queries to a single .ql-container (or a dedicated test id), and replace waitForTimeout with expectations.
Apply this diff:
@@
- test('should render the editor', async ({ page }) => {
- const editor = page.locator('.ql-editor')
- await expect(editor).toBeVisible()
- })
+ test('should render the editor', async ({ page }) => {
+ const container = page.locator('.ql-container').first()
+ const editor = container.locator('.ql-editor')
+ await expect(editor).toBeVisible()
+ })
@@
- test('should have flow-chart button in toolbar', async ({ page }) => {
- const toolbar = page.locator('.ql-toolbar')
- await expect(toolbar).toBeVisible()
-
- const flowChartButton = toolbar.locator('.ql-flow-chart')
- await expect(flowChartButton).toBeVisible()
- })
+ test('should have flow-chart button in toolbar', async ({ page }) => {
+ const container = page.locator('.ql-container').first()
+ const toolbar = container.locator('.ql-toolbar')
+ await expect(toolbar).toBeVisible()
+ await expect(toolbar.locator('.ql-flow-chart')).toBeVisible()
+ })
@@
- test('should initialize editor with flow chart content', async ({ page }) => {
- const editor = page.locator('.ql-editor')
- await expect(editor).toBeVisible()
-
- await page.waitForTimeout(1000)
-
- const flowChartElement = editor.locator('.ql-flow-chart-item')
- await expect(flowChartElement).toBeVisible()
- })
+ test('should initialize editor with flow chart content', async ({ page }) => {
+ const container = page.locator('.ql-container').first()
+ const editor = container.locator('.ql-editor')
+ await expect(editor).toBeVisible()
+ await expect(container.locator('.ql-flow-chart-item').first()).toBeVisible()
+ })
@@
- test('should contain initial flow chart nodes and edges', async ({ page }) => {
- await page.waitForTimeout(2000)
-
- const flowChartContainer = page.locator('.ql-flow-chart-item')
- await expect(flowChartContainer).toBeVisible()
-
- const nodes = page.locator('.lf-node')
- await expect(nodes).toHaveCount(2)
-
- const edges = page.locator('.lf-edge')
- await expect(edges).toHaveCount(1)
- })
+ test('should contain initial flow chart nodes and edges', async ({ page }) => {
+ const container = page.locator('.ql-container').first()
+ const flowChart = container.locator('.ql-flow-chart-item').first()
+ await expect(flowChart).toBeVisible()
+ await expect(flowChart.locator('.lf-node')).toHaveCount(2, { timeout: 5000 })
+ await expect(flowChart.locator('.lf-edge')).toHaveCount(1, { timeout: 5000 })
+ })
@@
- test('should activate flow-chart when button is clicked', async ({ page }) => {
- const flowChartButton = page.locator('.ql-toolbar .ql-flow-chart')
- await expect(flowChartButton).toBeVisible()
-
- await flowChartButton.click()
-
- await page.waitForTimeout(500)
-
- const editor = page.locator('.ql-editor')
- await expect(editor).toBeVisible()
- })
+ test('should activate flow-chart when button is clicked', async ({ page }) => {
+ const container = page.locator('.ql-container').first()
+ const flowChartButton = container.locator('.ql-toolbar .ql-flow-chart')
+ await expect(flowChartButton).toBeVisible()
+ await flowChartButton.click()
+ await expect(container.locator('.ql-editor')).toBeVisible()
+ })Optionally, add data-testid hooks to the demo page and scope to page.getByTestId('flow-chart-demo') for maximum stability.
Also applies to: 13-19, 21-29, 31-42, 44-54
🧰 Tools
🪛 GitHub Actions: Playwright Tests
[error] 8-10: Playwright test failure: strict mode violation. locator('.ql-editor') resolved to multiple elements; expected a single element for visibility check.
🤖 Prompt for AI Agents
In packages/docs/fluent-editor/demos/flow-chart.spec.ts around lines 8-11 (and
similarly for ranges 13-19, 21-29, 31-42, 44-54), the tests use generic
selectors that conflict in Playwright strict mode and may rely on arbitrary
waits; scope all locators to a single editor container (e.g.,
page.locator('.ql-container').first() or better
page.getByTestId('flow-chart-demo') if you add a data-testid to the demo),
replace any waitForTimeout calls with explicit expectations (e.g., await
expect(scopedLocator.locator('.ql-editor')).toBeVisible()) and update all other
queries in the file to use the same scoped container so the test targets only
one editor instance and becomes stable.
| test('should initialize editor with mind map content', async ({ page }) => { | ||
| const editor = page.locator('.ql-editor') | ||
| await expect(editor).toBeVisible() | ||
|
|
||
| await page.waitForTimeout(1000) | ||
|
|
||
| const mindMapElement = page.locator('.ql-mind-map-item') | ||
| await expect(mindMapElement).toBeVisible() | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix strict mode violation and replace waitForTimeout with deterministic wait.
The locator .ql-editor resolved to multiple elements. Additionally, page.waitForTimeout(1000) is non-deterministic and discouraged in Playwright.
Apply this diff:
test('should initialize editor with mind map content', async ({ page }) => {
- const editor = page.locator('.ql-editor')
- await expect(editor).toBeVisible()
-
- await page.waitForTimeout(1000)
-
- const mindMapElement = page.locator('.ql-mind-map-item')
+ const mindMapElement = page.locator('.ql-mind-map-item').first()
await expect(mindMapElement).toBeVisible()
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| test('should initialize editor with mind map content', async ({ page }) => { | |
| const editor = page.locator('.ql-editor') | |
| await expect(editor).toBeVisible() | |
| await page.waitForTimeout(1000) | |
| const mindMapElement = page.locator('.ql-mind-map-item') | |
| await expect(mindMapElement).toBeVisible() | |
| }) | |
| test('should initialize editor with mind map content', async ({ page }) => { | |
| const mindMapElement = page.locator('.ql-mind-map-item').first() | |
| await expect(mindMapElement).toBeVisible() | |
| }) |
🧰 Tools
🪛 GitHub Actions: Playwright Tests
[error] 22-25: Playwright test failure: strict mode violation. locator('.ql-editor') resolved to multiple elements for initialize content check.
🤖 Prompt for AI Agents
In packages/docs/fluent-editor/demos/mind-map.spec.ts around lines 22-30, the
test uses page.locator('.ql-editor') which resolves to multiple elements and
uses page.waitForTimeout(1000) which is non-deterministic; change the editor
locator to a specific element (e.g., const editor =
page.locator('.ql-editor').first() or use a more specific selector) and remove
the hard timeout—instead wait deterministically for the mind map element, e.g.,
const mindMapElement = page.locator('.ql-mind-map-item').first(); await
mindMapElement.waitFor({ state: 'visible', timeout: 5000 }); then assert await
expect(mindMapElement).toBeVisible().
| test('should activate mind-map when button is clicked', async ({ page }) => { | ||
| const mindMapButton = page.locator('.ql-toolbar .ql-mind-map') | ||
| await expect(mindMapButton).toBeVisible() | ||
|
|
||
| await mindMapButton.click() | ||
|
|
||
| await page.waitForTimeout(500) | ||
|
|
||
| const editor = page.locator('.ql-editor') | ||
| await expect(editor).toBeVisible() | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix strict mode violation and replace waitForTimeout with deterministic wait.
The locator .ql-toolbar .ql-mind-map resolved to multiple buttons. The arbitrary timeout is also fragile.
Apply this diff:
test('should activate mind-map when button is clicked', async ({ page }) => {
- const mindMapButton = page.locator('.ql-toolbar .ql-mind-map')
+ const mindMapButton = page.locator('.ql-toolbar .ql-mind-map').first()
await expect(mindMapButton).toBeVisible()
await mindMapButton.click()
-
- await page.waitForTimeout(500)
-
- const editor = page.locator('.ql-editor')
- await expect(editor).toBeVisible()
+
+ // Assert on a meaningful state change after activation
+ await expect(page.locator('.ql-editor').first()).toBeVisible()
})🧰 Tools
🪛 GitHub Actions: Playwright Tests
[error] 32-35: Playwright test failure: strict mode violation. locator('.ql-toolbar .ql-mind-map') resolved to multiple buttons; expected a single button for visibility check.
🤖 Prompt for AI Agents
In packages/docs/fluent-editor/demos/mind-map.spec.ts around lines 32-42, the
locator '.ql-toolbar .ql-mind-map' resolves to multiple elements and the test
uses an arbitrary waitForTimeout; update the test to target a single button
(e.g. use .first() or a more specific selector) before clicking, remove
waitForTimeout, and replace it with a deterministic wait such as waiting for the
editor to become visible or focused (await expect(editor).toBeVisible() or await
editor.waitFor({ state: 'visible' }) or await
expect(editor).toHaveJSProperty('isContentEditable', true) /
expect(editor).toBeFocused()), ensuring the click acts on a single element and
the subsequent assertion waits for a concrete editor state instead of a fixed
timeout.
packages/fluent-editor/src/modules/mind-map/modules/custom-resize-action.ts
Outdated
Show resolved
Hide resolved
| box.addEventListener('mousedown', this.onMouseDown.bind(this)) | ||
| return box |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Event listeners added with bind() can’t be removed — leaks and duplicate handlers.
addEventListener(..., this.onDrag.bind(this)) creates a new function each time; removeEventListener(..., this.onDrag.bind(this)) won’t match, so listeners persist and accumulate.
Apply stable handler references (arrow properties) and drop bind():
@@
- box.addEventListener('mousedown', this.onMouseDown.bind(this))
+ box.addEventListener('mousedown', this.onMouseDown)
@@
- onMouseDown(event: MouseEvent) {
+ onMouseDown = (event: MouseEvent) => {
@@
- document.addEventListener('mousemove', this.onDrag.bind(this))
- document.addEventListener('mouseup', this.onMouseUp.bind(this))
+ document.addEventListener('mousemove', this.onDrag)
+ document.addEventListener('mouseup', this.onMouseUp)
@@
- }
+ }
@@
- onDrag(event: MouseEvent) {
+ onDrag = (event: MouseEvent) => {
@@
- }
+ }
@@
- onMouseUp() {
+ onMouseUp = () => {
@@
- document.removeEventListener('mousemove', this.onDrag.bind(this))
- document.removeEventListener('mouseup', this.onMouseUp.bind(this))
+ document.removeEventListener('mousemove', this.onDrag)
+ document.removeEventListener('mouseup', this.onMouseUp)
this.dragHandle = null
- }
+ }Also applies to: 105-107, 161-164
🤖 Prompt for AI Agents
In packages/fluent-editor/src/modules/mind-map/modules/custom-resize-action.ts
around lines 58-59 (and similarly at 105-107 and 161-164), event listeners are
added with this.onX.bind(this) which creates new function objects so
removeEventListener can't remove them; replace bound calls by creating stable
handler properties (e.g., arrow-function class fields like onMouseDown = (e) =>
{ ... }) and use those properties directly in
addEventListener/removeEventListener; update all places that currently use
.bind(this) to reference the persistent arrow property and ensure you register
and unregister the same function reference.
| updateDependentElementsHeight(newHeight: number) { | ||
| const iconPanel = this.blot.domNode.querySelector('.ql-mind-map-icon-panel') as HTMLElement | ||
| const layoutPanel = this.blot.domNode.querySelector('.ql-mind-map-layout-panel') as HTMLElement | ||
| if (iconPanel && newHeight < HEIGHT_THRESHOLD) { | ||
| iconPanel.style.height = `${newHeight - PANEL_HEIGHT_OFFSET}px` | ||
| } | ||
| if (layoutPanel && newHeight < HEIGHT_THRESHOLD) { | ||
| layoutPanel.style.height = `${newHeight - PANEL_HEIGHT_OFFSET}px` | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Panel heights never reset when resizing back above threshold.
Heights are only set when < HEIGHT_THRESHOLD; they remain stuck afterward.
- if (iconPanel && newHeight < HEIGHT_THRESHOLD) {
- iconPanel.style.height = `${newHeight - PANEL_HEIGHT_OFFSET}px`
- }
- if (layoutPanel && newHeight < HEIGHT_THRESHOLD) {
- layoutPanel.style.height = `${newHeight - PANEL_HEIGHT_OFFSET}px`
- }
+ const computed = Math.max(0, newHeight - PANEL_HEIGHT_OFFSET)
+ if (iconPanel) {
+ iconPanel.style.height = newHeight < HEIGHT_THRESHOLD ? `${computed}px` : ''
+ }
+ if (layoutPanel) {
+ layoutPanel.style.height = newHeight < HEIGHT_THRESHOLD ? `${computed}px` : ''
+ }🤖 Prompt for AI Agents
packages/fluent-editor/src/modules/mind-map/modules/custom-resize-action.ts
around lines 109 to 118: the current updateDependentElementsHeight only sets
panel heights when newHeight < HEIGHT_THRESHOLD, so once set they never revert
when resizing back above the threshold; modify the method so that for each panel
(iconPanel and layoutPanel) you both set the reduced height when newHeight <
HEIGHT_THRESHOLD and clear/reset the inline height when newHeight >=
HEIGHT_THRESHOLD (e.g., set style.height = '' or remove the style) — keep null
checks for the panels and apply the reset in the else branch so panels return to
their default styling when above the threshold.
| .ql-mind-map-control-item.disabled { | ||
| opacity: 0.5; | ||
| cursor: not-allowed; | ||
| pointer-events: none; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add focus/focus-visible states for keyboard accessibility.
Interactive elements (.ql-mind-map-control-item, .ql-mind-map-layout-item) lack :focus or :focus-visible indicators, breaking keyboard navigation. This is an accessibility blocker that prevents users from identifying focused elements when tabbing.
.ql-mind-map-control-item:hover {
background-color: #efefef;
}
+
+.ql-mind-map-control-item:focus-visible {
+ outline: 2px solid #0066cc;
+ outline-offset: 2px;
+}
+
+.ql-mind-map-layout-item:focus-visible {
+ outline: 2px solid #0066cc;
+ outline-offset: 2px;
+}Also applies to: 169-171
🤖 Prompt for AI Agents
In packages/fluent-editor/src/modules/mind-map/style/mind-map.scss around lines
147-151 (and similarly 169-171), add keyboard focus indicators for
.ql-mind-map-control-item and .ql-mind-map-layout-item by defining
:focus-visible and fallback :focus rules that provide a clear, high-contrast
outline or box-shadow (e.g., 2px solid or 3px offset shadow) so focused elements
are visually identifiable when tabbing; ensure these focus styles are not
applied when the element has the .disabled class (use
:not(.disabled):focus-visible / :not(.disabled):focus) and keep the styling
consistent with the theme and existing spacing.
a260797 to
ff076c4
Compare
|
@shenyaofeng 还有以下几处需要优化:
|
* 改变导入方式 * 删除响应图标使用svg格式 * 更改导入方式 * 删除打印 * 删除类型 * 删除多余样式 * 修改错误类名 * 添加类型 * 修改自动化测试用例
* 更换图标为svg格式 * 更改导入方式 * 更改样式文件 * 修改自动化测试用例 * 删除多余样式 * 修改文档 * 添加国际化 * 添加定位 * 修改自动化测试用例
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (9)
packages/docs/fluent-editor/docs/demo/flow-chart.md (1)
11-11: Address remaining Chinese wording improvements from previous review.I see that some corrections from the previous review have been applied (e.g., "撤退" → "撤销", removal of duplicated "的的"). However, the following suggested improvements still need to be implemented:
- Line 11: Remove redundant "功能" at the end: "隐藏控制面板功能" → "隐藏控制面板"
- Line 13: Improve phrasing for better grammar and flow:
- "出现在四个角的手柄" → "出现的四个角手柄"
- "右上方的手柄可以旋转" → "右上方手柄可旋转"
- "方向上的手柄可以添加" → "方向上的手柄可添加"
The static analysis tool also flags a grammar issue about missing classifiers (量词) between numerals and nouns on line 13, which the suggested rewording resolves.
Apply this diff to align with the previous review's suggestions:
-1. 通过左边控制面板可以拖拽节点添加到流程图中,也可以改变连线类型。通过右上控制面板可以缩小,放大,适应,全屏/恢复大小,撤销,恢复,隐藏控制面板功能。 +1. 通过左侧控制面板可以拖拽节点添加到流程图中,也可以改变连线类型。通过右上控制面板可以缩小、放大、适应、全屏/恢复大小、撤销、恢复、隐藏控制面板。 -3. 鼠标悬停在节点上出现在四个角的手柄可以调整节点大小,右上方的手柄可以旋转节点,上下左右方向上的手柄可以添加连线。 +3. 鼠标悬停在节点上出现的四个角手柄可以调整节点大小,右上方手柄可旋转节点,上下左右方向上的手柄可添加连线。Also applies to: 13-13
packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts (4)
98-101: KeepselectedNodeson the blot in sync withnode_activeeventsThe icon panel later reads
(blot as any).selectedNodes, but the listener only updates the localselectedNodesvariable. If the selection changes while the panel is open, icon clicks operate on stale nodes. Update the blot alongside the local cache.blot.mindMap.on('node_active', (...args: unknown[]) => { selectedNodes = Array.isArray(args[1]) ? args[1] : [] + ;(blot as any).selectedNodes = selectedNodes })
304-320: Reuse the outside-click handler instead of creating throwaway functionsEach open defines a brand-new
handleOutsideClick; the prematureremoveEventListenercall can’t unhook the previous listener, so handlers pile up until a click finally fires. Store the handler reference (e.g., on the blot) so you detach the same function before re-registering it.- const handleOutsideClick = (e: MouseEvent) => { + const handleOutsideClick = (e: MouseEvent) => { @@ - document.removeEventListener('click', handleOutsideClick) + document.removeEventListener('click', (blot as any).__iconOutsideClick) + ;(blot as any).__iconOutsideClick = null } } - document.removeEventListener('click', handleOutsideClick) - document.addEventListener('click', handleOutsideClick) + if ((blot as any).__iconOutsideClick) { + document.removeEventListener('click', (blot as any).__iconOutsideClick) + } + ;(blot as any).__iconOutsideClick = handleOutsideClick + document.addEventListener('click', handleOutsideClick)
261-285: Stop injecting third-party SVG viainnerHTMLThe icons in
nodeIconListcome from an external package. Dropping the raw SVG into the DOM viainnerHTMLis an XSS sink if the upstream package ever adds scripts or handlers. Parse and sanitize before attaching.+function parseSafeSvg(svgString: string): SVGElement | null { + const doc = new DOMParser().parseFromString(svgString, 'image/svg+xml') + const svg = doc.documentElement as SVGElement + if (!svg || svg.tagName.toLowerCase() !== 'svg') return null + svg.querySelectorAll('script').forEach((n) => n.remove()) + svg.querySelectorAll('*').forEach((el) => { + ;[...el.attributes].forEach((attr) => { + if (attr.name.startsWith('on')) el.removeAttribute(attr.name) + }) + }) + return svg +} @@ - iconItem.innerHTML = icon.icon + const svgEl = parseSafeSvg(icon.icon) + if (svgEl) iconItem.appendChild(svgEl)
445-472: Restore original positioning (top/left/z-index) when exiting fullscreenExiting fullscreen only resets
position/width/heightand hard-setszIndexto'0', leaving any existingtop/left/zIndexoverrides stuck. Persist these properties before entering fullscreen and reinstate them on exit.- const originalPosition = mindMapContainer.getAttribute('data-original-position') - const originalWidth = mindMapContainer.getAttribute('data-original-width') - const originalHeight = mindMapContainer.getAttribute('data-original-height') - if (originalWidth && originalHeight) { - mindMapContainer.style.position = originalPosition - mindMapContainer.style.width = originalWidth - mindMapContainer.style.height = originalHeight - mindMapContainer.style.zIndex = '0' + const originalPosition = mindMapContainer.getAttribute('data-original-position') + const originalWidth = mindMapContainer.getAttribute('data-original-width') + const originalHeight = mindMapContainer.getAttribute('data-original-height') + const originalTop = mindMapContainer.getAttribute('data-original-top') + const originalLeft = mindMapContainer.getAttribute('data-original-left') + const originalZ = mindMapContainer.getAttribute('data-original-zindex') + if (originalWidth && originalHeight) { + mindMapContainer.style.position = originalPosition || '' + mindMapContainer.style.width = originalWidth + mindMapContainer.style.height = originalHeight + mindMapContainer.style.top = originalTop || '' + mindMapContainer.style.left = originalLeft || '' + mindMapContainer.style.zIndex = originalZ || '' @@ - mindMapContainer.setAttribute('data-original-width', mindMapContainer.style.width || '') - mindMapContainer.setAttribute('data-original-height', mindMapContainer.style.height || '') + mindMapContainer.setAttribute('data-original-width', mindMapContainer.style.width || '') + mindMapContainer.setAttribute('data-original-height', mindMapContainer.style.height || '') + mindMapContainer.setAttribute('data-original-top', mindMapContainer.style.top || '') + mindMapContainer.setAttribute('data-original-left', mindMapContainer.style.left || '') + mindMapContainer.setAttribute('data-original-zindex', mindMapContainer.style.zIndex || '')packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts (2)
95-106: Fix undo availability check
historyData.data.undos.length < 0is always false, so undo never enables even when history data exists. Flip to> 0so the control reflects actual availability.- const isUndoAvailable = historyData.data.undoAble || historyData.data.undos.length < 0 + const isUndoAvailable = historyData.data.undoAble || historyData.data.undos.length > 0
113-116: Null-check.lf-dndpanelbefore appending
querySelector('.lf-dndpanel')can return null (e.g., when the left panel is hidden). Calling.appendon null throws; guard the reference before appending.setTimeout(() => { const controlLeftUpPanel = blot.domNode.querySelector('.lf-dndpanel') as HTMLElement | null - controlLeftUpPanel.append(setEdgeTypeBtn) + if (controlLeftUpPanel) { + controlLeftUpPanel.append(setEdgeTypeBtn) + } }, 0)packages/fluent-editor/src/modules/mind-map/modules/custom-resize-action.ts (2)
58-107: Use stable handler references for drag lifecycle
addEventListener(..., this.onDrag.bind(this))/removeEventListener(..., this.onDrag.bind(this))never match, so the listeners stay registered forever. Convert these handlers to class properties (arrow functions) and register/remove the same reference.- box.addEventListener('mousedown', this.onMouseDown.bind(this)) + box.addEventListener('mousedown', this.onMouseDown) @@ - onMouseDown(event: MouseEvent) { + onMouseDown = (event: MouseEvent) => { @@ - document.addEventListener('mousemove', this.onDrag.bind(this)) - document.addEventListener('mouseup', this.onMouseUp.bind(this)) + document.addEventListener('mousemove', this.onDrag) + document.addEventListener('mouseup', this.onMouseUp) } @@ - onDrag(event: MouseEvent) { + onDrag = (event: MouseEvent) => { @@ - onMouseUp() { + onMouseUp = () => { @@ - document.removeEventListener('mousemove', this.onDrag.bind(this)) - document.removeEventListener('mouseup', this.onMouseUp.bind(this)) + document.removeEventListener('mousemove', this.onDrag) + document.removeEventListener('mouseup', this.onMouseUp)Also applies to: 161-164
109-117: Reset panel heights when resizing back above the thresholdOnce height drops below
HEIGHT_THRESHOLD, the panels get a reducedstyle.heightthat never clears, so returning above the threshold leaves them stuck short. Reapply the default (empty string) when the new height exceeds the threshold.- if (iconPanel && newHeight < HEIGHT_THRESHOLD) { - iconPanel.style.height = `${newHeight - PANEL_HEIGHT_OFFSET}px` - } - if (layoutPanel && newHeight < HEIGHT_THRESHOLD) { - layoutPanel.style.height = `${newHeight - PANEL_HEIGHT_OFFSET}px` - } + const computed = Math.max(0, newHeight - PANEL_HEIGHT_OFFSET) + if (iconPanel) { + iconPanel.style.height = newHeight < HEIGHT_THRESHOLD ? `${computed}px` : '' + } + if (layoutPanel) { + layoutPanel.style.height = newHeight < HEIGHT_THRESHOLD ? `${computed}px` : '' + }
🧹 Nitpick comments (2)
packages/fluent-editor/src/modules/mind-map/style/mind-map.scss (2)
197-235: Clarify intent of commented-out icon background declarations.Lines 197–235 contain 14 commented-out CSS rules mapping icon classes to background images. Either remove these if no longer needed, or add a brief comment explaining the intent (e.g., "// TODO: Enable icon backgrounds once image assets are available").
194-195: Justify or remove the!importantflag on.smm-node-edit-wrap.The
!importantdeclaration used here suggests fighting specificity elsewhere in the cascade. Either document why this override is necessary, or refactor to avoid the flag by adjusting selector specificity or HTML/class structure.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (30)
packages/docs/fluent-editor/demos/flow-chart-background.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart-grid.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart-resize.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart.spec.ts(1 hunks)packages/docs/fluent-editor/demos/flow-chart.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-background.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-line.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-resize.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-theme.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map.spec.ts(1 hunks)packages/docs/fluent-editor/demos/mind-map.vue(1 hunks)packages/docs/fluent-editor/docs/demo/flow-chart.md(1 hunks)packages/docs/types/global.d.ts(1 hunks)packages/fluent-editor/src/config/i18n/en-us.ts(1 hunks)packages/fluent-editor/src/config/i18n/zh-cn.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/config-utils.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/formats/flow-chart-blot.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/icons.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/index.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/options.ts(1 hunks)packages/fluent-editor/src/modules/flow-chart/style/flow-chart.scss(1 hunks)packages/fluent-editor/src/modules/mind-map/config-utils.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/formats/mind-map-blot.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/index.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/modules/custom-resize-action.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/options.ts(1 hunks)packages/fluent-editor/src/modules/mind-map/style/mind-map.scss(1 hunks)packages/fluent-editor/vite.config.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
- packages/docs/fluent-editor/demos/mind-map-resize.vue
- packages/docs/fluent-editor/demos/flow-chart.spec.ts
- packages/docs/fluent-editor/demos/flow-chart.vue
- packages/docs/fluent-editor/demos/flow-chart-background.vue
- packages/docs/fluent-editor/demos/mind-map.spec.ts
- packages/fluent-editor/src/modules/flow-chart/index.ts
- packages/fluent-editor/src/modules/flow-chart/style/flow-chart.scss
- packages/fluent-editor/src/modules/mind-map/options.ts
- packages/docs/fluent-editor/demos/mind-map-line.vue
- packages/fluent-editor/src/config/i18n/zh-cn.ts
- packages/docs/fluent-editor/demos/flow-chart-resize.vue
- packages/fluent-editor/src/modules/flow-chart/config-utils.ts
- packages/docs/fluent-editor/demos/flow-chart-grid.vue
🧰 Additional context used
🧬 Code graph analysis (6)
packages/fluent-editor/src/modules/mind-map/config-utils.ts (1)
packages/fluent-editor/src/modules/mind-map/options.ts (2)
BackgroundConfig(1-7)LineConfig(9-14)
packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts (5)
packages/fluent-editor/src/modules/i18n.ts (1)
I18N(10-64)packages/fluent-editor/src/modules/mind-map/i18n/index.ts (1)
registerMindMapI18N(4-9)packages/fluent-editor/src/config/editor.config.ts (1)
CHANGE_LANGUAGE_EVENT(4-4)packages/fluent-editor/src/core/fluent-editor.ts (1)
lang(17-20)packages/fluent-editor/src/modules/mind-map/modules/custom-resize-action.ts (1)
isFullscreen(28-31)
packages/fluent-editor/src/modules/mind-map/formats/mind-map-blot.ts (5)
packages/fluent-editor/src/modules/mind-map/config-utils.ts (1)
getAllConfigs(97-112)packages/fluent-editor/src/modules/mind-map/modules/custom-resize-action.ts (1)
MindMapResizeAction(6-175)packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts (1)
createControlPanel(94-153)packages/fluent-editor/src/modules/mind-map/modules/context-menu.ts (1)
initContextMenu(50-93)packages/fluent-editor/src/modules/mind-map/icons.ts (2)
expandIcon(21-21)contractIcon(20-20)
packages/fluent-editor/src/modules/flow-chart/formats/flow-chart-blot.ts (5)
packages/fluent-editor/src/modules/flow-chart/config-utils.ts (1)
getAllConfigs(96-109)packages/fluent-editor/src/modules/flow-chart/modules/custom-resize-action.ts (1)
FlowChartResizeAction(4-164)packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts (1)
createControlPanel(68-121)packages/fluent-editor/src/modules/flow-chart/modules/context-menu.ts (1)
initContextMenu(54-104)packages/fluent-editor/src/modules/flow-chart/icons.ts (2)
expandIcon(9-9)contractIcon(8-8)
packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts (6)
packages/fluent-editor/src/modules/i18n.ts (1)
I18N(10-64)packages/fluent-editor/src/modules/flow-chart/i18n/index.ts (1)
registerFlowChartI18N(4-9)packages/fluent-editor/src/config/editor.config.ts (1)
CHANGE_LANGUAGE_EVENT(4-4)packages/fluent-editor/src/core/fluent-editor.ts (1)
lang(17-20)packages/fluent-editor/src/modules/flow-chart/icons.ts (11)
backIcon(1-1)forwardIcon(2-2)zoomOutIcon(3-3)zoomInIcon(4-4)fitIcon(5-5)screenTypeIcon(6-6)contractIcon(8-8)lineIcon(12-12)polyLineIcon(10-10)bezierIcon(11-11)screenReduceIcon(7-7)packages/fluent-editor/src/modules/flow-chart/modules/custom-resize-action.ts (1)
isFullscreen(26-29)
packages/fluent-editor/src/modules/mind-map/index.ts (1)
packages/fluent-editor/src/modules/mind-map/options.ts (1)
MindMapOptions(23-29)
🪛 ast-grep (0.39.6)
packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts
[warning] 179-179: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: icon.innerHTML = iconMap[iconClass] || ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 263-263: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconItem.innerHTML = icon.icon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 375-375: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: img.innerHTML = layout.icon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 456-456: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconElement.innerHTML = screenTypeIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 471-471: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconElement.innerHTML = screenReduceIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 179-179: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: icon.innerHTML = iconMap[iconClass] || ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 263-263: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconItem.innerHTML = icon.icon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 375-375: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: img.innerHTML = layout.icon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 456-456: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconElement.innerHTML = screenTypeIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 471-471: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconElement.innerHTML = screenReduceIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
packages/fluent-editor/src/modules/mind-map/formats/mind-map-blot.ts
[warning] 192-192: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconElement.innerHTML = expandIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 204-204: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconElement.innerHTML = contractIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 192-192: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconElement.innerHTML = expandIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 204-204: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconElement.innerHTML = contractIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
packages/fluent-editor/src/modules/flow-chart/formats/flow-chart-blot.ts
[warning] 206-206: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconElement.innerHTML = expandIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 218-218: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconElement.innerHTML = contractIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 206-206: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconElement.innerHTML = expandIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 218-218: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconElement.innerHTML = contractIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts
[warning] 171-171: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: icon.innerHTML = iconMap[iconClass] || ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 230-230: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: edgeIcon.innerHTML = edgeType.icon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 297-297: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconElement.innerHTML = screenTypeIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 312-312: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconElement.innerHTML = screenReduceIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 171-171: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: icon.innerHTML = iconMap[iconClass] || ''
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 230-230: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: edgeIcon.innerHTML = edgeType.icon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 297-297: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconElement.innerHTML = screenTypeIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 312-312: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconElement.innerHTML = screenReduceIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
🪛 LanguageTool
packages/docs/fluent-editor/docs/demo/flow-chart.md
[uncategorized] ~13-~13: 数词与名词之间一般应存在量词,可能缺少量词。
Context: ...标悬停在节点上出现在四个角的手柄可以调整节点大小,右上方的手柄可以旋转节点,上下左右方向上的手柄可以添加连线。 4. 该流程图模块支持配置网格样式,背景样式,调整大小。...
(wa5)
🔇 Additional comments (5)
packages/fluent-editor/vite.config.ts (1)
39-39: Good fix for asset naming!Returning an empty string for asset file names would cause naming collisions and build failures. The updated pattern
'[name][extname]'correctly preserves the original asset name and extension, which is the standard approach for Rollup/Vite configurations. This change likely addresses thepnpm build:libfailure mentioned in the PR objectives.packages/docs/types/global.d.ts (2)
14-17: Window interface extensions declare types that aren't populated at runtime—clarify if this is intentional.The Vue demo files use direct module imports (
import SimpleMindMap from 'simple-mind-map') rather than accessing window globals. No runtime assignments towindow.SimpleMindMap,window.Themes,window.Drag, orwindow.Exportexist in the codebase. The type declarations in lines 14-17 don't correspond to actual runtime behavior in this repository.This may be intentional if these types are meant for external consumers or CDN injection. However, if the code is meant to work within this codebase, either:
- Remove the Window interface extensions, or
- Explicitly populate these properties at runtime
Verify the intended usage pattern.
4-7: These imports align with the library's official documented export paths—no changes needed.The imports on lines 4-7 correctly follow the simple-mind-map library's documented API. According to official documentation, importing from
simple-mind-map/src/plugins/with.jsextensions is the recommended approach for accessing plugins directly. These are stable, officially supported export paths, not internal fragile dependencies.packages/fluent-editor/src/config/i18n/en-us.ts (1)
75-76: LGTM! i18n entries added for both features.Both 'mind-map' and 'flow-chart' i18n entries are now present, resolving the concern from the previous review. The toolbar buttons will now have proper tooltips.
packages/fluent-editor/src/modules/mind-map/formats/mind-map-blot.ts (1)
110-113: Correct dependency injection pattern implemented.The mind-map module correctly receives dependencies (
SimpleMindMap,Themes,Drag,Export) via the options parameter rather than importing them directly. This follows the pattern requested in the PR objectives (point 3) and allows on-demand importing to avoid increasing bundle size.
| if (!disabled) { | ||
| controlItem.addEventListener('click', onClick) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard listener registration when there’s no click handler
panel-status is created without an onClick, so this addEventListener call throws and aborts mounting. Only bind the listener when a handler is supplied.
- if (!disabled) {
- controlItem.addEventListener('click', onClick)
- }
+ if (!disabled && onClick) {
+ controlItem.addEventListener('click', onClick)
+ }Also applies to: 153-178
🤖 Prompt for AI Agents
In packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts around
lines 175-177 (and generally for the block covering 153-178), the code
unconditionally calls controlItem.addEventListener('click', onClick) which
throws when onClick is undefined; update the guard to only register the listener
when a click handler exists and is a function (e.g. if (!disabled && typeof
onClick === 'function') { controlItem.addEventListener('click', onClick); }) and
apply the same conditional guard to the other similar listener registrations in
lines 153-178.
| if (isFullscreen) { | ||
| const originalPosition = flowChartContainer.getAttribute('data-original-position') | ||
| const originalWidth = flowChartContainer.getAttribute('data-original-width') | ||
| const originalHeight = flowChartContainer.getAttribute('data-original-height') | ||
| if (originalWidth && originalHeight) { | ||
| flowChartContainer.style.position = originalPosition | ||
| flowChartContainer.style.width = originalWidth | ||
| flowChartContainer.style.height = originalHeight | ||
| flowChartContainer.style.zIndex = '0' | ||
| } | ||
| const iconElement = screenTypeBtn.querySelector('i') | ||
| if (iconElement) { | ||
| iconElement.innerHTML = screenTypeIcon | ||
| } | ||
| } | ||
| else { | ||
| flowChartContainer.setAttribute('data-original-position', flowChartContainer.style.position || '') | ||
| flowChartContainer.setAttribute('data-original-width', flowChartContainer.style.width || '') | ||
| flowChartContainer.setAttribute('data-original-height', flowChartContainer.style.height || '') | ||
| flowChartContainer.style.position = 'fixed' | ||
| flowChartContainer.style.top = '0' | ||
| flowChartContainer.style.left = '0' | ||
| flowChartContainer.style.width = '100vw' | ||
| flowChartContainer.style.height = '100vh' | ||
| flowChartContainer.style.zIndex = '100' | ||
| const iconElement = screenTypeBtn.querySelector('i') | ||
| if (iconElement) { | ||
| iconElement.innerHTML = screenReduceIcon | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore original positioning when leaving fullscreen
Fullscreen exit currently resets only size and forces zIndex to '0', leaving top/left/zIndex dirty. Persist and restore these styles to avoid layout corruption.
- const originalPosition = flowChartContainer.getAttribute('data-original-position')
- const originalWidth = flowChartContainer.getAttribute('data-original-width')
- const originalHeight = flowChartContainer.getAttribute('data-original-height')
- if (originalWidth && originalHeight) {
- flowChartContainer.style.position = originalPosition
- flowChartContainer.style.width = originalWidth
- flowChartContainer.style.height = originalHeight
- flowChartContainer.style.zIndex = '0'
+ const originalPosition = flowChartContainer.getAttribute('data-original-position')
+ const originalWidth = flowChartContainer.getAttribute('data-original-width')
+ const originalHeight = flowChartContainer.getAttribute('data-original-height')
+ const originalTop = flowChartContainer.getAttribute('data-original-top')
+ const originalLeft = flowChartContainer.getAttribute('data-original-left')
+ const originalZ = flowChartContainer.getAttribute('data-original-zindex')
+ if (originalWidth && originalHeight) {
+ flowChartContainer.style.position = originalPosition || ''
+ flowChartContainer.style.width = originalWidth
+ flowChartContainer.style.height = originalHeight
+ flowChartContainer.style.top = originalTop || ''
+ flowChartContainer.style.left = originalLeft || ''
+ flowChartContainer.style.zIndex = originalZ || ''
@@
- flowChartContainer.setAttribute('data-original-width', flowChartContainer.style.width || '')
- flowChartContainer.setAttribute('data-original-height', flowChartContainer.style.height || '')
+ flowChartContainer.setAttribute('data-original-width', flowChartContainer.style.width || '')
+ flowChartContainer.setAttribute('data-original-height', flowChartContainer.style.height || '')
+ flowChartContainer.setAttribute('data-original-top', flowChartContainer.style.top || '')
+ flowChartContainer.setAttribute('data-original-left', flowChartContainer.style.left || '')
+ flowChartContainer.setAttribute('data-original-zindex', flowChartContainer.style.zIndex || '')🧰 Tools
🪛 ast-grep (0.39.6)
[warning] 297-297: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconElement.innerHTML = screenTypeIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 312-312: Direct HTML content assignment detected. Modifying innerHTML, outerHTML, or using document.write with unsanitized content can lead to XSS vulnerabilities. Use secure alternatives like textContent or sanitize HTML with libraries like DOMPurify.
Context: iconElement.innerHTML = screenReduceIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://www.dhairyashah.dev/posts/why-innerhtml-is-a-bad-idea-and-how-to-avoid-it/
- https://cwe.mitre.org/data/definitions/79.html
(unsafe-html-content-assignment)
[warning] 297-297: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconElement.innerHTML = screenTypeIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
[warning] 312-312: Direct modification of innerHTML or outerHTML properties detected. Modifying these properties with unsanitized user input can lead to XSS vulnerabilities. Use safe alternatives or sanitize content first.
Context: iconElement.innerHTML = screenReduceIcon
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://owasp.org/www-community/xss-filter-evasion-cheatsheet
- https://cwe.mitre.org/data/definitions/79.html
(dom-content-modification)
🤖 Prompt for AI Agents
In packages/fluent-editor/src/modules/flow-chart/modules/control-panel.ts around
lines 286 to 314, exiting fullscreen only restores width/height/position and
forces zIndex to '0' while leaving top/left/zIndex dirty; update the fullscreen
toggle to also save original top, left and zIndex when entering fullscreen (set
data-original-top, data-original-left, data-original-zindex from style or ''),
and when exiting fullscreen restore those attributes back onto
flowChartContainer.style.top, .left and .zIndex (only if the saved values exist)
so the element returns to its exact original layout.
| export interface FlowChartDeps { | ||
| SimpleMindMap: any | ||
| Themes: any | ||
| Drag: any | ||
| Export: any | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix FlowChartDeps interface to match actual usage.
The FlowChartDeps interface declares properties (SimpleMindMap, Themes, Drag, Export) that don't match the dependencies actually destructured in flow-chart-blot.ts line 106: { LogicFlow, DndPanel, SelectionSelect, Snapshot }. This appears to be copy-pasted from MindMapDeps without updating for flow-chart.
Apply this diff to fix the interface:
export interface FlowChartDeps {
- SimpleMindMap: any
- Themes: any
- Drag: any
- Export: any
+ LogicFlow: any
+ DndPanel: any
+ SelectionSelect: any
+ Snapshot: any
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export interface FlowChartDeps { | |
| SimpleMindMap: any | |
| Themes: any | |
| Drag: any | |
| Export: any | |
| } | |
| export interface FlowChartDeps { | |
| LogicFlow: any | |
| DndPanel: any | |
| SelectionSelect: any | |
| Snapshot: any | |
| } |
🤖 Prompt for AI Agents
In packages/fluent-editor/src/modules/flow-chart/options.ts around lines 19 to
24, the FlowChartDeps interface is incorrect (still lists SimpleMindMap, Themes,
Drag, Export); replace it with the actual dependencies used in
flow-chart-blot.ts — declare properties LogicFlow, DndPanel, SelectionSelect,
and Snapshot (use appropriate types or any) and remove the outdated properties
so the interface matches the destructured deps at line 106 of
flow-chart-blot.ts.
| if (!disabled) { | ||
| controlItem.addEventListener('click', onClick) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prevent TypeError when no click handler is provided
createControlItem always registers addEventListener('click', onClick) even when onClick is undefined (e.g., panel-status). This throws immediately and prevents the control panel from mounting. Guard the registration by ensuring a handler exists.
- if (!disabled) {
- controlItem.addEventListener('click', onClick)
- }
+ if (!disabled && onClick) {
+ controlItem.addEventListener('click', onClick)
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!disabled) { | |
| controlItem.addEventListener('click', onClick) | |
| } | |
| if (!disabled && onClick) { | |
| controlItem.addEventListener('click', onClick) | |
| } |
🤖 Prompt for AI Agents
In packages/fluent-editor/src/modules/mind-map/modules/control-panel.ts around
lines 183 to 185, the code always calls controlItem.addEventListener('click',
onClick) even when onClick is undefined, causing a TypeError; update the guard
so the click listener is only registered when a handler exists (e.g., check
typeof onClick === 'function' or if (onClick) before calling addEventListener)
while keeping the existing disabled check.
| .ql-mind-map-control-item:hover { | ||
| background-color: #efefef; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add keyboard focus indicators for interactive elements — critical accessibility issue.
This file lacks :focus-visible states for all interactive controls (.ql-mind-map-control-item, .ql-mind-map-layout-item, .ql-mind-map-icon-item). Only hover states are defined, breaking keyboard navigation and violating WCAG accessibility requirements. Users tabbing through the interface cannot identify the currently focused element.
This was flagged in a previous review but remains unaddressed.
Apply this diff to add focus indicators to interactive elements:
.ql-mind-map-control-item:hover {
background-color: #efefef;
}
+
+.ql-mind-map-control-item:focus-visible {
+ outline: 2px solid #0066cc;
+ outline-offset: 2px;
+}
+
+.ql-mind-map-control-item:not(.disabled):focus-visible {
+ outline: 2px solid #0066cc;
+ outline-offset: 2px;
+}
.ql-mind-map-layout-panel {
display: flex;
flex-wrap: wrap;
gap: 6px;
@include mind-map-panel;
.ql-mind-map-layout-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
border: 1px solid #eee;
border-radius: inherit;
padding: 2px;
width: 100%;
min-width: 100px;
@include hover-shadow-effect;
+
+ &:focus-visible {
+ outline: 2px solid #0066cc;
+ outline-offset: 2px;
+ }
}
}
.ql-mind-map-icon-panel {
@include mind-map-panel;
.ql-mind-map-icon-group-container {
display: flex;
flex-wrap: wrap;
gap: 6px;
.ql-mind-map-icon-item {
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: 1px solid #eee;
border-radius: inherit;
padding: 2px;
@include hover-shadow-effect;
+
+ &:focus-visible {
+ outline: 2px solid #0066cc;
+ outline-offset: 2px;
+ }
}
}
}Also applies to: 117-127, 97-108
🤖 Prompt for AI Agents
In packages/fluent-editor/src/modules/mind-map/style/mind-map.scss around lines
168-170 (and similarly at 117-127 and 97-108), the interactive controls only
define :hover styles and lack keyboard focus-visible states; add :focus-visible
rules for .ql-mind-map-control-item, .ql-mind-map-layout-item, and
.ql-mind-map-icon-item that mirror the hover visual (e.g., background-color:
#efefef) and include a high-contrast visible indicator (outline or box-shadow)
so keyboard users can see focus; use :focus-visible rather than :focus to avoid
showing for mouse interactions and keep specificity consistent with existing
selectors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
♻️ Duplicate comments (1)
packages/fluent-editor/src/modules/mind-map/style/mind-map.scss (1)
168-170: Keyboard focus indicators still missing — critical accessibility issue remains unaddressed.Interactive elements (
.ql-mind-map-control-item,.ql-mind-map-layout-itemat lines 117-127,.ql-mind-map-icon-itemat lines 97-108) still lack:focus-visiblestates. Only:hoverstates are defined, breaking keyboard navigation and violating WCAG accessibility requirements.This issue was flagged in previous reviews but remains unresolved.
Apply this diff to add focus indicators:
.ql-mind-map-control-item:hover { background-color: #efefef; } + +.ql-mind-map-control-item:focus-visible { + outline: 2px solid #0066cc; + outline-offset: 2px; + background-color: #efefef; +} .ql-mind-map-layout-panel { // ... .ql-mind-map-layout-item { // ... @include hover-shadow-effect; + + &:focus-visible { + outline: 2px solid #0066cc; + outline-offset: 2px; + } } } .ql-mind-map-icon-panel { // ... .ql-mind-map-icon-group-container { // ... .ql-mind-map-icon-item { // ... @include hover-shadow-effect; + + &:focus-visible { + outline: 2px solid #0066cc; + outline-offset: 2px; + } } } }
🧹 Nitpick comments (2)
packages/docs/fluent-editor/demos/mind-map-resize.vue (1)
47-48: Consider extracting mind-map seed data for readability.The inline ops definition is over 1000 characters on a single line, making it hard to read and maintain. Consider extracting to a constant or separate file.
Example:
+const INITIAL_MIND_MAP_OPS = [ + { insert: '\n' }, + { + insert: { + 'mind-map': { + layout: 'logicalStructure', + root: { /* ... */ }, + theme: { template: 'default', config: {} }, + view: { /* ... */ } + } + } + }, + { insert: '\n\n' } +] + - const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] - editor.setContents(ops) + editor.setContents(INITIAL_MIND_MAP_OPS)packages/docs/fluent-editor/demos/mind-map.vue (1)
16-48: Consider extracting common mind-map initialization logic.The initialization pattern (dynamic imports, editor setup, content seeding) is duplicated across all five mind-map demo files (mind-map.vue, mind-map-resize.vue, mind-map-line.vue, mind-map-background.vue, mind-map-theme.vue). This creates maintenance burden.
Consider creating a shared composable or helper function:
// composables/useMindMapEditor.ts export function useMindMapEditor(options: MindMapOptions) { const editorRef = ref<HTMLElement>() const editor = ref<FluentEditor>() onMounted(async () => { const [ { default: FluentEditor }, { default: SimpleMindMap }, // ... ] = await Promise.all([/* imports */]) if (!editorRef.value) return editor.value = new FluentEditor(editorRef.value, { theme: 'snow', modules: { toolbar: TOOLBAR_CONFIG, 'mind-map': { deps: { SimpleMindMap, /* ... */ }, ...options } } }) // seed content... }) return { editorRef, editor } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
packages/docs/fluent-editor/demos/flow-chart-background.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart-grid.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart-resize.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-background.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-line.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-resize.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-theme.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map.vue(1 hunks)packages/docs/fluent-editor/docs/demo/mind-map.md(1 hunks)packages/fluent-editor/src/modules/mind-map/style/mind-map.scss(1 hunks)
🧰 Additional context used
🪛 ESLint
packages/docs/fluent-editor/demos/flow-chart.vue
[error] 3-3: 'LogicFlow' is defined but never used.
(unused-imports/no-unused-imports)
[error] 4-4: 'DndPanel' is defined but never used.
(unused-imports/no-unused-imports)
[error] 4-4: 'SelectionSelect' is defined but never used.
(unused-imports/no-unused-imports)
[error] 4-4: 'Snapshot' is defined but never used.
(unused-imports/no-unused-imports)
[error] 21-21: Expected indentation of 2 spaces but found 4.
(style/indent)
packages/docs/fluent-editor/demos/mind-map-background.vue
[error] 24-55: Should have line breaks between items, in node CallExpression
(antfu/consistent-list-newline)
[error] 31-31: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 32-32: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 33-33: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 34-34: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 35-35: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 36-36: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 37-37: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 38-38: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 39-39: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 40-40: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 41-41: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 42-42: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 43-43: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 44-44: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 45-45: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 46-46: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 47-47: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 48-48: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 49-49: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 50-50: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 51-51: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 52-52: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 53-53: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 54-54: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 55-55: Expected indentation of 4 spaces but found 2.
(style/indent)
packages/docs/fluent-editor/demos/flow-chart-background.vue
[error] 24-54: Should have line breaks between items, in node CallExpression
(antfu/consistent-list-newline)
[error] 29-29: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 30-30: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 31-31: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 32-32: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 33-33: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 34-34: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 35-35: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 36-36: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 37-37: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 38-38: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 39-39: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 40-40: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 41-41: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 42-42: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 43-43: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 44-44: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 45-45: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 46-46: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 47-47: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 48-48: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 49-49: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 50-50: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 51-51: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 52-52: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 53-53: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 54-54: Expected indentation of 4 spaces but found 2.
(style/indent)
packages/docs/fluent-editor/demos/flow-chart-grid.vue
[error] 24-55: Should have line breaks between items, in node CallExpression
(antfu/consistent-list-newline)
[error] 29-29: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 30-30: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 31-31: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 32-32: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 33-33: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 34-34: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 35-35: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 36-36: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 37-37: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 38-38: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 39-39: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 40-40: Expected indentation of 16 spaces but found 14.
(style/indent)
[error] 41-41: Expected indentation of 16 spaces but found 14.
(style/indent)
[error] 42-42: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 43-43: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 44-44: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 45-45: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 46-46: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 47-47: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 48-48: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 49-49: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 50-50: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 51-51: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 52-52: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 53-53: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 54-54: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 55-55: Expected indentation of 4 spaces but found 2.
(style/indent)
packages/docs/fluent-editor/demos/flow-chart-resize.vue
[error] 24-47: Should have line breaks between items, in node CallExpression
(antfu/consistent-list-newline)
[error] 29-29: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 30-30: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 31-31: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 32-32: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 33-33: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 34-34: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 35-35: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 36-36: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 37-37: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 38-38: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 39-39: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 40-40: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 41-41: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 42-42: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 43-43: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 44-44: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 45-45: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 46-46: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 47-47: Expected indentation of 4 spaces but found 2.
(style/indent)
packages/docs/fluent-editor/demos/mind-map-line.vue
[error] 24-54: Should have line breaks between items, in node CallExpression
(antfu/consistent-list-newline)
[error] 31-31: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 32-32: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 33-33: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 34-34: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 35-35: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 36-36: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 37-37: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 38-38: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 39-39: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 40-40: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 41-41: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 42-42: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 43-43: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 44-44: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 45-45: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 46-46: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 47-47: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 48-48: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 49-49: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 50-50: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 51-51: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 52-52: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 53-53: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 54-54: Expected indentation of 4 spaces but found 2.
(style/indent)
packages/docs/fluent-editor/demos/mind-map-resize.vue
[error] 24-49: Should have line breaks between items, in node CallExpression
(antfu/consistent-list-newline)
[error] 31-31: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 32-32: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 33-33: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 34-34: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 35-35: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 36-36: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 37-37: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 38-38: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 39-39: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 40-40: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 41-41: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 42-42: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 43-43: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 44-44: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 45-45: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 46-46: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 47-47: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 48-48: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 49-49: Expected indentation of 4 spaces but found 2.
(style/indent)
packages/docs/fluent-editor/demos/mind-map-theme.vue
[error] 24-50: Should have line breaks between items, in node CallExpression
(antfu/consistent-list-newline)
[error] 31-31: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 32-32: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 33-33: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 34-34: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 35-35: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 36-36: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 37-37: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 38-38: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 39-39: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 40-40: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 41-41: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 42-42: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 43-43: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 44-44: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 45-45: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 46-46: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 48-48: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 49-49: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 50-50: Expected indentation of 4 spaces but found 2.
(style/indent)
packages/docs/fluent-editor/demos/mind-map.vue
[error] 24-48: Should have line breaks between items, in node CallExpression
(antfu/consistent-list-newline)
[error] 31-31: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 32-32: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 33-33: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 34-34: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 35-35: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 36-36: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 37-37: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 38-38: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 39-39: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 40-40: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 41-41: Expected indentation of 14 spaces but found 12.
(style/indent)
[error] 42-42: Expected indentation of 12 spaces but found 10.
(style/indent)
[error] 43-43: Expected indentation of 10 spaces but found 8.
(style/indent)
[error] 44-44: Expected indentation of 8 spaces but found 6.
(style/indent)
[error] 45-45: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 46-46: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 47-47: Expected indentation of 6 spaces but found 4.
(style/indent)
[error] 48-48: Expected indentation of 4 spaces but found 2.
(style/indent)
🔇 Additional comments (1)
packages/docs/fluent-editor/docs/demo/mind-map.md (1)
1-86: All verification confirms the documentation is accurate and complete.The demo file references and configuration properties have been verified:
- Demo files: All 5 files exist at
packages/docs/fluent-editor/demos/. Thesrc=demos/path follows the standard convention used throughout the documentation; the build system resolves these paths.- Configuration properties: All tables are verified against
options.tsandconfig-utils.ts. Background, line, resize, and theme configurations match their implementation types exactly.No corrections or updates are needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
♻️ Duplicate comments (1)
packages/docs/fluent-editor/demos/mind-map-resize.vue (1)
24-49: Fix indentation to match project style.This is a duplicate of the previously reported ESLint indentation violations.
🧹 Nitpick comments (2)
packages/docs/fluent-editor/demos/mind-map-resize.vue (2)
5-5: Consider more precise typing for the editor variable.The
editorvariable is declared but not initialized, so its type should reflect that it can beundefineduntil the Promise resolves.Apply this diff for more precise typing:
-let editor: FluentEditor +let editor: FluentEditor | undefined
16-51: Add cleanup for the editor instance.The editor instance is created in
onMountedbut never cleaned up. This can lead to memory leaks or unexpected behavior if the component is unmounted and remounted.Add cleanup using
onBeforeUnmount:import { onBeforeUnmount, onMounted, ref } from 'vue' // ... existing code ... onBeforeUnmount(() => { if (editor) { // Assuming FluentEditor has a destroy or cleanup method // Adjust based on actual API editor = undefined } })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (17)
packages/docs/fluent-editor/.vitepress/sidebar.ts(1 hunks)packages/docs/fluent-editor/demos/flow-chart-background.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart-grid.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart-resize.vue(1 hunks)packages/docs/fluent-editor/demos/flow-chart.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-background.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-line.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-resize.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map-theme.vue(1 hunks)packages/docs/fluent-editor/demos/mind-map.vue(1 hunks)packages/docs/package.json(2 hunks)packages/docs/types/global.d.ts(1 hunks)packages/fluent-editor/package.json(2 hunks)packages/fluent-editor/scripts/pre-release.cjs(1 hunks)packages/fluent-editor/src/modules/index.ts(1 hunks)packages/fluent-editor/vite.config.ts(1 hunks)packages/projects/src/views/yuque/YuQue.vue(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
- packages/docs/fluent-editor/demos/flow-chart-resize.vue
- packages/fluent-editor/vite.config.ts
- packages/fluent-editor/package.json
- packages/docs/fluent-editor/demos/flow-chart.vue
- packages/docs/types/global.d.ts
- packages/fluent-editor/src/modules/index.ts
- packages/docs/fluent-editor/demos/mind-map-line.vue
- packages/docs/fluent-editor/demos/flow-chart-grid.vue
- packages/docs/fluent-editor/demos/mind-map-background.vue
- packages/docs/fluent-editor/demos/flow-chart-background.vue
- packages/projects/src/views/yuque/YuQue.vue
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (2)
packages/docs/fluent-editor/demos/mind-map-resize.vue (2)
8-14: LGTM!The toolbar configuration is clean and correctly includes the mind-map button.
54-56: LGTM!The template is clean and correctly binds the editor container reference.
| Promise.all([ | ||
| import('@opentiny/fluent-editor'), | ||
| import('simple-mind-map'), | ||
| import('simple-mind-map/src/plugins/Drag.js'), | ||
| import('simple-mind-map/src/plugins/Export.js'), | ||
| import('simple-mind-map-plugin-themes'), | ||
| ]).then( | ||
| ([ | ||
| { default: FluentEditor }, | ||
| { default: SimpleMindMap }, | ||
| { default: Drag }, | ||
| { default: Export }, | ||
| { default: Themes }, | ||
| ]) => { | ||
| if (!editorRef.value) return | ||
| editor = new FluentEditor(editorRef.value, { | ||
| theme: 'snow', | ||
| modules: { | ||
| 'toolbar': TOOLBAR_CONFIG, | ||
| 'mind-map': { | ||
| deps: { | ||
| SimpleMindMap, | ||
| Themes, | ||
| Drag, | ||
| Export, | ||
| }, | ||
| resize: true, | ||
| }, | ||
| }, | ||
| }) | ||
| const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] | ||
| editor.setContents(ops) | ||
| }, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for module loading failures.
The Promise.all has no .catch() handler. If any of the dynamic imports fail, the error will be silently swallowed, leaving the user with a non-functional editor and no feedback.
Apply this diff to add error handling:
Promise.all([
import('@opentiny/fluent-editor'),
import('simple-mind-map'),
import('simple-mind-map/src/plugins/Drag.js'),
import('simple-mind-map/src/plugins/Export.js'),
import('simple-mind-map-plugin-themes'),
]).then(
([
{ default: FluentEditor },
{ default: SimpleMindMap },
{ default: Drag },
{ default: Export },
{ default: Themes },
]) => {
if (!editorRef.value) return
editor = new FluentEditor(editorRef.value, {
theme: 'snow',
modules: {
'toolbar': TOOLBAR_CONFIG,
'mind-map': {
deps: {
SimpleMindMap,
Themes,
Drag,
Export,
},
resize: true,
},
},
})
const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }]
editor.setContents(ops)
},
- )
+ ).catch((error) => {
+ console.error('Failed to load mind-map dependencies:', error)
+ })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Promise.all([ | |
| import('@opentiny/fluent-editor'), | |
| import('simple-mind-map'), | |
| import('simple-mind-map/src/plugins/Drag.js'), | |
| import('simple-mind-map/src/plugins/Export.js'), | |
| import('simple-mind-map-plugin-themes'), | |
| ]).then( | |
| ([ | |
| { default: FluentEditor }, | |
| { default: SimpleMindMap }, | |
| { default: Drag }, | |
| { default: Export }, | |
| { default: Themes }, | |
| ]) => { | |
| if (!editorRef.value) return | |
| editor = new FluentEditor(editorRef.value, { | |
| theme: 'snow', | |
| modules: { | |
| 'toolbar': TOOLBAR_CONFIG, | |
| 'mind-map': { | |
| deps: { | |
| SimpleMindMap, | |
| Themes, | |
| Drag, | |
| Export, | |
| }, | |
| resize: true, | |
| }, | |
| }, | |
| }) | |
| const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] | |
| editor.setContents(ops) | |
| }, | |
| ) | |
| Promise.all([ | |
| import('@opentiny/fluent-editor'), | |
| import('simple-mind-map'), | |
| import('simple-mind-map/src/plugins/Drag.js'), | |
| import('simple-mind-map/src/plugins/Export.js'), | |
| import('simple-mind-map-plugin-themes'), | |
| ]).then( | |
| ([ | |
| { default: FluentEditor }, | |
| { default: SimpleMindMap }, | |
| { default: Drag }, | |
| { default: Export }, | |
| { default: Themes }, | |
| ]) => { | |
| if (!editorRef.value) return | |
| editor = new FluentEditor(editorRef.value, { | |
| theme: 'snow', | |
| modules: { | |
| 'toolbar': TOOLBAR_CONFIG, | |
| 'mind-map': { | |
| deps: { | |
| SimpleMindMap, | |
| Themes, | |
| Drag, | |
| Export, | |
| }, | |
| resize: true, | |
| }, | |
| }, | |
| }) | |
| const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] | |
| editor.setContents(ops) | |
| }, | |
| ).catch((error) => { | |
| console.error('Failed to load mind-map dependencies:', error) | |
| }) |
🤖 Prompt for AI Agents
In packages/docs/fluent-editor/demos/mind-map-resize.vue around lines 17 to 50,
the Promise.all dynamic imports lack error handling which will silently swallow
failures; add a .catch handler on the Promise chain to capture any import
errors, log the error (use console.error or the project logger) and surface a
user-friendly message or fallback behavior (e.g., show an error notice or
disable the editor UI) so the failure does not leave the page silently broken;
ensure the .catch runs when imports fail and prevents further initialization
when necessary.
| }, | ||
| }, | ||
| }) | ||
| const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Extract the ops array to improve readability.
Line 47 contains an 800+ character array literal that makes the code extremely difficult to read and maintain. Extract this to a constant or helper function.
Apply this diff to extract the ops array:
+const INITIAL_MIND_MAP_OPS = [
+ { insert: '\n' },
+ {
+ insert: {
+ 'mind-map': {
+ layout: 'logicalStructure',
+ root: {
+ data: {
+ text: '根节点',
+ expand: true,
+ uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a',
+ isActive: false,
+ },
+ children: [
+ {
+ data: {
+ text: '二级节点',
+ uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d',
+ expand: true,
+ richText: false,
+ isActive: false,
+ },
+ children: [],
+ },
+ ],
+ smmVersion: '0.14.0-fix.1',
+ },
+ theme: {
+ template: 'default',
+ config: {},
+ },
+ view: {
+ transform: {
+ scaleX: 1,
+ scaleY: 1,
+ shear: 0,
+ rotate: 0,
+ translateX: 0,
+ translateY: 0,
+ originX: 0,
+ originY: 0,
+ a: 1,
+ b: 0,
+ c: 0,
+ d: 1,
+ e: 0,
+ f: 0,
+ },
+ state: {
+ scale: 1,
+ x: 0,
+ y: 0,
+ sx: 0,
+ sy: 0,
+ },
+ },
+ },
+ },
+ },
+ { insert: '\n\n' },
+]
+
onMounted(() => {
Promise.all([
import('@opentiny/fluent-editor'),
import('simple-mind-map'),
import('simple-mind-map/src/plugins/Drag.js'),
import('simple-mind-map/src/plugins/Export.js'),
import('simple-mind-map-plugin-themes'),
]).then(
([
{ default: FluentEditor },
{ default: SimpleMindMap },
{ default: Drag },
{ default: Export },
{ default: Themes },
]) => {
if (!editorRef.value) return
editor = new FluentEditor(editorRef.value, {
theme: 'snow',
modules: {
'toolbar': TOOLBAR_CONFIG,
'mind-map': {
deps: {
SimpleMindMap,
Themes,
Drag,
Export,
},
resize: true,
},
},
})
- const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }]
- editor.setContents(ops)
+ editor.setContents(INITIAL_MIND_MAP_OPS)
},
)
})Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/docs/fluent-editor/demos/mind-map-resize.vue around line 47 the very
large inline ops array literal makes the file hard to read; extract that array
to a named constant (e.g., DEFAULT_MIND_MAP_OPS) or a helper function placed
near the top of the file (or in a small local module if reused), then replace
the inline literal with a simple reference (const ops = DEFAULT_MIND_MAP_OPS) in
the demo; ensure the constant is clearly named, typed/annotated if relevant, and
keep the original structure/contents intact.
| import { onMounted, ref } from 'vue' | ||
|
|
||
| let editor: FluentEditor | ||
| const editorRef = ref<HTMLElement>() | ||
|
|
||
| const TOOLBAR_CONFIG = [ | ||
| [{ header: [] }], | ||
| ['bold', 'italic', 'underline', 'link'], | ||
| [{ align: [] }, { list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], | ||
| ['clean'], | ||
| ['mind-map'], | ||
| ] | ||
|
|
||
| onMounted(() => { | ||
| Promise.all([ | ||
| import('@opentiny/fluent-editor'), | ||
| import('simple-mind-map'), | ||
| import('simple-mind-map/src/plugins/Drag.js'), | ||
| import('simple-mind-map/src/plugins/Export.js'), | ||
| import('simple-mind-map-plugin-themes'), | ||
| ]).then( | ||
| ([ | ||
| { default: FluentEditor }, | ||
| { default: SimpleMindMap }, | ||
| { default: Drag }, | ||
| { default: Export }, | ||
| { default: Themes }, | ||
| ]) => { | ||
| if (!editorRef.value) return | ||
| editor = new FluentEditor(editorRef.value, { | ||
| theme: 'snow', | ||
| modules: { | ||
| 'toolbar': TOOLBAR_CONFIG, | ||
| 'mind-map': { | ||
| deps: { | ||
| SimpleMindMap, | ||
| Themes, | ||
| Drag, | ||
| Export, | ||
| }, | ||
| theme: 'minions', | ||
| }, | ||
| }, | ||
| }) | ||
|
|
||
| const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'minions', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] | ||
| editor.setContents(ops) | ||
| }, | ||
| ) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Destroy the FluentEditor instance during component teardown.
Same lifecycle gap here: the editor is created on mount but never released, so every visit leaves listeners hanging around. Please mirror the fix from the base demo—import onBeforeUnmount and call editor?.destroy() there.
-import { onMounted, ref } from 'vue'
+import { onMounted, onBeforeUnmount, ref } from 'vue'
@@
)
})
+
+onBeforeUnmount(() => {
+ editor?.destroy()
+})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { onMounted, ref } from 'vue' | |
| let editor: FluentEditor | |
| const editorRef = ref<HTMLElement>() | |
| const TOOLBAR_CONFIG = [ | |
| [{ header: [] }], | |
| ['bold', 'italic', 'underline', 'link'], | |
| [{ align: [] }, { list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], | |
| ['clean'], | |
| ['mind-map'], | |
| ] | |
| onMounted(() => { | |
| Promise.all([ | |
| import('@opentiny/fluent-editor'), | |
| import('simple-mind-map'), | |
| import('simple-mind-map/src/plugins/Drag.js'), | |
| import('simple-mind-map/src/plugins/Export.js'), | |
| import('simple-mind-map-plugin-themes'), | |
| ]).then( | |
| ([ | |
| { default: FluentEditor }, | |
| { default: SimpleMindMap }, | |
| { default: Drag }, | |
| { default: Export }, | |
| { default: Themes }, | |
| ]) => { | |
| if (!editorRef.value) return | |
| editor = new FluentEditor(editorRef.value, { | |
| theme: 'snow', | |
| modules: { | |
| 'toolbar': TOOLBAR_CONFIG, | |
| 'mind-map': { | |
| deps: { | |
| SimpleMindMap, | |
| Themes, | |
| Drag, | |
| Export, | |
| }, | |
| theme: 'minions', | |
| }, | |
| }, | |
| }) | |
| const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'minions', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] | |
| editor.setContents(ops) | |
| }, | |
| ) | |
| }) | |
| import { onMounted, onBeforeUnmount, ref } from 'vue' | |
| let editor: FluentEditor | |
| const editorRef = ref<HTMLElement>() | |
| const TOOLBAR_CONFIG = [ | |
| [{ header: [] }], | |
| ['bold', 'italic', 'underline', 'link'], | |
| [{ align: [] }, { list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], | |
| ['clean'], | |
| ['mind-map'], | |
| ] | |
| onMounted(() => { | |
| Promise.all([ | |
| import('@opentiny/fluent-editor'), | |
| import('simple-mind-map'), | |
| import('simple-mind-map/src/plugins/Drag.js'), | |
| import('simple-mind-map/src/plugins/Export.js'), | |
| import('simple-mind-map-plugin-themes'), | |
| ]).then( | |
| ([ | |
| { default: FluentEditor }, | |
| { default: SimpleMindMap }, | |
| { default: Drag }, | |
| { default: Export }, | |
| { default: Themes }, | |
| ]) => { | |
| if (!editorRef.value) return | |
| editor = new FluentEditor(editorRef.value, { | |
| theme: 'snow', | |
| modules: { | |
| 'toolbar': TOOLBAR_CONFIG, | |
| 'mind-map': { | |
| deps: { | |
| SimpleMindMap, | |
| Themes, | |
| Drag, | |
| Export, | |
| }, | |
| theme: 'minions', | |
| }, | |
| }, | |
| }) | |
| const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'minions', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] | |
| editor.setContents(ops) | |
| }, | |
| ) | |
| }) | |
| onBeforeUnmount(() => { | |
| editor?.destroy() | |
| }) |
🤖 Prompt for AI Agents
In packages/docs/fluent-editor/demos/mind-map-theme.vue around lines 3 to 52,
the FluentEditor instance is created on mount but never destroyed; import
onBeforeUnmount from 'vue' and add an onBeforeUnmount handler that calls
editor?.destroy() (and optionally clears editorRef.value) to remove listeners
and free resources when the component is torn down.
| import { onMounted, ref } from 'vue' | ||
|
|
||
| let editor: FluentEditor | ||
| const editorRef = ref<HTMLElement>() | ||
|
|
||
| const TOOLBAR_CONFIG = [ | ||
| [{ header: [] }], | ||
| ['bold', 'italic', 'underline', 'link'], | ||
| [{ align: [] }, { list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], | ||
| ['clean'], | ||
| ['mind-map'], | ||
| ] | ||
|
|
||
| onMounted(() => { | ||
| Promise.all([ | ||
| import('@opentiny/fluent-editor'), | ||
| import('simple-mind-map'), | ||
| import('simple-mind-map/src/plugins/Drag.js'), | ||
| import('simple-mind-map/src/plugins/Export.js'), | ||
| import('simple-mind-map-plugin-themes'), | ||
| ]).then( | ||
| ([ | ||
| { default: FluentEditor }, | ||
| { default: SimpleMindMap }, | ||
| { default: Drag }, | ||
| { default: Export }, | ||
| { default: Themes }, | ||
| ]) => { | ||
| if (!editorRef.value) return | ||
| editor = new FluentEditor(editorRef.value, { | ||
| theme: 'snow', | ||
| modules: { | ||
| 'toolbar': TOOLBAR_CONFIG, | ||
| 'mind-map': { | ||
| deps: { | ||
| SimpleMindMap, | ||
| Themes, | ||
| Drag, | ||
| Export, | ||
| }, | ||
| }, | ||
| }, | ||
| }) | ||
| const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] | ||
| editor.setContents(ops) | ||
| }, | ||
| ) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure FluentEditor is destroyed on unmount.
The demo instantiates FluentEditor during onMounted but never disposes it. When the user navigates away (VitePress tears down the component), the instance keeps its window/document listeners alive, so returning to the page stacks duplicate handlers and leaks memory. Add an onBeforeUnmount hook that calls editor?.destroy() and include the import.
-import { onMounted, ref } from 'vue'
+import { onMounted, onBeforeUnmount, ref } from 'vue'
@@
onMounted(() => {
Promise.all([
@@
)
})
+
+onBeforeUnmount(() => {
+ editor?.destroy()
+})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { onMounted, ref } from 'vue' | |
| let editor: FluentEditor | |
| const editorRef = ref<HTMLElement>() | |
| const TOOLBAR_CONFIG = [ | |
| [{ header: [] }], | |
| ['bold', 'italic', 'underline', 'link'], | |
| [{ align: [] }, { list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], | |
| ['clean'], | |
| ['mind-map'], | |
| ] | |
| onMounted(() => { | |
| Promise.all([ | |
| import('@opentiny/fluent-editor'), | |
| import('simple-mind-map'), | |
| import('simple-mind-map/src/plugins/Drag.js'), | |
| import('simple-mind-map/src/plugins/Export.js'), | |
| import('simple-mind-map-plugin-themes'), | |
| ]).then( | |
| ([ | |
| { default: FluentEditor }, | |
| { default: SimpleMindMap }, | |
| { default: Drag }, | |
| { default: Export }, | |
| { default: Themes }, | |
| ]) => { | |
| if (!editorRef.value) return | |
| editor = new FluentEditor(editorRef.value, { | |
| theme: 'snow', | |
| modules: { | |
| 'toolbar': TOOLBAR_CONFIG, | |
| 'mind-map': { | |
| deps: { | |
| SimpleMindMap, | |
| Themes, | |
| Drag, | |
| Export, | |
| }, | |
| }, | |
| }, | |
| }) | |
| const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] | |
| editor.setContents(ops) | |
| }, | |
| ) | |
| }) | |
| import { onMounted, onBeforeUnmount, ref } from 'vue' | |
| let editor: FluentEditor | |
| const editorRef = ref<HTMLElement>() | |
| const TOOLBAR_CONFIG = [ | |
| [{ header: [] }], | |
| ['bold', 'italic', 'underline', 'link'], | |
| [{ align: [] }, { list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], | |
| ['clean'], | |
| ['mind-map'], | |
| ] | |
| onMounted(() => { | |
| Promise.all([ | |
| import('@opentiny/fluent-editor'), | |
| import('simple-mind-map'), | |
| import('simple-mind-map/src/plugins/Drag.js'), | |
| import('simple-mind-map/src/plugins/Export.js'), | |
| import('simple-mind-map-plugin-themes'), | |
| ]).then( | |
| ([ | |
| { default: FluentEditor }, | |
| { default: SimpleMindMap }, | |
| { default: Drag }, | |
| { default: Export }, | |
| { default: Themes }, | |
| ]) => { | |
| if (!editorRef.value) return | |
| editor = new FluentEditor(editorRef.value, { | |
| theme: 'snow', | |
| modules: { | |
| 'toolbar': TOOLBAR_CONFIG, | |
| 'mind-map': { | |
| deps: { | |
| SimpleMindMap, | |
| Themes, | |
| Drag, | |
| Export, | |
| }, | |
| }, | |
| }, | |
| }) | |
| const ops = [{ insert: '\n' }, { insert: { 'mind-map': { layout: 'logicalStructure', root: { data: { text: '根节点', expand: true, uid: '36bae545-da0b-4c08-be14-ff05f7f05d0a', isActive: false }, children: [{ data: { text: '二级节点', uid: 'ef0895d2-b5cc-4214-b0ee-e29f8f02420d', expand: true, richText: false, isActive: false }, children: [] }], smmVersion: '0.14.0-fix.1' }, theme: { template: 'default', config: {} }, view: { transform: { scaleX: 1, scaleY: 1, shear: 0, rotate: 0, translateX: 0, translateY: 0, originX: 0, originY: 0, a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 }, state: { scale: 1, x: 0, y: 0, sx: 0, sy: 0 } } } } }, { insert: '\n\n' }] | |
| editor.setContents(ops) | |
| }, | |
| ) | |
| }) | |
| onBeforeUnmount(() => { | |
| editor?.destroy() | |
| }) |
🤖 Prompt for AI Agents
In packages/docs/fluent-editor/demos/mind-map.vue around lines 3 to 50, the
FluentEditor instance created in onMounted is never disposed which leaks event
listeners; import onBeforeUnmount from 'vue' and add an onBeforeUnmount hook
that calls editor?.destroy() (guarded for undefined) to clean up the editor when
the component unmounts.
PR
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information
Summary by CodeRabbit
New Features
Documentation
Tests
Chores