@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.
- package/CHANGELOG.md +14 -0
- package/demo/diff/AppContext.tsx +36 -2
- package/demo/diff/contentApi.json +21 -0
- package/demo/diff/index.scss +2 -0
- package/lib/EditorToolbar/Tools/Link/Form/LinkForm.d.ts +3 -2
- package/lib/EditorToolbar/Tools/Link/Form/LinkForm.js +18 -4
- package/lib/EditorToolbar/Tools/Link/LinkButton.js +11 -7
- package/lib/EditorToolbar/Tools/Link/LinkModal.js +8 -4
- package/lib/EditorToolbar/Tools/Link/RemoveLinkButton.js +2 -2
- package/lib/Extensions/Extensions.d.ts +2 -1
- package/lib/Extensions/Extensions.js +3 -0
- package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.d.ts +1 -0
- package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.js +10 -0
- package/lib/Extensions/LinkExtension/AssetLinkExtension.js +1 -1
- package/lib/Extensions/LinkExtension/DamLinkExtension.d.ts +29 -0
- package/lib/Extensions/LinkExtension/DamLinkExtension.js +111 -0
- package/lib/Extensions/LinkExtension/LinkExtension.js +1 -1
- package/lib/ui/Fields/MatrixAsset/MatrixAsset.d.ts +16 -2
- package/lib/ui/Fields/MatrixAsset/MatrixAsset.js +71 -20
- package/lib/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.d.ts +3 -0
- package/lib/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.js +8 -0
- package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +15 -1
- package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +13 -0
- package/package.json +9 -9
- package/src/Editor/Editor.spec.tsx +1 -0
- package/src/EditorToolbar/Tools/Link/Form/LinkForm.tsx +26 -7
- package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +74 -1
- package/src/EditorToolbar/Tools/Link/LinkButton.tsx +19 -8
- package/src/EditorToolbar/Tools/Link/LinkModal.tsx +10 -5
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.tsx +4 -3
- package/src/Extensions/Extensions.ts +3 -0
- package/src/Extensions/FetchUrlExtension/FetchUrlExtension.ts +14 -0
- package/src/Extensions/LinkExtension/AssetLinkExtension.ts +1 -1
- package/src/Extensions/LinkExtension/DamLinkExtension.spec.ts +110 -0
- package/src/Extensions/LinkExtension/DamLinkExtension.ts +137 -0
- package/src/Extensions/LinkExtension/LinkExtension.ts +1 -1
- package/src/ui/Fields/MatrixAsset/MatrixAsset.tsx +83 -26
- package/src/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.spec.tsx +71 -1
- package/src/ui/Fields/ResourceBrowserSelector/ResourceBrowserSelector.tsx +30 -16
- package/src/utils/converters/mocks/squizNodeJson.mock.ts +48 -0
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.spec.ts +63 -0
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +16 -1
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +12 -0
- package/src/utils/getMarkNamesByGroup.spec.ts +1 -1
- package/tests/renderWithContext.tsx +2 -0
@@ -40,6 +40,7 @@ const ResourceBrowserSelector = ({ modalTitle, allowedTypes, value, onChange, ..
|
|
40
40
|
return {
|
41
41
|
sourceId: value.damSystemIdentifier,
|
42
42
|
resourceId: value.damObjectId,
|
43
|
+
variant: value.damAdditional?.variant,
|
43
44
|
};
|
44
45
|
}
|
45
46
|
return null;
|
@@ -53,6 +54,7 @@ const ResourceBrowserSelector = ({ modalTitle, allowedTypes, value, onChange, ..
|
|
53
54
|
damSystemIdentifier: undefined,
|
54
55
|
damObjectId: undefined,
|
55
56
|
damSystemType: undefined,
|
57
|
+
damAdditional: undefined,
|
56
58
|
url: undefined,
|
57
59
|
};
|
58
60
|
if (resource?.source?.type === 'matrix') {
|
@@ -65,11 +67,17 @@ const ResourceBrowserSelector = ({ modalTitle, allowedTypes, value, onChange, ..
|
|
65
67
|
};
|
66
68
|
}
|
67
69
|
else if (resource?.source?.type === 'dam') {
|
70
|
+
const damResource = resource;
|
68
71
|
onChangeData = {
|
69
72
|
...value,
|
70
73
|
damSystemIdentifier: resource?.source?.id,
|
71
74
|
damObjectId: resource?.id,
|
72
75
|
damSystemType: (resource?.source).configuration.externalType,
|
76
|
+
damAdditional: damResource.variant
|
77
|
+
? {
|
78
|
+
variant: damResource.variant,
|
79
|
+
}
|
80
|
+
: undefined,
|
73
81
|
url: resource?.url,
|
74
82
|
nodeType: Extensions_1.NodeName.DAMImage,
|
75
83
|
};
|
@@ -115,12 +115,16 @@ const transformNode = (node) => {
|
|
115
115
|
};
|
116
116
|
}
|
117
117
|
if (node.type.name === Extensions_1.NodeName.DAMImage) {
|
118
|
+
let damAdditional = node.attrs.damAdditional;
|
119
|
+
if (damAdditional && typeof node.attrs.damAdditional === 'string') {
|
120
|
+
damAdditional = JSON.parse(node.attrs.damAdditional);
|
121
|
+
}
|
118
122
|
transformedNode = {
|
119
123
|
type: 'dam-image',
|
120
124
|
damObjectId: node.attrs.damObjectId,
|
121
125
|
damSystemIdentifier: node.attrs.damSystemIdentifier,
|
122
126
|
damSystemType: node.attrs.damSystemType,
|
123
|
-
damAdditional
|
127
|
+
damAdditional,
|
124
128
|
};
|
125
129
|
}
|
126
130
|
node.marks.forEach((mark) => {
|
@@ -212,6 +216,16 @@ const transformMark = (mark, node) => {
|
|
212
216
|
matrixAssetId: mark.attrs.matrixAssetId,
|
213
217
|
children: [],
|
214
218
|
});
|
219
|
+
case 'DAMLink':
|
220
|
+
return wrapNodeIfNeeded(node, {
|
221
|
+
type: 'link-to-dam-asset',
|
222
|
+
target: mark.attrs.target,
|
223
|
+
damSystemType: mark.attrs.damSystemType,
|
224
|
+
damSystemIdentifier: mark.attrs.damSystemIdentifier,
|
225
|
+
damObjectId: mark.attrs.damObjectId,
|
226
|
+
damAdditional: mark.attrs.damAdditional,
|
227
|
+
children: [],
|
228
|
+
});
|
215
229
|
}
|
216
230
|
throw new Error(`Unsupported mark "${mark.type.name}" was applied to node.`);
|
217
231
|
};
|
@@ -8,6 +8,7 @@ const getNodeType = (node) => {
|
|
8
8
|
'link-to-matrix-asset': Extensions_1.NodeName.Text,
|
9
9
|
'matrix-image': Extensions_1.NodeName.AssetImage,
|
10
10
|
'dam-image': Extensions_1.NodeName.DAMImage,
|
11
|
+
'link-to-dam-asset': Extensions_1.NodeName.Text,
|
11
12
|
text: 'text',
|
12
13
|
};
|
13
14
|
const tagMap = {
|
@@ -125,6 +126,18 @@ const getNodeMarks = (node) => {
|
|
125
126
|
},
|
126
127
|
});
|
127
128
|
}
|
129
|
+
else if (node.type === 'link-to-dam-asset') {
|
130
|
+
marks.push({
|
131
|
+
type: Extensions_1.MarkName.DAMLink,
|
132
|
+
attrs: {
|
133
|
+
damSystemType: node.damSystemType,
|
134
|
+
damSystemIdentifier: node.damSystemIdentifier,
|
135
|
+
damObjectId: node.damObjectId,
|
136
|
+
damAdditional: node.damAdditional ? JSON.stringify(node.damAdditional) : undefined,
|
137
|
+
target: node.target,
|
138
|
+
},
|
139
|
+
});
|
140
|
+
}
|
128
141
|
else if (node.type === 'tag' && node.tag === 'strong') {
|
129
142
|
marks.push({ type: 'bold' });
|
130
143
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@squiz/formatted-text-editor",
|
3
|
-
"version": "2.6.
|
3
|
+
"version": "2.6.6",
|
4
4
|
"main": "lib/index.js",
|
5
5
|
"types": "lib/index.d.ts",
|
6
6
|
"private": false,
|
@@ -25,7 +25,7 @@
|
|
25
25
|
"@mui/icons-material": "5.15.18",
|
26
26
|
"@remirror/extension-react-tables": "^2.2.21",
|
27
27
|
"@remirror/react": "2.0.35",
|
28
|
-
"@squiz/dx-json-schema-lib": "^1.
|
28
|
+
"@squiz/dx-json-schema-lib": "^1.81.4",
|
29
29
|
"@squiz/dxp-content-tools-modal": "^0.4.0",
|
30
30
|
"clsx": "2.1.1",
|
31
31
|
"react-hook-form": "7.51.4",
|
@@ -33,16 +33,16 @@
|
|
33
33
|
"remirror": "2.0.39"
|
34
34
|
},
|
35
35
|
"devDependencies": {
|
36
|
-
"@squiz/dam-resource-browser-plugin": "^3.
|
36
|
+
"@squiz/dam-resource-browser-plugin": "^3.2.7",
|
37
37
|
"@squiz/dxp-ai-client-react": "^1.0.0",
|
38
|
-
"@squiz/matrix-resource-browser-plugin": "^3.1
|
39
|
-
"@squiz/resource-browser": "^3.
|
38
|
+
"@squiz/matrix-resource-browser-plugin": "^3.3.1",
|
39
|
+
"@squiz/resource-browser": "^3.2.2",
|
40
40
|
"@squiz/sds": "^1.5.0",
|
41
41
|
"@testing-library/cypress": "^10.0.2",
|
42
42
|
"@testing-library/jest-dom": "5.16.5",
|
43
43
|
"@testing-library/react": "14.0.0",
|
44
44
|
"@testing-library/user-event": "14.4.3",
|
45
|
-
"@types/node": "
|
45
|
+
"@types/node": "22.10.5",
|
46
46
|
"@types/react": "^18.2.45",
|
47
47
|
"@types/react-dom": "^18.2.18",
|
48
48
|
"@vitejs/plugin-react": "3.0.0",
|
@@ -70,10 +70,10 @@
|
|
70
70
|
"vite": "^4.5.3"
|
71
71
|
},
|
72
72
|
"peerDependencies": {
|
73
|
-
"@squiz/dam-resource-browser-plugin": "^3.1.0
|
73
|
+
"@squiz/dam-resource-browser-plugin": "^3.1.0",
|
74
74
|
"@squiz/dxp-ai-client-react": "^1.0.0",
|
75
|
-
"@squiz/matrix-resource-browser-plugin": "^3.1.0
|
76
|
-
"@squiz/resource-browser": "^3.1.0
|
75
|
+
"@squiz/matrix-resource-browser-plugin": "^3.1.0",
|
76
|
+
"@squiz/resource-browser": "^3.1.0",
|
77
77
|
"@squiz/sds": "^1.5.0",
|
78
78
|
"@types/react": "^16.14.0 || ^17 || ^18",
|
79
79
|
"@types/react-dom": "^16.9.0 || ^17 || ^18",
|
@@ -5,19 +5,20 @@ import { FromToProps } from 'remirror';
|
|
5
5
|
import { Input } from '../../../../ui/Fields/Input/Input';
|
6
6
|
import { Checkbox } from '../../../../ui/Fields/Checkbox/Checkbox';
|
7
7
|
import { UpdateLinkProps } from '../../../../Extensions/LinkExtension/LinkExtension';
|
8
|
-
import { UpdateAssetLinkProps } from '../../../../Extensions/LinkExtension/AssetLinkExtension';
|
9
8
|
import { LinkTarget } from '../../../../Extensions/LinkExtension/common';
|
10
9
|
import { MarkName } from '../../../../Extensions/Extensions';
|
11
10
|
import { DeepPartial } from '../../../../types';
|
12
|
-
import {
|
11
|
+
import { noEmptySpacesValidation } from '../../../../utils/validation';
|
13
12
|
import { TabOptions, Tabs } from '../../../../ui/Tabs/Tabs';
|
14
13
|
import { MatrixAsset } from '../../../../ui/Fields/MatrixAsset/MatrixAsset';
|
14
|
+
import { UpdateAssetLinkProps } from '../../../../Extensions/LinkExtension/AssetLinkExtension';
|
15
|
+
import { UpdateDAMLinkProps } from '../../../../Extensions/LinkExtension/DamLinkExtension';
|
15
16
|
|
16
17
|
export type LinkFormData = {
|
17
18
|
linkType: MarkName;
|
18
19
|
text: string;
|
19
20
|
link: UpdateLinkProps['attrs'];
|
20
|
-
assetLink: UpdateAssetLinkProps['attrs']
|
21
|
+
assetLink: Partial<UpdateDAMLinkProps['attrs'] & UpdateAssetLinkProps['attrs']>;
|
21
22
|
range: FromToProps;
|
22
23
|
};
|
23
24
|
|
@@ -48,7 +49,7 @@ export const LinkForm = ({ data, onSubmit }: FormProps): ReactElement => {
|
|
48
49
|
<form className="squiz-fte-form" onSubmit={handleSubmit(onSubmit)}>
|
49
50
|
<div className="squiz-fte-form-group mb-4">
|
50
51
|
<Tabs
|
51
|
-
value={linkType}
|
52
|
+
value={linkType === MarkName.DAMLink ? MarkName.AssetLink : linkType}
|
52
53
|
options={linkTypeOptions}
|
53
54
|
onChange={(value) => setValue('linkType', value as MarkName)}
|
54
55
|
/>
|
@@ -98,17 +99,35 @@ export const LinkForm = ({ data, onSubmit }: FormProps): ReactElement => {
|
|
98
99
|
</>
|
99
100
|
)}
|
100
101
|
{/* Asset link form fields */}
|
101
|
-
{linkType === MarkName.AssetLink && (
|
102
|
+
{(linkType === MarkName.AssetLink || linkType === MarkName.DAMLink) && (
|
102
103
|
<>
|
103
104
|
<div className={clsx('squiz-fte-form-group mb-2')}>
|
104
105
|
<Controller
|
105
106
|
control={control}
|
106
107
|
name="assetLink"
|
107
108
|
rules={{
|
108
|
-
validate:
|
109
|
+
validate: (value) => {
|
110
|
+
if (value?.damObjectId || value?.damSystemIdentifier) {
|
111
|
+
return value.damObjectId && value.damSystemIdentifier ? true : 'A DAM asset must be selected';
|
112
|
+
}
|
113
|
+
return value?.matrixAssetId && value?.matrixIdentifier ? true : 'An asset must be selected';
|
114
|
+
},
|
109
115
|
}}
|
110
116
|
render={({ field: { onChange, value }, fieldState: { error } }) => (
|
111
|
-
<MatrixAsset
|
117
|
+
<MatrixAsset
|
118
|
+
modalTitle="Insert link"
|
119
|
+
value={value}
|
120
|
+
onChange={(newValue) => {
|
121
|
+
onChange(newValue);
|
122
|
+
const updated = newValue.target.value;
|
123
|
+
if (updated.damSystemIdentifier && updated.damObjectId) {
|
124
|
+
setValue('linkType', MarkName.DAMLink);
|
125
|
+
} else {
|
126
|
+
setValue('linkType', MarkName.AssetLink);
|
127
|
+
}
|
128
|
+
}}
|
129
|
+
error={error?.message}
|
130
|
+
/>
|
112
131
|
)}
|
113
132
|
/>
|
114
133
|
</div>
|
@@ -1,8 +1,15 @@
|
|
1
1
|
import '@testing-library/jest-dom';
|
2
2
|
import { act, screen, fireEvent, waitForElementToBeRemoved, waitFor } from '@testing-library/react';
|
3
3
|
import React from 'react';
|
4
|
-
import { renderWithEditor, mockResourceBrowserContext } from '../../../../tests';
|
4
|
+
import { renderWithEditor, mockResourceBrowserContext, mockResourceBrowser } from '../../../../tests';
|
5
5
|
import LinkButton from './LinkButton';
|
6
|
+
import { ResourceBrowserResource } from '@squiz/resource-browser';
|
7
|
+
|
8
|
+
const { setSelectedResource, setShouldUseMockResourceBrowser, mockResourceBrowserImpl } = mockResourceBrowser();
|
9
|
+
jest.mock('@squiz/resource-browser', () => ({
|
10
|
+
...jest.requireActual('@squiz/resource-browser'),
|
11
|
+
ResourceBrowser: (props: any) => mockResourceBrowserImpl(props),
|
12
|
+
}));
|
6
13
|
|
7
14
|
describe('LinkButton', () => {
|
8
15
|
const openModal = async () => {
|
@@ -399,4 +406,70 @@ describe('LinkButton', () => {
|
|
399
406
|
|
400
407
|
expect(screen.getAllByText('Empty space is not allowed')).toHaveLength(2);
|
401
408
|
});
|
409
|
+
|
410
|
+
it('Updates an existing link to be a DAM link', async () => {
|
411
|
+
setShouldUseMockResourceBrowser(true);
|
412
|
+
setSelectedResource({
|
413
|
+
id: 'my-resource-id',
|
414
|
+
name: 'My resource',
|
415
|
+
url: 'myResourceUrl',
|
416
|
+
source: {
|
417
|
+
id: 'my-source-id',
|
418
|
+
type: 'dam',
|
419
|
+
configuration: {
|
420
|
+
externalType: 'bynder',
|
421
|
+
},
|
422
|
+
},
|
423
|
+
} as unknown as ResourceBrowserResource);
|
424
|
+
|
425
|
+
const { editor, getJsonContent } = await renderWithEditor(<LinkButton />, {
|
426
|
+
content: '<a href="https://www.example.org/my-link">Sample link</a> with text',
|
427
|
+
context: {
|
428
|
+
editor: {
|
429
|
+
matrix: {
|
430
|
+
matrixDomain: 'https://my-matrix.squiz.net',
|
431
|
+
},
|
432
|
+
},
|
433
|
+
},
|
434
|
+
});
|
435
|
+
|
436
|
+
await act(() => editor.selectText(5));
|
437
|
+
|
438
|
+
await openModal();
|
439
|
+
fireEvent.click(screen.getByRole('button', { name: 'From source' }));
|
440
|
+
await waitFor(() => {
|
441
|
+
expect(screen.getByRole('button', { name: 'Select ResourceBrowser Resource' })).toBeInTheDocument();
|
442
|
+
});
|
443
|
+
fireEvent.click(await screen.findByRole('button', { name: 'Select ResourceBrowser Resource' }));
|
444
|
+
// Close the image selection modal
|
445
|
+
await act(() => fireEvent.click(screen.getByRole('button', { name: 'Apply' })));
|
446
|
+
|
447
|
+
expect(getJsonContent()).toEqual({
|
448
|
+
type: 'paragraph',
|
449
|
+
attrs: expect.any(Object),
|
450
|
+
content: [
|
451
|
+
{
|
452
|
+
type: 'text',
|
453
|
+
marks: [
|
454
|
+
{
|
455
|
+
type: 'DAMLink',
|
456
|
+
attrs: {
|
457
|
+
damObjectId: 'my-resource-id',
|
458
|
+
damSystemIdentifier: 'my-source-id',
|
459
|
+
damSystemType: 'bynder',
|
460
|
+
href: 'myResourceUrl',
|
461
|
+
target: '_self',
|
462
|
+
damAdditional: undefined,
|
463
|
+
},
|
464
|
+
},
|
465
|
+
],
|
466
|
+
text: 'Sample link',
|
467
|
+
},
|
468
|
+
{
|
469
|
+
type: 'text',
|
470
|
+
text: ' with text',
|
471
|
+
},
|
472
|
+
],
|
473
|
+
});
|
474
|
+
});
|
402
475
|
});
|
@@ -5,8 +5,9 @@ import { LinkFormData } from './Form/LinkForm';
|
|
5
5
|
import Button from '../../../ui/Button/Button';
|
6
6
|
import { useActive, useCommands, useKeymap } from '@remirror/react';
|
7
7
|
import { LinkExtension } from '../../../Extensions/LinkExtension/LinkExtension';
|
8
|
-
import { CommandsExtension } from '../../../Extensions/CommandsExtension/CommandsExtension';
|
9
8
|
import { AssetLinkExtension } from '../../../Extensions/LinkExtension/AssetLinkExtension';
|
9
|
+
import { DAMLinkExtension } from '../../../Extensions/LinkExtension/DamLinkExtension';
|
10
|
+
import { CommandsExtension } from '../../../Extensions/CommandsExtension/CommandsExtension';
|
10
11
|
import { MarkName } from '../../../Extensions/Extensions';
|
11
12
|
import { ImageExtension } from '../../../Extensions/ImageExtension/ImageExtension';
|
12
13
|
import { CodeBlockExtension } from 'remirror/dist-types/extensions';
|
@@ -18,8 +19,13 @@ export type LinkButtonProps = {
|
|
18
19
|
|
19
20
|
const LinkButton = ({ inPopover = false }: LinkButtonProps) => {
|
20
21
|
const [showModal, setShowModal] = useState(false);
|
21
|
-
const { updateLink, updateAssetLink } = useCommands<
|
22
|
-
|
22
|
+
const { updateLink, updateAssetLink, updateDAMLink } = useCommands<
|
23
|
+
AssetLinkExtension | LinkExtension | CommandsExtension | DAMLinkExtension
|
24
|
+
>();
|
25
|
+
const active = useActive<
|
26
|
+
LinkExtension | AssetLinkExtension | ImageExtension | CodeBlockExtension | DAMLinkExtension
|
27
|
+
>();
|
28
|
+
|
23
29
|
// If the image tool is active, disable the link tool as they shouldn't work at the same time
|
24
30
|
const disabled = active.image() || active.codeBlock();
|
25
31
|
const handleClick = () => {
|
@@ -34,10 +40,15 @@ const LinkButton = ({ inPopover = false }: LinkButtonProps) => {
|
|
34
40
|
}, []);
|
35
41
|
|
36
42
|
const handleSubmit = (data: LinkFormData) => {
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
43
|
+
switch (data.linkType) {
|
44
|
+
case MarkName.AssetLink:
|
45
|
+
updateAssetLink({ text: data.text, attrs: data.assetLink, range: data.range });
|
46
|
+
break;
|
47
|
+
case MarkName.DAMLink:
|
48
|
+
updateDAMLink({ text: data.text, attrs: data.assetLink, range: data.range });
|
49
|
+
break;
|
50
|
+
default:
|
51
|
+
updateLink({ text: data.text, attrs: data.link, range: data.range });
|
41
52
|
}
|
42
53
|
|
43
54
|
setShowModal(false);
|
@@ -54,7 +65,7 @@ const LinkButton = ({ inPopover = false }: LinkButtonProps) => {
|
|
54
65
|
<>
|
55
66
|
<Button
|
56
67
|
handleOnClick={handleClick}
|
57
|
-
isActive={active.link() || active.assetLink()}
|
68
|
+
isActive={active.link() || active.assetLink() || active.DAMLink()}
|
58
69
|
icon={<InsertLinkRoundedIcon />}
|
59
70
|
label={`Link (${getShortcutSymbol()}+K)`}
|
60
71
|
isDisabled={disabled}
|
@@ -17,13 +17,18 @@ const LinkModal = ({ onCancel, onSubmit }: LinkModalProps) => {
|
|
17
17
|
helpers,
|
18
18
|
view: { state },
|
19
19
|
} = useRemirrorContext();
|
20
|
-
const { selection, marks } = useExpandedSelection([MarkName.Link, MarkName.AssetLink]);
|
20
|
+
const { selection, marks } = useExpandedSelection([MarkName.Link, MarkName.AssetLink, MarkName.DAMLink]);
|
21
21
|
const selectedText = helpers.getTextBetween(selection.from, selection.to, state.doc);
|
22
|
-
|
23
|
-
|
22
|
+
|
23
|
+
const data: LinkFormData = {
|
24
|
+
assetLink: { ...marks.find((m) => m.type.name === MarkName.DAMLink || m.type.name === MarkName.AssetLink)?.attrs },
|
25
|
+
link: { ...marks.find((m) => m.type.name === 'link')?.attrs },
|
24
26
|
text: selectedText,
|
25
|
-
|
26
|
-
|
27
|
+
linkType: marks.find((m) => m.type.name === MarkName.DAMLink)
|
28
|
+
? MarkName.DAMLink
|
29
|
+
: marks[0]?.type.name === MarkName.Link
|
30
|
+
? MarkName.Link
|
31
|
+
: MarkName.AssetLink,
|
27
32
|
range: { from: selection.from, to: selection.to },
|
28
33
|
};
|
29
34
|
|
@@ -4,16 +4,17 @@ import Button from '../../../ui/Button/Button';
|
|
4
4
|
import LinkOffIcon from '@mui/icons-material/LinkOff';
|
5
5
|
import { AssetLinkExtension } from '../../../Extensions/LinkExtension/AssetLinkExtension';
|
6
6
|
import { LinkExtension } from '../../../Extensions/LinkExtension/LinkExtension';
|
7
|
+
import { DAMLinkExtension } from '../../../Extensions/LinkExtension/DamLinkExtension';
|
7
8
|
import { LinkButtonProps } from './LinkButton';
|
8
9
|
import { getShortcutSymbol } from '../../../utils/getShortcutSymbol';
|
9
10
|
|
10
11
|
const RemoveLinkButton = ({ inPopover = false }: LinkButtonProps) => {
|
11
12
|
const chain = useChainedCommands();
|
12
|
-
const active = useActive<LinkExtension | AssetLinkExtension>();
|
13
|
-
const disabled = !active.link() && !active.assetLink();
|
13
|
+
const active = useActive<LinkExtension | AssetLinkExtension | DAMLinkExtension>();
|
14
|
+
const disabled = !active.link() && !active.assetLink() && !active.DAMLink();
|
14
15
|
|
15
16
|
const handleClick = () => {
|
16
|
-
chain.removeLink().removeAssetLink().focus().run();
|
17
|
+
chain.removeLink().removeAssetLink().removeDAMLink().focus().run();
|
17
18
|
};
|
18
19
|
const handleShortcut = useCallback(() => {
|
19
20
|
handleClick();
|
@@ -30,6 +30,7 @@ import { UnsupportedNodeExtension } from './UnsuportedExtension/UnsupportedNodeE
|
|
30
30
|
import { FetchUrlExtension } from './FetchUrlExtension/FetchUrlExtension';
|
31
31
|
import { TableExtension } from '@remirror/extension-react-tables';
|
32
32
|
import { ReactComponentExtension } from '@remirror/extension-react-component';
|
33
|
+
import { DAMLinkExtension } from './LinkExtension/DamLinkExtension';
|
33
34
|
|
34
35
|
export enum NodeName {
|
35
36
|
Image = 'image',
|
@@ -46,6 +47,7 @@ export enum NodeName {
|
|
46
47
|
export enum MarkName {
|
47
48
|
Link = 'link',
|
48
49
|
AssetLink = 'assetLink',
|
50
|
+
DAMLink = 'DAMLink',
|
49
51
|
}
|
50
52
|
|
51
53
|
export const createExtensions = (context: EditorContextOptions, enableDecorations: boolean = true) => {
|
@@ -69,6 +71,7 @@ export const createExtensions = (context: EditorContextOptions, enableDecoration
|
|
69
71
|
matrixDomain: context.matrix.matrixDomain,
|
70
72
|
}),
|
71
73
|
new DAMImageExtension(),
|
74
|
+
new DAMLinkExtension(),
|
72
75
|
new LinkExtension(),
|
73
76
|
new AssetLinkExtension({
|
74
77
|
matrixDomain: context.matrix.matrixDomain,
|
@@ -51,6 +51,16 @@ export class FetchUrlExtension extends PlainExtension<FetchUrlOptions> {
|
|
51
51
|
}),
|
52
52
|
);
|
53
53
|
}
|
54
|
+
|
55
|
+
const damLinkMark = this.findDAMLinkMark(node.marks as Mark[]);
|
56
|
+
if (node.type.name === 'text' && damLinkMark) {
|
57
|
+
promises.push(
|
58
|
+
this.fetchAndReplace(damLinkMark.attrs, (url: string) => {
|
59
|
+
const updatedMark = damLinkMark.type.create({ ...damLinkMark.attrs, href: url });
|
60
|
+
tr.addMark(pos, pos + node.nodeSize, updatedMark);
|
61
|
+
}),
|
62
|
+
);
|
63
|
+
}
|
54
64
|
});
|
55
65
|
|
56
66
|
if (promises.length) {
|
@@ -64,6 +74,10 @@ export class FetchUrlExtension extends PlainExtension<FetchUrlOptions> {
|
|
64
74
|
return marks.find((mark) => mark.type.name === MarkName.AssetLink && mark.attrs.href === '');
|
65
75
|
}
|
66
76
|
|
77
|
+
private findDAMLinkMark(marks: Mark[]): Mark | undefined {
|
78
|
+
return marks.find((mark) => mark.type.name === MarkName.DAMLink && mark.attrs.href === '');
|
79
|
+
}
|
80
|
+
|
67
81
|
private fetchAndReplace(nodeAttrs: ResolveNodeType, onFetched: (url: string) => void): Promise<void> {
|
68
82
|
return this.options
|
69
83
|
.fetchUrl(nodeAttrs)
|
@@ -48,7 +48,7 @@ export class AssetLinkExtension extends MarkExtension<AssetLinkOptions> {
|
|
48
48
|
createMarkSpec(extra: ApplySchemaAttributes, override: MarkSpecOverride): MarkExtensionSpec {
|
49
49
|
return {
|
50
50
|
inclusive: false,
|
51
|
-
excludes: [this.name, MarkName.Link].join(' '),
|
51
|
+
excludes: [this.name, MarkName.Link, MarkName.DAMLink].join(' '),
|
52
52
|
...override,
|
53
53
|
attrs: {
|
54
54
|
...extra.defaults(),
|
@@ -0,0 +1,110 @@
|
|
1
|
+
import { renderWithEditor } from '../../../tests';
|
2
|
+
|
3
|
+
describe('DAMLinkExtension', () => {
|
4
|
+
it('Parses HTML content representing a DAM link', async () => {
|
5
|
+
const { getJsonContent } = await renderWithEditor(null, {
|
6
|
+
content: `<a data-dam-object-id="123"
|
7
|
+
data-dam-system-identifier="sys-id"
|
8
|
+
data-dam-system-type="type"
|
9
|
+
data-dam-additional='{"variant":"primary"}'
|
10
|
+
href="https://example.org/page"
|
11
|
+
target="_blank">
|
12
|
+
Link Text
|
13
|
+
</a>`,
|
14
|
+
});
|
15
|
+
|
16
|
+
expect(getJsonContent()).toEqual({
|
17
|
+
type: 'paragraph',
|
18
|
+
attrs: expect.any(Object),
|
19
|
+
content: [
|
20
|
+
{
|
21
|
+
type: 'text',
|
22
|
+
text: 'Link Text',
|
23
|
+
marks: [
|
24
|
+
{
|
25
|
+
type: 'DAMLink',
|
26
|
+
attrs: {
|
27
|
+
damObjectId: '123',
|
28
|
+
damSystemIdentifier: 'sys-id',
|
29
|
+
damSystemType: 'type',
|
30
|
+
damAdditional: { variant: 'primary' },
|
31
|
+
href: 'https://example.org/page',
|
32
|
+
target: '_blank',
|
33
|
+
},
|
34
|
+
},
|
35
|
+
],
|
36
|
+
},
|
37
|
+
],
|
38
|
+
});
|
39
|
+
});
|
40
|
+
|
41
|
+
it('Outputs expected HTML', async () => {
|
42
|
+
const { getHtmlContent } = await renderWithEditor(null, {
|
43
|
+
content: {
|
44
|
+
type: 'paragraph',
|
45
|
+
content: [
|
46
|
+
{
|
47
|
+
type: 'text',
|
48
|
+
text: 'Link Text',
|
49
|
+
marks: [
|
50
|
+
{
|
51
|
+
type: 'DAMLink',
|
52
|
+
attrs: {
|
53
|
+
damObjectId: '123',
|
54
|
+
damSystemIdentifier: 'sys-id',
|
55
|
+
damSystemType: 'type',
|
56
|
+
damAdditional: { variant: 'primary' },
|
57
|
+
href: 'https://example.org/page',
|
58
|
+
target: '_blank',
|
59
|
+
},
|
60
|
+
},
|
61
|
+
],
|
62
|
+
},
|
63
|
+
],
|
64
|
+
},
|
65
|
+
});
|
66
|
+
|
67
|
+
expect(getHtmlContent()).toEqual(
|
68
|
+
'<a rel="noopener noreferrer nofollow" ' +
|
69
|
+
'href="https://example.org/page" ' +
|
70
|
+
'target="_blank" ' +
|
71
|
+
'data-dam-object-id="123" ' +
|
72
|
+
'data-dam-system-identifier="sys-id" ' +
|
73
|
+
'data-dam-system-type="type" ' +
|
74
|
+
'data-dam-additional="{"variant":"primary"}">' +
|
75
|
+
'Link Text' +
|
76
|
+
'</a>',
|
77
|
+
);
|
78
|
+
});
|
79
|
+
|
80
|
+
it('Resolves to a regular link if HTML content is missing some of the expected attributes', async () => {
|
81
|
+
const { getJsonContent } = await renderWithEditor(null, {
|
82
|
+
content: `<a href="https://my-bynder-link.com"
|
83
|
+
target="_self"
|
84
|
+
data-dam-object-id="123">
|
85
|
+
Hello!
|
86
|
+
</a>`,
|
87
|
+
});
|
88
|
+
|
89
|
+
expect(getJsonContent()).toEqual({
|
90
|
+
type: 'paragraph',
|
91
|
+
attrs: expect.any(Object),
|
92
|
+
content: [
|
93
|
+
{
|
94
|
+
type: 'text',
|
95
|
+
text: 'Hello!',
|
96
|
+
marks: [
|
97
|
+
{
|
98
|
+
type: 'link',
|
99
|
+
attrs: {
|
100
|
+
href: 'https://my-bynder-link.com',
|
101
|
+
target: '_self',
|
102
|
+
title: null,
|
103
|
+
},
|
104
|
+
},
|
105
|
+
],
|
106
|
+
},
|
107
|
+
],
|
108
|
+
});
|
109
|
+
});
|
110
|
+
});
|