@squiz/formatted-text-editor 1.21.1-alpha.9 → 1.22.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/demo/App.tsx +38 -10
- package/demo/index.scss +2 -7
- package/jest.config.ts +0 -2
- package/lib/Editor/Editor.js +45 -7
- package/lib/Editor/EditorContext.d.ts +15 -0
- package/lib/Editor/EditorContext.js +15 -0
- package/lib/EditorToolbar/FloatingToolbar.js +11 -5
- package/lib/EditorToolbar/Tools/Image/Form/ImageForm.d.ts +9 -8
- package/lib/EditorToolbar/Tools/Image/Form/ImageForm.js +91 -23
- package/lib/EditorToolbar/Tools/Image/ImageButton.d.ts +4 -1
- package/lib/EditorToolbar/Tools/Image/ImageButton.js +22 -14
- package/lib/EditorToolbar/Tools/Image/ImageModal.js +9 -5
- package/lib/EditorToolbar/Tools/Link/Form/LinkForm.d.ts +14 -5
- package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +66 -14
- package/lib/EditorToolbar/Tools/Link/LinkButton.js +21 -13
- package/lib/EditorToolbar/Tools/Link/LinkModal.js +12 -5
- package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +1 -8
- package/lib/Extensions/CommandsExtension/CommandsExtension.d.ts +20 -0
- package/lib/Extensions/CommandsExtension/CommandsExtension.js +52 -0
- package/lib/Extensions/Extensions.d.ts +11 -1
- package/lib/Extensions/Extensions.js +42 -20
- package/lib/Extensions/ImageExtension/AssetImageExtension.d.ts +17 -0
- package/lib/Extensions/ImageExtension/AssetImageExtension.js +92 -0
- package/lib/Extensions/ImageExtension/ImageExtension.d.ts +4 -0
- package/lib/Extensions/ImageExtension/ImageExtension.js +11 -0
- package/lib/Extensions/LinkExtension/AssetLinkExtension.d.ts +26 -0
- package/lib/Extensions/LinkExtension/AssetLinkExtension.js +102 -0
- package/lib/Extensions/LinkExtension/LinkExtension.d.ts +19 -12
- package/lib/Extensions/LinkExtension/LinkExtension.js +56 -66
- package/lib/Extensions/LinkExtension/common.d.ts +7 -0
- package/lib/Extensions/LinkExtension/common.js +14 -0
- package/lib/Extensions/PreformattedExtension/PreformattedExtension.js +6 -2
- package/lib/hooks/index.d.ts +1 -0
- package/lib/hooks/index.js +1 -0
- package/lib/hooks/useExpandedSelection.d.ts +23 -0
- package/lib/hooks/useExpandedSelection.js +37 -0
- package/lib/index.css +58 -26
- package/lib/index.d.ts +3 -2
- package/lib/index.js +5 -3
- package/lib/types.d.ts +3 -0
- package/lib/types.js +2 -0
- package/lib/ui/Button/Button.d.ts +2 -1
- package/lib/ui/Button/Button.js +4 -5
- package/lib/ui/Fields/Input/Input.d.ts +1 -0
- package/lib/ui/Fields/Input/Input.js +9 -3
- package/lib/ui/Modal/Modal.js +5 -3
- package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.d.ts +1 -2
- package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +118 -104
- package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +102 -69
- package/lib/utils/resolveMatrixAssetUrl.d.ts +1 -0
- package/lib/utils/resolveMatrixAssetUrl.js +10 -0
- package/lib/utils/undefinedIfEmpty.d.ts +1 -0
- package/lib/utils/undefinedIfEmpty.js +7 -0
- package/package.json +8 -4
- package/src/Editor/Editor.spec.tsx +78 -18
- package/src/Editor/Editor.tsx +28 -9
- package/src/Editor/EditorContext.spec.tsx +26 -0
- package/src/Editor/EditorContext.ts +26 -0
- package/src/Editor/_editor.scss +20 -4
- package/src/EditorToolbar/FloatingToolbar.spec.tsx +26 -7
- package/src/EditorToolbar/FloatingToolbar.tsx +15 -6
- package/src/EditorToolbar/Tools/Image/Form/ImageForm.spec.tsx +81 -6
- package/src/EditorToolbar/Tools/Image/Form/ImageForm.tsx +167 -47
- package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +250 -2
- package/src/EditorToolbar/Tools/Image/ImageButton.tsx +29 -16
- package/src/EditorToolbar/Tools/Image/ImageModal.spec.tsx +59 -20
- package/src/EditorToolbar/Tools/Image/ImageModal.tsx +12 -10
- package/src/EditorToolbar/Tools/Link/Form/LinkForm.spec.tsx +37 -9
- package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +96 -26
- package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +137 -26
- package/src/EditorToolbar/Tools/Link/LinkButton.tsx +28 -19
- package/src/EditorToolbar/Tools/Link/LinkModal.tsx +13 -6
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.spec.tsx +27 -26
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +2 -10
- package/src/EditorToolbar/Tools/Undo/UndoButton.spec.tsx +22 -1
- package/src/EditorToolbar/_floating-toolbar.scss +4 -5
- package/src/EditorToolbar/_toolbar.scss +1 -1
- package/src/Extensions/CommandsExtension/CommandsExtension.ts +54 -0
- package/src/Extensions/Extensions.ts +42 -19
- package/src/Extensions/ImageExtension/AssetImageExtension.spec.ts +76 -0
- package/src/Extensions/ImageExtension/AssetImageExtension.ts +111 -0
- package/src/Extensions/ImageExtension/ImageExtension.ts +17 -1
- package/src/Extensions/LinkExtension/AssetLinkExtension.spec.ts +104 -0
- package/src/Extensions/LinkExtension/AssetLinkExtension.ts +128 -0
- package/src/Extensions/LinkExtension/LinkExtension.spec.ts +68 -0
- package/src/Extensions/LinkExtension/LinkExtension.ts +71 -85
- package/src/Extensions/LinkExtension/common.ts +10 -0
- package/src/Extensions/PreformattedExtension/PreformattedExtension.spec.ts +41 -0
- package/src/Extensions/PreformattedExtension/PreformattedExtension.ts +6 -2
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useExpandedSelection.ts +44 -0
- package/src/index.ts +3 -2
- package/src/types.ts +5 -0
- package/src/ui/Button/Button.tsx +10 -6
- package/src/ui/Button/_button.scss +1 -1
- package/src/ui/Fields/Input/Input.spec.tsx +7 -1
- package/src/ui/Fields/Input/Input.tsx +23 -4
- package/src/ui/Modal/Modal.spec.tsx +15 -0
- package/src/ui/Modal/Modal.tsx +8 -4
- package/src/ui/ToolbarDropdown/_toolbar-dropdown.scss +1 -1
- package/src/ui/_forms.scss +14 -0
- package/src/utils/converters/mocks/squizNodeJson.mock.ts +196 -0
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.spec.ts +41 -6
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +132 -111
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.spec.ts +68 -34
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +115 -79
- package/src/utils/resolveMatrixAssetUrl.spec.ts +26 -0
- package/src/utils/resolveMatrixAssetUrl.ts +7 -0
- package/src/utils/undefinedIfEmpty.spec.ts +12 -0
- package/src/utils/undefinedIfEmpty.ts +3 -0
- package/tailwind.config.cjs +3 -0
- package/tests/renderWithEditor.tsx +26 -13
- package/tsconfig.json +1 -1
- package/lib/FormattedTextEditor.d.ts +0 -2
- package/lib/FormattedTextEditor.js +0 -7
- package/lib/utils/converters/validNodeTypes.d.ts +0 -2
- package/lib/utils/converters/validNodeTypes.js +0 -21
- package/src/Editor/Editor.mock.tsx +0 -43
- package/src/FormattedTextEditor.spec.tsx +0 -10
- package/src/FormattedTextEditor.tsx +0 -3
- package/src/utils/converters/validNodeTypes.spec.ts +0 -33
- package/src/utils/converters/validNodeTypes.ts +0 -21
- /package/tests/{select.tsx → select.ts} +0 -0
@@ -1,4 +1,5 @@
|
|
1
1
|
import { FORMATTED_TEXT_MODELS as FormattedTextModels } from '@squiz/dx-json-schema-lib';
|
2
|
+
import { RemirrorJSON } from '@remirror/core';
|
2
3
|
|
3
4
|
export const mockSquizNodeJson: FormattedTextModels.v1.FormattedText = [
|
4
5
|
{
|
@@ -73,3 +74,198 @@ export const mockSquizNodeTextJson: FormattedTextModels.v1.FormattedText = [
|
|
73
74
|
type: 'text',
|
74
75
|
},
|
75
76
|
];
|
77
|
+
|
78
|
+
type NodeExample = {
|
79
|
+
description: string;
|
80
|
+
remirrorNode: RemirrorJSON;
|
81
|
+
squizNode: FormattedTextModels.v1.FormattedText;
|
82
|
+
};
|
83
|
+
|
84
|
+
export const sharedNodeExamples: NodeExample[] = [
|
85
|
+
{
|
86
|
+
description: 'Asset link',
|
87
|
+
remirrorNode: {
|
88
|
+
type: 'text',
|
89
|
+
text: 'Hello',
|
90
|
+
marks: [
|
91
|
+
{
|
92
|
+
type: 'assetLink',
|
93
|
+
attrs: {
|
94
|
+
target: '_blank',
|
95
|
+
matrixAssetId: '123',
|
96
|
+
matrixDomain: 'https://my-matrix.squiz.net',
|
97
|
+
matrixIdentifier: 'matrix-api-identifier',
|
98
|
+
},
|
99
|
+
},
|
100
|
+
],
|
101
|
+
},
|
102
|
+
squizNode: [
|
103
|
+
{
|
104
|
+
type: 'link-to-matrix-asset',
|
105
|
+
target: '_blank',
|
106
|
+
matrixAssetId: '123',
|
107
|
+
matrixDomain: 'https://my-matrix.squiz.net',
|
108
|
+
matrixIdentifier: 'matrix-api-identifier',
|
109
|
+
children: [{ type: 'text', value: 'Hello' }],
|
110
|
+
},
|
111
|
+
],
|
112
|
+
},
|
113
|
+
{
|
114
|
+
description: 'Asset link with formatting applied inside of the link',
|
115
|
+
remirrorNode: {
|
116
|
+
type: 'text',
|
117
|
+
text: 'Hello',
|
118
|
+
marks: [
|
119
|
+
{ type: 'bold' },
|
120
|
+
{
|
121
|
+
type: 'assetLink',
|
122
|
+
attrs: {
|
123
|
+
target: '_blank',
|
124
|
+
matrixAssetId: '123',
|
125
|
+
matrixDomain: 'https://my-matrix.squiz.net',
|
126
|
+
matrixIdentifier: 'matrix-api-identifier',
|
127
|
+
},
|
128
|
+
},
|
129
|
+
],
|
130
|
+
},
|
131
|
+
squizNode: [
|
132
|
+
{
|
133
|
+
type: 'link-to-matrix-asset',
|
134
|
+
target: '_blank',
|
135
|
+
matrixAssetId: '123',
|
136
|
+
matrixDomain: 'https://my-matrix.squiz.net',
|
137
|
+
matrixIdentifier: 'matrix-api-identifier',
|
138
|
+
children: [
|
139
|
+
{
|
140
|
+
type: 'tag',
|
141
|
+
tag: 'span',
|
142
|
+
font: { bold: true },
|
143
|
+
children: [{ type: 'text', value: 'Hello' }],
|
144
|
+
},
|
145
|
+
],
|
146
|
+
},
|
147
|
+
],
|
148
|
+
},
|
149
|
+
{
|
150
|
+
description: 'Asset image',
|
151
|
+
remirrorNode: {
|
152
|
+
type: 'assetImage',
|
153
|
+
attrs: {
|
154
|
+
matrixAssetId: '123',
|
155
|
+
matrixDomain: 'https://my-matrix.squiz.net',
|
156
|
+
matrixIdentifier: 'matrix-api-identifier',
|
157
|
+
},
|
158
|
+
},
|
159
|
+
squizNode: [
|
160
|
+
{
|
161
|
+
type: 'matrix-image',
|
162
|
+
matrixAssetId: '123',
|
163
|
+
matrixDomain: 'https://my-matrix.squiz.net',
|
164
|
+
matrixIdentifier: 'matrix-api-identifier',
|
165
|
+
},
|
166
|
+
],
|
167
|
+
},
|
168
|
+
];
|
169
|
+
|
170
|
+
export const squizOnlyNodeExamples: NodeExample[] = [
|
171
|
+
{
|
172
|
+
description: 'Asset link with formatting applied inside, outside and with multiple levels of nesting',
|
173
|
+
squizNode: [
|
174
|
+
{
|
175
|
+
type: 'tag',
|
176
|
+
tag: 'span',
|
177
|
+
font: { bold: true },
|
178
|
+
children: [
|
179
|
+
{
|
180
|
+
type: 'link-to-matrix-asset',
|
181
|
+
target: '_blank',
|
182
|
+
matrixAssetId: '123',
|
183
|
+
matrixDomain: 'https://my-matrix.squiz.net',
|
184
|
+
matrixIdentifier: 'matrix-api-identifier',
|
185
|
+
children: [
|
186
|
+
{
|
187
|
+
type: 'tag',
|
188
|
+
tag: 'span',
|
189
|
+
font: { italics: true },
|
190
|
+
children: [
|
191
|
+
{
|
192
|
+
type: 'tag',
|
193
|
+
tag: 'span',
|
194
|
+
font: { underline: true },
|
195
|
+
children: [{ type: 'text', value: 'Hello' }],
|
196
|
+
},
|
197
|
+
],
|
198
|
+
},
|
199
|
+
],
|
200
|
+
},
|
201
|
+
],
|
202
|
+
},
|
203
|
+
],
|
204
|
+
remirrorNode: {
|
205
|
+
// reverse operation covered by "Asset link with formatting applied inside of the link".
|
206
|
+
type: 'text',
|
207
|
+
text: 'Hello',
|
208
|
+
marks: [
|
209
|
+
{ type: 'underline' },
|
210
|
+
{ type: 'italic' },
|
211
|
+
{
|
212
|
+
type: 'assetLink',
|
213
|
+
attrs: {
|
214
|
+
target: '_blank',
|
215
|
+
matrixAssetId: '123',
|
216
|
+
matrixDomain: 'https://my-matrix.squiz.net',
|
217
|
+
matrixIdentifier: 'matrix-api-identifier',
|
218
|
+
},
|
219
|
+
},
|
220
|
+
{ type: 'bold' },
|
221
|
+
],
|
222
|
+
},
|
223
|
+
},
|
224
|
+
{
|
225
|
+
description: 'Asset link with multiple levels of un-necessary nesting',
|
226
|
+
squizNode: [
|
227
|
+
{
|
228
|
+
type: 'tag',
|
229
|
+
tag: 'span',
|
230
|
+
children: [
|
231
|
+
{
|
232
|
+
type: 'link-to-matrix-asset',
|
233
|
+
target: '_blank',
|
234
|
+
matrixAssetId: '123',
|
235
|
+
matrixDomain: 'https://my-matrix.squiz.net',
|
236
|
+
matrixIdentifier: 'matrix-api-identifier',
|
237
|
+
children: [
|
238
|
+
{
|
239
|
+
type: 'tag',
|
240
|
+
tag: 'span',
|
241
|
+
children: [
|
242
|
+
{
|
243
|
+
type: 'tag',
|
244
|
+
tag: 'span',
|
245
|
+
children: [{ type: 'text', value: 'Hello' }],
|
246
|
+
},
|
247
|
+
],
|
248
|
+
},
|
249
|
+
],
|
250
|
+
},
|
251
|
+
],
|
252
|
+
},
|
253
|
+
],
|
254
|
+
remirrorNode: {
|
255
|
+
// reverse operation covered by "Asset link".
|
256
|
+
type: 'text',
|
257
|
+
text: 'Hello',
|
258
|
+
marks: [
|
259
|
+
{
|
260
|
+
type: 'assetLink',
|
261
|
+
attrs: {
|
262
|
+
target: '_blank',
|
263
|
+
matrixAssetId: '123',
|
264
|
+
matrixDomain: 'https://my-matrix.squiz.net',
|
265
|
+
matrixIdentifier: 'matrix-api-identifier',
|
266
|
+
},
|
267
|
+
},
|
268
|
+
],
|
269
|
+
},
|
270
|
+
},
|
271
|
+
];
|
@@ -2,6 +2,8 @@ import { FORMATTED_TEXT_MODELS as FormattedTextModels } from '@squiz/dx-json-sch
|
|
2
2
|
import { remirrorNodeToSquizNode, resolveNodeTag } from './remirrorNodeToSquizNode';
|
3
3
|
import { renderWithEditor } from '../../../../tests';
|
4
4
|
import { RemirrorJSON } from '@remirror/core';
|
5
|
+
import { sharedNodeExamples } from '../mocks/squizNodeJson.mock';
|
6
|
+
import { ParagraphExtension, SupExtension } from 'remirror/extensions';
|
5
7
|
|
6
8
|
type FormattedText = FormattedTextModels.v1.FormattedText;
|
7
9
|
|
@@ -375,8 +377,9 @@ describe('remirrorNodeToSquizNode', () => {
|
|
375
377
|
});
|
376
378
|
|
377
379
|
it('should handle invalid Remirror node provided', () => {
|
378
|
-
|
379
|
-
|
380
|
+
expect(() => remirrorNodeToSquizNode(false as any)).toThrow(
|
381
|
+
'Unable to convert from Remirror to Node data structure, unexpected node provided.',
|
382
|
+
);
|
380
383
|
});
|
381
384
|
|
382
385
|
it('should handle no content provided by Remirror', async () => {
|
@@ -389,6 +392,38 @@ describe('remirrorNodeToSquizNode', () => {
|
|
389
392
|
const result = remirrorNodeToSquizNode(editor.doc);
|
390
393
|
expect(result).toEqual([]);
|
391
394
|
});
|
395
|
+
|
396
|
+
it.each(sharedNodeExamples)(
|
397
|
+
'should convert a Remirror node to the expected Squiz representation - $description',
|
398
|
+
async ({ remirrorNode, squizNode }: any) => {
|
399
|
+
const { editor } = await renderWithEditor(null, {
|
400
|
+
content: {
|
401
|
+
type: 'doc',
|
402
|
+
content: [{ type: 'paragraph', content: [remirrorNode] }],
|
403
|
+
},
|
404
|
+
});
|
405
|
+
|
406
|
+
expect(remirrorNodeToSquizNode(editor.doc)).toEqual([
|
407
|
+
{
|
408
|
+
type: 'tag',
|
409
|
+
tag: 'p',
|
410
|
+
children: squizNode,
|
411
|
+
},
|
412
|
+
]);
|
413
|
+
},
|
414
|
+
);
|
415
|
+
|
416
|
+
it('should throw if the remirror node has an unsupported mark applied', async () => {
|
417
|
+
const { editor } = await renderWithEditor(null, {
|
418
|
+
extensions: [new ParagraphExtension(), new SupExtension()],
|
419
|
+
content: {
|
420
|
+
type: 'doc',
|
421
|
+
content: [{ type: 'paragraph', marks: [{ type: 'sup' }], content: [] }],
|
422
|
+
},
|
423
|
+
});
|
424
|
+
|
425
|
+
expect(() => remirrorNodeToSquizNode(editor.doc)).toThrow('Unsupported mark "sup" was applied to node.');
|
426
|
+
});
|
392
427
|
});
|
393
428
|
|
394
429
|
describe('resolveNodeTag', () => {
|
@@ -425,7 +460,7 @@ describe('resolveNodeTag', () => {
|
|
425
460
|
expect(resolveNodeTag(node)).toBe('ul');
|
426
461
|
});
|
427
462
|
|
428
|
-
it('should
|
463
|
+
it('should throw for a node with a toDOM method that returns undefined', () => {
|
429
464
|
const node: any = {
|
430
465
|
type: {
|
431
466
|
spec: {
|
@@ -433,13 +468,13 @@ describe('resolveNodeTag', () => {
|
|
433
468
|
},
|
434
469
|
},
|
435
470
|
};
|
436
|
-
expect(resolveNodeTag(node)).
|
471
|
+
expect(() => resolveNodeTag(node)).toThrow('Unexpected Remirror node encountered, cannot resolve tag.');
|
437
472
|
});
|
438
473
|
|
439
|
-
it('should
|
474
|
+
it('should throw for a node without a toDOM method', () => {
|
440
475
|
const node: any = {
|
441
476
|
type: {},
|
442
477
|
};
|
443
|
-
expect(resolveNodeTag(node)).
|
478
|
+
expect(() => resolveNodeTag(node)).toThrow('Unexpected Remirror node encountered, cannot resolve tag.');
|
444
479
|
});
|
445
480
|
});
|
@@ -1,6 +1,8 @@
|
|
1
|
-
import { ProsemirrorNode, Fragment as ProsemirrorFragment } from 'remirror';
|
1
|
+
import { ProsemirrorNode, Fragment as ProsemirrorFragment, Mark } from 'remirror';
|
2
|
+
import { Attrs } from 'prosemirror-model';
|
2
3
|
import { FORMATTED_TEXT_MODELS as FormattedTextModels } from '@squiz/dx-json-schema-lib';
|
3
|
-
import {
|
4
|
+
import { undefinedIfEmpty } from '../../undefinedIfEmpty';
|
5
|
+
import { NodeName } from '../../../Extensions/Extensions';
|
4
6
|
|
5
7
|
type Fragment = ProsemirrorFragment & {
|
6
8
|
content?: Fragment[];
|
@@ -9,16 +11,23 @@ type Fragment = ProsemirrorFragment & {
|
|
9
11
|
type FormattingOptions = FormattedTextModels.v1.FormattingOptions;
|
10
12
|
type FontOptions = FormattedTextModels.v1.FormattedNodeFontProperties;
|
11
13
|
type FormattedText = FormattedTextModels.v1.FormattedText;
|
14
|
+
type FormattedNode = FormattedTextModels.v1.FormattedNodes;
|
15
|
+
type FormattedNodeFontProperties = FormattedTextModels.v1.FormattedNodeFontProperties;
|
16
|
+
type FormattedNodeWithChildren = Extract<FormattedNode, { children: FormattedNode[] }>;
|
17
|
+
|
18
|
+
export const resolveNodeTag = (node: ProsemirrorNode): string => {
|
19
|
+
if (node.type.name === 'text') {
|
20
|
+
return 'span';
|
21
|
+
}
|
12
22
|
|
13
|
-
export const resolveNodeTag = (node: ProsemirrorNode): string | null => {
|
14
23
|
if (node.type.spec?.toDOM) {
|
15
|
-
const domNode = node.type.spec.toDOM(node)
|
24
|
+
const domNode = node.type.spec.toDOM(node);
|
16
25
|
|
17
26
|
if (domNode instanceof window.Node) {
|
18
27
|
return domNode.nodeName.toLowerCase();
|
19
28
|
}
|
20
29
|
|
21
|
-
if (domNode
|
30
|
+
if (typeof domNode === 'object' && 'dom' in domNode && domNode.dom instanceof window.Node) {
|
22
31
|
return domNode.dom.nodeName.toLowerCase();
|
23
32
|
}
|
24
33
|
|
@@ -28,7 +37,7 @@ export const resolveNodeTag = (node: ProsemirrorNode): string | null => {
|
|
28
37
|
}
|
29
38
|
}
|
30
39
|
|
31
|
-
|
40
|
+
throw new Error('Unexpected Remirror node encountered, cannot resolve tag.');
|
32
41
|
};
|
33
42
|
|
34
43
|
const resolveFormattingOptions = (node: ProsemirrorNode): FormattingOptions => {
|
@@ -41,7 +50,7 @@ const resolveFormattingOptions = (node: ProsemirrorNode): FormattingOptions => {
|
|
41
50
|
return formattingOptions;
|
42
51
|
};
|
43
52
|
|
44
|
-
const resolveFontOptions = (node: ProsemirrorNode):
|
53
|
+
const resolveFontOptions = (node: ProsemirrorNode): FormattedNodeFontProperties => {
|
45
54
|
const fontOptions: FontOptions = {};
|
46
55
|
|
47
56
|
node.marks.forEach((mark) => {
|
@@ -55,137 +64,149 @@ const resolveFontOptions = (node: ProsemirrorNode): FontOptions => {
|
|
55
64
|
case 'underline':
|
56
65
|
fontOptions.underline = true;
|
57
66
|
break;
|
58
|
-
default:
|
59
|
-
// Currently unsupported mark type
|
60
|
-
break;
|
61
67
|
}
|
62
68
|
});
|
63
69
|
|
64
70
|
return fontOptions;
|
65
71
|
};
|
66
72
|
|
67
|
-
const
|
68
|
-
|
69
|
-
|
70
|
-
if (nodeType === 'image') {
|
71
|
-
attributeOptions = { ...node.attrs };
|
72
|
-
} else {
|
73
|
-
node.marks.forEach((mark) => {
|
74
|
-
switch (mark.type.name) {
|
75
|
-
case 'link':
|
76
|
-
attributeOptions = { ...mark.attrs };
|
77
|
-
break;
|
78
|
-
default:
|
79
|
-
// Currently unsupported mark type
|
80
|
-
break;
|
81
|
-
}
|
82
|
-
});
|
83
|
-
}
|
73
|
+
const transformAttributes = (attributes: Attrs): Record<string, string> => {
|
74
|
+
const transformed: Record<string, string> = {};
|
84
75
|
|
85
|
-
|
86
|
-
|
87
|
-
if (typeof
|
88
|
-
|
89
|
-
// If it's a number we make it a string so its accepted by component service
|
90
|
-
} else {
|
91
|
-
attributeOptions[key] = String(attributeOptions[key]);
|
76
|
+
Object.keys(attributes).forEach((key) => {
|
77
|
+
// Component service requires attributes to be a string, cast as needed.
|
78
|
+
if (typeof attributes[key] === 'string' || typeof attributes[key] === 'number') {
|
79
|
+
transformed[key] = String(attributes[key]);
|
92
80
|
}
|
93
81
|
});
|
94
82
|
|
95
|
-
return
|
83
|
+
return transformed;
|
96
84
|
};
|
97
85
|
|
98
|
-
|
99
|
-
|
100
|
-
* @param {ProsemirrorNode} node Remirror node to convert to component.
|
101
|
-
* @export
|
102
|
-
* @returns {FormattedText} The converted Squiz component JSON.
|
103
|
-
*/
|
104
|
-
export const remirrorNodeToSquizNode = (node: ProsemirrorNode): FormattedText => {
|
105
|
-
if (!validRemirrorNode(node)) return [];
|
106
|
-
|
107
|
-
const nodeType = node.type.name;
|
108
|
-
let nodeTag = resolveNodeTag(node);
|
109
|
-
|
110
|
-
// Filter out any children nodes that aren't currently supported.
|
111
|
-
const children = ((node.content as Fragment).content || []).map((child: any) => remirrorNodeToSquizNode(child));
|
112
|
-
|
113
|
-
let transformed: any = {
|
114
|
-
children,
|
115
|
-
formattingOptions: resolveFormattingOptions(node),
|
116
|
-
attributes: resolveAttributeOptions(node, nodeType),
|
117
|
-
font: resolveFontOptions(node),
|
118
|
-
};
|
86
|
+
const transformFragment = (fragment: Fragment): FormattedText => {
|
87
|
+
const transformed: FormattedText = [];
|
119
88
|
|
120
|
-
|
121
|
-
return transformed.children;
|
122
|
-
}
|
89
|
+
fragment.forEach((child) => transformed.push(transformNode(child)));
|
123
90
|
|
124
|
-
|
125
|
-
|
126
|
-
node.marks.forEach((mark) => {
|
127
|
-
switch (mark.type.name) {
|
128
|
-
case 'link':
|
129
|
-
nodeTag = 'a';
|
130
|
-
break;
|
131
|
-
default:
|
132
|
-
// Currently unsupported mark type
|
133
|
-
break;
|
134
|
-
}
|
135
|
-
});
|
136
|
-
}
|
91
|
+
return transformed;
|
92
|
+
};
|
137
93
|
|
138
|
-
|
139
|
-
|
140
|
-
|
94
|
+
const transformNode = (node: ProsemirrorNode): FormattedNode => {
|
95
|
+
const attributes = node.type.name === NodeName.Image ? transformAttributes(node.attrs) : undefined;
|
96
|
+
const formattingOptions = undefinedIfEmpty(resolveFormattingOptions(node));
|
97
|
+
const font = undefinedIfEmpty(resolveFontOptions(node));
|
98
|
+
let transformedNode: FormattedNode = { type: 'text', value: node.text || '' };
|
99
|
+
|
100
|
+
// Squiz "text" nodes can't have formatting/font options but Remirror "text" nodes can.
|
101
|
+
// If the node has formatting options wrap in a tag.
|
102
|
+
// If the node isn't a text type assume it is a tag type and wrap in a tag.
|
103
|
+
// If we pick the wrong tag here it will be corrected later as part of looping through the
|
104
|
+
// non-font marks.
|
105
|
+
if (node.type.name !== NodeName.Text || attributes || formattingOptions || font) {
|
106
|
+
transformedNode = {
|
141
107
|
type: 'tag',
|
142
|
-
tag:
|
108
|
+
tag: resolveNodeTag(node),
|
109
|
+
children: node.type.name === NodeName.Text ? [transformedNode] : transformFragment(node.content),
|
110
|
+
attributes,
|
111
|
+
formattingOptions,
|
112
|
+
font,
|
143
113
|
};
|
144
114
|
}
|
145
115
|
|
146
|
-
if (
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
116
|
+
if (node.type.name === NodeName.AssetImage) {
|
117
|
+
transformedNode = {
|
118
|
+
type: 'matrix-image',
|
119
|
+
matrixAssetId: node.attrs.matrixAssetId,
|
120
|
+
matrixIdentifier: node.attrs.matrixIdentifier,
|
121
|
+
matrixDomain: node.attrs.matrixDomain,
|
122
|
+
};
|
152
123
|
}
|
153
124
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
value: node.text,
|
163
|
-
},
|
164
|
-
],
|
165
|
-
};
|
166
|
-
} else {
|
167
|
-
// If we don't have a tag just rewrite the transformed value to be the text.
|
168
|
-
transformed = {
|
169
|
-
type: 'text',
|
170
|
-
value: node.text,
|
171
|
-
};
|
125
|
+
node.marks.forEach((mark) => {
|
126
|
+
switch (mark.type.name) {
|
127
|
+
case 'bold':
|
128
|
+
case 'italic':
|
129
|
+
case 'underline':
|
130
|
+
break;
|
131
|
+
default:
|
132
|
+
transformedNode = transformMark(mark, transformedNode);
|
172
133
|
}
|
173
|
-
}
|
134
|
+
});
|
174
135
|
|
175
|
-
|
176
|
-
|
177
|
-
|
136
|
+
return transformedNode;
|
137
|
+
};
|
138
|
+
|
139
|
+
/**
|
140
|
+
* Merges 2 nodes together if they are compatible without losing any important details.
|
141
|
+
* Otherwise will wrap the node.
|
142
|
+
*
|
143
|
+
* @param {FormattedNode} node
|
144
|
+
* @param {FormattedNodeWithChildren} wrappingNode
|
145
|
+
*
|
146
|
+
* @return {FormattedNode}
|
147
|
+
*/
|
148
|
+
const wrapNodeIfNeeded = (node: FormattedNode, wrappingNode: FormattedNodeWithChildren): FormattedNode => {
|
149
|
+
if (node.type === 'tag' && wrappingNode.type === 'tag' && (node.tag === 'span' || node.tag === wrappingNode.tag)) {
|
150
|
+
// if the node we are wrapping with is a DOM node, and the node being wrapped is
|
151
|
+
// a plain looking DOM node merge the 2 nodes.
|
152
|
+
return {
|
153
|
+
...node,
|
154
|
+
...wrappingNode,
|
155
|
+
formattingOptions: undefinedIfEmpty({
|
156
|
+
...node.formattingOptions,
|
157
|
+
...wrappingNode.formattingOptions,
|
158
|
+
}),
|
159
|
+
attributes: undefinedIfEmpty({
|
160
|
+
...node.attributes,
|
161
|
+
...wrappingNode.attributes,
|
162
|
+
}),
|
163
|
+
font: undefinedIfEmpty({
|
164
|
+
...node.font,
|
165
|
+
...wrappingNode.font,
|
166
|
+
}),
|
167
|
+
children: [...node.children, ...wrappingNode.children],
|
168
|
+
};
|
178
169
|
}
|
179
170
|
|
180
|
-
//
|
181
|
-
|
182
|
-
|
171
|
+
// if the node we are wrapping or the wrapping nodes are not compatible merge them.
|
172
|
+
return {
|
173
|
+
...wrappingNode,
|
174
|
+
children: [node, ...wrappingNode.children],
|
175
|
+
};
|
176
|
+
};
|
177
|
+
|
178
|
+
const transformMark = (mark: Mark, node: FormattedNode): FormattedNode => {
|
179
|
+
switch (mark.type.name) {
|
180
|
+
case 'link':
|
181
|
+
return wrapNodeIfNeeded(node, {
|
182
|
+
type: 'tag',
|
183
|
+
tag: 'a',
|
184
|
+
attributes: transformAttributes(mark.attrs),
|
185
|
+
children: [],
|
186
|
+
});
|
187
|
+
case 'assetLink':
|
188
|
+
return wrapNodeIfNeeded(node, {
|
189
|
+
type: 'link-to-matrix-asset',
|
190
|
+
target: mark.attrs.target,
|
191
|
+
matrixIdentifier: mark.attrs.matrixIdentifier,
|
192
|
+
matrixDomain: mark.attrs.matrixDomain,
|
193
|
+
matrixAssetId: mark.attrs.matrixAssetId,
|
194
|
+
children: [],
|
195
|
+
});
|
183
196
|
}
|
184
197
|
|
185
|
-
|
186
|
-
|
187
|
-
|
198
|
+
throw new Error(`Unsupported mark "${mark.type.name}" was applied to node.`);
|
199
|
+
};
|
200
|
+
|
201
|
+
/**
|
202
|
+
* Converts Remirror node JSON structure to Squiz component JSON structure.
|
203
|
+
* @param {ProsemirrorNode} node Remirror node to convert to component.
|
204
|
+
* @returns {FormattedText} The converted Squiz component JSON.
|
205
|
+
*/
|
206
|
+
export const remirrorNodeToSquizNode = (node: ProsemirrorNode): FormattedText => {
|
207
|
+
if (node?.type?.name !== 'doc') {
|
208
|
+
throw new Error('Unable to convert from Remirror to Node data structure, unexpected node provided.');
|
188
209
|
}
|
189
210
|
|
190
|
-
return
|
211
|
+
return transformFragment(node.content);
|
191
212
|
};
|