feat: the feature of delete block

This commit is contained in:
qinluhe 2023-03-23 16:13:03 +08:00
parent a38c213744
commit c6c97f7c83
5 changed files with 84 additions and 28 deletions

View File

@ -1,15 +1,26 @@
import { BaseEditor, BaseSelection, Descendant } from "slate"; import { BaseEditor, BaseSelection, Descendant, Editor, Transforms } from "slate";
import { TreeNode } from '$app/block_editor/view/tree_node'; import { TreeNode } from '$app/block_editor/view/tree_node';
import { Operation } from "$app/block_editor/core/operation"; import { Operation } from "$app/block_editor/core/operation";
import { TextBlockSelectionManager } from './text_selection'; import { TextBlockSelectionManager } from './text_selection';
import { BlockType } from "@/appflowy_app/interfaces"; import { BlockType } from "@/appflowy_app/interfaces";
import { ReactEditor } from "slate-react";
export class TextBlockManager { export class TextBlockManager {
public selectionManager: TextBlockSelectionManager; public selectionManager: TextBlockSelectionManager;
private editorMap: Map<string, BaseEditor & ReactEditor> = new Map();
constructor(private rootId: string, private operation: Operation) { constructor(private rootId: string, private operation: Operation) {
this.selectionManager = new TextBlockSelectionManager(); this.selectionManager = new TextBlockSelectionManager();
} }
register(id: string, editor: BaseEditor & ReactEditor) {
this.editorMap.set(id, editor);
}
unregister(id: string) {
this.editorMap.delete(id);
}
setSelection(node: TreeNode, selection: BaseSelection) { setSelection(node: TreeNode, selection: BaseSelection) {
// console.log(node.id, selection); // console.log(node.id, selection);
this.selectionManager.setSelection(node.id, selection) this.selectionManager.setSelection(node.id, selection)
@ -22,19 +33,41 @@ export class TextBlockManager {
deleteNode(node: TreeNode) { deleteNode(node: TreeNode) {
if (node.type !== BlockType.TextBlock) { if (node.type !== BlockType.TextBlock) {
this.operation.updateNode(node.id, ['type'], BlockType.TextBlock); this.operation.updateNode(node.id, ['type'], BlockType.TextBlock);
this.operation.updateNode(node.id, ['data'], { content: node.data.content });
return; return;
} }
if (node.parent!.id !== this.rootId) {
if (!node.block.next && node.parent!.id !== this.rootId) {
const newParent = node.parent!.parent!; const newParent = node.parent!.parent!;
const newPrev = node.parent; const newPrev = node.parent;
this.operation.moveNode(node.id, newParent.id, newPrev?.id || ''); this.operation.moveNode(node.id, newParent.id, newPrev?.id || '');
return;
} }
if (!node.prevLine) return; if (!node.prevLine) return;
const retainData = node.prevLine.data.content;
const editor = this.editorMap.get(node.prevLine.id);
if (editor) {
const index = retainData.length - 1;
const anchor = {
path: [0, index],
offset: retainData[index].text.length,
};
const selection = {
anchor,
focus: {...anchor}
};
ReactEditor.focus(editor);
Transforms.select(editor, selection);
}
this.operation.updateNode(node.prevLine.id, ['data', 'content'], [ this.operation.updateNode(node.prevLine.id, ['data', 'content'], [
...node.prevLine.data.content, ...retainData,
...node.data.content, ...node.data.content,
]); ]);
this.operation.deleteNode(node.id); this.operation.deleteNode(node.id);
} }
splitNode(node: TreeNode, editor: BaseEditor) { splitNode(node: TreeNode, editor: BaseEditor) {

View File

@ -118,9 +118,10 @@ export class BlockChain {
remove(blockId: string) { remove(blockId: string) {
const block = this.getBlock(blockId); const block = this.getBlock(blockId);
if (!block) return; if (!block) return;
const oldParentId = block.parent?.id;
block.remove(); block.remove();
this.map.delete(block.id); this.map.delete(block.id);
this.onBlockChange('delete', { block }); this.onBlockChange('remove', { oldParentId });
return block; return block;
} }

View File

@ -87,27 +87,28 @@ export class RenderTree {
if (!block) return null; if (!block) return null;
const node = this.createNode(block); const node = this.createNode(block);
if (!node) return null; if (!node) return null;
if (!shouldUpdateChildren) {
if (shouldUpdateChildren) { node.update(node.block, node.children);
const children: TreeNode[] = []; node.reRender();
let childBlock = block.firstChild; return;
while(childBlock) {
const child = this.createNode(childBlock);
child.update(childBlock, child.children);
children.push(child);
childBlock = childBlock.next;
}
node.update(block, children);
node?.reRender();
node?.children.forEach(child => {
child.reRender();
})
} else {
node.update(block, node.children);
node?.reRender();
} }
const children: TreeNode[] = [];
let childBlock = block.firstChild;
while (childBlock) {
const child = this.createNode(childBlock);
child.update(childBlock, child.children);
children.push(child);
childBlock = childBlock.next;
}
node.update(block, children);
node.reRender();
node.children.forEach(child => {
child.reRender();
});
} }
onBlockChange(command: string, data: BlockChangeProps) { onBlockChange(command: string, data: BlockChangeProps) {
@ -119,6 +120,9 @@ export class RenderTree {
case 'update': case 'update':
this.forceUpdate(block!.id); this.forceUpdate(block!.id);
break; break;
case 'remove':
if (oldParentId) this.forceUpdate(oldParentId, true);
break;
case 'move': case 'move':
if (oldParentId) this.forceUpdate(oldParentId, true); if (oldParentId) this.forceUpdate(oldParentId, true);
if (block?.parent) this.forceUpdate(block.parent.id, true); if (block?.parent) this.forceUpdate(block.parent.id, true);
@ -127,7 +131,7 @@ export class RenderTree {
default: default:
break; break;
} }
} }
updateSelections(selections: string[]) { updateSelections(selections: string[]) {

View File

@ -36,6 +36,7 @@ export class TreeNode {
} }
update(block: Block, children: TreeNode[]) { update(block: Block, children: TreeNode[]) {
this.type = block.type;
this.data = block.data; this.data = block.data;
this.children = []; this.children = [];
children.forEach(child => { children.forEach(child => {

View File

@ -1,7 +1,7 @@
import { TreeNode } from "@/appflowy_app/block_editor/view/tree_node"; import { TreeNode } from "@/appflowy_app/block_editor/view/tree_node";
import { triggerHotkey } from "@/appflowy_app/utils/slate/hotkey"; import { triggerHotkey } from "@/appflowy_app/utils/slate/hotkey";
import { useCallback, useContext, useLayoutEffect, useState } from "react"; import { useCallback, useContext, useEffect, useLayoutEffect, useState } from "react";
import { Transforms, createEditor, Descendant } from 'slate'; import { Transforms, createEditor, Descendant, Range } from 'slate';
import { ReactEditor, withReact } from 'slate-react'; import { ReactEditor, withReact } from 'slate-react';
import { TextBlockContext } from '$app/utils/slate/context'; import { TextBlockContext } from '$app/utils/slate/context';
@ -50,7 +50,14 @@ export function useTextBlock({
return; return;
} }
case 'Backspace': { case 'Backspace': {
console.log(editor.selection) if (!editor.selection) return;
const { anchor } = editor.selection;
const isCollapase = Range.isCollapsed(editor.selection);
if (isCollapase && anchor.offset === 0 && anchor.path.toString() === '0,0') {
event.stopPropagation();
event.preventDefault();
textBlockManager?.deleteNode(node);
}
} }
} }
@ -64,7 +71,17 @@ export function useTextBlock({
editor.children = value; editor.children = value;
Transforms.collapse(editor); Transforms.collapse(editor);
useEffect(() => {
textBlockManager?.register(node.id, editor);
return () => {
textBlockManager?.unregister(node.id);
}
}, [ editor ])
useLayoutEffect(() => { useLayoutEffect(() => {
let timer: NodeJS.Timeout; let timer: NodeJS.Timeout;
if (focusId === node.id && selection) { if (focusId === node.id && selection) {
ReactEditor.focus(editor); ReactEditor.focus(editor);