@squiz/formatted-text-editor 1.71.0 → 2.0.1-rc.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/CHANGELOG.md +24 -0
- package/demo/App.tsx +7 -1
- package/demo/AppContext.tsx +49 -18
- package/demo/resources.json +44 -0
- package/demo/sources.json +4 -0
- package/lib/Editor/Editor.js +1 -2
- package/lib/Editor/EditorContext.d.ts +2 -0
- package/lib/Editor/EditorContext.js +3 -0
- package/lib/Extensions/Extensions.d.ts +1 -2
- package/lib/Extensions/Extensions.js +2 -2
- package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.d.ts +2 -4
- package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.js +7 -7
- package/lib/index.d.ts +2 -0
- package/lib/types.d.ts +4 -0
- package/lib/ui/Fields/MatrixAsset/MatrixAsset.js +7 -7
- package/lib/utils/converters/htmlToSquizNode/htmlToSquizNode.js +2 -9
- package/package.json +3 -2
- package/src/Editor/Editor.spec.tsx +14 -4
- package/src/Editor/Editor.tsx +1 -2
- package/src/Editor/EditorContext.spec.tsx +1 -0
- package/src/Editor/EditorContext.ts +5 -0
- package/src/EditorToolbar/Tools/Image/Form/ImageForm.spec.tsx +5 -3
- package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +6 -0
- package/src/EditorToolbar/Tools/Link/Form/LinkForm.spec.tsx +19 -7
- package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +7 -1
- package/src/EditorToolbar/Tools/Link/RemoveLinkButton.spec.tsx +3 -3
- package/src/Extensions/Extensions.ts +2 -3
- package/src/Extensions/FetchUrlExtension/FetchUrlExtension.ts +12 -17
- package/src/index.ts +2 -0
- package/src/types.ts +3 -0
- package/src/ui/Fields/MatrixAsset/MatrixAsset.spec.tsx +26 -9
- package/src/ui/Fields/MatrixAsset/MatrixAsset.tsx +8 -8
- package/src/utils/converters/htmlToSquizNode/htmlToSquizNode.ts +6 -16
- package/tests/mockResourceBrowserContext.tsx +48 -12
- package/tests/renderWithContext.tsx +31 -3
- package/tests/renderWithEditor.tsx +1 -3
- package/vite.config.ts +2 -2
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,29 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 0.0.0-rbv2-20240607040706
|
4
|
+
|
5
|
+
### Patch Changes
|
6
|
+
|
7
|
+
- version bump for updated rb styles
|
8
|
+
|
9
|
+
## 0.0.0-rbv2-20240606061755
|
10
|
+
|
11
|
+
### Patch Changes
|
12
|
+
|
13
|
+
- d422ee2: version bump for testing in Matrix
|
14
|
+
|
15
|
+
## 0.0.0-rbv2-20240530041851
|
16
|
+
|
17
|
+
### Major Changes
|
18
|
+
|
19
|
+
- f7279c6: Migration to resource-browser 2.x.x and use of plugin model. Alterations to data fetching for url resolution for FTE due to changes in RB2.
|
20
|
+
|
21
|
+
Breaking change: change to resource-browser 2.x.x requires a different context wrapper to version 1.x.x which the component editing ui and the formatted text editor relies upon to render the resource picker when selecting a resource; making the internal update to RB2 incompatible with 1.x.x usages of these packages.
|
22
|
+
|
23
|
+
This change was made to expand the support the resource browser had for data sources other than Matrix.
|
24
|
+
|
25
|
+
Consumers need to update usages of ResourceBrowserContext.provider with ResourceBrowserContextProvider and provide plugins e.g. @squiz/matrix-resource-browser-plugin or @squiz/dam-resource-browser-plugin to the context interface. Consumers also need to provide a new url fetcher to the FTE EditorContext.Provider `resolveNodeToUrl` to resolve MatrixAsset nodes to a url form; previously this relied upon exposed parts of the ResourceBrowser which are no longer exposed outside the RB itself.
|
26
|
+
|
3
27
|
## 1.71.0
|
4
28
|
|
5
29
|
### Minor Changes
|
package/demo/App.tsx
CHANGED
@@ -76,7 +76,13 @@ function App() {
|
|
76
76
|
<Editor
|
77
77
|
editable={editable}
|
78
78
|
border={border}
|
79
|
-
content={
|
79
|
+
content={`
|
80
|
+
<p>Hello
|
81
|
+
<a href="https://www.google.com"><strong>Mr Bean</strong></a>, nice to <a href="https://www.google.com">meet you</a>.
|
82
|
+
<img src="https://media2.giphy.com/media/3o6ozsIxg5legYvggo/giphy.gif" height="150" width="200"/>
|
83
|
+
<img data-matrix-asset-id="1" data-matrix-identifier="matrixIdentifier" data-matrix-domain="https://matrix-domain.squiz.net">
|
84
|
+
</p>
|
85
|
+
`}
|
80
86
|
onChange={handleEditorChange}
|
81
87
|
enableTableTool={enableTableTool}
|
82
88
|
>
|
package/demo/AppContext.tsx
CHANGED
@@ -1,29 +1,48 @@
|
|
1
1
|
import React, { PropsWithChildren } from 'react';
|
2
|
-
import {
|
2
|
+
import { ResourceBrowserSource, ResourceBrowserContextProvider } from '@squiz/resource-browser';
|
3
|
+
import MatrixResourceBrowserPlugin, {
|
4
|
+
Source,
|
5
|
+
Resource,
|
6
|
+
ResourceReference,
|
7
|
+
} from '@squiz/matrix-resource-browser-plugin';
|
3
8
|
import { EditorContext } from '../src';
|
4
9
|
import resources from './resources.json';
|
5
10
|
import sources from './sources.json';
|
11
|
+
import { ResolveNodeType } from '../src/types';
|
6
12
|
|
7
13
|
export type AppContextProps = PropsWithChildren;
|
8
|
-
|
9
14
|
export const AppContext = ({ children }: AppContextProps) => (
|
10
|
-
<
|
15
|
+
<ResourceBrowserContextProvider
|
11
16
|
value={{
|
12
|
-
onRequestSources: (): Promise<
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
onRequestSources: (): Promise<ResourceBrowserSource[]> =>
|
18
|
+
Promise.resolve([
|
19
|
+
{
|
20
|
+
name: 'Matrix',
|
21
|
+
id: 'matrixIdentifier',
|
22
|
+
type: 'matrix',
|
23
|
+
},
|
24
|
+
]),
|
25
|
+
plugins: [
|
26
|
+
MatrixResourceBrowserPlugin({
|
27
|
+
onRequestSources: (): Promise<Source> => Promise.resolve(sources),
|
28
|
+
onRequestChildren: (source: Source, resource: Resource | null): Promise<Resource[]> =>
|
29
|
+
Promise.resolve(resource._children || resources),
|
30
|
+
onRequestResource: (reference: ResourceReference): Promise<Resource | null> => {
|
31
|
+
const flattenResources = (resources: unknown[]) => {
|
32
|
+
return [
|
33
|
+
...resources,
|
34
|
+
...resources.flatMap((resource) =>
|
35
|
+
'_children' in resource ? flattenResources(resource._children) : [],
|
36
|
+
),
|
37
|
+
];
|
38
|
+
};
|
22
39
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
40
|
+
return Promise.resolve(
|
41
|
+
flattenResources(resources).find((resource) => resource.id === reference.resource) || null,
|
42
|
+
);
|
43
|
+
},
|
44
|
+
}),
|
45
|
+
],
|
27
46
|
}}
|
28
47
|
>
|
29
48
|
<EditorContext.Provider
|
@@ -31,9 +50,21 @@ export const AppContext = ({ children }: AppContextProps) => (
|
|
31
50
|
matrix: {
|
32
51
|
matrixDomain: 'https://matrix-domain.squiz.net',
|
33
52
|
},
|
53
|
+
resolveNodeToUrl: (node: ResolveNodeType): Promise<string> => {
|
54
|
+
const nodeResource = node.matrixAssetId;
|
55
|
+
const flattenResources = (resources: unknown[]) => {
|
56
|
+
return [
|
57
|
+
...resources,
|
58
|
+
...resources.flatMap((resource) => ('_children' in resource ? flattenResources(resource._children) : [])),
|
59
|
+
];
|
60
|
+
};
|
61
|
+
|
62
|
+
const resource = flattenResources(resources).find((resource) => resource.id === nodeResource);
|
63
|
+
return Promise.resolve(resource.url || '');
|
64
|
+
},
|
34
65
|
}}
|
35
66
|
>
|
36
67
|
{children}
|
37
68
|
</EditorContext.Provider>
|
38
|
-
</
|
69
|
+
</ResourceBrowserContextProvider>
|
39
70
|
);
|
package/demo/resources.json
CHANGED
@@ -9,6 +9,10 @@
|
|
9
9
|
"code": "live",
|
10
10
|
"name": "Live"
|
11
11
|
},
|
12
|
+
"source": {
|
13
|
+
"id": "1",
|
14
|
+
"type": "matrix"
|
15
|
+
},
|
12
16
|
"name": "Example image one",
|
13
17
|
"childCount": 0,
|
14
18
|
"url": "https://picsum.photos/200/300"
|
@@ -23,6 +27,10 @@
|
|
23
27
|
"code": "live",
|
24
28
|
"name": "Live"
|
25
29
|
},
|
30
|
+
"source": {
|
31
|
+
"id": "1",
|
32
|
+
"type": "matrix"
|
33
|
+
},
|
26
34
|
"name": "Example image two",
|
27
35
|
"childCount": 0,
|
28
36
|
"url": "https://picsum.photos/200/300"
|
@@ -37,6 +45,10 @@
|
|
37
45
|
"code": "live",
|
38
46
|
"name": "Live"
|
39
47
|
},
|
48
|
+
"source": {
|
49
|
+
"id": "1",
|
50
|
+
"type": "matrix"
|
51
|
+
},
|
40
52
|
"name": "Example page",
|
41
53
|
"childCount": 0,
|
42
54
|
"url": "https://picsum.photos/200/300"
|
@@ -51,6 +63,10 @@
|
|
51
63
|
"code": "live",
|
52
64
|
"name": "Live"
|
53
65
|
},
|
66
|
+
"source": {
|
67
|
+
"id": "1",
|
68
|
+
"type": "matrix"
|
69
|
+
},
|
54
70
|
"name": "Example folder",
|
55
71
|
"childCount": 10,
|
56
72
|
"url": "",
|
@@ -65,6 +81,10 @@
|
|
65
81
|
"code": "live",
|
66
82
|
"name": "Live"
|
67
83
|
},
|
84
|
+
"source": {
|
85
|
+
"id": "1",
|
86
|
+
"type": "matrix"
|
87
|
+
},
|
68
88
|
"name": "Example image one #2",
|
69
89
|
"childCount": 0,
|
70
90
|
"url": "https://picsum.photos/200/300"
|
@@ -79,6 +99,10 @@
|
|
79
99
|
"code": "live",
|
80
100
|
"name": "Live"
|
81
101
|
},
|
102
|
+
"source": {
|
103
|
+
"id": "1",
|
104
|
+
"type": "matrix"
|
105
|
+
},
|
82
106
|
"name": "Example image two #2",
|
83
107
|
"childCount": 0,
|
84
108
|
"url": "https://picsum.photos/200/300"
|
@@ -93,6 +117,10 @@
|
|
93
117
|
"code": "live",
|
94
118
|
"name": "Live"
|
95
119
|
},
|
120
|
+
"source": {
|
121
|
+
"id": "1",
|
122
|
+
"type": "matrix"
|
123
|
+
},
|
96
124
|
"name": "Example page #2",
|
97
125
|
"childCount": 0,
|
98
126
|
"url": "https://picsum.photos/200/300"
|
@@ -107,6 +135,10 @@
|
|
107
135
|
"code": "live",
|
108
136
|
"name": "Live"
|
109
137
|
},
|
138
|
+
"source": {
|
139
|
+
"id": "1",
|
140
|
+
"type": "matrix"
|
141
|
+
},
|
110
142
|
"name": "Example folder #2",
|
111
143
|
"childCount": 10,
|
112
144
|
"url": "",
|
@@ -121,6 +153,10 @@
|
|
121
153
|
"code": "live",
|
122
154
|
"name": "Live"
|
123
155
|
},
|
156
|
+
"source": {
|
157
|
+
"id": "1",
|
158
|
+
"type": "matrix"
|
159
|
+
},
|
124
160
|
"name": "Example image one #3",
|
125
161
|
"childCount": 0,
|
126
162
|
"url": "https://picsum.photos/200/300"
|
@@ -135,6 +171,10 @@
|
|
135
171
|
"code": "live",
|
136
172
|
"name": "Live"
|
137
173
|
},
|
174
|
+
"source": {
|
175
|
+
"id": "1",
|
176
|
+
"type": "matrix"
|
177
|
+
},
|
138
178
|
"name": "Example image two #3",
|
139
179
|
"childCount": 0,
|
140
180
|
"url": "https://picsum.photos/200/300"
|
@@ -149,6 +189,10 @@
|
|
149
189
|
"code": "live",
|
150
190
|
"name": "Live"
|
151
191
|
},
|
192
|
+
"source": {
|
193
|
+
"id": "1",
|
194
|
+
"type": "matrix"
|
195
|
+
},
|
152
196
|
"name": "Example page #3",
|
153
197
|
"childCount": 0,
|
154
198
|
"url": "https://picsum.photos/200/300"
|
package/demo/sources.json
CHANGED
package/lib/Editor/Editor.js
CHANGED
@@ -33,7 +33,6 @@ const EditorToolbar_1 = require("../EditorToolbar");
|
|
33
33
|
const EditorContext_1 = require("./EditorContext");
|
34
34
|
const Extensions_1 = require("../Extensions/Extensions");
|
35
35
|
const useFocus_1 = __importDefault(require("../hooks/useFocus"));
|
36
|
-
const resource_browser_1 = require("@squiz/resource-browser");
|
37
36
|
const extension_react_tables_1 = require("@remirror/extension-react-tables");
|
38
37
|
const WrappedEditor = () => {
|
39
38
|
const preventImagePaste = (0, react_1.useCallback)((event) => {
|
@@ -53,7 +52,7 @@ const WrappedEditor = () => {
|
|
53
52
|
};
|
54
53
|
const Editor = ({ content, className, border = true, editable = true, onChange, children, isFocused, attributes, enableTableTool = false, }) => {
|
55
54
|
const { manager, state, setState } = (0, react_2.useRemirror)({
|
56
|
-
extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext)
|
55
|
+
extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext)),
|
57
56
|
content,
|
58
57
|
selection: 'start',
|
59
58
|
stringHandler: 'html',
|
@@ -1,8 +1,10 @@
|
|
1
1
|
import React from 'react';
|
2
|
+
import { ResolveNodeToUrl } from '../types';
|
2
3
|
export type EditorContextOptions = {
|
3
4
|
matrix: {
|
4
5
|
matrixDomain: string;
|
5
6
|
};
|
7
|
+
resolveNodeToUrl: ResolveNodeToUrl;
|
6
8
|
};
|
7
9
|
export declare const defaultEditorContext: EditorContextOptions;
|
8
10
|
export declare const EditorContext: React.Context<EditorContextOptions>;
|
@@ -9,5 +9,8 @@ exports.defaultEditorContext = {
|
|
9
9
|
matrix: {
|
10
10
|
matrixDomain: '',
|
11
11
|
},
|
12
|
+
resolveNodeToUrl: () => {
|
13
|
+
throw new Error('resolveNodeToUrl has not been configured.');
|
14
|
+
},
|
12
15
|
};
|
13
16
|
exports.EditorContext = react_1.default.createContext(exports.defaultEditorContext);
|
@@ -1,6 +1,5 @@
|
|
1
1
|
import { Extension } from '@remirror/core';
|
2
2
|
import { EditorContextOptions } from '../Editor/EditorContext';
|
3
|
-
import { ResourceBrowserContextProps } from '@squiz/resource-browser';
|
4
3
|
export declare enum NodeName {
|
5
4
|
Image = "image",
|
6
5
|
CodeBlock = "codeBlock",
|
@@ -15,4 +14,4 @@ export declare enum MarkName {
|
|
15
14
|
Link = "link",
|
16
15
|
AssetLink = "assetLink"
|
17
16
|
}
|
18
|
-
export declare const createExtensions: (context: EditorContextOptions
|
17
|
+
export declare const createExtensions: (context: EditorContextOptions) => () => Extension[];
|
@@ -30,7 +30,7 @@ var MarkName;
|
|
30
30
|
MarkName["Link"] = "link";
|
31
31
|
MarkName["AssetLink"] = "assetLink";
|
32
32
|
})(MarkName = exports.MarkName || (exports.MarkName = {}));
|
33
|
-
const createExtensions = (context
|
33
|
+
const createExtensions = (context) => {
|
34
34
|
return () => {
|
35
35
|
return [
|
36
36
|
new CommandsExtension_1.CommandsExtension(),
|
@@ -62,7 +62,7 @@ const createExtensions = (context, browserContext) => {
|
|
62
62
|
new extensions_1.HorizontalRuleExtension(),
|
63
63
|
new extensions_1.PlaceholderExtension(),
|
64
64
|
new FetchUrlExtension_1.FetchUrlExtension({
|
65
|
-
fetchUrl:
|
65
|
+
fetchUrl: context.resolveNodeToUrl,
|
66
66
|
}),
|
67
67
|
new extensions_1.TextExtension(),
|
68
68
|
new extension_react_tables_1.TableExtension(),
|
@@ -1,10 +1,8 @@
|
|
1
1
|
import { PlainExtension } from '@remirror/core';
|
2
2
|
import { Dispose, EditorView } from '@remirror/core-types';
|
3
|
+
import { ResolveNodeToUrl } from '../../types';
|
3
4
|
export type FetchUrlOptions = {
|
4
|
-
fetchUrl?:
|
5
|
-
resource: string;
|
6
|
-
source: string;
|
7
|
-
}) => Promise<any>;
|
5
|
+
fetchUrl?: ResolveNodeToUrl;
|
8
6
|
};
|
9
7
|
export declare class FetchUrlExtension extends PlainExtension<FetchUrlOptions> {
|
10
8
|
get name(): string;
|
@@ -20,14 +20,14 @@ let FetchUrlExtension = class FetchUrlExtension extends core_1.PlainExtension {
|
|
20
20
|
const promises = [];
|
21
21
|
state.doc.descendants((node, pos) => {
|
22
22
|
if (node.type.name === Extensions_1.NodeName.AssetImage && node.attrs.src === '') {
|
23
|
-
promises.push(this.fetchAndReplace(node.attrs
|
23
|
+
promises.push(this.fetchAndReplace(node.attrs, (url) => {
|
24
24
|
const newNode = state.schema.nodes[Extensions_1.NodeName.AssetImage].create({ ...node.attrs, src: url });
|
25
25
|
tr.replaceWith(pos, pos + node.nodeSize, newNode);
|
26
26
|
}));
|
27
27
|
}
|
28
28
|
const assetLinkMark = this.findAssetLinkMark(node.marks);
|
29
29
|
if (node.type.name === 'text' && assetLinkMark) {
|
30
|
-
promises.push(this.fetchAndReplace(assetLinkMark.attrs
|
30
|
+
promises.push(this.fetchAndReplace(assetLinkMark.attrs, (url) => {
|
31
31
|
const updatedMark = assetLinkMark.type.create({ ...assetLinkMark.attrs, href: url });
|
32
32
|
tr.addMark(pos, pos + node.nodeSize, updatedMark);
|
33
33
|
}));
|
@@ -42,11 +42,11 @@ let FetchUrlExtension = class FetchUrlExtension extends core_1.PlainExtension {
|
|
42
42
|
findAssetLinkMark(marks) {
|
43
43
|
return marks.find((mark) => mark.type.name === Extensions_1.MarkName.AssetLink && mark.attrs.href === '');
|
44
44
|
}
|
45
|
-
fetchAndReplace(
|
45
|
+
fetchAndReplace(nodeAttrs, onFetched) {
|
46
46
|
return this.options
|
47
|
-
.fetchUrl(
|
48
|
-
.then((
|
49
|
-
onFetched(
|
47
|
+
.fetchUrl(nodeAttrs)
|
48
|
+
.then((url) => {
|
49
|
+
onFetched(url);
|
50
50
|
})
|
51
51
|
.catch((error) => {
|
52
52
|
console.error('Error fetching URL:', error);
|
@@ -56,7 +56,7 @@ let FetchUrlExtension = class FetchUrlExtension extends core_1.PlainExtension {
|
|
56
56
|
FetchUrlExtension = __decorate([
|
57
57
|
(0, core_1.extension)({
|
58
58
|
defaultOptions: {
|
59
|
-
fetchUrl: () => Promise.resolve(),
|
59
|
+
fetchUrl: () => Promise.resolve(''),
|
60
60
|
},
|
61
61
|
})
|
62
62
|
], FetchUrlExtension);
|
package/lib/index.d.ts
CHANGED
@@ -3,4 +3,6 @@ import { EditorContext } from './Editor/EditorContext';
|
|
3
3
|
import { remirrorNodeToSquizNode } from './utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode';
|
4
4
|
import { squizNodeToRemirrorNode } from './utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode';
|
5
5
|
import { htmlToSquizNode } from './utils/converters/htmlToSquizNode/htmlToSquizNode';
|
6
|
+
import { ResolveNodeType, ResolveNodeToUrl } from './types';
|
6
7
|
export { Editor, EditorContext, remirrorNodeToSquizNode, squizNodeToRemirrorNode, htmlToSquizNode };
|
8
|
+
export type { ResolveNodeType, ResolveNodeToUrl };
|
package/lib/types.d.ts
CHANGED
@@ -1,3 +1,7 @@
|
|
1
1
|
export type DeepPartial<T> = {
|
2
2
|
[P in keyof T]?: T[P] extends Array<infer U> ? Array<DeepPartial<U>> : T[P] extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : DeepPartial<T[P]>;
|
3
3
|
};
|
4
|
+
export type ResolveNodeType = {
|
5
|
+
[attr: string]: any;
|
6
|
+
};
|
7
|
+
export type ResolveNodeToUrl = (node: ResolveNodeType) => Promise<string>;
|
@@ -9,19 +9,19 @@ const resource_browser_1 = require("@squiz/resource-browser");
|
|
9
9
|
const InputContainer_1 = require("../InputContainer/InputContainer");
|
10
10
|
const MatrixAsset = ({ modalTitle, allowedTypes, value, onChange, ...props }) => {
|
11
11
|
return (react_1.default.createElement(InputContainer_1.InputContainer, { ...props },
|
12
|
-
react_1.default.createElement(resource_browser_1.
|
12
|
+
react_1.default.createElement(resource_browser_1.ResourceBrowser, { modalTitle: modalTitle, allowedTypes: allowedTypes, value: value && value.matrixIdentifier && value.matrixAssetId
|
13
13
|
? {
|
14
|
-
|
15
|
-
|
14
|
+
sourceId: value.matrixIdentifier,
|
15
|
+
resourceId: value.matrixAssetId,
|
16
16
|
}
|
17
|
-
: null, onChange: (
|
17
|
+
: null, onChange: (resource) => {
|
18
18
|
onChange({
|
19
19
|
target: {
|
20
20
|
value: {
|
21
21
|
...value,
|
22
|
-
matrixIdentifier:
|
23
|
-
matrixAssetId:
|
24
|
-
url:
|
22
|
+
matrixIdentifier: resource?.source?.id,
|
23
|
+
matrixAssetId: resource?.id,
|
24
|
+
url: resource?.url,
|
25
25
|
},
|
26
26
|
},
|
27
27
|
});
|
@@ -10,15 +10,8 @@ const htmlToSquizNode = ({ content }) => {
|
|
10
10
|
matrix: {
|
11
11
|
matrixDomain: 'unsupported',
|
12
12
|
},
|
13
|
-
|
14
|
-
|
15
|
-
throw new Error('Resolving Matrix assets is not supported.');
|
16
|
-
},
|
17
|
-
onRequestSources: () => {
|
18
|
-
throw new Error('Resolving Matrix assets is not supported.');
|
19
|
-
},
|
20
|
-
onRequestChildren: () => {
|
21
|
-
throw new Error('Resolving Matrix assets is not supported.');
|
13
|
+
resolveNodeToUrl: () => {
|
14
|
+
throw new Error('Resolving Url is not supported.');
|
22
15
|
},
|
23
16
|
});
|
24
17
|
const manager = core_1.RemirrorManager.create(extensions);
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@squiz/formatted-text-editor",
|
3
|
-
"version": "1.
|
3
|
+
"version": "2.0.1-rc.0",
|
4
4
|
"main": "lib/index.js",
|
5
5
|
"types": "lib/index.d.ts",
|
6
6
|
"private": false,
|
@@ -25,7 +25,8 @@
|
|
25
25
|
"@remirror/extension-react-tables": "^2.2.19",
|
26
26
|
"@remirror/react": "2.0.25",
|
27
27
|
"@squiz/dx-json-schema-lib": "^1.65.1",
|
28
|
-
"@squiz/resource-browser": "^
|
28
|
+
"@squiz/matrix-resource-browser-plugin": "^2.0.0",
|
29
|
+
"@squiz/resource-browser": "^2.0.0",
|
29
30
|
"clsx": "1.2.1",
|
30
31
|
"react-hook-form": "7.43.2",
|
31
32
|
"react-image-size": "2.0.0",
|
@@ -1,9 +1,9 @@
|
|
1
1
|
import '@testing-library/jest-dom';
|
2
2
|
import React from 'react';
|
3
3
|
import { act, fireEvent, render, screen, within } from '@testing-library/react';
|
4
|
-
import {
|
4
|
+
import { MatrixResourceBrowserPluginProps } from '@squiz/matrix-resource-browser-plugin';
|
5
5
|
import Editor from './Editor';
|
6
|
-
import { renderWithEditor } from '../../tests';
|
6
|
+
import { renderWithEditor, mockResourceBrowserContext } from '../../tests';
|
7
7
|
import ImageButton from '../EditorToolbar/Tools/Image/ImageButton';
|
8
8
|
import * as useFocus from '../hooks/useFocus';
|
9
9
|
|
@@ -432,10 +432,20 @@ describe('Formatted text editor', () => {
|
|
432
432
|
childCount: '1',
|
433
433
|
});
|
434
434
|
|
435
|
+
const { MockResourceBrowserContext } = mockResourceBrowserContext({
|
436
|
+
sources: [],
|
437
|
+
resources: [],
|
438
|
+
pluginProps: {
|
439
|
+
onRequestSources,
|
440
|
+
onRequestChildren,
|
441
|
+
onRequestResource,
|
442
|
+
} as MatrixResourceBrowserPluginProps,
|
443
|
+
});
|
444
|
+
|
435
445
|
render(
|
436
|
-
<
|
446
|
+
<MockResourceBrowserContext>
|
437
447
|
<Editor />
|
438
|
-
</
|
448
|
+
</MockResourceBrowserContext>,
|
439
449
|
);
|
440
450
|
|
441
451
|
await act(() => fireEvent.click(screen.getByRole('button', { name: 'Link (Ctrl+K)' })));
|
package/src/Editor/Editor.tsx
CHANGED
@@ -7,7 +7,6 @@ import { Toolbar, FloatingToolbar } from '../EditorToolbar';
|
|
7
7
|
import { EditorContext } from './EditorContext';
|
8
8
|
import { createExtensions } from '../Extensions/Extensions';
|
9
9
|
import useFocus from '../hooks/useFocus';
|
10
|
-
import { ResourceBrowserContext } from '@squiz/resource-browser';
|
11
10
|
import { TableComponents } from '@remirror/extension-react-tables';
|
12
11
|
|
13
12
|
type EditorProps = {
|
@@ -56,7 +55,7 @@ const Editor = ({
|
|
56
55
|
enableTableTool = false,
|
57
56
|
}: EditorProps) => {
|
58
57
|
const { manager, state, setState } = useRemirror({
|
59
|
-
extensions: createExtensions(useContext(EditorContext)
|
58
|
+
extensions: createExtensions(useContext(EditorContext)),
|
60
59
|
content,
|
61
60
|
selection: 'start',
|
62
61
|
stringHandler: 'html',
|
@@ -1,15 +1,20 @@
|
|
1
1
|
import React from 'react';
|
2
|
+
import { ResolveNodeToUrl } from '../types';
|
2
3
|
|
3
4
|
export type EditorContextOptions = {
|
4
5
|
matrix: {
|
5
6
|
matrixDomain: string;
|
6
7
|
};
|
8
|
+
resolveNodeToUrl: ResolveNodeToUrl;
|
7
9
|
};
|
8
10
|
|
9
11
|
export const defaultEditorContext: EditorContextOptions = {
|
10
12
|
matrix: {
|
11
13
|
matrixDomain: '',
|
12
14
|
},
|
15
|
+
resolveNodeToUrl: () => {
|
16
|
+
throw new Error('resolveNodeToUrl has not been configured.');
|
17
|
+
},
|
13
18
|
};
|
14
19
|
|
15
20
|
export const EditorContext = React.createContext(defaultEditorContext);
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import '@testing-library/jest-dom';
|
2
|
-
import { render, screen, act, fireEvent } from '@testing-library/react';
|
2
|
+
import { render, screen, act, fireEvent, waitFor } from '@testing-library/react';
|
3
3
|
import React from 'react';
|
4
4
|
import ImageForm from './ImageForm';
|
5
5
|
import { NodeName } from '../../../../Extensions/Extensions';
|
@@ -48,7 +48,7 @@ describe('Image Form', () => {
|
|
48
48
|
data={{
|
49
49
|
...data,
|
50
50
|
imageType: NodeName.AssetImage,
|
51
|
-
assetImage: { matrixAssetId: '100', matrixIdentifier: 'matrix-identifier' },
|
51
|
+
assetImage: { matrixAssetId: '100', matrixIdentifier: 'matrix-api-identifier' },
|
52
52
|
}}
|
53
53
|
onSubmit={handleSubmit}
|
54
54
|
/>
|
@@ -56,7 +56,9 @@ describe('Image Form', () => {
|
|
56
56
|
);
|
57
57
|
|
58
58
|
expect(document.querySelector('div[data-headlessui-state="selected"]')).toHaveTextContent('From source');
|
59
|
-
|
59
|
+
await waitFor(() => {
|
60
|
+
expect(screen.getByText('My selected image')).toBeInTheDocument();
|
61
|
+
});
|
60
62
|
});
|
61
63
|
|
62
64
|
it('calculates the height when width changes and aspect ratio is locked', () => {
|
@@ -269,6 +269,9 @@ describe('ImageButton', () => {
|
|
269
269
|
// open the modal and add an image.
|
270
270
|
await openModal();
|
271
271
|
fireEvent.click(screen.getByRole('button', { name: 'From source' }));
|
272
|
+
await waitFor(() => {
|
273
|
+
screen.getByRole('button', { name: 'Choose image' });
|
274
|
+
});
|
272
275
|
await selectResource(screen.getByRole('button', { name: 'Choose image' }), 'My image resource');
|
273
276
|
fireEvent.click(screen.getByRole('button', { name: 'Apply' }));
|
274
277
|
|
@@ -329,6 +332,9 @@ describe('ImageButton', () => {
|
|
329
332
|
|
330
333
|
await openModal();
|
331
334
|
fireEvent.click(screen.getByRole('button', { name: 'From source' }));
|
335
|
+
await waitFor(() => {
|
336
|
+
screen.getByRole('button', { name: 'Choose image' });
|
337
|
+
});
|
332
338
|
await selectResource(screen.getByRole('button', { name: 'Choose image' }), 'My image resource');
|
333
339
|
fireEvent.click(screen.getByRole('button', { name: 'Apply' }));
|
334
340
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import '@testing-library/jest-dom';
|
2
|
-
import { render, screen } from '@testing-library/react';
|
2
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
3
3
|
import React from 'react';
|
4
4
|
import { LinkForm } from './LinkForm';
|
5
5
|
import { LinkTarget } from '../../../../Extensions/LinkExtension/common';
|
@@ -19,20 +19,28 @@ describe('Link Form', () => {
|
|
19
19
|
},
|
20
20
|
assetLink: {
|
21
21
|
matrixAssetId: '100',
|
22
|
-
matrixIdentifier: 'matrix-identifier',
|
22
|
+
matrixIdentifier: 'matrix-api-identifier',
|
23
23
|
matrixDomain: 'my-matrix.squiz.net',
|
24
24
|
target: LinkTarget.Blank,
|
25
25
|
},
|
26
26
|
};
|
27
27
|
|
28
|
-
it('Renders the form with expected default values when no data is provided', () => {
|
29
|
-
|
28
|
+
it('Renders the form with expected default values when no data is provided', async () => {
|
29
|
+
const { MockResourceBrowserContext } = mockResourceBrowserContext({ sources: [], resources: [] });
|
30
|
+
render(
|
31
|
+
<MockResourceBrowserContext>
|
32
|
+
<LinkForm onSubmit={handleSubmit} />
|
33
|
+
</MockResourceBrowserContext>,
|
34
|
+
);
|
30
35
|
|
31
36
|
expect(document.querySelector('div[data-headlessui-state="selected"]')).toHaveTextContent('From source');
|
32
|
-
expect(screen.queryByRole('button', { name: 'Choose asset' })).toBeInTheDocument();
|
33
37
|
expect(screen.getByLabelText('Text')).toHaveValue('');
|
34
38
|
expect(document.querySelector('div.squiz-fte-checkbox')).toHaveTextContent('Open link in new window');
|
35
39
|
expect(document.querySelectorAll('label')).toHaveLength(1);
|
40
|
+
|
41
|
+
await waitFor(() => {
|
42
|
+
expect(screen.queryByRole('button', { name: 'Choose asset' })).toBeInTheDocument();
|
43
|
+
});
|
36
44
|
});
|
37
45
|
|
38
46
|
it('Renders the form with the expected fields for arbitrary links', () => {
|
@@ -46,7 +54,7 @@ describe('Link Form', () => {
|
|
46
54
|
expect(document.querySelectorAll('label')).toHaveLength(3);
|
47
55
|
});
|
48
56
|
|
49
|
-
it('Renders the form with the expected fields for asset links', () => {
|
57
|
+
it('Renders the form with the expected fields for asset links', async () => {
|
50
58
|
const { MockResourceBrowserContext } = mockResourceBrowserContext({
|
51
59
|
sources: [{ id: 'my-source-id' }],
|
52
60
|
resources: [{ id: '100', name: 'My selected resource' }],
|
@@ -59,9 +67,13 @@ describe('Link Form', () => {
|
|
59
67
|
);
|
60
68
|
|
61
69
|
expect(document.querySelector('div[data-headlessui-state="selected"]')).toHaveTextContent('From source');
|
62
|
-
|
70
|
+
|
63
71
|
expect(screen.getByLabelText('Text')).toHaveValue('Link text');
|
64
72
|
expect(document.querySelector('div.squiz-fte-checkbox')).toHaveTextContent('Open link in new window');
|
65
73
|
expect(document.querySelectorAll('label')).toHaveLength(1);
|
74
|
+
|
75
|
+
await waitFor(() => {
|
76
|
+
expect(screen.getByText('My selected resource')).toBeInTheDocument();
|
77
|
+
});
|
66
78
|
});
|
67
79
|
});
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import '@testing-library/jest-dom';
|
2
|
-
import { act, screen, fireEvent, waitForElementToBeRemoved } from '@testing-library/react';
|
2
|
+
import { act, screen, fireEvent, waitForElementToBeRemoved, waitFor } from '@testing-library/react';
|
3
3
|
import React from 'react';
|
4
4
|
import { renderWithEditor, mockResourceBrowserContext } from '../../../../tests';
|
5
5
|
import LinkButton from './LinkButton';
|
@@ -258,6 +258,9 @@ describe('LinkButton', () => {
|
|
258
258
|
|
259
259
|
await openModal();
|
260
260
|
fireEvent.click(screen.getByRole('button', { name: 'From source' }));
|
261
|
+
await waitFor(() => {
|
262
|
+
expect(screen.getByRole('button', { name: 'Choose asset' })).toBeInTheDocument();
|
263
|
+
});
|
261
264
|
await selectResource(screen.getByRole('button', { name: 'Choose asset' }), 'My resource');
|
262
265
|
fireEvent.change(screen.getByLabelText('Text'), { target: { value: 'Link text' } });
|
263
266
|
fireEvent.click(screen.getByRole('button', { name: 'Apply' }));
|
@@ -318,6 +321,9 @@ describe('LinkButton', () => {
|
|
318
321
|
|
319
322
|
await openModal();
|
320
323
|
fireEvent.click(screen.getByRole('button', { name: 'From source' }));
|
324
|
+
await waitFor(() => {
|
325
|
+
expect(screen.getByRole('button', { name: 'Choose asset' })).toBeInTheDocument();
|
326
|
+
});
|
321
327
|
await selectResource(screen.getByRole('button', { name: 'Choose asset' }), 'My resource');
|
322
328
|
fireEvent.click(document.querySelector('div.squiz-fte-checkbox button') as HTMLButtonElement);
|
323
329
|
fireEvent.click(screen.getByRole('button', { name: 'Apply' }));
|
@@ -20,7 +20,7 @@ describe('RemoveLinkButton', () => {
|
|
20
20
|
marks: [
|
21
21
|
{
|
22
22
|
type: 'assetLink',
|
23
|
-
attrs: { matrixAssetId: '123', matrixIdentifier: 'matrix-identifier', target: '_blank' },
|
23
|
+
attrs: { matrixAssetId: '123', matrixIdentifier: 'matrix-api-identifier', target: '_blank' },
|
24
24
|
},
|
25
25
|
],
|
26
26
|
},
|
@@ -65,7 +65,7 @@ describe('RemoveLinkButton', () => {
|
|
65
65
|
marks: [
|
66
66
|
{
|
67
67
|
type: 'assetLink',
|
68
|
-
attrs: { matrixAssetId: '123', matrixIdentifier: 'matrix-identifier', target: '_blank' },
|
68
|
+
attrs: { matrixAssetId: '123', matrixIdentifier: 'matrix-api-identifier', target: '_blank' },
|
69
69
|
},
|
70
70
|
],
|
71
71
|
},
|
@@ -123,7 +123,7 @@ describe('RemoveLinkButton', () => {
|
|
123
123
|
marks: [
|
124
124
|
{
|
125
125
|
type: 'assetLink',
|
126
|
-
attrs: { matrixAssetId: '123', matrixIdentifier: 'matrix-identifier', target: '_blank' },
|
126
|
+
attrs: { matrixAssetId: '123', matrixIdentifier: 'matrix-api-identifier', target: '_blank' },
|
127
127
|
},
|
128
128
|
],
|
129
129
|
},
|
@@ -26,7 +26,6 @@ import { AssetImageExtension } from './ImageExtension/AssetImageExtension';
|
|
26
26
|
import { ExtendedCodeBlockExtension } from './CodeBlockExtension/CodeBlockExtension';
|
27
27
|
import { ClearFormattingExtension } from './ClearFormattingExtension/ClearFormattingExtension';
|
28
28
|
import { UnsupportedNodeExtension } from './UnsuportedExtension/UnsupportedNodeExtension';
|
29
|
-
import { ResourceBrowserContextProps } from '@squiz/resource-browser';
|
30
29
|
import { FetchUrlExtension } from './FetchUrlExtension/FetchUrlExtension';
|
31
30
|
import { TableExtension } from '@remirror/extension-react-tables';
|
32
31
|
import { ReactComponentExtension } from '@remirror/extension-react-component';
|
@@ -47,7 +46,7 @@ export enum MarkName {
|
|
47
46
|
AssetLink = 'assetLink',
|
48
47
|
}
|
49
48
|
|
50
|
-
export const createExtensions = (context: EditorContextOptions
|
49
|
+
export const createExtensions = (context: EditorContextOptions) => {
|
51
50
|
return (): Extension[] => {
|
52
51
|
return [
|
53
52
|
new CommandsExtension(),
|
@@ -79,7 +78,7 @@ export const createExtensions = (context: EditorContextOptions, browserContext:
|
|
79
78
|
new HorizontalRuleExtension(),
|
80
79
|
new PlaceholderExtension(),
|
81
80
|
new FetchUrlExtension({
|
82
|
-
fetchUrl:
|
81
|
+
fetchUrl: context.resolveNodeToUrl,
|
83
82
|
}),
|
84
83
|
new TextExtension(),
|
85
84
|
new TableExtension(),
|
@@ -1,15 +1,14 @@
|
|
1
1
|
import { extension, PlainExtension, Mark } from '@remirror/core';
|
2
2
|
import { Dispose, EditorView } from '@remirror/core-types';
|
3
|
-
import { Resource } from '@squiz/resource-browser';
|
4
3
|
import { MarkName, NodeName } from '../Extensions';
|
4
|
+
import { ResolveNodeToUrl, ResolveNodeType } from '../../types';
|
5
5
|
|
6
6
|
export type FetchUrlOptions = {
|
7
|
-
fetchUrl?:
|
7
|
+
fetchUrl?: ResolveNodeToUrl;
|
8
8
|
};
|
9
|
-
|
10
9
|
@extension<FetchUrlOptions>({
|
11
10
|
defaultOptions: {
|
12
|
-
fetchUrl: () => Promise.resolve(),
|
11
|
+
fetchUrl: () => Promise.resolve(''),
|
13
12
|
},
|
14
13
|
})
|
15
14
|
export class FetchUrlExtension extends PlainExtension<FetchUrlOptions> {
|
@@ -27,7 +26,7 @@ export class FetchUrlExtension extends PlainExtension<FetchUrlOptions> {
|
|
27
26
|
state.doc.descendants((node, pos) => {
|
28
27
|
if (node.type.name === NodeName.AssetImage && node.attrs.src === '') {
|
29
28
|
promises.push(
|
30
|
-
this.fetchAndReplace(node.attrs
|
29
|
+
this.fetchAndReplace(node.attrs, (url: string) => {
|
31
30
|
const newNode = state.schema.nodes[NodeName.AssetImage].create({ ...node.attrs, src: url });
|
32
31
|
tr.replaceWith(pos, pos + node.nodeSize, newNode);
|
33
32
|
}),
|
@@ -37,14 +36,10 @@ export class FetchUrlExtension extends PlainExtension<FetchUrlOptions> {
|
|
37
36
|
const assetLinkMark = this.findAssetLinkMark(node.marks as Mark[]);
|
38
37
|
if (node.type.name === 'text' && assetLinkMark) {
|
39
38
|
promises.push(
|
40
|
-
this.fetchAndReplace(
|
41
|
-
assetLinkMark.attrs
|
42
|
-
|
43
|
-
|
44
|
-
const updatedMark = assetLinkMark.type.create({ ...assetLinkMark.attrs, href: url });
|
45
|
-
tr.addMark(pos, pos + node.nodeSize, updatedMark);
|
46
|
-
},
|
47
|
-
),
|
39
|
+
this.fetchAndReplace(assetLinkMark.attrs, (url: string) => {
|
40
|
+
const updatedMark = assetLinkMark.type.create({ ...assetLinkMark.attrs, href: url });
|
41
|
+
tr.addMark(pos, pos + node.nodeSize, updatedMark);
|
42
|
+
}),
|
48
43
|
);
|
49
44
|
}
|
50
45
|
});
|
@@ -60,11 +55,11 @@ export class FetchUrlExtension extends PlainExtension<FetchUrlOptions> {
|
|
60
55
|
return marks.find((mark) => mark.type.name === MarkName.AssetLink && mark.attrs.href === '');
|
61
56
|
}
|
62
57
|
|
63
|
-
private fetchAndReplace(
|
58
|
+
private fetchAndReplace(nodeAttrs: ResolveNodeType, onFetched: (url: string) => void): Promise<void> {
|
64
59
|
return this.options
|
65
|
-
.fetchUrl(
|
66
|
-
.then((
|
67
|
-
onFetched(
|
60
|
+
.fetchUrl(nodeAttrs)
|
61
|
+
.then((url: string) => {
|
62
|
+
onFetched(url);
|
68
63
|
})
|
69
64
|
.catch((error) => {
|
70
65
|
console.error('Error fetching URL:', error);
|
package/src/index.ts
CHANGED
@@ -3,5 +3,7 @@ import { EditorContext } from './Editor/EditorContext';
|
|
3
3
|
import { remirrorNodeToSquizNode } from './utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode';
|
4
4
|
import { squizNodeToRemirrorNode } from './utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode';
|
5
5
|
import { htmlToSquizNode } from './utils/converters/htmlToSquizNode/htmlToSquizNode';
|
6
|
+
import { ResolveNodeType, ResolveNodeToUrl } from './types';
|
6
7
|
|
7
8
|
export { Editor, EditorContext, remirrorNodeToSquizNode, squizNodeToRemirrorNode, htmlToSquizNode };
|
9
|
+
export type { ResolveNodeType, ResolveNodeToUrl };
|
package/src/types.ts
CHANGED
@@ -1,17 +1,27 @@
|
|
1
1
|
import '@testing-library/jest-dom';
|
2
2
|
import React from 'react';
|
3
|
-
import { fireEvent, render, screen } from '@testing-library/react';
|
3
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
4
4
|
import { MatrixAsset } from './MatrixAsset';
|
5
5
|
import { mockResourceBrowserContext } from '../../../../tests';
|
6
6
|
|
7
7
|
describe('MatrixAsset', () => {
|
8
|
-
it('Renders empty state when no value is provided', () => {
|
9
|
-
|
8
|
+
it('Renders empty state when no value is provided', async () => {
|
9
|
+
const { MockResourceBrowserContext } = mockResourceBrowserContext({
|
10
|
+
sources: [],
|
11
|
+
resources: [],
|
12
|
+
});
|
13
|
+
render(
|
14
|
+
<MockResourceBrowserContext>
|
15
|
+
<MatrixAsset modalTitle="Insert asset" onChange={jest.fn()} />
|
16
|
+
</MockResourceBrowserContext>,
|
17
|
+
);
|
10
18
|
|
11
|
-
|
19
|
+
await waitFor(() => {
|
20
|
+
expect(screen.getByRole('button', { name: 'Choose asset' })).toBeInTheDocument();
|
21
|
+
});
|
12
22
|
});
|
13
23
|
|
14
|
-
it('Renders a selected state when a value is provided', () => {
|
24
|
+
it('Renders a selected state when a value is provided', async () => {
|
15
25
|
const { MockResourceBrowserContext } = mockResourceBrowserContext({
|
16
26
|
sources: [{ id: 'my-source-id' }],
|
17
27
|
resources: [{ id: 'my-resource-id', name: 'My resource' }],
|
@@ -22,7 +32,7 @@ describe('MatrixAsset', () => {
|
|
22
32
|
<MatrixAsset
|
23
33
|
modalTitle="Insert asset"
|
24
34
|
value={{
|
25
|
-
matrixIdentifier: '
|
35
|
+
matrixIdentifier: 'matrix-api-identifier',
|
26
36
|
matrixAssetId: 'my-resource-id',
|
27
37
|
addional: 'addditional data',
|
28
38
|
}}
|
@@ -30,8 +40,9 @@ describe('MatrixAsset', () => {
|
|
30
40
|
/>
|
31
41
|
</MockResourceBrowserContext>,
|
32
42
|
);
|
33
|
-
|
34
|
-
|
43
|
+
await waitFor(() => {
|
44
|
+
expect(screen.getByText('My resource')).toBeInTheDocument();
|
45
|
+
});
|
35
46
|
});
|
36
47
|
|
37
48
|
it('Calls onChange with expected value when resources is selected', async () => {
|
@@ -51,6 +62,9 @@ describe('MatrixAsset', () => {
|
|
51
62
|
</MockResourceBrowserContext>,
|
52
63
|
);
|
53
64
|
|
65
|
+
await waitFor(() => {
|
66
|
+
expect(screen.getByRole('button', { name: 'Choose asset' })).toBeInTheDocument();
|
67
|
+
});
|
54
68
|
await selectResource(screen.getByRole('button', { name: 'Choose asset' }), 'My resource');
|
55
69
|
|
56
70
|
expect(handleChange).toHaveBeenCalledWith({
|
@@ -58,7 +72,7 @@ describe('MatrixAsset', () => {
|
|
58
72
|
value: {
|
59
73
|
additional: 'additional data',
|
60
74
|
matrixAssetId: 'my-resource-id',
|
61
|
-
matrixIdentifier: '
|
75
|
+
matrixIdentifier: 'matrix-api-identifier',
|
62
76
|
url: 'https://default-resource/',
|
63
77
|
},
|
64
78
|
},
|
@@ -89,6 +103,9 @@ describe('MatrixAsset', () => {
|
|
89
103
|
</MockResourceBrowserContext>,
|
90
104
|
);
|
91
105
|
|
106
|
+
await waitFor(() => {
|
107
|
+
expect(screen.getByRole('button', { name: 'Remove selection' })).toBeInTheDocument();
|
108
|
+
});
|
92
109
|
fireEvent.click(screen.getByRole('button', { name: 'Remove selection' }));
|
93
110
|
|
94
111
|
expect(handleChange).toHaveBeenCalledWith({
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import {
|
2
|
+
import { ResourceBrowserResource, ResourceBrowser } from '@squiz/resource-browser';
|
3
3
|
import { InputContainer, InputContainerProps } from '../InputContainer/InputContainer';
|
4
4
|
|
5
5
|
type MatrixAssetValue = {
|
@@ -28,25 +28,25 @@ export const MatrixAsset = <T extends MatrixAssetValue>({
|
|
28
28
|
}: MatrixAssetProps<T>) => {
|
29
29
|
return (
|
30
30
|
<InputContainer {...props}>
|
31
|
-
<
|
31
|
+
<ResourceBrowser
|
32
32
|
modalTitle={modalTitle}
|
33
33
|
allowedTypes={allowedTypes}
|
34
34
|
value={
|
35
35
|
value && value.matrixIdentifier && value.matrixAssetId
|
36
36
|
? {
|
37
|
-
|
38
|
-
|
37
|
+
sourceId: value.matrixIdentifier,
|
38
|
+
resourceId: value.matrixAssetId,
|
39
39
|
}
|
40
40
|
: null
|
41
41
|
}
|
42
|
-
onChange={(
|
42
|
+
onChange={(resource: ResourceBrowserResource | null) => {
|
43
43
|
onChange({
|
44
44
|
target: {
|
45
45
|
value: {
|
46
46
|
...value,
|
47
|
-
matrixIdentifier:
|
48
|
-
matrixAssetId:
|
49
|
-
url:
|
47
|
+
matrixIdentifier: resource?.source?.id,
|
48
|
+
matrixAssetId: resource?.id,
|
49
|
+
url: resource?.url,
|
50
50
|
} as T,
|
51
51
|
},
|
52
52
|
});
|
@@ -8,24 +8,14 @@ import { HtmlToSquizNodeProps } from './htmlToSquizNode.props';
|
|
8
8
|
type FormattedText = FORMATTED_TEXT_MODELS.v1.FormattedText;
|
9
9
|
|
10
10
|
export const htmlToSquizNode = ({ content }: HtmlToSquizNodeProps): FormattedText | undefined => {
|
11
|
-
const extensions = createExtensions(
|
12
|
-
{
|
13
|
-
|
14
|
-
matrixDomain: 'unsupported',
|
15
|
-
},
|
11
|
+
const extensions = createExtensions({
|
12
|
+
matrix: {
|
13
|
+
matrixDomain: 'unsupported',
|
16
14
|
},
|
17
|
-
{
|
18
|
-
|
19
|
-
throw new Error('Resolving Matrix assets is not supported.');
|
20
|
-
},
|
21
|
-
onRequestSources: () => {
|
22
|
-
throw new Error('Resolving Matrix assets is not supported.');
|
23
|
-
},
|
24
|
-
onRequestChildren: () => {
|
25
|
-
throw new Error('Resolving Matrix assets is not supported.');
|
26
|
-
},
|
15
|
+
resolveNodeToUrl: () => {
|
16
|
+
throw new Error('Resolving Url is not supported.');
|
27
17
|
},
|
28
|
-
);
|
18
|
+
});
|
29
19
|
const manager = RemirrorManager.create(extensions);
|
30
20
|
|
31
21
|
return remirrorNodeToSquizNode(
|
@@ -1,11 +1,23 @@
|
|
1
1
|
import React, { ReactNode } from 'react';
|
2
|
-
import {
|
2
|
+
import {
|
3
|
+
ResourceBrowserSource,
|
4
|
+
ResourceBrowserContextProvider,
|
5
|
+
ResourceBrowserPluginType,
|
6
|
+
} from '@squiz/resource-browser';
|
7
|
+
import MatrixResourceBrowserPlugin, {
|
8
|
+
Source,
|
9
|
+
Resource,
|
10
|
+
ResourceReference,
|
11
|
+
MatrixResourceBrowserPluginProps,
|
12
|
+
} from '@squiz/matrix-resource-browser-plugin';
|
13
|
+
|
3
14
|
import { fireEvent, screen } from '@testing-library/react';
|
4
15
|
import { DeepPartial } from '../src/types';
|
5
16
|
|
6
17
|
export type MockResourceBrowserContextOptions = DeepPartial<{
|
7
18
|
sources: Source[];
|
8
19
|
resources: Resource[];
|
20
|
+
pluginProps?: MatrixResourceBrowserPluginProps;
|
9
21
|
}>;
|
10
22
|
|
11
23
|
export const mockResource = (resource: DeepPartial<Resource> = {}): Resource =>
|
@@ -23,6 +35,10 @@ export const mockResource = (resource: DeepPartial<Resource> = {}): Resource =>
|
|
23
35
|
code: 'live',
|
24
36
|
name: 'Live',
|
25
37
|
},
|
38
|
+
source: {
|
39
|
+
id: '1',
|
40
|
+
type: 'matrix' as ResourceBrowserPluginType,
|
41
|
+
},
|
26
42
|
...resource,
|
27
43
|
} as Resource);
|
28
44
|
|
@@ -33,29 +49,49 @@ export const mockSource = (source: DeepPartial<Source> = {}): Source => ({
|
|
33
49
|
nodes: (source.nodes || [mockResource()]).map((resource) => mockResource(resource)),
|
34
50
|
});
|
35
51
|
|
36
|
-
export const mockResourceBrowserContext = ({ sources, resources }: MockResourceBrowserContextOptions) => {
|
52
|
+
export const mockResourceBrowserContext = ({ sources, resources, pluginProps }: MockResourceBrowserContextOptions) => {
|
37
53
|
sources = (sources || []).map((source) => mockSource(source));
|
38
54
|
resources = (resources || []).map((resource) => mockResource(resource));
|
39
55
|
|
40
|
-
const onRequestSources = jest.fn().mockResolvedValue(sources);
|
41
|
-
const onRequestChildren = jest.fn().mockResolvedValue(resources);
|
42
|
-
const onRequestResource =
|
43
|
-
|
44
|
-
|
45
|
-
(
|
46
|
-
|
56
|
+
const onRequestSources = pluginProps?.onRequestSources || jest.fn().mockResolvedValue(sources);
|
57
|
+
const onRequestChildren = pluginProps?.onRequestChildren || jest.fn().mockResolvedValue(resources);
|
58
|
+
const onRequestResource =
|
59
|
+
pluginProps?.onRequestResource ||
|
60
|
+
jest
|
61
|
+
.fn()
|
62
|
+
.mockImplementation((reference: ResourceReference) =>
|
63
|
+
Promise.resolve(resources?.find((resource) => resource.id === reference.resource) || null),
|
64
|
+
);
|
47
65
|
|
48
66
|
return {
|
49
67
|
MockResourceBrowserContext: ({ children }: { children: ReactNode }) => (
|
50
|
-
<
|
68
|
+
<ResourceBrowserContextProvider
|
69
|
+
value={{
|
70
|
+
onRequestSources: (): Promise<ResourceBrowserSource[]> =>
|
71
|
+
Promise.resolve([
|
72
|
+
{
|
73
|
+
name: 'Matrix API',
|
74
|
+
id: 'matrix-api-identifier',
|
75
|
+
type: 'matrix',
|
76
|
+
},
|
77
|
+
]),
|
78
|
+
plugins: [
|
79
|
+
MatrixResourceBrowserPlugin({
|
80
|
+
onRequestSources: onRequestSources,
|
81
|
+
onRequestChildren: onRequestChildren,
|
82
|
+
onRequestResource: onRequestResource,
|
83
|
+
} as MatrixResourceBrowserPluginProps),
|
84
|
+
],
|
85
|
+
}}
|
86
|
+
>
|
51
87
|
{children}
|
52
|
-
</
|
88
|
+
</ResourceBrowserContextProvider>
|
53
89
|
),
|
54
90
|
selectResource: async (opener: HTMLElement, resourceName: string) => {
|
55
91
|
const sourceLabel = `Drill down to ${sources?.[0]?.nodes?.[0]?.name} children`;
|
56
92
|
|
57
93
|
fireEvent.click(opener);
|
58
|
-
fireEvent.click(await screen.
|
94
|
+
fireEvent.click((await screen.findAllByRole('button', { name: sourceLabel }))[0]);
|
59
95
|
fireEvent.click(await screen.findByTitle(resourceName));
|
60
96
|
fireEvent.click(await screen.findByRole('button', { name: 'Select' }));
|
61
97
|
},
|
@@ -4,7 +4,12 @@ import merge from 'deepmerge';
|
|
4
4
|
import { EditorContext } from '../src';
|
5
5
|
import { defaultEditorContext, EditorContextOptions } from '../src/Editor/EditorContext';
|
6
6
|
import { DeepPartial } from '../src/types';
|
7
|
-
import {
|
7
|
+
import {
|
8
|
+
ResourceBrowserSource,
|
9
|
+
ResourceBrowserContextProvider,
|
10
|
+
ResourceBrowserPluginType,
|
11
|
+
} from '@squiz/resource-browser';
|
12
|
+
import MatrixResourceBrowserPlugin from '@squiz/matrix-resource-browser-plugin';
|
8
13
|
import { mockSource } from './mockResourceBrowserContext';
|
9
14
|
|
10
15
|
export type ContextRenderOptions = RenderOptions & {
|
@@ -31,17 +36,40 @@ export const renderWithContext = (ui: ReactElement | null, options?: ContextRend
|
|
31
36
|
code: 'live',
|
32
37
|
name: 'Live',
|
33
38
|
},
|
39
|
+
source: {
|
40
|
+
id: '1',
|
41
|
+
type: 'matrix' as ResourceBrowserPluginType,
|
42
|
+
},
|
34
43
|
},
|
35
44
|
];
|
36
45
|
const onRequestSources = jest.fn().mockResolvedValue(sources);
|
37
46
|
const onRequestChildren = jest.fn().mockResolvedValue(resources);
|
38
47
|
const onRequestResource = jest.fn(() => Promise.resolve(resources[0]));
|
48
|
+
editorContext.resolveNodeToUrl = jest.fn(() => Promise.resolve(resources[0].url));
|
39
49
|
|
40
50
|
return render(
|
41
51
|
<EditorContext.Provider value={editorContext}>
|
42
|
-
<
|
52
|
+
<ResourceBrowserContextProvider
|
53
|
+
value={{
|
54
|
+
onRequestSources: (): Promise<ResourceBrowserSource[]> =>
|
55
|
+
Promise.resolve([
|
56
|
+
{
|
57
|
+
name: 'Matrix',
|
58
|
+
id: 'matrixIdentifier',
|
59
|
+
type: 'matrix',
|
60
|
+
},
|
61
|
+
]),
|
62
|
+
plugins: [
|
63
|
+
MatrixResourceBrowserPlugin({
|
64
|
+
onRequestSources: onRequestSources,
|
65
|
+
onRequestChildren: onRequestChildren,
|
66
|
+
onRequestResource: onRequestResource,
|
67
|
+
}),
|
68
|
+
],
|
69
|
+
}}
|
70
|
+
>
|
43
71
|
{ui}
|
44
|
-
</
|
72
|
+
</ResourceBrowserContextProvider>
|
45
73
|
</EditorContext.Provider>,
|
46
74
|
);
|
47
75
|
};
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import React, { ReactElement, useContext, useEffect } from 'react';
|
2
2
|
import { Extension, RemirrorContentType, RemirrorManager } from '@remirror/core';
|
3
3
|
import { CorePreset } from '@remirror/preset-core';
|
4
|
-
import { ResourceBrowserContext } from '@squiz/resource-browser';
|
5
4
|
import { BuiltinPreset } from 'remirror';
|
6
5
|
import { EditorComponent, Remirror, useRemirror } from '@remirror/react';
|
7
6
|
import { RemirrorTestChain } from 'jest-remirror';
|
@@ -34,9 +33,8 @@ export type EditorRenderResult = {
|
|
34
33
|
|
35
34
|
const TestEditor = ({ children, extensions, content, onReady, editable }: TestEditorProps) => {
|
36
35
|
const context = useContext(EditorContext);
|
37
|
-
const browserContext = useContext(ResourceBrowserContext);
|
38
36
|
const { manager, state, setState } = useRemirror({
|
39
|
-
extensions: () => extensions || createExtensions(context
|
37
|
+
extensions: () => extensions || createExtensions(context)(),
|
40
38
|
content: content,
|
41
39
|
selection: 'start',
|
42
40
|
stringHandler: 'html',
|
package/vite.config.ts
CHANGED
@@ -7,12 +7,12 @@ import react from '@vitejs/plugin-react';
|
|
7
7
|
export default defineConfig({
|
8
8
|
root: 'demo',
|
9
9
|
optimizeDeps: {
|
10
|
-
include: [
|
10
|
+
include: [],
|
11
11
|
},
|
12
12
|
build: {
|
13
13
|
outDir: 'build/demo',
|
14
14
|
commonjsOptions: {
|
15
|
-
include: [/node_modules
|
15
|
+
include: [/node_modules/],
|
16
16
|
},
|
17
17
|
},
|
18
18
|
plugins: [
|