@squiz/formatted-text-editor 2.6.4 → 2.6.6

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 (45) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/demo/diff/AppContext.tsx +36 -2
  3. package/demo/diff/contentApi.json +21 -0
  4. package/demo/diff/index.scss +2 -0
  5. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.d.ts +3 -2
  6. package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +18 -4
  7. package/lib/EditorToolbar/Tools/Link/LinkButton.js +11 -7
  8. package/lib/EditorToolbar/Tools/Link/LinkModal.js +8 -4
  9. package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +2 -2
  10. package/lib/Extensions/Extensions.d.ts +2 -1
  11. package/lib/Extensions/Extensions.js +3 -0
  12. package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.d.ts +1 -0
  13. package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.js +10 -0
  14. package/lib/Extensions/LinkExtension/AssetLinkExtension.js +1 -1
  15. package/lib/Extensions/LinkExtension/DamLinkExtension.d.ts +29 -0
  16. package/lib/Extensions/LinkExtension/DamLinkExtension.js +111 -0
  17. package/lib/Extensions/LinkExtension/LinkExtension.js +1 -1
  18. package/lib/ui/Fields/MatrixAsset/MatrixAsset.d.ts +16 -2
  19. package/lib/ui/Fields/MatrixAsset/MatrixAsset.js +71 -20
  20. package/lib/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.d.ts +3 -0
  21. package/lib/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.js +8 -0
  22. package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +15 -1
  23. package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +13 -0
  24. package/package.json +9 -9
  25. package/src/Editor/Editor.spec.tsx +1 -0
  26. package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +26 -7
  27. package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +74 -1
  28. package/src/EditorToolbar/Tools/Link/LinkButton.tsx +19 -8
  29. package/src/EditorToolbar/Tools/Link/LinkModal.tsx +10 -5
  30. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +4 -3
  31. package/src/Extensions/Extensions.ts +3 -0
  32. package/src/Extensions/FetchUrlExtension/FetchUrlExtension.ts +14 -0
  33. package/src/Extensions/LinkExtension/AssetLinkExtension.ts +1 -1
  34. package/src/Extensions/LinkExtension/DamLinkExtension.spec.ts +110 -0
  35. package/src/Extensions/LinkExtension/DamLinkExtension.ts +137 -0
  36. package/src/Extensions/LinkExtension/LinkExtension.ts +1 -1
  37. package/src/ui/Fields/MatrixAsset/MatrixAsset.tsx +83 -26
  38. package/src/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.spec.tsx +71 -1
  39. package/src/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.tsx +30 -16
  40. package/src/utils/converters/mocks/squizNodeJson.mock.ts +48 -0
  41. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.spec.ts +63 -0
  42. package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +16 -1
  43. package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +12 -0
  44. package/src/utils/getMarkNamesByGroup.spec.ts +1 -1
  45. package/tests/renderWithContext.tsx +2 -0
@@ -0,0 +1,137 @@
1
+ import {
2
+ ApplySchemaAttributes,
3
+ ExtensionPriority,
4
+ FromToProps,
5
+ isElementDomNode,
6
+ MarkExtensionSpec,
7
+ MarkSpecOverride,
8
+ } from 'remirror';
9
+ import { command, CommandFunction, extension, MarkExtension, omitExtraAttributes, removeMark } from '@remirror/core';
10
+ import { LinkTarget, validateTarget } from './common';
11
+ import { CommandsExtension } from '../CommandsExtension/CommandsExtension';
12
+ import { MarkName } from '../Extensions';
13
+
14
+ export type DAMLinkAttributes = {
15
+ damObjectId: string;
16
+ damSystemIdentifier: string;
17
+ damSystemType: string;
18
+ damAdditional?: {
19
+ variant: string;
20
+ };
21
+ target: LinkTarget;
22
+ href: string;
23
+ url: string;
24
+ };
25
+
26
+ export type DAMLinkOptions = {
27
+ defaultTarget?: LinkTarget;
28
+ supportedTargets?: LinkTarget[];
29
+ };
30
+
31
+ export type UpdateDAMLinkProps = {
32
+ text: string;
33
+ attrs: Partial<DAMLinkAttributes>;
34
+ range: FromToProps;
35
+ };
36
+
37
+ @extension<DAMLinkOptions>({
38
+ defaultOptions: {
39
+ defaultTarget: LinkTarget.Self,
40
+ supportedTargets: [LinkTarget.Self, LinkTarget.Blank],
41
+ },
42
+ defaultPriority: ExtensionPriority.High,
43
+ })
44
+ export class DAMLinkExtension extends MarkExtension<DAMLinkOptions> {
45
+ get name(): string {
46
+ return MarkName.DAMLink;
47
+ }
48
+
49
+ createMarkSpec(extra: ApplySchemaAttributes, override: MarkSpecOverride): MarkExtensionSpec {
50
+ return {
51
+ inclusive: false,
52
+ excludes: [this.name, MarkName.Link, MarkName.AssetLink].join(' '),
53
+ ...override,
54
+ attrs: {
55
+ ...extra.defaults(),
56
+ damObjectId: {},
57
+ damSystemIdentifier: {},
58
+ damSystemType: {},
59
+ damAdditional: { default: undefined },
60
+ href: { default: '' },
61
+ target: { default: this.options.defaultTarget },
62
+ },
63
+ parseDOM: [
64
+ {
65
+ tag: 'a[data-dam-object-id]',
66
+ getAttrs: (node) => {
67
+ if (!isElementDomNode(node)) {
68
+ return false;
69
+ }
70
+ const damObjectId = node.getAttribute('data-dam-object-id');
71
+ const damSystemIdentifier = node.getAttribute('data-dam-system-identifier');
72
+ const damSystemType = node.getAttribute('data-dam-system-type');
73
+ let damAdditional = node.getAttribute('data-dam-additional') || undefined;
74
+
75
+ if (!damObjectId || !damSystemIdentifier || !damSystemType) {
76
+ return false;
77
+ }
78
+
79
+ if (damAdditional) {
80
+ damAdditional = JSON.parse(damAdditional);
81
+ }
82
+
83
+ return {
84
+ ...extra.parse(node),
85
+ damObjectId,
86
+ damSystemIdentifier,
87
+ damSystemType,
88
+ damAdditional,
89
+ target: validateTarget(
90
+ node.getAttribute('target'),
91
+ this.options.supportedTargets,
92
+ this.options.defaultTarget,
93
+ ),
94
+ href: node.getAttribute('href') || '',
95
+ };
96
+ },
97
+ },
98
+ ],
99
+ toDOM: (node) => {
100
+ const { damObjectId, damSystemIdentifier, damSystemType, damAdditional, target, href, ...rest } =
101
+ omitExtraAttributes(node.attrs, extra);
102
+ const rel = 'noopener noreferrer nofollow';
103
+ return [
104
+ 'a',
105
+ {
106
+ ...extra.dom(node),
107
+ ...rest,
108
+ rel,
109
+ href,
110
+ target: validateTarget(target, this.options.supportedTargets, this.options.defaultTarget),
111
+ 'data-dam-object-id': damObjectId,
112
+ 'data-dam-system-identifier': damSystemIdentifier,
113
+ 'data-dam-system-type': damSystemType,
114
+ 'data-dam-additional': damAdditional ? JSON.stringify(damAdditional) : undefined,
115
+ },
116
+ 0,
117
+ ];
118
+ },
119
+ };
120
+ }
121
+
122
+ @command()
123
+ updateDAMLink({ text, attrs, range }: UpdateDAMLinkProps): CommandFunction {
124
+ return this.store.getExtension(CommandsExtension).updateMark({
125
+ attrs: { ...attrs, href: attrs.url },
126
+ text,
127
+ range,
128
+ mark: this.type,
129
+ removeMark: !attrs.damObjectId,
130
+ });
131
+ }
132
+
133
+ @command()
134
+ removeDAMLink(): CommandFunction {
135
+ return removeMark({ type: this.type });
136
+ }
137
+ }
@@ -43,7 +43,7 @@ export class LinkExtension extends MarkExtension<LinkOptions> {
43
43
  createMarkSpec(extra: ApplySchemaAttributes, override: MarkSpecOverride): MarkExtensionSpec {
44
44
  return {
45
45
  inclusive: false,
46
- excludes: [this.name, MarkName.AssetLink].join(' '),
46
+ excludes: [this.name, MarkName.AssetLink, MarkName.DAMLink].join(' '),
47
47
  ...override,
48
48
  attrs: {
49
49
  ...extra.defaults(),
@@ -1,14 +1,33 @@
1
- import React from 'react';
2
- import { ResourceBrowserResource, ResourceBrowser } from '@squiz/resource-browser';
1
+ import React, { useCallback } from 'react';
2
+ import { DAMResource, DamResourceBrowserSource } from '@squiz/dam-resource-browser-plugin';
3
+ import { ResourceBrowser, ResourceBrowserResource, ResourceBrowserUnresolvedResource } from '@squiz/resource-browser';
3
4
  import { InputContainer, InputContainerProps } from '../InputContainer/InputContainer';
5
+ import { LinkTarget } from '../../../Extensions/LinkExtension/common';
4
6
 
5
7
  type MatrixAssetValue = {
6
8
  matrixIdentifier?: string;
7
9
  matrixAssetId?: string;
8
- url?: string;
10
+ matrixDomain?: string;
9
11
  };
10
12
 
11
- export type MatrixAssetProps<T extends MatrixAssetValue> = Omit<InputContainerProps, 'children'> & {
13
+ type DAMAssetValue = {
14
+ damSystemIdentifier?: string;
15
+ damObjectId?: string;
16
+ damSystemType?: string;
17
+ damAdditional?: { variant: string };
18
+ };
19
+
20
+ export type ResourceBrowserSelectorValue = MatrixAssetValue &
21
+ DAMAssetValue & {
22
+ url?: string;
23
+ target?: LinkTarget;
24
+ subType?: 'matrix' | 'dam';
25
+ };
26
+
27
+ export type ResourceBrowserSelectorProps<T extends ResourceBrowserSelectorValue> = Omit<
28
+ InputContainerProps,
29
+ 'children'
30
+ > & {
12
31
  modalTitle: string;
13
32
  allowedTypes?: string[];
14
33
  value?: T | null;
@@ -19,38 +38,76 @@ export type MatrixAssetProps<T extends MatrixAssetValue> = Omit<InputContainerPr
19
38
  onChange: (value: { target: { value: T } }) => void;
20
39
  };
21
40
 
22
- export const MatrixAsset = <T extends MatrixAssetValue>({
41
+ export const MatrixAsset = <T extends ResourceBrowserSelectorValue>({
23
42
  modalTitle,
24
43
  allowedTypes,
25
44
  value,
26
45
  onChange,
27
46
  ...props
28
- }: MatrixAssetProps<T>) => {
47
+ }: ResourceBrowserSelectorProps<T>) => {
48
+ const convertFormDataToResourceBrowserValue = useCallback(
49
+ (value: T | null): ResourceBrowserUnresolvedResource | null => {
50
+ if (value && value.damSystemIdentifier && value.damObjectId) {
51
+ return {
52
+ sourceId: value.damSystemIdentifier,
53
+ resourceId: value.damObjectId,
54
+ variant: value.damAdditional?.variant,
55
+ };
56
+ }
57
+ if (value && value.matrixIdentifier && value.matrixAssetId) {
58
+ return {
59
+ sourceId: value.matrixIdentifier,
60
+ resourceId: value.matrixAssetId,
61
+ };
62
+ }
63
+ return null;
64
+ },
65
+ [],
66
+ );
67
+
68
+ const handleResourceChange = useCallback(
69
+ (resource: ResourceBrowserResource | null) => {
70
+ let onChangeData: ResourceBrowserSelectorValue = {
71
+ ...value,
72
+ matrixIdentifier: undefined,
73
+ matrixAssetId: undefined,
74
+ damSystemIdentifier: undefined,
75
+ damObjectId: undefined,
76
+ damSystemType: undefined,
77
+ damAdditional: undefined,
78
+ };
79
+
80
+ if (resource?.source?.type === 'dam') {
81
+ const damResource = resource as DAMResource;
82
+ onChangeData = {
83
+ ...onChangeData,
84
+ damSystemIdentifier: resource.source.id,
85
+ damObjectId: resource.id,
86
+ damSystemType: (resource.source as DamResourceBrowserSource).configuration.externalType,
87
+ damAdditional: damResource.variant ? { variant: damResource.variant } : undefined,
88
+ url: resource.url,
89
+ };
90
+ }
91
+ if (resource?.source.type === 'matrix') {
92
+ onChangeData = {
93
+ ...onChangeData,
94
+ matrixIdentifier: resource?.source?.id,
95
+ matrixAssetId: resource?.id,
96
+ url: resource?.url,
97
+ };
98
+ }
99
+ onChange({ target: { value: onChangeData as T } });
100
+ },
101
+ [value],
102
+ );
103
+
29
104
  return (
30
105
  <InputContainer {...props}>
31
106
  <ResourceBrowser
32
107
  modalTitle={modalTitle}
33
108
  allowedTypes={allowedTypes}
34
- value={
35
- value && value.matrixIdentifier && value.matrixAssetId
36
- ? {
37
- sourceId: value.matrixIdentifier,
38
- resourceId: value.matrixAssetId,
39
- }
40
- : null
41
- }
42
- onChange={(resource: ResourceBrowserResource | null) => {
43
- onChange({
44
- target: {
45
- value: {
46
- ...value,
47
- matrixIdentifier: resource?.source?.id,
48
- matrixAssetId: resource?.id,
49
- url: resource?.url,
50
- } as T,
51
- },
52
- });
53
- }}
109
+ value={convertFormDataToResourceBrowserValue(value as T)}
110
+ onChange={handleResourceChange}
54
111
  />
55
112
  </InputContainer>
56
113
  );
@@ -57,7 +57,6 @@ describe('ResourceBrowserSelector', () => {
57
57
  value={{
58
58
  damObjectId: '123-456-789',
59
59
  damSystemIdentifier: 'zzz-xxx-ccc',
60
- additional: 'addditional data',
61
60
  }}
62
61
  onChange={jest.fn()}
63
62
  />,
@@ -71,6 +70,30 @@ describe('ResourceBrowserSelector', () => {
71
70
  },
72
71
  }),
73
72
  );
73
+
74
+ render(
75
+ <ResourceBrowserSelector
76
+ modalTitle="Insert asset"
77
+ value={{
78
+ damObjectId: '123-456-789',
79
+ damSystemIdentifier: 'zzz-xxx-ccc',
80
+ damAdditional: {
81
+ variant: 'v1',
82
+ },
83
+ }}
84
+ onChange={jest.fn()}
85
+ />,
86
+ );
87
+
88
+ expect(resourceBrowserSpy).toHaveBeenCalledWith(
89
+ expect.objectContaining({
90
+ value: {
91
+ resourceId: '123-456-789',
92
+ sourceId: 'zzz-xxx-ccc',
93
+ variant: 'v1',
94
+ },
95
+ }),
96
+ );
74
97
  });
75
98
 
76
99
  it('AssetImage: Calls onChange with expected value when resources is selected', async () => {
@@ -155,6 +178,53 @@ describe('ResourceBrowserSelector', () => {
155
178
  });
156
179
  });
157
180
 
181
+ it('DAMImage: Calls onChange with expected value when variant resources is selected', async () => {
182
+ const handleChange = jest.fn();
183
+ setSelectedResource({
184
+ id: 'my-resource-id',
185
+ name: 'My resource',
186
+ url: 'myResourceUrl',
187
+ source: {
188
+ id: 'my-source-id',
189
+ type: 'dam',
190
+ configuration: {
191
+ externalType: 'bynder',
192
+ },
193
+ },
194
+ variant: 'v1',
195
+ } as unknown as ResourceBrowserResource);
196
+
197
+ render(
198
+ <ResourceBrowserSelector
199
+ modalTitle="Insert asset"
200
+ value={{ matrixIdentifier: undefined, matrixAssetId: undefined, additional: 'additional data' }}
201
+ onChange={handleChange}
202
+ />,
203
+ );
204
+
205
+ // Trigger the onChange handler to be called
206
+ await waitFor(() => {
207
+ expect(screen.getByRole('button', { name: 'Select ResourceBrowser Resource' })).toBeInTheDocument();
208
+ });
209
+ fireEvent.click(await screen.findByRole('button', { name: 'Select ResourceBrowser Resource' }));
210
+
211
+ expect(handleChange).toHaveBeenCalledWith({
212
+ target: {
213
+ value: {
214
+ additional: 'additional data',
215
+ damObjectId: 'my-resource-id',
216
+ damSystemIdentifier: 'my-source-id',
217
+ damSystemType: 'bynder',
218
+ damAdditional: {
219
+ variant: 'v1',
220
+ },
221
+ url: 'myResourceUrl',
222
+ nodeType: 'DAMImage',
223
+ },
224
+ },
225
+ });
226
+ });
227
+
158
228
  it('ImageAsset: Calls onChange with expected value when resources is cleared', async () => {
159
229
  const handleChange = jest.fn();
160
230
  setSelectedResource(null);
@@ -1,6 +1,6 @@
1
1
  import React, { useCallback } from 'react';
2
- import { ResourceBrowserResource, ResourceBrowser } from '@squiz/resource-browser';
3
- import { DamResourceBrowserSource } from '@squiz/dam-resource-browser-plugin';
2
+ import { ResourceBrowserUnresolvedResource, ResourceBrowserResource, ResourceBrowser } from '@squiz/resource-browser';
3
+ import { DamResourceBrowserSource, DAMResource } from '@squiz/dam-resource-browser-plugin';
4
4
  import { InputContainer, InputContainerProps } from '../InputContainer/InputContainer';
5
5
  import { NodeName } from '../../../Extensions/Extensions';
6
6
 
@@ -12,6 +12,9 @@ type DAMAsset = {
12
12
  damSystemIdentifier?: string;
13
13
  damObjectId?: string;
14
14
  damSystemType?: string;
15
+ damAdditional?: {
16
+ variant: string;
17
+ };
15
18
  };
16
19
  type ResourceBrowserSelectorValue = MatrixAsset &
17
20
  DAMAsset & {
@@ -40,21 +43,25 @@ export const ResourceBrowserSelector = <T extends ResourceBrowserSelectorValue>(
40
43
  onChange,
41
44
  ...props
42
45
  }: ResourceBrowserSelectorProps<T>) => {
43
- const convertFormDataToResourceBrowserValue = useCallback((value: T | null) => {
44
- if (value?.matrixIdentifier && value?.matrixAssetId) {
45
- return {
46
- sourceId: value.matrixIdentifier,
47
- resourceId: value.matrixAssetId,
48
- };
49
- } else if (value?.damSystemIdentifier && value?.damObjectId) {
50
- return {
51
- sourceId: value.damSystemIdentifier,
52
- resourceId: value.damObjectId,
53
- };
54
- }
46
+ const convertFormDataToResourceBrowserValue = useCallback(
47
+ (value: T | null): ResourceBrowserUnresolvedResource | null => {
48
+ if (value?.matrixIdentifier && value?.matrixAssetId) {
49
+ return {
50
+ sourceId: value.matrixIdentifier,
51
+ resourceId: value.matrixAssetId,
52
+ };
53
+ } else if (value?.damSystemIdentifier && value?.damObjectId) {
54
+ return {
55
+ sourceId: value.damSystemIdentifier,
56
+ resourceId: value.damObjectId,
57
+ variant: value.damAdditional?.variant,
58
+ };
59
+ }
55
60
 
56
- return null;
57
- }, []);
61
+ return null;
62
+ },
63
+ [],
64
+ );
58
65
 
59
66
  const handleResourceChange = useCallback((resource: ResourceBrowserResource | null) => {
60
67
  // Clear out any key properties for clear resource use case
@@ -65,6 +72,7 @@ export const ResourceBrowserSelector = <T extends ResourceBrowserSelectorValue>(
65
72
  damSystemIdentifier: undefined,
66
73
  damObjectId: undefined,
67
74
  damSystemType: undefined,
75
+ damAdditional: undefined,
68
76
  url: undefined,
69
77
  };
70
78
 
@@ -77,11 +85,17 @@ export const ResourceBrowserSelector = <T extends ResourceBrowserSelectorValue>(
77
85
  nodeType: NodeName.AssetImage,
78
86
  };
79
87
  } else if (resource?.source?.type === 'dam') {
88
+ const damResource = resource as DAMResource;
80
89
  onChangeData = {
81
90
  ...value,
82
91
  damSystemIdentifier: resource?.source?.id,
83
92
  damObjectId: resource?.id,
84
93
  damSystemType: (resource?.source as DamResourceBrowserSource).configuration.externalType,
94
+ damAdditional: damResource.variant
95
+ ? {
96
+ variant: damResource.variant,
97
+ }
98
+ : undefined,
85
99
  url: resource?.url,
86
100
  nodeType: NodeName.DAMImage,
87
101
  };
@@ -288,4 +288,52 @@ export const squizOnlyNodeExamples: NodeExample[] = [
288
288
  ],
289
289
  },
290
290
  },
291
+ {
292
+ description: 'Link to DAM asset',
293
+ squizNode: [
294
+ {
295
+ type: 'tag',
296
+ tag: 'span',
297
+ children: [
298
+ {
299
+ type: 'link-to-dam-asset',
300
+ damObjectId: '123-456-789',
301
+ damSystemIdentifier: 'zzz-xxx-ccc',
302
+ damSystemType: 'bynder',
303
+ damAdditional: { variant: 'xyz' },
304
+ target: '_blank',
305
+ children: [
306
+ {
307
+ type: 'tag',
308
+ tag: 'span',
309
+ children: [
310
+ {
311
+ type: 'tag',
312
+ tag: 'span',
313
+ children: [{ type: 'text', value: 'Hello' }],
314
+ },
315
+ ],
316
+ },
317
+ ],
318
+ },
319
+ ],
320
+ },
321
+ ],
322
+ remirrorNode: {
323
+ type: 'text',
324
+ text: 'Hello',
325
+ marks: [
326
+ {
327
+ type: 'DAMLink',
328
+ attrs: {
329
+ target: '_blank',
330
+ damObjectId: '123-456-789',
331
+ damSystemIdentifier: 'zzz-xxx-ccc',
332
+ damSystemType: 'bynder',
333
+ damAdditional: JSON.stringify({ variant: 'xyz' }),
334
+ },
335
+ },
336
+ ],
337
+ },
338
+ },
291
339
  ];
@@ -1053,4 +1053,67 @@ describe('resolveNodeTag', () => {
1053
1053
  };
1054
1054
  expect(() => resolveNodeTag(node)).toThrow('Unexpected Remirror node encountered, cannot resolve tag.');
1055
1055
  });
1056
+
1057
+ it('should convert a DAMLink mark to a link-to-dam-asset node', async () => {
1058
+ const content: RemirrorJSON = {
1059
+ type: 'doc',
1060
+ content: [
1061
+ {
1062
+ type: 'paragraph',
1063
+ attrs: {
1064
+ nodeIndent: null,
1065
+ nodeTextAlignment: null,
1066
+ nodeLineHeight: null,
1067
+ style: '',
1068
+ },
1069
+ content: [
1070
+ {
1071
+ type: 'text',
1072
+ text: 'Click here for DAM asset',
1073
+ marks: [
1074
+ {
1075
+ type: 'DAMLink',
1076
+ attrs: {
1077
+ target: '_blank',
1078
+ damSystemType: 'bynder',
1079
+ damSystemIdentifier: 'idA',
1080
+ damObjectId: 'objA',
1081
+ damAdditional: { variant: 'primary' },
1082
+ },
1083
+ },
1084
+ ],
1085
+ },
1086
+ ],
1087
+ },
1088
+ ],
1089
+ };
1090
+
1091
+ const { editor } = await renderWithEditor(null, { content });
1092
+
1093
+ const expected: FormattedTextModels.v1.FormattedText = [
1094
+ {
1095
+ type: 'tag',
1096
+ tag: 'p',
1097
+ children: [
1098
+ {
1099
+ type: 'link-to-dam-asset',
1100
+ target: '_blank',
1101
+ damSystemType: 'bynder',
1102
+ damSystemIdentifier: 'idA',
1103
+ damObjectId: 'objA',
1104
+ damAdditional: { variant: 'primary' },
1105
+ children: [
1106
+ {
1107
+ type: 'text',
1108
+ value: 'Click here for DAM asset',
1109
+ },
1110
+ ],
1111
+ },
1112
+ ],
1113
+ },
1114
+ ];
1115
+
1116
+ const result = remirrorNodeToSquizNode(editor.doc);
1117
+ expect(result).toEqual(expected);
1118
+ });
1056
1119
  });
@@ -154,12 +154,17 @@ const transformNode = (node: ProsemirrorNode): FormattedNode => {
154
154
  }
155
155
 
156
156
  if (node.type.name === NodeName.DAMImage) {
157
+ let damAdditional = node.attrs.damAdditional;
158
+ if (damAdditional && typeof node.attrs.damAdditional === 'string') {
159
+ damAdditional = JSON.parse(node.attrs.damAdditional);
160
+ }
161
+
157
162
  transformedNode = {
158
163
  type: 'dam-image',
159
164
  damObjectId: node.attrs.damObjectId,
160
165
  damSystemIdentifier: node.attrs.damSystemIdentifier,
161
166
  damSystemType: node.attrs.damSystemType,
162
- damAdditional: node.attrs.damAdditional ? JSON.parse(node.attrs.damAdditional) : undefined,
167
+ damAdditional,
163
168
  };
164
169
  }
165
170
 
@@ -277,6 +282,16 @@ const transformMark = (mark: Mark, node: FormattedNode): FormattedNode => {
277
282
  matrixAssetId: mark.attrs.matrixAssetId,
278
283
  children: [],
279
284
  });
285
+ case 'DAMLink':
286
+ return wrapNodeIfNeeded(node, {
287
+ type: 'link-to-dam-asset',
288
+ target: mark.attrs.target,
289
+ damSystemType: mark.attrs.damSystemType,
290
+ damSystemIdentifier: mark.attrs.damSystemIdentifier,
291
+ damObjectId: mark.attrs.damObjectId,
292
+ damAdditional: mark.attrs.damAdditional,
293
+ children: [],
294
+ });
280
295
  }
281
296
 
282
297
  throw new Error(`Unsupported mark "${mark.type.name}" was applied to node.`);
@@ -12,6 +12,7 @@ const getNodeType = (node: FormattedNodes): string => {
12
12
  'link-to-matrix-asset': NodeName.Text,
13
13
  'matrix-image': NodeName.AssetImage,
14
14
  'dam-image': NodeName.DAMImage,
15
+ 'link-to-dam-asset': NodeName.Text,
15
16
  text: 'text',
16
17
  };
17
18
 
@@ -134,6 +135,17 @@ const getNodeMarks = (node: FormattedNodes): ObjectMark[] => {
134
135
  target: node.target,
135
136
  },
136
137
  });
138
+ } else if (node.type === 'link-to-dam-asset') {
139
+ marks.push({
140
+ type: MarkName.DAMLink,
141
+ attrs: {
142
+ damSystemType: node.damSystemType,
143
+ damSystemIdentifier: node.damSystemIdentifier,
144
+ damObjectId: node.damObjectId,
145
+ damAdditional: node.damAdditional ? JSON.stringify(node.damAdditional) : undefined,
146
+ target: node.target,
147
+ },
148
+ });
137
149
  } else if (node.type === 'tag' && node.tag === 'strong') {
138
150
  marks.push({ type: 'bold' });
139
151
  } else if (node.type === 'tag' && node.tag === 'em') {
@@ -15,6 +15,6 @@ describe('getMarkNamesGroup', () => {
15
15
  // Marks in the first array will be transformed to a paragraph when formatting is cleared.
16
16
  // Mark in the second array will be left as-is.
17
17
  expect(formattingMarkNames).toEqual(['bold', 'italic', 'underline']);
18
- expect(otherMarkNames).toEqual(['assetLink', 'link']);
18
+ expect(otherMarkNames).toEqual(['DAMLink', 'assetLink', 'link']);
19
19
  });
20
20
  });
@@ -46,6 +46,7 @@ export const renderWithContext = (ui: ReactElement | null, options?: ContextRend
46
46
  const onRequestChildren = jest.fn().mockResolvedValue(resources);
47
47
  const onRequestResource = jest.fn(() => Promise.resolve(resources[0]));
48
48
  const onSearchRequest = jest.fn();
49
+ const onRequestMatrixAsset = jest.fn();
49
50
  editorContext.resolveNodeToUrl = jest.fn(() => Promise.resolve(resources[0].url));
50
51
 
51
52
  return render(
@@ -67,6 +68,7 @@ export const renderWithContext = (ui: ReactElement | null, options?: ContextRend
67
68
  onRequestChildren: onRequestChildren,
68
69
  onRequestResource: onRequestResource,
69
70
  onSearchRequest,
71
+ onRequestMatrixAsset,
70
72
  }),
71
73
  ],
72
74
  }}