@squiz/formatted-text-editor 1.40.1-alpha.9 → 1.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/demo/AppContext.tsx +14 -3
- package/demo/resources.json +135 -3
- package/lib/Editor/Editor.js +3 -2
- package/lib/EditorToolbar/Toolbar.js +2 -3
- package/lib/EditorToolbar/Tools/Image/ImageButton.js +1 -1
- package/lib/EditorToolbar/Tools/Lists/ListButtons.d.ts +2 -0
- package/lib/EditorToolbar/Tools/Lists/ListButtons.js +14 -0
- package/lib/EditorToolbar/Tools/Lists/OrderedList/OrderedListButton.d.ts +2 -0
- package/lib/EditorToolbar/Tools/Lists/OrderedList/OrderedListButton.js +22 -0
- package/lib/EditorToolbar/Tools/{UnorderedList → Lists/UnorderedList}/UnorderedListButton.js +1 -1
- package/lib/Extensions/Extensions.d.ts +2 -1
- package/lib/Extensions/Extensions.js +7 -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/hooks/useFocus.js +24 -5
- package/lib/index.css +9 -1
- package/lib/ui/Fields/MatrixAsset/MatrixAsset.d.ts +1 -0
- package/lib/ui/Fields/MatrixAsset/MatrixAsset.js +1 -0
- package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +2 -1
- package/package.json +4 -4
- package/src/Editor/Editor.spec.tsx +91 -16
- package/src/Editor/Editor.tsx +3 -2
- package/src/EditorToolbar/Toolbar.tsx +2 -3
- 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/EditorToolbar/Tools/Lists/ListButtons.tsx +14 -0
- package/src/EditorToolbar/Tools/Lists/OrderedList/OrderListButton.spec.tsx +39 -0
- package/src/EditorToolbar/Tools/Lists/OrderedList/OrderedListButton.tsx +30 -0
- package/src/EditorToolbar/Tools/{UnorderedList → Lists/UnorderedList}/UnorderedList.spec.tsx +1 -1
- package/src/EditorToolbar/Tools/{UnorderedList → Lists/UnorderedList}/UnorderedListButton.tsx +1 -1
- package/src/Extensions/Extensions.ts +10 -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/hooks/useFocus.ts +30 -7
- package/src/ui/Fields/MatrixAsset/MatrixAsset.spec.tsx +1 -0
- package/src/ui/Fields/MatrixAsset/MatrixAsset.tsx +2 -0
- package/src/ui/_typography.scss +10 -1
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.spec.ts +73 -0
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +2 -1
- package/src/utils/getNodeNamesByGroup.spec.ts +1 -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/EditorToolbar/Tools/{UnorderedList → Lists/UnorderedList}/UnorderedListButton.d.ts +0 -0
package/src/hooks/useFocus.ts
CHANGED
@@ -13,14 +13,37 @@ const useFocus = (
|
|
13
13
|
|
14
14
|
const handleFocus = useCallback(() => {
|
15
15
|
setIsVisible(true);
|
16
|
-
}, []);
|
16
|
+
}, [wrapperRef]);
|
17
17
|
|
18
|
-
const handleBlur
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
18
|
+
const handleBlur = useCallback(
|
19
|
+
(event: FocusEvent<HTMLDivElement>) => {
|
20
|
+
// React event bubbling is interesting, it bubbles up the React tree rather than the DOM tree.
|
21
|
+
// The tree deviates when rendering portals (eg. for modals).
|
22
|
+
//
|
23
|
+
// Only hide the toolbar if:
|
24
|
+
// 1. We are blurring a node in the editor **DOM** tree.
|
25
|
+
// 2. We are focusing on something that is not in the editor DOM tree
|
26
|
+
// (elements in the portal won't be in the tree but don't influence the focus state per #1).
|
27
|
+
//
|
28
|
+
// This avoids the scenario where an element in a portal is blurred and another one in the portal focused.
|
29
|
+
// Without this logic the blur and focus handlers are called (in that order). The impact of these handlers being
|
30
|
+
// called is that the "isFocused" state changes inconsistently. This state changing then causes subtle issues.
|
31
|
+
// eg. unable to drill down in resource browser, toolbar appearing/disappearing.
|
32
|
+
//
|
33
|
+
// Ideally we would instead solely seeing if the "relatedTarget" is in the React tree. This isn't easily
|
34
|
+
// identifiable however without reaching into React internals.
|
35
|
+
//
|
36
|
+
// An assumption here is that anything in a portal will only blur to another element that is also in the portal
|
37
|
+
// (and therefore still in our React tree resulting in the element still effectively being focused).
|
38
|
+
const isBlurringEditor = wrapperRef.current?.contains(event.target);
|
39
|
+
const isFocusedInEditor = wrapperRef.current?.contains(event.relatedTarget);
|
40
|
+
|
41
|
+
if (isBlurringEditor && !isFocusedInEditor) {
|
42
|
+
setIsVisible(false);
|
43
|
+
}
|
44
|
+
},
|
45
|
+
[wrapperRef],
|
46
|
+
);
|
24
47
|
|
25
48
|
return { handleFocus, handleBlur, isVisible, wrapperRef };
|
26
49
|
};
|
@@ -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
|
});
|
package/src/ui/_typography.scss
CHANGED
@@ -5,6 +5,10 @@
|
|
5
5
|
text-decoration: underline;
|
6
6
|
}
|
7
7
|
|
8
|
+
p {
|
9
|
+
margin-block-end: 0.8rem;
|
10
|
+
}
|
11
|
+
|
8
12
|
h1 {
|
9
13
|
font-size: 1.625rem;
|
10
14
|
font-weight: 600;
|
@@ -73,9 +77,14 @@
|
|
73
77
|
}
|
74
78
|
}
|
75
79
|
|
76
|
-
ul
|
80
|
+
ul,
|
81
|
+
ol {
|
77
82
|
list-style-type: disc;
|
78
83
|
padding: 0 0 0 2.5rem;
|
79
84
|
margin: 1rem 0;
|
80
85
|
}
|
86
|
+
|
87
|
+
ol {
|
88
|
+
list-style-type: decimal;
|
89
|
+
}
|
81
90
|
}
|
@@ -426,6 +426,79 @@ describe('squizNodeToRemirrorNode', () => {
|
|
426
426
|
},
|
427
427
|
);
|
428
428
|
|
429
|
+
it('should handle ordered lists', () => {
|
430
|
+
const squizComponentJSON: FormattedText = [
|
431
|
+
{
|
432
|
+
type: 'tag',
|
433
|
+
tag: 'ol',
|
434
|
+
children: [
|
435
|
+
{
|
436
|
+
type: 'tag',
|
437
|
+
tag: 'li',
|
438
|
+
children: [
|
439
|
+
{
|
440
|
+
type: 'tag',
|
441
|
+
tag: 'p',
|
442
|
+
children: [
|
443
|
+
{
|
444
|
+
type: 'text',
|
445
|
+
value: 'ddd',
|
446
|
+
},
|
447
|
+
],
|
448
|
+
},
|
449
|
+
],
|
450
|
+
},
|
451
|
+
],
|
452
|
+
},
|
453
|
+
];
|
454
|
+
|
455
|
+
const expected: RemirrorJSON = {
|
456
|
+
content: [
|
457
|
+
{
|
458
|
+
attrs: { level: undefined, nodeIndent: null, nodeLineHeight: null, nodeTextAlignment: null, style: '' },
|
459
|
+
type: 'orderedList',
|
460
|
+
marks: undefined,
|
461
|
+
text: undefined,
|
462
|
+
content: [
|
463
|
+
{
|
464
|
+
attrs: { level: undefined, nodeIndent: null, nodeLineHeight: null, nodeTextAlignment: null, style: '' },
|
465
|
+
type: 'listItem',
|
466
|
+
marks: undefined,
|
467
|
+
text: undefined,
|
468
|
+
content: [
|
469
|
+
{
|
470
|
+
attrs: {
|
471
|
+
level: undefined,
|
472
|
+
nodeIndent: null,
|
473
|
+
nodeLineHeight: null,
|
474
|
+
nodeTextAlignment: null,
|
475
|
+
style: '',
|
476
|
+
},
|
477
|
+
marks: undefined,
|
478
|
+
text: undefined,
|
479
|
+
type: 'paragraph',
|
480
|
+
content: [
|
481
|
+
{
|
482
|
+
attrs: undefined,
|
483
|
+
content: undefined,
|
484
|
+
marks: undefined,
|
485
|
+
text: 'ddd',
|
486
|
+
type: 'text',
|
487
|
+
},
|
488
|
+
],
|
489
|
+
},
|
490
|
+
],
|
491
|
+
},
|
492
|
+
],
|
493
|
+
},
|
494
|
+
],
|
495
|
+
type: 'doc',
|
496
|
+
};
|
497
|
+
|
498
|
+
const result = squizNodeToRemirrorNode(squizComponentJSON);
|
499
|
+
expect(result).toEqual(expected);
|
500
|
+
});
|
501
|
+
|
429
502
|
it('should handle unordered lists', () => {
|
430
503
|
const squizComponentJSON: FormattedText = [
|
431
504
|
{
|
@@ -24,8 +24,9 @@ const getNodeType = (node: FormattedNodes): string => {
|
|
24
24
|
img: 'image',
|
25
25
|
pre: 'preformatted',
|
26
26
|
p: 'paragraph',
|
27
|
-
|
27
|
+
ol: 'orderedList',
|
28
28
|
li: 'listItem',
|
29
|
+
ul: 'bulletList',
|
29
30
|
a: NodeName.Text,
|
30
31
|
span: NodeName.Text,
|
31
32
|
code: NodeName.CodeBlock,
|
@@ -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
|
-
};
|
/package/lib/EditorToolbar/Tools/{UnorderedList → Lists/UnorderedList}/UnorderedListButton.d.ts
RENAMED
File without changes
|