@platecms/delta-smart-text 0.6.0 → 0.8.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.
Files changed (142) hide show
  1. package/.env.example +7 -0
  2. package/__generated__/fragment-masking.ts +87 -0
  3. package/__generated__/gql.ts +238 -0
  4. package/__generated__/graphql.ts +3441 -0
  5. package/__generated__/index.ts +2 -0
  6. package/codegen.config.ts +24 -0
  7. package/eslint.config.mjs +43 -0
  8. package/i18n.js +28 -0
  9. package/package.json +5 -5
  10. package/project.json +54 -0
  11. package/src/components/DeltaSlateEditor.vue +74 -0
  12. package/src/components/icon/FontAwesomeIcon.vue +21 -0
  13. package/src/graphql/apiTokens/apiTokens.fragments.gql +8 -0
  14. package/src/graphql/assets/assets.fragments.gql +10 -0
  15. package/src/graphql/blueprints/blueprints.fragments.gql +52 -0
  16. package/src/graphql/buildingBlockFieldFulfillments/buildingBlockFieldFullfillment.fragements.gql +6 -0
  17. package/src/graphql/buildingBlockFields/buildingBlockField.fragments.gql +8 -0
  18. package/src/graphql/buildingBlocks/buildingBlocks.fragments.gql +11 -0
  19. package/src/graphql/channels/channels.fragments.gql +9 -0
  20. package/src/graphql/contentExperiences/allContentExperiences.query.gql +24 -0
  21. package/src/graphql/contentExperiences/contentExperience.query.gql +20 -0
  22. package/src/graphql/contentExperiences/contentExperiences.fragments.gql +14 -0
  23. package/src/graphql/contentFields/contentFields.fragments.gql +7 -0
  24. package/src/graphql/contentItems/allContentItems.query.gql +48 -0
  25. package/src/graphql/contentItems/contentItems.fragments.gql +11 -0
  26. package/src/graphql/contentTypes/allContentTypes.query.gql +26 -0
  27. package/src/graphql/contentTypes/contentTypes.fragments.gql +11 -0
  28. package/src/graphql/contentValidations/contentValidationRule.fragments.gql +34 -0
  29. package/src/graphql/contentValues/allContentValues.query.gql +41 -0
  30. package/src/graphql/contentValues/contentValues.fragments.gql +9 -0
  31. package/src/graphql/contentValues/createContentValue.mutation.gql +17 -0
  32. package/src/graphql/experienceComponents/experienceComponent.fragments.gql +13 -0
  33. package/src/graphql/fragments.gql +6 -0
  34. package/src/graphql/gridDefinition/gridDefinition.fragments.gql +5 -0
  35. package/src/graphql/gridPlacements/gridPlacement.fragments.gql +7 -0
  36. package/src/graphql/grids/grid.fragments.gql +7 -0
  37. package/src/graphql/invitations/invitations.fragments.gql +7 -0
  38. package/src/graphql/organizations/organizations.fragments.gql +13 -0
  39. package/src/graphql/pathParts/pathParts.fragments.gql +19 -0
  40. package/src/graphql/plateMaintainers/plateMaintainer.fragements.gql +10 -0
  41. package/src/graphql/roleAssignments/roleAssignment.fragments.gql +9 -0
  42. package/src/graphql/roles/roles.fragments.gql +7 -0
  43. package/src/graphql/subject/subject.fragments.gql +8 -0
  44. package/src/graphql/tags/tags.fragments.gql +17 -0
  45. package/src/graphql/themes/themes.fragments.gql +8 -0
  46. package/src/index.css +1 -0
  47. package/src/index.ts +21 -0
  48. package/src/locales/en.json +52 -0
  49. package/src/locales/nl.json +52 -0
  50. package/src/react/components/DeltaSlateEditor.tsx +243 -0
  51. package/src/react/components/DeltaSlateEditorConnector.tsx +50 -0
  52. package/src/react/components/Element.spec.tsx +244 -0
  53. package/src/react/components/Element.tsx +151 -0
  54. package/src/react/components/FontAwesomeIcon.tsx +17 -0
  55. package/src/react/components/Leaf.spec.tsx +61 -0
  56. package/src/react/components/Leaf.tsx +22 -0
  57. package/src/react/components/elements/CodeElement.tsx +16 -0
  58. package/src/react/components/elements/ContentValueElement.tsx +33 -0
  59. package/src/react/components/elements/LinkElement.tsx +44 -0
  60. package/src/react/components/inputs/SearchInput.tsx +22 -0
  61. package/src/react/components/inputs/TextInput.tsx +30 -0
  62. package/src/react/components/menus/ContentAndFormatMenu.tsx +272 -0
  63. package/src/react/components/menus/ContentLibraryMenu.tsx +48 -0
  64. package/src/react/components/menus/ReusableContentMenu.tsx +190 -0
  65. package/src/react/components/menus/content/ContentItemsMenu.tsx +215 -0
  66. package/src/react/components/menus/content/ContentTypesMenu.tsx +129 -0
  67. package/src/react/components/menus/content/partials/ContentFieldMenuItem.tsx +11 -0
  68. package/src/react/components/menus/content/partials/ContentValueMenuItem.tsx +58 -0
  69. package/src/react/components/menus/link/AnchorInput.tsx +123 -0
  70. package/src/react/components/menus/link/LinkInput.tsx +195 -0
  71. package/src/react/components/menus/link/LinkMenu.spec.tsx +145 -0
  72. package/src/react/components/menus/link/LinkMenu.tsx +289 -0
  73. package/src/react/components/menus/partials/MenuButton.tsx +52 -0
  74. package/src/react/components/menus/partials/MenuContainer.tsx +9 -0
  75. package/src/react/components/menus/partials/MenuHeader.tsx +11 -0
  76. package/src/react/components/toolbar/Toolbar.tsx +249 -0
  77. package/src/react/components/toolbar/ToolbarBlockButton.tsx +31 -0
  78. package/src/react/components/toolbar/ToolbarHeadingDropdownButton.tsx +76 -0
  79. package/src/react/components/toolbar/ToolbarLinkButton.tsx +33 -0
  80. package/src/react/components/toolbar/ToolbarMarkButton.tsx +25 -0
  81. package/src/react/components/toolbar/content/ContentExtractToolbarButton.tsx +68 -0
  82. package/src/react/components/toolbar/content/ContentLibraryToolbarButton.tsx +43 -0
  83. package/src/react/components/toolbar/content/ContentToolbar.tsx +37 -0
  84. package/src/react/components/toolbar/link/ToolbarDisplayLink.tsx +36 -0
  85. package/src/react/components/toolbar/link/UnlinkButton.tsx +25 -0
  86. package/src/react/config/hotkeys.ts +8 -0
  87. package/src/react/plugins/index.ts +59 -0
  88. package/src/react/store/editorSlice.ts +124 -0
  89. package/src/react/store/store.ts +12 -0
  90. package/src/react/types.ts +87 -0
  91. package/src/react/utils/decorator.ts +61 -0
  92. package/src/react/utils/index.ts +110 -0
  93. package/src/vue-shims.d.ts +5 -0
  94. package/tsconfig.json +26 -0
  95. package/tsconfig.lib.json +25 -0
  96. package/tsconfig.spec.json +22 -0
  97. package/vite.config.ts +67 -0
  98. package/components/DeltaSlateEditor.vue.d.ts +0 -26
  99. package/index.cjs +0 -381
  100. package/index.css +0 -1
  101. package/index.d.ts +0 -12
  102. package/index.js +0 -49254
  103. package/react/components/DeltaSlateEditor.d.ts +0 -7
  104. package/react/components/DeltaSlateEditorConnector.d.ts +0 -12
  105. package/react/components/Element.d.ts +0 -8
  106. package/react/components/FontAwesomeIcon.d.ts +0 -6
  107. package/react/components/Leaf.d.ts +0 -3
  108. package/react/components/elements/CodeElement.d.ts +0 -8
  109. package/react/components/elements/ContentValueElement.d.ts +0 -8
  110. package/react/components/elements/LinkElement.d.ts +0 -8
  111. package/react/components/inputs/SearchInput.d.ts +0 -5
  112. package/react/components/inputs/TextInput.d.ts +0 -7
  113. package/react/components/menus/ContentAndFormatMenu.d.ts +0 -10
  114. package/react/components/menus/ContentLibraryMenu.d.ts +0 -4
  115. package/react/components/menus/ReusableContentMenu.d.ts +0 -3
  116. package/react/components/menus/content/ContentItemsMenu.d.ts +0 -5
  117. package/react/components/menus/content/ContentTypesMenu.d.ts +0 -6
  118. package/react/components/menus/content/partials/ContentFieldMenuItem.d.ts +0 -6
  119. package/react/components/menus/content/partials/ContentValueMenuItem.d.ts +0 -7
  120. package/react/components/menus/link/AnchorInput.d.ts +0 -8
  121. package/react/components/menus/link/LinkInput.d.ts +0 -11
  122. package/react/components/menus/link/LinkMenu.d.ts +0 -18
  123. package/react/components/menus/partials/MenuButton.d.ts +0 -7
  124. package/react/components/menus/partials/MenuContainer.d.ts +0 -4
  125. package/react/components/menus/partials/MenuHeader.d.ts +0 -5
  126. package/react/components/toolbar/Toolbar.d.ts +0 -6
  127. package/react/components/toolbar/ToolbarBlockButton.d.ts +0 -12
  128. package/react/components/toolbar/ToolbarHeadingDropdownButton.d.ts +0 -2
  129. package/react/components/toolbar/ToolbarLinkButton.d.ts +0 -6
  130. package/react/components/toolbar/ToolbarMarkButton.d.ts +0 -6
  131. package/react/components/toolbar/content/ContentExtractToolbarButton.d.ts +0 -2
  132. package/react/components/toolbar/content/ContentLibraryToolbarButton.d.ts +0 -5
  133. package/react/components/toolbar/content/ContentToolbar.d.ts +0 -4
  134. package/react/components/toolbar/link/ToolbarDisplayLink.d.ts +0 -2
  135. package/react/components/toolbar/link/UnlinkButton.d.ts +0 -2
  136. package/react/config/hotkeys.d.ts +0 -2
  137. package/react/plugins/index.d.ts +0 -3
  138. package/react/store/editorSlice.d.ts +0 -169
  139. package/react/store/store.d.ts +0 -5
  140. package/react/types.d.ts +0 -65
  141. package/react/utils/decorator.d.ts +0 -15
  142. package/react/utils/index.d.ts +0 -17
@@ -0,0 +1,244 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { Element } from "./Element";
3
+ import { describe, it } from "vitest";
4
+ import { c } from "@platecms/delta-castscript";
5
+ import { Leaf } from "./Leaf";
6
+ import { Descendant, createEditor } from "slate";
7
+ import { Slate, withReact } from "slate-react";
8
+
9
+ describe("Element", () => {
10
+ const editor = withReact(createEditor());
11
+ const slateEditorValue: Descendant[] = [
12
+ {
13
+ type: "paragraph",
14
+ children: [{ text: "Test text" }],
15
+ },
16
+ ];
17
+
18
+ beforeEach(async () => {
19
+ vi.clearAllMocks();
20
+ });
21
+
22
+ it("should render element as blockquote", () => {
23
+ // Arrange
24
+ render(
25
+ <Slate editor={editor} initialValue={slateEditorValue}>
26
+ <Element attributes={{}} children={[]} element={{ type: "blockquote", children: [] }} />
27
+ </Slate>,
28
+ );
29
+
30
+ // Act
31
+
32
+ // Assert
33
+ expect(screen.getByRole("blockquote")).toBeDefined();
34
+ });
35
+
36
+ describe("heading", () => {
37
+ it("should render element as h1", () => {
38
+ // Arrange
39
+ render(
40
+ <Slate editor={editor} initialValue={slateEditorValue}>
41
+ <Element attributes={{}} children={[]} element={{ type: "heading", level: 1, children: [] }} />
42
+ </Slate>,
43
+ );
44
+
45
+ // Act
46
+
47
+ // Assert
48
+ expect(screen.getByRole("heading", { level: 1 })).toBeDefined();
49
+ });
50
+
51
+ it("should render element as h2", () => {
52
+ // Arrange
53
+ render(
54
+ <Slate editor={editor} initialValue={slateEditorValue}>
55
+ <Element attributes={{}} children={[]} element={{ type: "heading", level: 2, children: [] }} />
56
+ </Slate>,
57
+ );
58
+
59
+ // Act
60
+
61
+ // Assert
62
+ expect(screen.getByRole("heading", { level: 2 })).toBeDefined();
63
+ });
64
+
65
+ it("should render element as h3", () => {
66
+ // Arrange
67
+ render(
68
+ <Slate editor={editor} initialValue={slateEditorValue}>
69
+ <Element attributes={{}} children={[]} element={{ type: "heading", level: 3, children: [] }} />
70
+ </Slate>,
71
+ );
72
+
73
+ // Act
74
+
75
+ // Assert
76
+ expect(screen.getByRole("heading", { level: 3 })).toBeDefined();
77
+ });
78
+
79
+ it("should render element as h4", () => {
80
+ // Arrange
81
+ render(
82
+ <Slate editor={editor} initialValue={slateEditorValue}>
83
+ <Element attributes={{}} children={[]} element={{ type: "heading", level: 4, children: [] }} />
84
+ </Slate>,
85
+ );
86
+
87
+ // Act
88
+
89
+ // Assert
90
+ expect(screen.getByRole("heading", { level: 4 })).toBeDefined();
91
+ });
92
+
93
+ it("should render element as h5", () => {
94
+ // Arrange
95
+ render(
96
+ <Slate editor={editor} initialValue={slateEditorValue}>
97
+ <Element attributes={{}} children={[]} element={{ type: "heading", level: 5, children: [] }} />
98
+ </Slate>,
99
+ );
100
+
101
+ // Act
102
+
103
+ // Assert
104
+ expect(screen.getByRole("heading", { level: 5 })).toBeDefined();
105
+ });
106
+ });
107
+
108
+ describe("list", () => {
109
+ it("should render element as ul", () => {
110
+ // Arrange
111
+ render(
112
+ <Slate editor={editor} initialValue={slateEditorValue}>
113
+ <Element attributes={{}} children={[]} element={{ type: "list", ordered: false, children: [] }} />
114
+ </Slate>,
115
+ );
116
+
117
+ // Act
118
+
119
+ // Assert
120
+ expect(screen.getByRole("list")).toBeDefined();
121
+ });
122
+
123
+ it("should render element as ol", () => {
124
+ // Arrange
125
+ render(
126
+ <Slate editor={editor} initialValue={slateEditorValue}>
127
+ <Element attributes={{}} children={[]} element={{ type: "list", ordered: true, children: [] }} />
128
+ </Slate>,
129
+ );
130
+
131
+ // Act
132
+
133
+ // Assert
134
+ expect(screen.getByRole("list")).toBeDefined();
135
+ });
136
+
137
+ it("should render element as list item", () => {
138
+ // Arrange
139
+ render(
140
+ <Slate editor={editor} initialValue={slateEditorValue}>
141
+ <Element attributes={{}} children={[]} element={{ type: "listItem", children: [] }} />
142
+ </Slate>,
143
+ );
144
+
145
+ // Act
146
+
147
+ // Assert
148
+ expect(screen.getByRole("listitem")).toBeDefined();
149
+ });
150
+
151
+ it("should render list with list items", () => {
152
+ // Arrange
153
+ render(
154
+ <Slate editor={editor} initialValue={slateEditorValue}>
155
+ <Element
156
+ attributes={{}}
157
+ element={{ type: "list", ordered: false, children: [{ type: "listItem", children: [] }] }}
158
+ >
159
+ <Element attributes={{}} children={[]} element={{ type: "listItem", children: [] }} />
160
+ </Element>
161
+ </Slate>,
162
+ );
163
+
164
+ // Act
165
+
166
+ // Assert
167
+ expect(screen.getByRole("list")).toBeDefined();
168
+ expect(screen.getByRole("listitem")).toBeDefined();
169
+ });
170
+ });
171
+
172
+ describe("code", () => {
173
+ it("should render element as code", () => {
174
+ // Arrange
175
+ render(
176
+ <Slate editor={editor} initialValue={slateEditorValue}>
177
+ <Element attributes={{}} children={[]} element={{ type: "code", children: [] }} />
178
+ </Slate>,
179
+ );
180
+
181
+ // Act
182
+
183
+ // Assert
184
+ expect(screen.getByRole("code")).toBeDefined();
185
+ });
186
+ });
187
+
188
+ describe("contentValue", () => {
189
+ it("should render element as contentValue", () => {
190
+ // Arrange
191
+ render(
192
+ <Slate editor={editor} initialValue={slateEditorValue}>
193
+ <Element
194
+ attributes={{}}
195
+ children={[]}
196
+ element={{
197
+ type: "contentValue",
198
+ prn: "test",
199
+ root: c("root", "welcome"),
200
+ children: [{ text: "" }],
201
+ }}
202
+ />
203
+ </Slate>,
204
+ );
205
+
206
+ // Act
207
+
208
+ // Assert
209
+ expect(screen.getByText("welcome")).toBeDefined();
210
+ });
211
+ });
212
+
213
+ describe("link", () => {
214
+ it("should render element as link", () => {
215
+ // Arrange
216
+ render(
217
+ <Slate editor={editor} initialValue={slateEditorValue}>
218
+ <Element
219
+ attributes={{}}
220
+ children={[
221
+ <Leaf
222
+ attributes={{ "data-slate-leaf": true }}
223
+ children={[<p>Test</p>]}
224
+ leaf={{ text: "Test", bold: true, italic: true }}
225
+ text={{ text: "Test", bold: true, italic: true }}
226
+ />,
227
+ ]}
228
+ element={{
229
+ type: "link",
230
+ url: "https://www.google.com",
231
+ children: [{ bold: true, italic: true, text: "Test" }],
232
+ target: "blank",
233
+ }}
234
+ />
235
+ </Slate>,
236
+ );
237
+
238
+ // Act
239
+
240
+ // Assert
241
+ expect(screen.getByText("Test")).toBeDefined();
242
+ });
243
+ });
244
+ });
@@ -0,0 +1,151 @@
1
+ import React, { HTMLAttributes, ReactNode, useEffect } from "react";
2
+ import { DeltaElement, ListElement } from "../types";
3
+ import ContentValueElement from "./elements/ContentValueElement";
4
+ import CodeElement from "./elements/CodeElement";
5
+ import LinkElement from "./elements/LinkElement";
6
+ import { Editor, Element as SlateElement, Transforms } from "slate";
7
+ import { useSlate } from "slate-react";
8
+ import { findElement } from "../utils";
9
+
10
+ export function Element({
11
+ attributes,
12
+ children,
13
+ element,
14
+ lastKey,
15
+ }: {
16
+ attributes: HTMLAttributes<HTMLElement>;
17
+ children: ReactNode;
18
+ element: DeltaElement;
19
+ lastKey: string | undefined;
20
+ }): React.ReactElement {
21
+ const style = {};
22
+ const editor = useSlate();
23
+
24
+ useEffect(() => {
25
+ if(element.type === "list" || element.type === "listItem") {
26
+ const list = findElement(editor, "list") as [ListElement, number[]] | undefined;
27
+ if(!list) return;
28
+ if(lastKey === "Enter") {
29
+ // If the last list item is empty, remove the last list item and continue with a paragraph node
30
+ if(list[0].children?.length > 1 && list[0].children[list[0].children.length - 2].children?.[0]?.text?.length === 0) {
31
+ Transforms.removeNodes(editor, {
32
+ match: node =>
33
+ !Editor.isEditor(node) &&
34
+ SlateElement.isElement(node) &&
35
+ node.type === "listItem" && node.children?.[0]?.text?.length === 0,
36
+ });
37
+ Transforms.unwrapNodes(editor, {
38
+ match: node =>
39
+ !Editor.isEditor(node) &&
40
+ SlateElement.isElement(node) &&
41
+ node.type === "list",
42
+ split: true,
43
+ });
44
+
45
+ Transforms.setNodes(editor, { type: "paragraph" });
46
+ }
47
+ }
48
+ if(lastKey === "Backspace") {
49
+ // If the first list item is empty, remove the list and insert a paragraph
50
+ if(list[0].children?.[0]?.children?.[0]?.text?.length === 0) {
51
+ Transforms.removeNodes(editor, {
52
+ match: (node) => !Editor.isEditor(node) && SlateElement.isElement(node) && node.type === "list",
53
+ });
54
+ Transforms.insertNodes(editor, { type: 'paragraph', children: [{ text: '' }] });
55
+ }
56
+ }
57
+ }
58
+
59
+ }, [lastKey]);
60
+
61
+ switch (element.type) {
62
+ case "heading":
63
+ switch (element.level) {
64
+ case 1:
65
+ return (
66
+ <h1 style={style} {...attributes} className={"text-5xl"}>
67
+ {children}
68
+ </h1>
69
+ );
70
+ case 2:
71
+ return (
72
+ <h2 style={style} {...attributes} className={"text-4xl"}>
73
+ {children}
74
+ </h2>
75
+ );
76
+ case 3:
77
+ return (
78
+ <h3 style={style} {...attributes} className={"text-3xl"}>
79
+ {children}
80
+ </h3>
81
+ );
82
+ case 4:
83
+ return (
84
+ <h4 style={style} {...attributes} className={"text-2xl"}>
85
+ {children}
86
+ </h4>
87
+ );
88
+ case 5:
89
+ return (
90
+ <h5 style={style} {...attributes} className={"text-xl"}>
91
+ {children}
92
+ </h5>
93
+ );
94
+ default:
95
+ return (
96
+ <h5 style={style} {...attributes} className={"text-xl"}>
97
+ {children}
98
+ </h5>
99
+ );
100
+ }
101
+ case "list":
102
+ if (element.ordered) {
103
+ return (
104
+ <ol style={style} {...attributes} className={"list-decimal pl-4"}>
105
+ {children}
106
+ </ol>
107
+ );
108
+ }
109
+ return (
110
+ <ul style={style} {...attributes} className={"list-disc pl-4"}>
111
+ {children}
112
+ </ul>
113
+ );
114
+ case "blockquote":
115
+ return (
116
+ <blockquote style={style} {...attributes}>
117
+ {children}
118
+ </blockquote>
119
+ );
120
+ case "listItem":
121
+ return (
122
+ <li style={style} {...attributes}>
123
+ {children}
124
+ </li>
125
+ );
126
+ case "code":
127
+ return (
128
+ <CodeElement element={element} attributes={attributes}>
129
+ {children}
130
+ </CodeElement>
131
+ );
132
+ case "contentValue":
133
+ return (
134
+ <ContentValueElement element={element} attributes={attributes}>
135
+ {children}
136
+ </ContentValueElement>
137
+ );
138
+ case "link":
139
+ return (
140
+ <LinkElement element={element} attributes={attributes}>
141
+ {children}
142
+ </LinkElement>
143
+ );
144
+ default:
145
+ return (
146
+ <p style={style} {...attributes}>
147
+ {children}
148
+ </p>
149
+ );
150
+ }
151
+ }
@@ -0,0 +1,17 @@
1
+ export function FontAwesomeIcon({
2
+ icon,
3
+ className,
4
+ size = "md",
5
+ spin = false,
6
+ }: {
7
+ icon: [string, string];
8
+ className?: string;
9
+ size?: "lg" | "md" | "sm" | "xs";
10
+ spin?: boolean;
11
+ }): React.ReactElement {
12
+ return (
13
+ <i
14
+ className={`fa-light fa-${icon[1]} ${className ?? ""} ${size !== "md" ? `text-${size}` : "text-[16px]"} ${spin ? "fa-spin" : ""}`}
15
+ ></i>
16
+ );
17
+ }
@@ -0,0 +1,61 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { Leaf } from "./Leaf";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ describe("Leaf", () => {
6
+ it("should render text inside leaf", () => {
7
+ // Arrange
8
+ render(
9
+ <Leaf attributes={{}} leaf={{ text: "Hello" }}>
10
+ Hello
11
+ </Leaf>,
12
+ );
13
+
14
+ // Act
15
+
16
+ // Assert
17
+ expect(screen.getByText("Hello")).toBeDefined();
18
+ });
19
+
20
+ it("should render bold text inside leaf", () => {
21
+ // Arrange
22
+ const { container } = render(
23
+ <Leaf attributes={{}} leaf={{ text: "Hello", bold: true }}>
24
+ Hello
25
+ </Leaf>,
26
+ );
27
+
28
+ // Act
29
+
30
+ // Assert
31
+ expect(container.getElementsByClassName("bold")).toBeDefined();
32
+ });
33
+
34
+ it("should render italic text inside leaf", () => {
35
+ // Arrange
36
+ const { container } = render(
37
+ <Leaf attributes={{}} leaf={{ text: "Hello", italic: true }}>
38
+ Hello
39
+ </Leaf>,
40
+ );
41
+
42
+ // Act
43
+
44
+ // Assert
45
+ expect(container.getElementsByClassName("italic")).toBeDefined();
46
+ });
47
+
48
+ it("should render underline text inside leaf", () => {
49
+ // Arrange
50
+ const { container } = render(
51
+ <Leaf attributes={{}} leaf={{ text: "Hello", underline: true }}>
52
+ Hello
53
+ </Leaf>,
54
+ );
55
+
56
+ // Act
57
+
58
+ // Assert
59
+ expect(container.getElementsByClassName("underline")).toBeDefined();
60
+ });
61
+ });
@@ -0,0 +1,22 @@
1
+ import React from "react";
2
+ import { DeltaLeaf } from "../types";
3
+ import { DecoratorBuilder, fontSize, fontStyle, fontWeight, textDecoration } from "../utils/decorator";
4
+ import { RenderLeafProps } from "slate-react";
5
+
6
+ export function Leaf(props: RenderLeafProps): React.ReactElement {
7
+ function buildDecorations(): string {
8
+ return new DecoratorBuilder<DeltaLeaf>(props.leaf)
9
+ .add(fontWeight)
10
+ .add(fontSize)
11
+ .add(fontStyle)
12
+ .add(textDecoration)
13
+ .build()([])
14
+ .join(" ");
15
+ }
16
+
17
+ return (
18
+ <span {...props.attributes} className={`${buildDecorations()} ${props.leaf.text === "" ? "pl-0.5" : ""}`}>
19
+ {props.children}
20
+ </span>
21
+ );
22
+ }
@@ -0,0 +1,16 @@
1
+ import { HTMLAttributes, ReactNode } from "react";
2
+ import { CodeElement as CodeElementType } from "../../types";
3
+
4
+ export function CodeElement(props: {
5
+ attributes: HTMLAttributes<HTMLElement>;
6
+ children: ReactNode;
7
+ element: CodeElementType;
8
+ }): React.ReactElement {
9
+ return (
10
+ <pre {...props.attributes}>
11
+ <code>{props.children}</code>
12
+ </pre>
13
+ );
14
+ }
15
+
16
+ export default CodeElement;
@@ -0,0 +1,33 @@
1
+ import React, { HTMLAttributes, ReactNode } from "react";
2
+ import { ContentValueElement as ContentValueElementType } from "../../types";
3
+ import { useSelected } from "slate-react";
4
+ import { toPlaintext } from "@platecms/delta-cast-util-to-plaintext";
5
+
6
+ export function ContentValueElement({
7
+ attributes,
8
+ children,
9
+ element,
10
+ }: {
11
+ attributes: HTMLAttributes<HTMLElement>;
12
+ children: ReactNode;
13
+ element: ContentValueElementType;
14
+ }): React.ReactElement {
15
+ const selected = useSelected();
16
+
17
+ const style: React.CSSProperties = {
18
+ fontWeight: element.children[0].bold ? "bold" : "normal",
19
+ fontStyle: element.children[0].italic ? "italic" : "normal",
20
+ textDecoration: `${element.children[0].underline ? "underline" : ""} ${element.children[0].strikethrough ? "line-through" : ""}`,
21
+ };
22
+
23
+ return (
24
+ <span className={`${selected ? "bg-[#EAEAFA]" : ""} border border-[#705ED9] py-1 rounded-l-sm`}>
25
+ <span {...attributes} contentEditable={false} className={`border-[#705ED9] px-2 py-1.5 border-r-3`} style={style}>
26
+ {children}
27
+ {element.root ? toPlaintext(element.root) : ""}
28
+ </span>
29
+ </span>
30
+ );
31
+ }
32
+
33
+ export default ContentValueElement;
@@ -0,0 +1,44 @@
1
+ import React, { HTMLAttributes, ReactNode, useEffect } from "react";
2
+ import { useSelected, useSlate } from "slate-react";
3
+ import { LinkElement as LinkElementType } from "../../types";
4
+ import { Editor, Element, Transforms } from "slate";
5
+
6
+ export function LinkElement({
7
+ attributes,
8
+ children,
9
+ element,
10
+ }: {
11
+ attributes: HTMLAttributes<HTMLElement>;
12
+ children: ReactNode;
13
+ element: LinkElementType;
14
+ }): React.ReactElement {
15
+ const selected = useSelected();
16
+ const editor = useSlate();
17
+
18
+ const style: React.CSSProperties = {
19
+ fontWeight: element.children[0].bold ? "bold" : "normal",
20
+ fontStyle: element.children[0].italic ? "italic" : "normal",
21
+ };
22
+
23
+ useEffect(() => {
24
+ if (element.children[0].text.length === 0) {
25
+ Transforms.unwrapNodes(editor, {
26
+ match: (node) => !Editor.isEditor(node) && Element.isElement(node) && node.type === "link",
27
+ });
28
+ }
29
+ }, [element]);
30
+
31
+ return (
32
+ <span className="relative">
33
+ <div
34
+ className={`${selected ? "bg-blue-400/20 px-1" : "border-transparent underline text-primary"} inline-block transition-all duration-300`}
35
+ style={style}
36
+ {...attributes}
37
+ >
38
+ {children}
39
+ </div>
40
+ </span>
41
+ );
42
+ }
43
+
44
+ export default LinkElement;
@@ -0,0 +1,22 @@
1
+ export function SearchInput({
2
+ onChange,
3
+ value,
4
+ placeholder,
5
+ }: {
6
+ onChange: (value: string) => void;
7
+ value: string;
8
+ placeholder: string;
9
+ }): React.ReactElement {
10
+ return (
11
+ <input
12
+ onChange={(event) => {
13
+ onChange(event.target.value);
14
+ }}
15
+ placeholder={placeholder}
16
+ value={value}
17
+ name="search"
18
+ className={"bg-gray-50 w-full outline-none rounded-md px-3 py-2"}
19
+ type="text"
20
+ />
21
+ );
22
+ }
@@ -0,0 +1,30 @@
1
+ export function TextInput({
2
+ onChange,
3
+ name = "text",
4
+ value,
5
+ placeholder,
6
+ label,
7
+ }: {
8
+ onChange: (value: string) => void;
9
+ name?: string;
10
+ label: string;
11
+ value: string;
12
+ placeholder: string;
13
+ }): React.ReactElement {
14
+ return (
15
+ <div className={"flex flex-col gap-1 w-full"}>
16
+ <label className={"font-semibold text-xs"}>{label}</label>
17
+ <input
18
+ onChange={(event) => {
19
+ onChange(event.target.value);
20
+ }}
21
+ placeholder={placeholder}
22
+ value={value}
23
+ name={name}
24
+ className={"w-full outline-none rounded-md px-5 py-3 border border-gray-300"}
25
+ type="text"
26
+ data-testid="text-input"
27
+ />
28
+ </div>
29
+ );
30
+ }