mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: the feature of delete block
This commit is contained in:
parent
a38c213744
commit
c6c97f7c83
@ -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) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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[]) {
|
||||||
|
@ -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 => {
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user