@squiz/formatted-text-editor 1.21.1-alpha.3 → 1.21.1-alpha.30

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 (168) hide show
  1. package/demo/App.tsx +45 -10
  2. package/demo/index.scss +11 -10
  3. package/jest.config.ts +0 -2
  4. package/lib/Editor/Editor.d.ts +1 -0
  5. package/lib/Editor/Editor.js +45 -7
  6. package/lib/Editor/EditorContext.d.ts +10 -0
  7. package/lib/Editor/EditorContext.js +15 -0
  8. package/lib/EditorToolbar/FloatingToolbar.d.ts +1 -0
  9. package/lib/EditorToolbar/FloatingToolbar.js +11 -5
  10. package/lib/EditorToolbar/Toolbar.d.ts +1 -0
  11. package/lib/EditorToolbar/Toolbar.js +3 -1
  12. package/lib/EditorToolbar/Tools/Bold/BoldButton.d.ts +1 -0
  13. package/lib/EditorToolbar/Tools/Bold/BoldButton.js +2 -2
  14. package/lib/EditorToolbar/Tools/Image/Form/ImageForm.d.ts +17 -0
  15. package/lib/EditorToolbar/Tools/Image/Form/ImageForm.js +82 -0
  16. package/lib/EditorToolbar/Tools/Image/ImageButton.d.ts +6 -0
  17. package/lib/EditorToolbar/Tools/Image/ImageButton.js +77 -0
  18. package/lib/EditorToolbar/Tools/Image/ImageModal.d.ts +9 -0
  19. package/lib/EditorToolbar/Tools/Image/ImageModal.js +16 -0
  20. package/lib/EditorToolbar/Tools/Italic/ItalicButton.d.ts +1 -0
  21. package/lib/EditorToolbar/Tools/Italic/ItalicButton.js +2 -2
  22. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.d.ts +14 -5
  23. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +67 -15
  24. package/lib/EditorToolbar/Tools/Link/LinkButton.d.ts +1 -0
  25. package/lib/EditorToolbar/Tools/Link/LinkButton.js +22 -14
  26. package/lib/EditorToolbar/Tools/Link/LinkModal.d.ts +1 -0
  27. package/lib/EditorToolbar/Tools/Link/LinkModal.js +12 -5
  28. package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.d.ts +1 -0
  29. package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +2 -9
  30. package/lib/EditorToolbar/Tools/Redo/RedoButton.d.ts +1 -0
  31. package/lib/EditorToolbar/Tools/Redo/RedoButton.js +2 -2
  32. package/lib/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.d.ts +1 -0
  33. package/lib/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.js +2 -2
  34. package/lib/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.d.ts +1 -0
  35. package/lib/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.js +2 -2
  36. package/lib/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.d.ts +1 -0
  37. package/lib/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.js +2 -2
  38. package/lib/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.d.ts +1 -0
  39. package/lib/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.js +2 -2
  40. package/lib/EditorToolbar/Tools/TextAlign/TextAlignButtons.d.ts +1 -0
  41. package/lib/EditorToolbar/Tools/TextType/Heading/HeadingButton.d.ts +1 -0
  42. package/lib/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.d.ts +1 -0
  43. package/lib/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.d.ts +1 -0
  44. package/lib/EditorToolbar/Tools/TextType/TextTypeDropdown.d.ts +1 -0
  45. package/lib/EditorToolbar/Tools/Underline/UnderlineButton.d.ts +1 -0
  46. package/lib/EditorToolbar/Tools/Underline/UnderlineButton.js +2 -2
  47. package/lib/EditorToolbar/Tools/Undo/UndoButton.d.ts +1 -0
  48. package/lib/EditorToolbar/Tools/Undo/UndoButton.js +2 -2
  49. package/lib/Extensions/CommandsExtension/CommandsExtension.d.ts +20 -0
  50. package/lib/Extensions/CommandsExtension/CommandsExtension.js +52 -0
  51. package/lib/Extensions/Extensions.d.ts +7 -4
  52. package/lib/Extensions/Extensions.js +32 -19
  53. package/lib/Extensions/ImageExtension/ImageExtension.d.ts +10 -0
  54. package/lib/Extensions/ImageExtension/ImageExtension.js +92 -0
  55. package/lib/Extensions/LinkExtension/AssetLinkExtension.d.ts +26 -0
  56. package/lib/Extensions/LinkExtension/AssetLinkExtension.js +102 -0
  57. package/lib/Extensions/LinkExtension/LinkExtension.d.ts +21 -12
  58. package/lib/Extensions/LinkExtension/LinkExtension.js +63 -65
  59. package/lib/Extensions/LinkExtension/common.d.ts +7 -0
  60. package/lib/Extensions/LinkExtension/common.js +14 -0
  61. package/lib/Extensions/PreformattedExtension/PreformattedExtension.d.ts +1 -1
  62. package/lib/hooks/index.d.ts +1 -0
  63. package/lib/hooks/index.js +1 -0
  64. package/lib/hooks/useExpandedSelection.d.ts +23 -0
  65. package/lib/hooks/useExpandedSelection.js +37 -0
  66. package/lib/index.css +159 -74
  67. package/lib/index.d.ts +5 -2
  68. package/lib/index.js +9 -3
  69. package/lib/types.d.ts +3 -0
  70. package/lib/types.js +2 -0
  71. package/lib/ui/Button/Button.d.ts +11 -0
  72. package/lib/ui/{ToolbarButton/ToolbarButton.js → Button/Button.js} +6 -3
  73. package/lib/ui/Fields/Input/Input.d.ts +5 -0
  74. package/lib/ui/{Inputs/Text/TextInput.js → Fields/Input/Input.js} +10 -5
  75. package/lib/ui/{Inputs → Fields}/Select/Select.d.ts +1 -0
  76. package/lib/ui/Modal/Modal.js +5 -3
  77. package/lib/ui/ToolbarDropdown/ToolbarDropdown.d.ts +1 -0
  78. package/lib/ui/ToolbarDropdownButton/ToolbarDropdownButton.d.ts +1 -0
  79. package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.d.ts +9 -0
  80. package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +165 -0
  81. package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.d.ts +9 -0
  82. package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +129 -0
  83. package/lib/utils/undefinedIfEmpty.d.ts +1 -0
  84. package/lib/utils/undefinedIfEmpty.js +7 -0
  85. package/package.json +11 -4
  86. package/src/Editor/Editor.spec.tsx +78 -18
  87. package/src/Editor/Editor.tsx +28 -9
  88. package/src/Editor/EditorContext.spec.tsx +26 -0
  89. package/src/Editor/EditorContext.ts +19 -0
  90. package/src/Editor/_editor.scss +20 -51
  91. package/src/EditorToolbar/FloatingToolbar.spec.tsx +2 -3
  92. package/src/EditorToolbar/FloatingToolbar.tsx +15 -6
  93. package/src/EditorToolbar/Toolbar.tsx +2 -0
  94. package/src/EditorToolbar/Tools/Bold/BoldButton.spec.tsx +1 -1
  95. package/src/EditorToolbar/Tools/Bold/BoldButton.tsx +2 -2
  96. package/src/EditorToolbar/Tools/Image/Form/ImageForm.spec.tsx +77 -0
  97. package/src/EditorToolbar/Tools/Image/Form/ImageForm.tsx +90 -0
  98. package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +135 -0
  99. package/src/EditorToolbar/Tools/Image/ImageButton.tsx +72 -0
  100. package/src/EditorToolbar/Tools/Image/ImageModal.spec.tsx +83 -0
  101. package/src/EditorToolbar/Tools/Image/ImageModal.tsx +24 -0
  102. package/src/EditorToolbar/Tools/Italic/ItalicButton.spec.tsx +1 -1
  103. package/src/EditorToolbar/Tools/Italic/ItalicButton.tsx +2 -2
  104. package/src/EditorToolbar/Tools/Link/Form/LinkForm.spec.tsx +37 -9
  105. package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +97 -27
  106. package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +104 -26
  107. package/src/EditorToolbar/Tools/Link/LinkButton.tsx +30 -21
  108. package/src/EditorToolbar/Tools/Link/LinkModal.tsx +13 -6
  109. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.spec.tsx +26 -26
  110. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +4 -12
  111. package/src/EditorToolbar/Tools/Redo/RedoButton.tsx +2 -2
  112. package/src/EditorToolbar/Tools/TextAlign/CenterAlign/CenterAlignButton.tsx +2 -2
  113. package/src/EditorToolbar/Tools/TextAlign/JustifyAlign/JustifyAlignButton.tsx +2 -2
  114. package/src/EditorToolbar/Tools/TextAlign/LeftAlign/LeftAlignButton.tsx +2 -2
  115. package/src/EditorToolbar/Tools/TextAlign/RightAlign/RightAlignButton.tsx +2 -2
  116. package/src/EditorToolbar/Tools/Underline/Underline.spec.tsx +1 -1
  117. package/src/EditorToolbar/Tools/Underline/UnderlineButton.tsx +2 -2
  118. package/src/EditorToolbar/Tools/Undo/UndoButton.spec.tsx +22 -1
  119. package/src/EditorToolbar/Tools/Undo/UndoButton.tsx +2 -2
  120. package/src/EditorToolbar/_floating-toolbar.scss +5 -0
  121. package/src/EditorToolbar/_toolbar.scss +11 -5
  122. package/src/Extensions/CommandsExtension/CommandsExtension.ts +54 -0
  123. package/src/Extensions/Extensions.ts +32 -17
  124. package/src/Extensions/ImageExtension/ImageExtension.ts +112 -0
  125. package/src/Extensions/LinkExtension/AssetLinkExtension.spec.ts +104 -0
  126. package/src/Extensions/LinkExtension/AssetLinkExtension.ts +128 -0
  127. package/src/Extensions/LinkExtension/LinkExtension.spec.ts +68 -0
  128. package/src/Extensions/LinkExtension/LinkExtension.ts +88 -82
  129. package/src/Extensions/LinkExtension/common.ts +10 -0
  130. package/src/hooks/index.ts +1 -0
  131. package/src/hooks/useExpandedSelection.ts +44 -0
  132. package/src/index.scss +2 -2
  133. package/src/index.ts +5 -2
  134. package/src/types.ts +5 -0
  135. package/src/ui/Button/Button.spec.tsx +44 -0
  136. package/src/ui/Button/Button.tsx +29 -0
  137. package/src/ui/{_buttons.scss → Button/_button.scss} +19 -1
  138. package/src/ui/{Inputs/Text/TextInput.spec.tsx → Fields/Input/Input.spec.tsx} +8 -8
  139. package/src/ui/Fields/Input/Input.tsx +34 -0
  140. package/src/ui/Modal/Modal.spec.tsx +15 -0
  141. package/src/ui/Modal/Modal.tsx +8 -4
  142. package/src/ui/ToolbarDropdown/_toolbar-dropdown.scss +1 -1
  143. package/src/ui/_forms.scss +14 -0
  144. package/src/ui/_typography.scss +46 -0
  145. package/src/utils/converters/mocks/squizNodeJson.mock.ts +252 -0
  146. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.spec.ts +480 -0
  147. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +202 -0
  148. package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.spec.ts +329 -0
  149. package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +151 -0
  150. package/src/utils/undefinedIfEmpty.spec.ts +12 -0
  151. package/src/utils/undefinedIfEmpty.ts +3 -0
  152. package/tailwind.config.cjs +3 -0
  153. package/tests/renderWithEditor.tsx +28 -15
  154. package/tsconfig.json +1 -1
  155. package/lib/FormattedTextEditor.d.ts +0 -2
  156. package/lib/FormattedTextEditor.js +0 -7
  157. package/lib/ui/Inputs/Text/TextInput.d.ts +0 -4
  158. package/lib/ui/ToolbarButton/ToolbarButton.d.ts +0 -10
  159. package/src/Editor/Editor.mock.tsx +0 -43
  160. package/src/FormattedTextEditor.spec.tsx +0 -10
  161. package/src/FormattedTextEditor.tsx +0 -3
  162. package/src/ui/Inputs/Text/TextInput.tsx +0 -20
  163. package/src/ui/ToolbarButton/ToolbarButton.tsx +0 -26
  164. package/src/ui/ToolbarButton/_toolbar-button.scss +0 -17
  165. /package/lib/ui/{Inputs → Fields}/Select/Select.js +0 -0
  166. /package/src/ui/{Inputs → Fields}/Select/Select.spec.tsx +0 -0
  167. /package/src/ui/{Inputs → Fields}/Select/Select.tsx +0 -0
  168. /package/tests/{select.tsx → select.ts} +0 -0
@@ -0,0 +1,329 @@
1
+ import { FORMATTED_TEXT_MODELS as FormattedTextModels } from '@squiz/dx-json-schema-lib';
2
+ import { RemirrorJSON } from '@remirror/core';
3
+ import { squizNodeToRemirrorNode } from './squizNodeToRemirrorNode';
4
+ import {
5
+ mockSquizNodeJson,
6
+ mockSquizNodeTextJson,
7
+ sharedNodeExamples,
8
+ squizOnlyNodeExamples,
9
+ } from '../mocks/squizNodeJson.mock';
10
+
11
+ type FormattedText = FormattedTextModels.v1.FormattedText;
12
+
13
+ describe('squizNodeToRemirrorNode', () => {
14
+ it('should convert complex Squiz component JSON to Remirror JSON', () => {
15
+ expect(squizNodeToRemirrorNode(mockSquizNodeJson)).toEqual({
16
+ type: 'doc',
17
+ content: [
18
+ {
19
+ type: 'paragraph',
20
+ attrs: {
21
+ nodeIndent: null,
22
+ nodeTextAlignment: null,
23
+ nodeLineHeight: null,
24
+ style: '',
25
+ },
26
+ content: [
27
+ {
28
+ type: 'text',
29
+ text: 'Hello ',
30
+ },
31
+ {
32
+ type: 'text',
33
+ marks: [
34
+ {
35
+ type: 'link',
36
+ attrs: {
37
+ href: 'https://www.google.com',
38
+ target: null,
39
+ auto: false,
40
+ title: null,
41
+ },
42
+ },
43
+ {
44
+ type: 'bold',
45
+ },
46
+ ],
47
+ text: 'Mr Bean',
48
+ },
49
+ {
50
+ type: 'text',
51
+ text: ', nice to ',
52
+ },
53
+ {
54
+ type: 'text',
55
+ marks: [
56
+ {
57
+ type: 'link',
58
+ attrs: {
59
+ href: 'https://www.google.com',
60
+ target: null,
61
+ auto: false,
62
+ title: null,
63
+ },
64
+ },
65
+ ],
66
+ text: 'meet you',
67
+ },
68
+ {
69
+ type: 'text',
70
+ text: '.',
71
+ },
72
+ {
73
+ type: 'image',
74
+ attrs: {
75
+ alt: 'Test',
76
+ height: '150',
77
+ width: '200',
78
+ src: 'https://media2.giphy.com/media/3o6ozsIxg5legYvggo/giphy.gif',
79
+ title: '',
80
+ },
81
+ },
82
+ ],
83
+ },
84
+ ],
85
+ });
86
+ });
87
+
88
+ it('should convert top level text component JSON to Remirror JSON', () => {
89
+ expect(squizNodeToRemirrorNode(mockSquizNodeTextJson)).toEqual({
90
+ content: [
91
+ {
92
+ type: 'paragraph',
93
+ content: [
94
+ {
95
+ type: 'text',
96
+ text: 'Hello world!',
97
+ },
98
+ {
99
+ type: 'text',
100
+ text: 'Another one...',
101
+ },
102
+ ],
103
+ },
104
+ ],
105
+ type: 'doc',
106
+ });
107
+ });
108
+
109
+ it('should handle empty Squiz component JSON', () => {
110
+ expect(squizNodeToRemirrorNode([])).toEqual({ content: [], type: 'doc' });
111
+ expect(
112
+ squizNodeToRemirrorNode([
113
+ {
114
+ children: [],
115
+ type: 'tag',
116
+ tag: 'p',
117
+ },
118
+ ]),
119
+ ).toEqual({
120
+ content: [
121
+ {
122
+ attrs: { nodeIndent: null, nodeLineHeight: null, nodeTextAlignment: null, style: '' },
123
+ type: 'paragraph',
124
+ },
125
+ ],
126
+ type: 'doc',
127
+ });
128
+ });
129
+
130
+ it.each([
131
+ [
132
+ 'Unsupported DOM tag',
133
+ [
134
+ {
135
+ children: [
136
+ {
137
+ type: 'text',
138
+ value: 'Should throw an error.',
139
+ },
140
+ ],
141
+ type: 'tag',
142
+ tag: 'code',
143
+ },
144
+ ],
145
+ 'Unsupported node type provided: tag (tag: code)',
146
+ ],
147
+ [
148
+ 'Unsupported node type',
149
+ [
150
+ {
151
+ children: [
152
+ {
153
+ type: 'text',
154
+ value: 'Should throw an error.',
155
+ },
156
+ ],
157
+ type: 'unsupported-type',
158
+ },
159
+ ],
160
+ 'Unsupported node type provided: unsupported-type',
161
+ ],
162
+ ])('should throw an error for non supported node types', (description: string, node: any, expectedError: string) => {
163
+ expect(() => squizNodeToRemirrorNode(node)).toThrow(expectedError);
164
+ });
165
+
166
+ it('should handle pre formatted text', () => {
167
+ const squizComponentJSON: FormattedText = [
168
+ {
169
+ children: [
170
+ {
171
+ type: 'text',
172
+ value: 'Hello world!',
173
+ },
174
+ ],
175
+ type: 'tag',
176
+ tag: 'pre',
177
+ },
178
+ ];
179
+
180
+ const expected: RemirrorJSON = {
181
+ content: [
182
+ {
183
+ attrs: { nodeIndent: null, nodeLineHeight: null, nodeTextAlignment: null, style: '' },
184
+ content: [{ text: 'Hello world!', type: 'text' }],
185
+ type: 'preformatted',
186
+ },
187
+ ],
188
+ type: 'doc',
189
+ };
190
+
191
+ const result = squizNodeToRemirrorNode(squizComponentJSON);
192
+ expect(result).toEqual(expected);
193
+ });
194
+
195
+ it('should handle images', () => {
196
+ const squizComponentJSON: FormattedText = [
197
+ {
198
+ children: [
199
+ {
200
+ children: [],
201
+ attributes: {
202
+ alt: 'This is a test alt',
203
+ height: '360',
204
+ width: '480',
205
+ src: 'https://media2.giphy.com/media/3o6ozsIxg5legYvggo/giphy.gif',
206
+ title: '',
207
+ },
208
+ type: 'tag',
209
+ tag: 'img',
210
+ },
211
+ ],
212
+ type: 'tag',
213
+ tag: 'p',
214
+ },
215
+ ];
216
+
217
+ const expected: RemirrorJSON = {
218
+ content: [
219
+ {
220
+ attrs: { nodeIndent: null, nodeLineHeight: null, nodeTextAlignment: null, style: '' },
221
+ content: [
222
+ {
223
+ type: 'image',
224
+ attrs: {
225
+ alt: 'This is a test alt',
226
+ height: '360',
227
+ width: '480',
228
+ src: 'https://media2.giphy.com/media/3o6ozsIxg5legYvggo/giphy.gif',
229
+ title: '',
230
+ },
231
+ },
232
+ ],
233
+ type: 'paragraph',
234
+ },
235
+ ],
236
+ type: 'doc',
237
+ };
238
+
239
+ const result = squizNodeToRemirrorNode(squizComponentJSON);
240
+ expect(result).toEqual(expected);
241
+ });
242
+
243
+ it.each([
244
+ ['italics', 'italic'],
245
+ ['bold', 'bold'],
246
+ ['underline', 'underline'],
247
+ ])('should handle %s formatting', (a, b) => {
248
+ const squizComponentJSON: FormattedText = [
249
+ {
250
+ children: [
251
+ {
252
+ children: [
253
+ {
254
+ type: 'text',
255
+ value: 'Hello world!',
256
+ },
257
+ ],
258
+ font: {
259
+ [a]: true,
260
+ },
261
+ tag: 'span',
262
+ type: 'tag',
263
+ },
264
+ ],
265
+ type: 'tag',
266
+ tag: 'p',
267
+ },
268
+ ];
269
+
270
+ const expected: RemirrorJSON = {
271
+ content: [
272
+ {
273
+ attrs: { nodeIndent: null, nodeLineHeight: null, nodeTextAlignment: null, style: '' },
274
+ content: [{ text: 'Hello world!', marks: [{ type: b }], type: 'text' }],
275
+ type: 'paragraph',
276
+ },
277
+ ],
278
+ type: 'doc',
279
+ };
280
+
281
+ const result = squizNodeToRemirrorNode(squizComponentJSON);
282
+ expect(result).toEqual(expected);
283
+ });
284
+
285
+ it.each([1, 2, 3, 4, 5, 6])('should handle heading %s and set it to level %s', (level) => {
286
+ const squizComponentJSON: FormattedText = [
287
+ {
288
+ children: [
289
+ {
290
+ type: 'text',
291
+ value: 'Hello world!',
292
+ },
293
+ ],
294
+ type: 'tag',
295
+ tag: `h${level}`,
296
+ },
297
+ ];
298
+
299
+ const expected: RemirrorJSON = {
300
+ content: [
301
+ {
302
+ attrs: { nodeIndent: null, nodeLineHeight: null, nodeTextAlignment: null, style: '', level },
303
+ content: [{ text: 'Hello world!', type: 'text' }],
304
+ type: 'heading',
305
+ },
306
+ ],
307
+ type: 'doc',
308
+ };
309
+
310
+ const result = squizNodeToRemirrorNode(squizComponentJSON);
311
+ expect(result).toEqual(expected);
312
+ });
313
+
314
+ it.each([...sharedNodeExamples, ...squizOnlyNodeExamples])(
315
+ 'should convert a Squiz node to the expected Remirror representation - $description',
316
+ async ({ remirrorNode, squizNode }: any) => {
317
+ const result = squizNodeToRemirrorNode(squizNode);
318
+ expect(result).toEqual({
319
+ type: 'doc',
320
+ content: [
321
+ {
322
+ type: 'paragraph',
323
+ content: [remirrorNode],
324
+ },
325
+ ],
326
+ });
327
+ },
328
+ );
329
+ });
@@ -0,0 +1,151 @@
1
+ import { RemirrorJSON, ObjectMark } from '@remirror/core';
2
+ import { Attrs } from 'prosemirror-model';
3
+ import { FORMATTED_TEXT_MODELS as FormattedTextModels } from '@squiz/dx-json-schema-lib';
4
+ import { undefinedIfEmpty } from '../../undefinedIfEmpty';
5
+
6
+ type FormattedText = FormattedTextModels.v1.FormattedText;
7
+ type FormattedNodes = FormattedTextModels.v1.FormattedNodes;
8
+
9
+ const getNodeType = (node: FormattedNodes): string => {
10
+ const typeMap: Record<string, string> = {
11
+ 'link-to-matrix-asset': 'text',
12
+ text: 'text',
13
+ };
14
+
15
+ const tagMap: Record<string, string> = {
16
+ h1: 'heading',
17
+ h2: 'heading',
18
+ h3: 'heading',
19
+ h4: 'heading',
20
+ h5: 'heading',
21
+ h6: 'heading',
22
+ img: 'image',
23
+ pre: 'preformatted',
24
+ p: 'paragraph',
25
+ a: 'text',
26
+ span: 'text',
27
+ };
28
+
29
+ if (typeMap[node.type]) {
30
+ return typeMap[node.type];
31
+ }
32
+
33
+ if (node.type === 'tag' && tagMap[node.tag]) {
34
+ return tagMap[node.tag];
35
+ }
36
+
37
+ // Unsupported node type
38
+ throw new Error(
39
+ node.type === 'tag'
40
+ ? `Unsupported node type provided: ${node.type} (tag: ${node.tag})`
41
+ : `Unsupported node type provided: ${node.type}`,
42
+ );
43
+ };
44
+
45
+ const getNodeAttributes = (node: FormattedNodes): Attrs => {
46
+ if (node.type === 'tag' && node.tag === 'img') {
47
+ return {
48
+ alt: node.attributes?.alt,
49
+ height: node.attributes?.height,
50
+ width: node.attributes?.width,
51
+ src: node.attributes?.src,
52
+ title: node.attributes?.title,
53
+ };
54
+ } else if (node.type === 'tag') {
55
+ return {
56
+ nodeIndent: null,
57
+ nodeTextAlignment: node.formattingOptions?.alignment || null,
58
+ nodeLineHeight: null,
59
+ style: '',
60
+ level: node.tag?.startsWith('h') ? parseInt(node.tag.substring(1)) : undefined,
61
+ };
62
+ }
63
+
64
+ return {};
65
+ };
66
+
67
+ const getNodeMarks = (node: FormattedNodes): ObjectMark[] => {
68
+ const marks: ObjectMark[] = [];
69
+
70
+ if (node.type === 'tag' && node.tag === 'a') {
71
+ marks.push({
72
+ type: 'link',
73
+ attrs: {
74
+ href: node.attributes?.href,
75
+ target: node.attributes?.target ?? null,
76
+ auto: false,
77
+ title: node.attributes?.title ?? null,
78
+ },
79
+ });
80
+ } else if (node.type === 'link-to-matrix-asset') {
81
+ marks.push({
82
+ type: 'assetLink',
83
+ attrs: {
84
+ matrixAssetId: node.matrixAssetId,
85
+ matrixDomain: node.matrixDomain,
86
+ matrixIdentifier: node.matrixIdentifier,
87
+ target: node.target,
88
+ },
89
+ });
90
+ }
91
+
92
+ // Handle font formatting
93
+ if ('font' in node) {
94
+ node.font?.bold && marks.push({ type: 'bold' });
95
+ node.font?.italics && marks.push({ type: 'italic' });
96
+ node.font?.underline && marks.push({ type: 'underline' });
97
+ }
98
+
99
+ return marks;
100
+ };
101
+
102
+ const unwrapNodeIfNeeded = (node: RemirrorJSON): RemirrorJSON[] => {
103
+ if (node.type === 'text' && node.content?.length) {
104
+ return node.content.map((child) => {
105
+ return {
106
+ ...child,
107
+ marks: [...(child.marks || []), ...(node.marks || [])],
108
+ };
109
+ });
110
+ }
111
+
112
+ return [node];
113
+ };
114
+
115
+ const formatNode = (node: FormattedNodes): RemirrorJSON[] => {
116
+ const children: RemirrorJSON[] = [];
117
+
118
+ if ('children' in node) {
119
+ node.children.forEach((child: FormattedNodes) => {
120
+ children.push(...formatNode(child));
121
+ });
122
+ }
123
+
124
+ return unwrapNodeIfNeeded({
125
+ type: getNodeType(node),
126
+ attrs: undefinedIfEmpty(getNodeAttributes(node)),
127
+ marks: undefinedIfEmpty(getNodeMarks(node)),
128
+ text: node.type === 'text' ? node.value : undefined,
129
+ content: undefinedIfEmpty(children),
130
+ });
131
+ };
132
+
133
+ /**
134
+ * Converts Squiz component JSON structure to Remirror node JSON structure.
135
+ * @param {FormattedText} nodes Squiz nodes to convert to Remirror.
136
+ * @export
137
+ * @returns {RemirrorJSON} The converted Remirror JSON.
138
+ */
139
+ export const squizNodeToRemirrorNode = (nodes: FormattedText): RemirrorJSON => {
140
+ let children: RemirrorJSON[] = [];
141
+
142
+ nodes.forEach((node) => {
143
+ children.push(...formatNode(node));
144
+ });
145
+
146
+ if (children.find((child) => child.type === 'text')) {
147
+ children = [{ type: 'paragraph', content: children }];
148
+ }
149
+
150
+ return { type: 'doc', content: children };
151
+ };
@@ -0,0 +1,12 @@
1
+ import { undefinedIfEmpty } from './undefinedIfEmpty';
2
+
3
+ describe('undefinedIfEmpty', () => {
4
+ it.each([
5
+ ['empty object', {}, undefined],
6
+ ['non-empty object', { a: 'not empty' }, { a: 'not empty' }],
7
+ ['empty array', [], undefined],
8
+ ['non-empty array', ['not empty'], ['not empty']],
9
+ ])('Returns expected for %s', (description: string, object: object, expected: object | undefined) => {
10
+ expect(undefinedIfEmpty(object)).toEqual(expected);
11
+ });
12
+ });
@@ -0,0 +1,3 @@
1
+ export const undefinedIfEmpty = <T extends object>(object: T): T | undefined => {
2
+ return Object.keys(object).length > 0 ? object : undefined;
3
+ };
@@ -75,6 +75,9 @@ module.exports = {
75
75
  300: '#0774d2',
76
76
  400: '#044985',
77
77
  },
78
+ red: {
79
+ 300: '#d72321',
80
+ },
78
81
  },
79
82
  },
80
83
  },
@@ -1,19 +1,26 @@
1
- import React, { ReactElement, useEffect } from 'react';
1
+ import React, { ReactElement, useContext, useEffect } from 'react';
2
2
  import { render, RenderOptions } from '@testing-library/react';
3
3
  import { Extension, RemirrorContentType, RemirrorManager } from '@remirror/core';
4
4
  import { CorePreset } from '@remirror/preset-core';
5
5
  import { BuiltinPreset } from 'remirror';
6
6
  import { EditorComponent, Remirror, useRemirror } from '@remirror/react';
7
- import { Extensions } from '../src/Extensions/Extensions';
8
7
  import { RemirrorTestChain } from 'jest-remirror';
8
+ import merge from 'deepmerge';
9
+ import { createExtensions } from '../src/Extensions/Extensions';
10
+ import { EditorContext } from '../src/Editor/EditorContext';
11
+ import { FloatingToolbar } from '../src/EditorToolbar/FloatingToolbar';
12
+ import { defaultEditorContext, EditorContextOptions } from '../src/Editor/EditorContext';
13
+ import { DeepPartial } from '../src/types';
9
14
 
10
15
  export type EditorRenderOptions = RenderOptions & {
11
16
  content?: RemirrorContentType;
17
+ editable?: boolean;
12
18
  extensions?: Extension[];
19
+ context?: DeepPartial<EditorContextOptions>;
13
20
  };
14
21
 
15
22
  type TestEditorProps = EditorRenderOptions & {
16
- children: ReactElement;
23
+ children: ReactElement | null;
17
24
  onReady: (manager: RemirrorManager<Extension>) => void;
18
25
  };
19
26
 
@@ -27,9 +34,10 @@ type EditorRenderResult = {
27
34
  };
28
35
  };
29
36
 
30
- const TestEditor = ({ children, extensions, content, onReady }: TestEditorProps) => {
37
+ const TestEditor = ({ children, extensions, content, onReady, editable }: TestEditorProps) => {
38
+ const context = useContext(EditorContext);
31
39
  const { manager, state, setState } = useRemirror({
32
- extensions: () => extensions || Extensions(),
40
+ extensions: () => extensions || createExtensions(context)(),
33
41
  content: content,
34
42
  selection: 'start',
35
43
  stringHandler: 'html',
@@ -43,12 +51,14 @@ const TestEditor = ({ children, extensions, content, onReady }: TestEditorProps)
43
51
  <Remirror
44
52
  manager={manager}
45
53
  state={state}
54
+ editable={editable}
46
55
  onChange={(params) => {
47
56
  setState(params.state);
48
57
  }}
49
58
  >
50
59
  {children}
51
60
  <EditorComponent />
61
+ {editable && <FloatingToolbar />}
52
62
  </Remirror>
53
63
  );
54
64
  };
@@ -76,9 +86,10 @@ const TestEditor = ({ children, extensions, content, onReady }: TestEditorProps)
76
86
  * @return {Promise<EditorRenderResult>}
77
87
  */
78
88
  export const renderWithEditor = async (
79
- ui: ReactElement,
89
+ ui: ReactElement | null,
80
90
  options?: EditorRenderOptions,
81
91
  ): Promise<EditorRenderResult> => {
92
+ const context = merge(defaultEditorContext, options?.context || {}) as EditorContextOptions;
82
93
  const result: Partial<EditorRenderResult> = {
83
94
  getHtmlContent: () => document.querySelector('.remirror-editor')?.innerHTML,
84
95
  getJsonContent: () => result.editor?.state.doc.content.child(0).toJSON(),
@@ -87,15 +98,17 @@ export const renderWithEditor = async (
87
98
  let isReady = false;
88
99
 
89
100
  const { container } = render(
90
- <TestEditor
91
- onReady={(manager) => {
92
- result.editor = RemirrorTestChain.create(manager);
93
- isReady = true;
94
- }}
95
- {...options}
96
- >
97
- {ui}
98
- </TestEditor>,
101
+ <EditorContext.Provider value={context}>
102
+ <TestEditor
103
+ onReady={(manager) => {
104
+ result.editor = RemirrorTestChain.create(manager);
105
+ isReady = true;
106
+ }}
107
+ {...options}
108
+ >
109
+ {ui}
110
+ </TestEditor>
111
+ </EditorContext.Provider>,
99
112
  );
100
113
 
101
114
  if (!isReady) {
package/tsconfig.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "strict": true,
12
12
  "forceConsistentCasingInFileNames": true,
13
13
  "module": "CommonJS",
14
- "moduleResolution": "Node",
14
+ "moduleResolution": "node",
15
15
  "resolveJsonModule": true,
16
16
  "isolatedModules": true,
17
17
  "jsx": "react",
@@ -1,2 +0,0 @@
1
- import Editor from './Editor/Editor';
2
- export default Editor;
@@ -1,7 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const Editor_1 = __importDefault(require("./Editor/Editor"));
7
- exports.default = Editor_1.default;
@@ -1,4 +0,0 @@
1
- import React from 'react';
2
- export declare const TextInput: React.ForwardRefExoticComponent<React.InputHTMLAttributes<HTMLInputElement> & {
3
- label?: string | undefined;
4
- } & React.RefAttributes<HTMLInputElement>>;
@@ -1,10 +0,0 @@
1
- import { ReactElement } from 'react';
2
- type ToolbarButtonProps = {
3
- handleOnClick: () => void;
4
- isDisabled?: boolean;
5
- isActive: boolean;
6
- icon: ReactElement;
7
- label: string;
8
- };
9
- declare const ToolbarButton: ({ handleOnClick, isDisabled, isActive, icon, label }: ToolbarButtonProps) => JSX.Element;
10
- export default ToolbarButton;
@@ -1,43 +0,0 @@
1
- import { Remirror, useRemirror, useRemirrorContext } from '@remirror/react';
2
- import React from 'react';
3
- import { Toolbar, FloatingToolbar } from '../EditorToolbar';
4
- import { Extensions } from '../Extensions/Extensions';
5
-
6
- type MockEditorProps = {
7
- setContent?: any;
8
- };
9
-
10
- /**
11
- * @deprecated Use "renderWithEditor" and render the specific component being tested instead.
12
- */
13
- export const MockEditor = ({ setContent }: MockEditorProps) => {
14
- const { manager, state, setState } = useRemirror({
15
- extensions: Extensions,
16
- selection: 'start',
17
- stringHandler: 'html',
18
- });
19
-
20
- const Component = () => {
21
- setContent && setContent.mockImplementation(useRemirrorContext().setContent);
22
- return null;
23
- };
24
-
25
- const handleChange = (parameter: { state: any }) => {
26
- setState(parameter.state);
27
- };
28
-
29
- return (
30
- <Remirror
31
- manager={manager}
32
- onChange={handleChange}
33
- placeholder="Write something"
34
- label="Text editor"
35
- state={state}
36
- autoRender="start"
37
- >
38
- <Toolbar />
39
- <Component />
40
- <FloatingToolbar />
41
- </Remirror>
42
- );
43
- };
@@ -1,10 +0,0 @@
1
- import { render } from '@testing-library/react';
2
- import { FormattedTextEditor } from './';
3
- import React from 'react';
4
-
5
- describe('<FormattedTextEditor />', () => {
6
- it('should render "<FormattedTextEditor />" component', () => {
7
- const { baseElement } = render(<FormattedTextEditor />);
8
- expect(baseElement).toBeTruthy();
9
- });
10
- });