返回 presentation-ai
empty-block.tsx
根目录 / src / components / notebook / presentation / editor / plugins / empty-block.tsx
1 import { KEYS, NodeApi, type TElement } from "platejs";
2 import { createTPlatePlugin } from "platejs/react";
3
4 const REMOVABLE_EMPTY_BLOCK_TYPES = new Set<string>([
5 KEYS.p,
6 KEYS.h1,
7 KEYS.h2,
8 KEYS.h3,
9 KEYS.h4,
10 KEYS.h5,
11 KEYS.h6,
12 ]);
13
14 function isRemovableEmptyTextBlock(element: TElement): boolean {
15 return (
16 REMOVABLE_EMPTY_BLOCK_TYPES.has(element.type) &&
17 NodeApi.string(element).trim().length === 0
18 );
19 }
20
21 export const EmptyBlockPlugin = createTPlatePlugin({
22 key: "only-when-empty",
23 handlers: {
24 onKeyDown: ({ editor, event }) => {
25 if (
26 event.defaultPrevented ||
27 (event.key !== "Backspace" && event.key !== "Delete") ||
28 !editor.api.isCollapsed()
29 ) {
30 return;
31 }
32
33 const blockEntry = editor.api.block();
34 if (!blockEntry) return;
35
36 const [block, blockPath] = blockEntry;
37 const blockIndex = blockPath[0];
38
39 if (
40 blockIndex === undefined ||
41 blockPath.length !== 1 ||
42 editor.children.length <= 1 ||
43 !isRemovableEmptyTextBlock(block)
44 ) {
45 return;
46 }
47
48 event.preventDefault();
49 event.stopPropagation();
50
51 const nextSelectionIndex =
52 blockIndex < editor.children.length - 1 ? blockIndex : blockIndex - 1;
53
54 editor.tf.removeNodes({ at: blockPath });
55
56 if (nextSelectionIndex >= 0 && editor.children[nextSelectionIndex]) {
57 editor.tf.select([nextSelectionIndex]);
58 }
59
60 return true;
61 },
62 onChange: ({ editor, value }) => {
63 // Check if the editor effectively has no children or is in an invalid state
64 const isEmpty = !value || value.length === 0;
65 if (isEmpty) {
66 // Insert a default paragraph if completely empty
67 editor.tf.insertNode({
68 type: "p", // Make sure this matches your paragraph type key
69 children: [{ text: "" }],
70 });
71 }
72 },
73 },
74 });
75
75 lines Plain Text