diff --git a/frontend/appflowy_tauri/src/appflowy_app/block_editor/blocks/text_block/index.ts b/frontend/appflowy_tauri/src/appflowy_app/block_editor/blocks/text_block/index.ts index de42c3c373..29e468c78b 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/block_editor/blocks/text_block/index.ts +++ b/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]; diff --git a/frontend/appflowy_tauri/src/appflowy_app/block_editor/core/operation.ts b/frontend/appflowy_tauri/src/appflowy_app/block_editor/core/operation.ts index 38f3a3fb76..1755e05f8a 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/block_editor/core/operation.ts +++ b/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(blockId: string, path: string[], value: T): { type: 'update', data: UpdateOpData diff --git a/frontend/appflowy_tauri/src/appflowy_app/block_editor/view/block_position.ts b/frontend/appflowy_tauri/src/appflowy_app/block_editor/view/block_position.ts index a2841d8a3b..883c711132 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/block_editor/view/block_position.ts +++ b/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 = new Set(); private blockPositions: Map = 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(); } } \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/block_editor/view/tree.ts b/frontend/appflowy_tauri/src/appflowy_app/block_editor/view/tree.ts index 4eb136ff09..f416bc80ae 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/block_editor/view/tree.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/block_editor/view/tree.ts @@ -133,21 +133,25 @@ export class RenderTree { updateSelections(selections: string[]) { const newSelections = filterSelections(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) { diff --git a/frontend/appflowy_tauri/src/appflowy_app/block_editor/view/tree_node.ts b/frontend/appflowy_tauri/src/appflowy_app/block_editor/view/tree_node.ts index 9ed78bd4b4..19120e08c1 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/block_editor/view/tree_node.ts +++ b/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; } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/BlockList.hooks.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/BlockList.hooks.tsx index 0d673a47e8..e55bb3f7d7 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/block/BlockList/BlockList.hooks.tsx +++ b/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 () => { diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/block/TextBlock/index.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/block/TextBlock/index.hooks.ts index a776ae8be4..59ac3c934d 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/block/TextBlock/index.hooks.ts +++ b/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);