@valbuild/ui 0.18.0 → 0.20.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/jest.config.js ADDED
@@ -0,0 +1,4 @@
1
+ /** @type {import("jest").Config} */
2
+ module.exports = {
3
+ preset: "../../jest.preset",
4
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valbuild/ui",
3
- "version": "0.18.0",
3
+ "version": "0.20.0",
4
4
  "sideEffects": false,
5
5
  "scripts": {
6
6
  "typecheck": "tsc --noEmit",
@@ -16,17 +16,19 @@
16
16
  "@lexical/utils": "^0.10.0",
17
17
  "@types/express": "^4.17.17",
18
18
  "@types/react": "^18.0.26",
19
+ "@valbuild/core": "~0.20.0",
19
20
  "classnames": "^2.3.2",
20
21
  "esbuild": "^0.17.19",
21
22
  "lexical": "^0.10.0",
22
23
  "react-feather": "^2.0.10",
24
+ "react-resizable": "^3.0.5",
23
25
  "rollup-plugin-peer-deps-external": "^2.2.4",
24
26
  "rollup-plugin-postcss": "^4.0.2",
25
27
  "rollup-plugin-typescript2": "^0.34.1",
26
- "zod": "^3.22.2",
27
- "@valbuild/core": "~0.18.0"
28
+ "zod": "^3.22.2"
28
29
  },
29
30
  "devDependencies": {
31
+ "@lexical/headless": "^0.10.0",
30
32
  "@storybook/addon-essentials": "^7.0.12",
31
33
  "@storybook/addon-interactions": "^7.0.12",
32
34
  "@storybook/addon-links": "^7.0.12",
@@ -36,6 +38,7 @@
36
38
  "@storybook/react": "^7.0.12",
37
39
  "@storybook/react-vite": "^7.0.12",
38
40
  "@storybook/testing-library": "^0.0.14-next.2",
41
+ "@types/react-resizable": "^3.0.5",
39
42
  "autoprefixer": "^10.4.13",
40
43
  "postcss": "^8.4.21",
41
44
  "prop-types": "^15.8.1",
@@ -3,6 +3,8 @@ import { FC } from "react";
3
3
  const Bold: FC<{ className?: string }> = ({ className }) => {
4
4
  return (
5
5
  <svg
6
+ height={12}
7
+ width={12}
6
8
  xmlns="http://www.w3.org/2000/svg"
7
9
  viewBox="0 0 24 24"
8
10
  fill="none"
@@ -11,8 +13,6 @@ const Bold: FC<{ className?: string }> = ({ className }) => {
11
13
  strokeLinecap="round"
12
14
  strokeLinejoin="round"
13
15
  className={className}
14
- width={25}
15
- height={25}
16
16
  >
17
17
  <path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
18
18
  <path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"></path>
@@ -3,8 +3,8 @@ import { FC } from "react";
3
3
  const Chevron: FC<{ className?: string }> = ({ className }) => {
4
4
  return (
5
5
  <svg
6
- width="16"
7
- height="16"
6
+ width="10"
7
+ height="10"
8
8
  viewBox="0 0 12 12"
9
9
  xmlns="http://www.w3.org/2000/svg"
10
10
  className={className}
@@ -3,8 +3,8 @@ import { FC } from "react";
3
3
  const ImageIcon: FC<{ className?: string }> = ({ className }) => {
4
4
  return (
5
5
  <svg
6
- width="9"
7
- height="10"
6
+ width="12"
7
+ height="12"
8
8
  viewBox="0 0 9 10"
9
9
  fill="none"
10
10
  xmlns="http://www.w3.org/2000/svg"
@@ -11,8 +11,8 @@ const Italic: FC<{ className?: string }> = ({ className }) => {
11
11
  strokeLinecap="round"
12
12
  strokeLinejoin="round"
13
13
  className={className}
14
- width={25}
15
- height={25}
14
+ width={12}
15
+ height={12}
16
16
  >
17
17
  <line x1="19" y1="4" x2="10" y2="4"></line>
18
18
  <line x1="14" y1="20" x2="5" y2="20"></line>
@@ -8,8 +8,8 @@ const Strikethrough: FC<{ className?: string }> = ({ className }) => {
8
8
  stroke="currentColor"
9
9
  strokeWidth="1"
10
10
  className={className}
11
- width={25}
12
- height={25}
11
+ width={12}
12
+ height={12}
13
13
  >
14
14
  <path
15
15
  fillRule="evenodd"
@@ -11,8 +11,8 @@ const Underline: FC<{ className?: string }> = ({ className }) => {
11
11
  strokeLinecap="round"
12
12
  strokeLinejoin="round"
13
13
  className={className}
14
- width={25}
15
- height={25}
14
+ width={12}
15
+ height={12}
16
16
  >
17
17
  <path d="M18 4V11C18 14.3137 15.3137 17 12 17C8.68629 17 6 14.3137 6 11V4M4 21H20" />
18
18
  </svg>
@@ -40,7 +40,7 @@ const Button: FC<ButtonProps> = ({
40
40
  <button
41
41
  disabled={disabled}
42
42
  className={classNames(
43
- "font-sans font-[12px] tracking-[0.04em] py-[8px] px-[12px] h-[40px] rounded whitespace-nowrap group relative text-primary",
43
+ "font-sans font-[12px] tracking-[0.04em] py-1 px-2 rounded whitespace-nowrap group relative text-primary",
44
44
  {
45
45
  "font-bold": variant === "primary",
46
46
  "bg-base hover:bg-base text-fill disabled:bg-fill disabled:text-base":
@@ -53,7 +53,7 @@ const Button: FC<ButtonProps> = ({
53
53
  >
54
54
  {tooltip && (
55
55
  <div
56
- className={`absolute bottom-[-75%] left-0 z-20 bg-black w-fit h-fit text-base hidden group-hover:block`}
56
+ className={`absolute bottom-[-75%] left-0 z-20 bg-black w-fit h-fit text-primary hidden group-hover:block`}
57
57
  >
58
58
  <div>{tooltip}</div>
59
59
  </div>
@@ -48,7 +48,7 @@ const Dropdown: React.FC<DropdownProps> = ({
48
48
  }, []);
49
49
 
50
50
  return (
51
- <div ref={dropdownRef}>
51
+ <div className="text-[12px]" ref={dropdownRef}>
52
52
  <Button
53
53
  onClick={(ev) => {
54
54
  ev.preventDefault();
@@ -68,7 +68,7 @@ const Dropdown: React.FC<DropdownProps> = ({
68
68
  </span>
69
69
  </Button>
70
70
  {isOpen && (
71
- <div className="absolute left-0 mt-2 w-48 border shadow-lg font-mono font-[500] tracking-[0.04em] text-[14px] border-base text-primary bg-border z-10">
71
+ <div className="absolute left-0 mt-2 w-48 shadow-lg font-mono text-[14px] text-primary bg-border z-overlay">
72
72
  <div className="py-1 rounded-md">
73
73
  {options?.map((option, idx) => (
74
74
  <button
@@ -6,80 +6,65 @@ import {
6
6
  Spread,
7
7
  } from "lexical";
8
8
 
9
- export interface ImagePayload {
10
- altText: string;
11
- height?: number;
12
- key?: NodeKey;
13
- maxWidth?: number;
9
+ export type ImagePayload = {
14
10
  src: string;
11
+ sha256?: string;
12
+ fileExt?: string;
13
+ altText?: string;
14
+ height?: number;
15
15
  width?: number;
16
- }
16
+ };
17
17
 
18
- export type SerializedImageNode = Spread<
19
- {
20
- altText: string;
21
- width?: number;
22
- maxWidth: number;
23
- height?: number;
24
- src: string;
25
- },
26
- SerializedLexicalNode
27
- >;
18
+ export type SerializedImageNode = Spread<ImagePayload, SerializedLexicalNode>;
28
19
 
29
20
  export class ImageNode extends DecoratorNode<JSX.Element> {
30
21
  __src: string;
31
- __altText: string;
32
- __width: "inherit" | number;
33
- __height: "inherit" | number;
34
- __maxWidth: number;
22
+ __sha256?: string;
23
+ __imageFileExt?: string;
24
+ __altText?: string;
25
+ __width?: number;
26
+ __height?: number;
35
27
 
36
28
  static getType(): string {
37
29
  return "image";
38
30
  }
39
31
 
40
32
  static clone(node: ImageNode): ImageNode {
41
- return new ImageNode(
42
- node.__src,
43
- node.__altText,
44
- node.__width,
45
- node.__height,
46
- node.__maxWidth,
47
- node.__key
48
- );
33
+ return new ImageNode({
34
+ src: node.__src,
35
+ sha256: node.__sha256,
36
+ altText: node.__altText,
37
+ width: node.__width,
38
+ height: node.__height,
39
+ fileExt: node.__fileExt,
40
+ });
49
41
  }
50
42
 
51
- constructor(
52
- src: string,
53
- altText?: string,
54
- width?: "inherit" | number,
55
- height?: "inherit" | number,
56
- maxWidth?: number,
57
- key?: NodeKey
58
- ) {
43
+ constructor(payload: ImagePayload, key?: NodeKey) {
59
44
  super(key);
60
- this.__src = src;
61
- this.__altText = altText || "";
62
- this.__width = width || "inherit";
63
- this.__height = height || "inherit";
64
- this.__maxWidth = maxWidth || 0;
45
+ this.__src = payload.src;
46
+ this.__altText = payload.altText;
47
+ this.__width = payload.width;
48
+ this.__height = payload.height;
49
+ this.__imageFileExt = payload.fileExt;
50
+ this.__sha256 = payload.sha256;
65
51
  }
66
52
 
67
53
  exportJSON(): SerializedImageNode {
68
54
  return {
69
55
  altText: this.__altText,
70
- height: this.__height === "inherit" ? 0 : this.__height,
71
- maxWidth: this.__maxWidth,
56
+ height: this.__width,
72
57
  src: this.__src,
73
58
  type: "image",
74
59
  version: 1,
75
- width: this.__width === "inherit" ? 0 : this.__width,
60
+ fileExt: this.__imageFileExt,
61
+ width: this.__width,
62
+ sha256: this.__sha256,
76
63
  };
77
64
  }
78
65
 
79
66
  static importJSON(serializedNode: SerializedImageNode): ImageNode {
80
- const { src } = serializedNode;
81
- const node = $createImageNode(src);
82
- return node;
67
+ return $createImageNode(serializedNode);
83
68
  }
84
69
 
85
70
  createDOM(): HTMLElement {
@@ -92,24 +77,30 @@ export class ImageNode extends DecoratorNode<JSX.Element> {
92
77
 
93
78
  decorate(): JSX.Element {
94
79
  return (
95
- <img
96
- key={this.__key}
80
+ <ImageComponent
97
81
  src={this.__src}
98
- alt={this.__altText}
99
- width={this.__width}
82
+ altText={this.__altText}
83
+ empty={this.__empty}
100
84
  height={this.__height}
85
+ nodeKey={this.getKey()}
101
86
  />
102
87
  );
103
88
  }
104
89
  }
105
- export function $createImageNode(
106
- src: string,
107
- altText?: string,
108
- width?: "inherit" | number,
109
- height?: "inherit" | number,
110
- maxWidth?: number
111
- ): ImageNode {
112
- return new ImageNode(src, altText, width, height, maxWidth);
90
+
91
+ function ImageComponent(props: {
92
+ empty: boolean;
93
+ altText?: string;
94
+ height?: number;
95
+ nodeKey?: NodeKey;
96
+ src: string;
97
+ width?: number;
98
+ }): JSX.Element {
99
+ return <img src={props.src}></img>;
100
+ }
101
+
102
+ export function $createImageNode(payload: ImagePayload): ImageNode {
103
+ return new ImageNode(payload);
113
104
  }
114
105
 
115
106
  export function $isImageNode(node: LexicalNode | null): boolean {
@@ -29,12 +29,11 @@ export default function ImagesPlugin(): JSX.Element | null {
29
29
  editor.registerCommand<InsertImagePayload>(
30
30
  INSERT_IMAGE_COMMAND,
31
31
  (payload) => {
32
- const imageNode = $createImageNode(payload.src);
32
+ const imageNode = $createImageNode(payload);
33
33
  $insertNodes([imageNode]);
34
34
  if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {
35
35
  $wrapNodeInElement(imageNode, $createParagraphNode).selectEnd();
36
36
  }
37
-
38
37
  return true;
39
38
  },
40
39
  COMMAND_PRIORITY_EDITOR
@@ -52,6 +52,7 @@ import Button from "../../Button";
52
52
  import Dropdown from "../../Dropdown";
53
53
  import UploadModal from "../../UploadModal";
54
54
  import { INSERT_IMAGE_COMMAND } from "./ImagePlugin";
55
+ import { readImage } from "../../../utils/readImage";
55
56
 
56
57
  export interface ToolbarSettingsProps {
57
58
  fontsFamilies?: string[];
@@ -152,15 +153,15 @@ const Toolbar: FC<ToolbarSettingsProps> = ({
152
153
  }
153
154
  }
154
155
 
155
- setFontSize(
156
- $getSelectionStyleValueForProperty(selection, "font-size", "15px")
157
- );
158
- setFontColor(
159
- $getSelectionStyleValueForProperty(selection, "color", "#000")
160
- );
161
- setFontFamily(
162
- $getSelectionStyleValueForProperty(selection, "font-family", "Arial")
163
- );
156
+ // setFontSize(
157
+ // $getSelectionStyleValueForProperty(selection, "font-size", "15px")
158
+ // );
159
+ // setFontColor(
160
+ // $getSelectionStyleValueForProperty(selection, "color", "#000")
161
+ // );
162
+ // setFontFamily(
163
+ // $getSelectionStyleValueForProperty(selection, "font-family", "Arial")
164
+ // );
164
165
  }
165
166
  }, [activeEditor]);
166
167
 
@@ -276,16 +277,9 @@ const Toolbar: FC<ToolbarSettingsProps> = ({
276
277
  });
277
278
  };
278
279
 
279
- const uploadImage = (url: string, alt?: string) => {
280
- editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
281
- altText: "URL image",
282
- src: url,
283
- });
284
- };
285
-
286
280
  return (
287
- <div className="flex flex-row items-center gap-6 p-2 overflow-clip">
288
- <div className="flex flex-row gap-2">
281
+ <div className="sticky top-0 border-b bg-base border-highlight">
282
+ <div className="flex flex-row gap-1">
289
283
  <Dropdown
290
284
  options={Object.values(blockTypes)}
291
285
  label={
@@ -295,28 +289,12 @@ const Toolbar: FC<ToolbarSettingsProps> = ({
295
289
  formatText(blockTypesLookup[selectedOption]);
296
290
  }}
297
291
  />
298
- <Dropdown
299
- onChange={changeFontFamily}
300
- options={fontsFamilies ?? ["sans", "serif", "solina"]}
301
- label={fontFamily}
302
- />
303
- <Dropdown
304
- onChange={changeFontSize}
305
- options={
306
- fontSizes ??
307
- [11, 12, 13, 14, 15, 16, 17, 18, 19, 20].map((size) => `${size}px`)
308
- }
309
- label={fontSize}
310
- />
311
- </div>
312
- <div className="flex flex-row gap-2">
313
292
  <Button
314
293
  variant="primary"
315
294
  onClick={(ev) => {
316
295
  ev.preventDefault();
317
296
  editor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold");
318
297
  }}
319
- tooltip="Format text as bold"
320
298
  active={isBold}
321
299
  icon={<Bold className={`${isBold && "stroke-[3px]"}`} />}
322
300
  />
@@ -338,27 +316,27 @@ const Toolbar: FC<ToolbarSettingsProps> = ({
338
316
  }}
339
317
  icon={<Italic className={`${isItalic && "stroke-[3px]"}`} />}
340
318
  />
341
- <Button
342
- active={isUnderline}
343
- onClick={(ev) => {
344
- ev.preventDefault();
345
- editor.dispatchCommand(FORMAT_TEXT_COMMAND, "underline");
346
- }}
347
- icon={<Underline className={`${isUnderline && "stroke-[3px]"}`} />}
348
- />
319
+ <label className="flex items-center justify-center">
320
+ <ImageIcon />
321
+ <input
322
+ type="file"
323
+ hidden={true}
324
+ onChange={(ev) => {
325
+ ev.preventDefault();
326
+
327
+ readImage(ev)
328
+ .then((res) => {
329
+ editor.dispatchCommand(INSERT_IMAGE_COMMAND, {
330
+ ...res,
331
+ });
332
+ })
333
+ .catch((err) => {
334
+ console.error("Error reading image", err);
335
+ });
336
+ }}
337
+ />
338
+ </label>
349
339
  </div>
350
- <Button
351
- icon={<ImageIcon />}
352
- onClick={(ev) => {
353
- ev.preventDefault();
354
- setShowModal(true);
355
- }}
356
- ></Button>
357
- <UploadModal
358
- setShowModal={setShowModal}
359
- showModal={showModal}
360
- uploadImage={uploadImage}
361
- />
362
340
  </div>
363
341
  );
364
342
  };
@@ -1,17 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import {
3
- $createParagraphNode,
4
- $createTextNode,
5
- $getRoot,
6
- LexicalEditor,
7
- LexicalNode,
8
- } from "lexical";
9
- import {
10
- ListItemNode,
11
- ListNode,
12
- $createListNode,
13
- $createListItemNode,
14
- } from "@lexical/list";
2
+ import { LexicalEditor } from "lexical";
3
+ import { ListItemNode, ListNode } from "@lexical/list";
15
4
  import { LexicalComposer } from "@lexical/react/LexicalComposer";
16
5
  import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
17
6
  import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
@@ -23,18 +12,12 @@ import { ImageNode } from "./Nodes/ImageNode";
23
12
  import { AutoFocus } from "./Plugins/AutoFocus";
24
13
  import ImagesPlugin from "./Plugins/ImagePlugin";
25
14
  import Toolbar from "./Plugins/Toolbar";
26
- import {
27
- RichText,
28
- TextNode as ValTextNode,
29
- HeadingNode as ValHeadingNode,
30
- ListItemNode as ValListItemNode,
31
- ParagraphNode as ValParagraphNode,
32
- ListNode as ValListNode,
33
- } from "@valbuild/core";
34
- import { $createHeadingNode, HeadingNode } from "@lexical/rich-text";
15
+ import { AnyRichTextOptions, RichText } from "@valbuild/core";
16
+ import { HeadingNode } from "@lexical/rich-text";
17
+ import { toLexical } from "./conversion";
35
18
 
36
19
  export interface RichTextEditorProps {
37
- richtext: RichText;
20
+ richtext: RichText<AnyRichTextOptions>;
38
21
  onEditor?: (editor: LexicalEditor) => void; // Not the ideal way of passing the editor to the upper context, we need it to be able to save
39
22
  }
40
23
 
@@ -42,92 +25,21 @@ function onError(error: any) {
42
25
  console.error(error);
43
26
  }
44
27
 
45
- type ValNode =
46
- | ValTextNode
47
- | ValHeadingNode
48
- | ValListItemNode
49
- | ValParagraphNode
50
- | ValListNode;
51
- function toLexicalNode(node: ValNode): LexicalNode {
52
- switch (node.type) {
53
- case "heading":
54
- return toLexicalHeadingNode(node);
55
- case "listitem":
56
- return toLexicalListItemNode(node);
57
- case "paragraph":
58
- return toLexicalParagraphNode(node);
59
- case "list":
60
- return toLexicalListNode(node);
61
- case "text":
62
- return toLexicalTextNode(node);
63
- }
64
- }
65
-
66
- function toLexicalHeadingNode(heading: ValHeadingNode): LexicalNode {
67
- const node = $createHeadingNode(heading.tag);
68
- node.setFormat(heading.format || "");
69
- node.setIndent(heading.indent || 0);
70
- node.setDirection(heading.direction || "ltr");
71
- node.append(...heading.children.map((child) => toLexicalNode(child)));
72
- return node;
73
- }
74
-
75
- function toLexicalParagraphNode(paragraph: ValParagraphNode): LexicalNode {
76
- const node = $createParagraphNode();
77
- node.setFormat(paragraph.format || "");
78
- node.setIndent(paragraph.indent || 0);
79
- node.setDirection(paragraph.direction || "ltr");
80
- node.append(...paragraph.children.map((child) => toLexicalNode(child)));
81
- return node;
82
- }
83
-
84
- function toLexicalListItemNode(listItem: ValListItemNode): LexicalNode {
85
- const node = $createListItemNode();
86
- node.setFormat(listItem.format || "");
87
- node.setIndent(listItem.indent || 0);
88
- node.setDirection(listItem.direction || "ltr");
89
- node.setValue(listItem.value);
90
- node.setChecked(listItem.checked);
91
- node.append(...listItem.children.map((child) => toLexicalNode(child)));
92
- return node;
93
- }
94
-
95
- function toLexicalListNode(list: ValListNode): LexicalNode {
96
- const node = $createListNode(list.listType, list.start);
97
- node.setFormat(list.format || "");
98
- node.setIndent(list.indent || 0);
99
- node.setDirection(list.direction || "ltr");
100
- node.append(...list.children.map((child) => toLexicalNode(child)));
101
- return node;
102
- }
103
-
104
- function toLexicalTextNode(text: ValTextNode): LexicalNode {
105
- const node = $createTextNode(text.text);
106
- node.setFormat(text.format as any); // TODO: why is text.format numbers when we are trying it out?
107
- text.indent && node.setIndent(text.indent);
108
- text.direction && node.setDirection(text.direction);
109
- node.setStyle(text.style || "");
110
- node.setDetail(text.detail || 0);
111
- return node;
112
- }
113
-
114
28
  export const RichTextEditor: FC<RichTextEditorProps> = ({
115
29
  richtext,
116
30
  onEditor,
117
31
  }) => {
118
- const prePopulatedState = () => {
119
- const root = $getRoot();
120
- $getRoot().append(
121
- ...richtext.children.map((child) => toLexicalNode(child))
32
+ const prePopulatedState = (editor: LexicalEditor) => {
33
+ editor.setEditorState(
34
+ editor.parseEditorState({ root: toLexical(richtext) })
122
35
  );
123
- root.selectEnd();
124
36
  };
125
37
  const initialConfig = {
126
38
  namespace: "val",
127
39
  editorState: prePopulatedState,
128
40
  nodes: [HeadingNode, ImageNode, ListNode, ListItemNode],
129
41
  theme: {
130
- root: "relative p-4 bg-base min-h-[200px] text-white font-roboto",
42
+ root: "p-4 bg-fill text-white font-roboto border-b border-highlight",
131
43
  text: {
132
44
  bold: "font-semibold",
133
45
  underline: "underline",
@@ -152,25 +64,17 @@ export const RichTextEditor: FC<RichTextEditorProps> = ({
152
64
  onError,
153
65
  };
154
66
  return (
155
- <div className=" relative bg-base min-h-[200px] mt-2 border border-highlight rounded overflow-none resize">
156
- <LexicalComposer initialConfig={initialConfig}>
157
- <Toolbar onEditor={onEditor} />
158
- <ImagesPlugin />
159
- <RichTextPlugin
160
- contentEditable={
161
- <LexicalContentEditable className="relative bg-fill flex flex-col h-full w-full min-h-[200px] min-w-[566px] text-primary outline-none overflow-auto resize" />
162
- }
163
- placeholder={
164
- <div className="absolute top-[calc(58px+1rem)] left-4 text-base/25 text-primary">
165
- Enter some text...
166
- </div>
167
- }
168
- ErrorBoundary={LexicalErrorBoundary}
169
- />
170
- <ListPlugin />
171
- <AutoFocus />
172
- <HistoryPlugin />
173
- </LexicalComposer>
174
- </div>
67
+ <LexicalComposer initialConfig={initialConfig}>
68
+ <AutoFocus />
69
+ <Toolbar onEditor={onEditor} />
70
+ <RichTextPlugin
71
+ contentEditable={<LexicalContentEditable className="outline-none" />}
72
+ placeholder={<div className="">Enter some text...</div>}
73
+ ErrorBoundary={LexicalErrorBoundary}
74
+ />
75
+ <ListPlugin />
76
+ <ImagesPlugin />
77
+ <HistoryPlugin />
78
+ </LexicalComposer>
175
79
  );
176
80
  };