@squiz/formatted-text-editor 1.40.1-alpha.27 → 1.40.1-alpha.28

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 (32) hide show
  1. package/lib/Editor/Editor.js +2 -1
  2. package/lib/EditorToolbar/Tools/Image/ImageButton.js +1 -1
  3. package/lib/Extensions/Extensions.d.ts +2 -1
  4. package/lib/Extensions/Extensions.js +6 -1
  5. package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.d.ts +14 -0
  6. package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.js +63 -0
  7. package/lib/Extensions/ImageExtension/AssetImageExtension.d.ts +1 -0
  8. package/lib/Extensions/ImageExtension/AssetImageExtension.js +4 -4
  9. package/lib/Extensions/LinkExtension/AssetLinkExtension.d.ts +2 -0
  10. package/lib/Extensions/LinkExtension/AssetLinkExtension.js +4 -4
  11. package/lib/ui/Fields/MatrixAsset/MatrixAsset.d.ts +1 -0
  12. package/lib/ui/Fields/MatrixAsset/MatrixAsset.js +1 -0
  13. package/package.json +4 -4
  14. package/src/Editor/Editor.tsx +2 -1
  15. package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +12 -2
  16. package/src/EditorToolbar/Tools/Image/ImageButton.tsx +1 -1
  17. package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +14 -2
  18. package/src/Extensions/Extensions.ts +8 -2
  19. package/src/Extensions/FetchUrlExtension/FetchUrlExtension.ts +73 -0
  20. package/src/Extensions/ImageExtension/AssetImageExtension.spec.ts +2 -1
  21. package/src/Extensions/ImageExtension/AssetImageExtension.ts +5 -5
  22. package/src/Extensions/LinkExtension/AssetLinkExtension.spec.ts +3 -1
  23. package/src/Extensions/LinkExtension/AssetLinkExtension.ts +6 -5
  24. package/src/ui/Fields/MatrixAsset/MatrixAsset.spec.tsx +1 -0
  25. package/src/ui/Fields/MatrixAsset/MatrixAsset.tsx +2 -0
  26. package/tests/mockResourceBrowserContext.tsx +2 -2
  27. package/tests/renderWithContext.tsx +30 -1
  28. package/tests/renderWithEditor.tsx +18 -13
  29. package/lib/utils/resolveMatrixAssetUrl.d.ts +0 -1
  30. package/lib/utils/resolveMatrixAssetUrl.js +0 -10
  31. package/src/utils/resolveMatrixAssetUrl.spec.ts +0 -26
  32. package/src/utils/resolveMatrixAssetUrl.ts +0 -7
@@ -33,6 +33,7 @@ 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");
36
37
  const WrappedEditor = () => {
37
38
  const preventImagePaste = (0, react_1.useCallback)((event) => {
38
39
  const { clipboardData } = event;
@@ -48,7 +49,7 @@ const WrappedEditor = () => {
48
49
  };
49
50
  const Editor = ({ content, editable = true, onChange, children, isFocused }) => {
50
51
  const { manager, state, setState } = (0, react_2.useRemirror)({
51
- extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext)),
52
+ extensions: (0, Extensions_1.createExtensions)((0, react_1.useContext)(EditorContext_1.EditorContext), (0, react_1.useContext)(resource_browser_1.ResourceBrowserContext)),
52
53
  content,
53
54
  selection: 'start',
54
55
  stringHandler: 'html',
@@ -53,7 +53,7 @@ const ImageButton = ({ inPopover = false }) => {
53
53
  insertAssetImage(assetImage);
54
54
  }
55
55
  };
56
- const handleSubmit = (data) => {
56
+ const handleSubmit = async (data) => {
57
57
  insertImageFromData(data);
58
58
  setShowModal(false);
59
59
  };
@@ -1,5 +1,6 @@
1
1
  import { Extension } from '@remirror/core';
2
2
  import { EditorContextOptions } from '../Editor/EditorContext';
3
+ import { ResourceBrowserContextProps } from '@squiz/resource-browser';
3
4
  export declare enum NodeName {
4
5
  Image = "image",
5
6
  CodeBlock = "codeBlock",
@@ -11,4 +12,4 @@ export declare enum MarkName {
11
12
  Link = "link",
12
13
  AssetLink = "assetLink"
13
14
  }
14
- export declare const createExtensions: (context: EditorContextOptions) => () => Extension[];
15
+ export declare const createExtensions: (context: EditorContextOptions, browserContext: ResourceBrowserContextProps) => () => Extension[];
@@ -11,6 +11,7 @@ const AssetImageExtension_1 = require("./ImageExtension/AssetImageExtension");
11
11
  const CodeBlockExtension_1 = require("./CodeBlockExtension/CodeBlockExtension");
12
12
  const ClearFormattingExtension_1 = require("./ClearFormattingExtension/ClearFormattingExtension");
13
13
  const UnsupportedNodeExtension_1 = require("./UnsuportedExtension/UnsupportedNodeExtension");
14
+ const FetchUrlExtension_1 = require("./FetchUrlExtension/FetchUrlExtension");
14
15
  var NodeName;
15
16
  (function (NodeName) {
16
17
  NodeName["Image"] = "image";
@@ -24,7 +25,7 @@ var MarkName;
24
25
  MarkName["Link"] = "link";
25
26
  MarkName["AssetLink"] = "assetLink";
26
27
  })(MarkName = exports.MarkName || (exports.MarkName = {}));
27
- const createExtensions = (context) => {
28
+ const createExtensions = (context, browserContext) => {
28
29
  return () => {
29
30
  return [
30
31
  new CommandsExtension_1.CommandsExtension(),
@@ -51,6 +52,10 @@ const createExtensions = (context) => {
51
52
  new extensions_1.BulletListExtension(),
52
53
  new extensions_1.ListItemExtension(),
53
54
  new extensions_1.OrderedListExtension(),
55
+ new extensions_1.PlaceholderExtension(),
56
+ new FetchUrlExtension_1.FetchUrlExtension({
57
+ fetchUrl: browserContext.onRequestResource,
58
+ }),
54
59
  ];
55
60
  };
56
61
  };
@@ -0,0 +1,14 @@
1
+ import { PlainExtension } from '@remirror/core';
2
+ import { Dispose, EditorView } from '@remirror/core-types';
3
+ export type FetchUrlOptions = {
4
+ fetchUrl?: (params: {
5
+ resource: string;
6
+ source: string;
7
+ }) => Promise<any>;
8
+ };
9
+ export declare class FetchUrlExtension extends PlainExtension<FetchUrlOptions> {
10
+ get name(): string;
11
+ onView(view: EditorView): Dispose | void;
12
+ private findAssetLinkMark;
13
+ private fetchAndReplace;
14
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.FetchUrlExtension = void 0;
10
+ const core_1 = require("@remirror/core");
11
+ const Extensions_1 = require("../Extensions");
12
+ let FetchUrlExtension = class FetchUrlExtension extends core_1.PlainExtension {
13
+ get name() {
14
+ return 'fetchUrl';
15
+ }
16
+ // Implement the `onView` lifecycle method to scan for `[fetch]` and replace attributes
17
+ onView(view) {
18
+ const { state } = view;
19
+ const { tr } = state;
20
+ const promises = [];
21
+ state.doc.descendants((node, pos) => {
22
+ if (node.type.name === Extensions_1.NodeName.AssetImage && node.attrs.src === '') {
23
+ promises.push(this.fetchAndReplace(node.attrs.matrixAssetId, node.attrs.matrixIdentifier, (url) => {
24
+ const newNode = state.schema.nodes[Extensions_1.NodeName.AssetImage].create({ ...node.attrs, src: url });
25
+ tr.replaceWith(pos, pos + node.nodeSize, newNode);
26
+ }));
27
+ }
28
+ const assetLinkMark = this.findAssetLinkMark(node.marks);
29
+ if (node.type.name === 'text' && assetLinkMark) {
30
+ promises.push(this.fetchAndReplace(assetLinkMark.attrs.matrixAssetId, assetLinkMark.attrs.matrixIdentifier, (url) => {
31
+ const updatedMark = assetLinkMark.type.create({ ...assetLinkMark.attrs, href: url });
32
+ tr.addMark(pos, pos + node.nodeSize, updatedMark);
33
+ }));
34
+ }
35
+ });
36
+ if (promises.length) {
37
+ Promise.all(promises).then(() => {
38
+ view.dispatch(tr);
39
+ });
40
+ }
41
+ }
42
+ findAssetLinkMark(marks) {
43
+ return marks.find((mark) => mark.type.name === Extensions_1.MarkName.AssetLink && mark.attrs.href === '');
44
+ }
45
+ fetchAndReplace(resource, source, onFetched) {
46
+ return this.options
47
+ .fetchUrl({ resource, source })
48
+ .then((asset) => {
49
+ onFetched(asset.url);
50
+ })
51
+ .catch((error) => {
52
+ console.error('Error fetching URL:', error);
53
+ });
54
+ }
55
+ };
56
+ FetchUrlExtension = __decorate([
57
+ (0, core_1.extension)({
58
+ defaultOptions: {
59
+ fetchUrl: () => Promise.resolve(),
60
+ },
61
+ })
62
+ ], FetchUrlExtension);
63
+ exports.FetchUrlExtension = FetchUrlExtension;
@@ -7,6 +7,7 @@ export type AssetImageAttributes = {
7
7
  matrixAssetId: string;
8
8
  matrixIdentifier: string;
9
9
  matrixDomain: string;
10
+ url: string;
10
11
  };
11
12
  export declare class AssetImageExtension extends NodeExtension<AssetImageOptions> {
12
13
  get name(): NodeName.AssetImage;
@@ -9,7 +9,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.AssetImageExtension = void 0;
10
10
  const core_1 = require("@remirror/core");
11
11
  const remirror_1 = require("remirror");
12
- const resolveMatrixAssetUrl_1 = require("../../utils/resolveMatrixAssetUrl");
13
12
  const Extensions_1 = require("../Extensions");
14
13
  let AssetImageExtension = class AssetImageExtension extends core_1.NodeExtension {
15
14
  get name() {
@@ -29,6 +28,7 @@ let AssetImageExtension = class AssetImageExtension extends core_1.NodeExtension
29
28
  matrixAssetId: {},
30
29
  matrixIdentifier: {},
31
30
  matrixDomain: { default: this.options.matrixDomain },
31
+ src: { default: '' },
32
32
  },
33
33
  parseDOM: [
34
34
  {
@@ -53,13 +53,13 @@ let AssetImageExtension = class AssetImageExtension extends core_1.NodeExtension
53
53
  },
54
54
  ],
55
55
  toDOM: (node) => {
56
- const { matrixAssetId, matrixIdentifier, matrixDomain, ...rest } = (0, core_1.omitExtraAttributes)(node.attrs, extra);
56
+ const { matrixAssetId, matrixIdentifier, matrixDomain, src, ...rest } = (0, core_1.omitExtraAttributes)(node.attrs, extra);
57
57
  return [
58
58
  'img',
59
59
  {
60
60
  ...extra.dom(node),
61
61
  ...rest,
62
- src: (0, resolveMatrixAssetUrl_1.resolveMatrixAssetUrl)(String(matrixAssetId), String(matrixDomain)),
62
+ src,
63
63
  'data-matrix-asset-id': matrixAssetId,
64
64
  'data-matrix-identifier': matrixIdentifier,
65
65
  'data-matrix-domain': matrixDomain,
@@ -71,7 +71,7 @@ let AssetImageExtension = class AssetImageExtension extends core_1.NodeExtension
71
71
  insertAssetImage(attrs) {
72
72
  return ({ tr, dispatch }) => {
73
73
  const { from, to } = (0, remirror_1.getTextSelection)(tr.selection, tr.doc);
74
- const node = this.type.create(attrs);
74
+ const node = this.type.create({ ...attrs, src: attrs.url });
75
75
  dispatch?.(tr.replaceRangeWith(from, to, node));
76
76
  return true;
77
77
  };
@@ -6,6 +6,8 @@ export type AssetLinkAttributes = {
6
6
  matrixIdentifier: string;
7
7
  matrixDomain: string;
8
8
  target: LinkTarget;
9
+ href: string;
10
+ url: string;
9
11
  };
10
12
  export type AssetLinkOptions = {
11
13
  matrixDomain?: string;
@@ -12,7 +12,6 @@ const core_1 = require("@remirror/core");
12
12
  const common_1 = require("./common");
13
13
  const CommandsExtension_1 = require("../CommandsExtension/CommandsExtension");
14
14
  const Extensions_1 = require("../Extensions");
15
- const resolveMatrixAssetUrl_1 = require("../../utils/resolveMatrixAssetUrl");
16
15
  let AssetLinkExtension = class AssetLinkExtension extends core_1.MarkExtension {
17
16
  get name() {
18
17
  return Extensions_1.MarkName.AssetLink;
@@ -26,6 +25,7 @@ let AssetLinkExtension = class AssetLinkExtension extends core_1.MarkExtension {
26
25
  ...extra.defaults(),
27
26
  matrixAssetId: {},
28
27
  matrixIdentifier: {},
28
+ href: { default: '' },
29
29
  matrixDomain: { default: this.options.matrixDomain },
30
30
  target: { default: this.options.defaultTarget },
31
31
  },
@@ -53,13 +53,13 @@ let AssetLinkExtension = class AssetLinkExtension extends core_1.MarkExtension {
53
53
  },
54
54
  ],
55
55
  toDOM: (node) => {
56
- const { matrixAssetId, matrixIdentifier, matrixDomain, target, ...rest } = (0, core_1.omitExtraAttributes)(node.attrs, extra);
56
+ const { matrixAssetId, matrixIdentifier, matrixDomain, target, href, ...rest } = (0, core_1.omitExtraAttributes)(node.attrs, extra);
57
57
  const rel = 'noopener noreferrer nofollow';
58
58
  const attrs = {
59
59
  ...extra.dom(node),
60
60
  ...rest,
61
61
  rel,
62
- href: (0, resolveMatrixAssetUrl_1.resolveMatrixAssetUrl)(String(matrixAssetId), String(matrixDomain)),
62
+ href,
63
63
  target: (0, common_1.validateTarget)(target, this.options.supportedTargets, this.options.defaultTarget),
64
64
  'data-matrix-asset-id': matrixAssetId,
65
65
  'data-matrix-identifier': matrixIdentifier,
@@ -71,7 +71,7 @@ let AssetLinkExtension = class AssetLinkExtension extends core_1.MarkExtension {
71
71
  }
72
72
  updateAssetLink({ text, attrs, range }) {
73
73
  return this.store.getExtension(CommandsExtension_1.CommandsExtension).updateMark({
74
- attrs,
74
+ attrs: { ...attrs, href: attrs.url },
75
75
  text,
76
76
  range,
77
77
  mark: this.type,
@@ -2,6 +2,7 @@ import { InputContainerProps } from '../InputContainer/InputContainer';
2
2
  type MatrixAssetValue = {
3
3
  matrixIdentifier?: string;
4
4
  matrixAssetId?: string;
5
+ url?: string;
5
6
  };
6
7
  export type MatrixAssetProps<T extends MatrixAssetValue> = Omit<InputContainerProps, 'children'> & {
7
8
  modalTitle: string;
@@ -21,6 +21,7 @@ const MatrixAsset = ({ modalTitle, allowedTypes, value, onChange, ...props }) =>
21
21
  ...value,
22
22
  matrixIdentifier: reference?.source?.id,
23
23
  matrixAssetId: reference?.resource?.id,
24
+ url: reference?.resource?.url,
24
25
  },
25
26
  },
26
27
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@squiz/formatted-text-editor",
3
- "version": "1.40.1-alpha.27",
3
+ "version": "1.40.1-alpha.28",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "scripts": {
@@ -20,8 +20,8 @@
20
20
  "@headlessui/react": "1.7.11",
21
21
  "@mui/icons-material": "5.11.16",
22
22
  "@remirror/react": "2.0.25",
23
- "@squiz/dx-json-schema-lib": "1.40.1-alpha.27",
24
- "@squiz/resource-browser": "1.40.1-alpha.27",
23
+ "@squiz/dx-json-schema-lib": "1.40.1-alpha.28",
24
+ "@squiz/resource-browser": "1.40.1-alpha.28",
25
25
  "clsx": "1.2.1",
26
26
  "react-hook-form": "7.43.2",
27
27
  "react-image-size": "2.0.0",
@@ -75,5 +75,5 @@
75
75
  "volta": {
76
76
  "node": "18.15.0"
77
77
  },
78
- "gitHead": "c7d710fd2b564d3ed3f308cf937acd8a3d8fcba1"
78
+ "gitHead": "e5f9c0316999993b1a8f475e2fef72dc4e6b3229"
79
79
  }
@@ -7,6 +7,7 @@ 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';
10
11
 
11
12
  type EditorProps = {
12
13
  content?: RemirrorContentType;
@@ -34,7 +35,7 @@ const WrappedEditor = () => {
34
35
 
35
36
  const Editor = ({ content, editable = true, onChange, children, isFocused }: EditorProps) => {
36
37
  const { manager, state, setState } = useRemirror({
37
- extensions: createExtensions(useContext(EditorContext)),
38
+ extensions: createExtensions(useContext(EditorContext), useContext(ResourceBrowserContext)),
38
39
  content,
39
40
  selection: 'start',
40
41
  stringHandler: 'html',
@@ -280,7 +280,12 @@ describe('ImageButton', () => {
280
280
  content: [
281
281
  {
282
282
  type: 'assetImage',
283
- attrs: { matrixAssetId: 'image-resource-id', matrixIdentifier, matrixDomain },
283
+ attrs: {
284
+ matrixAssetId: 'image-resource-id',
285
+ matrixIdentifier,
286
+ matrixDomain,
287
+ src: 'https://default-resource/',
288
+ },
284
289
  },
285
290
  { type: 'text', text: 'Some nonsense content here' },
286
291
  ],
@@ -339,7 +344,12 @@ describe('ImageButton', () => {
339
344
  },
340
345
  {
341
346
  type: 'assetImage',
342
- attrs: { matrixAssetId: 'image-resource-id', matrixIdentifier, matrixDomain },
347
+ attrs: {
348
+ matrixAssetId: 'image-resource-id',
349
+ matrixIdentifier,
350
+ matrixDomain,
351
+ src: 'https://default-resource/',
352
+ },
343
353
  },
344
354
  { type: 'text', text: ' nonsense' },
345
355
  ],
@@ -36,7 +36,7 @@ const ImageButton = ({ inPopover = false }: ImageButtonProps) => {
36
36
  }
37
37
  };
38
38
 
39
- const handleSubmit = (data: ImageFormData) => {
39
+ const handleSubmit = async (data: ImageFormData) => {
40
40
  insertImageFromData(data);
41
41
  setShowModal(false);
42
42
  };
@@ -274,7 +274,13 @@ describe('LinkButton', () => {
274
274
  marks: [
275
275
  {
276
276
  type: 'assetLink',
277
- attrs: { matrixAssetId: 'my-resource-id', target: '_self', matrixDomain, matrixIdentifier },
277
+ attrs: {
278
+ matrixAssetId: 'my-resource-id',
279
+ target: '_self',
280
+ matrixDomain,
281
+ matrixIdentifier,
282
+ href: 'https://default-resource/',
283
+ },
278
284
  },
279
285
  ],
280
286
  },
@@ -328,7 +334,13 @@ describe('LinkButton', () => {
328
334
  marks: [
329
335
  {
330
336
  type: 'assetLink',
331
- attrs: { matrixAssetId: 'my-resource-id', target: '_blank', matrixDomain, matrixIdentifier },
337
+ attrs: {
338
+ matrixAssetId: 'my-resource-id',
339
+ target: '_blank',
340
+ matrixDomain,
341
+ matrixIdentifier,
342
+ href: 'https://default-resource/',
343
+ },
332
344
  },
333
345
  ],
334
346
  },
@@ -9,6 +9,7 @@ import {
9
9
  OrderedListExtension,
10
10
  BulletListExtension,
11
11
  ListItemExtension,
12
+ PlaceholderExtension,
12
13
  } from 'remirror/extensions';
13
14
  import { Extension } from '@remirror/core';
14
15
  import { PreformattedExtension } from './PreformattedExtension/PreformattedExtension';
@@ -21,7 +22,8 @@ import { AssetImageExtension } from './ImageExtension/AssetImageExtension';
21
22
  import { ExtendedCodeBlockExtension } from './CodeBlockExtension/CodeBlockExtension';
22
23
  import { ClearFormattingExtension } from './ClearFormattingExtension/ClearFormattingExtension';
23
24
  import { UnsupportedNodeExtension } from './UnsuportedExtension/UnsupportedNodeExtension';
24
-
25
+ import { ResourceBrowserContextProps } from '@squiz/resource-browser';
26
+ import { FetchUrlExtension } from './FetchUrlExtension/FetchUrlExtension';
25
27
  export enum NodeName {
26
28
  Image = 'image',
27
29
  CodeBlock = 'codeBlock',
@@ -35,7 +37,7 @@ export enum MarkName {
35
37
  AssetLink = 'assetLink',
36
38
  }
37
39
 
38
- export const createExtensions = (context: EditorContextOptions) => {
40
+ export const createExtensions = (context: EditorContextOptions, browserContext: ResourceBrowserContextProps) => {
39
41
  return (): Extension[] => {
40
42
  return [
41
43
  new CommandsExtension(),
@@ -62,6 +64,10 @@ export const createExtensions = (context: EditorContextOptions) => {
62
64
  new BulletListExtension(),
63
65
  new ListItemExtension(),
64
66
  new OrderedListExtension(),
67
+ new PlaceholderExtension(),
68
+ new FetchUrlExtension({
69
+ fetchUrl: browserContext.onRequestResource,
70
+ }),
65
71
  ];
66
72
  };
67
73
  };
@@ -0,0 +1,73 @@
1
+ import { extension, PlainExtension, Mark } from '@remirror/core';
2
+ import { Dispose, EditorView } from '@remirror/core-types';
3
+ import { Resource } from '@squiz/resource-browser';
4
+ import { MarkName, NodeName } from '../Extensions';
5
+
6
+ export type FetchUrlOptions = {
7
+ fetchUrl?: (params: { resource: string; source: string }) => Promise<any>;
8
+ };
9
+
10
+ @extension<FetchUrlOptions>({
11
+ defaultOptions: {
12
+ fetchUrl: () => Promise.resolve(),
13
+ },
14
+ })
15
+ export class FetchUrlExtension extends PlainExtension<FetchUrlOptions> {
16
+ get name() {
17
+ return 'fetchUrl';
18
+ }
19
+
20
+ // Implement the `onView` lifecycle method to scan for `[fetch]` and replace attributes
21
+ onView(view: EditorView): Dispose | void {
22
+ const { state } = view;
23
+ const { tr } = state;
24
+
25
+ const promises: Promise<void>[] = [];
26
+
27
+ state.doc.descendants((node, pos) => {
28
+ if (node.type.name === NodeName.AssetImage && node.attrs.src === '') {
29
+ promises.push(
30
+ this.fetchAndReplace(node.attrs.matrixAssetId, node.attrs.matrixIdentifier, (url: string) => {
31
+ const newNode = state.schema.nodes[NodeName.AssetImage].create({ ...node.attrs, src: url });
32
+ tr.replaceWith(pos, pos + node.nodeSize, newNode);
33
+ }),
34
+ );
35
+ }
36
+
37
+ const assetLinkMark = this.findAssetLinkMark(node.marks as Mark[]);
38
+ if (node.type.name === 'text' && assetLinkMark) {
39
+ 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
+ ),
48
+ );
49
+ }
50
+ });
51
+
52
+ if (promises.length) {
53
+ Promise.all(promises).then(() => {
54
+ view.dispatch(tr);
55
+ });
56
+ }
57
+ }
58
+
59
+ private findAssetLinkMark(marks: Mark[]): Mark | undefined {
60
+ return marks.find((mark) => mark.type.name === MarkName.AssetLink && mark.attrs.href === '');
61
+ }
62
+
63
+ private fetchAndReplace(resource: string, source: string, onFetched: (url: string) => void): Promise<void> {
64
+ return this.options
65
+ .fetchUrl({ resource, source })
66
+ .then((asset: Resource) => {
67
+ onFetched(asset.url);
68
+ })
69
+ .catch((error) => {
70
+ console.error('Error fetching URL:', error);
71
+ });
72
+ }
73
+ }
@@ -20,6 +20,7 @@ describe('AssetImageExtension', () => {
20
20
  matrixAssetId: '123',
21
21
  matrixDomain: 'https://matrix-domain.squiz.net',
22
22
  matrixIdentifier: 'matrix-api-identifier',
23
+ src: 'https://default-resource/',
23
24
  },
24
25
  },
25
26
  ],
@@ -64,7 +65,7 @@ describe('AssetImageExtension', () => {
64
65
 
65
66
  expect(getHtmlContent()).toEqual(
66
67
  '<img ' +
67
- 'src="https://matrix-domain.squiz.net/_nocache?a=123" ' +
68
+ 'src="https://default-resource/" ' +
68
69
  'data-matrix-asset-id="123" ' +
69
70
  'data-matrix-identifier="matrix-api-identifier" ' +
70
71
  'data-matrix-domain="https://matrix-domain.squiz.net" ' +
@@ -12,7 +12,6 @@ import {
12
12
  CommandFunction,
13
13
  } from '@remirror/core';
14
14
  import { getTextSelection } from 'remirror';
15
- import { resolveMatrixAssetUrl } from '../../utils/resolveMatrixAssetUrl';
16
15
  import { NodeName } from '../Extensions';
17
16
 
18
17
  export type AssetImageOptions = {
@@ -23,6 +22,7 @@ export type AssetImageAttributes = {
23
22
  matrixAssetId: string;
24
23
  matrixIdentifier: string;
25
24
  matrixDomain: string;
25
+ url: string;
26
26
  };
27
27
 
28
28
  @extension<AssetImageOptions>({
@@ -51,6 +51,7 @@ export class AssetImageExtension extends NodeExtension<AssetImageOptions> {
51
51
  matrixAssetId: {},
52
52
  matrixIdentifier: {},
53
53
  matrixDomain: { default: this.options.matrixDomain },
54
+ src: { default: '' },
54
55
  },
55
56
  parseDOM: [
56
57
  {
@@ -78,14 +79,13 @@ export class AssetImageExtension extends NodeExtension<AssetImageOptions> {
78
79
  },
79
80
  ],
80
81
  toDOM: (node) => {
81
- const { matrixAssetId, matrixIdentifier, matrixDomain, ...rest } = omitExtraAttributes(node.attrs, extra);
82
-
82
+ const { matrixAssetId, matrixIdentifier, matrixDomain, src, ...rest } = omitExtraAttributes(node.attrs, extra);
83
83
  return [
84
84
  'img',
85
85
  {
86
86
  ...extra.dom(node),
87
87
  ...rest,
88
- src: resolveMatrixAssetUrl(String(matrixAssetId), String(matrixDomain)),
88
+ src,
89
89
  'data-matrix-asset-id': matrixAssetId,
90
90
  'data-matrix-identifier': matrixIdentifier,
91
91
  'data-matrix-domain': matrixDomain,
@@ -99,7 +99,7 @@ export class AssetImageExtension extends NodeExtension<AssetImageOptions> {
99
99
  insertAssetImage(attrs: AssetImageAttributes): CommandFunction {
100
100
  return ({ tr, dispatch }) => {
101
101
  const { from, to } = getTextSelection(tr.selection, tr.doc);
102
- const node = this.type.create(attrs);
102
+ const node = this.type.create({ ...attrs, src: attrs.url });
103
103
 
104
104
  dispatch?.(tr.replaceRangeWith(from, to, node));
105
105
 
@@ -23,6 +23,7 @@ describe('AssetLinkExtension', () => {
23
23
  {
24
24
  type: 'assetLink',
25
25
  attrs: {
26
+ href: 'https://default-resource/',
26
27
  matrixAssetId: '123',
27
28
  matrixDomain: 'https://matrix-domain.squiz.net',
28
29
  matrixIdentifier: 'matrix-api-identifier',
@@ -78,6 +79,7 @@ describe('AssetLinkExtension', () => {
78
79
  {
79
80
  type: 'assetLink',
80
81
  attrs: {
82
+ href: 'https://default-resource/',
81
83
  matrixAssetId: '123',
82
84
  matrixDomain: 'https://matrix-domain.squiz.net',
83
85
  matrixIdentifier: 'matrix-api-identifier',
@@ -92,7 +94,7 @@ describe('AssetLinkExtension', () => {
92
94
 
93
95
  expect(getHtmlContent()).toEqual(
94
96
  '<a rel="noopener noreferrer nofollow" ' +
95
- 'href="https://matrix-domain.squiz.net/_nocache?a=123" ' +
97
+ 'href="https://default-resource/" ' +
96
98
  'target="_blank" ' +
97
99
  'data-matrix-asset-id="123" ' +
98
100
  'data-matrix-identifier="matrix-api-identifier" ' +
@@ -10,13 +10,14 @@ import { command, CommandFunction, extension, MarkExtension, omitExtraAttributes
10
10
  import { LinkTarget, validateTarget } from './common';
11
11
  import { CommandsExtension } from '../CommandsExtension/CommandsExtension';
12
12
  import { MarkName } from '../Extensions';
13
- import { resolveMatrixAssetUrl } from '../../utils/resolveMatrixAssetUrl';
14
13
 
15
14
  export type AssetLinkAttributes = {
16
15
  matrixAssetId: string;
17
16
  matrixIdentifier: string;
18
17
  matrixDomain: string;
19
18
  target: LinkTarget;
19
+ href: string;
20
+ url: string;
20
21
  };
21
22
 
22
23
  export type AssetLinkOptions = {
@@ -53,6 +54,7 @@ export class AssetLinkExtension extends MarkExtension<AssetLinkOptions> {
53
54
  ...extra.defaults(),
54
55
  matrixAssetId: {},
55
56
  matrixIdentifier: {},
57
+ href: { default: '' },
56
58
  matrixDomain: { default: this.options.matrixDomain },
57
59
  target: { default: this.options.defaultTarget },
58
60
  },
@@ -87,7 +89,7 @@ export class AssetLinkExtension extends MarkExtension<AssetLinkOptions> {
87
89
  },
88
90
  ],
89
91
  toDOM: (node) => {
90
- const { matrixAssetId, matrixIdentifier, matrixDomain, target, ...rest } = omitExtraAttributes(
92
+ const { matrixAssetId, matrixIdentifier, matrixDomain, target, href, ...rest } = omitExtraAttributes(
91
93
  node.attrs,
92
94
  extra,
93
95
  );
@@ -96,7 +98,7 @@ export class AssetLinkExtension extends MarkExtension<AssetLinkOptions> {
96
98
  ...extra.dom(node),
97
99
  ...rest,
98
100
  rel,
99
- href: resolveMatrixAssetUrl(String(matrixAssetId), String(matrixDomain)),
101
+ href,
100
102
  target: validateTarget(target, this.options.supportedTargets, this.options.defaultTarget),
101
103
  'data-matrix-asset-id': matrixAssetId,
102
104
  'data-matrix-identifier': matrixIdentifier,
@@ -111,14 +113,13 @@ export class AssetLinkExtension extends MarkExtension<AssetLinkOptions> {
111
113
  @command()
112
114
  updateAssetLink({ text, attrs, range }: UpdateAssetLinkProps): CommandFunction {
113
115
  return this.store.getExtension(CommandsExtension).updateMark({
114
- attrs,
116
+ attrs: { ...attrs, href: attrs.url },
115
117
  text,
116
118
  range,
117
119
  mark: this.type,
118
120
  removeMark: !attrs.matrixAssetId,
119
121
  });
120
122
  }
121
-
122
123
  @command()
123
124
  removeAssetLink(): CommandFunction {
124
125
  return removeMark({ type: this.type });
@@ -59,6 +59,7 @@ describe('MatrixAsset', () => {
59
59
  additional: 'additional data',
60
60
  matrixAssetId: 'my-resource-id',
61
61
  matrixIdentifier: 'my-source-id',
62
+ url: 'https://default-resource/',
62
63
  },
63
64
  },
64
65
  });
@@ -5,6 +5,7 @@ import { InputContainer, InputContainerProps } from '../InputContainer/InputCont
5
5
  type MatrixAssetValue = {
6
6
  matrixIdentifier?: string;
7
7
  matrixAssetId?: string;
8
+ url?: string;
8
9
  };
9
10
 
10
11
  export type MatrixAssetProps<T extends MatrixAssetValue> = Omit<InputContainerProps, 'children'> & {
@@ -45,6 +46,7 @@ export const MatrixAsset = <T extends MatrixAssetValue>({
45
46
  ...value,
46
47
  matrixIdentifier: reference?.source?.id,
47
48
  matrixAssetId: reference?.resource?.id,
49
+ url: reference?.resource?.url,
48
50
  } as T,
49
51
  },
50
52
  });
@@ -8,7 +8,7 @@ export type MockResourceBrowserContextOptions = DeepPartial<{
8
8
  resources: Resource[];
9
9
  }>;
10
10
 
11
- const mockResource = (resource: DeepPartial<Resource> = {}): Resource =>
11
+ export const mockResource = (resource: DeepPartial<Resource> = {}): Resource =>
12
12
  ({
13
13
  id: 'default-resource',
14
14
  name: 'Default resource',
@@ -26,7 +26,7 @@ const mockResource = (resource: DeepPartial<Resource> = {}): Resource =>
26
26
  ...resource,
27
27
  } as Resource);
28
28
 
29
- const mockSource = (source: DeepPartial<Source>): Source => ({
29
+ export const mockSource = (source: DeepPartial<Source> = {}): Source => ({
30
30
  id: 'default-source',
31
31
  name: 'Default source',
32
32
  ...source,
@@ -4,6 +4,8 @@ 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';
8
+ import { mockSource } from './mockResourceBrowserContext';
7
9
 
8
10
  export type ContextRenderOptions = RenderOptions & {
9
11
  context?: DeepPartial<{
@@ -13,6 +15,33 @@ export type ContextRenderOptions = RenderOptions & {
13
15
 
14
16
  export const renderWithContext = (ui: ReactElement | null, options?: ContextRenderOptions): RenderResult => {
15
17
  const editorContext = merge(defaultEditorContext, options?.context?.editor || {}) as EditorContextOptions;
18
+ const sources = mockSource();
19
+ const resources = [
20
+ {
21
+ id: 'default-resource',
22
+ name: 'Default resource',
23
+ url: 'https://default-resource/',
24
+ urls: [],
25
+ childCount: 0,
26
+ type: {
27
+ code: 'unspecified',
28
+ name: 'Unspecified',
29
+ },
30
+ status: {
31
+ code: 'live',
32
+ name: 'Live',
33
+ },
34
+ },
35
+ ];
36
+ const onRequestSources = jest.fn().mockResolvedValue(sources);
37
+ const onRequestChildren = jest.fn().mockResolvedValue(resources);
38
+ const onRequestResource = jest.fn(() => Promise.resolve(resources[0]));
16
39
 
17
- return render(<EditorContext.Provider value={editorContext}>{ui}</EditorContext.Provider>);
40
+ return render(
41
+ <EditorContext.Provider value={editorContext}>
42
+ <ResourceBrowserContext.Provider value={{ onRequestSources, onRequestChildren, onRequestResource }}>
43
+ {ui}
44
+ </ResourceBrowserContext.Provider>
45
+ </EditorContext.Provider>,
46
+ );
18
47
  };
@@ -1,6 +1,7 @@
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';
4
5
  import { BuiltinPreset } from 'remirror';
5
6
  import { EditorComponent, Remirror, useRemirror } from '@remirror/react';
6
7
  import { RemirrorTestChain } from 'jest-remirror';
@@ -8,6 +9,7 @@ import { createExtensions } from '../src/Extensions/Extensions';
8
9
  import { EditorContext } from '../src';
9
10
  import { FloatingToolbar } from '../src/EditorToolbar';
10
11
  import { renderWithContext, ContextRenderOptions } from './renderWithContext';
12
+ import { act } from '@testing-library/react';
11
13
 
12
14
  export type EditorRenderOptions = ContextRenderOptions & {
13
15
  content?: RemirrorContentType;
@@ -20,7 +22,7 @@ type TestEditorProps = EditorRenderOptions & {
20
22
  onReady: (manager: RemirrorManager<Extension>) => void;
21
23
  };
22
24
 
23
- type EditorRenderResult = {
25
+ export type EditorRenderResult = {
24
26
  editor: RemirrorTestChain<Extension | CorePreset | BuiltinPreset>;
25
27
  getHtmlContent: () => string | undefined;
26
28
  getJsonContent: () => any;
@@ -32,8 +34,9 @@ type EditorRenderResult = {
32
34
 
33
35
  const TestEditor = ({ children, extensions, content, onReady, editable }: TestEditorProps) => {
34
36
  const context = useContext(EditorContext);
37
+ const browserContext = useContext(ResourceBrowserContext);
35
38
  const { manager, state, setState } = useRemirror({
36
- extensions: () => extensions || createExtensions(context)(),
39
+ extensions: () => extensions || createExtensions(context, browserContext)(),
37
40
  content: content,
38
41
  selection: 'start',
39
42
  stringHandler: 'html',
@@ -95,17 +98,19 @@ export const renderWithEditor = async (
95
98
  };
96
99
  let isReady = false;
97
100
 
98
- const { container } = renderWithContext(
99
- <TestEditor
100
- onReady={(manager) => {
101
- result.editor = RemirrorTestChain.create(manager);
102
- isReady = true;
103
- }}
104
- {...options}
105
- >
106
- {ui}
107
- </TestEditor>,
108
- options,
101
+ const { container } = await act(() =>
102
+ renderWithContext(
103
+ <TestEditor
104
+ onReady={(manager) => {
105
+ result.editor = RemirrorTestChain.create(manager);
106
+ isReady = true;
107
+ }}
108
+ {...options}
109
+ >
110
+ {ui}
111
+ </TestEditor>,
112
+ options,
113
+ ),
109
114
  );
110
115
 
111
116
  if (!isReady) {
@@ -1 +0,0 @@
1
- export declare const resolveMatrixAssetUrl: (id: string, matrixDomain: string) => string;
@@ -1,10 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveMatrixAssetUrl = void 0;
4
- const resolveMatrixAssetUrl = (id, matrixDomain) => {
5
- if (matrixDomain.indexOf('://') < 0) {
6
- matrixDomain = `${window.location.protocol}//${matrixDomain}`;
7
- }
8
- return new URL(`/_nocache?a=${encodeURIComponent(id)}`, matrixDomain).toString();
9
- };
10
- exports.resolveMatrixAssetUrl = resolveMatrixAssetUrl;
@@ -1,26 +0,0 @@
1
- import { resolveMatrixAssetUrl } from './resolveMatrixAssetUrl';
2
-
3
- describe('resolveMatrixAssetUrl', () => {
4
- it.each([
5
- [
6
- 'domain with no scheme',
7
- '123:hello?.txt',
8
- 'matrix.labs.squiz.test',
9
- 'http://matrix.labs.squiz.test/_nocache?a=123%3Ahello%3F.txt',
10
- ],
11
- [
12
- 'domain with scheme',
13
- '123:hello?.txt',
14
- 'https://matrix.labs.squiz.test',
15
- 'https://matrix.labs.squiz.test/_nocache?a=123%3Ahello%3F.txt',
16
- ],
17
- [
18
- 'domain with path',
19
- '123:hello?.txt',
20
- 'https://matrix.labs.squiz.test/site-1',
21
- 'https://matrix.labs.squiz.test/_nocache?a=123%3Ahello%3F.txt',
22
- ],
23
- ])('Resolves to expected URL for %s', (description: string, id: string, matrixDomain: string, expected: string) => {
24
- expect(resolveMatrixAssetUrl(id, matrixDomain)).toBe(expected);
25
- });
26
- });
@@ -1,7 +0,0 @@
1
- export const resolveMatrixAssetUrl = (id: string, matrixDomain: string): string => {
2
- if (matrixDomain.indexOf('://') < 0) {
3
- matrixDomain = `${window.location.protocol}//${matrixDomain}`;
4
- }
5
-
6
- return new URL(`/_nocache?a=${encodeURIComponent(id)}`, matrixDomain).toString();
7
- };