瀏覽代碼

fix-mindmap-outline-update-interval (#2641)

chendapao 2 月之前
父節點
當前提交
6fd3703c45
共有 1 個文件被更改,包括 164 次插入8 次删除
  1. 164 8
      src/data/extra/web/js/mindmap/features/outline/outline.js

+ 164 - 8
src/data/extra/web/js/mindmap/features/outline/outline.js

@@ -16,6 +16,13 @@ class OutlineFeature {
         this.lastSize = null; // 记录最后的大小
         this.COLLAPSE_THRESHOLD = 750; // 思维导图尺寸小于这个值时自动折叠
         this.titleBarHeight = 45; // 标题栏高度
+        
+        // 添加防抖和监听器管理
+        this.updateTimer = null;
+        this.mutationObserver = null;
+        this.isUpdating = false;
+        this.lastUpdateTime = 0;
+        this.UPDATE_DEBOUNCE_DELAY = 300; // 防抖延迟300ms
     }
 
     /**
@@ -34,6 +41,9 @@ class OutlineFeature {
      */
     init() {
         console.log('OutlineFeature: init called');
+        // 先清理之前的实例(如果有的话)
+        this.cleanupObservers();
+        
         // 先检查并删除已存在的大纲窗口
         const existingWindow = document.getElementById('vx-outline-window');
         if (existingWindow) {
@@ -581,13 +591,44 @@ class OutlineFeature {
     }
 
     /**
-     * 更新大纲窗口内容
+     * 更新大纲窗口内容(带防抖)
      * 步骤:
      * 1. 清空现有内容
      * 2. 获取根节点数据
      * 3. 递归渲染节点结构
      */
     updateOutlineWindow() {
+        // 防抖处理 - 清除之前的定时器
+        if (this.updateTimer) {
+            clearTimeout(this.updateTimer);
+        }
+
+        // 如果正在更新,直接返回
+        if (this.isUpdating) {
+            return;
+        }
+
+        // 检查更新频率限制
+        const now = Date.now();
+        const timeSinceLastUpdate = now - this.lastUpdateTime;
+        
+        if (timeSinceLastUpdate < 500) { // 500ms内不重复更新
+            this.updateTimer = setTimeout(() => {
+                this.doUpdateOutlineWindow();
+            }, this.UPDATE_DEBOUNCE_DELAY);
+            return;
+        }
+
+        // 设置延迟更新
+        this.updateTimer = setTimeout(() => {
+            this.doUpdateOutlineWindow();
+        }, 50); // 短延迟确保DOM更新完成
+    }
+
+    /**
+     * 实际执行大纲窗口更新
+     */
+    doUpdateOutlineWindow() {
         if (!this.outlineWindow) {
             console.warn('OutlineFeature: outlineWindow not found');
             return;
@@ -600,6 +641,9 @@ class OutlineFeature {
         }
 
         try {
+            this.isUpdating = true;
+            this.lastUpdateTime = Date.now();
+
             // 获取MindElixir数据
             const allData = this.core.mindElixir && this.core.mindElixir.getAllData();
             
@@ -614,6 +658,13 @@ class OutlineFeature {
         } catch (error) {
             console.error('OutlineFeature: Error updating outline window:', error);
             content.innerHTML = '<div style="color: #e74c3c; text-align: center; padding: 20px;">数据加载失败</div>';
+        } finally {
+            this.isUpdating = false;
+            // 清除定时器
+            if (this.updateTimer) {
+                clearTimeout(this.updateTimer);
+                this.updateTimer = null;
+            }
         }
     }
 
@@ -948,15 +999,95 @@ class OutlineFeature {
      * 监听思维导图变化并更新大纲
      */
     setupDOMObserver() {
-        const observer = new MutationObserver(() => {
-            this.updateOutlineWindow();
-        });
+        // 清理之前的监听器
+        this.cleanupObservers();
+
+        // 监听 MindElixir 的 operation 事件
+        this.core.mindElixir.bus.addListener('operation', (operation) => {
+            console.log('OutlineFeature: Operation detected:', operation.name);
+            
+            // 根据操作类型决定是否需要更新大纲
+            const outlineUpdateOperations = [
+                'addChild',      // 添加子节点
+                'removeNode',    // 删除节点
+                'moveNode',      // 移动节点
+                'finishEdit'     // 完成编辑(文本内容变化)
+            ];
+
+            const immediateUpdateOperations = [
+                'addChild',
+                'removeNode', 
+                'moveNode'
+            ];
+
+            const delayedUpdateOperations = [
+                'finishEdit'     // 编辑完成时再更新,避免输入过程中频繁更新
+            ];
+
+            if (immediateUpdateOperations.includes(operation.name)) {
+                // 立即更新(有50ms防抖)
+                this.updateOutlineWindow();
+            } else if (delayedUpdateOperations.includes(operation.name)) {
+                // 延迟更新,给更多时间让用户完成编辑
+                if (this.updateTimer) {
+                    clearTimeout(this.updateTimer);
+                }
+                this.updateTimer = setTimeout(() => {
+                    this.doUpdateOutlineWindow();
+                }, this.UPDATE_DEBOUNCE_DELAY);
+            }
 
-        observer.observe(document.getElementById('vx-mindmap'), {
-            childList: true,
-            subtree: true,
-            characterData: true
+            // 不再监听 editStyle, editTags, editIcons 等,这些不影响大纲结构
         });
+
+        // 创建单一的 MutationObserver 作为备用监听器
+        // 只监听结构性变化,不监听文本内容变化
+        const mindmapElement = document.getElementById('vx-mindmap');
+        if (mindmapElement) {
+            this.mutationObserver = new MutationObserver((mutations) => {
+                let needsUpdate = false;
+                
+                mutations.forEach((mutation) => {
+                    // 只关注子节点的添加/删除,忽略文本内容变化
+                    if (mutation.type === 'childList' && 
+                        (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)) {
+                        needsUpdate = true;
+                    }
+                });
+
+                if (needsUpdate) {
+                    console.log('OutlineFeature: DOM structure change detected via MutationObserver');
+                    this.updateOutlineWindow();
+                }
+            });
+
+            // 只监听子节点变化,不监听characterData
+            this.mutationObserver.observe(mindmapElement, {
+                childList: true,
+                subtree: true
+                // 不包含 characterData: true,避免文本编辑时的频繁触发
+            });
+        }
+    }
+
+    /**
+     * 清理所有观察器和监听器
+     */
+    cleanupObservers() {
+        // 清理定时器
+        if (this.updateTimer) {
+            clearTimeout(this.updateTimer);
+            this.updateTimer = null;
+        }
+
+        // 清理 MutationObserver
+        if (this.mutationObserver) {
+            this.mutationObserver.disconnect();
+            this.mutationObserver = null;
+        }
+
+        // 重置状态
+        this.isUpdating = false;
     }
 
     /**
@@ -999,4 +1130,29 @@ class OutlineFeature {
             });
         }
     }
+
+    /**
+     * 销毁大纲功能,清理所有资源
+     */
+    destroy() {
+        console.log('OutlineFeature: destroy called');
+        
+        // 清理所有观察器和监听器
+        this.cleanupObservers();
+        
+        // 移除大纲窗口
+        if (this.outlineWindow && this.outlineWindow.parentNode) {
+            this.outlineWindow.parentNode.removeChild(this.outlineWindow);
+        }
+        
+        // 清理所有引用
+        this.outlineWindow = null;
+        this.core = null;
+        this.nodeDataMap.clear();
+        this.collapsedNodes = null;
+        this.lastPosition = null;
+        this.lastSize = null;
+        
+        console.log('OutlineFeature: destroyed successfully');
+    }
 }