@pie-lib/editable-html-tip-tap 1.2.0-next.8 → 2.0.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/CHANGELOG.md +178 -0
- package/lib/components/CharacterPicker.js +1 -0
- package/lib/components/CharacterPicker.js.map +1 -1
- package/lib/components/EditableHtml.js +84 -43
- package/lib/components/EditableHtml.js.map +1 -1
- package/lib/components/MenuBar.js +74 -43
- package/lib/components/MenuBar.js.map +1 -1
- package/lib/components/TiptapContainer.js +9 -8
- package/lib/components/TiptapContainer.js.map +1 -1
- package/lib/components/icons/TextAlign.js +2 -2
- package/lib/components/icons/TextAlign.js.map +1 -1
- package/lib/components/image/InsertImageHandler.js +10 -13
- package/lib/components/image/InsertImageHandler.js.map +1 -1
- package/lib/components/media/MediaDialog.js.map +1 -1
- package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js +6 -1
- package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js.map +1 -1
- package/lib/components/respArea/DragInTheBlank/choice.js +15 -7
- package/lib/components/respArea/DragInTheBlank/choice.js.map +1 -1
- package/lib/components/respArea/ExplicitConstructedResponse.js +29 -11
- package/lib/components/respArea/ExplicitConstructedResponse.js.map +1 -1
- package/lib/components/respArea/InlineDropdown.js +35 -6
- package/lib/components/respArea/InlineDropdown.js.map +1 -1
- package/lib/components/respArea/MathTemplated.js +130 -0
- package/lib/components/respArea/MathTemplated.js.map +1 -0
- package/lib/extensions/custom-toolbar-wrapper.js +3 -2
- package/lib/extensions/custom-toolbar-wrapper.js.map +1 -1
- package/lib/extensions/div-node.js +83 -0
- package/lib/extensions/div-node.js.map +1 -0
- package/lib/extensions/ensure-empty-root-div.js +48 -0
- package/lib/extensions/ensure-empty-root-div.js.map +1 -0
- package/lib/extensions/ensure-list-item-content-is-div.js +64 -0
- package/lib/extensions/ensure-list-item-content-is-div.js.map +1 -0
- package/lib/extensions/extended-list-item.js +15 -0
- package/lib/extensions/extended-list-item.js.map +1 -0
- package/lib/extensions/extended-table-cell.js +22 -0
- package/lib/extensions/extended-table-cell.js.map +1 -0
- package/lib/extensions/extended-table.js +50 -1
- package/lib/extensions/extended-table.js.map +1 -1
- package/lib/extensions/image-component.js +102 -51
- package/lib/extensions/image-component.js.map +1 -1
- package/lib/extensions/image.js +51 -2
- package/lib/extensions/image.js.map +1 -1
- package/lib/extensions/math.js +50 -9
- package/lib/extensions/math.js.map +1 -1
- package/lib/extensions/media.js +3 -1
- package/lib/extensions/media.js.map +1 -1
- package/lib/extensions/responseArea.js +22 -13
- package/lib/extensions/responseArea.js.map +1 -1
- package/lib/styles/editorContainerStyles.js +5 -4
- package/lib/styles/editorContainerStyles.js.map +1 -1
- package/lib/utils/helper.js +17 -0
- package/lib/utils/helper.js.map +1 -0
- package/package.json +8 -8
- package/src/__tests__/EditableHtml.test.jsx +93 -7
- package/src/__tests__/index.test.jsx +11 -3
- package/src/components/CharacterPicker.jsx +1 -0
- package/src/components/EditableHtml.jsx +93 -41
- package/src/components/MenuBar.jsx +57 -24
- package/src/components/TiptapContainer.jsx +10 -8
- package/src/components/__tests__/CharacterPicker.test.jsx +22 -0
- package/src/components/__tests__/ExplicitConstructedResponse.test.jsx +55 -12
- package/src/components/__tests__/InlineDropdown.test.jsx +203 -10
- package/src/components/__tests__/InsertImageHandler.test.js +28 -21
- package/src/components/__tests__/MenuBar.test.jsx +32 -0
- package/src/components/icons/TextAlign.jsx +1 -1
- package/src/components/image/InsertImageHandler.js +9 -13
- package/src/components/respArea/DragInTheBlank/DragInTheBlank.jsx +6 -1
- package/src/components/respArea/DragInTheBlank/choice.jsx +32 -4
- package/src/components/respArea/ExplicitConstructedResponse.jsx +33 -10
- package/src/components/respArea/InlineDropdown.jsx +45 -10
- package/src/components/respArea/MathTemplated.jsx +124 -0
- package/src/components/respArea/__tests__/MathTemplated.test.jsx +210 -0
- package/src/extensions/__tests__/divNode.test.js +87 -0
- package/src/extensions/__tests__/ensure-empty-root-div.test.js +57 -0
- package/src/extensions/__tests__/ensure-list-item-content-is-div.test.js +44 -0
- package/src/extensions/__tests__/extended-list-item.test.js +13 -0
- package/src/extensions/__tests__/extended-table-cell.test.js +22 -0
- package/src/extensions/__tests__/extended-table.test.js +98 -1
- package/src/extensions/__tests__/image-component.test.jsx +105 -9
- package/src/extensions/__tests__/image.test.js +109 -8
- package/src/extensions/__tests__/math.test.js +348 -0
- package/src/extensions/__tests__/media-node-view.test.jsx +10 -8
- package/src/extensions/__tests__/responseArea.test.js +291 -0
- package/src/extensions/custom-toolbar-wrapper.jsx +2 -2
- package/src/extensions/div-node.js +86 -0
- package/src/extensions/ensure-empty-root-div.js +47 -0
- package/src/extensions/ensure-list-item-content-is-div.js +62 -0
- package/src/extensions/extended-list-item.js +10 -0
- package/src/extensions/extended-table-cell.js +19 -0
- package/src/extensions/extended-table.js +37 -1
- package/src/extensions/image-component.jsx +114 -69
- package/src/extensions/image.js +56 -1
- package/src/extensions/math.js +62 -10
- package/src/extensions/media.js +1 -1
- package/src/extensions/responseArea.js +15 -12
- package/src/styles/editorContainerStyles.js +5 -4
- package/src/utils/helper.js +17 -0
- /package/src/components/media/{MediaDialog.js → MediaDialog.jsx} +0 -0
|
@@ -63,7 +63,7 @@ var styles = function styles(theme) {
|
|
|
63
63
|
background: 'var(--black)',
|
|
64
64
|
borderRadius: '0.5rem',
|
|
65
65
|
color: 'var(--white)',
|
|
66
|
-
fontFamily:
|
|
66
|
+
fontFamily: '\'JetBrainsMono\', monospace',
|
|
67
67
|
margin: '1.5rem 0',
|
|
68
68
|
padding: '0.75rem 1rem',
|
|
69
69
|
'& code': {
|
|
@@ -74,9 +74,10 @@ var styles = function styles(theme) {
|
|
|
74
74
|
}
|
|
75
75
|
},
|
|
76
76
|
'& blockquote': {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
background: '#f9f9f9',
|
|
78
|
+
borderLeft: '5px solid #ccc',
|
|
79
|
+
margin: '1.5em 10px',
|
|
80
|
+
padding: '.5em 10px'
|
|
80
81
|
},
|
|
81
82
|
'& hr': {
|
|
82
83
|
border: 'none',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editorContainerStyles.js","names":["_renderUi","require","styles","theme","root","position","padding","border","borderRadius","cursor","wordBreak","overflow","maxHeight","marginTop","margin","marginBottom","lineHeight","textWrap","fontSize","backgroundColor","color","background","fontFamily","borderLeft","
|
|
1
|
+
{"version":3,"file":"editorContainerStyles.js","names":["_renderUi","require","styles","theme","root","position","padding","border","borderRadius","cursor","wordBreak","overflow","maxHeight","marginTop","margin","marginBottom","lineHeight","textWrap","fontSize","backgroundColor","color","background","fontFamily","borderLeft","borderTop","tableLayout","width","borderCollapse","text","textAlign","children","editorHolder","overflowY","disabledScrollbar","display","scrollbarWidth","error","concat","palette","main","noBorder","noPadding","toolbarOnTop","_default","exports"],"sources":["../../src/styles/editorContainerStyles.js"],"sourcesContent":["import { color } from '@pie-lib/render-ui';\n\nconst styles = (theme) => ({\n root: {\n position: 'relative',\n padding: '0px',\n border: '1px solid #ccc',\n borderRadius: '4px',\n cursor: 'text',\n '& [data-slate-editor=\"true\"]': {\n wordBreak: 'break-word',\n overflow: 'visible',\n maxHeight: '500px',\n // needed in order to be able to put the focus before a void element when it is the first one in the editor\n padding: '5px',\n },\n\n '&:first-child': {\n marginTop: 0,\n },\n\n '& ul, & ol': {\n padding: '0 1rem',\n margin: '1.25rem 1rem 1.25rem 0.4rem',\n },\n\n '& ul li p, & ol li p': {\n marginTop: '0.25em',\n marginBottom: '0.25em',\n },\n\n '& h1, & h2, & h3, & h4, & h5, & h6': {\n lineHeight: 1.1,\n marginTop: '2.5rem',\n textWrap: 'pretty',\n },\n\n '& h1, & h2': {\n marginTop: '3.5rem',\n marginBottom: '1.5rem',\n },\n\n '& h1': {\n fontSize: '1.4rem',\n },\n\n '& h2': {\n fontSize: '1.2rem',\n },\n\n '& h3': {\n fontSize: '1.1rem',\n },\n\n '& h4, & h5, & h6': {\n fontSize: '1rem',\n },\n\n '& code': {\n backgroundColor: 'var(--purple-light)',\n borderRadius: '0.4rem',\n color: 'var(--black)',\n fontSize: '0.85rem',\n padding: '0.25em 0.3em',\n },\n\n '& pre': {\n background: 'var(--black)',\n borderRadius: '0.5rem',\n color: 'var(--white)',\n fontFamily: '\\'JetBrainsMono\\', monospace',\n margin: '1.5rem 0',\n padding: '0.75rem 1rem',\n\n '& code': {\n background: 'none',\n color: 'inherit',\n fontSize: '0.8rem',\n padding: 0,\n },\n },\n\n '& blockquote': {\n background: '#f9f9f9',\n borderLeft: '5px solid #ccc',\n margin: '1.5em 10px',\n padding: '.5em 10px',\n },\n\n '& hr': {\n border: 'none',\n borderTop: '1px solid var(--gray-2)',\n margin: '2rem 0',\n },\n\n '& table': {\n tableLayout: 'fixed',\n width: '100%',\n borderCollapse: 'collapse',\n color: color.text(),\n backgroundColor: color.background(),\n },\n '& table:not([border=\"1\"]) tr': {\n borderTop: '1px solid #dfe2e5',\n },\n '& td, th': {\n padding: '.6em 1em',\n textAlign: 'center',\n },\n '& table:not([border=\"1\"]) td, th': {\n border: '1px solid #dfe2e5',\n },\n },\n children: {\n padding: '10px 16px',\n },\n editorHolder: {\n position: 'relative',\n padding: '0px',\n overflowY: 'auto',\n color: color.text(),\n backgroundColor: color.background(),\n },\n disabledScrollbar: {\n '&::-webkit-scrollbar': {\n display: 'none',\n },\n scrollbarWidth: 'none',\n '-ms-overflow-style': 'none',\n },\n error: {\n border: `2px solid ${theme.palette.error.main} !important`,\n },\n noBorder: {\n border: 'none',\n },\n noPadding: {\n padding: 0,\n },\n toolbarOnTop: {\n marginTop: '45px',\n },\n});\n\nexport default styles;\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.normalizeInitialMarkup = void 0;
|
|
7
|
+
var escapeHtml = function escapeHtml(str) {
|
|
8
|
+
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
9
|
+
};
|
|
10
|
+
var normalizeInitialMarkup = exports.normalizeInitialMarkup = function normalizeInitialMarkup(markup) {
|
|
11
|
+
var trimmed = String(markup !== null && markup !== void 0 ? markup : '').trim();
|
|
12
|
+
if (!trimmed) return '<div></div>';
|
|
13
|
+
var looksLikeHtml = /<[^>]+>/.test(trimmed);
|
|
14
|
+
if (looksLikeHtml) return trimmed;
|
|
15
|
+
return "<div>".concat(escapeHtml(trimmed), "</div>");
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.js","names":["escapeHtml","str","String","replace","normalizeInitialMarkup","exports","markup","trimmed","trim","looksLikeHtml","test","concat"],"sources":["../../src/utils/helper.js"],"sourcesContent":["const escapeHtml = (str) =>\n String(str)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n\nexport const normalizeInitialMarkup = (markup) => {\n const trimmed = String(markup ?? '').trim();\n if (!trimmed) return '<div></div>';\n\n const looksLikeHtml = /<[^>]+>/.test(trimmed);\n if (looksLikeHtml) return trimmed;\n\n return `<div>${escapeHtml(trimmed)}</div>`;\n};\n"],"mappings":";;;;;;AAAA,IAAMA,UAAU,GAAG,SAAbA,UAAUA,CAAIC,GAAG;EAAA,OACrBC,MAAM,CAACD,GAAG,CAAC,CACRE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CACtBA,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CACrBA,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CACrBA,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CACvBA,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;AAAA;AAEpB,IAAMC,sBAAsB,GAAAC,OAAA,CAAAD,sBAAA,GAAG,SAAzBA,sBAAsBA,CAAIE,MAAM,EAAK;EAChD,IAAMC,OAAO,GAAGL,MAAM,CAACI,MAAM,aAANA,MAAM,cAANA,MAAM,GAAI,EAAE,CAAC,CAACE,IAAI,CAAC,CAAC;EAC3C,IAAI,CAACD,OAAO,EAAE,OAAO,aAAa;EAElC,IAAME,aAAa,GAAG,SAAS,CAACC,IAAI,CAACH,OAAO,CAAC;EAC7C,IAAIE,aAAa,EAAE,OAAOF,OAAO;EAEjC,eAAAI,MAAA,CAAeX,UAAU,CAACO,OAAO,CAAC;AACpC,CAAC","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "2.0.0",
|
|
7
7
|
"description": "",
|
|
8
8
|
"license": "ISC",
|
|
9
9
|
"main": "lib/index.js",
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
"@dnd-kit/utilities": "3.2.2",
|
|
17
17
|
"@mui/icons-material": "^7.3.4",
|
|
18
18
|
"@mui/material": "^7.3.4",
|
|
19
|
-
"@pie-lib/drag": "^
|
|
20
|
-
"@pie-lib/math-input": "^
|
|
21
|
-
"@pie-lib/math-rendering": "^
|
|
22
|
-
"@pie-lib/math-toolbar": "^
|
|
23
|
-
"@pie-lib/render-ui": "^
|
|
19
|
+
"@pie-lib/drag": "^4.0.0",
|
|
20
|
+
"@pie-lib/math-input": "^8.0.0",
|
|
21
|
+
"@pie-lib/math-rendering": "^5.0.0",
|
|
22
|
+
"@pie-lib/math-toolbar": "^3.0.0",
|
|
23
|
+
"@pie-lib/render-ui": "^6.0.0",
|
|
24
24
|
"@tiptap/core": "3.0.9",
|
|
25
25
|
"@tiptap/extension-character-count": "3.0.9",
|
|
26
26
|
"@tiptap/extension-color": "3.0.9",
|
|
@@ -52,13 +52,13 @@
|
|
|
52
52
|
"to-style": "^1.3.3"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@pie-framework/mathquill": "
|
|
55
|
+
"@pie-framework/mathquill": "1.2.1-beta.1",
|
|
56
56
|
"react": "^18.2.0",
|
|
57
57
|
"react-dom": "^18.2.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"react": "^18.2.0"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "bf0904ba8bdd2d6f88fb8ad99060e45e40180348",
|
|
63
63
|
"scripts": {}
|
|
64
64
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, waitFor } from '@testing-library/react';
|
|
3
|
+
import { useEditor } from '@tiptap/react';
|
|
3
4
|
import { EditableHtml } from '../components/EditableHtml';
|
|
4
5
|
|
|
5
6
|
// Mock TipTap dependencies
|
|
@@ -24,7 +25,9 @@ jest.mock('@tiptap/react', () => ({
|
|
|
24
25
|
|
|
25
26
|
jest.mock('@tiptap/starter-kit', () => ({
|
|
26
27
|
__esModule: true,
|
|
27
|
-
default: {
|
|
28
|
+
default: {
|
|
29
|
+
configure: jest.fn(() => ({})),
|
|
30
|
+
},
|
|
28
31
|
}));
|
|
29
32
|
|
|
30
33
|
jest.mock('@tiptap/extension-text-style', () => ({
|
|
@@ -68,12 +71,9 @@ jest.mock('@tiptap/extension-table-row', () => ({
|
|
|
68
71
|
TableRow: {},
|
|
69
72
|
}));
|
|
70
73
|
|
|
71
|
-
jest.mock('
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
jest.mock('@tiptap/extension-table-header', () => ({
|
|
76
|
-
TableHeader: {},
|
|
74
|
+
jest.mock('../extensions/extended-table-cell', () => ({
|
|
75
|
+
ExtendedTableCell: {},
|
|
76
|
+
ExtendedTableHeader: {},
|
|
77
77
|
}));
|
|
78
78
|
|
|
79
79
|
jest.mock('../extensions/extended-table', () => ({
|
|
@@ -81,6 +81,18 @@ jest.mock('../extensions/extended-table', () => ({
|
|
|
81
81
|
default: {},
|
|
82
82
|
}));
|
|
83
83
|
|
|
84
|
+
jest.mock('../extensions/ensure-empty-root-div', () => ({
|
|
85
|
+
EnsureEmptyRootIsDiv: {},
|
|
86
|
+
}));
|
|
87
|
+
|
|
88
|
+
jest.mock('../extensions/extended-list-item', () => ({
|
|
89
|
+
ExtendedListItem: {},
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
jest.mock('../extensions/ensure-list-item-content-is-div', () => ({
|
|
93
|
+
EnsureListItemContentIsDiv: {},
|
|
94
|
+
}));
|
|
95
|
+
|
|
84
96
|
jest.mock('../extensions/responseArea', () => ({
|
|
85
97
|
ExplicitConstructedResponseNode: {
|
|
86
98
|
configure: jest.fn(() => ({})),
|
|
@@ -91,6 +103,9 @@ jest.mock('../extensions/responseArea', () => ({
|
|
|
91
103
|
InlineDropdownNode: {
|
|
92
104
|
configure: jest.fn(() => ({})),
|
|
93
105
|
},
|
|
106
|
+
MathTemplatedNode: {
|
|
107
|
+
configure: jest.fn(() => ({})),
|
|
108
|
+
},
|
|
94
109
|
ResponseAreaExtension: {
|
|
95
110
|
configure: jest.fn(() => ({})),
|
|
96
111
|
},
|
|
@@ -263,4 +278,75 @@ describe('EditableHtml', () => {
|
|
|
263
278
|
const { container } = render(<EditableHtml {...defaultProps} disableImageAlignmentButtons={true} />);
|
|
264
279
|
expect(container).toBeInTheDocument();
|
|
265
280
|
});
|
|
281
|
+
|
|
282
|
+
it('calls editorRef callback when editor is initialized', async () => {
|
|
283
|
+
const editorRef = jest.fn();
|
|
284
|
+
render(<EditableHtml {...defaultProps} editorRef={editorRef} />);
|
|
285
|
+
|
|
286
|
+
await waitFor(() => {
|
|
287
|
+
expect(editorRef).toHaveBeenCalled();
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('calls editorRef with the editor instance', async () => {
|
|
292
|
+
const editorRef = jest.fn();
|
|
293
|
+
render(<EditableHtml {...defaultProps} editorRef={editorRef} />);
|
|
294
|
+
|
|
295
|
+
await waitFor(() => {
|
|
296
|
+
expect(editorRef).toHaveBeenCalled();
|
|
297
|
+
// Verify it was called with an object that has editor-like properties
|
|
298
|
+
const callArg = editorRef.mock.calls[0][0];
|
|
299
|
+
expect(callArg).toHaveProperty('getHTML');
|
|
300
|
+
expect(callArg).toHaveProperty('commands');
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('handles editorRef being undefined', () => {
|
|
305
|
+
const { container } = render(<EditableHtml {...defaultProps} editorRef={undefined} />);
|
|
306
|
+
expect(container).toBeInTheDocument();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('applies flex display to StyledEditorContent', async () => {
|
|
310
|
+
const { getByTestId } = render(<EditableHtml {...defaultProps} />);
|
|
311
|
+
await waitFor(() => {
|
|
312
|
+
const editorContent = getByTestId('editor-content');
|
|
313
|
+
expect(editorContent).toBeInTheDocument();
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('does not run blur onChange/onDone while an image insert flow is active', async () => {
|
|
318
|
+
jest.useFakeTimers();
|
|
319
|
+
const onChange = jest.fn();
|
|
320
|
+
const onDone = jest.fn();
|
|
321
|
+
|
|
322
|
+
render(
|
|
323
|
+
<EditableHtml
|
|
324
|
+
{...defaultProps}
|
|
325
|
+
markup="<p>Hello World</p>"
|
|
326
|
+
onChange={onChange}
|
|
327
|
+
onDone={onDone}
|
|
328
|
+
toolbarOpts={{ ...defaultProps.toolbarOpts, doneOn: 'blur' }}
|
|
329
|
+
/>,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
await waitFor(() => {
|
|
333
|
+
expect(useEditor).toHaveBeenCalled();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const editorConfig = useEditor.mock.calls[useEditor.mock.calls.length - 1][0];
|
|
337
|
+
const blurEditor = {
|
|
338
|
+
getHTML: jest.fn(() => '<p>changed</p>'),
|
|
339
|
+
_insertingImage: true,
|
|
340
|
+
_toolbarOpened: false,
|
|
341
|
+
isActive: jest.fn(() => false),
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
editorConfig.onBlur({ editor: blurEditor });
|
|
345
|
+
jest.advanceTimersByTime(200);
|
|
346
|
+
|
|
347
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
348
|
+
expect(onDone).not.toHaveBeenCalled();
|
|
349
|
+
|
|
350
|
+
jest.useRealTimers();
|
|
351
|
+
});
|
|
266
352
|
});
|
|
@@ -13,7 +13,10 @@ jest.mock('@tiptap/react', () => ({
|
|
|
13
13
|
useEditorState: jest.fn(() => ({ isFocused: false })),
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
|
-
jest.mock('@tiptap/starter-kit', () => ({
|
|
16
|
+
jest.mock('@tiptap/starter-kit', () => ({
|
|
17
|
+
__esModule: true,
|
|
18
|
+
default: { configure: jest.fn(() => ({})) },
|
|
19
|
+
}));
|
|
17
20
|
jest.mock('@tiptap/extension-text-style', () => ({ TextStyleKit: {} }));
|
|
18
21
|
jest.mock('@tiptap/extension-character-count', () => ({
|
|
19
22
|
CharacterCount: { configure: jest.fn(() => ({})) },
|
|
@@ -27,9 +30,14 @@ jest.mock('@tiptap/extension-text-align', () => ({
|
|
|
27
30
|
jest.mock('@tiptap/extension-image', () => ({ __esModule: true, default: {} }));
|
|
28
31
|
jest.mock('@tiptap/extension-table', () => ({ __esModule: true, default: {} }));
|
|
29
32
|
jest.mock('@tiptap/extension-table-row', () => ({ TableRow: {} }));
|
|
30
|
-
jest.mock('
|
|
31
|
-
|
|
33
|
+
jest.mock('../extensions/extended-table-cell', () => ({
|
|
34
|
+
ExtendedTableCell: {},
|
|
35
|
+
ExtendedTableHeader: {},
|
|
36
|
+
}));
|
|
32
37
|
jest.mock('../extensions/extended-table', () => ({ __esModule: true, default: {} }));
|
|
38
|
+
jest.mock('../extensions/ensure-empty-root-div', () => ({ EnsureEmptyRootIsDiv: {} }));
|
|
39
|
+
jest.mock('../extensions/extended-list-item', () => ({ ExtendedListItem: {} }));
|
|
40
|
+
jest.mock('../extensions/ensure-list-item-content-is-div', () => ({ EnsureListItemContentIsDiv: {} }));
|
|
33
41
|
jest.mock('../extensions/responseArea', () => ({
|
|
34
42
|
ExplicitConstructedResponseNode: { configure: jest.fn(() => ({})) },
|
|
35
43
|
DragInTheBlankNode: { configure: jest.fn(() => ({})) },
|
|
@@ -121,6 +121,7 @@ export function CharacterPicker({ editor, opts, onClose }) {
|
|
|
121
121
|
<div
|
|
122
122
|
ref={containerRef}
|
|
123
123
|
className="insert-character-dialog"
|
|
124
|
+
data-toolbar-for={editor.instanceId}
|
|
124
125
|
style={{
|
|
125
126
|
visibility: position.top === 0 && position.left === 0 ? 'hidden' : 'initial',
|
|
126
127
|
position: 'absolute',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import debounce from 'lodash-es/debounce';
|
|
2
3
|
import { EditorContent, useEditor, useEditorState } from '@tiptap/react';
|
|
4
|
+
import { styled } from '@mui/material/styles';
|
|
3
5
|
import StarterKit from '@tiptap/starter-kit';
|
|
4
6
|
import { TextStyleKit } from '@tiptap/extension-text-style';
|
|
5
7
|
import { CharacterCount } from '@tiptap/extension-character-count';
|
|
@@ -8,23 +10,26 @@ import SubScript from '@tiptap/extension-subscript';
|
|
|
8
10
|
import TextAlign from '@tiptap/extension-text-align';
|
|
9
11
|
import Image from '@tiptap/extension-image';
|
|
10
12
|
import Placeholder from '@tiptap/extension-placeholder';
|
|
11
|
-
import {
|
|
12
|
-
import debounce from 'lodash-es/debounce';
|
|
13
|
+
import { normalizeInitialMarkup } from '../utils/helper';
|
|
13
14
|
|
|
14
15
|
import ExtendedTable from '../extensions/extended-table';
|
|
16
|
+
import { ExtendedTableCell, ExtendedTableHeader } from '../extensions/extended-table-cell';
|
|
17
|
+
import { DivNode } from '../extensions/div-node';
|
|
18
|
+
import { EnsureEmptyRootIsDiv } from '../extensions/ensure-empty-root-div';
|
|
19
|
+
import { EnsureListItemContentIsDiv } from '../extensions/ensure-list-item-content-is-div';
|
|
15
20
|
import { TableRow } from '@tiptap/extension-table-row';
|
|
16
|
-
import { TableCell } from '@tiptap/extension-table-cell';
|
|
17
|
-
import { TableHeader } from '@tiptap/extension-table-header';
|
|
18
21
|
import {
|
|
19
22
|
DragInTheBlankNode,
|
|
20
23
|
ExplicitConstructedResponseNode,
|
|
21
24
|
InlineDropdownNode,
|
|
25
|
+
MathTemplatedNode,
|
|
22
26
|
ResponseAreaExtension,
|
|
23
27
|
} from '../extensions/responseArea';
|
|
24
28
|
import { MathNode } from '../extensions/math';
|
|
25
29
|
import { ImageUploadNode } from '../extensions/image';
|
|
26
30
|
import { Media } from '../extensions/media';
|
|
27
31
|
import { CSSMark } from '../extensions/css';
|
|
32
|
+
import { ExtendedListItem } from '../extensions/extended-list-item';
|
|
28
33
|
|
|
29
34
|
import EditorContainer from './TiptapContainer';
|
|
30
35
|
import { valueToSize } from '../utils/size';
|
|
@@ -96,6 +101,19 @@ export const EditableHtml = (props) => {
|
|
|
96
101
|
const [scheduled, setScheduled] = useState(false);
|
|
97
102
|
const { toolbarOpts } = props;
|
|
98
103
|
|
|
104
|
+
const removePendingImage = useCallback(
|
|
105
|
+
(imagePos) => {
|
|
106
|
+
setPendingImages((prev) => {
|
|
107
|
+
const next = prev.filter((img) => img.pos !== imagePos);
|
|
108
|
+
if (next.length === 0) {
|
|
109
|
+
setScheduled(false);
|
|
110
|
+
}
|
|
111
|
+
return next;
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
[setPendingImages],
|
|
115
|
+
);
|
|
116
|
+
|
|
99
117
|
const toolbarOptsToUse = {
|
|
100
118
|
...defaultToolbarOpts,
|
|
101
119
|
...toolbarOpts,
|
|
@@ -134,11 +152,24 @@ export const EditableHtml = (props) => {
|
|
|
134
152
|
}, [props]);
|
|
135
153
|
|
|
136
154
|
const extensions = [
|
|
155
|
+
TextAlign.configure({
|
|
156
|
+
types: ['heading', 'paragraph', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'th'],
|
|
157
|
+
alignments: ['left', 'right', 'center', 'justify'],
|
|
158
|
+
}),
|
|
137
159
|
TextStyleKit,
|
|
138
160
|
CharacterCount.configure({
|
|
139
161
|
limit: props.charactersLimit || 1000000,
|
|
140
162
|
}),
|
|
141
|
-
StarterKit
|
|
163
|
+
StarterKit.configure({
|
|
164
|
+
trailingNode: {
|
|
165
|
+
node: 'paragraph',
|
|
166
|
+
notAfter: ['paragraph', 'div'],
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
ExtendedListItem,
|
|
170
|
+
DivNode,
|
|
171
|
+
EnsureEmptyRootIsDiv,
|
|
172
|
+
EnsureListItemContentIsDiv,
|
|
142
173
|
Placeholder.configure({
|
|
143
174
|
placeholder: props.placeholder,
|
|
144
175
|
// show placeholder even when editor is focused
|
|
@@ -148,27 +179,25 @@ export const EditableHtml = (props) => {
|
|
|
148
179
|
}),
|
|
149
180
|
ExtendedTable,
|
|
150
181
|
TableRow,
|
|
151
|
-
|
|
152
|
-
|
|
182
|
+
ExtendedTableHeader,
|
|
183
|
+
ExtendedTableCell,
|
|
153
184
|
ResponseAreaExtension.configure(props.responseAreaProps),
|
|
154
185
|
ExplicitConstructedResponseNode.configure(props.responseAreaProps),
|
|
155
186
|
DragInTheBlankNode.configure(props.responseAreaProps),
|
|
156
187
|
InlineDropdownNode.configure(props.responseAreaProps),
|
|
188
|
+
MathTemplatedNode.configure(props.responseAreaProps),
|
|
157
189
|
MathNode.configure({
|
|
158
190
|
toolbarOpts: toolbarOptsToUse,
|
|
191
|
+
math: props.pluginProps?.math || {},
|
|
159
192
|
}),
|
|
160
193
|
SubScript,
|
|
161
194
|
SuperScript,
|
|
162
|
-
TextAlign.configure({
|
|
163
|
-
types: ['heading', 'paragraph'],
|
|
164
|
-
alignments: ['left', 'right', 'center'],
|
|
165
|
-
}),
|
|
166
195
|
Image,
|
|
167
196
|
ImageUploadNode.configure({
|
|
168
197
|
toolbarOpts: toolbarOptsToUse,
|
|
169
198
|
imageHandling: {
|
|
170
199
|
disableImageAlignmentButtons: props.disableImageAlignmentButtons,
|
|
171
|
-
onDone: () => props.onDone?.(editor.getHTML()),
|
|
200
|
+
onDone: (editor) => props.onDone?.(editor.getHTML()),
|
|
172
201
|
onDelete:
|
|
173
202
|
props.imageSupport &&
|
|
174
203
|
props.imageSupport.delete &&
|
|
@@ -176,19 +205,14 @@ export const EditableHtml = (props) => {
|
|
|
176
205
|
const { src } = node.attrs;
|
|
177
206
|
|
|
178
207
|
props.imageSupport.delete(src, (e) => {
|
|
179
|
-
|
|
180
|
-
const newState = {
|
|
181
|
-
pendingImages: newPendingImages,
|
|
182
|
-
scheduled: scheduled && newPendingImages.length === 0 ? false : scheduled,
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
setPendingImages(newState.pendingImages);
|
|
186
|
-
setScheduled(newState.scheduled);
|
|
208
|
+
removePendingImage(node.pos);
|
|
187
209
|
});
|
|
188
210
|
}),
|
|
189
211
|
insertImageRequested:
|
|
190
212
|
props.imageSupport &&
|
|
191
|
-
((
|
|
213
|
+
((editor, imageInfo, getHandler) => {
|
|
214
|
+
const [addedImage, pos] = imageInfo;
|
|
215
|
+
|
|
192
216
|
const onFinish = (result) => {
|
|
193
217
|
let cb;
|
|
194
218
|
|
|
@@ -197,29 +221,39 @@ export const EditableHtml = (props) => {
|
|
|
197
221
|
cb = props.onChange;
|
|
198
222
|
}
|
|
199
223
|
|
|
200
|
-
|
|
201
|
-
const newState = {
|
|
202
|
-
pendingImages: newPendingImages,
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
if (newPendingImages.length === 0) {
|
|
206
|
-
newState.scheduled = false;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
setPendingImages(newState.pendingImages);
|
|
210
|
-
setScheduled(newState.scheduled);
|
|
224
|
+
removePendingImage(pos);
|
|
211
225
|
cb?.(editor.getHTML());
|
|
212
226
|
};
|
|
227
|
+
|
|
213
228
|
const callback = () => {
|
|
214
229
|
/**
|
|
215
230
|
* The handler is the object through which the outer context
|
|
216
231
|
* communicates file upload events like: fileChosen, cancel, progress
|
|
217
232
|
*/
|
|
218
233
|
const handler = getHandler(onFinish);
|
|
234
|
+
|
|
235
|
+
// If the user closes the file picker without choosing a file, the window regains
|
|
236
|
+
// focus while _insertingImage is still true — drop the stale pending entry.
|
|
237
|
+
const focusHandler = debounce(() => {
|
|
238
|
+
const detach = () => window.removeEventListener('focus', focusHandler);
|
|
239
|
+
|
|
240
|
+
if (!editor._insertingImage) {
|
|
241
|
+
detach();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
removePendingImage(pos);
|
|
246
|
+
editor._insertingImage = false;
|
|
247
|
+
detach();
|
|
248
|
+
}, 500);
|
|
249
|
+
|
|
250
|
+
window.addEventListener('focus', focusHandler);
|
|
251
|
+
|
|
219
252
|
props.imageSupport.add(handler);
|
|
220
253
|
};
|
|
221
254
|
|
|
222
|
-
|
|
255
|
+
editor._insertingImage = true;
|
|
256
|
+
setPendingImages((prev) => [...prev, addedImage]);
|
|
223
257
|
callback();
|
|
224
258
|
}),
|
|
225
259
|
maxImageWidth: props.maxImageWidth,
|
|
@@ -250,7 +284,7 @@ export const EditableHtml = (props) => {
|
|
|
250
284
|
},
|
|
251
285
|
},
|
|
252
286
|
editable: !props.disabled,
|
|
253
|
-
content: props.markup,
|
|
287
|
+
content: normalizeInitialMarkup(props.markup),
|
|
254
288
|
onUpdate: ({ editor, transaction }) => {
|
|
255
289
|
if (transaction.isDone) {
|
|
256
290
|
props.onChange?.(editor.getHTML());
|
|
@@ -258,6 +292,7 @@ export const EditableHtml = (props) => {
|
|
|
258
292
|
},
|
|
259
293
|
onBlur: debounce(({ editor }) => {
|
|
260
294
|
const otherToolbarOpened =
|
|
295
|
+
editor._insertingImage ||
|
|
261
296
|
editor._toolbarOpened ||
|
|
262
297
|
editor.isActive('inline_dropdown') ||
|
|
263
298
|
editor.isActive('explicit_constructed_response');
|
|
@@ -278,6 +313,12 @@ export const EditableHtml = (props) => {
|
|
|
278
313
|
[props.charactersLimit],
|
|
279
314
|
);
|
|
280
315
|
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
if (props.editorRef) {
|
|
318
|
+
props.editorRef(editor);
|
|
319
|
+
}
|
|
320
|
+
}, [props.editorRef, editor]);
|
|
321
|
+
|
|
281
322
|
useEffect(() => {
|
|
282
323
|
editor?.setEditable(!props.disabled);
|
|
283
324
|
}, [props.disabled, editor]);
|
|
@@ -286,9 +327,10 @@ export const EditableHtml = (props) => {
|
|
|
286
327
|
if (!editor) {
|
|
287
328
|
return;
|
|
288
329
|
}
|
|
330
|
+
const nextMarkup = normalizeInitialMarkup(props.markup);
|
|
289
331
|
|
|
290
|
-
if (
|
|
291
|
-
editor.commands.setContent(
|
|
332
|
+
if (nextMarkup !== editor.getHTML()) {
|
|
333
|
+
editor.commands.setContent(nextMarkup, false);
|
|
292
334
|
}
|
|
293
335
|
}, [props.markup, editor]);
|
|
294
336
|
|
|
@@ -347,26 +389,36 @@ export const EditableHtml = (props) => {
|
|
|
347
389
|
const StyledEditorContent = styled(EditorContent, {
|
|
348
390
|
shouldForwardProp: (prop) => !['showParagraph', 'separateParagraph'].includes(prop),
|
|
349
391
|
})(({ showParagraph, separateParagraph }) => ({
|
|
392
|
+
display: 'flex',
|
|
350
393
|
outline: 'none !important',
|
|
351
394
|
'& .ProseMirror': {
|
|
395
|
+
flex: 1,
|
|
352
396
|
padding: '5px',
|
|
353
397
|
maxHeight: '500px',
|
|
354
398
|
outline: 'none !important',
|
|
355
399
|
position: 'initial',
|
|
356
|
-
|
|
400
|
+
|
|
401
|
+
// reset default margins for all block paragraphs/divs in the editor
|
|
402
|
+
'& > p, & > div': {
|
|
357
403
|
margin: '0',
|
|
358
404
|
},
|
|
359
405
|
|
|
360
|
-
|
|
406
|
+
// Out of flow so the caret stays at the start of the block; in-flow ::before pushes the caret after the hint text.
|
|
407
|
+
'& p.is-editor-empty, & div.is-editor-empty': {
|
|
408
|
+
position: 'relative',
|
|
409
|
+
},
|
|
410
|
+
'& p.is-editor-empty::before, & div.is-editor-empty::before': {
|
|
361
411
|
content: 'attr(data-placeholder)',
|
|
362
|
-
|
|
412
|
+
position: 'absolute',
|
|
413
|
+
left: 0,
|
|
414
|
+
top: 0,
|
|
363
415
|
color: '#9CA3AF',
|
|
364
416
|
pointerEvents: 'none',
|
|
365
417
|
whiteSpace: 'pre-wrap',
|
|
366
418
|
},
|
|
367
419
|
|
|
368
420
|
...(showParagraph && {
|
|
369
|
-
'& > p:has(+ p)::after': {
|
|
421
|
+
'& > p:has(+ p)::after, & > div:has(+ div)::after': {
|
|
370
422
|
display: 'block',
|
|
371
423
|
content: '"¶"',
|
|
372
424
|
fontSize: '1em',
|