返回 presentation-ai
transforms.ts
根目录 / src / components / plate / utils / transforms.ts
1 "use client";
2
3 import { insertCallout } from "@platejs/callout";
4 import { insertCodeBlock } from "@platejs/code-block";
5 import { insertDate } from "@platejs/date";
6 import { insertColumnGroup, toggleColumnGroup } from "@platejs/layout";
7 import { triggerFloatingLink } from "@platejs/link/react";
8 import { isOrderedList } from "@platejs/list";
9 import { insertEquation, insertInlineEquation } from "@platejs/math";
10 import {
11 insertAudioPlaceholder,
12 insertFilePlaceholder,
13 insertMedia,
14 insertVideoPlaceholder,
15 } from "@platejs/media";
16 import { SuggestionPlugin } from "@platejs/suggestion/react";
17 import { TablePlugin } from "@platejs/table/react";
18 import { insertToc } from "@platejs/toc";
19 import {
20 KEYS,
21 PathApi,
22 type NodeEntry,
23 type Path,
24 type TElement,
25 } from "platejs";
26 import { type PlateEditor } from "platejs/react";
27
28 const ACTION_THREE_COLUMNS = "action_three_columns";
29 const LIST_STYLE_TYPE_KEY = "listStyleType";
30 const LIST_START_KEY = "listStart";
31 const LIST_RESTART_KEY = "listRestart";
32 const LIST_RESTART_POLITE_KEY = "listRestartPolite";
33
34 const insertList = (editor: PlateEditor, type: string) => {
35 editor.tf.insertNodes(
36 editor.api.create.block({
37 indent: 1,
38 listStyleType: type,
39 }),
40 { select: true },
41 );
42 };
43
44 const insertBlockMap: Record<
45 string,
46 (editor: PlateEditor, type: string, props?: Partial<TElement>) => void
47 > = {
48 [KEYS.listTodo]: insertList,
49 [KEYS.ol]: insertList,
50 [KEYS.ul]: insertList,
51 [ACTION_THREE_COLUMNS]: (editor) =>
52 insertColumnGroup(editor, { columns: 3, select: true }),
53 [KEYS.audio]: (editor) => insertAudioPlaceholder(editor, { select: true }),
54 [KEYS.callout]: (editor, _type, props) =>
55 insertCallout(editor, {
56 icon: getStringProp(props, "icon"),
57 select: true,
58 variant: getStringProp(props, "variant"),
59 }),
60 [KEYS.codeBlock]: (editor) => insertCodeBlock(editor, { select: true }),
61 [KEYS.equation]: (editor) => insertEquation(editor, { select: true }),
62 [KEYS.file]: (editor) => insertFilePlaceholder(editor, { select: true }),
63 [KEYS.img]: (editor) =>
64 insertMedia(editor, {
65 select: true,
66 type: KEYS.img,
67 }),
68 [KEYS.mediaEmbed]: (editor) =>
69 insertMedia(editor, {
70 select: true,
71 type: KEYS.mediaEmbed,
72 }),
73 [KEYS.table]: (editor) =>
74 editor.getTransforms(TablePlugin).insert.table({}, { select: true }),
75 [KEYS.toc]: (editor) => insertToc(editor, { select: true }),
76 [KEYS.video]: (editor) => insertVideoPlaceholder(editor, { select: true }),
77 };
78
79 const insertInlineMap: Record<
80 string,
81 (editor: PlateEditor, type: string) => void
82 > = {
83 [KEYS.date]: (editor) => insertDate(editor, { select: true }),
84 [KEYS.inlineEquation]: (editor) =>
85 insertInlineEquation(editor, "", { select: true }),
86 [KEYS.link]: (editor) => triggerFloatingLink(editor, { focused: true }),
87 };
88
89 export const insertBlock = (
90 editor: PlateEditor,
91 type: string,
92 { props }: { props?: Partial<TElement> } = {},
93 ) => {
94 editor.tf.withoutNormalizing(() => {
95 const block = editor.api.block();
96
97 if (!block) return;
98 if (type in insertBlockMap) {
99 insertBlockMap[type]!(editor, type, props);
100 } else {
101 editor.tf.insertNodes(editor.api.create.block({ ...props, type }), {
102 at: PathApi.next(block[1]),
103 select: true,
104 });
105 }
106 if (getBlockType(block[0]) !== type) {
107 editor.getApi(SuggestionPlugin).suggestion.withoutSuggestions(() => {
108 editor.tf.removeNodes({ previousEmptyBlock: true });
109 });
110 }
111 });
112 };
113
114 function getStringProp(props: Partial<TElement> | undefined, key: string) {
115 const value = props?.[key];
116
117 return typeof value === "string" ? value : undefined;
118 }
119
120 export const insertInlineElement = (editor: PlateEditor, type: string) => {
121 if (insertInlineMap[type]) {
122 insertInlineMap[type](editor, type);
123 }
124 };
125
126 const setList = (
127 editor: PlateEditor,
128 type: string,
129 entry: NodeEntry<TElement>,
130 ) => {
131 editor.tf.setNodes(
132 editor.api.create.block({
133 indent: 1,
134 listStyleType: type,
135 }),
136 {
137 at: entry[1],
138 },
139 );
140 };
141
142 const setBlockMap: Record<
143 string,
144 (editor: PlateEditor, type: string, entry: NodeEntry<TElement>) => void
145 > = {
146 [KEYS.listTodo]: setList,
147 [KEYS.ol]: setList,
148 [KEYS.ul]: setList,
149 [ACTION_THREE_COLUMNS]: (editor) => toggleColumnGroup(editor, { columns: 3 }),
150 };
151
152 type SetBlockTypeOptions = {
153 at?: Path;
154 props?: Partial<TElement>;
155 };
156
157 export const setBlockType = (
158 editor: PlateEditor,
159 type: string,
160 { at, props }: SetBlockTypeOptions = {},
161 ) => {
162 editor.tf.withoutNormalizing(() => {
163 const setEntry = (entry: NodeEntry<TElement>) => {
164 const [node, path] = entry;
165
166 if (node[KEYS.listType] || node[LIST_STYLE_TYPE_KEY]) {
167 editor.tf.unsetNodes(
168 [
169 KEYS.listType,
170 "indent",
171 LIST_STYLE_TYPE_KEY,
172 LIST_START_KEY,
173 LIST_RESTART_KEY,
174 LIST_RESTART_POLITE_KEY,
175 ],
176 { at: path },
177 );
178 }
179 if (type in setBlockMap) {
180 return setBlockMap[type]!(editor, type, entry);
181 }
182 if (node.type !== type) {
183 editor.tf.setNodes({ ...props, type }, { at: path });
184 } else if (props) {
185 editor.tf.setNodes(props, { at: path });
186 }
187 };
188
189 if (at) {
190 const entry = editor.api.node(at) as NodeEntry<TElement> | undefined;
191
192 if (entry) {
193 setEntry(entry);
194
195 return;
196 }
197 }
198
199 const entries = editor.api.blocks({ mode: "lowest" });
200
201 entries.forEach((entry) => setEntry(entry));
202 });
203 };
204
205 export const getBlockType = (block: TElement) => {
206 if (typeof block[LIST_STYLE_TYPE_KEY] === "string") {
207 if (block[LIST_STYLE_TYPE_KEY] === KEYS.listTodo) {
208 return KEYS.listTodo;
209 }
210
211 return isOrderedList(block) ? KEYS.ol : KEYS.ul;
212 }
213
214 if (block[KEYS.listType]) {
215 if (block[KEYS.listType] === KEYS.ol) {
216 return KEYS.ol;
217 } else if (block[KEYS.listType] === KEYS.listTodo) {
218 return KEYS.listTodo;
219 } else {
220 return KEYS.ul;
221 }
222 }
223
224 return block.type;
225 };
226
226 lines TYPESCRIPT