@valbuild/ui 0.20.2 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/valbuild-ui.cjs.d.ts +4 -2
- package/dist/valbuild-ui.cjs.js +10915 -9677
- package/dist/valbuild-ui.esm.js +8191 -6953
- package/package.json +4 -2
- package/src/components/RichTextEditor/Nodes/ImageNode.tsx +0 -8
- package/src/components/RichTextEditor/Plugins/LinkEditorPlugin.tsx +58 -0
- package/src/components/RichTextEditor/Plugins/Toolbar.tsx +70 -2
- package/src/components/RichTextEditor/RichTextEditor.tsx +15 -4
- package/src/components/ValOverlay.tsx +39 -27
- package/src/components/ValWindow.stories.tsx +5 -54
- package/src/components/dashboard/ValDashboard.tsx +2 -0
- package/src/exports.ts +1 -0
- package/src/richtext/conversion/conversion.test.ts +146 -0
- package/src/richtext/conversion/lexicalToRichTextSource.test.ts +89 -0
- package/src/richtext/conversion/lexicalToRichTextSource.ts +286 -0
- package/src/richtext/conversion/parseRichTextSource.test.ts +424 -0
- package/src/richtext/conversion/parseRichTextSource.ts +228 -0
- package/src/richtext/conversion/richTextSourceToLexical.test.ts +381 -0
- package/src/richtext/conversion/richTextSourceToLexical.ts +293 -0
- package/src/stories/RichTextEditor.stories.tsx +3 -47
- package/src/utils/imageMimeType.ts +23 -0
- package/src/utils/readImage.ts +1 -25
- package/src/components/RichTextEditor/conversion.test.ts +0 -132
- package/src/components/RichTextEditor/conversion.ts +0 -389
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { RichText, AnyRichTextOptions } from "@valbuild/core";
|
|
2
|
+
import {
|
|
3
|
+
toLexicalFormat,
|
|
4
|
+
fromLexicalFormat,
|
|
5
|
+
richTextSourceToLexical,
|
|
6
|
+
LexicalRootNode,
|
|
7
|
+
} from "./richTextSourceToLexical";
|
|
8
|
+
|
|
9
|
+
describe("richtext conversion", () => {
|
|
10
|
+
test("format conversion", () => {
|
|
11
|
+
//
|
|
12
|
+
expect(toLexicalFormat([])).toStrictEqual(0);
|
|
13
|
+
expect(toLexicalFormat(["bold"])).toStrictEqual(1);
|
|
14
|
+
expect(toLexicalFormat(["italic"])).toStrictEqual(2);
|
|
15
|
+
expect(toLexicalFormat(["bold", "italic"])).toStrictEqual(3);
|
|
16
|
+
expect(toLexicalFormat(["line-through"])).toStrictEqual(4);
|
|
17
|
+
expect(toLexicalFormat(["bold", "line-through"])).toStrictEqual(5);
|
|
18
|
+
expect(toLexicalFormat(["italic", "line-through"])).toStrictEqual(6);
|
|
19
|
+
expect(toLexicalFormat(["bold", "italic", "line-through"])).toStrictEqual(
|
|
20
|
+
7
|
|
21
|
+
);
|
|
22
|
+
//
|
|
23
|
+
expect(fromLexicalFormat(0)).toStrictEqual([]);
|
|
24
|
+
expect(fromLexicalFormat(1)).toStrictEqual(["bold"]);
|
|
25
|
+
expect(fromLexicalFormat(2)).toStrictEqual(["italic"]);
|
|
26
|
+
expect(fromLexicalFormat(3)).toStrictEqual(["bold", "italic"]);
|
|
27
|
+
expect(fromLexicalFormat(4)).toStrictEqual(["line-through"]);
|
|
28
|
+
expect(fromLexicalFormat(5)).toStrictEqual(["bold", "line-through"]);
|
|
29
|
+
expect(fromLexicalFormat(6)).toStrictEqual(["italic", "line-through"]);
|
|
30
|
+
expect(fromLexicalFormat(7)).toStrictEqual([
|
|
31
|
+
"bold",
|
|
32
|
+
"italic",
|
|
33
|
+
"line-through",
|
|
34
|
+
]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("basic toLexical", () => {
|
|
38
|
+
const input: RichText<AnyRichTextOptions> = {
|
|
39
|
+
_type: "richtext",
|
|
40
|
+
children: [
|
|
41
|
+
{ tag: "h1", children: ["Title 1"] },
|
|
42
|
+
{ tag: "h2", children: ["Title 2"] },
|
|
43
|
+
{ tag: "h3", children: ["Title 3"] },
|
|
44
|
+
{ tag: "h4", children: ["Title 4"] },
|
|
45
|
+
{ tag: "h5", children: ["Title 5"] },
|
|
46
|
+
{ tag: "h6", children: ["Title 6"] },
|
|
47
|
+
{
|
|
48
|
+
tag: "p",
|
|
49
|
+
children: [
|
|
50
|
+
{
|
|
51
|
+
tag: "span",
|
|
52
|
+
classes: ["bold", "italic", "line-through"],
|
|
53
|
+
children: ["Formatted span"],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
tag: "p",
|
|
59
|
+
children: ["Inline line break", { tag: "br", children: [] }],
|
|
60
|
+
},
|
|
61
|
+
{ tag: "br", children: [] },
|
|
62
|
+
{ tag: "br", children: [] },
|
|
63
|
+
{
|
|
64
|
+
tag: "p",
|
|
65
|
+
children: [
|
|
66
|
+
{ tag: "a", href: "https://example.com", children: ["Link"] },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
tag: "ul",
|
|
71
|
+
children: [
|
|
72
|
+
{
|
|
73
|
+
tag: "li",
|
|
74
|
+
children: [
|
|
75
|
+
{
|
|
76
|
+
tag: "ol",
|
|
77
|
+
dir: "rtl",
|
|
78
|
+
children: [
|
|
79
|
+
{
|
|
80
|
+
tag: "li",
|
|
81
|
+
children: [
|
|
82
|
+
{
|
|
83
|
+
tag: "span",
|
|
84
|
+
classes: ["italic"],
|
|
85
|
+
children: ["number 1.1. breaking lines: "],
|
|
86
|
+
},
|
|
87
|
+
{ tag: "br", children: [] },
|
|
88
|
+
"after line break",
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
{ tag: "li", children: ["number 1.2"] },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
const output: LexicalRootNode = {
|
|
101
|
+
version: 1,
|
|
102
|
+
format: "",
|
|
103
|
+
indent: 0,
|
|
104
|
+
direction: null,
|
|
105
|
+
type: "root",
|
|
106
|
+
children: [
|
|
107
|
+
{
|
|
108
|
+
version: 1,
|
|
109
|
+
format: "",
|
|
110
|
+
indent: 0,
|
|
111
|
+
direction: null,
|
|
112
|
+
type: "heading",
|
|
113
|
+
tag: "h1",
|
|
114
|
+
children: [
|
|
115
|
+
{
|
|
116
|
+
version: 1,
|
|
117
|
+
format: "",
|
|
118
|
+
indent: 0,
|
|
119
|
+
direction: null,
|
|
120
|
+
type: "text",
|
|
121
|
+
text: "Title 1",
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
version: 1,
|
|
127
|
+
format: "",
|
|
128
|
+
indent: 0,
|
|
129
|
+
direction: null,
|
|
130
|
+
type: "heading",
|
|
131
|
+
tag: "h2",
|
|
132
|
+
children: [
|
|
133
|
+
{
|
|
134
|
+
version: 1,
|
|
135
|
+
format: "",
|
|
136
|
+
indent: 0,
|
|
137
|
+
direction: null,
|
|
138
|
+
type: "text",
|
|
139
|
+
text: "Title 2",
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
version: 1,
|
|
145
|
+
format: "",
|
|
146
|
+
indent: 0,
|
|
147
|
+
direction: null,
|
|
148
|
+
type: "heading",
|
|
149
|
+
tag: "h3",
|
|
150
|
+
children: [
|
|
151
|
+
{
|
|
152
|
+
version: 1,
|
|
153
|
+
format: "",
|
|
154
|
+
indent: 0,
|
|
155
|
+
direction: null,
|
|
156
|
+
type: "text",
|
|
157
|
+
text: "Title 3",
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
version: 1,
|
|
163
|
+
format: "",
|
|
164
|
+
indent: 0,
|
|
165
|
+
direction: null,
|
|
166
|
+
type: "heading",
|
|
167
|
+
tag: "h4",
|
|
168
|
+
children: [
|
|
169
|
+
{
|
|
170
|
+
version: 1,
|
|
171
|
+
format: "",
|
|
172
|
+
indent: 0,
|
|
173
|
+
direction: null,
|
|
174
|
+
type: "text",
|
|
175
|
+
text: "Title 4",
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
version: 1,
|
|
181
|
+
format: "",
|
|
182
|
+
indent: 0,
|
|
183
|
+
direction: null,
|
|
184
|
+
type: "heading",
|
|
185
|
+
tag: "h5",
|
|
186
|
+
children: [
|
|
187
|
+
{
|
|
188
|
+
version: 1,
|
|
189
|
+
format: "",
|
|
190
|
+
indent: 0,
|
|
191
|
+
direction: null,
|
|
192
|
+
type: "text",
|
|
193
|
+
text: "Title 5",
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
version: 1,
|
|
199
|
+
format: "",
|
|
200
|
+
indent: 0,
|
|
201
|
+
direction: null,
|
|
202
|
+
type: "heading",
|
|
203
|
+
tag: "h6",
|
|
204
|
+
children: [
|
|
205
|
+
{
|
|
206
|
+
version: 1,
|
|
207
|
+
format: "",
|
|
208
|
+
indent: 0,
|
|
209
|
+
direction: null,
|
|
210
|
+
type: "text",
|
|
211
|
+
text: "Title 6",
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
version: 1,
|
|
217
|
+
format: "",
|
|
218
|
+
indent: 0,
|
|
219
|
+
direction: null,
|
|
220
|
+
type: "paragraph",
|
|
221
|
+
children: [
|
|
222
|
+
{
|
|
223
|
+
version: 1,
|
|
224
|
+
format: 7,
|
|
225
|
+
indent: 0,
|
|
226
|
+
direction: null,
|
|
227
|
+
type: "text",
|
|
228
|
+
text: "Formatted span",
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
version: 1,
|
|
234
|
+
format: "",
|
|
235
|
+
indent: 0,
|
|
236
|
+
direction: null,
|
|
237
|
+
type: "paragraph",
|
|
238
|
+
children: [
|
|
239
|
+
{
|
|
240
|
+
version: 1,
|
|
241
|
+
format: "",
|
|
242
|
+
indent: 0,
|
|
243
|
+
direction: null,
|
|
244
|
+
type: "text",
|
|
245
|
+
text: "Inline line break",
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
version: 1,
|
|
249
|
+
format: "",
|
|
250
|
+
indent: 0,
|
|
251
|
+
direction: null,
|
|
252
|
+
type: "linebreak",
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
version: 1,
|
|
258
|
+
format: "",
|
|
259
|
+
indent: 0,
|
|
260
|
+
direction: null,
|
|
261
|
+
type: "paragraph",
|
|
262
|
+
children: [],
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
version: 1,
|
|
266
|
+
format: "",
|
|
267
|
+
indent: 0,
|
|
268
|
+
direction: null,
|
|
269
|
+
type: "paragraph",
|
|
270
|
+
children: [],
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
version: 1,
|
|
274
|
+
format: "",
|
|
275
|
+
indent: 0,
|
|
276
|
+
direction: null,
|
|
277
|
+
type: "paragraph",
|
|
278
|
+
children: [
|
|
279
|
+
{
|
|
280
|
+
version: 1,
|
|
281
|
+
format: "",
|
|
282
|
+
indent: 0,
|
|
283
|
+
direction: null,
|
|
284
|
+
type: "link",
|
|
285
|
+
url: "https://example.com",
|
|
286
|
+
children: [
|
|
287
|
+
{
|
|
288
|
+
version: 1,
|
|
289
|
+
format: "",
|
|
290
|
+
indent: 0,
|
|
291
|
+
direction: null,
|
|
292
|
+
type: "text",
|
|
293
|
+
text: "Link",
|
|
294
|
+
},
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
version: 1,
|
|
301
|
+
format: "",
|
|
302
|
+
indent: 0,
|
|
303
|
+
direction: null,
|
|
304
|
+
type: "list",
|
|
305
|
+
listType: "bullet",
|
|
306
|
+
children: [
|
|
307
|
+
{
|
|
308
|
+
version: 1,
|
|
309
|
+
format: "",
|
|
310
|
+
indent: 0,
|
|
311
|
+
direction: null,
|
|
312
|
+
type: "listitem",
|
|
313
|
+
children: [
|
|
314
|
+
{
|
|
315
|
+
version: 1,
|
|
316
|
+
format: "",
|
|
317
|
+
indent: 0,
|
|
318
|
+
direction: "rtl",
|
|
319
|
+
type: "list",
|
|
320
|
+
listType: "number",
|
|
321
|
+
children: [
|
|
322
|
+
{
|
|
323
|
+
version: 1,
|
|
324
|
+
format: "",
|
|
325
|
+
indent: 0,
|
|
326
|
+
direction: null,
|
|
327
|
+
type: "listitem",
|
|
328
|
+
children: [
|
|
329
|
+
{
|
|
330
|
+
version: 1,
|
|
331
|
+
format: 2,
|
|
332
|
+
indent: 0,
|
|
333
|
+
direction: null,
|
|
334
|
+
type: "text",
|
|
335
|
+
text: "number 1.1. breaking lines: ",
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
version: 1,
|
|
339
|
+
format: "",
|
|
340
|
+
indent: 0,
|
|
341
|
+
direction: null,
|
|
342
|
+
type: "linebreak",
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
version: 1,
|
|
346
|
+
format: "",
|
|
347
|
+
indent: 0,
|
|
348
|
+
direction: null,
|
|
349
|
+
type: "text",
|
|
350
|
+
text: "after line break",
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
version: 1,
|
|
356
|
+
format: "",
|
|
357
|
+
indent: 0,
|
|
358
|
+
direction: null,
|
|
359
|
+
type: "listitem",
|
|
360
|
+
children: [
|
|
361
|
+
{
|
|
362
|
+
version: 1,
|
|
363
|
+
format: "",
|
|
364
|
+
indent: 0,
|
|
365
|
+
direction: null,
|
|
366
|
+
type: "text",
|
|
367
|
+
text: "number 1.2",
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
},
|
|
375
|
+
],
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
};
|
|
379
|
+
expect(richTextSourceToLexical(input)).toStrictEqual(output);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnyRichTextOptions,
|
|
3
|
+
HeadingNode as ValHeadingNode,
|
|
4
|
+
ListItemNode as ValListItemNode,
|
|
5
|
+
SpanNode as ValSpanNode,
|
|
6
|
+
UnorderedListNode as ValUnorderedListNode,
|
|
7
|
+
OrderedListNode as ValOrderedListNode,
|
|
8
|
+
ParagraphNode as ValParagraphNode,
|
|
9
|
+
RichTextNode as ValRichTextNode,
|
|
10
|
+
LinkNode as ValLinkNode,
|
|
11
|
+
ImageNode as ValImageNode,
|
|
12
|
+
RichText,
|
|
13
|
+
} from "@valbuild/core";
|
|
14
|
+
import { ImagePayload } from "../../components/RichTextEditor/Nodes/ImageNode";
|
|
15
|
+
|
|
16
|
+
/// Serialized Lexical Nodes:
|
|
17
|
+
// TODO: replace with Lexical libs types - not currently exported?
|
|
18
|
+
|
|
19
|
+
export type LexicalTextNode = CommonLexicalProps & {
|
|
20
|
+
type: "text";
|
|
21
|
+
text: string;
|
|
22
|
+
format: "" | number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type InlineNode = LexicalTextNode | LexicalLinkNode;
|
|
26
|
+
|
|
27
|
+
export type LexicalParagraphNode = CommonLexicalProps & {
|
|
28
|
+
type: "paragraph";
|
|
29
|
+
children: (InlineNode | LexicalLineBreakNode)[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type LexicalHeadingNode = CommonLexicalProps & {
|
|
33
|
+
type: "heading";
|
|
34
|
+
tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
|
35
|
+
children: InlineNode[];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type LexicalListItemNode = CommonLexicalProps & {
|
|
39
|
+
type: "listitem";
|
|
40
|
+
children: (InlineNode | LexicalListNode | LexicalLineBreakNode)[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type LexicalListNode = CommonLexicalProps & {
|
|
44
|
+
type: "list";
|
|
45
|
+
listType: "bullet" | "number" | "checked";
|
|
46
|
+
direction: "ltr" | "rtl" | null;
|
|
47
|
+
children: LexicalListItemNode[];
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type LexicalImageNode = CommonLexicalProps & {
|
|
51
|
+
type: "image";
|
|
52
|
+
} & ImagePayload;
|
|
53
|
+
|
|
54
|
+
export type LexicalLinkNode = CommonLexicalProps & {
|
|
55
|
+
type: "link";
|
|
56
|
+
url: string;
|
|
57
|
+
children: LexicalTextNode[];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type LexicalLineBreakNode = CommonLexicalProps & {
|
|
61
|
+
type: "linebreak";
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type LexicalNode =
|
|
65
|
+
| LexicalTextNode
|
|
66
|
+
| LexicalParagraphNode
|
|
67
|
+
| LexicalHeadingNode
|
|
68
|
+
| LexicalListItemNode
|
|
69
|
+
| LexicalListNode
|
|
70
|
+
| LexicalImageNode
|
|
71
|
+
| LexicalLineBreakNode
|
|
72
|
+
| LexicalLinkNode;
|
|
73
|
+
|
|
74
|
+
export type LexicalRootNode = {
|
|
75
|
+
type: "root";
|
|
76
|
+
children: (LexicalHeadingNode | LexicalParagraphNode | LexicalListNode)[];
|
|
77
|
+
version: 1;
|
|
78
|
+
format: "left" | "start" | "center" | "right" | "end" | "justify" | "";
|
|
79
|
+
direction: null | "ltr" | "rtl";
|
|
80
|
+
} & CommonLexicalProps;
|
|
81
|
+
|
|
82
|
+
export const COMMON_LEXICAL_PROPS = {
|
|
83
|
+
version: 1,
|
|
84
|
+
format: "" as number | "",
|
|
85
|
+
indent: 0,
|
|
86
|
+
direction: null as null | "ltr" | "rtl",
|
|
87
|
+
} as const;
|
|
88
|
+
|
|
89
|
+
type CommonLexicalProps = typeof COMMON_LEXICAL_PROPS;
|
|
90
|
+
|
|
91
|
+
export function toLexicalNode(
|
|
92
|
+
node: ValRichTextNode<AnyRichTextOptions>,
|
|
93
|
+
useBreakNode = false
|
|
94
|
+
): LexicalNode {
|
|
95
|
+
if (typeof node === "string") {
|
|
96
|
+
return {
|
|
97
|
+
...COMMON_LEXICAL_PROPS,
|
|
98
|
+
type: "text",
|
|
99
|
+
format: "",
|
|
100
|
+
text: node,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if ("tag" in node) {
|
|
104
|
+
switch (node.tag) {
|
|
105
|
+
case "h1":
|
|
106
|
+
return toLexicalHeadingNode(node);
|
|
107
|
+
case "h2":
|
|
108
|
+
return toLexicalHeadingNode(node);
|
|
109
|
+
case "h3":
|
|
110
|
+
return toLexicalHeadingNode(node);
|
|
111
|
+
case "h4":
|
|
112
|
+
return toLexicalHeadingNode(node);
|
|
113
|
+
case "h5":
|
|
114
|
+
return toLexicalHeadingNode(node);
|
|
115
|
+
case "h6":
|
|
116
|
+
return toLexicalHeadingNode(node);
|
|
117
|
+
case "li":
|
|
118
|
+
return toLexicalListItemNode(node);
|
|
119
|
+
case "p":
|
|
120
|
+
return toLexicalParagraphNode(node);
|
|
121
|
+
case "ul":
|
|
122
|
+
return toLexicalListNode(node);
|
|
123
|
+
case "ol":
|
|
124
|
+
return toLexicalListNode(node);
|
|
125
|
+
case "span":
|
|
126
|
+
return toLexicalTextNode(node);
|
|
127
|
+
case "a":
|
|
128
|
+
return toLexicalLinkNode(node);
|
|
129
|
+
case "img":
|
|
130
|
+
return toLexicalImageNode(node);
|
|
131
|
+
case "br":
|
|
132
|
+
if (useBreakNode) {
|
|
133
|
+
return {
|
|
134
|
+
...COMMON_LEXICAL_PROPS,
|
|
135
|
+
type: "linebreak",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return toLexicalPseudoLineBreakNode();
|
|
139
|
+
default:
|
|
140
|
+
throw Error("Unexpected node tag: " + JSON.stringify(node, null, 2));
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
throw Error("Unexpected node: " + JSON.stringify(node, null, 2));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function toLexicalImageNode(
|
|
148
|
+
node: ValImageNode<AnyRichTextOptions>
|
|
149
|
+
): LexicalImageNode {
|
|
150
|
+
const url = node.src;
|
|
151
|
+
return {
|
|
152
|
+
...COMMON_LEXICAL_PROPS,
|
|
153
|
+
type: "image",
|
|
154
|
+
src: url,
|
|
155
|
+
width: node.width,
|
|
156
|
+
height: node.height,
|
|
157
|
+
// TODO: altText
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function toLexicalLinkNode(
|
|
162
|
+
link: ValLinkNode<AnyRichTextOptions>
|
|
163
|
+
): LexicalLinkNode {
|
|
164
|
+
return {
|
|
165
|
+
...COMMON_LEXICAL_PROPS,
|
|
166
|
+
type: "link",
|
|
167
|
+
url: link.href,
|
|
168
|
+
children: link.children.map((child) =>
|
|
169
|
+
toLexicalNode(child)
|
|
170
|
+
) as LexicalLinkNode["children"],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export function richTextSourceToLexical(
|
|
174
|
+
richtext: RichText<AnyRichTextOptions>
|
|
175
|
+
): LexicalRootNode {
|
|
176
|
+
return {
|
|
177
|
+
...COMMON_LEXICAL_PROPS,
|
|
178
|
+
format: "",
|
|
179
|
+
type: "root",
|
|
180
|
+
children: richtext.children.map((child) =>
|
|
181
|
+
toLexicalNode(child)
|
|
182
|
+
) as LexicalRootNode["children"],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function toLexicalHeadingNode(
|
|
187
|
+
heading: ValHeadingNode<AnyRichTextOptions>
|
|
188
|
+
): LexicalHeadingNode {
|
|
189
|
+
return {
|
|
190
|
+
...COMMON_LEXICAL_PROPS,
|
|
191
|
+
type: "heading",
|
|
192
|
+
tag: heading.tag,
|
|
193
|
+
children: heading.children.map((child) =>
|
|
194
|
+
toLexicalNode(child)
|
|
195
|
+
) as LexicalHeadingNode["children"],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function toLexicalParagraphNode(
|
|
200
|
+
paragraph: ValParagraphNode<AnyRichTextOptions>
|
|
201
|
+
): LexicalParagraphNode {
|
|
202
|
+
return {
|
|
203
|
+
...COMMON_LEXICAL_PROPS,
|
|
204
|
+
type: "paragraph",
|
|
205
|
+
children: paragraph.children.map((child) =>
|
|
206
|
+
toLexicalNode(child, true)
|
|
207
|
+
) as LexicalParagraphNode["children"],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Lexical does not support line breaks, so we convert them to empty paragraphs
|
|
212
|
+
function toLexicalPseudoLineBreakNode(): LexicalParagraphNode {
|
|
213
|
+
return {
|
|
214
|
+
...COMMON_LEXICAL_PROPS,
|
|
215
|
+
type: "paragraph",
|
|
216
|
+
children: [] as LexicalParagraphNode["children"],
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function toLexicalListItemNode(
|
|
221
|
+
listItem: ValListItemNode<AnyRichTextOptions>
|
|
222
|
+
): LexicalListItemNode {
|
|
223
|
+
return {
|
|
224
|
+
...COMMON_LEXICAL_PROPS,
|
|
225
|
+
type: "listitem",
|
|
226
|
+
children: listItem.children.map((child) =>
|
|
227
|
+
toLexicalNode(child, true)
|
|
228
|
+
) as LexicalListItemNode["children"],
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function toLexicalListNode(
|
|
233
|
+
list:
|
|
234
|
+
| ValUnorderedListNode<AnyRichTextOptions>
|
|
235
|
+
| ValOrderedListNode<AnyRichTextOptions>
|
|
236
|
+
): LexicalListNode {
|
|
237
|
+
return {
|
|
238
|
+
...COMMON_LEXICAL_PROPS,
|
|
239
|
+
type: "list",
|
|
240
|
+
listType: list.tag === "ol" ? "number" : "bullet",
|
|
241
|
+
children: list.children.map((child) =>
|
|
242
|
+
toLexicalNode(child)
|
|
243
|
+
) as LexicalListNode["children"],
|
|
244
|
+
...(list.dir ? { direction: list.dir } : { direction: null }),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const FORMAT_MAPPING = {
|
|
249
|
+
bold: 1, // 0001
|
|
250
|
+
italic: 2, // 0010
|
|
251
|
+
"line-through": 4, // 0100
|
|
252
|
+
// underline: 8, // 1000
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export function toLexicalFormat(
|
|
256
|
+
classes: (keyof typeof FORMAT_MAPPING)[]
|
|
257
|
+
): number {
|
|
258
|
+
return classes.reduce(
|
|
259
|
+
(prev, curr) => prev | /* bitwise or */ FORMAT_MAPPING[curr],
|
|
260
|
+
0
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function fromLexicalFormat(
|
|
265
|
+
format: number
|
|
266
|
+
): (keyof typeof FORMAT_MAPPING)[] {
|
|
267
|
+
return Object.entries(FORMAT_MAPPING).flatMap(([key, value]) => {
|
|
268
|
+
if ((value & /* bitwise and */ format) === value) {
|
|
269
|
+
return [key as keyof typeof FORMAT_MAPPING];
|
|
270
|
+
}
|
|
271
|
+
return [];
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function toLexicalTextNode(
|
|
276
|
+
spanNode: ValSpanNode<AnyRichTextOptions>
|
|
277
|
+
): LexicalTextNode {
|
|
278
|
+
const child = spanNode.children[0];
|
|
279
|
+
if (typeof child === "string") {
|
|
280
|
+
return {
|
|
281
|
+
...COMMON_LEXICAL_PROPS,
|
|
282
|
+
type: "text",
|
|
283
|
+
text: child,
|
|
284
|
+
format: toLexicalFormat(spanNode.classes),
|
|
285
|
+
};
|
|
286
|
+
} else {
|
|
287
|
+
// recurse the spans and merge their classes
|
|
288
|
+
return toLexicalTextNode({
|
|
289
|
+
...child,
|
|
290
|
+
classes: spanNode.classes.concat(child.classes),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|