Quellcode durchsuchen

fix: Optimize the re-render node when the selection changes

qinluhe vor 2 Jahren
Ursprung
Commit
a38c213744

+ 20 - 1
frontend/appflowy_tauri/src/appflowy_app/block_editor/blocks/text_block/index.ts

@@ -2,10 +2,11 @@ import { BaseEditor, BaseSelection, Descendant } from "slate";
 import { TreeNode } from '$app/block_editor/view/tree_node';
 import { Operation } from "$app/block_editor/core/operation";
 import { TextBlockSelectionManager } from './text_selection';
+import { BlockType } from "@/appflowy_app/interfaces";
 
 export class TextBlockManager {
   public selectionManager: TextBlockSelectionManager;
-  constructor(private operation: Operation) {
+  constructor(private rootId: string, private operation: Operation) {
     this.selectionManager = new TextBlockSelectionManager();
   }
 
@@ -18,6 +19,24 @@ export class TextBlockManager {
     this.operation.updateNode(node.id, path, data);
   }
 
+  deleteNode(node: TreeNode) {
+    if (node.type !== BlockType.TextBlock) {
+      this.operation.updateNode(node.id, ['type'], BlockType.TextBlock);
+      return;
+    }
+    if (node.parent!.id !== this.rootId) {
+      const newParent = node.parent!.parent!;
+      const newPrev = node.parent;
+      this.operation.moveNode(node.id, newParent.id, newPrev?.id || '');
+    }
+    if (!node.prevLine) return;
+    this.operation.updateNode(node.prevLine.id, ['data', 'content'], [
+      ...node.prevLine.data.content,
+      ...node.data.content,
+    ]);
+    this.operation.deleteNode(node.id);
+  }
+
   splitNode(node: TreeNode, editor: BaseEditor) {
     const focus = editor.selection?.focus;
     const path = focus?.path || [0, editor.children.length - 1];

+ 14 - 0
frontend/appflowy_tauri/src/appflowy_app/block_editor/core/operation.ts

@@ -5,6 +5,7 @@ import { Block } from './block';
 
 export class Operation {
   private sync: BlockEditorSync;
+
   constructor(private blockChain: BlockChain) {
     this.sync = new BlockEditorSync();
   }
@@ -66,6 +67,19 @@ export class Operation {
     });
     this.sync.sendOps([op]);
   }
+
+  moveNode(blockId: string, newParentId: string, newPrevId: string) {
+    const op = this.getMoveOp(blockId, newParentId, newPrevId);
+    this.blockChain.move(blockId, newParentId, newPrevId);
+    this.sync.sendOps([op]);
+  }
+
+  deleteNode(blockId: string) {
+    const op = this.getRemoveOp(blockId);
+    this.blockChain.remove(blockId);
+    this.sync.sendOps([op]);
+  }
+
   private getUpdateNodeOp<T>(blockId: string, path: string[], value: T): {
     type: 'update',
     data: UpdateOpData

+ 16 - 16
frontend/appflowy_tauri/src/appflowy_app/block_editor/view/block_position.ts

@@ -3,30 +3,31 @@ export class BlockPositionManager {
   private regionGrid: RegionGrid;
   private viewportBlocks: Set<string> = new Set();
   private blockPositions: Map<string, BlockPosition> = new Map();
-  private observer: IntersectionObserver;
   private container: HTMLDivElement | null = null;
 
   constructor(container: HTMLDivElement) {
     this.container = container;
     this.regionGrid = new RegionGrid(container.offsetHeight);
-    this.observer = new IntersectionObserver((entries) => {
-      for (const entry of entries) {
-        const blockId = entry.target.getAttribute('data-block-id');
-        if (!blockId) return;
-        if (entry.isIntersecting) {
-          this.updateBlockPosition(blockId);
-          this.viewportBlocks.add(blockId);
-        } else {
-          this.viewportBlocks.delete(blockId);
-        }
-      }
-    }, { root: container });
+    
+  }
+
+  isInViewport(nodeId: string) {
+    return this.viewportBlocks.has(nodeId);
   }
 
   observeBlock(node: HTMLDivElement) {
-    this.observer.observe(node);
+    const blockId = node.getAttribute('data-block-id');
+    if (blockId) {
+      this.updateBlockPosition(blockId);
+      this.viewportBlocks.add(blockId);
+    }
+    
     return {
-      unobserve: () => this.observer.unobserve(node),
+      unobserve: () => {
+        if (blockId) {
+          this.viewportBlocks.delete(blockId);
+        }
+      },
     }
   }
 
@@ -67,7 +68,6 @@ export class BlockPositionManager {
 
   destroy() {
     this.container = null;
-    this.observer.disconnect();
   }
 
 }

+ 17 - 13
frontend/appflowy_tauri/src/appflowy_app/block_editor/view/tree.ts

@@ -133,21 +133,25 @@ export class RenderTree {
   updateSelections(selections: string[]) {
     const newSelections = filterSelections<TreeNode>(selections, this.map);
 
-    let isDiff = false;
-    if (newSelections.length !== this.selections.size) {
-      isDiff = true;
-    }
-
     const selectedBlocksSet = new Set(newSelections);
-    if (Array.from(this.selections).some((id) => !selectedBlocksSet.has(id))) {
-      isDiff = true;
-    }
 
-    if (isDiff) {
-      const shouldUpdateIds = new Set([...this.selections, ...newSelections]);
-      this.selections = selectedBlocksSet;
-      shouldUpdateIds.forEach((id) => this.forceUpdate(id));
-    }
+    const updateNotSelected: string[] = [];
+    const updateSelected: string[] = [];
+    Array.from(this.selections).forEach((id) => {
+      if (!selectedBlocksSet.has(id)) {
+        updateNotSelected.push(id);
+      }
+    });
+    newSelections.forEach(id => {
+      if (!this.selections.has(id)) {
+        updateSelected.push(id);
+      }
+    });
+
+    this.selections = selectedBlocksSet;
+    [...updateNotSelected, ...updateSelected].forEach((id) => {
+      this.forceUpdate(id);
+    });
   }
 
   isSelected(nodeId: string) {

+ 18 - 0
frontend/appflowy_tauri/src/appflowy_app/block_editor/view/tree_node.ts

@@ -52,6 +52,24 @@ export class TreeNode {
     this.children.push(node);
   }
 
+  get lastChild() {
+    return this.children[this.children.length - 1];
+  }
+
+  get prevLine(): TreeNode | null {
+    if (!this.parent) return null;
+    const index = this.parent?.children.findIndex(item => item.id === this.id);
+    if (index === 0) {
+      return this.parent;
+    }
+    const prev = this.parent.children[index - 1];
+    let line = prev;
+    while(line.lastChild) {
+      line = line.lastChild;
+    }
+    return line;
+  }
+
   get block() {
     return this._block;
   }

+ 1 - 2
frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/BlockList.hooks.tsx

@@ -21,7 +21,6 @@ export function useBlockList({ blockId, blockEditor }: BlockListProps) {
   const rowVirtualizer = useVirtualizer({
     count: root?.children.length || 0,
     getScrollElement: () => parentRef.current,
-    overscan: 5,
     estimateSize: () => {
       return defaultSize;
     },
@@ -71,7 +70,7 @@ export function ErrorBoundaryFallbackComponent({ error, resetErrorBoundary }: Fa
 
 export function withTextBlockManager(Component: (props: BlockListProps) => React.ReactElement) {
   return (props: BlockListProps) => {
-    const textBlockManager = new TextBlockManager(props.blockEditor.operation);
+    const textBlockManager = new TextBlockManager(props.blockId, props.blockEditor.operation);
 
     useEffect(() => {
       return () => {

+ 3 - 0
frontend/appflowy_tauri/src/appflowy_app/components/block/TextBlock/index.hooks.ts

@@ -49,6 +49,9 @@ export function useTextBlock({
 
         return;
       }
+      case 'Backspace': {
+        console.log(editor.selection)
+      }
     }
 
     triggerHotkey(event, editor);