@squiz/formatted-text-editor 1.34.1-alpha.1 → 1.34.1-alpha.10
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/FloatingToolbar.js +7 -1
- package/lib/EditorToolbar/Toolbar.js +4 -2
- package/lib/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.d.ts +2 -0
- package/lib/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.js +56 -0
- package/lib/EditorToolbar/Tools/Image/ImageButton.js +1 -1
- package/lib/EditorToolbar/Tools/Link/LinkButton.js +1 -1
- package/lib/EditorToolbar/Tools/TextAlign/TextAlignButtons.js +2 -2
- package/lib/EditorToolbar/Tools/TextType/CodeBlock/CodeBlockButton.d.ts +2 -0
- package/lib/EditorToolbar/Tools/TextType/CodeBlock/CodeBlockButton.js +22 -0
- package/lib/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.js +3 -2
- package/lib/EditorToolbar/Tools/TextType/TextTypeDropdown.js +8 -2
- package/lib/Extensions/ClearFormattingExtension/ClearFormattingExtension.d.ts +5 -0
- package/lib/Extensions/ClearFormattingExtension/ClearFormattingExtension.js +63 -0
- package/lib/Extensions/CodeBlockExtension/CodeBlockExtension.d.ts +5 -0
- package/lib/Extensions/CodeBlockExtension/CodeBlockExtension.js +30 -0
- package/lib/Extensions/Extensions.d.ts +1 -0
- package/lib/Extensions/Extensions.js +5 -0
- package/lib/Extensions/PreformattedExtension/PreformattedExtension.d.ts +2 -0
- package/lib/Extensions/PreformattedExtension/PreformattedExtension.js +23 -0
- package/lib/index.css +50 -9
- package/lib/ui/ToolbarDropdownButton/ToolbarDropdownButton.d.ts +2 -1
- package/lib/ui/ToolbarDropdownButton/ToolbarDropdownButton.js +6 -4
- package/lib/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.js +7 -2
- package/lib/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.js +7 -0
- package/lib/utils/getMarkNamesByGroup.d.ts +2 -0
- package/lib/utils/getMarkNamesByGroup.js +9 -0
- package/lib/utils/getNodeNamesByGroup.d.ts +2 -0
- package/lib/utils/getNodeNamesByGroup.js +9 -0
- package/package.json +4 -4
- package/postcss.config.js +1 -1
- package/src/EditorToolbar/FloatingToolbar.tsx +10 -5
- package/src/EditorToolbar/Toolbar.tsx +3 -1
- package/src/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.spec.tsx +34 -0
- package/src/EditorToolbar/Tools/ClearFormatting/ClearFormattingButton.tsx +45 -0
- package/src/EditorToolbar/Tools/Image/ImageButton.tsx +3 -2
- package/src/EditorToolbar/Tools/Link/LinkButton.tsx +3 -2
- package/src/EditorToolbar/Tools/TextAlign/TextAlignButtons.tsx +2 -2
- package/src/EditorToolbar/Tools/TextType/CodeBlock/CodeBlockButton.spec.tsx +47 -0
- package/src/EditorToolbar/Tools/TextType/CodeBlock/CodeBlockButton.tsx +32 -0
- package/src/EditorToolbar/Tools/TextType/Heading/HeadingButton.spec.tsx +2 -2
- package/src/EditorToolbar/Tools/TextType/Paragraph/ParagraphButton.spec.tsx +1 -1
- package/src/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.spec.tsx +2 -2
- package/src/EditorToolbar/Tools/TextType/Preformatted/PreformattedButton.tsx +3 -1
- package/src/EditorToolbar/Tools/TextType/TextTypeDropdown.tsx +12 -2
- package/src/EditorToolbar/_floating-toolbar.scss +1 -1
- package/src/EditorToolbar/_toolbar.scss +2 -2
- package/src/Extensions/ClearFormattingExtension/ClearFormattingExtension.ts +57 -0
- package/src/Extensions/CodeBlockExtension/CodeBlockExtension.ts +34 -0
- package/src/Extensions/Extensions.ts +5 -0
- package/src/Extensions/PreformattedExtension/PreformattedExtension.spec.ts +4 -2
- package/src/Extensions/PreformattedExtension/PreformattedExtension.ts +31 -0
- package/src/index.scss +3 -0
- package/src/ui/ToolbarDropdownButton/ToolbarDropdownButton.tsx +8 -4
- package/src/ui/ToolbarDropdownButton/_toolbar-dropdown-button.scss +11 -0
- package/src/ui/_typography.scss +26 -0
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.spec.ts +37 -0
- package/src/utils/converters/remirrorNodeToSquizNode/remirrorNodeToSquizNode.ts +10 -2
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.spec.ts +39 -2
- package/src/utils/converters/squizNodeToRemirrorNode/squizNodeToRemirrorNode.ts +6 -0
- package/src/utils/getMarkNamesByGroup.spec.ts +20 -0
- package/src/utils/getMarkNamesByGroup.ts +7 -0
- package/src/utils/getNodeNamesByGroup.spec.ts +20 -0
- package/src/utils/getNodeNamesByGroup.ts +7 -0
- package/tailwind.config.cjs +4 -3
@@ -1,5 +1,5 @@
|
|
1
1
|
import React from 'react';
|
2
|
-
import
|
2
|
+
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
|
3
3
|
|
4
4
|
type DropdownButtonProps = {
|
5
5
|
children?: JSX.Element;
|
@@ -7,9 +7,10 @@ type DropdownButtonProps = {
|
|
7
7
|
isDisabled: boolean;
|
8
8
|
isActive: boolean;
|
9
9
|
label: string;
|
10
|
+
icon?: JSX.Element;
|
10
11
|
};
|
11
12
|
|
12
|
-
const DropdownButton = ({ children, handleOnClick, isDisabled, isActive, label }: DropdownButtonProps) => {
|
13
|
+
const DropdownButton = ({ children, handleOnClick, isDisabled, isActive, label, icon }: DropdownButtonProps) => {
|
13
14
|
return (
|
14
15
|
<button
|
15
16
|
aria-label={label}
|
@@ -20,8 +21,11 @@ const DropdownButton = ({ children, handleOnClick, isDisabled, isActive, label }
|
|
20
21
|
disabled={isDisabled}
|
21
22
|
className={`btn dropdown-button ${isActive ? 'is-active' : ''}`}
|
22
23
|
>
|
23
|
-
<
|
24
|
-
|
24
|
+
<div className="dropdown-button-label">
|
25
|
+
{icon && icon}
|
26
|
+
{children || label}
|
27
|
+
</div>
|
28
|
+
{isActive && <CheckRoundedIcon className="dropdown-button-icon" />}
|
25
29
|
</button>
|
26
30
|
);
|
27
31
|
};
|
package/src/ui/_typography.scss
CHANGED
@@ -44,3 +44,29 @@ h6 {
|
|
44
44
|
letter-spacing: -0.2px;
|
45
45
|
line-height: 1.25rem;
|
46
46
|
}
|
47
|
+
|
48
|
+
.code-block,
|
49
|
+
.preformatted {
|
50
|
+
display: flex;
|
51
|
+
|
52
|
+
.material-symbols-rounded {
|
53
|
+
@apply text-gray-700 text-xlg;
|
54
|
+
pointer-events: none;
|
55
|
+
margin-right: 0.375rem;
|
56
|
+
}
|
57
|
+
|
58
|
+
.block-divider {
|
59
|
+
@apply bg-gray-200;
|
60
|
+
width: 4px;
|
61
|
+
border-radius: 0.75rem;
|
62
|
+
margin-right: 0.625rem;
|
63
|
+
}
|
64
|
+
|
65
|
+
code,
|
66
|
+
pre {
|
67
|
+
@apply text-gray-700;
|
68
|
+
font-size: 0.75rem;
|
69
|
+
padding-top: 0.75rem;
|
70
|
+
padding-bottom: 0.75rem;
|
71
|
+
}
|
72
|
+
}
|
@@ -74,6 +74,43 @@ describe('remirrorNodeToSquizNode', () => {
|
|
74
74
|
expect(result).toEqual(expected);
|
75
75
|
});
|
76
76
|
|
77
|
+
it('should handle code block formatting', async () => {
|
78
|
+
const content: RemirrorJSON = {
|
79
|
+
type: 'doc',
|
80
|
+
content: [
|
81
|
+
{
|
82
|
+
type: 'codeBlock',
|
83
|
+
attrs: {
|
84
|
+
language: 'js',
|
85
|
+
wrap: true,
|
86
|
+
},
|
87
|
+
content: [
|
88
|
+
{
|
89
|
+
type: 'text',
|
90
|
+
text: 'Hello world',
|
91
|
+
},
|
92
|
+
],
|
93
|
+
},
|
94
|
+
],
|
95
|
+
};
|
96
|
+
|
97
|
+
const { editor } = await renderWithEditor(null, { content });
|
98
|
+
|
99
|
+
const expected: FormattedText = [
|
100
|
+
{
|
101
|
+
type: 'tag',
|
102
|
+
tag: 'code',
|
103
|
+
children: [{ type: 'text', value: 'Hello world' }],
|
104
|
+
attributes: {
|
105
|
+
language: 'js',
|
106
|
+
},
|
107
|
+
},
|
108
|
+
];
|
109
|
+
|
110
|
+
const result = remirrorNodeToSquizNode(editor.doc);
|
111
|
+
expect(result).toEqual(expected);
|
112
|
+
});
|
113
|
+
|
77
114
|
it('should handle images', async () => {
|
78
115
|
const content: RemirrorJSON = {
|
79
116
|
type: 'doc',
|
@@ -16,10 +16,14 @@ type FormattedNodeFontProperties = FormattedTextModels.v1.FormattedNodeFontPrope
|
|
16
16
|
type FormattedNodeWithChildren = Extract<FormattedNode, { children: FormattedNode[] }>;
|
17
17
|
|
18
18
|
export const resolveNodeTag = (node: ProsemirrorNode): string => {
|
19
|
-
if (node.type.name ===
|
19
|
+
if (node.type.name === NodeName.Text) {
|
20
20
|
return 'span';
|
21
21
|
}
|
22
22
|
|
23
|
+
if (node.type.name === NodeName.CodeBlock) {
|
24
|
+
return 'code';
|
25
|
+
}
|
26
|
+
|
23
27
|
if (node.type.spec?.toDOM) {
|
24
28
|
const domNode = node.type.spec.toDOM(node);
|
25
29
|
|
@@ -92,9 +96,13 @@ const transformFragment = (fragment: Fragment): FormattedText => {
|
|
92
96
|
};
|
93
97
|
|
94
98
|
const transformNode = (node: ProsemirrorNode): FormattedNode => {
|
95
|
-
const attributes = node.type.name === NodeName.Image ? transformAttributes(node.attrs) : undefined;
|
96
99
|
const formattingOptions = undefinedIfEmpty(resolveFormattingOptions(node));
|
97
100
|
const font = undefinedIfEmpty(resolveFontOptions(node));
|
101
|
+
const attributes =
|
102
|
+
node.type.name === NodeName.Image || node.type.name === NodeName.CodeBlock
|
103
|
+
? transformAttributes(node.attrs)
|
104
|
+
: undefined;
|
105
|
+
|
98
106
|
let transformedNode: FormattedNode = { type: 'text', value: node.text || '' };
|
99
107
|
|
100
108
|
// Squiz "text" nodes can't have formatting/font options but Remirror "text" nodes can.
|
@@ -139,10 +139,10 @@ describe('squizNodeToRemirrorNode', () => {
|
|
139
139
|
},
|
140
140
|
],
|
141
141
|
type: 'tag',
|
142
|
-
tag: '
|
142
|
+
tag: 'video',
|
143
143
|
},
|
144
144
|
],
|
145
|
-
'Unsupported node type provided: tag (tag:
|
145
|
+
'Unsupported node type provided: tag (tag: video)',
|
146
146
|
],
|
147
147
|
[
|
148
148
|
'Unsupported node type',
|
@@ -192,6 +192,43 @@ describe('squizNodeToRemirrorNode', () => {
|
|
192
192
|
expect(result).toEqual(expected);
|
193
193
|
});
|
194
194
|
|
195
|
+
it('should handle code block text', () => {
|
196
|
+
const squizComponentJSON: FormattedText = [
|
197
|
+
{
|
198
|
+
children: [
|
199
|
+
{
|
200
|
+
type: 'text',
|
201
|
+
value: 'Hello world!',
|
202
|
+
},
|
203
|
+
],
|
204
|
+
type: 'tag',
|
205
|
+
tag: 'code',
|
206
|
+
},
|
207
|
+
];
|
208
|
+
|
209
|
+
const expected: RemirrorJSON = {
|
210
|
+
content: [
|
211
|
+
{
|
212
|
+
type: 'codeBlock',
|
213
|
+
attrs: {
|
214
|
+
language: 'markup',
|
215
|
+
wrap: true,
|
216
|
+
},
|
217
|
+
content: [
|
218
|
+
{
|
219
|
+
type: 'text',
|
220
|
+
text: 'Hello world!',
|
221
|
+
},
|
222
|
+
],
|
223
|
+
},
|
224
|
+
],
|
225
|
+
type: 'doc',
|
226
|
+
};
|
227
|
+
|
228
|
+
const result = squizNodeToRemirrorNode(squizComponentJSON);
|
229
|
+
expect(result).toEqual(expected);
|
230
|
+
});
|
231
|
+
|
195
232
|
it('should handle images', () => {
|
196
233
|
const squizComponentJSON: FormattedText = [
|
197
234
|
{
|
@@ -26,6 +26,7 @@ const getNodeType = (node: FormattedNodes): string => {
|
|
26
26
|
p: 'paragraph',
|
27
27
|
a: NodeName.Text,
|
28
28
|
span: NodeName.Text,
|
29
|
+
code: NodeName.CodeBlock,
|
29
30
|
};
|
30
31
|
|
31
32
|
if (typeMap[node.type]) {
|
@@ -53,6 +54,11 @@ const getNodeAttributes = (node: FormattedNodes): Attrs => {
|
|
53
54
|
src: node.attributes?.src,
|
54
55
|
title: node.attributes?.title,
|
55
56
|
};
|
57
|
+
} else if (node.type === 'tag' && node.tag === 'code') {
|
58
|
+
return {
|
59
|
+
language: node.attributes?.language || 'markup',
|
60
|
+
wrap: node.attributes?.wrap || true,
|
61
|
+
};
|
56
62
|
} else if (node.type === 'matrix-image') {
|
57
63
|
return {
|
58
64
|
matrixAssetId: node.matrixAssetId,
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { EditorSchema, ExtensionTag } from '@remirror/core';
|
2
|
+
import { renderWithEditor } from '../../tests';
|
3
|
+
import { getMarkNamesByGroup } from './getMarkNamesByGroup';
|
4
|
+
|
5
|
+
describe('getMarkNamesGroup', () => {
|
6
|
+
it('Should return expected extensions with "FormattingMark" tag', async () => {
|
7
|
+
const { editor } = await renderWithEditor(null);
|
8
|
+
const schema: EditorSchema = editor.schema;
|
9
|
+
const formattingMarkNames = getMarkNamesByGroup(schema, ExtensionTag.FormattingMark);
|
10
|
+
const otherMarkNames = Object.values(schema.marks)
|
11
|
+
.map((mark) => mark.name)
|
12
|
+
.filter((mark) => !formattingMarkNames.includes(mark));
|
13
|
+
|
14
|
+
// This tag is used by the clear formatting extension.
|
15
|
+
// Marks in the first array will be transformed to a paragraph when formatting is cleared.
|
16
|
+
// Mark in the second array will be left as-is.
|
17
|
+
expect(formattingMarkNames).toEqual(['bold', 'italic', 'underline']);
|
18
|
+
expect(otherMarkNames).toEqual(['assetLink', 'link']);
|
19
|
+
});
|
20
|
+
});
|
@@ -0,0 +1,7 @@
|
|
1
|
+
import { EditorSchema, ExtensionTagType } from '@remirror/core';
|
2
|
+
|
3
|
+
export const getMarkNamesByGroup = (schema: EditorSchema, group: ExtensionTagType): string[] => {
|
4
|
+
return Object.values(schema.marks)
|
5
|
+
.filter((mark) => mark.spec.group?.includes(group))
|
6
|
+
.map((mark) => mark.name);
|
7
|
+
};
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { EditorSchema, ExtensionTag } from '@remirror/core';
|
2
|
+
import { renderWithEditor } from '../../tests';
|
3
|
+
import { getNodeNamesByGroup } from './getNodeNamesByGroup';
|
4
|
+
|
5
|
+
describe('getNodeNamesByGroup', () => {
|
6
|
+
it('Should return expected extensions with "FormattingNode" tag', async () => {
|
7
|
+
const { editor } = await renderWithEditor(null);
|
8
|
+
const schema: EditorSchema = editor.schema;
|
9
|
+
const formattingNodeNames = getNodeNamesByGroup(schema, ExtensionTag.FormattingNode);
|
10
|
+
const otherNodeNames = Object.values(schema.nodes)
|
11
|
+
.map((node) => node.name)
|
12
|
+
.filter((node) => !formattingNodeNames.includes(node));
|
13
|
+
|
14
|
+
// This tag is used by the clear formatting extension.
|
15
|
+
// Nodes in the first array will be transformed to a paragraph when formatting is cleared.
|
16
|
+
// Nodes in the second array will be left as-is.
|
17
|
+
expect(formattingNodeNames).toEqual(['paragraph', 'heading', 'preformatted']);
|
18
|
+
expect(otherNodeNames).toEqual(['assetImage', 'doc', 'text', 'codeBlock', 'image']);
|
19
|
+
});
|
20
|
+
});
|
@@ -0,0 +1,7 @@
|
|
1
|
+
import { EditorSchema, ExtensionTagType } from '@remirror/core';
|
2
|
+
|
3
|
+
export const getNodeNamesByGroup = (schema: EditorSchema, group: ExtensionTagType): string[] => {
|
4
|
+
return Object.values(schema.nodes)
|
5
|
+
.filter((node) => node.spec.group?.includes(group))
|
6
|
+
.map((node) => node.name);
|
7
|
+
};
|
package/tailwind.config.cjs
CHANGED
@@ -27,9 +27,6 @@ module.exports = {
|
|
27
27
|
'heading-3': ['1.125rem', '1.375rem'],
|
28
28
|
'heading-4': ['1rem', '1.25rem'],
|
29
29
|
},
|
30
|
-
fontFamily: {
|
31
|
-
base: 'Open Sans, Arial, sans-serif',
|
32
|
-
},
|
33
30
|
boxShadow: {
|
34
31
|
outline: '0 0 0 1px rgba(0,0,0,0.10)',
|
35
32
|
sm: '0 0 0 1px rgba(0,0,0,0.04), 0 1px 4px 2px rgba(0,0,0,0.08)',
|
@@ -75,6 +72,10 @@ module.exports = {
|
|
75
72
|
300: '#0774d2',
|
76
73
|
400: '#044985',
|
77
74
|
},
|
75
|
+
teal: {
|
76
|
+
100: '#E6F4F6',
|
77
|
+
400: '#024752',
|
78
|
+
},
|
78
79
|
red: {
|
79
80
|
300: '#d72321',
|
80
81
|
},
|