@pie-lib/editable-html-tip-tap 1.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.
Files changed (167) hide show
  1. package/CHANGELOG.json +32 -0
  2. package/CHANGELOG.md +2280 -0
  3. package/lib/__tests__/editor.test.js +470 -0
  4. package/lib/__tests__/serialization.test.js +246 -0
  5. package/lib/__tests__/utils.js +106 -0
  6. package/lib/block-tags.js +25 -0
  7. package/lib/constants.js +16 -0
  8. package/lib/editor.js +1356 -0
  9. package/lib/extensions/MediaView.js +112 -0
  10. package/lib/extensions/characters.js +65 -0
  11. package/lib/extensions/component.js +325 -0
  12. package/lib/extensions/css.js +252 -0
  13. package/lib/extensions/custom-toolbar-wrapper.js +124 -0
  14. package/lib/extensions/image.js +106 -0
  15. package/lib/extensions/math.js +330 -0
  16. package/lib/extensions/media.js +276 -0
  17. package/lib/extensions/responseArea.js +278 -0
  18. package/lib/index.js +1213 -0
  19. package/lib/old-index.js +269 -0
  20. package/lib/parse-html.js +16 -0
  21. package/lib/plugins/characters/custom-popper.js +73 -0
  22. package/lib/plugins/characters/index.js +305 -0
  23. package/lib/plugins/characters/utils.js +381 -0
  24. package/lib/plugins/css/icons/index.js +37 -0
  25. package/lib/plugins/css/index.js +390 -0
  26. package/lib/plugins/customPlugin/index.js +114 -0
  27. package/lib/plugins/html/icons/index.js +38 -0
  28. package/lib/plugins/html/index.js +81 -0
  29. package/lib/plugins/image/__tests__/component.test.js +51 -0
  30. package/lib/plugins/image/__tests__/image-toolbar-logic.test.js +56 -0
  31. package/lib/plugins/image/__tests__/image-toolbar.test.js +26 -0
  32. package/lib/plugins/image/__tests__/index.test.js +98 -0
  33. package/lib/plugins/image/__tests__/insert-image-handler.test.js +125 -0
  34. package/lib/plugins/image/__tests__/mock-change.js +25 -0
  35. package/lib/plugins/image/alt-dialog.js +129 -0
  36. package/lib/plugins/image/component.js +419 -0
  37. package/lib/plugins/image/image-toolbar.js +177 -0
  38. package/lib/plugins/image/index.js +263 -0
  39. package/lib/plugins/image/insert-image-handler.js +117 -0
  40. package/lib/plugins/index.js +413 -0
  41. package/lib/plugins/list/__tests__/index.test.js +79 -0
  42. package/lib/plugins/list/index.js +334 -0
  43. package/lib/plugins/math/__tests__/index.test.js +300 -0
  44. package/lib/plugins/math/index.js +454 -0
  45. package/lib/plugins/media/__tests__/index.test.js +71 -0
  46. package/lib/plugins/media/index.js +387 -0
  47. package/lib/plugins/media/media-dialog.js +709 -0
  48. package/lib/plugins/media/media-toolbar.js +101 -0
  49. package/lib/plugins/media/media-wrapper.js +93 -0
  50. package/lib/plugins/rendering/index.js +46 -0
  51. package/lib/plugins/respArea/drag-in-the-blank/choice.js +289 -0
  52. package/lib/plugins/respArea/drag-in-the-blank/index.js +94 -0
  53. package/lib/plugins/respArea/explicit-constructed-response/index.js +120 -0
  54. package/lib/plugins/respArea/icons/index.js +95 -0
  55. package/lib/plugins/respArea/index.js +341 -0
  56. package/lib/plugins/respArea/inline-dropdown/index.js +126 -0
  57. package/lib/plugins/respArea/math-templated/index.js +130 -0
  58. package/lib/plugins/respArea/utils.js +125 -0
  59. package/lib/plugins/table/CustomTablePlugin.js +133 -0
  60. package/lib/plugins/table/__tests__/index.test.js +442 -0
  61. package/lib/plugins/table/__tests__/table-toolbar.test.js +54 -0
  62. package/lib/plugins/table/icons/index.js +69 -0
  63. package/lib/plugins/table/index.js +483 -0
  64. package/lib/plugins/table/table-toolbar.js +187 -0
  65. package/lib/plugins/textAlign/icons/index.js +194 -0
  66. package/lib/plugins/textAlign/index.js +34 -0
  67. package/lib/plugins/toolbar/__tests__/default-toolbar.test.js +128 -0
  68. package/lib/plugins/toolbar/__tests__/editor-and-toolbar.test.js +51 -0
  69. package/lib/plugins/toolbar/__tests__/toolbar-buttons.test.js +54 -0
  70. package/lib/plugins/toolbar/__tests__/toolbar.test.js +120 -0
  71. package/lib/plugins/toolbar/default-toolbar.js +229 -0
  72. package/lib/plugins/toolbar/done-button.js +53 -0
  73. package/lib/plugins/toolbar/editor-and-toolbar.js +286 -0
  74. package/lib/plugins/toolbar/index.js +34 -0
  75. package/lib/plugins/toolbar/toolbar-buttons.js +194 -0
  76. package/lib/plugins/toolbar/toolbar.js +376 -0
  77. package/lib/plugins/utils.js +62 -0
  78. package/lib/serialization.js +677 -0
  79. package/lib/shared/alert-dialog.js +75 -0
  80. package/lib/theme.js +9 -0
  81. package/package.json +69 -0
  82. package/src/__tests__/editor.test.jsx +363 -0
  83. package/src/__tests__/serialization.test.js +291 -0
  84. package/src/__tests__/utils.js +36 -0
  85. package/src/block-tags.js +17 -0
  86. package/src/constants.js +7 -0
  87. package/src/editor.jsx +1197 -0
  88. package/src/extensions/characters.js +46 -0
  89. package/src/extensions/component.jsx +294 -0
  90. package/src/extensions/css.js +217 -0
  91. package/src/extensions/custom-toolbar-wrapper.jsx +100 -0
  92. package/src/extensions/image.js +55 -0
  93. package/src/extensions/math.js +259 -0
  94. package/src/extensions/media.js +182 -0
  95. package/src/extensions/responseArea.js +205 -0
  96. package/src/index.jsx +1462 -0
  97. package/src/old-index.jsx +162 -0
  98. package/src/parse-html.js +8 -0
  99. package/src/plugins/README.md +27 -0
  100. package/src/plugins/characters/custom-popper.js +48 -0
  101. package/src/plugins/characters/index.jsx +284 -0
  102. package/src/plugins/characters/utils.js +447 -0
  103. package/src/plugins/css/icons/index.jsx +17 -0
  104. package/src/plugins/css/index.jsx +340 -0
  105. package/src/plugins/customPlugin/index.jsx +85 -0
  106. package/src/plugins/html/icons/index.jsx +19 -0
  107. package/src/plugins/html/index.jsx +72 -0
  108. package/src/plugins/image/__tests__/__snapshots__/component.test.jsx.snap +51 -0
  109. package/src/plugins/image/__tests__/__snapshots__/image-toolbar-logic.test.jsx.snap +27 -0
  110. package/src/plugins/image/__tests__/__snapshots__/image-toolbar.test.jsx.snap +44 -0
  111. package/src/plugins/image/__tests__/component.test.jsx +41 -0
  112. package/src/plugins/image/__tests__/image-toolbar-logic.test.jsx +42 -0
  113. package/src/plugins/image/__tests__/image-toolbar.test.jsx +11 -0
  114. package/src/plugins/image/__tests__/index.test.js +95 -0
  115. package/src/plugins/image/__tests__/insert-image-handler.test.js +113 -0
  116. package/src/plugins/image/__tests__/mock-change.js +15 -0
  117. package/src/plugins/image/alt-dialog.jsx +82 -0
  118. package/src/plugins/image/component.jsx +343 -0
  119. package/src/plugins/image/image-toolbar.jsx +100 -0
  120. package/src/plugins/image/index.jsx +227 -0
  121. package/src/plugins/image/insert-image-handler.js +79 -0
  122. package/src/plugins/index.jsx +377 -0
  123. package/src/plugins/list/__tests__/index.test.js +54 -0
  124. package/src/plugins/list/index.jsx +305 -0
  125. package/src/plugins/math/__tests__/__snapshots__/index.test.jsx.snap +48 -0
  126. package/src/plugins/math/__tests__/index.test.jsx +245 -0
  127. package/src/plugins/math/index.jsx +379 -0
  128. package/src/plugins/media/__tests__/index.test.js +75 -0
  129. package/src/plugins/media/index.jsx +325 -0
  130. package/src/plugins/media/media-dialog.js +624 -0
  131. package/src/plugins/media/media-toolbar.jsx +56 -0
  132. package/src/plugins/media/media-wrapper.jsx +43 -0
  133. package/src/plugins/rendering/index.js +31 -0
  134. package/src/plugins/respArea/drag-in-the-blank/choice.jsx +215 -0
  135. package/src/plugins/respArea/drag-in-the-blank/index.jsx +70 -0
  136. package/src/plugins/respArea/explicit-constructed-response/index.jsx +92 -0
  137. package/src/plugins/respArea/icons/index.jsx +71 -0
  138. package/src/plugins/respArea/index.jsx +299 -0
  139. package/src/plugins/respArea/inline-dropdown/index.jsx +108 -0
  140. package/src/plugins/respArea/math-templated/index.jsx +104 -0
  141. package/src/plugins/respArea/utils.jsx +90 -0
  142. package/src/plugins/table/CustomTablePlugin.js +113 -0
  143. package/src/plugins/table/__tests__/__snapshots__/table-toolbar.test.jsx.snap +44 -0
  144. package/src/plugins/table/__tests__/index.test.jsx +401 -0
  145. package/src/plugins/table/__tests__/table-toolbar.test.jsx +42 -0
  146. package/src/plugins/table/icons/index.jsx +53 -0
  147. package/src/plugins/table/index.jsx +427 -0
  148. package/src/plugins/table/table-toolbar.jsx +136 -0
  149. package/src/plugins/textAlign/icons/index.jsx +114 -0
  150. package/src/plugins/textAlign/index.jsx +23 -0
  151. package/src/plugins/toolbar/__tests__/__snapshots__/default-toolbar.test.jsx.snap +923 -0
  152. package/src/plugins/toolbar/__tests__/__snapshots__/editor-and-toolbar.test.jsx.snap +20 -0
  153. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar-buttons.test.jsx.snap +36 -0
  154. package/src/plugins/toolbar/__tests__/__snapshots__/toolbar.test.jsx.snap +46 -0
  155. package/src/plugins/toolbar/__tests__/default-toolbar.test.jsx +94 -0
  156. package/src/plugins/toolbar/__tests__/editor-and-toolbar.test.jsx +37 -0
  157. package/src/plugins/toolbar/__tests__/toolbar-buttons.test.jsx +51 -0
  158. package/src/plugins/toolbar/__tests__/toolbar.test.jsx +106 -0
  159. package/src/plugins/toolbar/default-toolbar.jsx +206 -0
  160. package/src/plugins/toolbar/done-button.jsx +38 -0
  161. package/src/plugins/toolbar/editor-and-toolbar.jsx +257 -0
  162. package/src/plugins/toolbar/index.jsx +23 -0
  163. package/src/plugins/toolbar/toolbar-buttons.jsx +138 -0
  164. package/src/plugins/toolbar/toolbar.jsx +338 -0
  165. package/src/plugins/utils.js +31 -0
  166. package/src/serialization.jsx +621 -0
  167. package/src/theme.js +1 -0
package/src/index.jsx ADDED
@@ -0,0 +1,1462 @@
1
+ import React, { useEffect, useMemo, useRef, useState } from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import { createRoot } from 'react-dom/client';
4
+ import { NodeSelection } from 'prosemirror-state';
5
+ import { TextStyleKit } from '@tiptap/extension-text-style';
6
+ import { EditorContent, useEditor, useEditorState } from '@tiptap/react';
7
+ import StarterKit from '@tiptap/starter-kit';
8
+ import { Table } from '@tiptap/extension-table';
9
+ import { TableRow } from '@tiptap/extension-table-row';
10
+ import { TableCell } from '@tiptap/extension-table-cell';
11
+ import { TableHeader } from '@tiptap/extension-table-header';
12
+ import SuperScript from '@tiptap/extension-superscript';
13
+ import SubScript from '@tiptap/extension-subscript';
14
+ import TextAlign from '@tiptap/extension-text-align';
15
+ import Image from '@tiptap/extension-image';
16
+
17
+ import { withStyles } from '@material-ui/core/styles';
18
+ import {
19
+ ExplicitConstructedResponseNode,
20
+ DragInTheBlankNode,
21
+ InlineDropdownNode,
22
+ ResponseAreaExtension,
23
+ } from './extensions/responseArea';
24
+ import { MathNode } from './extensions/math';
25
+ import classNames from 'classnames';
26
+ import { color } from '@pie-lib/render-ui';
27
+ import { primary } from './theme';
28
+ import { PIE_TOOLBAR__CLASS } from './constants';
29
+ import { DoneButton } from './plugins/toolbar/done-button';
30
+ import Bold from '@material-ui/icons/FormatBold';
31
+ import Italic from '@material-ui/icons/FormatItalic';
32
+ import Strikethrough from '@material-ui/icons/FormatStrikethrough';
33
+ import Code from '@material-ui/icons/Code';
34
+ import GridOn from '@material-ui/icons/GridOn';
35
+ import BulletedListIcon from '@material-ui/icons/FormatListBulleted';
36
+ import NumberedListIcon from '@material-ui/icons/FormatListNumbered';
37
+ import Underline from '@material-ui/icons/FormatUnderlined';
38
+ import Functions from '@material-ui/icons/Functions';
39
+ import ImageIcon from '@material-ui/icons/Image';
40
+ import { ToolbarIcon } from './plugins/respArea/icons';
41
+ import Redo from '@material-ui/icons/Redo';
42
+ import Undo from '@material-ui/icons/Undo';
43
+ import TheatersIcon from "@material-ui/icons/Theaters";
44
+ import VolumeUpIcon from "@material-ui/icons/VolumeUp";
45
+ import { characterIcons, spanishConfig, specialConfig } from './plugins/characters/utils';
46
+ import PropTypes from 'prop-types';
47
+ import { MathToolbar, PureToolbar } from '@pie-lib/math-toolbar';
48
+ import get from 'lodash/get';
49
+ import CustomPopper from './plugins/characters/custom-popper';
50
+ import TextAlignIcon from './plugins/textAlign/icons';
51
+ import CSSIcon from './plugins/css/icons';
52
+
53
+ import { ImageUploadNode } from './extensions/image';
54
+ import { Media } from './extensions/media';
55
+ import { CSSMark } from "./extensions/css";
56
+
57
+ const CharacterIcon = ({ letter }) => (
58
+ <div
59
+ style={{
60
+ fontSize: '24px',
61
+ lineHeight: '24px',
62
+ }}
63
+ >
64
+ {letter}
65
+ </div>
66
+ );
67
+
68
+ CharacterIcon.propTypes = {
69
+ letter: PropTypes.string,
70
+ };
71
+
72
+ export function CharacterPicker({ editor, opts, onClose }) {
73
+ if (!opts?.characters?.length) {
74
+ return null;
75
+ }
76
+
77
+ const containerRef = useRef(null);
78
+ const [position, setPosition] = useState({ top: 0, left: 0 });
79
+ const [popover, setPopover] = useState(null);
80
+
81
+ let configToUse;
82
+
83
+ switch (true) {
84
+ case opts.language === 'spanish':
85
+ configToUse = spanishConfig;
86
+ break;
87
+ case opts.language === 'special':
88
+ configToUse = specialConfig;
89
+ break;
90
+ default:
91
+ configToUse = opts;
92
+ }
93
+
94
+ const layoutForCharacters = configToUse.characters.reduce(
95
+ (obj, arr) => {
96
+ if (arr.length >= obj.columns) {
97
+ obj.columns = arr.length;
98
+ }
99
+
100
+ return obj;
101
+ },
102
+ { rows: configToUse.characters.length, columns: 0 },
103
+ );
104
+
105
+ useEffect(() => {
106
+ return () => {
107
+ closePopOver();
108
+ };
109
+ }, []);
110
+
111
+ useEffect(() => {
112
+ if (!editor) return;
113
+
114
+ // Calculate position relative to selection
115
+ const bodyRect = document.body.getBoundingClientRect();
116
+ const { from } = editor.state.selection;
117
+ const start = editor.view.coordsAtPos(from);
118
+ setPosition({
119
+ top: start.top + Math.abs(bodyRect.top) + 40, // shift above
120
+ left: start.left,
121
+ });
122
+
123
+ const handleClickOutside = (e) => {
124
+ if (containerRef.current && !containerRef.current.contains(e.target) && !editor.view.dom.contains(e.target)) {
125
+ onClose();
126
+ }
127
+ };
128
+
129
+ document.addEventListener('mousedown', handleClickOutside);
130
+ return () => document.removeEventListener('mousedown', handleClickOutside);
131
+ }, [editor]);
132
+
133
+ const renderPopOver = (event, el) => setPopover({ anchorEl: event.currentTarget, el });
134
+ const closePopOver = () => setPopover(null);
135
+
136
+ const handleChange = (val) => {
137
+ if (typeof val === 'string') {
138
+ editor
139
+ .chain()
140
+ .focus()
141
+ .insertContent(val)
142
+ .run();
143
+ }
144
+ };
145
+
146
+ return (
147
+ <React.Fragment>
148
+ {ReactDOM.createPortal(
149
+ <div
150
+ ref={containerRef}
151
+ className="insert-character-dialog"
152
+ style={{
153
+ position: 'absolute',
154
+ top: `${position.top}px`,
155
+ left: `${position.left}px`,
156
+ maxWidth: '500px',
157
+ }}
158
+ >
159
+ <div>
160
+ <PureToolbar
161
+ keyPadCharacterRef={opts.keyPadCharacterRef}
162
+ setKeypadInteraction={opts.setKeypadInteraction}
163
+ autoFocus
164
+ noDecimal
165
+ hideInput
166
+ noLatexHandling
167
+ hideDoneButtonBackground
168
+ layoutForKeyPad={layoutForCharacters}
169
+ additionalKeys={configToUse.characters.reduce((arr, n) => {
170
+ arr = [
171
+ ...arr,
172
+ ...n.map((k) => ({
173
+ name: get(k, 'name') || k,
174
+ write: get(k, 'write') || k,
175
+ label: get(k, 'label') || k,
176
+ category: 'character',
177
+ extraClass: 'character',
178
+ extraProps: {
179
+ ...(k.extraProps || {}),
180
+ style: {
181
+ ...(k.extraProps || {}).style,
182
+ border: '1px solid #000',
183
+ },
184
+ },
185
+ ...(configToUse.hasPreview
186
+ ? {
187
+ actions: { onMouseEnter: (ev) => renderPopOver(ev, k), onMouseLeave: closePopOver },
188
+ }
189
+ : {}),
190
+ })),
191
+ ];
192
+
193
+ return arr;
194
+ }, [])}
195
+ keypadMode="language"
196
+ onChange={handleChange}
197
+ onDone={onClose}
198
+ />
199
+ </div>
200
+ </div>,
201
+ document.body,
202
+ )}
203
+ {popover &&
204
+ ReactDOM.createPortal(
205
+ <CustomPopper onClose={closePopOver} anchorEl={popover.anchorEl}>
206
+ <div>{popover.el.label}</div>
207
+ <div style={{ fontSize: 20, lineHeight: '20px' }}>{popover.el.description}</div>
208
+ <div style={{ fontSize: 20, lineHeight: '20px' }}>{popover.el.unicode}</div>
209
+ </CustomPopper>,
210
+ document.body,
211
+ )}
212
+ </React.Fragment>
213
+ );
214
+ }
215
+
216
+ const SuperscriptIcon = () => (
217
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="none">
218
+ <path
219
+ 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"
220
+ fill="currentColor"
221
+ />
222
+ </svg>
223
+ );
224
+
225
+ const SubscriptIcon = () => (
226
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="none">
227
+ <path
228
+ 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"
229
+ fill="currentColor"
230
+ />
231
+ </svg>
232
+ );
233
+
234
+ const HeadingIcon = () => (
235
+ <svg
236
+ width="30"
237
+ height="28"
238
+ viewBox="0 0 30 28"
239
+ fill="none"
240
+ xmlns="http://www.w3.org/2000/svg"
241
+ style={{ width: '20px', height: '18px' }}
242
+ >
243
+ <path
244
+ 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"
245
+ fill="currentColor"
246
+ />
247
+ </svg>
248
+ );
249
+
250
+ const ExtendedTable = Table.extend({
251
+ renderHTML(props) {
252
+ const originalTable = this.parent(props);
253
+
254
+ originalTable[1].style = `${originalTable[1].style} width: 100%;
255
+ color: var(--pie-text, black);
256
+ table-layout: fixed;
257
+ border-collapse: collapse;
258
+ background-color: var(--pie-background, rgba(255, 255, 255))`;
259
+ originalTable[1].border = '1';
260
+
261
+ return originalTable;
262
+ },
263
+ });
264
+
265
+ const styles = (theme) => ({
266
+ root: {
267
+ position: 'relative',
268
+ padding: '0px',
269
+ border: '1px solid #ccc',
270
+ borderRadius: '4px',
271
+ cursor: 'text',
272
+ '& [data-slate-editor="true"]': {
273
+ wordBreak: 'break-word',
274
+ overflow: 'visible',
275
+ maxHeight: '500px',
276
+ // needed in order to be able to put the focus before a void element when it is the first one in the editor
277
+ padding: '5px',
278
+ },
279
+
280
+ '&:first-child': {
281
+ marginTop: 0,
282
+ },
283
+
284
+ '& ul, & ol': {
285
+ padding: '0 1rem',
286
+ margin: '1.25rem 1rem 1.25rem 0.4rem',
287
+ },
288
+
289
+ '& ul li p, & ol li p': {
290
+ marginTop: '0.25em',
291
+ marginBottom: '0.25em',
292
+ },
293
+
294
+ '& h1, & h2, & h3, & h4, & h5, & h6': {
295
+ lineHeight: 1.1,
296
+ marginTop: '2.5rem',
297
+ textWrap: 'pretty',
298
+ },
299
+
300
+ '& h1, & h2': {
301
+ marginTop: '3.5rem',
302
+ marginBottom: '1.5rem',
303
+ },
304
+
305
+ '& h1': {
306
+ fontSize: '1.4rem',
307
+ },
308
+
309
+ '& h2': {
310
+ fontSize: '1.2rem',
311
+ },
312
+
313
+ '& h3': {
314
+ fontSize: '1.1rem',
315
+ },
316
+
317
+ '& h4, & h5, & h6': {
318
+ fontSize: '1rem',
319
+ },
320
+
321
+ '& code': {
322
+ backgroundColor: 'var(--purple-light)',
323
+ borderRadius: '0.4rem',
324
+ color: 'var(--black)',
325
+ fontSize: '0.85rem',
326
+ padding: '0.25em 0.3em',
327
+ },
328
+
329
+ '& pre': {
330
+ background: 'var(--black)',
331
+ borderRadius: '0.5rem',
332
+ color: 'var(--white)',
333
+ fontFamily: "'JetBrainsMono', monospace",
334
+ margin: '1.5rem 0',
335
+ padding: '0.75rem 1rem',
336
+
337
+ '& code': {
338
+ background: 'none',
339
+ color: 'inherit',
340
+ fontSize: '0.8rem',
341
+ padding: 0,
342
+ },
343
+ },
344
+
345
+ '& blockquote': {
346
+ borderLeft: '3px solid var(--gray-3)',
347
+ margin: '1.5rem 0',
348
+ paddingLeft: '1rem',
349
+ },
350
+
351
+ '& hr': {
352
+ border: 'none',
353
+ borderTop: '1px solid var(--gray-2)',
354
+ margin: '2rem 0',
355
+ },
356
+ },
357
+ children: {
358
+ padding: '10px 16px',
359
+ },
360
+ editorHolder: {
361
+ position: 'relative',
362
+ padding: '0px',
363
+ // overflowY: 'auto',
364
+ overflow: 'visible',
365
+ color: color.text(),
366
+ backgroundColor: color.background(),
367
+ '&::before': {
368
+ left: '0',
369
+ right: '0',
370
+ bottom: '0',
371
+ height: '1px',
372
+ content: '""',
373
+ position: 'absolute',
374
+ transition: 'background-color 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
375
+ pointerEvents: 'none',
376
+ backgroundColor: 'rgba(0, 0, 0, 0.42)',
377
+ },
378
+ '&::after': {
379
+ left: '0',
380
+ right: '0',
381
+ bottom: '0',
382
+ height: '1px',
383
+ content: '""',
384
+ position: 'absolute',
385
+ transform: 'scaleX(0)',
386
+ transition: 'transform 200ms cubic-bezier(0.0, 0.0, 0.2, 1) 0ms, background-color 200ms linear',
387
+ backgroundColor: 'rgba(0, 0, 0, 0.42)',
388
+ },
389
+ '&:focus': {
390
+ '&::after': {
391
+ transform: 'scaleX(1)',
392
+ backgroundColor: primary,
393
+ height: '2px',
394
+ },
395
+ },
396
+ '&:hover': {
397
+ '&::after': {
398
+ transform: 'scaleX(1)',
399
+ backgroundColor: theme.palette.common.black,
400
+ height: '2px',
401
+ },
402
+ },
403
+ },
404
+ disabledUnderline: {
405
+ '&::before': {
406
+ display: 'none',
407
+ },
408
+ '&::after': {
409
+ display: 'none',
410
+ },
411
+ },
412
+ disabledScrollbar: {
413
+ '&::-webkit-scrollbar': {
414
+ display: 'none',
415
+ },
416
+ scrollbarWidth: 'none',
417
+ '-ms-overflow-style': 'none',
418
+ },
419
+ readOnly: {
420
+ '&::before': {
421
+ background: 'transparent',
422
+ backgroundSize: '5px 1px',
423
+ backgroundImage: 'linear-gradient(to right, rgba(0, 0, 0, 0.42) 33%, transparent 0%)',
424
+ backgroundRepeat: 'repeat-x',
425
+ backgroundPosition: 'left top',
426
+ },
427
+ '&::after': {
428
+ left: '0',
429
+ right: '0',
430
+ bottom: '0',
431
+ height: '1px',
432
+ content: '""',
433
+ position: 'absolute',
434
+ transform: 'scaleX(0)',
435
+ transition: 'transform 200ms cubic-bezier(0.0, 0.0, 0.2, 1) 0ms, background-color 0ms linear',
436
+ backgroundColor: 'rgba(0, 0, 0, 0)',
437
+ },
438
+ '&:hover': {
439
+ '&::after': {
440
+ transform: 'scaleX(0)',
441
+ backgroundColor: theme.palette.common.black,
442
+ height: '2px',
443
+ },
444
+ },
445
+ },
446
+ editorInFocus: {
447
+ '&::after': {
448
+ transform: 'scaleX(1)',
449
+ backgroundColor: primary,
450
+ height: '2px',
451
+ },
452
+ '&:hover': {
453
+ '&::after': {
454
+ backgroundColor: primary,
455
+ },
456
+ },
457
+ },
458
+ error: {
459
+ border: `2px solid ${theme.palette.error.main} !important`,
460
+ },
461
+ noBorder: {
462
+ border: 'none',
463
+ },
464
+ noPadding: {
465
+ padding: 0,
466
+ },
467
+ toolbarOnTop: {
468
+ marginTop: '45px',
469
+ },
470
+ });
471
+
472
+ const defaultResponseAreaProps = {
473
+ options: {},
474
+ respAreaToolbar: () => {},
475
+ onHandleAreaChange: () => {},
476
+ };
477
+
478
+ const valueToSize = (v) => {
479
+ if (!v) {
480
+ return;
481
+ }
482
+ const calcRegex = /^calc\((.*)\)$/;
483
+
484
+ if (typeof v === 'string') {
485
+ if (v.endsWith('%')) {
486
+ return undefined;
487
+ } else if (
488
+ v.endsWith('px') ||
489
+ v.endsWith('vh') ||
490
+ v.endsWith('vw') ||
491
+ v.endsWith('ch') ||
492
+ v.endsWith('em') ||
493
+ v.match(calcRegex)
494
+ ) {
495
+ return v;
496
+ } else {
497
+ const value = parseInt(v, 10);
498
+ return isNaN(value) ? value : `${value}px`;
499
+ }
500
+ }
501
+ if (typeof v === 'number') {
502
+ return `${v}px`;
503
+ }
504
+ };
505
+
506
+ function TiptapContainer(props) {
507
+ const {
508
+ editor,
509
+ editorState,
510
+ classes,
511
+ children,
512
+ disableUnderline,
513
+ disableScrollbar,
514
+ toolbarOpts,
515
+ responseAreaProps,
516
+ autoFocus,
517
+ minWidth,
518
+ width,
519
+ maxWidth,
520
+ minHeight,
521
+ height,
522
+ maxHeight,
523
+ } = props;
524
+ const holderNames = classNames(classes.editorHolder, {
525
+ [classes.editorInFocus]: editorState.isFocused,
526
+ [classes.readOnly]: editorState.readOnly,
527
+ [classes.disabledUnderline]: disableUnderline,
528
+ [classes.disabledScrollbar]: disableScrollbar,
529
+ });
530
+
531
+ useEffect(() => {
532
+ if (editor && autoFocus) {
533
+ Promise.resolve().then(() => {
534
+ editor.commands.focus('end');
535
+ });
536
+ }
537
+ }, [editor, autoFocus]);
538
+
539
+ const sizeStyle = useMemo(() => {
540
+ return {
541
+ width: valueToSize(width),
542
+ minWidth: valueToSize(minWidth),
543
+ maxWidth: valueToSize(maxWidth),
544
+ height: valueToSize(height),
545
+ minHeight: valueToSize(minHeight),
546
+ maxHeight: valueToSize(maxHeight),
547
+ };
548
+ }, [minWidth, width, maxWidth, minHeight, height, maxHeight]);
549
+
550
+ return (
551
+ <div
552
+ className={classNames(
553
+ {
554
+ [classes.noBorder]: toolbarOpts && toolbarOpts.noBorder,
555
+ [classes.error]: toolbarOpts && toolbarOpts.error,
556
+ },
557
+ classes.root,
558
+ props.className,
559
+ )}
560
+ style={{ width: sizeStyle.width, minWidth: sizeStyle.minWidth, maxWidth: sizeStyle.maxWidth }}
561
+ >
562
+ <div className={holderNames}>
563
+ <div
564
+ className={classNames(
565
+ {
566
+ [classes.noPadding]: toolbarOpts && toolbarOpts.noPadding,
567
+ },
568
+ classes.children,
569
+ )}
570
+ >
571
+ {children}
572
+ </div>
573
+ </div>
574
+
575
+ {/*{editorState.isFocused && <MenuBar editor={editor} />}*/}
576
+ {/*<Toolbar editor={editor} editorState={editorState} plugins={plugins} toolbarOpts={toolbarOpts} isFocused={editorState.isFocused} />*/}
577
+ {editor && (
578
+ <StyledMenuBar
579
+ editor={editor}
580
+ responseAreaProps={responseAreaProps}
581
+ toolbarOpts={toolbarOpts}
582
+ onChange={props.onChange}
583
+ />
584
+ )}
585
+ </div>
586
+ );
587
+ }
588
+
589
+ const EditorContainer = withStyles(styles)(TiptapContainer);
590
+
591
+ function MenuBar({ editor, classes, toolbarOpts: toolOpts, responseAreaProps, onChange }) {
592
+ const [showPicker, setShowPicker] = useState(false);
593
+ const toolbarOpts = toolOpts ?? {};
594
+ // Read the current editor's state, and re-render the component when it changes
595
+ const editorState = useEditorState({
596
+ editor,
597
+ selector: (ctx) => {
598
+ const { selection } = ctx.editor?.state || {};
599
+
600
+ let currentNode;
601
+
602
+ if (selection instanceof NodeSelection) {
603
+ currentNode = selection.node; // the selected node
604
+ }
605
+
606
+ const customToolbarActive =
607
+ ctx.editor?.isActive('math') ||
608
+ ctx.editor?.isActive('explicit_constructed_response') ||
609
+ ctx.editor?.isActive('imageUploadNode');
610
+
611
+ return {
612
+ currentNode,
613
+ customToolbarActive,
614
+ isFocused: ctx.editor?.isFocused,
615
+ isBold: ctx.editor.isActive('bold') ?? false,
616
+ canBold:
617
+ ctx.editor
618
+ .can()
619
+ .chain()
620
+ .toggleBold()
621
+ .run() ?? false,
622
+ isTable: ctx.editor.isActive('table') ?? false,
623
+ canTable:
624
+ ctx.editor
625
+ .can()
626
+ .chain()
627
+ .insertTable()
628
+ .run() ?? false,
629
+ isItalic: ctx.editor.isActive('italic') ?? false,
630
+ canItalic:
631
+ ctx.editor
632
+ .can()
633
+ .chain()
634
+ .toggleItalic()
635
+ .run() ?? false,
636
+ isStrike: ctx.editor.isActive('strike') ?? false,
637
+ canStrike:
638
+ ctx.editor
639
+ .can()
640
+ .chain()
641
+ .toggleStrike()
642
+ .run() ?? false,
643
+ isCode: ctx.editor.isActive('code') ?? false,
644
+ canCode:
645
+ ctx.editor
646
+ .can()
647
+ .chain()
648
+ .toggleCode()
649
+ .run() ?? false,
650
+ canClearMarks:
651
+ ctx.editor
652
+ .can()
653
+ .chain()
654
+ .unsetAllMarks()
655
+ .run() ?? false,
656
+ isUnderline: ctx.editor.isActive('underline') ?? false,
657
+ isSubScript: ctx.editor.isActive('subscript') ?? false,
658
+ isSuperScript: ctx.editor.isActive('superscript') ?? false,
659
+ isParagraph: ctx.editor.isActive('paragraph') ?? false,
660
+ isHeading1: ctx.editor.isActive('heading', { level: 1 }) ?? false,
661
+ isHeading2: ctx.editor.isActive('heading', { level: 2 }) ?? false,
662
+ isHeading3: ctx.editor.isActive('heading', { level: 3 }) ?? false,
663
+ isHeading4: ctx.editor.isActive('heading', { level: 4 }) ?? false,
664
+ isHeading5: ctx.editor.isActive('heading', { level: 5 }) ?? false,
665
+ isHeading6: ctx.editor.isActive('heading', { level: 6 }) ?? false,
666
+ isBulletList: ctx.editor.isActive('bulletList') ?? false,
667
+ isOrderedList: ctx.editor.isActive('orderedList') ?? false,
668
+ isCodeBlock: ctx.editor.isActive('codeBlock') ?? false,
669
+ isBlockquote: ctx.editor.isActive('blockquote') ?? false,
670
+ canUndo:
671
+ ctx.editor
672
+ .can()
673
+ .chain()
674
+ .undo()
675
+ .run() ?? false,
676
+ canRedo:
677
+ ctx.editor
678
+ .can()
679
+ .chain()
680
+ .redo()
681
+ .run() ?? false,
682
+ };
683
+ },
684
+ });
685
+ const hasDoneButton = false;
686
+ const autoWidth = false;
687
+
688
+ const names = classNames(classes.toolbar, PIE_TOOLBAR__CLASS, {
689
+ [classes.toolbarWithNoDone]: !hasDoneButton,
690
+ [classes.toolbarTop]: toolbarOpts.position === 'top',
691
+ [classes.toolbarRight]: toolbarOpts.alignment === 'right',
692
+ [classes.focused]: toolbarOpts.alwaysVisible || (editorState.isFocused && !editor._toolbarOpened),
693
+ [classes.autoWidth]: autoWidth,
694
+ [classes.fullWidth]: !autoWidth,
695
+ [classes.hidden]: toolbarOpts.isHidden === true,
696
+ });
697
+ const customStyles = toolbarOpts.minWidth !== undefined ? { minWidth: toolbarOpts.minWidth } : {};
698
+ const handleMouseDown = (e) => {
699
+ e.preventDefault();
700
+ };
701
+
702
+ return (
703
+ <div className={names} style={{ ...customStyles }} onMouseDown={handleMouseDown}>
704
+ {!editorState.customToolbarActive && (
705
+ <div className={classes.defaultToolbar} tabIndex="1">
706
+ <div className={classes.buttonsContainer}>
707
+ <button
708
+ onClick={(e) => {
709
+ e.preventDefault();
710
+ editor
711
+ .chain()
712
+ .focus()
713
+ .insertTable()
714
+ .run();
715
+ }}
716
+ disabled={!editorState.canTable}
717
+ className={classNames(classes.button, { [classes.active]: editorState.isTable })}
718
+ >
719
+ <GridOn />
720
+ </button>
721
+ <button
722
+ onClick={(e) => {
723
+ e.preventDefault();
724
+ editor
725
+ .chain()
726
+ .focus()
727
+ .toggleBold()
728
+ .run();
729
+ }}
730
+ disabled={!editorState.canBold}
731
+ className={classNames(classes.button, { [classes.active]: editorState.isBold })}
732
+ >
733
+ <Bold />
734
+ </button>
735
+ <button
736
+ onClick={() =>
737
+ editor
738
+ .chain()
739
+ .focus()
740
+ .toggleItalic()
741
+ .run()
742
+ }
743
+ disabled={!editorState.canItalic}
744
+ active={editorState.isItalic}
745
+ className={classNames(classes.button, { [classes.active]: editorState.isItalic })}
746
+ >
747
+ <Italic />
748
+ </button>
749
+ <button
750
+ onClick={() =>
751
+ editor
752
+ .chain()
753
+ .focus()
754
+ .toggleStrike()
755
+ .run()
756
+ }
757
+ disabled={!editorState.canStrike}
758
+ active={editorState.isStrike}
759
+ className={classNames(classes.button, { [classes.active]: editorState.isStrike })}
760
+ >
761
+ <Strikethrough />
762
+ </button>
763
+ <button
764
+ onClick={() =>
765
+ editor
766
+ .chain()
767
+ .focus()
768
+ .toggleCode()
769
+ .run()
770
+ }
771
+ disabled={!editorState.canCode}
772
+ active={editorState.isCode}
773
+ className={classNames(classes.button, { [classes.active]: editorState.isCode })}
774
+ >
775
+ <Code />
776
+ </button>
777
+ {/*<button*/}
778
+ {/* onClick={() =>*/}
779
+ {/* editor*/}
780
+ {/* .chain()*/}
781
+ {/* .focus()*/}
782
+ {/* .unsetAllMarks()*/}
783
+ {/* .run()*/}
784
+ {/* }*/}
785
+ {/*>*/}
786
+ {/* Clear marks*/}
787
+ {/*</button>*/}
788
+ {/*<button*/}
789
+ {/* onClick={() =>*/}
790
+ {/* editor*/}
791
+ {/* .chain()*/}
792
+ {/* .focus()*/}
793
+ {/* .clearNodes()*/}
794
+ {/* .run()*/}
795
+ {/* }*/}
796
+ {/*>*/}
797
+ {/* Clear nodes*/}
798
+ {/*</button>*/}
799
+ {/*<button*/}
800
+ {/* onClick={() =>*/}
801
+ {/* editor*/}
802
+ {/* .chain()*/}
803
+ {/* .focus()*/}
804
+ {/* .setParagraph()*/}
805
+ {/* .run()*/}
806
+ {/* }*/}
807
+ {/* className={editorState.isParagraph ? classes.isActive : ''}*/}
808
+ {/*>*/}
809
+ {/* Paragraph*/}
810
+ {/*</button>*/}
811
+ {/*<button*/}
812
+ {/* onClick={() =>*/}
813
+ {/* editor*/}
814
+ {/* .chain()*/}
815
+ {/* .focus()*/}
816
+ {/* .toggleHeading({ level: 1 })*/}
817
+ {/* .run()*/}
818
+ {/* }*/}
819
+ {/* className={editorState.isHeading1 ? classes.isActive : ''}*/}
820
+ {/*>*/}
821
+ {/* H1*/}
822
+ {/*</button>*/}
823
+ {/*<button*/}
824
+ {/* onClick={() =>*/}
825
+ {/* editor*/}
826
+ {/* .chain()*/}
827
+ {/* .focus()*/}
828
+ {/* .toggleHeading({ level: 2 })*/}
829
+ {/* .run()*/}
830
+ {/* }*/}
831
+ {/* className={editorState.isHeading2 ? classes.isActive : ''}*/}
832
+ {/*>*/}
833
+ {/* H2*/}
834
+ {/*</button>*/}
835
+ <button
836
+ onClick={() =>
837
+ editor
838
+ .chain()
839
+ .focus()
840
+ .toggleUnderline()
841
+ .run()
842
+ }
843
+ className={classNames(classes.button, { [classes.active]: editorState.isUnderline })}
844
+ >
845
+ <Underline />
846
+ </button>
847
+ <button
848
+ onClick={() =>
849
+ editor
850
+ .chain()
851
+ .focus()
852
+ .toggleSubscript()
853
+ .run()
854
+ }
855
+ className={classNames(classes.button, { [classes.active]: editorState.isSubScript })}
856
+ >
857
+ <SubscriptIcon />
858
+ </button>
859
+ <button
860
+ onClick={() =>
861
+ editor
862
+ .chain()
863
+ .focus()
864
+ .toggleSuperscript()
865
+ .run()
866
+ }
867
+ className={classNames(classes.button, { [classes.active]: editorState.isSuperScript })}
868
+ >
869
+ <SuperscriptIcon />
870
+ </button>
871
+ <button
872
+ onClick={() =>
873
+ editor
874
+ .chain()
875
+ .focus()
876
+ .setImageUploadNode()
877
+ .run()
878
+ }
879
+ className={classNames(classes.button, { [classes.active]: editorState.isSuperScript })}
880
+ >
881
+ <ImageIcon />
882
+ </button>
883
+ <button
884
+ onClick={() =>
885
+ editor
886
+ .chain()
887
+ .focus()
888
+ .insertMedia({
889
+ tag: 'video',
890
+ })
891
+ .run()
892
+ }
893
+ className={classNames(classes.button)}
894
+ >
895
+ <TheatersIcon />
896
+ </button>
897
+ <button
898
+ onClick={() =>
899
+ editor
900
+ .chain()
901
+ .focus()
902
+ .insertMedia({
903
+ tag: 'audio',
904
+ })
905
+ .run()
906
+ }
907
+ className={classNames(classes.button)}
908
+ >
909
+ <VolumeUpIcon />
910
+ </button>
911
+ <button
912
+ onClick={() =>
913
+ editor.commands.openCSSClassDialog()
914
+ }
915
+ className={classNames(classes.button)}
916
+ >
917
+ <CSSIcon />
918
+ </button>
919
+ <button
920
+ onClick={() =>
921
+ editor
922
+ .chain()
923
+ .focus()
924
+ .toggleHeading({ level: 3 })
925
+ .run()
926
+ }
927
+ className={classNames(classes.button, { [classes.active]: editorState.isHeading3 })}
928
+ >
929
+ <HeadingIcon />
930
+ </button>
931
+ {/*<button*/}
932
+ {/* onClick={() =>*/}
933
+ {/* editor*/}
934
+ {/* .chain()*/}
935
+ {/* .focus()*/}
936
+ {/* .toggleHeading({ level: 3 })*/}
937
+ {/* .run()*/}
938
+ {/* }*/}
939
+ {/* className={classNames(classes.button, { [classes.active]: editorState.isHeading3 })}*/}
940
+ {/*>*/}
941
+ {/* <Image />*/}
942
+ {/*</button>*/}
943
+ {/*<button*/}
944
+ {/* onClick={() =>*/}
945
+ {/* editor*/}
946
+ {/* .chain()*/}
947
+ {/* .focus()*/}
948
+ {/* .toggleHeading({ level: 4 })*/}
949
+ {/* .run()*/}
950
+ {/* }*/}
951
+ {/* className={editorState.isHeading4 ? classes.isActive : ''}*/}
952
+ {/*>*/}
953
+ {/* H4*/}
954
+ {/*</button>*/}
955
+ {/*<button*/}
956
+ {/* onClick={() =>*/}
957
+ {/* editor*/}
958
+ {/* .chain()*/}
959
+ {/* .focus()*/}
960
+ {/* .toggleHeading({ level: 5 })*/}
961
+ {/* .run()*/}
962
+ {/* }*/}
963
+ {/* className={editorState.isHeading5 ? classes.isActive : ''}*/}
964
+ {/*>*/}
965
+ {/* H5*/}
966
+ {/*</button>*/}
967
+ {/*<button*/}
968
+ {/* onClick={() =>*/}
969
+ {/* editor*/}
970
+ {/* .chain()*/}
971
+ {/* .focus()*/}
972
+ {/* .toggleHeading({ level: 6 })*/}
973
+ {/* .run()*/}
974
+ {/* }*/}
975
+ {/* className={editorState.isHeading6 ? classes.isActive : ''}*/}
976
+ {/*>*/}
977
+ {/* H6*/}
978
+ {/*</button>*/}
979
+ <button
980
+ onClick={() =>
981
+ editor
982
+ .chain()
983
+ .focus()
984
+ .insertMath('')
985
+ .run()
986
+ }
987
+ className={classes.button}
988
+ >
989
+ <Functions />
990
+ </button>
991
+ <button onClick={() => setShowPicker(spanishConfig)} className={classes.button}>
992
+ <CharacterIcon letter="ñ" />
993
+ </button>
994
+ <button onClick={() => setShowPicker(specialConfig)} className={classes.button}>
995
+ <CharacterIcon letter="€" />
996
+ </button>
997
+ <button onClick={() => {}} className={classes.button}>
998
+ <TextAlignIcon editor={editor} />
999
+ </button>
1000
+ <button
1001
+ onClick={() =>
1002
+ editor
1003
+ .chain()
1004
+ .focus()
1005
+ .toggleBulletList()
1006
+ .run()
1007
+ }
1008
+ className={classNames(classes.button, { [classes.active]: editorState.isBulletList })}
1009
+ >
1010
+ <BulletedListIcon />
1011
+ </button>
1012
+ <button
1013
+ onClick={() =>
1014
+ editor
1015
+ .chain()
1016
+ .focus()
1017
+ .toggleOrderedList()
1018
+ .run()
1019
+ }
1020
+ className={classNames(classes.button, { [classes.active]: editorState.isOrderedList })}
1021
+ >
1022
+ <NumberedListIcon />
1023
+ </button>
1024
+ {/*<button*/}
1025
+ {/* onClick={() =>*/}
1026
+ {/* editor*/}
1027
+ {/* .chain()*/}
1028
+ {/* .focus()*/}
1029
+ {/* .toggleCodeBlock()*/}
1030
+ {/* .run()*/}
1031
+ {/* }*/}
1032
+ {/* className={classNames(classes.button, { [classes.active]: editorState.isCodeBlock })}*/}
1033
+ {/*>*/}
1034
+ {/* Code block*/}
1035
+ {/*</button>*/}
1036
+ {/*<button*/}
1037
+ {/* onClick={() =>*/}
1038
+ {/* editor*/}
1039
+ {/* .chain()*/}
1040
+ {/* .focus()*/}
1041
+ {/* .toggleBlockquote()*/}
1042
+ {/* .run()*/}
1043
+ {/* }*/}
1044
+ {/* className={classNames(classes.button, { [classes.active]: editorState.isBlockquote })}*/}
1045
+ {/*>*/}
1046
+ {/* Blockquote*/}
1047
+ {/*</button>*/}
1048
+ {/*<button*/}
1049
+ {/* onClick={() =>*/}
1050
+ {/* editor*/}
1051
+ {/* .chain()*/}
1052
+ {/* .focus()*/}
1053
+ {/* .setHorizontalRule()*/}
1054
+ {/* .run()*/}
1055
+ {/* }*/}
1056
+ {/*>*/}
1057
+ {/* Horizontal rule*/}
1058
+ {/*</button>*/}
1059
+ {/*<button*/}
1060
+ {/* onClick={() =>*/}
1061
+ {/* editor*/}
1062
+ {/* .chain()*/}
1063
+ {/* .focus()*/}
1064
+ {/* .setHardBreak()*/}
1065
+ {/* .run()*/}
1066
+ {/* }*/}
1067
+ {/*>*/}
1068
+ {/* Hard break*/}
1069
+ {/*</button>*/}
1070
+ <button
1071
+ onClick={() =>
1072
+ editor
1073
+ .chain()
1074
+ .focus()
1075
+ .undo()
1076
+ .run()
1077
+ }
1078
+ disabled={!editorState.canUndo}
1079
+ className={classes.button}
1080
+ >
1081
+ <Undo />
1082
+ </button>
1083
+ <button
1084
+ onClick={() =>
1085
+ editor
1086
+ .chain()
1087
+ .focus()
1088
+ .redo()
1089
+ .run()
1090
+ }
1091
+ disabled={!editorState.canRedo}
1092
+ className={classes.button}
1093
+ >
1094
+ <Redo />
1095
+ </button>
1096
+ </div>
1097
+ <button
1098
+ onClick={() => {
1099
+ editor
1100
+ .chain()
1101
+ .focus()
1102
+ .insertResponseArea(responseAreaProps.type)
1103
+ .run();
1104
+ }}
1105
+ className={classes.button}
1106
+ >
1107
+ <ToolbarIcon />
1108
+ </button>
1109
+
1110
+ <DoneButton
1111
+ onClick={() => {
1112
+ onChange?.(editor.getHTML());
1113
+ editor.commands.blur();
1114
+ }}
1115
+ />
1116
+ </div>
1117
+ )}
1118
+ {showPicker && (
1119
+ <CharacterPicker
1120
+ editor={editor}
1121
+ opts={{
1122
+ ...showPicker,
1123
+ renderPopOver: (ev, ch) => console.log('Show popover', ch),
1124
+ closePopOver: () => console.log('Close popover'),
1125
+ }}
1126
+ onClose={() => setShowPicker(false)}
1127
+ />
1128
+ )}
1129
+ </div>
1130
+ );
1131
+ }
1132
+
1133
+ const style = (theme) => ({
1134
+ defaultToolbar: {
1135
+ display: 'flex',
1136
+ width: '100%',
1137
+ justifyContent: 'space-between',
1138
+ },
1139
+ buttonsContainer: {
1140
+ alignItems: 'center',
1141
+ display: 'flex',
1142
+ width: '100%',
1143
+ },
1144
+ button: {
1145
+ color: 'grey',
1146
+ display: 'inline-flex',
1147
+ padding: '2px',
1148
+ background: 'none',
1149
+ border: 'none',
1150
+ cursor: 'pointer',
1151
+ '&:hover': {
1152
+ color: 'black',
1153
+ },
1154
+ '&:focus': {
1155
+ outline: `2px solid ${theme.palette.grey[700]}`,
1156
+ },
1157
+ },
1158
+ active: {
1159
+ color: 'black',
1160
+ },
1161
+ disabled: {
1162
+ opacity: 0.7,
1163
+ cursor: 'not-allowed',
1164
+ '& :hover': {
1165
+ color: 'grey',
1166
+ },
1167
+ },
1168
+ isActive: {
1169
+ background: 'var(--purple)',
1170
+ color: 'var(--white)',
1171
+ },
1172
+ toolbar: {
1173
+ position: 'absolute',
1174
+ zIndex: 20,
1175
+ cursor: 'pointer',
1176
+ justifyContent: 'space-between',
1177
+ background: 'var(--editable-html-toolbar-bg, #efefef)',
1178
+ minWidth: '280px',
1179
+ margin: '5px 0 0 0',
1180
+ padding: '2px',
1181
+ boxShadow:
1182
+ '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)',
1183
+ boxSizing: 'border-box',
1184
+ display: 'flex',
1185
+ opacity: 0,
1186
+ pointerEvents: 'none',
1187
+ },
1188
+ toolbarWithNoDone: {
1189
+ minWidth: '265px',
1190
+ },
1191
+ toolbarTop: {
1192
+ top: '-45px',
1193
+ },
1194
+ toolbarRight: {
1195
+ right: 0,
1196
+ },
1197
+ fullWidth: {
1198
+ width: '100%',
1199
+ },
1200
+ hidden: {
1201
+ visibility: 'hidden',
1202
+ },
1203
+ autoWidth: {
1204
+ width: 'auto',
1205
+ },
1206
+ focused: {
1207
+ opacity: 1,
1208
+ pointerEvents: 'auto',
1209
+ },
1210
+ iconRoot: {
1211
+ width: '28px',
1212
+ height: '28px',
1213
+ padding: '4px',
1214
+ verticalAlign: 'top',
1215
+ },
1216
+ label: {
1217
+ color: 'var(--editable-html-toolbar-check, #00bb00)',
1218
+ },
1219
+ shared: {
1220
+ display: 'flex',
1221
+ },
1222
+ });
1223
+ const StyledMenuBar = withStyles(style, { index: 1000 })(MenuBar);
1224
+
1225
+ const defaultToolbarOpts = {
1226
+ position: 'bottom',
1227
+ alignment: 'left',
1228
+ alwaysVisible: false,
1229
+ showDone: true,
1230
+ doneOn: 'blur',
1231
+ };
1232
+
1233
+ export const EditableHtml = (props) => {
1234
+ const [pendingImages, setPendingImages] = useState([]);
1235
+ const [scheduled, setScheduled] = useState(false);
1236
+ const { classes, toolbarOpts } = props;
1237
+ const toolbarOptsToUse = {
1238
+ ...defaultToolbarOpts,
1239
+ ...toolbarOpts,
1240
+ };
1241
+ const extensions = [
1242
+ TextStyleKit,
1243
+ StarterKit,
1244
+ ExtendedTable,
1245
+ TableRow,
1246
+ TableHeader,
1247
+ TableCell,
1248
+ ResponseAreaExtension,
1249
+ ExplicitConstructedResponseNode.configure(props.responseAreaProps),
1250
+ DragInTheBlankNode.configure(props.responseAreaProps),
1251
+ InlineDropdownNode.configure(props.responseAreaProps),
1252
+ MathNode.configure({
1253
+ toolbarOpts: toolbarOptsToUse,
1254
+ }),
1255
+ SubScript,
1256
+ SuperScript,
1257
+ TextAlign.configure({
1258
+ types: ['heading', 'paragraph'],
1259
+ alignments: ['left', 'right', 'center'],
1260
+ }),
1261
+ Image,
1262
+ ImageUploadNode.configure({
1263
+ toolbarOpts: toolbarOptsToUse,
1264
+ imageHandling: {
1265
+ disableImageAlignmentButtons: props.disableImageAlignmentButtons,
1266
+ onDelete:
1267
+ props.imageSupport &&
1268
+ props.imageSupport.delete &&
1269
+ ((node, done) => {
1270
+ const { src } = node.attrs;
1271
+
1272
+ props.imageSupport.delete(src, (e) => {
1273
+ const newPendingImages = pendingImages.filter((img) => img.key !== node.key);
1274
+ const newState = {
1275
+ pendingImages: newPendingImages,
1276
+ scheduled: scheduled && newPendingImages.length === 0 ? false : scheduled,
1277
+ };
1278
+
1279
+ setPendingImages(newState.pendingImages);
1280
+ setScheduled(newState.scheduled);
1281
+ done();
1282
+ });
1283
+ }),
1284
+ insertImageRequested:
1285
+ props.imageSupport &&
1286
+ ((addedImage, getHandler) => {
1287
+ const onFinish = (result) => {
1288
+ let cb;
1289
+
1290
+ if (scheduled && result) {
1291
+ // finish editing only on success
1292
+ cb = props.onChange;
1293
+ }
1294
+
1295
+ const newPendingImages = pendingImages.filter((img) => img.key !== addedImage.key);
1296
+ const newState = {
1297
+ pendingImages: newPendingImages,
1298
+ };
1299
+
1300
+ if (newPendingImages.length === 0) {
1301
+ newState.scheduled = false;
1302
+ }
1303
+
1304
+ setPendingImages(newState.pendingImages);
1305
+ setScheduled(newState.scheduled);
1306
+ cb?.(editor.getHTML());
1307
+ };
1308
+ const callback = () => {
1309
+ /**
1310
+ * The handler is the object through which the outer context
1311
+ * communicates file upload events like: fileChosen, cancel, progress
1312
+ */
1313
+ const handler = getHandler(onFinish);
1314
+ props.imageSupport.add(handler);
1315
+ };
1316
+
1317
+ setPendingImages([...pendingImages, addedImage]);
1318
+ callback();
1319
+ }),
1320
+ maxImageWidth: props.maxImageWidth,
1321
+ maxImageHeight: props.maxImageHeight,
1322
+ },
1323
+ limit: 3,
1324
+ }),
1325
+ Media.configure({
1326
+ uploadSoundSupport: props.uploadSoundSupport,
1327
+ }),
1328
+ CSSMark.configure({
1329
+ extraCSSRules: props.extraCSSRules
1330
+ }),
1331
+ ];
1332
+ const editor = useEditor({
1333
+ extensions,
1334
+ immediatelyRender: false,
1335
+ content: props.markup,
1336
+ onUpdate: ({ editor, transaction }) => transaction.isDone && props.onChange?.(editor.getHTML()),
1337
+ onBlur: ({ editor }) => {
1338
+ if (toolbarOptsToUse.doneOn === 'blur') {
1339
+ props.onChange?.(editor.getHTML());
1340
+ } else {
1341
+ props.onDone?.(editor.getHTML());
1342
+ }
1343
+ },
1344
+ });
1345
+
1346
+ useEffect(() => {
1347
+ if (!editor) {
1348
+ return;
1349
+ }
1350
+
1351
+ if (props.markup !== editor.getHTML()) {
1352
+ editor.commands.setContent(props.markup, false); // false = don’t emit update
1353
+ }
1354
+ }, [props.markup, editor]);
1355
+
1356
+ useEffect(() => {
1357
+ // Define your variables in a JS object
1358
+ const cssVariables = {
1359
+ '--white': '#fff',
1360
+ '--black': '#2e2b29',
1361
+ '--black-contrast': '#110f0e',
1362
+ '--gray-1': 'rgba(61, 37, 20, .05)',
1363
+ '--gray-2': 'rgba(61, 37, 20, .08)',
1364
+ '--gray-3': 'rgba(61, 37, 20, .12)',
1365
+ '--gray-4': 'rgba(53, 38, 28, .3)',
1366
+ '--gray-5': 'rgba(28, 25, 23, .6)',
1367
+ '--green': '#22c55e',
1368
+ '--purple': '#6a00f5',
1369
+ '--purple-contrast': '#5800cc',
1370
+ '--purple-light': 'rgba(88, 5, 255, .05)',
1371
+ '--yellow-contrast': '#facc15',
1372
+ '--yellow': 'rgba(250, 204, 21, .4)',
1373
+ '--yellow-light': '#fffae5',
1374
+ '--red': '#ff5c33',
1375
+ '--red-light': '#ffebe5',
1376
+ '--shadow': `0px 12px 33px 0px rgba(0, 0, 0, .06),
1377
+ 0px 3.618px 9.949px 0px rgba(0, 0, 0, .04)`,
1378
+ };
1379
+
1380
+ Object.entries(cssVariables).forEach(([key, value]) => {
1381
+ document.documentElement.style.setProperty(key, value);
1382
+ });
1383
+ }, []);
1384
+
1385
+ const editorState = useEditorState({
1386
+ editor,
1387
+ selector: (ctx) => {
1388
+ return {
1389
+ isFocused: ctx.editor?.isFocused,
1390
+ };
1391
+ },
1392
+ });
1393
+
1394
+ const valueToSize = (v) => {
1395
+ if (!v) {
1396
+ return;
1397
+ }
1398
+ const calcRegex = /^calc\((.*)\)$/;
1399
+
1400
+ if (typeof v === 'string') {
1401
+ if (v.endsWith('%')) {
1402
+ return undefined;
1403
+ } else if (
1404
+ v.endsWith('px') ||
1405
+ v.endsWith('vh') ||
1406
+ v.endsWith('vw') ||
1407
+ v.endsWith('ch') ||
1408
+ v.endsWith('em') ||
1409
+ v.match(calcRegex)
1410
+ ) {
1411
+ return v;
1412
+ } else {
1413
+ const value = parseInt(v, 10);
1414
+ return isNaN(value) ? value : `${value}px`;
1415
+ }
1416
+ }
1417
+ if (typeof v === 'number') {
1418
+ return `${v}px`;
1419
+ }
1420
+ };
1421
+
1422
+ const sizeStyle = useMemo(() => {
1423
+ const { minWidth, width, maxWidth, minHeight, height, maxHeight } = props;
1424
+
1425
+ return {
1426
+ width: valueToSize(width),
1427
+ minWidth: valueToSize(minWidth),
1428
+ maxWidth: valueToSize(maxWidth),
1429
+ height: valueToSize(height),
1430
+ minHeight: valueToSize(minHeight),
1431
+ maxHeight: valueToSize(maxHeight),
1432
+ };
1433
+ }, [props]);
1434
+
1435
+ return (
1436
+ <EditorContainer {...{ ...props, toolbarOpts: toolbarOptsToUse }} editorState={editorState} editor={editor}>
1437
+ {editor && (
1438
+ <EditorContent
1439
+ style={{
1440
+ minHeight: sizeStyle.minHeight,
1441
+ height: sizeStyle.height,
1442
+ maxHeight: sizeStyle.maxHeight,
1443
+ }}
1444
+ className={classes.root}
1445
+ editor={editor}
1446
+ />
1447
+ )}
1448
+ </EditorContainer>
1449
+ );
1450
+ };
1451
+
1452
+ const StyledEditor = withStyles({
1453
+ root: {
1454
+ outline: 'none !important',
1455
+ '& .ProseMirror': {
1456
+ outline: 'none !important',
1457
+ position: 'initial',
1458
+ },
1459
+ },
1460
+ })(EditableHtml);
1461
+
1462
+ export default StyledEditor;