@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.
Files changed (123) hide show
  1. package/demo/App.tsx +38 -10
  2. package/demo/index.scss +2 -7
  3. package/jest.config.ts +0 -2
  4. package/lib/Editor/Editor.js +45 -7
  5. package/lib/Editor/EditorContext.d.ts +15 -0
  6. package/lib/Editor/EditorContext.js +15 -0
  7. package/lib/EditorToolbar/FloatingToolbar.js +11 -5
  8. package/lib/EditorToolbar/Tools/Image/Form/ImageForm.d.ts +9 -8
  9. package/lib/EditorToolbar/Tools/Image/Form/ImageForm.js +91 -23
  10. package/lib/EditorToolbar/Tools/Image/ImageButton.d.ts +4 -1
  11. package/lib/EditorToolbar/Tools/Image/ImageButton.js +22 -14
  12. package/lib/EditorToolbar/Tools/Image/ImageModal.js +9 -5
  13. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.d.ts +14 -5
  14. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +66 -14
  15. package/lib/EditorToolbar/Tools/Link/LinkButton.js +21 -13
  16. package/lib/EditorToolbar/Tools/Link/LinkModal.js +12 -5
  17. package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +1 -8
  18. package/lib/Extensions/CommandsExtension/CommandsExtension.d.ts +20 -0
  19. package/lib/Extensions/CommandsExtension/CommandsExtension.js +52 -0
  20. package/lib/Extensions/Extensions.d.ts +11 -1
  21. package/lib/Extensions/Extensions.js +42 -20
  22. package/lib/Extensions/ImageExtension/AssetImageExtension.d.ts +17 -0
  23. package/lib/Extensions/ImageExtension/AssetImageExtension.js +92 -0
  24. package/lib/Extensions/ImageExtension/ImageExtension.d.ts +4 -0
  25. package/lib/Extensions/ImageExtension/ImageExtension.js +11 -0
  26. package/lib/Extensions/LinkExtension/AssetLinkExtension.d.ts +26 -0
  27. package/lib/Extensions/LinkExtension/AssetLinkExtension.js +102 -0
  28. package/lib/Extensions/LinkExtension/LinkExtension.d.ts +19 -12
  29. package/lib/Extensions/LinkExtension/LinkExtension.js +56 -66
  30. package/lib/Extensions/LinkExtension/common.d.ts +7 -0
  31. package/lib/Extensions/LinkExtension/common.js +14 -0
  32. package/lib/Extensions/PreformattedExtension/PreformattedExtension.js +6 -2
  33. package/lib/hooks/index.d.ts +1 -0
  34. package/lib/hooks/index.js +1 -0
  35. package/lib/hooks/useExpandedSelection.d.ts +23 -0
  36. package/lib/hooks/useExpandedSelection.js +37 -0
  37. package/lib/index.css +58 -26
  38. package/lib/index.d.ts +3 -2
  39. package/lib/index.js +5 -3
  40. package/lib/types.d.ts +3 -0
  41. package/lib/types.js +2 -0
  42. package/lib/ui/Button/Button.d.ts +2 -1
  43. package/lib/ui/Button/Button.js +4 -5
  44. package/lib/ui/Fields/Input/Input.d.ts +1 -0
  45. package/lib/ui/Fields/Input/Input.js +9 -3
  46. package/lib/ui/Modal/Modal.js +5 -3
  47. package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.d.ts +1 -2
  48. package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +118 -104
  49. package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +102 -69
  50. package/lib/utils/resolveMatrixAssetUrl.d.ts +1 -0
  51. package/lib/utils/resolveMatrixAssetUrl.js +10 -0
  52. package/lib/utils/undefinedIfEmpty.d.ts +1 -0
  53. package/lib/utils/undefinedIfEmpty.js +7 -0
  54. package/package.json +8 -4
  55. package/src/Editor/Editor.spec.tsx +78 -18
  56. package/src/Editor/Editor.tsx +28 -9
  57. package/src/Editor/EditorContext.spec.tsx +26 -0
  58. package/src/Editor/EditorContext.ts +26 -0
  59. package/src/Editor/_editor.scss +20 -4
  60. package/src/EditorToolbar/FloatingToolbar.spec.tsx +26 -7
  61. package/src/EditorToolbar/FloatingToolbar.tsx +15 -6
  62. package/src/EditorToolbar/Tools/Image/Form/ImageForm.spec.tsx +81 -6
  63. package/src/EditorToolbar/Tools/Image/Form/ImageForm.tsx +167 -47
  64. package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +250 -2
  65. package/src/EditorToolbar/Tools/Image/ImageButton.tsx +29 -16
  66. package/src/EditorToolbar/Tools/Image/ImageModal.spec.tsx +59 -20
  67. package/src/EditorToolbar/Tools/Image/ImageModal.tsx +12 -10
  68. package/src/EditorToolbar/Tools/Link/Form/LinkForm.spec.tsx +37 -9
  69. package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +96 -26
  70. package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +137 -26
  71. package/src/EditorToolbar/Tools/Link/LinkButton.tsx +28 -19
  72. package/src/EditorToolbar/Tools/Link/LinkModal.tsx +13 -6
  73. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.spec.tsx +27 -26
  74. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +2 -10
  75. package/src/EditorToolbar/Tools/Undo/UndoButton.spec.tsx +22 -1
  76. package/src/EditorToolbar/_floating-toolbar.scss +4 -5
  77. package/src/EditorToolbar/_toolbar.scss +1 -1
  78. package/src/Extensions/CommandsExtension/CommandsExtension.ts +54 -0
  79. package/src/Extensions/Extensions.ts +42 -19
  80. package/src/Extensions/ImageExtension/AssetImageExtension.spec.ts +76 -0
  81. package/src/Extensions/ImageExtension/AssetImageExtension.ts +111 -0
  82. package/src/Extensions/ImageExtension/ImageExtension.ts +17 -1
  83. package/src/Extensions/LinkExtension/AssetLinkExtension.spec.ts +104 -0
  84. package/src/Extensions/LinkExtension/AssetLinkExtension.ts +128 -0
  85. package/src/Extensions/LinkExtension/LinkExtension.spec.ts +68 -0
  86. package/src/Extensions/LinkExtension/LinkExtension.ts +71 -85
  87. package/src/Extensions/LinkExtension/common.ts +10 -0
  88. package/src/Extensions/PreformattedExtension/PreformattedExtension.spec.ts +41 -0
  89. package/src/Extensions/PreformattedExtension/PreformattedExtension.ts +6 -2
  90. package/src/hooks/index.ts +1 -0
  91. package/src/hooks/useExpandedSelection.ts +44 -0
  92. package/src/index.ts +3 -2
  93. package/src/types.ts +5 -0
  94. package/src/ui/Button/Button.tsx +10 -6
  95. package/src/ui/Button/_button.scss +1 -1
  96. package/src/ui/Fields/Input/Input.spec.tsx +7 -1
  97. package/src/ui/Fields/Input/Input.tsx +23 -4
  98. package/src/ui/Modal/Modal.spec.tsx +15 -0
  99. package/src/ui/Modal/Modal.tsx +8 -4
  100. package/src/ui/ToolbarDropdown/_toolbar-dropdown.scss +1 -1
  101. package/src/ui/_forms.scss +14 -0
  102. package/src/utils/converters/mocks/squizNodeJson.mock.ts +196 -0
  103. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.spec.ts +41 -6
  104. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +132 -111
  105. package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.spec.ts +68 -34
  106. package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +115 -79
  107. package/src/utils/resolveMatrixAssetUrl.spec.ts +26 -0
  108. package/src/utils/resolveMatrixAssetUrl.ts +7 -0
  109. package/src/utils/undefinedIfEmpty.spec.ts +12 -0
  110. package/src/utils/undefinedIfEmpty.ts +3 -0
  111. package/tailwind.config.cjs +3 -0
  112. package/tests/renderWithEditor.tsx +26 -13
  113. package/tsconfig.json +1 -1
  114. package/lib/FormattedTextEditor.d.ts +0 -2
  115. package/lib/FormattedTextEditor.js +0 -7
  116. package/lib/utils/converters/validNodeTypes.d.ts +0 -2
  117. package/lib/utils/converters/validNodeTypes.js +0 -21
  118. package/src/Editor/Editor.mock.tsx +0 -43
  119. package/src/FormattedTextEditor.spec.tsx +0 -10
  120. package/src/FormattedTextEditor.tsx +0 -3
  121. package/src/utils/converters/validNodeTypes.spec.ts +0 -33
  122. package/src/utils/converters/validNodeTypes.ts +0 -21
  123. /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 { mockSquizNodeJson, mockSquizNodeTextJson } from '../mocks/squizNodeJson.mock';
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('should throw an error for non supported node types', () => {
133
- const squizComponentJSON: FormattedText = [
134
- {
135
- children: [
136
- {
137
- type: 'text',
138
- value: 'Hello world!',
139
- },
140
- ],
141
- type: 'tag',
142
- tag: 'p',
143
- },
144
- // This should be filtered out, as we don't currently support <code> tags
145
- {
146
- children: [
147
- {
148
- type: 'text',
149
- value: 'Should be filtered out...',
150
- },
151
- ],
152
- type: 'tag',
153
- tag: 'code',
154
- },
155
- ];
156
-
157
- expect(() => squizNodeToRemirrorNode(squizComponentJSON)).toThrow(`Unsupported node type provided: code`);
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, Literal, ObjectMark } from '@remirror/core';
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: FormattedTextTag): string => {
9
- const nodeTypeMap: Record<string, string> = {
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
- text: 'paragraph',
27
+ a: NodeName.Text,
28
+ span: NodeName.Text,
20
29
  };
21
30
 
22
- const nodeType = nodeTypeMap[node.tag || node.type];
31
+ if (typeMap[node.type]) {
32
+ return typeMap[node.type];
33
+ }
23
34
 
24
- // Unsupported node type
25
- if (!nodeType) throw new Error(`Unsupported node type provided: ${node.tag}`);
35
+ if (node.type === 'tag' && tagMap[node.tag]) {
36
+ return tagMap[node.tag];
37
+ }
26
38
 
27
- return nodeType;
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: FormattedTextTag): Record<string, Literal> => {
31
- const { alignment } = node.formattingOptions || {};
32
- return {
33
- nodeIndent: null,
34
- nodeTextAlignment: alignment ?? null,
35
- nodeLineHeight: null,
36
- style: '',
37
- level: node.tag?.startsWith('h') ? parseInt(node.tag.substring(1)) : undefined,
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 resolveChild = (child: FormattedNodes): RemirrorJSON => {
42
- if (child.type === 'text') {
43
- return { type: 'text', text: child.value };
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
- let text = '';
47
- const marks: ObjectMark[] = [];
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
- if (child.type === 'tag') {
50
- // Handle link type
51
- if (child.tag === 'a') {
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
- type: 'image',
67
- attrs: {
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 { type: 'text', marks, text };
120
+ return [node];
87
121
  };
88
122
 
89
- const formatNode = (node: FormattedNodes): RemirrorJSON => {
90
- let content: RemirrorJSON[] | undefined;
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 (node.type === 'text') {
97
- content = [
98
- {
99
- type: 'text',
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 as FormattedTextTag),
107
- attrs: getNodeAttributes(node as FormattedTextTag),
108
- content,
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
- return {
120
- type: 'doc',
121
- content: nodes.filter((node: FormattedNodes) => getNodeType(node as FormattedTextTag)).map(formatNode),
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
+ });
@@ -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,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 || 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
- <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,2 +0,0 @@
1
- import { ProsemirrorNode } from 'remirror';
2
- export declare const validRemirrorNode: (node: ProsemirrorNode) => boolean;
@@ -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,3 +0,0 @@
1
- import Editor from './Editor/Editor';
2
-
3
- export default Editor;
@@ -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
- });