@pie-lib/editable-html-tip-tap 2.1.19 → 2.1.20-beta.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/CHANGELOG.md +0 -130
- package/lib/__tests__/EditableHtml.test.js +831 -0
- package/lib/__tests__/constants.test.js +21 -0
- package/lib/__tests__/div-to-paragraph-conversion.test.js +213 -0
- package/lib/__tests__/extensions.test.js +209 -0
- package/lib/__tests__/index.test.js +250 -0
- package/lib/__tests__/size-utils.test.js +57 -0
- package/lib/__tests__/spellcheck.test.js +142 -0
- package/lib/__tests__/theme.test.js +17 -0
- package/lib/components/EditableHtml.js +0 -56
- package/lib/components/EditableHtml.js.map +1 -1
- package/lib/components/MenuBar.js +12 -81
- package/lib/components/MenuBar.js.map +1 -1
- package/lib/components/TiptapContainer.js +2 -2
- package/lib/components/TiptapContainer.js.map +1 -1
- package/lib/components/__tests__/AltDialog.test.js +201 -0
- package/lib/components/__tests__/CharacterPicker.test.js +455 -0
- package/lib/components/__tests__/CssIcon.test.js +58 -0
- package/lib/components/__tests__/DragInTheBlank.test.js +295 -0
- package/lib/components/__tests__/ExplicitConstructedResponse.test.js +345 -0
- package/lib/components/__tests__/ImageToolbar.test.js +185 -0
- package/lib/components/__tests__/InlineDropdown.test.js +839 -0
- package/lib/components/__tests__/InsertImageHandler.test.js +171 -0
- package/lib/components/__tests__/MediaDialog.test.js +433 -0
- package/lib/components/__tests__/MediaToolbar.test.js +126 -0
- package/lib/components/__tests__/MediaWrapper.test.js +96 -0
- package/lib/components/__tests__/MenuBar.test.js +502 -0
- package/lib/components/__tests__/RespArea.test.js +171 -0
- package/lib/components/__tests__/TableIcons.test.js +153 -0
- package/lib/components/__tests__/TextAlign.test.js +209 -0
- package/lib/components/__tests__/TiptapContainer.test.js +196 -0
- package/lib/components/__tests__/characterUtils.test.js +178 -0
- package/lib/components/__tests__/choice.test.js +213 -0
- package/lib/components/__tests__/custom-popper.test.js +108 -0
- package/lib/components/__tests__/done-button.test.js +72 -0
- package/lib/components/__tests__/toolbar-buttons.test.js +277 -0
- package/lib/components/common/toolbar-buttons.js +5 -6
- package/lib/components/common/toolbar-buttons.js.map +1 -1
- package/lib/components/icons/TableIcons.js +1 -3
- package/lib/components/icons/TableIcons.js.map +1 -1
- package/lib/components/icons/TextAlign.js +5 -14
- package/lib/components/icons/TextAlign.js.map +1 -1
- package/lib/components/respArea/DragInTheBlank/__tests__/choice.test.js +90 -0
- package/lib/components/respArea/MathTemplated.js +8 -14
- package/lib/components/respArea/MathTemplated.js.map +1 -1
- package/lib/components/respArea/__tests__/MathTemplated.test.js +217 -0
- package/lib/extensions/__tests__/css.test.js +214 -0
- package/lib/extensions/__tests__/custom-toolbar-wrapper.test.js +175 -0
- package/lib/extensions/__tests__/divNode.test.js +108 -0
- package/lib/extensions/__tests__/ensure-empty-root-div.test.js +50 -0
- package/lib/extensions/__tests__/ensure-list-item-content-is-div.test.js +60 -0
- package/lib/extensions/__tests__/extended-list-item.test.js +13 -0
- package/lib/extensions/__tests__/extended-table-cell.test.js +15 -0
- package/lib/extensions/__tests__/extended-table.test.js +200 -0
- package/lib/extensions/__tests__/image-component.test.js +432 -0
- package/lib/extensions/__tests__/image.test.js +313 -0
- package/lib/extensions/__tests__/math.test.js +1411 -0
- package/lib/extensions/__tests__/media-node-view.test.js +307 -0
- package/lib/extensions/__tests__/media.test.js +292 -0
- package/lib/extensions/__tests__/responseArea.test.js +736 -0
- package/lib/extensions/math.js +37 -19
- package/lib/extensions/math.js.map +1 -1
- package/lib/styles/editorContainerStyles.js +2 -2
- package/lib/styles/editorContainerStyles.js.map +1 -1
- package/lib/utils/__tests__/helper.test.js +107 -0
- package/lib/utils/__tests__/toolbar.test.js +38 -0
- package/package.json +9 -9
- package/src/__tests__/EditableHtml.test.jsx +0 -80
- package/src/components/EditableHtml.jsx +0 -52
- package/src/components/MenuBar.jsx +9 -91
- package/src/components/TiptapContainer.jsx +2 -2
- package/src/components/__tests__/MenuBar.test.jsx +9 -97
- package/src/components/__tests__/toolbar-buttons.test.jsx +2 -15
- package/src/components/common/toolbar-buttons.jsx +4 -6
- package/src/components/icons/TableIcons.jsx +1 -9
- package/src/components/icons/TextAlign.jsx +5 -39
- package/src/components/respArea/MathTemplated.jsx +6 -13
- package/src/components/respArea/__tests__/MathTemplated.test.jsx +1 -1
- package/src/extensions/__tests__/math.test.js +11 -9
- package/src/extensions/math.js +19 -11
- package/src/styles/editorContainerStyles.js +2 -2
- package/LICENSE.md +0 -5
- package/src/__tests__/editor-attributes.test.jsx +0 -77
- package/src/__tests__/spellcheck.test.jsx +0 -63
|
@@ -11,7 +11,6 @@ jest.mock('@tiptap/react', () => ({
|
|
|
11
11
|
const mockEditor = {
|
|
12
12
|
getHTML: jest.fn(() => '<p>test content</p>'),
|
|
13
13
|
setEditable: jest.fn(),
|
|
14
|
-
setOptions: jest.fn(),
|
|
15
14
|
commands: {
|
|
16
15
|
setContent: jest.fn(),
|
|
17
16
|
},
|
|
@@ -586,83 +585,4 @@ describe('EditableHtml', () => {
|
|
|
586
585
|
}).not.toThrow();
|
|
587
586
|
});
|
|
588
587
|
});
|
|
589
|
-
|
|
590
|
-
describe('spellCheck', () => {
|
|
591
|
-
const lastEditorAttributes = () => {
|
|
592
|
-
const editorConfig = useEditor.mock.calls[useEditor.mock.calls.length - 1][0];
|
|
593
|
-
|
|
594
|
-
return editorConfig.editorProps.attributes;
|
|
595
|
-
};
|
|
596
|
-
|
|
597
|
-
it('disables browser spellcheck on the contenteditable when spellCheck is false', async () => {
|
|
598
|
-
render(<EditableHtml {...defaultProps} spellCheck={false} />);
|
|
599
|
-
|
|
600
|
-
await waitFor(() => {
|
|
601
|
-
expect(useEditor).toHaveBeenCalled();
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
expect(lastEditorAttributes()).toEqual({
|
|
605
|
-
role: 'textbox',
|
|
606
|
-
'data-pie-editor': 'true',
|
|
607
|
-
spellcheck: 'false',
|
|
608
|
-
autocorrect: 'off',
|
|
609
|
-
autocapitalize: 'off',
|
|
610
|
-
});
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
it('enables browser spellcheck when spellCheck is true', async () => {
|
|
614
|
-
render(<EditableHtml {...defaultProps} spellCheck={true} />);
|
|
615
|
-
|
|
616
|
-
await waitFor(() => {
|
|
617
|
-
expect(useEditor).toHaveBeenCalled();
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
expect(lastEditorAttributes()).toEqual({
|
|
621
|
-
role: 'textbox',
|
|
622
|
-
'data-pie-editor': 'true',
|
|
623
|
-
spellcheck: 'true',
|
|
624
|
-
autocorrect: 'on',
|
|
625
|
-
autocapitalize: 'on',
|
|
626
|
-
});
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
it('keeps the browser default when spellCheck is not provided', async () => {
|
|
630
|
-
render(<EditableHtml {...defaultProps} />);
|
|
631
|
-
|
|
632
|
-
await waitFor(() => {
|
|
633
|
-
expect(useEditor).toHaveBeenCalled();
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
expect(lastEditorAttributes().spellcheck).toEqual('true');
|
|
637
|
-
});
|
|
638
|
-
|
|
639
|
-
it('pushes spellCheck changes onto an existing editor', async () => {
|
|
640
|
-
const editorRef = jest.fn();
|
|
641
|
-
const { rerender } = render(<EditableHtml {...defaultProps} spellCheck={true} editorRef={editorRef} />);
|
|
642
|
-
|
|
643
|
-
await waitFor(() => {
|
|
644
|
-
expect(editorRef).toHaveBeenCalled();
|
|
645
|
-
});
|
|
646
|
-
|
|
647
|
-
rerender(<EditableHtml {...defaultProps} spellCheck={false} editorRef={editorRef} />);
|
|
648
|
-
|
|
649
|
-
await waitFor(() => {
|
|
650
|
-
expect(lastEditorAttributes().spellcheck).toEqual('false');
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
const editor = editorRef.mock.calls[editorRef.mock.calls.length - 1][0];
|
|
654
|
-
|
|
655
|
-
expect(editor.setOptions).toHaveBeenCalledWith({
|
|
656
|
-
editorProps: expect.objectContaining({
|
|
657
|
-
attributes: {
|
|
658
|
-
role: 'textbox',
|
|
659
|
-
'data-pie-editor': 'true',
|
|
660
|
-
spellcheck: 'false',
|
|
661
|
-
autocorrect: 'off',
|
|
662
|
-
autocapitalize: 'off',
|
|
663
|
-
},
|
|
664
|
-
}),
|
|
665
|
-
});
|
|
666
|
-
});
|
|
667
|
-
});
|
|
668
588
|
});
|
|
@@ -96,25 +96,6 @@ const cssVariables = {
|
|
|
96
96
|
0px 3.618px 9.949px 0px rgba(0, 0, 0, .04)`,
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
-
// Attributes PIE puts on the editor's contenteditable element.
|
|
100
|
-
//
|
|
101
|
-
// ProseMirror rebuilds the element's attribute set from editorProps.attributes on every view
|
|
102
|
-
// update and removes anything no longer listed, and Tiptap merges its own role="textbox" only in
|
|
103
|
-
// createView - not into editor.options, which is what setOptions re-pushes. So every attribute we
|
|
104
|
-
// want to survive has to be listed here. See PIE-1015.
|
|
105
|
-
const editorAttributes = (spellCheckEnabled) => ({
|
|
106
|
-
// Tiptap's own attribute, re-declared so it survives setOptions. setEditable alone re-pushes
|
|
107
|
-
// editorProps on every mount, which is what removed it in 2.1.17.
|
|
108
|
-
role: 'textbox',
|
|
109
|
-
// PIE-owned hook for external consumers - see PIE-1016. Query [data-pie-editor], or
|
|
110
|
-
// [data-pie-editor][contenteditable="true"] to skip read-only renders.
|
|
111
|
-
'data-pie-editor': 'true',
|
|
112
|
-
// Without these the browser spellchecks the editor regardless of the spellCheck prop.
|
|
113
|
-
spellcheck: spellCheckEnabled ? 'true' : 'false',
|
|
114
|
-
autocorrect: spellCheckEnabled ? 'on' : 'off',
|
|
115
|
-
autocapitalize: spellCheckEnabled ? 'on' : 'off',
|
|
116
|
-
});
|
|
117
|
-
|
|
118
99
|
export const EditableHtml = (props) => {
|
|
119
100
|
const { showParagraphs, separateParagraphs } = props.pluginProps || {};
|
|
120
101
|
const [pendingImages, setPendingImages] = useState([]);
|
|
@@ -209,13 +190,6 @@ export const EditableHtml = (props) => {
|
|
|
209
190
|
node: 'paragraph',
|
|
210
191
|
notAfter: ['paragraph', 'div'],
|
|
211
192
|
},
|
|
212
|
-
// StarterKit bundles the link extension, which turns anything that looks like a URL into
|
|
213
|
-
// a hyperlink as it is typed or pasted. See PIE-980 - we don't want that in any editor.
|
|
214
|
-
// The mark itself stays registered so links already in the markup keep round-tripping.
|
|
215
|
-
link: {
|
|
216
|
-
autolink: false,
|
|
217
|
-
linkOnPaste: false,
|
|
218
|
-
},
|
|
219
193
|
}),
|
|
220
194
|
ExtendedListItem,
|
|
221
195
|
DivNode,
|
|
@@ -321,17 +295,11 @@ export const EditableHtml = (props) => {
|
|
|
321
295
|
}),
|
|
322
296
|
];
|
|
323
297
|
|
|
324
|
-
// Callers that don't pass spellCheck keep the browser default (on), as they did with the
|
|
325
|
-
// slate-based editor. Players that have to default to off - extended-text-entry, see
|
|
326
|
-
// PIE-978 - resolve that in their controller and pass an explicit false.
|
|
327
|
-
const spellCheckEnabled = props.spellCheck !== false;
|
|
328
|
-
|
|
329
298
|
const editor = useEditor(
|
|
330
299
|
{
|
|
331
300
|
extensions,
|
|
332
301
|
immediatelyRender: false,
|
|
333
302
|
editorProps: {
|
|
334
|
-
attributes: editorAttributes(spellCheckEnabled),
|
|
335
303
|
handleKeyDown(view, event) {
|
|
336
304
|
if (props.onKeyDown) {
|
|
337
305
|
return props.onKeyDown(event);
|
|
@@ -375,26 +343,6 @@ export const EditableHtml = (props) => {
|
|
|
375
343
|
editor?.setEditable(!props.disabled);
|
|
376
344
|
}, [props.disabled, editor]);
|
|
377
345
|
|
|
378
|
-
// useEditor only re-applies options on its own when it is called with an empty dependency
|
|
379
|
-
// array, and this call site depends on charactersLimit, so a spellCheck change on a mounted
|
|
380
|
-
// editor has to be pushed in. The rest of editorProps stays as the editor already has it, and
|
|
381
|
-
// the attributes are merged rather than replaced so an attribute contributed by Tiptap or an
|
|
382
|
-
// extension can't be dropped the way role="textbox" was - see PIE-1015.
|
|
383
|
-
useEffect(() => {
|
|
384
|
-
if (!editor) {
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const currentEditorProps = editor.options?.editorProps;
|
|
389
|
-
const attributes = { ...currentEditorProps?.attributes, ...editorAttributes(spellCheckEnabled) };
|
|
390
|
-
|
|
391
|
-
if (currentEditorProps?.attributes?.spellcheck === attributes.spellcheck) {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
editor.setOptions({ editorProps: { ...currentEditorProps, attributes } });
|
|
396
|
-
}, [spellCheckEnabled, editor]);
|
|
397
|
-
|
|
398
346
|
useEffect(() => {
|
|
399
347
|
if (!editor) {
|
|
400
348
|
return;
|
|
@@ -2,7 +2,6 @@ import React, { useMemo, useState } from 'react';
|
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { styled, useTheme } from '@mui/material/styles';
|
|
4
4
|
import { NodeSelection } from 'prosemirror-state';
|
|
5
|
-
import { color } from '@pie-lib/render-ui';
|
|
6
5
|
|
|
7
6
|
import Bold from '@mui/icons-material/FormatBold';
|
|
8
7
|
import Italic from '@mui/icons-material/FormatItalic';
|
|
@@ -35,15 +34,7 @@ import { CharacterIcon, CharacterPicker } from './CharacterPicker';
|
|
|
35
34
|
import { DoneButton } from './common/done-button';
|
|
36
35
|
|
|
37
36
|
const SuperscriptIcon = () => (
|
|
38
|
-
<svg
|
|
39
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
40
|
-
height="24px"
|
|
41
|
-
viewBox="0 0 24 24"
|
|
42
|
-
width="24px"
|
|
43
|
-
fill="none"
|
|
44
|
-
aria-hidden="true"
|
|
45
|
-
focusable="false"
|
|
46
|
-
>
|
|
37
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="none">
|
|
47
38
|
<path
|
|
48
39
|
d="M22,7h-2v1h3v1h-4V7c0-0.55,0.45-1,1-1h2V5h-3V4h3c0.55,0,1,0.45,1,1v1C23,6.55,22.55,7,22,7z M5.88,20h2.66l3.4-5.42h0.12 l3.4,5.42h2.66l-4.65-7.27L17.81,6h-2.68l-3.07,4.99h-0.12L8.85,6H6.19l4.32,6.73L5.88,20z"
|
|
49
40
|
fill="currentColor"
|
|
@@ -52,15 +43,7 @@ const SuperscriptIcon = () => (
|
|
|
52
43
|
);
|
|
53
44
|
|
|
54
45
|
const SubscriptIcon = () => (
|
|
55
|
-
<svg
|
|
56
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
57
|
-
height="24px"
|
|
58
|
-
viewBox="0 0 24 24"
|
|
59
|
-
width="24px"
|
|
60
|
-
fill="none"
|
|
61
|
-
aria-hidden="true"
|
|
62
|
-
focusable="false"
|
|
63
|
-
>
|
|
46
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="none">
|
|
64
47
|
<path
|
|
65
48
|
d="M22,18h-2v1h3v1h-4v-2c0-0.55,0.45-1,1-1h2v-1h-3v-1h3c0.55,0,1,0.45,1,1v1C23,17.55,22.55,18,22,18z M5.88,18h2.66 l3.4-5.42h0.12l3.4,5.42h2.66l-4.65-7.27L17.81,4h-2.68l-3.07,4.99h-0.12L8.85,4H6.19l4.32,6.73L5.88,18z"
|
|
66
49
|
fill="currentColor"
|
|
@@ -76,8 +59,6 @@ const HeadingIcon = () => (
|
|
|
76
59
|
fill="none"
|
|
77
60
|
xmlns="http://www.w3.org/2000/svg"
|
|
78
61
|
style={{ width: '20px', height: '18px' }}
|
|
79
|
-
aria-hidden="true"
|
|
80
|
-
focusable="false"
|
|
81
62
|
>
|
|
82
63
|
<path
|
|
83
64
|
d="M27 4V24H29C29.5 24 30 24.5 30 25V27C30 27.5625 29.5 28 29 28H19C18.4375 28 18 27.5625 18 27V25C18 24.5 18.4375 24 19 24H21V16H9V24H11C11.5 24 12 24.5 12 25V27C12 27.5625 11.5 28 11 28H1C0.4375 28 0 27.5625 0 27V25C0 24.5 0.4375 24 1 24H3V4H1C0.4375 4 0 3.5625 0 3V1C0 0.5 0.4375 0 1 0H11C11.5 0 12 0.5 12 1V3C12 3.5625 11.5 4 11 4H9V12H21V4H19C18.4375 4 18 3.5625 18 3V1C18 0.5 18.4375 0 19 0H29C29.5 0 30 0.5 30 1V3C30 3.5625 29.5 4 29 4H27Z"
|
|
@@ -185,7 +166,6 @@ function MenuBar({
|
|
|
185
166
|
() => [
|
|
186
167
|
{
|
|
187
168
|
icon: <GridOn />,
|
|
188
|
-
label: 'Insert table',
|
|
189
169
|
onClick: (editor) => editor.chain().focus().insertTable({ rows: 2, cols: 2, withHeaderRow: false }).run(),
|
|
190
170
|
hidden: (state) => !activePlugins?.includes('table') || state.isTable,
|
|
191
171
|
isActive: (state) => state.isTable,
|
|
@@ -193,7 +173,6 @@ function MenuBar({
|
|
|
193
173
|
},
|
|
194
174
|
{
|
|
195
175
|
icon: <AddRow />,
|
|
196
|
-
label: 'Add table row',
|
|
197
176
|
onClick: (editor) => editor.chain().focus().addRowAfter().run(),
|
|
198
177
|
hidden: (state) => !(state.isTable && !state.hasTextSelectionInTable),
|
|
199
178
|
isActive: (state) => state.isTable,
|
|
@@ -201,7 +180,6 @@ function MenuBar({
|
|
|
201
180
|
},
|
|
202
181
|
{
|
|
203
182
|
icon: <RemoveRow />,
|
|
204
|
-
label: 'Remove table row',
|
|
205
183
|
onClick: (editor) => editor.chain().focus().deleteRow().run(),
|
|
206
184
|
hidden: (state) => !(state.isTable && !state.hasTextSelectionInTable),
|
|
207
185
|
isActive: (state) => state.isTable,
|
|
@@ -209,7 +187,6 @@ function MenuBar({
|
|
|
209
187
|
},
|
|
210
188
|
{
|
|
211
189
|
icon: <AddColumn />,
|
|
212
|
-
label: 'Add table column',
|
|
213
190
|
onClick: (editor) => editor.chain().focus().addColumnAfter().run(),
|
|
214
191
|
hidden: (state) => !(state.isTable && !state.hasTextSelectionInTable),
|
|
215
192
|
isActive: (state) => state.isTable,
|
|
@@ -217,7 +194,6 @@ function MenuBar({
|
|
|
217
194
|
},
|
|
218
195
|
{
|
|
219
196
|
icon: <RemoveColumn />,
|
|
220
|
-
label: 'Remove table column',
|
|
221
197
|
onClick: (editor) => editor.chain().focus().deleteColumn().run(),
|
|
222
198
|
hidden: (state) => !(state.isTable && !state.hasTextSelectionInTable),
|
|
223
199
|
isActive: (state) => state.isTable,
|
|
@@ -225,7 +201,6 @@ function MenuBar({
|
|
|
225
201
|
},
|
|
226
202
|
{
|
|
227
203
|
icon: <RemoveTable />,
|
|
228
|
-
label: 'Remove table',
|
|
229
204
|
onClick: (editor) => editor.chain().focus().deleteTable().run(),
|
|
230
205
|
hidden: (state) => !(state.isTable && !state.hasTextSelectionInTable),
|
|
231
206
|
isActive: (state) => state.isTable,
|
|
@@ -233,8 +208,6 @@ function MenuBar({
|
|
|
233
208
|
},
|
|
234
209
|
{
|
|
235
210
|
icon: <BorderAll />,
|
|
236
|
-
label: 'Table borders',
|
|
237
|
-
toggle: true,
|
|
238
211
|
onClick: (editor) => {
|
|
239
212
|
const tableAttrs = editor.getAttributes('table');
|
|
240
213
|
|
|
@@ -251,8 +224,6 @@ function MenuBar({
|
|
|
251
224
|
},
|
|
252
225
|
{
|
|
253
226
|
icon: <Bold />,
|
|
254
|
-
label: 'Bold',
|
|
255
|
-
toggle: true,
|
|
256
227
|
onClick: (editor) => editor.chain().focus().toggleBold().run(),
|
|
257
228
|
hidden: () => !activePlugins?.includes('bold'),
|
|
258
229
|
isActive: (state) => state.isBold,
|
|
@@ -260,8 +231,6 @@ function MenuBar({
|
|
|
260
231
|
},
|
|
261
232
|
{
|
|
262
233
|
icon: <Italic />,
|
|
263
|
-
label: 'Italic',
|
|
264
|
-
toggle: true,
|
|
265
234
|
onClick: (editor) => editor.chain().focus().toggleItalic().run(),
|
|
266
235
|
hidden: () => !activePlugins?.includes('italic'),
|
|
267
236
|
isActive: (state) => state.isItalic,
|
|
@@ -269,8 +238,6 @@ function MenuBar({
|
|
|
269
238
|
},
|
|
270
239
|
{
|
|
271
240
|
icon: <Strikethrough />,
|
|
272
|
-
label: 'Strikethrough',
|
|
273
|
-
toggle: true,
|
|
274
241
|
onClick: (editor) => editor.chain().focus().toggleStrike().run(),
|
|
275
242
|
hidden: () => !activePlugins?.includes('strikethrough'),
|
|
276
243
|
isActive: (state) => state.isStrike,
|
|
@@ -278,8 +245,6 @@ function MenuBar({
|
|
|
278
245
|
},
|
|
279
246
|
{
|
|
280
247
|
icon: <Code />,
|
|
281
|
-
label: 'Code',
|
|
282
|
-
toggle: true,
|
|
283
248
|
onClick: (editor) => editor.chain().focus().toggleCode().run(),
|
|
284
249
|
hidden: () => !activePlugins?.includes('code'),
|
|
285
250
|
isActive: (state) => state.isCode,
|
|
@@ -287,118 +252,94 @@ function MenuBar({
|
|
|
287
252
|
},
|
|
288
253
|
{
|
|
289
254
|
icon: <Underline />,
|
|
290
|
-
label: 'Underline',
|
|
291
|
-
toggle: true,
|
|
292
255
|
onClick: (editor) => editor.chain().focus().toggleUnderline().run(),
|
|
293
256
|
hidden: () => !activePlugins?.includes('underline'),
|
|
294
257
|
isActive: (state) => state.isUnderline,
|
|
295
258
|
},
|
|
296
259
|
{
|
|
297
260
|
icon: <SubscriptIcon />,
|
|
298
|
-
label: 'Subscript',
|
|
299
|
-
toggle: true,
|
|
300
261
|
onClick: (editor) => editor.chain().focus().toggleSubscript().run(),
|
|
301
262
|
hidden: () => !activePlugins?.includes('subscript'),
|
|
302
263
|
isActive: (state) => state.isSubScript,
|
|
303
264
|
},
|
|
304
265
|
{
|
|
305
266
|
icon: <SuperscriptIcon />,
|
|
306
|
-
label: 'Superscript',
|
|
307
|
-
toggle: true,
|
|
308
267
|
onClick: (editor) => editor.chain().focus().toggleSuperscript().run(),
|
|
309
268
|
hidden: () => !activePlugins?.includes('superscript'),
|
|
310
269
|
isActive: (state) => state.isSuperScript,
|
|
311
270
|
},
|
|
312
271
|
{
|
|
313
272
|
icon: <ImageIcon />,
|
|
314
|
-
label: 'Insert image',
|
|
315
273
|
hidden: () => !activePlugins?.includes('image'),
|
|
316
274
|
onClick: (editor) => editor.chain().focus().setImageUploadNode().run(),
|
|
317
275
|
},
|
|
318
276
|
{
|
|
319
277
|
icon: <TheatersIcon />,
|
|
320
|
-
label: 'Insert video',
|
|
321
278
|
hidden: () => !activePlugins?.includes('video'),
|
|
322
279
|
onClick: (editor) => editor.chain().focus().insertMedia({ type: 'video' }).run(),
|
|
323
280
|
},
|
|
324
281
|
{
|
|
325
282
|
icon: <VolumeUpIcon />,
|
|
326
|
-
label: 'Insert audio',
|
|
327
283
|
hidden: () => !activePlugins?.includes('audio'),
|
|
328
284
|
onClick: (editor) => editor.chain().focus().insertMedia({ type: 'audio', tag: 'audio' }).run(),
|
|
329
285
|
},
|
|
330
286
|
{
|
|
331
287
|
icon: <CSSIcon />,
|
|
332
|
-
label: 'CSS classes',
|
|
333
288
|
hidden: () => !activePlugins?.includes('css'),
|
|
334
289
|
onClick: (editor) => editor.commands.openCSSClassDialog(),
|
|
335
290
|
},
|
|
336
291
|
{
|
|
337
292
|
icon: <FormatQuote />,
|
|
338
|
-
label: 'Blockquote',
|
|
339
|
-
toggle: true,
|
|
340
293
|
hidden: () => !activePlugins?.includes('blockquote'),
|
|
341
294
|
onClick: (editor) => editor.chain().focus().toggleBlockquote().run(),
|
|
342
295
|
isActive: (state) => state.isBlockquote,
|
|
343
296
|
},
|
|
344
297
|
{
|
|
345
298
|
icon: <HeadingIcon />,
|
|
346
|
-
label: 'Heading',
|
|
347
|
-
toggle: true,
|
|
348
299
|
hidden: () => !activePlugins?.includes('h3'),
|
|
349
300
|
onClick: (editor) => editor.chain().focus().toggleHeadingParagraph().run(),
|
|
350
301
|
isActive: (state) => state.isHeadingParagraph,
|
|
351
302
|
},
|
|
352
303
|
{
|
|
353
304
|
icon: <Functions />,
|
|
354
|
-
label: 'Insert math',
|
|
355
305
|
hidden: () => !activePlugins?.includes('math'),
|
|
356
306
|
onClick: (editor) => editor.chain().focus().insertMath('').run(),
|
|
357
307
|
},
|
|
358
308
|
{
|
|
359
309
|
icon: <CharacterIcon letter="ñ" />,
|
|
360
|
-
label: 'Spanish characters (ñ)',
|
|
361
310
|
hidden: () => !activePlugins?.includes('languageCharacters'),
|
|
362
311
|
onClick: () => setShowPicker(spanishConfig),
|
|
363
312
|
},
|
|
364
313
|
{
|
|
365
314
|
icon: <CharacterIcon letter="€" />,
|
|
366
|
-
label: 'Special characters (€)',
|
|
367
315
|
hidden: () => activePlugins?.filter((p) => p === 'languageCharacters').length !== 2,
|
|
368
316
|
onClick: () => setShowPicker(specialConfig),
|
|
369
317
|
},
|
|
370
318
|
{
|
|
371
319
|
icon: <TextAlignIcon editor={editor} />,
|
|
372
|
-
label: 'Text alignment',
|
|
373
320
|
hidden: () => !activePlugins?.includes('text-align'),
|
|
374
321
|
onClick: () => {},
|
|
375
322
|
},
|
|
376
323
|
{
|
|
377
324
|
icon: <BulletedListIcon />,
|
|
378
|
-
label: 'Bulleted list',
|
|
379
|
-
toggle: true,
|
|
380
325
|
hidden: () => !activePlugins?.includes('bulleted-list'),
|
|
381
326
|
onClick: (editor) => editor.chain().focus().toggleBulletList().run(),
|
|
382
327
|
isActive: (state) => state.isBulletList,
|
|
383
328
|
},
|
|
384
329
|
{
|
|
385
330
|
icon: <NumberedListIcon />,
|
|
386
|
-
label: 'Numbered list',
|
|
387
|
-
toggle: true,
|
|
388
331
|
hidden: () => !activePlugins?.includes('numbered-list'),
|
|
389
332
|
onClick: (editor) => editor.chain().focus().toggleOrderedList().run(),
|
|
390
333
|
isActive: (state) => state.isOrderedList,
|
|
391
334
|
},
|
|
392
335
|
{
|
|
393
336
|
icon: <Undo />,
|
|
394
|
-
label: 'Undo',
|
|
395
337
|
hidden: () => !activePlugins?.includes('undo'),
|
|
396
338
|
onClick: (editor) => editor.chain().focus().undo().run(),
|
|
397
339
|
isDisabled: (state) => !state.canUndo,
|
|
398
340
|
},
|
|
399
341
|
{
|
|
400
342
|
icon: <Redo />,
|
|
401
|
-
label: 'Redo',
|
|
402
343
|
hidden: () => !activePlugins?.includes('redo'),
|
|
403
344
|
onClick: (editor) => editor.chain().focus().redo().run(),
|
|
404
345
|
isDisabled: (state) => !state.canRedo,
|
|
@@ -424,7 +365,6 @@ function MenuBar({
|
|
|
424
365
|
onChange?.(editor.getHTML());
|
|
425
366
|
}}
|
|
426
367
|
aria-label="Delete response area"
|
|
427
|
-
title="Delete response area"
|
|
428
368
|
>
|
|
429
369
|
<Delete />
|
|
430
370
|
</button>
|
|
@@ -443,11 +383,7 @@ function MenuBar({
|
|
|
443
383
|
return (
|
|
444
384
|
<button
|
|
445
385
|
key={index}
|
|
446
|
-
type="button"
|
|
447
386
|
disabled={disabled}
|
|
448
|
-
aria-label={btn.label}
|
|
449
|
-
title={btn.label}
|
|
450
|
-
aria-pressed={btn.toggle ? !!active : undefined}
|
|
451
387
|
onClick={(e) => {
|
|
452
388
|
e.preventDefault();
|
|
453
389
|
btn.onClick(editor);
|
|
@@ -461,9 +397,6 @@ function MenuBar({
|
|
|
461
397
|
</div>
|
|
462
398
|
{activePlugins?.includes('responseArea') && (
|
|
463
399
|
<button
|
|
464
|
-
type="button"
|
|
465
|
-
aria-label="Insert response area"
|
|
466
|
-
title="Insert response area"
|
|
467
400
|
onClick={() => {
|
|
468
401
|
editor.chain().focus().insertResponseArea(responseAreaProps.type).run();
|
|
469
402
|
}}
|
|
@@ -529,11 +462,7 @@ const StyledMenuBar = (props) => {
|
|
|
529
462
|
);
|
|
530
463
|
};
|
|
531
464
|
|
|
532
|
-
const StyledMenuBarRoot = styled('div')(() => ({
|
|
533
|
-
// A zero-height slot the toolbar overflows out of, so the toolbar rides normal flow while the
|
|
534
|
-
// root's geometry stays as it was.
|
|
535
|
-
height: 0,
|
|
536
|
-
overflow: 'visible',
|
|
465
|
+
const StyledMenuBarRoot = styled('div')(({ theme }) => ({
|
|
537
466
|
'& .defaultToolbar': {
|
|
538
467
|
display: 'flex',
|
|
539
468
|
width: '100%',
|
|
@@ -557,26 +486,20 @@ const StyledMenuBarRoot = styled('div')(() => ({
|
|
|
557
486
|
height: '24px',
|
|
558
487
|
},
|
|
559
488
|
'&:hover': {
|
|
560
|
-
color:
|
|
489
|
+
color: 'black',
|
|
561
490
|
},
|
|
562
491
|
'&:focus': {
|
|
563
|
-
|
|
564
|
-
* A focus ring is the one thing a keyboard user cannot do without, so it takes
|
|
565
|
-
* the registered focus token rather than a grey. `--pie-button-focus-outline`
|
|
566
|
-
* measures at least 3.68:1 against every scheme's own background; MUI's
|
|
567
|
-
* grey[700] fell to 1.28:1 on yellow-on-navy.
|
|
568
|
-
*/
|
|
569
|
-
outline: `2px solid ${color.buttonFocusOutline()}`,
|
|
492
|
+
outline: `2px solid ${theme.palette.grey[700]}`,
|
|
570
493
|
},
|
|
571
494
|
},
|
|
572
495
|
'& .active': {
|
|
573
|
-
color:
|
|
496
|
+
color: 'black',
|
|
574
497
|
},
|
|
575
498
|
'& .disabled': {
|
|
576
499
|
opacity: 0.7,
|
|
577
500
|
cursor: 'not-allowed',
|
|
578
501
|
'& :hover': {
|
|
579
|
-
color:
|
|
502
|
+
color: 'grey',
|
|
580
503
|
},
|
|
581
504
|
},
|
|
582
505
|
'& .isActive': {
|
|
@@ -584,16 +507,13 @@ const StyledMenuBarRoot = styled('div')(() => ({
|
|
|
584
507
|
color: 'var(--white)',
|
|
585
508
|
},
|
|
586
509
|
'& .toolbar': {
|
|
587
|
-
|
|
588
|
-
// either of which a lockdown browser extension can get wrong as the editor grows.
|
|
589
|
-
position: 'relative',
|
|
590
|
-
top: '5px',
|
|
510
|
+
position: 'absolute',
|
|
591
511
|
zIndex: 20,
|
|
592
512
|
cursor: 'pointer',
|
|
593
513
|
justifyContent: 'space-between',
|
|
594
514
|
background: 'var(--editable-html-toolbar-bg, #efefef)',
|
|
595
515
|
minWidth: '280px',
|
|
596
|
-
margin: 0,
|
|
516
|
+
margin: '5px 0 0 0',
|
|
597
517
|
padding: '2px',
|
|
598
518
|
boxShadow:
|
|
599
519
|
'0px 1px 5px 0px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 3px 1px -2px rgba(0, 0, 0, 0.12)',
|
|
@@ -606,8 +526,6 @@ const StyledMenuBarRoot = styled('div')(() => ({
|
|
|
606
526
|
minWidth: '265px',
|
|
607
527
|
},
|
|
608
528
|
'& .toolbarTop': {
|
|
609
|
-
// Anchored to the root's top, which never depended on the editor's height.
|
|
610
|
-
position: 'absolute',
|
|
611
529
|
top: '-45px',
|
|
612
530
|
},
|
|
613
531
|
'& .toolbarRight': {
|
|
@@ -89,14 +89,14 @@ const StyledRoot = styled('div', {
|
|
|
89
89
|
backgroundColor: color.background(),
|
|
90
90
|
},
|
|
91
91
|
'& table:not([border="1"]) tr': {
|
|
92
|
-
borderTop:
|
|
92
|
+
borderTop: '1px solid #dfe2e5',
|
|
93
93
|
},
|
|
94
94
|
'& td, th': {
|
|
95
95
|
padding: '.6em 1em',
|
|
96
96
|
textAlign: 'center',
|
|
97
97
|
},
|
|
98
98
|
'& table:not([border="1"]) td, th': {
|
|
99
|
-
border:
|
|
99
|
+
border: '1px solid #dfe2e5',
|
|
100
100
|
},
|
|
101
101
|
...(error && {
|
|
102
102
|
border: `2px solid ${theme.palette.error.main} !important`,
|
|
@@ -2,27 +2,21 @@ import React from 'react';
|
|
|
2
2
|
import { fireEvent, render } from '@testing-library/react';
|
|
3
3
|
import StyledMenuBar from '../MenuBar';
|
|
4
4
|
|
|
5
|
-
// flipped to false by the accessibility tests to render the toolbar in its disabled state
|
|
6
|
-
let mockCanRunCommands = true;
|
|
7
|
-
// node/mark types the accessibility tests want the editor state to report as active
|
|
8
|
-
let mockActiveTypes = [];
|
|
9
|
-
|
|
10
5
|
jest.mock('@tiptap/react', () => ({
|
|
11
6
|
EditorContent: ({ editor }) => <div data-testid="editor-content" />,
|
|
12
7
|
useEditorState: ({ selector }) => {
|
|
13
|
-
const can = () => ({ run: jest.fn(() => mockCanRunCommands) });
|
|
14
8
|
const mockEditor = {
|
|
15
|
-
isActive: jest.fn((
|
|
9
|
+
isActive: jest.fn(() => false),
|
|
16
10
|
can: jest.fn(() => ({
|
|
17
11
|
chain: jest.fn(() => ({
|
|
18
|
-
toggleBold:
|
|
19
|
-
insertTable:
|
|
20
|
-
toggleItalic:
|
|
21
|
-
toggleStrike:
|
|
22
|
-
toggleCode:
|
|
23
|
-
unsetAllMarks:
|
|
24
|
-
undo:
|
|
25
|
-
redo:
|
|
12
|
+
toggleBold: jest.fn(() => ({ run: jest.fn(() => true) })),
|
|
13
|
+
insertTable: jest.fn(() => ({ run: jest.fn(() => true) })),
|
|
14
|
+
toggleItalic: jest.fn(() => ({ run: jest.fn(() => true) })),
|
|
15
|
+
toggleStrike: jest.fn(() => ({ run: jest.fn(() => true) })),
|
|
16
|
+
toggleCode: jest.fn(() => ({ run: jest.fn(() => true) })),
|
|
17
|
+
unsetAllMarks: jest.fn(() => ({ run: jest.fn(() => true) })),
|
|
18
|
+
undo: jest.fn(() => ({ run: jest.fn(() => true) })),
|
|
19
|
+
redo: jest.fn(() => ({ run: jest.fn(() => true) })),
|
|
26
20
|
})),
|
|
27
21
|
})),
|
|
28
22
|
getAttributes: jest.fn(() => ({ border: '1' })),
|
|
@@ -253,86 +247,4 @@ describe('StyledMenuBar', () => {
|
|
|
253
247
|
const { container } = render(<StyledMenuBar {...defaultProps} activePlugins={['table', 'bold', 'italic']} />);
|
|
254
248
|
expect(container).toBeInTheDocument();
|
|
255
249
|
});
|
|
256
|
-
describe('accessible names', () => {
|
|
257
|
-
const allPlugins = [
|
|
258
|
-
'table',
|
|
259
|
-
'bold',
|
|
260
|
-
'italic',
|
|
261
|
-
'strikethrough',
|
|
262
|
-
'code',
|
|
263
|
-
'underline',
|
|
264
|
-
'subscript',
|
|
265
|
-
'superscript',
|
|
266
|
-
'image',
|
|
267
|
-
'video',
|
|
268
|
-
'audio',
|
|
269
|
-
'css',
|
|
270
|
-
'blockquote',
|
|
271
|
-
'h3',
|
|
272
|
-
'math',
|
|
273
|
-
'languageCharacters',
|
|
274
|
-
'bulleted-list',
|
|
275
|
-
'numbered-list',
|
|
276
|
-
'undo',
|
|
277
|
-
'redo',
|
|
278
|
-
'responseArea',
|
|
279
|
-
];
|
|
280
|
-
|
|
281
|
-
const accessibleName = (button) => button.getAttribute('aria-label') || button.textContent.trim();
|
|
282
|
-
|
|
283
|
-
afterEach(() => {
|
|
284
|
-
mockCanRunCommands = true;
|
|
285
|
-
mockActiveTypes = [];
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
it('gives every toolbar button an accessible name', () => {
|
|
289
|
-
const { container } = render(<StyledMenuBar {...defaultProps} activePlugins={allPlugins} />);
|
|
290
|
-
const buttons = Array.from(container.querySelectorAll('button'));
|
|
291
|
-
|
|
292
|
-
expect(buttons.length).toBeGreaterThan(0);
|
|
293
|
-
buttons.forEach((button) => expect(accessibleName(button)).not.toEqual(''));
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('keeps accessible names on disabled buttons', () => {
|
|
297
|
-
mockCanRunCommands = false;
|
|
298
|
-
|
|
299
|
-
const { container } = render(<StyledMenuBar {...defaultProps} activePlugins={allPlugins} />);
|
|
300
|
-
const disabled = Array.from(container.querySelectorAll('button[disabled]'));
|
|
301
|
-
|
|
302
|
-
expect(disabled.length).toBeGreaterThan(0);
|
|
303
|
-
disabled.forEach((button) => expect(accessibleName(button)).not.toEqual(''));
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('labels the formatting and history buttons', () => {
|
|
307
|
-
const { getByLabelText } = render(<StyledMenuBar {...defaultProps} activePlugins={allPlugins} />);
|
|
308
|
-
|
|
309
|
-
['Bold', 'Italic', 'Underline', 'Insert table', 'Undo', 'Redo'].forEach((label) =>
|
|
310
|
-
expect(getByLabelText(label)).toBeInTheDocument(),
|
|
311
|
-
);
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
it('labels the table buttons', () => {
|
|
315
|
-
mockActiveTypes = ['table'];
|
|
316
|
-
|
|
317
|
-
const { getByLabelText } = render(<StyledMenuBar {...defaultProps} activePlugins={allPlugins} />);
|
|
318
|
-
|
|
319
|
-
['Add table row', 'Remove table row', 'Add table column', 'Remove table column', 'Remove table'].forEach(
|
|
320
|
-
(label) => expect(getByLabelText(label)).toBeInTheDocument(),
|
|
321
|
-
);
|
|
322
|
-
expect(getByLabelText('Table borders')).toHaveAttribute('aria-pressed', 'true');
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it('labels the response area insert button', () => {
|
|
326
|
-
const { getByLabelText } = render(<StyledMenuBar {...defaultProps} activePlugins={['responseArea']} />);
|
|
327
|
-
expect(getByLabelText('Insert response area')).toBeInTheDocument();
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it('exposes toggle state via aria-pressed on mark buttons', () => {
|
|
331
|
-
const { getByLabelText } = render(<StyledMenuBar {...defaultProps} activePlugins={allPlugins} />);
|
|
332
|
-
|
|
333
|
-
expect(getByLabelText('Bold')).toHaveAttribute('aria-pressed', 'false');
|
|
334
|
-
// one-shot actions are not toggles, so they must not report a pressed state
|
|
335
|
-
expect(getByLabelText('Undo')).not.toHaveAttribute('aria-pressed');
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
250
|
});
|