@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,7 +1,12 @@
|
|
1
1
|
import { FORMATTED_TEXT_MODELS as FormattedTextModels } from '@squiz/dx-json-schema-lib';
|
2
2
|
import { RemirrorJSON } from '@remirror/core';
|
3
3
|
import { squizNodeToRemirrorNode } from './squizNodeToRemirrorNode';
|
4
|
-
import {
|
4
|
+
import {
|
5
|
+
mockSquizNodeJson,
|
6
|
+
mockSquizNodeTextJson,
|
7
|
+
sharedNodeExamples,
|
8
|
+
squizOnlyNodeExamples,
|
9
|
+
} from '../mocks/squizNodeJson.mock';
|
5
10
|
|
6
11
|
type FormattedText = FormattedTextModels.v1.FormattedText;
|
7
12
|
|
@@ -84,19 +89,12 @@ describe('squizNodeToRemirrorNode', () => {
|
|
84
89
|
expect(squizNodeToRemirrorNode(mockSquizNodeTextJson)).toEqual({
|
85
90
|
content: [
|
86
91
|
{
|
87
|
-
attrs: { nodeIndent: null, nodeLineHeight: null, nodeTextAlignment: null, style: '' },
|
88
92
|
type: 'paragraph',
|
89
93
|
content: [
|
90
94
|
{
|
91
95
|
type: 'text',
|
92
96
|
text: 'Hello world!',
|
93
97
|
},
|
94
|
-
],
|
95
|
-
},
|
96
|
-
{
|
97
|
-
attrs: { nodeIndent: null, nodeLineHeight: null, nodeTextAlignment: null, style: '' },
|
98
|
-
type: 'paragraph',
|
99
|
-
content: [
|
100
98
|
{
|
101
99
|
type: 'text',
|
102
100
|
text: 'Another one...',
|
@@ -129,32 +127,40 @@ describe('squizNodeToRemirrorNode', () => {
|
|
129
127
|
});
|
130
128
|
});
|
131
129
|
|
132
|
-
it(
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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);
|
158
164
|
});
|
159
165
|
|
160
166
|
it('should handle pre formatted text', () => {
|
@@ -304,4 +310,32 @@ describe('squizNodeToRemirrorNode', () => {
|
|
304
310
|
const result = squizNodeToRemirrorNode(squizComponentJSON);
|
305
311
|
expect(result).toEqual(expected);
|
306
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([
|
318
|
+
{
|
319
|
+
type: 'tag',
|
320
|
+
tag: 'p',
|
321
|
+
children: squizNode,
|
322
|
+
},
|
323
|
+
]);
|
324
|
+
expect(result).toEqual({
|
325
|
+
type: 'doc',
|
326
|
+
content: [
|
327
|
+
{
|
328
|
+
type: 'paragraph',
|
329
|
+
attrs: {
|
330
|
+
nodeIndent: null,
|
331
|
+
nodeLineHeight: null,
|
332
|
+
nodeTextAlignment: null,
|
333
|
+
style: '',
|
334
|
+
},
|
335
|
+
content: [remirrorNode],
|
336
|
+
},
|
337
|
+
],
|
338
|
+
});
|
339
|
+
},
|
340
|
+
);
|
307
341
|
});
|
@@ -1,12 +1,20 @@
|
|
1
|
-
import { RemirrorJSON,
|
1
|
+
import { RemirrorJSON, ObjectMark } from '@remirror/core';
|
2
|
+
import { Attrs } from 'prosemirror-model';
|
2
3
|
import { FORMATTED_TEXT_MODELS as FormattedTextModels } from '@squiz/dx-json-schema-lib';
|
4
|
+
import { undefinedIfEmpty } from '../../undefinedIfEmpty';
|
5
|
+
import { MarkName, NodeName } from '../../../Extensions/Extensions';
|
3
6
|
|
4
7
|
type FormattedText = FormattedTextModels.v1.FormattedText;
|
5
|
-
type FormattedTextTag = FormattedTextModels.v1.FormattedTextTag;
|
6
8
|
type FormattedNodes = FormattedTextModels.v1.FormattedNodes;
|
7
9
|
|
8
|
-
const getNodeType = (node:
|
9
|
-
const
|
10
|
+
const getNodeType = (node: FormattedNodes): string => {
|
11
|
+
const typeMap: Record<string, string> = {
|
12
|
+
'link-to-matrix-asset': NodeName.Text,
|
13
|
+
'matrix-image': NodeName.AssetImage,
|
14
|
+
text: 'text',
|
15
|
+
};
|
16
|
+
|
17
|
+
const tagMap: Record<string, string> = {
|
10
18
|
h1: 'heading',
|
11
19
|
h2: 'heading',
|
12
20
|
h3: 'heading',
|
@@ -16,97 +24,118 @@ const getNodeType = (node: FormattedTextTag): string => {
|
|
16
24
|
img: 'image',
|
17
25
|
pre: 'preformatted',
|
18
26
|
p: 'paragraph',
|
19
|
-
|
27
|
+
a: NodeName.Text,
|
28
|
+
span: NodeName.Text,
|
20
29
|
};
|
21
30
|
|
22
|
-
|
31
|
+
if (typeMap[node.type]) {
|
32
|
+
return typeMap[node.type];
|
33
|
+
}
|
23
34
|
|
24
|
-
|
25
|
-
|
35
|
+
if (node.type === 'tag' && tagMap[node.tag]) {
|
36
|
+
return tagMap[node.tag];
|
37
|
+
}
|
26
38
|
|
27
|
-
|
39
|
+
// Unsupported node type
|
40
|
+
throw new Error(
|
41
|
+
node.type === 'tag'
|
42
|
+
? `Unsupported node type provided: ${node.type} (tag: ${node.tag})`
|
43
|
+
: `Unsupported node type provided: ${node.type}`,
|
44
|
+
);
|
28
45
|
};
|
29
46
|
|
30
|
-
const getNodeAttributes = (node:
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
47
|
+
const getNodeAttributes = (node: FormattedNodes): Attrs => {
|
48
|
+
if (node.type === 'tag' && node.tag === 'img') {
|
49
|
+
return {
|
50
|
+
alt: node.attributes?.alt,
|
51
|
+
height: node.attributes?.height,
|
52
|
+
width: node.attributes?.width,
|
53
|
+
src: node.attributes?.src,
|
54
|
+
title: node.attributes?.title,
|
55
|
+
};
|
56
|
+
} else if (node.type === 'matrix-image') {
|
57
|
+
return {
|
58
|
+
matrixAssetId: node.matrixAssetId,
|
59
|
+
matrixDomain: node.matrixDomain,
|
60
|
+
matrixIdentifier: node.matrixIdentifier,
|
61
|
+
};
|
62
|
+
} else if (node.type === 'tag') {
|
63
|
+
return {
|
64
|
+
nodeIndent: null,
|
65
|
+
nodeTextAlignment: node.formattingOptions?.alignment || null,
|
66
|
+
nodeLineHeight: null,
|
67
|
+
style: '',
|
68
|
+
level: node.tag?.startsWith('h') ? parseInt(node.tag.substring(1)) : undefined,
|
69
|
+
};
|
70
|
+
}
|
71
|
+
|
72
|
+
return {};
|
39
73
|
};
|
40
74
|
|
41
|
-
const
|
42
|
-
|
43
|
-
|
75
|
+
const getNodeMarks = (node: FormattedNodes): ObjectMark[] => {
|
76
|
+
const marks: ObjectMark[] = [];
|
77
|
+
|
78
|
+
if (node.type === 'tag' && node.tag === 'a') {
|
79
|
+
marks.push({
|
80
|
+
type: MarkName.Link,
|
81
|
+
attrs: {
|
82
|
+
href: node.attributes?.href,
|
83
|
+
target: node.attributes?.target ?? null,
|
84
|
+
auto: false,
|
85
|
+
title: node.attributes?.title ?? null,
|
86
|
+
},
|
87
|
+
});
|
88
|
+
} else if (node.type === 'link-to-matrix-asset') {
|
89
|
+
marks.push({
|
90
|
+
type: MarkName.AssetLink,
|
91
|
+
attrs: {
|
92
|
+
matrixAssetId: node.matrixAssetId,
|
93
|
+
matrixDomain: node.matrixDomain,
|
94
|
+
matrixIdentifier: node.matrixIdentifier,
|
95
|
+
target: node.target,
|
96
|
+
},
|
97
|
+
});
|
44
98
|
}
|
45
99
|
|
46
|
-
|
47
|
-
|
100
|
+
// Handle font formatting
|
101
|
+
if ('font' in node) {
|
102
|
+
node.font?.bold && marks.push({ type: 'bold' });
|
103
|
+
node.font?.italics && marks.push({ type: 'italic' });
|
104
|
+
node.font?.underline && marks.push({ type: 'underline' });
|
105
|
+
}
|
106
|
+
|
107
|
+
return marks;
|
108
|
+
};
|
48
109
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
marks.push({
|
53
|
-
type: 'link',
|
54
|
-
attrs: {
|
55
|
-
href: child.attributes?.href,
|
56
|
-
target: child.attributes?.target ?? null,
|
57
|
-
auto: false,
|
58
|
-
title: child.attributes?.title ?? null,
|
59
|
-
},
|
60
|
-
});
|
61
|
-
}
|
62
|
-
|
63
|
-
// Handle image type
|
64
|
-
if (child.tag === 'img') {
|
110
|
+
const unwrapNodeIfNeeded = (node: RemirrorJSON): RemirrorJSON[] => {
|
111
|
+
if (node.type === 'text' && node.content?.length) {
|
112
|
+
return node.content.map((child) => {
|
65
113
|
return {
|
66
|
-
|
67
|
-
|
68
|
-
alt: child.attributes?.alt,
|
69
|
-
height: child.attributes?.height,
|
70
|
-
width: child.attributes?.width,
|
71
|
-
src: child.attributes?.src,
|
72
|
-
title: child.attributes?.title,
|
73
|
-
},
|
114
|
+
...child,
|
115
|
+
marks: [...(child.marks || []), ...(node.marks || [])],
|
74
116
|
};
|
75
|
-
}
|
76
|
-
|
77
|
-
// Handle font formatting
|
78
|
-
child.font?.bold && marks.push({ type: 'bold' });
|
79
|
-
child.font?.italics && marks.push({ type: 'italic' });
|
80
|
-
child.font?.underline && marks.push({ type: 'underline' });
|
81
|
-
|
82
|
-
// For now all children types should be "text"
|
83
|
-
text = child.children[0].type === 'text' ? child.children[0].value : '';
|
117
|
+
});
|
84
118
|
}
|
85
119
|
|
86
|
-
return
|
120
|
+
return [node];
|
87
121
|
};
|
88
122
|
|
89
|
-
const formatNode = (node: FormattedNodes): RemirrorJSON => {
|
90
|
-
|
91
|
-
|
92
|
-
if (node.type === 'tag') {
|
93
|
-
content = node.children.length ? node.children.map((child: FormattedNodes) => resolveChild(child)) : undefined;
|
94
|
-
}
|
123
|
+
const formatNode = (node: FormattedNodes): RemirrorJSON[] => {
|
124
|
+
const children: RemirrorJSON[] = [];
|
95
125
|
|
96
|
-
if (
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
text: node.value,
|
101
|
-
},
|
102
|
-
];
|
126
|
+
if ('children' in node) {
|
127
|
+
node.children.forEach((child: FormattedNodes) => {
|
128
|
+
children.push(...formatNode(child));
|
129
|
+
});
|
103
130
|
}
|
104
131
|
|
105
|
-
return {
|
106
|
-
type: getNodeType(node
|
107
|
-
attrs: getNodeAttributes(node
|
108
|
-
|
109
|
-
|
132
|
+
return unwrapNodeIfNeeded({
|
133
|
+
type: getNodeType(node),
|
134
|
+
attrs: undefinedIfEmpty(getNodeAttributes(node)),
|
135
|
+
marks: undefinedIfEmpty(getNodeMarks(node)),
|
136
|
+
text: node.type === 'text' ? node.value : undefined,
|
137
|
+
content: undefinedIfEmpty(children),
|
138
|
+
});
|
110
139
|
};
|
111
140
|
|
112
141
|
/**
|
@@ -116,8 +145,15 @@ const formatNode = (node: FormattedNodes): RemirrorJSON => {
|
|
116
145
|
* @returns {RemirrorJSON} The converted Remirror JSON.
|
117
146
|
*/
|
118
147
|
export const squizNodeToRemirrorNode = (nodes: FormattedText): RemirrorJSON => {
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
148
|
+
let children: RemirrorJSON[] = [];
|
149
|
+
|
150
|
+
nodes.forEach((node) => {
|
151
|
+
children.push(...formatNode(node));
|
152
|
+
});
|
153
|
+
|
154
|
+
if (children.find((child) => child.type === 'text')) {
|
155
|
+
children = [{ type: 'paragraph', content: children }];
|
156
|
+
}
|
157
|
+
|
158
|
+
return { type: 'doc', content: children };
|
123
159
|
};
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import { resolveMatrixAssetUrl } from './resolveMatrixAssetUrl';
|
2
|
+
|
3
|
+
describe('resolveMatrixAssetUrl', () => {
|
4
|
+
it.each([
|
5
|
+
[
|
6
|
+
'domain with no scheme',
|
7
|
+
'123:hello?.txt',
|
8
|
+
'matrix.labs.squiz.test',
|
9
|
+
'http://matrix.labs.squiz.test/_nocache?a=123%3Ahello%3F.txt',
|
10
|
+
],
|
11
|
+
[
|
12
|
+
'domain with scheme',
|
13
|
+
'123:hello?.txt',
|
14
|
+
'https://matrix.labs.squiz.test',
|
15
|
+
'https://matrix.labs.squiz.test/_nocache?a=123%3Ahello%3F.txt',
|
16
|
+
],
|
17
|
+
[
|
18
|
+
'domain with path',
|
19
|
+
'123:hello?.txt',
|
20
|
+
'https://matrix.labs.squiz.test/site-1',
|
21
|
+
'https://matrix.labs.squiz.test/_nocache?a=123%3Ahello%3F.txt',
|
22
|
+
],
|
23
|
+
])('Resolves to expected URL for %s', (description: string, id: string, matrixDomain: string, expected: string) => {
|
24
|
+
expect(resolveMatrixAssetUrl(id, matrixDomain)).toBe(expected);
|
25
|
+
});
|
26
|
+
});
|
@@ -0,0 +1,7 @@
|
|
1
|
+
export const resolveMatrixAssetUrl = (id: string, matrixDomain: string): string => {
|
2
|
+
if (matrixDomain.indexOf('://') < 0) {
|
3
|
+
matrixDomain = `${window.location.protocol}//${matrixDomain}`;
|
4
|
+
}
|
5
|
+
|
6
|
+
return new URL(`/_nocache?a=${encodeURIComponent(id)}`, matrixDomain).toString();
|
7
|
+
};
|
@@ -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
|
+
});
|
package/tailwind.config.cjs
CHANGED
@@ -1,15 +1,22 @@
|
|
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 & {
|
@@ -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 ||
|
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
|
};
|
@@ -79,6 +89,7 @@ export const renderWithEditor = async (
|
|
79
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
|
-
<
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
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
@@ -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,21 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.validRemirrorNode = void 0;
|
4
|
-
const Extensions_1 = require("../../Extensions/Extensions");
|
5
|
-
const validRemirrorNode = (node) => {
|
6
|
-
if (!node)
|
7
|
-
return false;
|
8
|
-
const nodeType = node.type.name;
|
9
|
-
const nodeMarks = node.marks;
|
10
|
-
// This is pulling in the currently supported extensions, this works for now...
|
11
|
-
// Could also just hard code these in as we go, but this should make it easier as we add more extensions
|
12
|
-
const supportedNodes = [...(0, Extensions_1.Extensions)().map((extension) => extension.name), 'doc', 'text'];
|
13
|
-
if (!supportedNodes.includes(nodeType))
|
14
|
-
return false;
|
15
|
-
for (let i = 0; i < nodeMarks.length; i++) {
|
16
|
-
if (!supportedNodes.includes(nodeMarks[i].type.name))
|
17
|
-
return false;
|
18
|
-
}
|
19
|
-
return true;
|
20
|
-
};
|
21
|
-
exports.validRemirrorNode = validRemirrorNode;
|
@@ -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
|
-
});
|
@@ -1,33 +0,0 @@
|
|
1
|
-
import { validRemirrorNode } from './validNodeTypes';
|
2
|
-
|
3
|
-
describe('validRemirrorNode', () => {
|
4
|
-
it('returns false for null input', () => {
|
5
|
-
expect(validRemirrorNode(null as any)).toBe(false);
|
6
|
-
});
|
7
|
-
|
8
|
-
it('returns false for unsupported node type', () => {
|
9
|
-
const node = { type: { name: 'unsupported' }, marks: [] };
|
10
|
-
expect(validRemirrorNode(node as any)).toBe(false);
|
11
|
-
});
|
12
|
-
|
13
|
-
it('returns false for unsupported mark type', () => {
|
14
|
-
const node = {
|
15
|
-
type: { name: 'doc' },
|
16
|
-
marks: [{ type: { name: 'unsupported' } }],
|
17
|
-
};
|
18
|
-
expect(validRemirrorNode(node as any)).toBe(false);
|
19
|
-
});
|
20
|
-
|
21
|
-
it('returns true for supported node type with no marks', () => {
|
22
|
-
const node = { type: { name: 'doc' }, marks: [] };
|
23
|
-
expect(validRemirrorNode(node as any)).toBe(true);
|
24
|
-
});
|
25
|
-
|
26
|
-
it('returns true for supported node type with supported mark type', () => {
|
27
|
-
const node = {
|
28
|
-
type: { name: 'doc' },
|
29
|
-
marks: [{ type: { name: 'bold' } }],
|
30
|
-
};
|
31
|
-
expect(validRemirrorNode(node as any)).toBe(true);
|
32
|
-
});
|
33
|
-
});
|