@pie-lib/editable-html-tip-tap 2.1.1 → 2.1.2-next.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 (272) hide show
  1. package/dist/components/CharacterPicker.d.ts +31 -0
  2. package/dist/components/CharacterPicker.js +131 -0
  3. package/dist/components/EditableHtml.d.ts +11 -0
  4. package/dist/components/EditableHtml.js +291 -0
  5. package/dist/components/MenuBar.d.ts +11 -0
  6. package/dist/components/MenuBar.js +462 -0
  7. package/dist/components/TiptapContainer.d.ts +11 -0
  8. package/dist/components/TiptapContainer.js +154 -0
  9. package/dist/components/characters/characterUtils.d.ts +35 -0
  10. package/dist/components/characters/characterUtils.js +465 -0
  11. package/dist/components/characters/custom-popper.d.ts +14 -0
  12. package/dist/components/characters/custom-popper.js +32 -0
  13. package/dist/components/common/done-button.d.ts +30 -0
  14. package/dist/components/common/done-button.js +26 -0
  15. package/dist/components/common/toolbar-buttons.d.ts +38 -0
  16. package/dist/components/common/toolbar-buttons.js +91 -0
  17. package/dist/components/icons/CssIcon.d.ts +11 -0
  18. package/dist/components/icons/CssIcon.js +14 -0
  19. package/dist/components/icons/RespArea.d.ts +26 -0
  20. package/dist/components/icons/RespArea.js +42 -0
  21. package/dist/components/icons/TableIcons.d.ts +14 -0
  22. package/dist/components/icons/TableIcons.js +32 -0
  23. package/dist/components/icons/TextAlign.d.ts +18 -0
  24. package/dist/components/icons/TextAlign.js +134 -0
  25. package/dist/components/image/AltDialog.d.ts +22 -0
  26. package/dist/components/image/AltDialog.js +61 -0
  27. package/dist/components/image/ImageToolbar.d.ts +24 -0
  28. package/dist/components/image/ImageToolbar.js +80 -0
  29. package/dist/components/image/InsertImageHandler.d.ts +32 -0
  30. package/dist/components/image/InsertImageHandler.js +53 -0
  31. package/dist/components/media/MediaDialog.d.ts +43 -0
  32. package/dist/components/media/MediaDialog.js +389 -0
  33. package/dist/components/media/MediaToolbar.d.ts +19 -0
  34. package/dist/components/media/MediaToolbar.js +41 -0
  35. package/dist/components/media/MediaWrapper.d.ts +19 -0
  36. package/dist/components/respArea/DragInTheBlank/DragInTheBlank.d.ts +23 -0
  37. package/dist/components/respArea/DragInTheBlank/DragInTheBlank.js +58 -0
  38. package/dist/components/respArea/DragInTheBlank/choice.d.ts +56 -0
  39. package/dist/components/respArea/DragInTheBlank/choice.js +156 -0
  40. package/dist/components/respArea/ExplicitConstructedResponse.d.ts +20 -0
  41. package/dist/components/respArea/ExplicitConstructedResponse.js +83 -0
  42. package/dist/components/respArea/InlineDropdown.d.ts +18 -0
  43. package/dist/components/respArea/InlineDropdown.js +119 -0
  44. package/dist/components/respArea/MathTemplated.d.ts +19 -0
  45. package/dist/components/respArea/MathTemplated.js +97 -0
  46. package/dist/components/respArea/ToolbarIcon.d.ts +14 -0
  47. package/dist/components/respArea/ToolbarIcon.js +17 -0
  48. package/dist/components/respArea/inlineDropdownUtils.d.ts +15 -0
  49. package/dist/components/respArea/inlineDropdownUtils.js +15 -0
  50. package/dist/constants.d.ts +13 -0
  51. package/dist/constants.js +4 -0
  52. package/dist/extensions/css.d.ts +11 -0
  53. package/dist/extensions/css.js +115 -0
  54. package/dist/extensions/custom-toolbar-wrapper.d.ts +11 -0
  55. package/dist/extensions/custom-toolbar-wrapper.js +61 -0
  56. package/dist/extensions/div-node.d.ts +10 -0
  57. package/dist/extensions/div-node.js +42 -0
  58. package/dist/extensions/ensure-empty-root-div.d.ts +14 -0
  59. package/dist/extensions/ensure-empty-root-div.js +24 -0
  60. package/dist/extensions/ensure-list-item-content-is-div.d.ts +15 -0
  61. package/dist/extensions/ensure-list-item-content-is-div.js +31 -0
  62. package/dist/extensions/extended-list-item.d.ts +13 -0
  63. package/dist/extensions/extended-list-item.js +5 -0
  64. package/dist/extensions/extended-table-cell.d.ts +10 -0
  65. package/dist/extensions/extended-table-cell.js +6 -0
  66. package/dist/extensions/extended-table.d.ts +17 -0
  67. package/dist/extensions/extended-table.js +34 -0
  68. package/dist/extensions/heading-paragraph.d.ts +17 -0
  69. package/dist/extensions/heading-paragraph.js +30 -0
  70. package/dist/extensions/image-component.d.ts +22 -0
  71. package/dist/extensions/image-component.js +220 -0
  72. package/dist/extensions/image.d.ts +10 -0
  73. package/dist/extensions/image.js +68 -0
  74. package/dist/extensions/index.d.ts +16 -0
  75. package/dist/extensions/index.js +65 -0
  76. package/dist/extensions/math.d.ts +15 -0
  77. package/dist/extensions/math.js +158 -0
  78. package/dist/extensions/media.d.ts +19 -0
  79. package/dist/extensions/media.js +149 -0
  80. package/dist/extensions/responseArea.d.ts +27 -0
  81. package/dist/extensions/responseArea.js +259 -0
  82. package/dist/index.d.ts +13 -0
  83. package/dist/index.js +7 -0
  84. package/dist/styles/editorContainerStyles.d.ts +134 -0
  85. package/dist/theme.d.ts +9 -0
  86. package/dist/utils/helper.d.ts +9 -0
  87. package/dist/utils/helper.js +27 -0
  88. package/dist/utils/size.d.ts +9 -0
  89. package/dist/utils/size.js +14 -0
  90. package/package.json +51 -38
  91. package/CHANGELOG.json +0 -32
  92. package/CHANGELOG.md +0 -2532
  93. package/LICENSE.md +0 -5
  94. package/lib/components/CharacterPicker.js +0 -195
  95. package/lib/components/CharacterPicker.js.map +0 -1
  96. package/lib/components/EditableHtml.js +0 -375
  97. package/lib/components/EditableHtml.js.map +0 -1
  98. package/lib/components/MenuBar.js +0 -693
  99. package/lib/components/MenuBar.js.map +0 -1
  100. package/lib/components/TiptapContainer.js +0 -234
  101. package/lib/components/TiptapContainer.js.map +0 -1
  102. package/lib/components/characters/characterUtils.js +0 -378
  103. package/lib/components/characters/characterUtils.js.map +0 -1
  104. package/lib/components/characters/custom-popper.js +0 -44
  105. package/lib/components/characters/custom-popper.js.map +0 -1
  106. package/lib/components/common/done-button.js +0 -34
  107. package/lib/components/common/done-button.js.map +0 -1
  108. package/lib/components/common/toolbar-buttons.js +0 -144
  109. package/lib/components/common/toolbar-buttons.js.map +0 -1
  110. package/lib/components/icons/CssIcon.js +0 -25
  111. package/lib/components/icons/CssIcon.js.map +0 -1
  112. package/lib/components/icons/RespArea.js +0 -72
  113. package/lib/components/icons/RespArea.js.map +0 -1
  114. package/lib/components/icons/TableIcons.js +0 -53
  115. package/lib/components/icons/TableIcons.js.map +0 -1
  116. package/lib/components/icons/TextAlign.js +0 -157
  117. package/lib/components/icons/TextAlign.js.map +0 -1
  118. package/lib/components/image/AltDialog.js +0 -98
  119. package/lib/components/image/AltDialog.js.map +0 -1
  120. package/lib/components/image/ImageToolbar.js +0 -137
  121. package/lib/components/image/ImageToolbar.js.map +0 -1
  122. package/lib/components/image/InsertImageHandler.js +0 -135
  123. package/lib/components/image/InsertImageHandler.js.map +0 -1
  124. package/lib/components/media/MediaDialog.js +0 -594
  125. package/lib/components/media/MediaDialog.js.map +0 -1
  126. package/lib/components/media/MediaToolbar.js +0 -74
  127. package/lib/components/media/MediaToolbar.js.map +0 -1
  128. package/lib/components/media/MediaWrapper.js +0 -67
  129. package/lib/components/media/MediaWrapper.js.map +0 -1
  130. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js +0 -84
  131. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js.map +0 -1
  132. package/lib/components/respArea/DragInTheBlank/choice.js +0 -250
  133. package/lib/components/respArea/DragInTheBlank/choice.js.map +0 -1
  134. package/lib/components/respArea/ExplicitConstructedResponse.js +0 -136
  135. package/lib/components/respArea/ExplicitConstructedResponse.js.map +0 -1
  136. package/lib/components/respArea/InlineDropdown.js +0 -165
  137. package/lib/components/respArea/InlineDropdown.js.map +0 -1
  138. package/lib/components/respArea/MathTemplated.js +0 -130
  139. package/lib/components/respArea/MathTemplated.js.map +0 -1
  140. package/lib/components/respArea/ToolbarIcon.js +0 -81
  141. package/lib/components/respArea/ToolbarIcon.js.map +0 -1
  142. package/lib/constants.js +0 -11
  143. package/lib/constants.js.map +0 -1
  144. package/lib/extensions/css.js +0 -217
  145. package/lib/extensions/css.js.map +0 -1
  146. package/lib/extensions/custom-toolbar-wrapper.js +0 -92
  147. package/lib/extensions/custom-toolbar-wrapper.js.map +0 -1
  148. package/lib/extensions/div-node.js +0 -83
  149. package/lib/extensions/div-node.js.map +0 -1
  150. package/lib/extensions/ensure-empty-root-div.js +0 -48
  151. package/lib/extensions/ensure-empty-root-div.js.map +0 -1
  152. package/lib/extensions/ensure-list-item-content-is-div.js +0 -64
  153. package/lib/extensions/ensure-list-item-content-is-div.js.map +0 -1
  154. package/lib/extensions/extended-list-item.js +0 -15
  155. package/lib/extensions/extended-list-item.js.map +0 -1
  156. package/lib/extensions/extended-table-cell.js +0 -22
  157. package/lib/extensions/extended-table-cell.js.map +0 -1
  158. package/lib/extensions/extended-table.js +0 -75
  159. package/lib/extensions/extended-table.js.map +0 -1
  160. package/lib/extensions/heading-paragraph.js +0 -61
  161. package/lib/extensions/heading-paragraph.js.map +0 -1
  162. package/lib/extensions/image-component.js +0 -348
  163. package/lib/extensions/image-component.js.map +0 -1
  164. package/lib/extensions/image.js +0 -134
  165. package/lib/extensions/image.js.map +0 -1
  166. package/lib/extensions/index.js +0 -46
  167. package/lib/extensions/index.js.map +0 -1
  168. package/lib/extensions/math.js +0 -343
  169. package/lib/extensions/math.js.map +0 -1
  170. package/lib/extensions/media.js +0 -243
  171. package/lib/extensions/media.js.map +0 -1
  172. package/lib/extensions/responseArea.js +0 -446
  173. package/lib/extensions/responseArea.js.map +0 -1
  174. package/lib/index.js +0 -30
  175. package/lib/index.js.map +0 -1
  176. package/lib/styles/editorContainerStyles.js +0 -137
  177. package/lib/styles/editorContainerStyles.js.map +0 -1
  178. package/lib/theme.js +0 -8
  179. package/lib/theme.js.map +0 -1
  180. package/lib/utils/helper.js +0 -73
  181. package/lib/utils/helper.js.map +0 -1
  182. package/lib/utils/size.js +0 -26
  183. package/lib/utils/size.js.map +0 -1
  184. package/src/__tests__/EditableHtml.test.jsx +0 -474
  185. package/src/__tests__/constants.test.js +0 -19
  186. package/src/__tests__/div-to-paragraph-conversion.test.jsx +0 -125
  187. package/src/__tests__/extensions.test.js +0 -208
  188. package/src/__tests__/index.test.jsx +0 -154
  189. package/src/__tests__/size-utils.test.js +0 -64
  190. package/src/__tests__/theme.test.js +0 -17
  191. package/src/components/CharacterPicker.jsx +0 -200
  192. package/src/components/EditableHtml.jsx +0 -438
  193. package/src/components/MenuBar.jsx +0 -549
  194. package/src/components/TiptapContainer.jsx +0 -219
  195. package/src/components/__tests__/AltDialog.test.jsx +0 -147
  196. package/src/components/__tests__/CharacterPicker.test.jsx +0 -219
  197. package/src/components/__tests__/CssIcon.test.jsx +0 -46
  198. package/src/components/__tests__/DragInTheBlank.test.jsx +0 -255
  199. package/src/components/__tests__/ExplicitConstructedResponse.test.jsx +0 -204
  200. package/src/components/__tests__/ImageToolbar.test.jsx +0 -128
  201. package/src/components/__tests__/InlineDropdown.test.jsx +0 -380
  202. package/src/components/__tests__/InsertImageHandler.test.js +0 -161
  203. package/src/components/__tests__/MediaDialog.test.jsx +0 -293
  204. package/src/components/__tests__/MediaToolbar.test.jsx +0 -74
  205. package/src/components/__tests__/MediaWrapper.test.jsx +0 -81
  206. package/src/components/__tests__/MenuBar.test.jsx +0 -249
  207. package/src/components/__tests__/RespArea.test.jsx +0 -122
  208. package/src/components/__tests__/TableIcons.test.jsx +0 -149
  209. package/src/components/__tests__/TextAlign.test.jsx +0 -167
  210. package/src/components/__tests__/TiptapContainer.test.jsx +0 -138
  211. package/src/components/__tests__/characterUtils.test.js +0 -166
  212. package/src/components/__tests__/choice.test.jsx +0 -171
  213. package/src/components/__tests__/custom-popper.test.jsx +0 -82
  214. package/src/components/__tests__/done-button.test.jsx +0 -54
  215. package/src/components/__tests__/toolbar-buttons.test.jsx +0 -234
  216. package/src/components/characters/characterUtils.js +0 -447
  217. package/src/components/characters/custom-popper.js +0 -38
  218. package/src/components/common/done-button.jsx +0 -27
  219. package/src/components/common/toolbar-buttons.jsx +0 -122
  220. package/src/components/icons/CssIcon.jsx +0 -15
  221. package/src/components/icons/RespArea.jsx +0 -71
  222. package/src/components/icons/TableIcons.jsx +0 -52
  223. package/src/components/icons/TextAlign.jsx +0 -114
  224. package/src/components/image/AltDialog.jsx +0 -82
  225. package/src/components/image/ImageToolbar.jsx +0 -99
  226. package/src/components/image/InsertImageHandler.js +0 -107
  227. package/src/components/media/MediaDialog.jsx +0 -596
  228. package/src/components/media/MediaToolbar.jsx +0 -49
  229. package/src/components/media/MediaWrapper.jsx +0 -39
  230. package/src/components/respArea/DragInTheBlank/DragInTheBlank.jsx +0 -76
  231. package/src/components/respArea/DragInTheBlank/choice.jsx +0 -256
  232. package/src/components/respArea/ExplicitConstructedResponse.jsx +0 -135
  233. package/src/components/respArea/InlineDropdown.jsx +0 -167
  234. package/src/components/respArea/MathTemplated.jsx +0 -124
  235. package/src/components/respArea/ToolbarIcon.jsx +0 -66
  236. package/src/components/respArea/__tests__/MathTemplated.test.jsx +0 -210
  237. package/src/constants.js +0 -5
  238. package/src/extensions/__tests__/css.test.js +0 -196
  239. package/src/extensions/__tests__/custom-toolbar-wrapper.test.jsx +0 -180
  240. package/src/extensions/__tests__/divNode.test.js +0 -87
  241. package/src/extensions/__tests__/ensure-empty-root-div.test.js +0 -57
  242. package/src/extensions/__tests__/ensure-list-item-content-is-div.test.js +0 -44
  243. package/src/extensions/__tests__/extended-list-item.test.js +0 -13
  244. package/src/extensions/__tests__/extended-table-cell.test.js +0 -22
  245. package/src/extensions/__tests__/extended-table.test.js +0 -183
  246. package/src/extensions/__tests__/image-component.test.jsx +0 -345
  247. package/src/extensions/__tests__/image.test.js +0 -237
  248. package/src/extensions/__tests__/math.test.js +0 -459
  249. package/src/extensions/__tests__/media-node-view.test.jsx +0 -298
  250. package/src/extensions/__tests__/media.test.js +0 -271
  251. package/src/extensions/__tests__/responseArea.test.js +0 -601
  252. package/src/extensions/css.js +0 -220
  253. package/src/extensions/custom-toolbar-wrapper.jsx +0 -78
  254. package/src/extensions/div-node.js +0 -86
  255. package/src/extensions/ensure-empty-root-div.js +0 -47
  256. package/src/extensions/ensure-list-item-content-is-div.js +0 -62
  257. package/src/extensions/extended-list-item.js +0 -10
  258. package/src/extensions/extended-table-cell.js +0 -19
  259. package/src/extensions/extended-table.js +0 -60
  260. package/src/extensions/heading-paragraph.js +0 -53
  261. package/src/extensions/image-component.jsx +0 -338
  262. package/src/extensions/image.js +0 -109
  263. package/src/extensions/index.js +0 -81
  264. package/src/extensions/math.js +0 -327
  265. package/src/extensions/media.js +0 -188
  266. package/src/extensions/responseArea.js +0 -401
  267. package/src/index.jsx +0 -5
  268. package/src/styles/editorContainerStyles.js +0 -145
  269. package/src/theme.js +0 -1
  270. package/src/utils/__tests__/helper.test.js +0 -126
  271. package/src/utils/helper.js +0 -69
  272. package/src/utils/size.js +0 -32
@@ -1,200 +0,0 @@
1
- import React, { useEffect, useMemo, useRef, useState } from 'react';
2
- import ReactDOM from 'react-dom';
3
- import PropTypes from 'prop-types';
4
- import get from 'lodash-es/get';
5
-
6
- import { PureToolbar } from '@pie-lib/math-toolbar';
7
-
8
- import CustomPopper from './characters/custom-popper';
9
- import { spanishConfig, specialConfig } from './characters/characterUtils';
10
-
11
- const CharacterIcon = ({ letter }) => (
12
- <div
13
- style={{
14
- fontSize: '24px',
15
- lineHeight: '24px',
16
- }}
17
- >
18
- {letter}
19
- </div>
20
- );
21
-
22
- CharacterIcon.propTypes = {
23
- letter: PropTypes.string,
24
- };
25
-
26
- export function CharacterPicker({ editor, opts, onClose }) {
27
- if (!opts?.characters?.length) {
28
- return null;
29
- }
30
-
31
- const containerRef = useRef(null);
32
- const [position, setPosition] = useState({ top: 0, left: 0 });
33
- const [popover, setPopover] = useState(null);
34
-
35
- const configToUse = useMemo(() => {
36
- if (!opts) return spanishConfig;
37
-
38
- switch (true) {
39
- case opts.language === 'spanish':
40
- return spanishConfig;
41
- case opts.language === 'special':
42
- return specialConfig;
43
- default:
44
- return opts;
45
- }
46
- }, [opts]);
47
-
48
- const layoutForCharacters = useMemo(
49
- () =>
50
- configToUse.characters.reduce(
51
- (obj, arr) => {
52
- if (arr.length >= obj.columns) {
53
- obj.columns = arr.length;
54
- }
55
-
56
- return obj;
57
- },
58
- { rows: configToUse.characters.length, columns: 0 },
59
- ),
60
- [configToUse],
61
- );
62
-
63
- const closePopOver = () => setPopover(null);
64
-
65
- useEffect(
66
- () => () => {
67
- closePopOver();
68
- },
69
- [],
70
- );
71
-
72
- useEffect(() => {
73
- if (!editor) return;
74
-
75
- // Calculate position relative to selection
76
- const editorDOM = editor.options.element;
77
- const editorRect = editorDOM.getBoundingClientRect();
78
- const bodyRect = document.body.getBoundingClientRect();
79
- const { from } = editor.state.selection;
80
- const start = editor.view.coordsAtPos(from);
81
-
82
- let top = editorRect.top + Math.abs(bodyRect.top) + editorRect.height + 60;
83
-
84
- if (editorRect.y > containerRef.current.offsetHeight) {
85
- top = top - (containerRef.current.offsetHeight + editorRect.height) - 80;
86
- }
87
-
88
- setPosition({
89
- // top: start.top + Math.abs(bodyRect.top) - containerRef.current.offsetHeight - 10 + additionalTopOffset, // shift above
90
- top: top,
91
- left: start.left,
92
- });
93
-
94
- const handleClickOutside = (e) => {
95
- if (containerRef.current && !containerRef.current.contains(e.target) && !editor.view.dom.contains(e.target)) {
96
- onClose();
97
- }
98
- };
99
-
100
- const timeoutId = setTimeout(() => {
101
- document.addEventListener('click', handleClickOutside);
102
- });
103
-
104
- return () => {
105
- clearTimeout(timeoutId);
106
- document.removeEventListener('click', handleClickOutside);
107
- };
108
- }, [editor, onClose]);
109
-
110
- const renderPopOver = (event, el) => setPopover({ anchorEl: event.currentTarget, el });
111
-
112
- const handleChange = (val) => {
113
- if (typeof val === 'string') {
114
- editor.chain().focus().insertContent(val).run();
115
- }
116
- };
117
-
118
- return (
119
- <>
120
- {ReactDOM.createPortal(
121
- <div
122
- ref={containerRef}
123
- className="insert-character-dialog"
124
- data-toolbar-for={editor.instanceId}
125
- style={{
126
- visibility: position.top === 0 && position.left === 0 ? 'hidden' : 'initial',
127
- position: 'absolute',
128
- top: `${position.top}px`,
129
- left: `${position.left}px`,
130
- maxWidth: '500px',
131
- zIndex: 99,
132
- }}
133
- >
134
- <div>
135
- <PureToolbar
136
- keyPadCharacterRef={opts.keyPadCharacterRef}
137
- setKeypadInteraction={opts.setKeypadInteraction}
138
- autoFocus
139
- noDecimal
140
- hideInput
141
- noLatexHandling
142
- hideDoneButtonBackground
143
- layoutForKeyPad={layoutForCharacters}
144
- additionalKeys={configToUse.characters.reduce((arr, n) => {
145
- arr = [
146
- ...arr,
147
- ...n.map((k) => ({
148
- name: get(k, 'name') || k,
149
- write: get(k, 'write') || k,
150
- label: get(k, 'label') || k,
151
- category: 'character',
152
- extraClass: 'character',
153
- extraProps: {
154
- ...(k.extraProps || {}),
155
- style: {
156
- ...(k.extraProps || {}).style,
157
- border: '1px solid #000',
158
- },
159
- },
160
- ...(configToUse.hasPreview
161
- ? {
162
- actions: {
163
- onMouseEnter: (ev) => renderPopOver(ev, k),
164
- onMouseLeave: closePopOver,
165
- },
166
- }
167
- : {}),
168
- })),
169
- ];
170
-
171
- return arr;
172
- }, [])}
173
- keypadMode="language"
174
- onChange={handleChange}
175
- onDone={onClose}
176
- />
177
- </div>
178
- </div>,
179
- document.body,
180
- )}
181
- {popover &&
182
- ReactDOM.createPortal(
183
- <CustomPopper onClose={closePopOver} anchorEl={popover.anchorEl}>
184
- <div>{popover.el.label}</div>
185
- <div style={{ fontSize: 20, lineHeight: '20px' }}>{popover.el.description}</div>
186
- <div style={{ fontSize: 20, lineHeight: '20px' }}>{popover.el.unicode}</div>
187
- </CustomPopper>,
188
- document.body,
189
- )}
190
- </>
191
- );
192
- }
193
-
194
- CharacterPicker.propTypes = {
195
- editor: PropTypes.object,
196
- opts: PropTypes.object,
197
- onClose: PropTypes.func.isRequired,
198
- };
199
-
200
- export { CharacterIcon };
@@ -1,438 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
- import debounce from 'lodash-es/debounce';
3
- import { EditorContent, useEditor, useEditorState } from '@tiptap/react';
4
- import { styled } from '@mui/material/styles';
5
- import StarterKit from '@tiptap/starter-kit';
6
- import { TextStyleKit } from '@tiptap/extension-text-style';
7
- import { CharacterCount } from '@tiptap/extension-character-count';
8
- import SuperScript from '@tiptap/extension-superscript';
9
- import SubScript from '@tiptap/extension-subscript';
10
- import TextAlign from '@tiptap/extension-text-align';
11
- import Image from '@tiptap/extension-image';
12
- import Placeholder from '@tiptap/extension-placeholder';
13
- import { normalizeInitialMarkup } from '../utils/helper';
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';
20
- import { TableRow } from '@tiptap/extension-table-row';
21
- import {
22
- DragInTheBlankNode,
23
- ExplicitConstructedResponseNode,
24
- InlineDropdownNode,
25
- MathTemplatedNode,
26
- ResponseAreaExtension,
27
- } from '../extensions/responseArea';
28
- import { MathNode } from '../extensions/math';
29
- import { ImageUploadNode } from '../extensions/image';
30
- import { Media } from '../extensions/media';
31
- import { CSSMark } from '../extensions/css';
32
- import { ExtendedListItem } from '../extensions/extended-list-item';
33
- import { HeadingParagraph } from '../extensions/heading-paragraph';
34
-
35
- import EditorContainer from './TiptapContainer';
36
- import { valueToSize } from '../utils/size';
37
- import { buildExtensions, PLUGINS_MAP } from '../extensions';
38
-
39
- const defaultToolbarOpts = {
40
- position: 'bottom',
41
- alignment: 'left',
42
- alwaysVisible: false,
43
- showDone: true,
44
- doneOn: 'blur',
45
- };
46
-
47
- const defaultResponseAreaProps = {
48
- options: {},
49
- respAreaToolbar: () => {},
50
- onHandleAreaChange: () => {},
51
- };
52
-
53
- const DEFAULT_ACTIVE_PLUGINS = [
54
- 'bold',
55
- 'italic',
56
- 'underline',
57
- 'strikethrough',
58
- 'code',
59
- 'bulleted-list',
60
- 'numbered-list',
61
- 'image',
62
- 'math',
63
- 'languageCharacters',
64
- 'text-align',
65
- 'table',
66
- 'video',
67
- 'audio',
68
- 'responseArea',
69
- 'superscript',
70
- 'subscript',
71
- 'css',
72
- 'h3',
73
- 'undo',
74
- 'redo',
75
- ];
76
-
77
- const cssVariables = {
78
- '--white': '#fff',
79
- '--black': '#2e2b29',
80
- '--black-contrast': '#110f0e',
81
- '--gray-1': 'rgba(61, 37, 20, .05)',
82
- '--gray-2': 'rgba(61, 37, 20, .08)',
83
- '--gray-3': 'rgba(61, 37, 20, .12)',
84
- '--gray-4': 'rgba(53, 38, 28, .3)',
85
- '--gray-5': 'rgba(28, 25, 23, .6)',
86
- '--green': '#22c55e',
87
- '--purple': '#6a00f5',
88
- '--purple-contrast': '#5800cc',
89
- '--purple-light': 'rgba(88, 5, 255, .05)',
90
- '--yellow-contrast': '#facc15',
91
- '--yellow': 'rgba(250, 204, 21, .4)',
92
- '--yellow-light': '#fffae5',
93
- '--red': '#ff5c33',
94
- '--red-light': '#ffebe5',
95
- '--shadow': `0px 12px 33px 0px rgba(0, 0, 0, .06),
96
- 0px 3.618px 9.949px 0px rgba(0, 0, 0, .04)`,
97
- };
98
-
99
- export const EditableHtml = (props) => {
100
- const { showParagraphs, separateParagraphs } = props.pluginProps || {};
101
- const [pendingImages, setPendingImages] = useState([]);
102
- const [scheduled, setScheduled] = useState(false);
103
- const { toolbarOpts } = props;
104
-
105
- const removePendingImage = useCallback(
106
- (imagePos) => {
107
- setPendingImages((prev) => {
108
- const next = prev.filter((img) => img.pos !== imagePos);
109
- if (next.length === 0) {
110
- setScheduled(false);
111
- }
112
- return next;
113
- });
114
- },
115
- [setPendingImages],
116
- );
117
-
118
- const toolbarOptsToUse = {
119
- ...defaultToolbarOpts,
120
- ...toolbarOpts,
121
- };
122
-
123
- const activePluginsToUse = useMemo(() => {
124
- let { customPlugins, ...otherPluginProps } = props.pluginProps || {};
125
-
126
- customPlugins = customPlugins || [];
127
-
128
- const filteredActivePlugins = (props.activePlugins || DEFAULT_ACTIVE_PLUGINS)?.filter((pluginName) => {
129
- const nameToUse = PLUGINS_MAP[pluginName] || pluginName;
130
- const pluginInfo = otherPluginProps[nameToUse] || {};
131
-
132
- return !pluginInfo || !pluginInfo.disabled;
133
- });
134
-
135
- return buildExtensions(filteredActivePlugins, customPlugins, {
136
- math: {},
137
- textAlign: props.textAlign,
138
- html: {},
139
- extraCSSRules: props.extraCSSRules || {},
140
- image: {
141
- ...props.imageSupport,
142
- },
143
- toolbar: {},
144
- table: {},
145
- responseArea: {
146
- type: props.responseAreaProps?.type,
147
- },
148
- languageCharacters: props.languageCharactersProps,
149
- keyPadCharacterRef: {},
150
- setKeypadInteraction: {},
151
- media: {},
152
- });
153
- }, [props]);
154
-
155
- const extensions = [
156
- TextAlign.configure({
157
- types: ['heading', 'paragraph', 'div', 'headingParagraph', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'td', 'th'],
158
- alignments: ['left', 'right', 'center', 'justify'],
159
- }),
160
- TextStyleKit,
161
- CharacterCount.configure({
162
- limit: props.charactersLimit || 1000000,
163
- }),
164
- StarterKit.configure({
165
- trailingNode: {
166
- node: 'paragraph',
167
- notAfter: ['paragraph', 'div'],
168
- },
169
- }),
170
- ExtendedListItem,
171
- DivNode,
172
- HeadingParagraph,
173
- EnsureEmptyRootIsDiv,
174
- EnsureListItemContentIsDiv,
175
- Placeholder.configure({
176
- placeholder: props.placeholder,
177
- // show placeholder even when editor is focused
178
- showOnlyWhenEditable: true,
179
- showOnlyCurrent: false, // show on all empty nodes, not only the current one
180
- includeChildren: true,
181
- }),
182
- ExtendedTable,
183
- TableRow,
184
- ExtendedTableHeader,
185
- ExtendedTableCell,
186
- ResponseAreaExtension.configure(props.responseAreaProps),
187
- ExplicitConstructedResponseNode.configure(props.responseAreaProps),
188
- DragInTheBlankNode.configure(props.responseAreaProps),
189
- InlineDropdownNode.configure(props.responseAreaProps),
190
- MathTemplatedNode.configure(props.responseAreaProps),
191
- MathNode.configure({
192
- toolbarOpts: toolbarOptsToUse,
193
- math: props.pluginProps?.math || {},
194
- }),
195
- SubScript,
196
- SuperScript,
197
- Image,
198
- ImageUploadNode.configure({
199
- toolbarOpts: toolbarOptsToUse,
200
- imageHandling: {
201
- disableImageAlignmentButtons: props.disableImageAlignmentButtons,
202
- onDone: (editor) => props.onDone?.(editor.getHTML()),
203
- onDelete:
204
- props.imageSupport &&
205
- props.imageSupport.delete &&
206
- ((node) => {
207
- const { src } = node.attrs;
208
-
209
- props.imageSupport.delete(src, (e) => {
210
- removePendingImage(node.pos);
211
- });
212
- }),
213
- insertImageRequested:
214
- props.imageSupport &&
215
- ((editor, imageInfo, getHandler) => {
216
- const [addedImage, pos] = imageInfo;
217
-
218
- const onFinish = (result) => {
219
- let cb;
220
-
221
- if (scheduled && result) {
222
- // finish editing only on success
223
- cb = props.onChange;
224
- }
225
-
226
- removePendingImage(pos);
227
- cb?.(editor.getHTML());
228
- };
229
-
230
- const callback = () => {
231
- /**
232
- * The handler is the object through which the outer context
233
- * communicates file upload events like: fileChosen, cancel, progress
234
- */
235
- const handler = getHandler(onFinish);
236
-
237
- // If the user closes the file picker without choosing a file, the window regains
238
- // focus while _insertingImage is still true — drop the stale pending entry.
239
- const focusHandler = debounce(() => {
240
- const detach = () => window.removeEventListener('focus', focusHandler);
241
-
242
- if (!editor._insertingImage) {
243
- detach();
244
- return;
245
- }
246
-
247
- removePendingImage(pos);
248
- editor._insertingImage = false;
249
- detach();
250
- }, 500);
251
-
252
- window.addEventListener('focus', focusHandler);
253
-
254
- props.imageSupport.add(handler);
255
- };
256
-
257
- editor._insertingImage = true;
258
- setPendingImages((prev) => [...prev, addedImage]);
259
- callback();
260
- }),
261
- maxImageWidth: props.maxImageWidth,
262
- maxImageHeight: props.maxImageHeight,
263
- },
264
- limit: 3,
265
- }),
266
- Media.configure({
267
- uploadSoundSupport: props.uploadSoundSupport,
268
- }),
269
- CSSMark.configure({
270
- extraCSSRules: props.extraCSSRules,
271
- }),
272
- ];
273
-
274
- const editor = useEditor(
275
- {
276
- extensions,
277
- immediatelyRender: false,
278
- editorProps: {
279
- handleKeyDown(view, event) {
280
- if (props.onKeyDown) {
281
- return props.onKeyDown(event);
282
- }
283
-
284
- // Return false to let default behavior continue
285
- return false;
286
- },
287
- },
288
- editable: !props.disabled,
289
- content: normalizeInitialMarkup(props.markup),
290
- onUpdate: ({ editor, transaction }) => {
291
- if (transaction.isDone || props.markup !== editor.getHTML()) {
292
- props.onChange?.(editor.getHTML());
293
- }
294
- },
295
- onBlur: debounce(({ editor }) => {
296
- const otherToolbarOpened =
297
- editor._insertingImage ||
298
- editor._toolbarOpened ||
299
- editor.isActive('inline_dropdown') ||
300
- editor.isActive('explicit_constructed_response');
301
-
302
- if (otherToolbarOpened) {
303
- return;
304
- }
305
-
306
- if (props.markup !== editor.getHTML()) {
307
- props.onChange?.(editor.getHTML());
308
- }
309
-
310
- if (toolbarOptsToUse.doneOn === 'blur') {
311
- props.onDone?.(editor.getHTML());
312
- }
313
- }, 200),
314
- },
315
- [props.charactersLimit],
316
- );
317
-
318
- useEffect(() => {
319
- if (props.editorRef) {
320
- props.editorRef(editor);
321
- }
322
- }, [props.editorRef, editor]);
323
-
324
- useEffect(() => {
325
- editor?.setEditable(!props.disabled);
326
- }, [props.disabled, editor]);
327
-
328
- useEffect(() => {
329
- if (!editor) {
330
- return;
331
- }
332
- const nextMarkup = normalizeInitialMarkup(props.markup);
333
-
334
- if (nextMarkup !== editor.getHTML()) {
335
- editor.commands.setContent(nextMarkup, false);
336
- }
337
- }, [props.markup, editor]);
338
-
339
- useEffect(() => {
340
- Object.entries(cssVariables).forEach(([key, value]) => {
341
- document.documentElement.style.setProperty(key, value);
342
- });
343
- }, []);
344
-
345
- const editorState = useEditorState({
346
- editor,
347
- selector: (ctx) => ({
348
- isFocused: ctx.editor?.isFocused,
349
- }),
350
- });
351
-
352
- const sizeStyle = useMemo(() => {
353
- const { minWidth, width, maxWidth, minHeight, height, maxHeight } = props;
354
-
355
- return {
356
- width: valueToSize(width),
357
- minWidth: valueToSize(minWidth),
358
- maxWidth: valueToSize(maxWidth),
359
- height: valueToSize(height),
360
- minHeight: valueToSize(minHeight),
361
- maxHeight: valueToSize(maxHeight),
362
- };
363
- }, [props]);
364
-
365
- return (
366
- <EditorContainer
367
- {...{
368
- ...props,
369
- activePlugins: activePluginsToUse,
370
- toolbarOpts: toolbarOptsToUse,
371
- }}
372
- editorState={editorState}
373
- editor={editor}
374
- >
375
- {editor && (
376
- <StyledEditorContent
377
- style={{
378
- minHeight: sizeStyle.minHeight,
379
- height: sizeStyle.height,
380
- maxHeight: sizeStyle.maxHeight,
381
- }}
382
- showParagraph={showParagraphs && !showParagraphs.disabled}
383
- separateParagraph={separateParagraphs && !separateParagraphs.disabled}
384
- editor={editor}
385
- />
386
- )}
387
- </EditorContainer>
388
- );
389
- };
390
-
391
- const StyledEditorContent = styled(EditorContent, {
392
- shouldForwardProp: (prop) => !['showParagraph', 'separateParagraph'].includes(prop),
393
- })(({ showParagraph, separateParagraph }) => ({
394
- display: 'flex',
395
- outline: 'none !important',
396
- '& .ProseMirror': {
397
- flex: 1,
398
- padding: '5px',
399
- maxHeight: '500px',
400
- outline: 'none !important',
401
- position: 'initial',
402
-
403
- // reset default margins for all block paragraphs/divs in the editor
404
- '& > p, & > div': {
405
- margin: '0',
406
- },
407
-
408
- // Out of flow so the caret stays at the start of the block; in-flow ::before pushes the caret after the hint text.
409
- '& p.is-editor-empty, & div.is-editor-empty': {
410
- position: 'relative',
411
- },
412
- '& p.is-editor-empty::before, & div.is-editor-empty::before': {
413
- content: 'attr(data-placeholder)',
414
- position: 'absolute',
415
- left: 0,
416
- top: 0,
417
- color: '#9CA3AF',
418
- pointerEvents: 'none',
419
- whiteSpace: 'pre-wrap',
420
- },
421
-
422
- ...(showParagraph && {
423
- '& > p:has(+ p)::after, & > div:has(+ div)::after': {
424
- display: 'block',
425
- content: '"¶"',
426
- fontSize: '1em',
427
- color: '#146EB3',
428
- },
429
- }),
430
- ...(separateParagraph && {
431
- '& > div:has(+ div)': {
432
- marginBottom: '1em',
433
- },
434
- }),
435
- },
436
- }));
437
-
438
- export default EditableHtml;