@squiz/formatted-text-editor 1.21.1-alpha.41 → 1.21.1-alpha.43
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/EditorToolbar/Tools/Image/ImageModal.js +3 -2
- package/lib/EditorToolbar/Tools/Link/LinkModal.js +2 -2
- package/lib/Extensions/PreformattedExtension/PreformattedExtension.js +6 -2
- package/package.json +2 -2
- package/src/EditorToolbar/Tools/Image/ImageButton.spec.tsx +34 -1
- package/src/EditorToolbar/Tools/Image/ImageModal.spec.tsx +14 -14
- package/src/EditorToolbar/Tools/Image/ImageModal.tsx +3 -2
- package/src/EditorToolbar/Tools/Link/LinkButton.spec.tsx +21 -0
- package/src/EditorToolbar/Tools/Link/LinkModal.tsx +2 -2
- package/src/Extensions/PreformattedExtension/PreformattedExtension.spec.ts +41 -0
- package/src/Extensions/PreformattedExtension/PreformattedExtension.ts +6 -2
@@ -11,10 +11,11 @@ const Extensions_1 = require("../../../Extensions/Extensions");
|
|
11
11
|
const ImageModal = ({ onCancel, onSubmit }) => {
|
12
12
|
const selection = (0, react_2.useCurrentSelection)();
|
13
13
|
const currentImage = selection?.node;
|
14
|
+
const currentImageAttrs = { ...currentImage?.attrs };
|
14
15
|
const formData = {
|
15
16
|
imageType: currentImage?.type.name === Extensions_1.NodeName.AssetImage ? Extensions_1.NodeName.AssetImage : Extensions_1.NodeName.Image,
|
16
|
-
image: currentImage?.type?.name === Extensions_1.NodeName.Image ?
|
17
|
-
assetImage: currentImage?.type?.name === Extensions_1.NodeName.AssetImage ?
|
17
|
+
image: currentImage?.type?.name === Extensions_1.NodeName.Image ? currentImageAttrs : {},
|
18
|
+
assetImage: currentImage?.type?.name === Extensions_1.NodeName.AssetImage ? currentImageAttrs : {},
|
18
19
|
};
|
19
20
|
return (react_1.default.createElement(FormModal_1.default, { title: "Image", onCancel: onCancel },
|
20
21
|
react_1.default.createElement(ImageForm_1.default, { data: formData, onSubmit: onSubmit })));
|
@@ -16,8 +16,8 @@ const LinkModal = ({ onCancel, onSubmit }) => {
|
|
16
16
|
const data = {
|
17
17
|
linkType: marks[0]?.type?.name === Extensions_1.MarkName.AssetLink ? Extensions_1.MarkName.AssetLink : Extensions_1.MarkName.Link,
|
18
18
|
text: selectedText,
|
19
|
-
link: marks.find((mark) => mark.type.name === 'link')?.attrs
|
20
|
-
assetLink: marks.find((mark) => mark.type.name === Extensions_1.MarkName.AssetLink)?.attrs
|
19
|
+
link: { ...marks.find((mark) => mark.type.name === 'link')?.attrs },
|
20
|
+
assetLink: { ...marks.find((mark) => mark.type.name === Extensions_1.MarkName.AssetLink)?.attrs },
|
21
21
|
range: { from: selection.from, to: selection.to },
|
22
22
|
};
|
23
23
|
return (react_1.default.createElement(FormModal_1.default, { title: "Link", onCancel: onCancel },
|
@@ -24,9 +24,13 @@ let PreformattedExtension = class PreformattedExtension extends core_1.NodeExten
|
|
24
24
|
attrs: {
|
25
25
|
...extra.defaults(),
|
26
26
|
},
|
27
|
-
parseDOM: [
|
27
|
+
parseDOM: [
|
28
|
+
{
|
29
|
+
tag: 'pre',
|
30
|
+
},
|
31
|
+
],
|
28
32
|
toDOM: (node) => {
|
29
|
-
return [
|
33
|
+
return ['pre', extra.dom(node), 0];
|
30
34
|
},
|
31
35
|
};
|
32
36
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@squiz/formatted-text-editor",
|
3
|
-
"version": "1.21.1-alpha.
|
3
|
+
"version": "1.21.1-alpha.43",
|
4
4
|
"main": "lib/index.js",
|
5
5
|
"types": "lib/index.d.ts",
|
6
6
|
"scripts": {
|
@@ -74,5 +74,5 @@
|
|
74
74
|
"volta": {
|
75
75
|
"node": "18.15.0"
|
76
76
|
},
|
77
|
-
"gitHead": "
|
77
|
+
"gitHead": "061e03f271b17ef1449370217bc5ee3dd92c0f62"
|
78
78
|
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import '@testing-library/jest-dom';
|
2
|
-
import { screen, fireEvent, waitForElementToBeRemoved, act } from '@testing-library/react';
|
2
|
+
import { screen, fireEvent, waitForElementToBeRemoved, act, waitFor } from '@testing-library/react';
|
3
3
|
import { NodeSelection } from 'prosemirror-state';
|
4
4
|
import React from 'react';
|
5
5
|
import { renderWithEditor, select } from '../../../../tests';
|
@@ -82,10 +82,43 @@ describe('ImageButton', () => {
|
|
82
82
|
await openModal();
|
83
83
|
fireEvent.change(screen.getByLabelText('Source'), { target: { value: 'https://httpcats.com/303.jpg' } });
|
84
84
|
fireEvent.change(screen.getByLabelText('Alternative description'), { target: { value: 'Updated cats!' } });
|
85
|
+
|
86
|
+
// wait for the new image dimensions to be calculated
|
87
|
+
await waitFor(() => expect(screen.getByLabelText('Height')).toHaveValue(2));
|
88
|
+
|
89
|
+
// verify the content matches what was initially set prior to applying
|
90
|
+
expect(getJsonContent()).toEqual({
|
91
|
+
type: 'paragraph',
|
92
|
+
attrs: expect.any(Object),
|
93
|
+
content: [
|
94
|
+
{
|
95
|
+
text: 'Some ',
|
96
|
+
type: 'text',
|
97
|
+
},
|
98
|
+
{
|
99
|
+
type: 'image',
|
100
|
+
attrs: {
|
101
|
+
alt: 'hi',
|
102
|
+
crop: null,
|
103
|
+
height: null,
|
104
|
+
width: null,
|
105
|
+
rotate: null,
|
106
|
+
src: 'https://httpcats.com/529.jpg',
|
107
|
+
title: '',
|
108
|
+
fileName: null,
|
109
|
+
resizable: false,
|
110
|
+
},
|
111
|
+
},
|
112
|
+
{ type: 'text', text: ' nonsense' },
|
113
|
+
],
|
114
|
+
});
|
115
|
+
|
116
|
+
// apply the changes
|
85
117
|
fireEvent.click(screen.getByRole('button', { name: 'Apply' }));
|
86
118
|
|
87
119
|
await waitForElementToBeRemoved(() => screen.getByRole('button', { name: 'Apply' }));
|
88
120
|
|
121
|
+
// asset the content has been updated
|
89
122
|
expect(getJsonContent()).toEqual({
|
90
123
|
type: 'paragraph',
|
91
124
|
attrs: expect.any(Object),
|
@@ -11,8 +11,8 @@ beforeEach(() => {
|
|
11
11
|
});
|
12
12
|
const mockSubmitFunction = jest.fn();
|
13
13
|
const mockCancelFunction = jest.fn();
|
14
|
-
const setup = () => {
|
15
|
-
const utils = renderWithEditor(<ImageModal onCancel={mockCancelFunction} onSubmit={mockSubmitFunction} />);
|
14
|
+
const setup = async () => {
|
15
|
+
const utils = await renderWithEditor(<ImageModal onCancel={mockCancelFunction} onSubmit={mockSubmitFunction} />);
|
16
16
|
const sourceInput = screen.getByRole('textbox', { name: /source/i }) as HTMLInputElement;
|
17
17
|
const altInput = screen.getByRole('textbox', { name: /alt/i }) as HTMLInputElement;
|
18
18
|
const widthInput = screen.getByRole('spinbutton', { name: /Width/i }) as HTMLInputElement;
|
@@ -34,7 +34,7 @@ describe('ImageModal', () => {
|
|
34
34
|
});
|
35
35
|
|
36
36
|
it('Populates the source field when a source is supplied', async () => {
|
37
|
-
const { sourceInput } = setup();
|
37
|
+
const { sourceInput } = await setup();
|
38
38
|
fireEvent.change(sourceInput, { target: { value: 'https://httpcats.com/302.jpg' } });
|
39
39
|
await waitFor(() => {
|
40
40
|
expect(sourceInput.value).toBe('https://httpcats.com/302.jpg');
|
@@ -42,7 +42,7 @@ describe('ImageModal', () => {
|
|
42
42
|
});
|
43
43
|
|
44
44
|
it('Renders empty width and height fields if the image source is empty', async () => {
|
45
|
-
const { sourceInput, widthInput, heightInput } = setup();
|
45
|
+
const { sourceInput, widthInput, heightInput } = await setup();
|
46
46
|
fireEvent.change(sourceInput, { target: { value: '' } });
|
47
47
|
await waitFor(() => {
|
48
48
|
expect(widthInput.value).toBe('');
|
@@ -51,7 +51,7 @@ describe('ImageModal', () => {
|
|
51
51
|
});
|
52
52
|
|
53
53
|
it('Updates the height field with aspect ratio based value from width', async () => {
|
54
|
-
const { widthInput, heightInput } = setup();
|
54
|
+
const { widthInput, heightInput } = await setup();
|
55
55
|
fireEvent.change(widthInput, { target: { value: '300' } });
|
56
56
|
await waitFor(() => {
|
57
57
|
expect(widthInput.value).toBe('300');
|
@@ -60,7 +60,7 @@ describe('ImageModal', () => {
|
|
60
60
|
});
|
61
61
|
|
62
62
|
it('Updates the width field with aspect ratio based value from height', async () => {
|
63
|
-
const { widthInput, heightInput } = setup();
|
63
|
+
const { widthInput, heightInput } = await setup();
|
64
64
|
fireEvent.change(heightInput, { target: { value: '100' } });
|
65
65
|
await waitFor(() => {
|
66
66
|
expect(heightInput.value).toBe('100');
|
@@ -68,8 +68,8 @@ describe('ImageModal', () => {
|
|
68
68
|
});
|
69
69
|
});
|
70
70
|
|
71
|
-
it('Does not change the width when height is changed and aspect ratio link is off', () => {
|
72
|
-
const { widthInput, heightInput } = setup();
|
71
|
+
it('Does not change the width when height is changed and aspect ratio link is off', async () => {
|
72
|
+
const { widthInput, heightInput } = await setup();
|
73
73
|
fireEvent.change(heightInput, { target: { value: '100' } });
|
74
74
|
expect(heightInput.value).toBe('100');
|
75
75
|
expect(widthInput.value).toBe('177.78');
|
@@ -79,8 +79,8 @@ describe('ImageModal', () => {
|
|
79
79
|
expect(widthInput.value).toBe('177.78');
|
80
80
|
});
|
81
81
|
|
82
|
-
it('Does not change the height when width is changed and aspect ratio link is off', () => {
|
83
|
-
const { widthInput, heightInput } = setup();
|
82
|
+
it('Does not change the height when width is changed and aspect ratio link is off', async () => {
|
83
|
+
const { widthInput, heightInput } = await setup();
|
84
84
|
fireEvent.change(widthInput, { target: { value: '450' } });
|
85
85
|
expect(widthInput.value).toBe('450');
|
86
86
|
expect(heightInput.value).toBe('253.13');
|
@@ -90,8 +90,8 @@ describe('ImageModal', () => {
|
|
90
90
|
expect(heightInput.value).toBe('253.13');
|
91
91
|
});
|
92
92
|
|
93
|
-
it('Changes the icon when aspect ratio button is toggled', () => {
|
94
|
-
|
93
|
+
it('Changes the icon when aspect ratio button is toggled', async () => {
|
94
|
+
await setup();
|
95
95
|
expect(screen.getByTestId('InsertLinkRoundedIcon')).toBeInTheDocument();
|
96
96
|
fireEvent.click(screen.getByRole('button', { name: /constrain properties/i }));
|
97
97
|
expect(screen.queryByTestId('InsertLinkRoundedIcon')).not.toBeInTheDocument();
|
@@ -99,7 +99,7 @@ describe('ImageModal', () => {
|
|
99
99
|
});
|
100
100
|
|
101
101
|
it('Returns relevant error message if the width is not higher than 0', async () => {
|
102
|
-
const { widthInput } = setup();
|
102
|
+
const { widthInput } = await setup();
|
103
103
|
|
104
104
|
fireEvent.change(widthInput, { target: { value: '0' } });
|
105
105
|
fireEvent.click(screen.getByRole('button', { name: /Apply/i }));
|
@@ -110,7 +110,7 @@ describe('ImageModal', () => {
|
|
110
110
|
});
|
111
111
|
|
112
112
|
it('Returns relevant error message if the height is not higher than 0', async () => {
|
113
|
-
const { heightInput } = setup();
|
113
|
+
const { heightInput } = await setup();
|
114
114
|
|
115
115
|
fireEvent.change(heightInput, { target: { value: '0' } });
|
116
116
|
fireEvent.click(screen.getByRole('button', { name: /Apply/i }));
|
@@ -14,10 +14,11 @@ type ImageModalProps = {
|
|
14
14
|
const ImageModal = ({ onCancel, onSubmit }: ImageModalProps) => {
|
15
15
|
const selection = useCurrentSelection() as NodeSelection;
|
16
16
|
const currentImage = selection?.node;
|
17
|
+
const currentImageAttrs = { ...currentImage?.attrs };
|
17
18
|
const formData = {
|
18
19
|
imageType: currentImage?.type.name === NodeName.AssetImage ? NodeName.AssetImage : NodeName.Image,
|
19
|
-
image: currentImage?.type?.name === NodeName.Image ?
|
20
|
-
assetImage: currentImage?.type?.name === NodeName.AssetImage ?
|
20
|
+
image: currentImage?.type?.name === NodeName.Image ? currentImageAttrs : {},
|
21
|
+
assetImage: currentImage?.type?.name === NodeName.AssetImage ? currentImageAttrs : {},
|
21
22
|
};
|
22
23
|
|
23
24
|
return (
|
@@ -58,6 +58,27 @@ describe('LinkButton', () => {
|
|
58
58
|
await openModal();
|
59
59
|
fireEvent.change(screen.getByLabelText('URL'), { target: { value: 'https://www.example.org/updated-link' } });
|
60
60
|
fireEvent.change(screen.getByLabelText('Text'), { target: { value: 'Updated sample link' } });
|
61
|
+
|
62
|
+
// verify the content matches what was initially set prior to applying.
|
63
|
+
expect(getJsonContent()).toEqual({
|
64
|
+
type: 'paragraph',
|
65
|
+
attrs: expect.any(Object),
|
66
|
+
content: [
|
67
|
+
{
|
68
|
+
type: 'text',
|
69
|
+
text: 'Sample link',
|
70
|
+
marks: [
|
71
|
+
{
|
72
|
+
type: 'link',
|
73
|
+
attrs: { href: 'https://www.example.org/my-link', target: '_self', title: null },
|
74
|
+
},
|
75
|
+
],
|
76
|
+
},
|
77
|
+
{ type: 'text', text: ' with some other content.' },
|
78
|
+
],
|
79
|
+
});
|
80
|
+
|
81
|
+
// apply the changes.
|
61
82
|
fireEvent.click(screen.getByRole('button', { name: 'Apply' }));
|
62
83
|
|
63
84
|
await waitForElementToBeRemoved(() => screen.getByRole('button', { name: 'Apply' }));
|
@@ -21,8 +21,8 @@ const LinkModal = ({ onCancel, onSubmit }: LinkModalProps) => {
|
|
21
21
|
const data = {
|
22
22
|
linkType: marks[0]?.type?.name === MarkName.AssetLink ? MarkName.AssetLink : MarkName.Link,
|
23
23
|
text: selectedText,
|
24
|
-
link: marks.find((mark) => mark.type.name === 'link')?.attrs
|
25
|
-
assetLink: marks.find((mark) => mark.type.name === MarkName.AssetLink)?.attrs
|
24
|
+
link: { ...marks.find((mark) => mark.type.name === 'link')?.attrs },
|
25
|
+
assetLink: { ...marks.find((mark) => mark.type.name === MarkName.AssetLink)?.attrs },
|
26
26
|
range: { from: selection.from, to: selection.to },
|
27
27
|
};
|
28
28
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import { renderWithEditor } from '../../../tests';
|
2
|
+
|
3
|
+
describe('AssetLinkExtension', () => {
|
4
|
+
it('Parses HTML content with preformatted text', async () => {
|
5
|
+
const { getJsonContent } = await renderWithEditor(null, {
|
6
|
+
content: `<pre>This is some preformatted text</pre>`,
|
7
|
+
});
|
8
|
+
|
9
|
+
expect(getJsonContent()).toEqual({
|
10
|
+
type: 'preformatted',
|
11
|
+
attrs: expect.any(Object),
|
12
|
+
content: [
|
13
|
+
{
|
14
|
+
type: 'text',
|
15
|
+
text: 'This is some preformatted text',
|
16
|
+
},
|
17
|
+
],
|
18
|
+
});
|
19
|
+
});
|
20
|
+
|
21
|
+
it('Outputs expected HTML', async () => {
|
22
|
+
const { getHtmlContent } = await renderWithEditor(null, {
|
23
|
+
content: {
|
24
|
+
type: 'doc',
|
25
|
+
content: [
|
26
|
+
{
|
27
|
+
type: 'preformatted',
|
28
|
+
content: [
|
29
|
+
{
|
30
|
+
type: 'text',
|
31
|
+
text: 'This is some preformatted text',
|
32
|
+
},
|
33
|
+
],
|
34
|
+
},
|
35
|
+
],
|
36
|
+
},
|
37
|
+
});
|
38
|
+
|
39
|
+
expect(getHtmlContent()).toBe('<pre style="">This is some preformatted text</pre>');
|
40
|
+
});
|
41
|
+
});
|
@@ -30,9 +30,13 @@ export class PreformattedExtension extends NodeExtension {
|
|
30
30
|
attrs: {
|
31
31
|
...extra.defaults(),
|
32
32
|
},
|
33
|
-
parseDOM: [
|
33
|
+
parseDOM: [
|
34
|
+
{
|
35
|
+
tag: 'pre',
|
36
|
+
},
|
37
|
+
],
|
34
38
|
toDOM: (node: ProsemirrorNode) => {
|
35
|
-
return [
|
39
|
+
return ['pre', extra.dom(node), 0];
|
36
40
|
},
|
37
41
|
};
|
38
42
|
}
|