@squiz/formatted-text-editor 1.40.1-alpha.27 → 1.40.1-alpha.29
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/lib/Editor/Editor.js +2 -1
- package/lib/EditorToolbar/Tools/Image/ImageButton.js +1 -1
- package/lib/Extensions/Extensions.d.ts +2 -1
- package/lib/Extensions/Extensions.js +6 -1
- package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.d.ts +14 -0
- package/lib/Extensions/FetchUrlExtension/FetchUrlExtension.js +63 -0
- package/lib/Extensions/ImageExtension/AssetImageExtension.d.ts +1 -0
- package/lib/Extensions/ImageExtension/AssetImageExtension.js +4 -4
- package/lib/Extensions/LinkExtension/AssetLinkExtension.d.ts +2 -0
- package/lib/Extensions/LinkExtension/AssetLinkExtension.js +4 -4
- package/lib/ui/Fields/MatrixAsset/MatrixAsset.d.ts +1 -0
- package/lib/ui/Fields/MatrixAsset/MatrixAsset.js +1 -0
- package/package.json +4 -4
- package/src/Editor/Editor.tsx +2 -1
- package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +12 -2
- package/src/EditorToolbar/Tools/Image/ImageButton.tsx +1 -1
- package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +14 -2
- package/src/Extensions/Extensions.ts +8 -2
- package/src/Extensions/FetchUrlExtension/FetchUrlExtension.ts +73 -0
- package/src/Extensions/ImageExtension/AssetImageExtension.spec.ts +2 -1
- package/src/Extensions/ImageExtension/AssetImageExtension.ts +5 -5
- package/src/Extensions/LinkExtension/AssetLinkExtension.spec.ts +3 -1
- package/src/Extensions/LinkExtension/AssetLinkExtension.ts +6 -5
- package/src/ui/Fields/MatrixAsset/MatrixAsset.spec.tsx +1 -0
- package/src/ui/Fields/MatrixAsset/MatrixAsset.tsx +2 -0
- package/tests/mockResourceBrowserContext.tsx +2 -2
- package/tests/renderWithContext.tsx +30 -1
- package/tests/renderWithEditor.tsx +18 -13
- package/lib/utils/resolveMatrixAssetUrl.d.ts +0 -1
- package/lib/utils/resolveMatrixAssetUrl.js +0 -10
- package/src/utils/resolveMatrixAssetUrl.spec.ts +0 -26
- package/src/utils/resolveMatrixAssetUrl.ts +0 -7
package/lib/Editor/Editor.js
CHANGED
@@ -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',
|
@@ -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;
|
@@ -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
|
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
|
};
|
@@ -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
|
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;
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@squiz/formatted-text-editor",
|
3
|
-
"version": "1.40.1-alpha.
|
3
|
+
"version": "1.40.1-alpha.29",
|
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.
|
24
|
-
"@squiz/resource-browser": "1.40.1-alpha.
|
23
|
+
"@squiz/dx-json-schema-lib": "1.40.1-alpha.29",
|
24
|
+
"@squiz/resource-browser": "1.40.1-alpha.29",
|
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": "
|
78
|
+
"gitHead": "9326d74cc366fdc140e2f699675778defce1f14a"
|
79
79
|
}
|
package/src/Editor/Editor.tsx
CHANGED
@@ -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: {
|
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: {
|
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
|
],
|
@@ -274,7 +274,13 @@ describe('LinkButton', () => {
|
|
274
274
|
marks: [
|
275
275
|
{
|
276
276
|
type: 'assetLink',
|
277
|
-
attrs: {
|
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: {
|
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://
|
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
|
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://
|
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
|
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 });
|
@@ -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(
|
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 } =
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
|
-
};
|