@squiz/formatted-text-editor 1.71.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/demo/App.tsx +7 -1
  3. package/demo/AppContext.tsx +49 -18
  4. package/demo/resources.json +44 -0
  5. package/demo/sources.json +4 -0
  6. package/lib/Editor/Editor.js +1 -2
  7. package/lib/Editor/EditorContext.d.ts +2 -0
  8. package/lib/Editor/EditorContext.js +3 -0
  9. package/lib/Extensions/Extensions.d.ts +1 -2
  10. package/lib/Extensions/Extensions.js +2 -2
  11. package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.d.ts +2 -4
  12. package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.js +7 -7
  13. package/lib/index.d.ts +2 -0
  14. package/lib/types.d.ts +4 -0
  15. package/lib/ui/Fields/MatrixAsset/MatrixAsset.js +7 -7
  16. package/lib/utils/converters/htmlToSquizNode/htmlToSquizNode.js +2 -9
  17. package/package.json +11 -10
  18. package/src/Editor/Editor.spec.tsx +14 -4
  19. package/src/Editor/Editor.tsx +1 -2
  20. package/src/Editor/EditorContext.spec.tsx +1 -0
  21. package/src/Editor/EditorContext.ts +5 -0
  22. package/src/EditorToolbar/Tools/Image/Form/ImageForm.spec.tsx +5 -3
  23. package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +6 -0
  24. package/src/EditorToolbar/Tools/Link/Form/LinkForm.spec.tsx +19 -7
  25. package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +7 -1
  26. package/src/EditorToolbar/Tools/Link/RemoveLinkButton.spec.tsx +3 -3
  27. package/src/Extensions/Extensions.ts +2 -3
  28. package/src/Extensions/FetchUrlExtension/FetchUrlExtension.ts +12 -17
  29. package/src/Extensions/UnsuportedExtension/UnsupportedNodeExtension.spec.ts +1 -1
  30. package/src/index.ts +2 -0
  31. package/src/types.ts +3 -0
  32. package/src/ui/Fields/MatrixAsset/MatrixAsset.spec.tsx +26 -9
  33. package/src/ui/Fields/MatrixAsset/MatrixAsset.tsx +8 -8
  34. package/src/utils/converters/htmlToSquizNode/htmlToSquizNode.ts +6 -16
  35. package/tests/mockResourceBrowserContext.tsx +48 -12
  36. package/tests/renderWithContext.tsx +31 -3
  37. package/tests/renderWithEditor.tsx +1 -3
  38. package/vite.config.ts +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Change Log
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 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.
8
+
9
+ 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.
10
+
11
+ This change was made to expand the support the resource browser had for data sources other than Matrix.
12
+
13
+ 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.
14
+
3
15
  ## 1.71.0
4
16
 
5
17
  ### 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={`<p>Hello <a href="https://www.google.com"><strong>Mr Bean</strong></a>, nice to <a href="https://www.google.com">meet you</a>.<img src="https://media2.giphy.com/media/3o6ozsIxg5legYvggo/giphy.gif" height="150" width="200"/></p>`}
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
  >
@@ -1,29 +1,48 @@
1
1
  import React, { PropsWithChildren } from 'react';
2
- import { ResourceBrowserContext, Source, Resource, ResourceReference } from '@squiz/resource-browser';
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
- <ResourceBrowserContext.Provider
15
+ <ResourceBrowserContextProvider
11
16
  value={{
12
- onRequestSources: (): Promise<Source> => Promise.resolve(sources),
13
- onRequestChildren: (source: Source, resource: Resource | null): Promise<Resource[]> =>
14
- Promise.resolve(resource._children || resources),
15
- onRequestResource: (reference: ResourceReference): Promise<Resource | null> => {
16
- const flattenResources = (resources: unknown[]) => {
17
- return [
18
- ...resources,
19
- ...resources.flatMap((resource) => ('_children' in resource ? flattenResources(resource._children) : [])),
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
- return Promise.resolve(
24
- flattenResources(resources).find((resource) => resource.id === reference.resource) || null,
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
- </ResourceBrowserContext.Provider>
69
+ </ResourceBrowserContextProvider>
39
70
  );
@@ -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
@@ -16,6 +16,10 @@
16
16
  "status": {
17
17
  "code": "live",
18
18
  "name": "Live"
19
+ },
20
+ "source": {
21
+ "id": "1",
22
+ "type": "matrix"
19
23
  }
20
24
  }
21
25
  ]
@@ -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), (0, react_1.useContext)(resource_browser_1.ResourceBrowserContext)),
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, browserContext: ResourceBrowserContextProps) => () => Extension[];
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, browserContext) => {
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: browserContext.onRequestResource,
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?: (params: {
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.matrixAssetId, node.attrs.matrixIdentifier, (url) => {
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.matrixAssetId, assetLinkMark.attrs.matrixIdentifier, (url) => {
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(resource, source, onFetched) {
45
+ fetchAndReplace(nodeAttrs, onFetched) {
46
46
  return this.options
47
- .fetchUrl({ resource, source })
48
- .then((asset) => {
49
- onFetched(asset.url);
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.ResourceBrowserInput, { modalTitle: modalTitle, allowedTypes: allowedTypes, value: value && value.matrixIdentifier && value.matrixAssetId
12
+ react_1.default.createElement(resource_browser_1.ResourceBrowser, { modalTitle: modalTitle, allowedTypes: allowedTypes, value: value && value.matrixIdentifier && value.matrixAssetId
13
13
  ? {
14
- source: value.matrixIdentifier,
15
- resource: value.matrixAssetId,
14
+ sourceId: value.matrixIdentifier,
15
+ resourceId: value.matrixAssetId,
16
16
  }
17
- : null, onChange: (reference) => {
17
+ : null, onChange: (resource) => {
18
18
  onChange({
19
19
  target: {
20
20
  value: {
21
21
  ...value,
22
- matrixIdentifier: reference?.source?.id,
23
- matrixAssetId: reference?.resource?.id,
24
- url: reference?.resource?.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
- onRequestResource: () => {
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.71.0",
3
+ "version": "2.0.0",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "private": false,
@@ -21,15 +21,16 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@headlessui/react": "1.7.11",
24
- "@mui/icons-material": "5.11.16",
24
+ "@mui/icons-material": "5.15.18",
25
25
  "@remirror/extension-react-tables": "^2.2.19",
26
- "@remirror/react": "2.0.25",
26
+ "@remirror/react": "2.0.35",
27
27
  "@squiz/dx-json-schema-lib": "^1.65.1",
28
- "@squiz/resource-browser": "^1.66.3",
29
- "clsx": "1.2.1",
30
- "react-hook-form": "7.43.2",
28
+ "@squiz/matrix-resource-browser-plugin": "^2.0.0",
29
+ "@squiz/resource-browser": "^2.0.0",
30
+ "clsx": "2.1.1",
31
+ "react-hook-form": "7.51.4",
31
32
  "react-image-size": "2.0.0",
32
- "remirror": "2.0.26"
33
+ "remirror": "2.0.39"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@testing-library/cypress": "9.0.0",
@@ -48,18 +49,18 @@
48
49
  "eslint-plugin-react": "7.32.2",
49
50
  "jest": "29.4.1",
50
51
  "jest-environment-jsdom": "29.4.1",
51
- "jest-remirror": "2.1.3",
52
+ "jest-remirror": "2.1.5",
52
53
  "postcss": "8.4.31",
53
54
  "postcss-nested": "6.0.0",
54
55
  "postcss-prefix-selector": "1.16.0",
55
56
  "react": "18.2.0",
56
57
  "react-diff-viewer-continued": "3.2.6",
57
58
  "react-dom": "18.2.0",
58
- "rimraf": "4.1.2",
59
+ "rimraf": "5.0.7",
59
60
  "tailwindcss": "3.2.6",
60
61
  "ts-jest": "29.0.5",
61
62
  "typescript": "4.9.3",
62
- "vite": "^4.5.0"
63
+ "vite": "^4.5.3"
63
64
  },
64
65
  "peerDependencies": {
65
66
  "@types/react": "^16.14.0 || ^17 || ^18",
@@ -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 { ResourceBrowserContext } from '@squiz/resource-browser';
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
- <ResourceBrowserContext.Provider value={{ onRequestSources, onRequestChildren, onRequestResource }}>
446
+ <MockResourceBrowserContext>
437
447
  <Editor />
438
- </ResourceBrowserContext.Provider>,
448
+ </MockResourceBrowserContext>,
439
449
  );
440
450
 
441
451
  await act(() => fireEvent.click(screen.getByRole('button', { name: 'Link (Ctrl+K)' })));
@@ -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), useContext(ResourceBrowserContext)),
58
+ extensions: createExtensions(useContext(EditorContext)),
60
59
  content,
61
60
  selection: 'start',
62
61
  stringHandler: 'html',
@@ -19,6 +19,7 @@ describe('EditorContext', () => {
19
19
  matrix: {
20
20
  matrixDomain: '',
21
21
  },
22
+ resolveNodeToUrl: expect.any(Function),
22
23
  });
23
24
  });
24
25
  });
@@ -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
- expect(screen.getByText('My selected image')).toBeInTheDocument();
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
- render(<LinkForm onSubmit={handleSubmit} />);
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
- expect(screen.getByText('My selected resource')).toBeInTheDocument();
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, browserContext: ResourceBrowserContextProps) => {
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: browserContext.onRequestResource,
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?: (params: { resource: string; source: string }) => Promise<any>;
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.matrixAssetId, node.attrs.matrixIdentifier, (url: string) => {
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.matrixAssetId,
42
- assetLinkMark.attrs.matrixIdentifier,
43
- (url: string) => {
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(resource: string, source: string, onFetched: (url: string) => void): Promise<void> {
58
+ private fetchAndReplace(nodeAttrs: ResolveNodeType, onFetched: (url: string) => void): Promise<void> {
64
59
  return this.options
65
- .fetchUrl({ resource, source })
66
- .then((asset: Resource) => {
67
- onFetched(asset.url);
60
+ .fetchUrl(nodeAttrs)
61
+ .then((url: string) => {
62
+ onFetched(url);
68
63
  })
69
64
  .catch((error) => {
70
65
  console.error('Error fetching URL:', error);
@@ -131,7 +131,7 @@ describe('UnsupportedNodeExtension', () => {
131
131
  });
132
132
 
133
133
  expect(getHtmlContent()).toBe(
134
- '<span class="unsupported-node-node-view-wrapper" originalnode="null" errormessage="null" data-unsupported-node="{&quot;originalNode&quot;:null,&quot;errorMessage&quot;:null}"><div class="collapse-box" contenteditable="false"><button class="collapse-box__header" type="button"><svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium collapse-box__icon--warning css-i4bv87-MuiSvgIcon-root" focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="MotionPhotosOffOutlinedIcon"><path d="M2.81 2.81 1.39 4.22l2.27 2.27C2.61 8.07 2 9.96 2 12c0 5.52 4.48 10 10 10 2.04 0 3.93-.61 5.51-1.66l2.27 2.27 1.41-1.42L2.81 2.81zM12 20c-4.41 0-8-3.59-8-8 0-1.48.41-2.86 1.12-4.06l10.93 10.94C14.86 19.59 13.48 20 12 20zm0-16c4.41 0 8 3.59 8 8 0 1.48-.41 2.86-1.12 4.05l1.45 1.45C21.39 15.93 22 14.04 22 12c0-5.52-4.48-10-10-10-2.04 0-3.93.61-5.51 1.66l1.45 1.45C9.14 4.41 10.52 4 12 4z"></path></svg><div class="collapse-box__label">This section cannot be displayed here due to unsupported HTML elements. The front-end view of your page won’t be affected.</div><svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root" focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="UnfoldLessOutlinedIcon"><path d="M7.41 18.59 8.83 20 12 16.83 15.17 20l1.41-1.41L12 14l-4.59 4.59zm9.18-13.18L15.17 4 12 7.17 8.83 4 7.41 5.41 12 10l4.59-4.59z"></path></svg></button><div class="collapse-box__content" hidden="" data-testid="content"><br>null</div></div></span>',
134
+ '<span class="unsupported-node-node-view-wrapper" originalnode="null" errormessage="null" data-unsupported-node="{&quot;originalNode&quot;:null,&quot;errorMessage&quot;:null}"><div class="collapse-box" contenteditable="false"><button class="collapse-box__header" type="button"><svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium collapse-box__icon--warning css-i4bv87-MuiSvgIcon-root" focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="MotionPhotosOffOutlinedIcon"><path d="M2.81 2.81 1.39 4.22l2.27 2.27C2.61 8.07 2 9.96 2 12c0 5.52 4.48 10 10 10 2.04 0 3.93-.61 5.51-1.66l2.27 2.27 1.41-1.42zM12 20c-4.41 0-8-3.59-8-8 0-1.48.41-2.86 1.12-4.06l10.93 10.94C14.86 19.59 13.48 20 12 20m0-16c4.41 0 8 3.59 8 8 0 1.48-.41 2.86-1.12 4.05l1.45 1.45C21.39 15.93 22 14.04 22 12c0-5.52-4.48-10-10-10-2.04 0-3.93.61-5.51 1.66l1.45 1.45C9.14 4.41 10.52 4 12 4"></path></svg><div class="collapse-box__label">This section cannot be displayed here due to unsupported HTML elements. The front-end view of your page won’t be affected.</div><svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-i4bv87-MuiSvgIcon-root" focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="UnfoldLessOutlinedIcon"><path d="M7.41 18.59 8.83 20 12 16.83 15.17 20l1.41-1.41L12 14zm9.18-13.18L15.17 4 12 7.17 8.83 4 7.41 5.41 12 10z"></path></svg></button><div class="collapse-box__content" hidden="" data-testid="content"><br>null</div></div></span>',
135
135
  );
136
136
  });
137
137
  });
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
@@ -5,3 +5,6 @@ export type DeepPartial<T> = {
5
5
  ? ReadonlyArray<DeepPartial<U>>
6
6
  : DeepPartial<T[P]>;
7
7
  };
8
+
9
+ export type ResolveNodeType = { [attr: string]: any };
10
+ export type ResolveNodeToUrl = (node: ResolveNodeType) => Promise<string>;
@@ -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
- render(<MatrixAsset modalTitle="Insert asset" onChange={jest.fn()} />);
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
- expect(screen.getByRole('button', { name: 'Choose asset' })).toBeInTheDocument();
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: 'my-source-id',
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
- expect(screen.getByText('My resource')).toBeInTheDocument();
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: 'my-source-id',
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 { HydratedResourceReference, ResourceBrowserInput } from '@squiz/resource-browser';
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
- <ResourceBrowserInput
31
+ <ResourceBrowser
32
32
  modalTitle={modalTitle}
33
33
  allowedTypes={allowedTypes}
34
34
  value={
35
35
  value && value.matrixIdentifier && value.matrixAssetId
36
36
  ? {
37
- source: value.matrixIdentifier,
38
- resource: value.matrixAssetId,
37
+ sourceId: value.matrixIdentifier,
38
+ resourceId: value.matrixAssetId,
39
39
  }
40
40
  : null
41
41
  }
42
- onChange={(reference: HydratedResourceReference | null) => {
42
+ onChange={(resource: ResourceBrowserResource | null) => {
43
43
  onChange({
44
44
  target: {
45
45
  value: {
46
46
  ...value,
47
- matrixIdentifier: reference?.source?.id,
48
- matrixAssetId: reference?.resource?.id,
49
- url: reference?.resource?.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
- matrix: {
14
- matrixDomain: 'unsupported',
15
- },
11
+ const extensions = createExtensions({
12
+ matrix: {
13
+ matrixDomain: 'unsupported',
16
14
  },
17
- {
18
- onRequestResource: () => {
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 { ResourceBrowserContext, Source, Resource, ResourceReference } from '@squiz/resource-browser';
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 = jest
43
- .fn()
44
- .mockImplementation(
45
- (reference: ResourceReference) => resources?.find((resource) => resource.id === reference.resource) || null,
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
- <ResourceBrowserContext.Provider value={{ onRequestSources, onRequestChildren, onRequestResource }}>
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
- </ResourceBrowserContext.Provider>
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.findByRole('button', { name: sourceLabel }));
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 { ResourceBrowserContext } from '@squiz/resource-browser';
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
- <ResourceBrowserContext.Provider value={{ onRequestSources, onRequestChildren, onRequestResource }}>
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
- </ResourceBrowserContext.Provider>
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, browserContext)(),
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: ['@squiz/resource-browser'],
10
+ include: [],
11
11
  },
12
12
  build: {
13
13
  outDir: 'build/demo',
14
14
  commonjsOptions: {
15
- include: [/node_modules/, /resource-browser/],
15
+ include: [/node_modules/],
16
16
  },
17
17
  },
18
18
  plugins: [