@pie-lib/editable-html-tip-tap 2.1.2-next.31 → 2.1.2-next.36

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 (280) hide show
  1. package/CHANGELOG.json +32 -0
  2. package/CHANGELOG.md +2532 -0
  3. package/LICENSE.md +5 -0
  4. package/lib/components/CharacterPicker.js +201 -0
  5. package/lib/components/CharacterPicker.js.map +1 -0
  6. package/lib/components/EditableHtml.js +376 -0
  7. package/lib/components/EditableHtml.js.map +1 -0
  8. package/lib/components/MenuBar.js +697 -0
  9. package/lib/components/MenuBar.js.map +1 -0
  10. package/lib/components/TiptapContainer.js +234 -0
  11. package/lib/components/TiptapContainer.js.map +1 -0
  12. package/lib/components/characters/characterUtils.js +378 -0
  13. package/lib/components/characters/characterUtils.js.map +1 -0
  14. package/lib/components/characters/custom-popper.js +44 -0
  15. package/lib/components/characters/custom-popper.js.map +1 -0
  16. package/lib/components/common/done-button.js +34 -0
  17. package/lib/components/common/done-button.js.map +1 -0
  18. package/lib/components/common/toolbar-buttons.js +144 -0
  19. package/lib/components/common/toolbar-buttons.js.map +1 -0
  20. package/lib/components/icons/CssIcon.js +25 -0
  21. package/lib/components/icons/CssIcon.js.map +1 -0
  22. package/lib/components/icons/RespArea.js +72 -0
  23. package/lib/components/icons/RespArea.js.map +1 -0
  24. package/lib/components/icons/TableIcons.js +53 -0
  25. package/lib/components/icons/TableIcons.js.map +1 -0
  26. package/lib/components/icons/TextAlign.js +157 -0
  27. package/lib/components/icons/TextAlign.js.map +1 -0
  28. package/lib/components/image/AltDialog.js +98 -0
  29. package/lib/components/image/AltDialog.js.map +1 -0
  30. package/lib/components/image/ImageToolbar.js +137 -0
  31. package/lib/components/image/ImageToolbar.js.map +1 -0
  32. package/lib/components/image/InsertImageHandler.js +135 -0
  33. package/lib/components/image/InsertImageHandler.js.map +1 -0
  34. package/lib/components/media/MediaDialog.js +594 -0
  35. package/lib/components/media/MediaDialog.js.map +1 -0
  36. package/lib/components/media/MediaToolbar.js +74 -0
  37. package/lib/components/media/MediaToolbar.js.map +1 -0
  38. package/lib/components/media/MediaWrapper.js +67 -0
  39. package/lib/components/media/MediaWrapper.js.map +1 -0
  40. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js +84 -0
  41. package/lib/components/respArea/DragInTheBlank/DragInTheBlank.js.map +1 -0
  42. package/lib/components/respArea/DragInTheBlank/choice.js +250 -0
  43. package/lib/components/respArea/DragInTheBlank/choice.js.map +1 -0
  44. package/lib/components/respArea/ExplicitConstructedResponse.js +137 -0
  45. package/lib/components/respArea/ExplicitConstructedResponse.js.map +1 -0
  46. package/lib/components/respArea/InlineDropdown.js +210 -0
  47. package/lib/components/respArea/InlineDropdown.js.map +1 -0
  48. package/lib/components/respArea/MathTemplated.js +130 -0
  49. package/lib/components/respArea/MathTemplated.js.map +1 -0
  50. package/lib/components/respArea/ToolbarIcon.js +81 -0
  51. package/lib/components/respArea/ToolbarIcon.js.map +1 -0
  52. package/lib/components/respArea/inlineDropdownUtils.js +67 -0
  53. package/lib/components/respArea/inlineDropdownUtils.js.map +1 -0
  54. package/lib/constants.js +11 -0
  55. package/lib/constants.js.map +1 -0
  56. package/lib/extensions/css.js +217 -0
  57. package/lib/extensions/css.js.map +1 -0
  58. package/lib/extensions/custom-toolbar-wrapper.js +92 -0
  59. package/lib/extensions/custom-toolbar-wrapper.js.map +1 -0
  60. package/lib/extensions/div-node.js +83 -0
  61. package/lib/extensions/div-node.js.map +1 -0
  62. package/lib/extensions/ensure-empty-root-div.js +48 -0
  63. package/lib/extensions/ensure-empty-root-div.js.map +1 -0
  64. package/lib/extensions/ensure-list-item-content-is-div.js +64 -0
  65. package/lib/extensions/ensure-list-item-content-is-div.js.map +1 -0
  66. package/lib/extensions/extended-list-item.js +15 -0
  67. package/lib/extensions/extended-list-item.js.map +1 -0
  68. package/lib/extensions/extended-table-cell.js +22 -0
  69. package/lib/extensions/extended-table-cell.js.map +1 -0
  70. package/lib/extensions/extended-table.js +75 -0
  71. package/lib/extensions/extended-table.js.map +1 -0
  72. package/lib/extensions/heading-paragraph.js +61 -0
  73. package/lib/extensions/heading-paragraph.js.map +1 -0
  74. package/lib/extensions/image-component.js +348 -0
  75. package/lib/extensions/image-component.js.map +1 -0
  76. package/lib/extensions/image.js +134 -0
  77. package/lib/extensions/image.js.map +1 -0
  78. package/lib/extensions/index.js +46 -0
  79. package/lib/extensions/index.js.map +1 -0
  80. package/lib/extensions/math.js +342 -0
  81. package/lib/extensions/math.js.map +1 -0
  82. package/lib/extensions/media.js +243 -0
  83. package/lib/extensions/media.js.map +1 -0
  84. package/lib/extensions/responseArea.js +446 -0
  85. package/lib/extensions/responseArea.js.map +1 -0
  86. package/lib/index.js +37 -0
  87. package/lib/index.js.map +1 -0
  88. package/lib/styles/editorContainerStyles.js +137 -0
  89. package/lib/styles/editorContainerStyles.js.map +1 -0
  90. package/lib/theme.js +8 -0
  91. package/lib/theme.js.map +1 -0
  92. package/lib/utils/helper.js +73 -0
  93. package/lib/utils/helper.js.map +1 -0
  94. package/lib/utils/size.js +26 -0
  95. package/lib/utils/size.js.map +1 -0
  96. package/lib/utils/toolbar.js +19 -0
  97. package/lib/utils/toolbar.js.map +1 -0
  98. package/package.json +24 -40
  99. package/src/__tests__/EditableHtml.test.jsx +554 -0
  100. package/src/__tests__/constants.test.js +19 -0
  101. package/src/__tests__/div-to-paragraph-conversion.test.jsx +125 -0
  102. package/src/__tests__/extensions.test.js +208 -0
  103. package/src/__tests__/index.test.jsx +154 -0
  104. package/src/__tests__/size-utils.test.js +64 -0
  105. package/src/__tests__/theme.test.js +17 -0
  106. package/src/components/CharacterPicker.jsx +207 -0
  107. package/src/components/EditableHtml.jsx +440 -0
  108. package/src/components/MenuBar.jsx +556 -0
  109. package/src/components/TiptapContainer.jsx +219 -0
  110. package/src/components/__tests__/AltDialog.test.jsx +147 -0
  111. package/src/components/__tests__/CharacterPicker.test.jsx +261 -0
  112. package/src/components/__tests__/CssIcon.test.jsx +46 -0
  113. package/src/components/__tests__/DragInTheBlank.test.jsx +255 -0
  114. package/src/components/__tests__/ExplicitConstructedResponse.test.jsx +209 -0
  115. package/src/components/__tests__/ImageToolbar.test.jsx +128 -0
  116. package/src/components/__tests__/InlineDropdown.test.jsx +393 -0
  117. package/src/components/__tests__/InsertImageHandler.test.js +161 -0
  118. package/src/components/__tests__/MediaDialog.test.jsx +293 -0
  119. package/src/components/__tests__/MediaToolbar.test.jsx +74 -0
  120. package/src/components/__tests__/MediaWrapper.test.jsx +81 -0
  121. package/src/components/__tests__/MenuBar.test.jsx +250 -0
  122. package/src/components/__tests__/RespArea.test.jsx +122 -0
  123. package/src/components/__tests__/TableIcons.test.jsx +149 -0
  124. package/src/components/__tests__/TextAlign.test.jsx +167 -0
  125. package/src/components/__tests__/TiptapContainer.test.jsx +138 -0
  126. package/src/components/__tests__/characterUtils.test.js +166 -0
  127. package/src/components/__tests__/choice.test.jsx +171 -0
  128. package/src/components/__tests__/custom-popper.test.jsx +82 -0
  129. package/src/components/__tests__/done-button.test.jsx +54 -0
  130. package/src/components/__tests__/toolbar-buttons.test.jsx +234 -0
  131. package/src/components/characters/characterUtils.js +447 -0
  132. package/src/components/characters/custom-popper.js +38 -0
  133. package/src/components/common/done-button.jsx +27 -0
  134. package/src/components/common/toolbar-buttons.jsx +122 -0
  135. package/src/components/icons/CssIcon.jsx +15 -0
  136. package/src/components/icons/RespArea.jsx +71 -0
  137. package/src/components/icons/TableIcons.jsx +52 -0
  138. package/src/components/icons/TextAlign.jsx +114 -0
  139. package/src/components/image/AltDialog.jsx +82 -0
  140. package/src/components/image/ImageToolbar.jsx +99 -0
  141. package/src/components/image/InsertImageHandler.js +107 -0
  142. package/src/components/media/MediaDialog.jsx +596 -0
  143. package/src/components/media/MediaToolbar.jsx +49 -0
  144. package/src/components/media/MediaWrapper.jsx +39 -0
  145. package/src/components/respArea/DragInTheBlank/DragInTheBlank.jsx +76 -0
  146. package/src/components/respArea/DragInTheBlank/choice.jsx +256 -0
  147. package/src/components/respArea/ExplicitConstructedResponse.jsx +136 -0
  148. package/src/components/respArea/InlineDropdown.jsx +221 -0
  149. package/src/components/respArea/MathTemplated.jsx +124 -0
  150. package/src/components/respArea/ToolbarIcon.jsx +66 -0
  151. package/src/components/respArea/__tests__/MathTemplated.test.jsx +210 -0
  152. package/src/components/respArea/inlineDropdownUtils.js +79 -0
  153. package/src/constants.js +5 -0
  154. package/src/extensions/__tests__/css.test.js +196 -0
  155. package/src/extensions/__tests__/custom-toolbar-wrapper.test.jsx +180 -0
  156. package/src/extensions/__tests__/divNode.test.js +87 -0
  157. package/src/extensions/__tests__/ensure-empty-root-div.test.js +57 -0
  158. package/src/extensions/__tests__/ensure-list-item-content-is-div.test.js +44 -0
  159. package/src/extensions/__tests__/extended-list-item.test.js +13 -0
  160. package/src/extensions/__tests__/extended-table-cell.test.js +22 -0
  161. package/src/extensions/__tests__/extended-table.test.js +183 -0
  162. package/src/extensions/__tests__/image-component.test.jsx +345 -0
  163. package/src/extensions/__tests__/image.test.js +237 -0
  164. package/src/extensions/__tests__/math.test.js +604 -0
  165. package/src/extensions/__tests__/media-node-view.test.jsx +298 -0
  166. package/src/extensions/__tests__/media.test.js +271 -0
  167. package/src/extensions/__tests__/responseArea.test.js +601 -0
  168. package/src/extensions/css.js +220 -0
  169. package/src/extensions/custom-toolbar-wrapper.jsx +78 -0
  170. package/src/extensions/div-node.js +86 -0
  171. package/src/extensions/ensure-empty-root-div.js +47 -0
  172. package/src/extensions/ensure-list-item-content-is-div.js +62 -0
  173. package/src/extensions/extended-list-item.js +10 -0
  174. package/src/extensions/extended-table-cell.js +19 -0
  175. package/src/extensions/extended-table.js +60 -0
  176. package/src/extensions/heading-paragraph.js +53 -0
  177. package/src/extensions/image-component.jsx +338 -0
  178. package/src/extensions/image.js +109 -0
  179. package/src/extensions/index.js +81 -0
  180. package/src/extensions/math.js +325 -0
  181. package/src/extensions/media.js +188 -0
  182. package/src/extensions/responseArea.js +401 -0
  183. package/src/index.jsx +5 -0
  184. package/src/styles/editorContainerStyles.js +145 -0
  185. package/src/theme.js +1 -0
  186. package/src/utils/__tests__/helper.test.js +126 -0
  187. package/src/utils/__tests__/toolbar.test.js +43 -0
  188. package/src/utils/helper.js +69 -0
  189. package/src/utils/size.js +32 -0
  190. package/src/utils/toolbar.js +15 -0
  191. package/dist/components/CharacterPicker.d.ts +0 -31
  192. package/dist/components/CharacterPicker.js +0 -131
  193. package/dist/components/EditableHtml.d.ts +0 -11
  194. package/dist/components/EditableHtml.js +0 -291
  195. package/dist/components/MenuBar.d.ts +0 -11
  196. package/dist/components/MenuBar.js +0 -462
  197. package/dist/components/TiptapContainer.d.ts +0 -11
  198. package/dist/components/TiptapContainer.js +0 -154
  199. package/dist/components/characters/characterUtils.d.ts +0 -35
  200. package/dist/components/characters/characterUtils.js +0 -465
  201. package/dist/components/characters/custom-popper.d.ts +0 -14
  202. package/dist/components/characters/custom-popper.js +0 -32
  203. package/dist/components/common/done-button.d.ts +0 -30
  204. package/dist/components/common/done-button.js +0 -26
  205. package/dist/components/common/toolbar-buttons.d.ts +0 -38
  206. package/dist/components/common/toolbar-buttons.js +0 -91
  207. package/dist/components/icons/CssIcon.d.ts +0 -11
  208. package/dist/components/icons/CssIcon.js +0 -14
  209. package/dist/components/icons/RespArea.d.ts +0 -26
  210. package/dist/components/icons/RespArea.js +0 -42
  211. package/dist/components/icons/TableIcons.d.ts +0 -14
  212. package/dist/components/icons/TableIcons.js +0 -32
  213. package/dist/components/icons/TextAlign.d.ts +0 -18
  214. package/dist/components/icons/TextAlign.js +0 -134
  215. package/dist/components/image/AltDialog.d.ts +0 -22
  216. package/dist/components/image/AltDialog.js +0 -61
  217. package/dist/components/image/ImageToolbar.d.ts +0 -24
  218. package/dist/components/image/ImageToolbar.js +0 -80
  219. package/dist/components/image/InsertImageHandler.d.ts +0 -32
  220. package/dist/components/image/InsertImageHandler.js +0 -53
  221. package/dist/components/media/MediaDialog.d.ts +0 -43
  222. package/dist/components/media/MediaDialog.js +0 -389
  223. package/dist/components/media/MediaToolbar.d.ts +0 -19
  224. package/dist/components/media/MediaToolbar.js +0 -41
  225. package/dist/components/media/MediaWrapper.d.ts +0 -19
  226. package/dist/components/respArea/DragInTheBlank/DragInTheBlank.d.ts +0 -23
  227. package/dist/components/respArea/DragInTheBlank/DragInTheBlank.js +0 -58
  228. package/dist/components/respArea/DragInTheBlank/choice.d.ts +0 -56
  229. package/dist/components/respArea/DragInTheBlank/choice.js +0 -156
  230. package/dist/components/respArea/ExplicitConstructedResponse.d.ts +0 -20
  231. package/dist/components/respArea/ExplicitConstructedResponse.js +0 -83
  232. package/dist/components/respArea/InlineDropdown.d.ts +0 -18
  233. package/dist/components/respArea/InlineDropdown.js +0 -119
  234. package/dist/components/respArea/MathTemplated.d.ts +0 -19
  235. package/dist/components/respArea/MathTemplated.js +0 -97
  236. package/dist/components/respArea/ToolbarIcon.d.ts +0 -14
  237. package/dist/components/respArea/ToolbarIcon.js +0 -17
  238. package/dist/components/respArea/inlineDropdownUtils.d.ts +0 -15
  239. package/dist/components/respArea/inlineDropdownUtils.js +0 -15
  240. package/dist/constants.d.ts +0 -13
  241. package/dist/constants.js +0 -4
  242. package/dist/extensions/css.d.ts +0 -11
  243. package/dist/extensions/css.js +0 -115
  244. package/dist/extensions/custom-toolbar-wrapper.d.ts +0 -11
  245. package/dist/extensions/custom-toolbar-wrapper.js +0 -61
  246. package/dist/extensions/div-node.d.ts +0 -10
  247. package/dist/extensions/div-node.js +0 -42
  248. package/dist/extensions/ensure-empty-root-div.d.ts +0 -14
  249. package/dist/extensions/ensure-empty-root-div.js +0 -24
  250. package/dist/extensions/ensure-list-item-content-is-div.d.ts +0 -15
  251. package/dist/extensions/ensure-list-item-content-is-div.js +0 -31
  252. package/dist/extensions/extended-list-item.d.ts +0 -13
  253. package/dist/extensions/extended-list-item.js +0 -5
  254. package/dist/extensions/extended-table-cell.d.ts +0 -10
  255. package/dist/extensions/extended-table-cell.js +0 -6
  256. package/dist/extensions/extended-table.d.ts +0 -17
  257. package/dist/extensions/extended-table.js +0 -34
  258. package/dist/extensions/heading-paragraph.d.ts +0 -17
  259. package/dist/extensions/heading-paragraph.js +0 -30
  260. package/dist/extensions/image-component.d.ts +0 -22
  261. package/dist/extensions/image-component.js +0 -220
  262. package/dist/extensions/image.d.ts +0 -10
  263. package/dist/extensions/image.js +0 -68
  264. package/dist/extensions/index.d.ts +0 -16
  265. package/dist/extensions/index.js +0 -64
  266. package/dist/extensions/math.d.ts +0 -15
  267. package/dist/extensions/math.js +0 -158
  268. package/dist/extensions/media.d.ts +0 -19
  269. package/dist/extensions/media.js +0 -149
  270. package/dist/extensions/responseArea.d.ts +0 -27
  271. package/dist/extensions/responseArea.js +0 -259
  272. package/dist/index.d.ts +0 -13
  273. package/dist/index.js +0 -7
  274. package/dist/node_modules/.bun/clsx@2.1.1/node_modules/clsx/dist/clsx.js +0 -16
  275. package/dist/styles/editorContainerStyles.d.ts +0 -134
  276. package/dist/theme.d.ts +0 -9
  277. package/dist/utils/helper.d.ts +0 -9
  278. package/dist/utils/helper.js +0 -27
  279. package/dist/utils/size.d.ts +0 -9
  280. package/dist/utils/size.js +0 -14
@@ -0,0 +1,338 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import isEqual from 'lodash-es/isEqual';
4
+ import debug from 'debug';
5
+ import LinearProgress from '@mui/material/LinearProgress';
6
+ import { styled } from '@mui/material/styles';
7
+ import { NodeViewWrapper } from '@tiptap/react';
8
+ import ReactDOM from 'react-dom';
9
+ import InsertImageHandler from '../components/image/InsertImageHandler';
10
+ import ImageToolbar from '../components/image/ImageToolbar';
11
+ import CustomToolbarWrapper from './custom-toolbar-wrapper';
12
+
13
+ const log = debug('@pie-lib:editable-html:plugins:image:component');
14
+
15
+ const StyledProgress = styled(LinearProgress, {
16
+ shouldForwardProp: (prop) => prop !== 'hideProgress',
17
+ })(({ hideProgress }) => ({
18
+ position: 'absolute',
19
+ left: '0',
20
+ width: 'fit-content',
21
+ top: '0%',
22
+ transition: 'opacity 200ms linear',
23
+ ...(hideProgress && {
24
+ opacity: 0,
25
+ }),
26
+ }));
27
+
28
+ const StyledRoot = styled('div', {
29
+ shouldForwardProp: (prop) => !['active', 'loading', 'pendingDelete'].includes(prop),
30
+ })(({ loading, pendingDelete }) => ({
31
+ position: 'relative',
32
+ display: 'flex',
33
+ transition: 'opacity 200ms linear',
34
+ ...(loading && {
35
+ opacity: 0.3,
36
+ }),
37
+ ...(pendingDelete && {
38
+ opacity: 0.3,
39
+ }),
40
+ }));
41
+
42
+ const StyledImageContainer = styled('div')(({ theme }) => ({
43
+ position: 'relative',
44
+ width: 'fit-content',
45
+ display: 'flex',
46
+ alignItems: 'center',
47
+ '&&:hover > .resize': {
48
+ display: 'block',
49
+ },
50
+ }));
51
+
52
+ const StyledImage = styled('img', {
53
+ shouldForwardProp: (prop) => prop !== 'active',
54
+ })(({ theme, active }) => ({
55
+ border: active ? `solid 1px ${theme.palette.primary.main}` : 'solid 1px transparent',
56
+ }));
57
+
58
+ const StyledResize = styled('div')(({ theme }) => ({
59
+ backgroundColor: theme.palette.primary.main,
60
+ cursor: 'col-resize',
61
+ height: '35px',
62
+ width: '5px',
63
+ borderRadius: 8,
64
+ marginLeft: '5px',
65
+ marginRight: '10px',
66
+ display: 'none',
67
+ }));
68
+
69
+ const sizePx = (s) => (s ? `${s}px` : 'calc(20px)');
70
+
71
+ function ImageComponent(props) {
72
+ const {
73
+ node,
74
+ editor,
75
+ attributes,
76
+ onFocus,
77
+ getPos,
78
+ selected,
79
+ options,
80
+ maxImageWidth = 700,
81
+ maxImageHeight = 900,
82
+ } = props;
83
+ const { alt } = node.attrs;
84
+ const pos = getPos();
85
+
86
+ const selFrom = editor.state.selection.from;
87
+ const selTo = editor.state.selection.to;
88
+ const onlyThisNodeSelected = useMemo(() => selFrom + node.nodeSize === selTo, [selFrom, selTo, node.nodeSize]);
89
+
90
+ const [showToolbar, setShowToolbar] = useState(false);
91
+
92
+ const latestNodeRef = useRef(node);
93
+ const imgRef = useRef(null);
94
+ const resizeRef = useRef(null);
95
+ const toolbarRef = useRef(null);
96
+
97
+ const getPercentFromWidth = useCallback((width) => {
98
+ const floored = (width / imgRef.current.naturalWidth) * 4;
99
+ return parseInt(floored.toFixed(0) * 25, 10);
100
+ }, []);
101
+
102
+ const findNodePos = useCallback(() => {
103
+ const key = latestNodeRef.current.attrs.nodeKey;
104
+ let found = null;
105
+ editor.state.doc.descendants((n, pos) => {
106
+ if (found !== null) return false;
107
+ if (n.type.name === 'imageUploadNode' && n.attrs.nodeKey === key) {
108
+ found = pos;
109
+ return false;
110
+ }
111
+ });
112
+ return found;
113
+ }, [editor]);
114
+
115
+ // dispatch an attribute update targeted precisely at this node by nodeKey.
116
+ const updateThisNode = useCallback(
117
+ (newAttrs) => {
118
+ const nodePos = findNodePos();
119
+ if (nodePos === null) return;
120
+ const currentNode = editor.state.doc.nodeAt(nodePos);
121
+ if (!currentNode) return;
122
+ editor.view.dispatch(editor.state.tr.setNodeMarkup(nodePos, undefined, { ...currentNode.attrs, ...newAttrs }));
123
+ },
124
+ [editor, findNodePos],
125
+ );
126
+
127
+ const applySizeData = useCallback(() => {
128
+ if (!node.attrs.width || !imgRef.current) return;
129
+ const resizePercent = getPercentFromWidth(node.attrs.width);
130
+ if (node.attrs.resizePercent === resizePercent) return;
131
+ updateThisNode({ resizePercent });
132
+ }, [node.attrs.width, node.attrs.resizePercent, getPercentFromWidth, updateThisNode]);
133
+
134
+ // keep ref in sync with latest node
135
+ useEffect(() => {
136
+ latestNodeRef.current = { ...node, pos };
137
+ }, [node, pos]);
138
+
139
+ useEffect(() => {
140
+ if (selected) {
141
+ if (onlyThisNodeSelected) {
142
+ // Only open the upload UI for a fresh placeholder. Remounting after tab switch
143
+ // would otherwise call insertImageRequested again and reopen the file modal.
144
+ const hasImageSrc = String(node.attrs?.src ?? '').trim();
145
+
146
+ if (!hasImageSrc && options.imageHandling?.insertImageRequested) {
147
+ options.imageHandling.insertImageRequested(
148
+ editor,
149
+ [node, pos],
150
+ (finish) => new InsertImageHandler(editor, [node, pos], finish),
151
+ );
152
+ }
153
+
154
+ setShowToolbar(selected);
155
+ }
156
+ } else {
157
+ setShowToolbar(selected);
158
+ }
159
+ }, [onlyThisNodeSelected, selected]);
160
+
161
+ useEffect(() => {
162
+ applySizeData();
163
+
164
+ const resizeHandle = resizeRef.current;
165
+ if (resizeHandle) {
166
+ resizeHandle.addEventListener('mousedown', initResize, false);
167
+ }
168
+ return () => {
169
+ if (resizeHandle) {
170
+ resizeHandle.removeEventListener('mousedown', initResize, false);
171
+ }
172
+ };
173
+ }, []);
174
+
175
+ useEffect(() => {
176
+ applySizeData();
177
+ });
178
+
179
+ const loadImage = useCallback(() => {
180
+ const box = imgRef.current;
181
+ if (!box) return;
182
+
183
+ if (!box.style.width || box.style.width === 'calc(20px)') {
184
+ const w = Math.min(box.naturalWidth, maxImageWidth);
185
+ const h = Math.min(box.naturalHeight, maxImageHeight);
186
+
187
+ box.style.width = `${w}px`;
188
+ box.style.height = `${h}px`;
189
+
190
+ const update = { width: w, height: h };
191
+ if (!isEqual(update, { width: node.attrs.width, height: node.attrs.height })) {
192
+ updateThisNode(update);
193
+ }
194
+ }
195
+ }, [node.attrs.width, node.attrs.height, maxImageWidth, maxImageHeight, updateThisNode]);
196
+
197
+ const updateAspect = (initial, next, keepAspect = true, resizeType) => {
198
+ if (keepAspect) {
199
+ const ratio = initial.width / initial.height;
200
+ if (resizeType === 'height') return { width: next.height * ratio, height: next.height };
201
+ return { width: next.width, height: next.width / ratio };
202
+ }
203
+ return next;
204
+ };
205
+
206
+ const startResize = useCallback(
207
+ (e) => {
208
+ const box = imgRef.current;
209
+ if (!box) return;
210
+
211
+ const bounds = e.target.getBoundingClientRect();
212
+ const initial = { width: box.naturalWidth, height: box.naturalHeight };
213
+
214
+ const next = updateAspect(initial, {
215
+ width: e.clientX - bounds.left,
216
+ height: e.clientY - bounds.top,
217
+ });
218
+
219
+ if (next.width > 50 && next.height > 50 && next.width <= 700 && next.height <= 900) {
220
+ box.style.width = `${next.width}px`;
221
+ box.style.height = `${next.height}px`;
222
+
223
+ updateThisNode({ width: next.width, height: next.height });
224
+ }
225
+ },
226
+ [editor, updateThisNode],
227
+ );
228
+
229
+ const onChange = useCallback(
230
+ (newValues) => {
231
+ updateThisNode(newValues);
232
+ },
233
+ [editor, updateThisNode],
234
+ );
235
+
236
+ const stopResize = useCallback(() => {
237
+ window.removeEventListener('mousemove', startResize);
238
+ window.removeEventListener('mouseup', stopResize);
239
+ }, [startResize]);
240
+
241
+ const initResize = useCallback(() => {
242
+ window.addEventListener('mousemove', startResize);
243
+ window.addEventListener('mouseup', stopResize);
244
+ }, [startResize, stopResize]);
245
+
246
+ const style = {
247
+ width: sizePx(node.attrs.width),
248
+ height: sizePx(node.attrs.height),
249
+ objectFit: 'contain',
250
+ };
251
+
252
+ const flexAlign = { left: 'flex-start', center: 'center', right: 'flex-end' }[node.attrs.alignment] || 'flex-start';
253
+
254
+ return (
255
+ <NodeViewWrapper>
256
+ <StyledRoot
257
+ onFocus={onFocus}
258
+ loading={!node.attrs.loaded}
259
+ pendingDelete={node.attrs.deleteStatus === 'pending'}
260
+ style={{ justifyContent: flexAlign }}
261
+ >
262
+ <StyledProgress mode="determinate" value={node.attrs.percent || 0} hideProgress={node.attrs.loaded} />
263
+
264
+ <StyledImageContainer onDragStart={(e) => e.preventDefault()}>
265
+ <StyledImage
266
+ {...attributes}
267
+ active={selected && node.attrs.loaded}
268
+ draggable={false}
269
+ ref={imgRef}
270
+ src={node.attrs.src}
271
+ style={style}
272
+ onLoad={loadImage}
273
+ alt={node.attrs.alt}
274
+ />
275
+ <StyledResize ref={resizeRef} className="resize" />
276
+ </StyledImageContainer>
277
+ </StyledRoot>
278
+
279
+ {showToolbar &&
280
+ editor._tiptapContainerEl &&
281
+ ReactDOM.createPortal(
282
+ <div
283
+ ref={toolbarRef}
284
+ style={{
285
+ zIndex: 20,
286
+ background: 'var(--editable-html-toolbar-bg, #efefef)',
287
+ boxShadow:
288
+ '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)',
289
+ width: '100%',
290
+ }}
291
+ >
292
+ <CustomToolbarWrapper
293
+ showDone
294
+ deletable
295
+ toolbarOpts={options.toolbarOpts || {}}
296
+ onDelete={() => {
297
+ const nodePos = findNodePos();
298
+ if (nodePos === null) return;
299
+
300
+ options.imageHandling?.onDelete?.(latestNodeRef.current);
301
+
302
+ editor.view.dispatch(
303
+ editor.state.tr.delete(nodePos, nodePos + editor.state.doc.nodeAt(nodePos).nodeSize),
304
+ );
305
+ setShowToolbar(false);
306
+ editor.commands.focus();
307
+ }}
308
+ onDone={() => {
309
+ setShowToolbar(false);
310
+ options.imageHandling?.onDone?.(editor);
311
+ editor.commands.focus('end');
312
+ }}
313
+ >
314
+ <ImageToolbar
315
+ disableImageAlignmentButtons={options.imageHandling?.disableImageAlignmentButtons}
316
+ alt={node.attrs.alt}
317
+ imageLoaded={node.attrs.loaded}
318
+ alignment={node.attrs.alignment || 'left'}
319
+ onChange={onChange}
320
+ />
321
+ </CustomToolbarWrapper>
322
+ </div>,
323
+ editor._tiptapContainerEl,
324
+ )}
325
+ </NodeViewWrapper>
326
+ );
327
+ }
328
+
329
+ ImageComponent.propTypes = {
330
+ node: PropTypes.object.isRequired,
331
+ editor: PropTypes.object.isRequired,
332
+ attributes: PropTypes.object,
333
+ onFocus: PropTypes.func,
334
+ maxImageWidth: PropTypes.number,
335
+ maxImageHeight: PropTypes.number,
336
+ };
337
+
338
+ export default ImageComponent;
@@ -0,0 +1,109 @@
1
+ import { mergeAttributes, Node } from '@tiptap/core';
2
+ import { ReactNodeViewRenderer } from '@tiptap/react';
3
+ import { Plugin } from '@tiptap/pm/state';
4
+ import React from 'react';
5
+ import ImageComponent from './image-component';
6
+ import { node } from 'prop-types';
7
+
8
+ export const ImageUploadNode = Node.create({
9
+ name: 'imageUploadNode',
10
+
11
+ group: 'block',
12
+ atom: true, // ✅ prevents content holes
13
+ selectable: true, // optional
14
+ draggable: true, // optional
15
+
16
+ addAttributes() {
17
+ return {
18
+ nodeKey: { default: null },
19
+ loaded: { default: false },
20
+ deleteStatus: { default: null },
21
+ alignment: { default: null },
22
+ percent: { default: null },
23
+ width: { default: null },
24
+ height: { default: null },
25
+ src: { default: null },
26
+ alt: { default: null },
27
+ };
28
+ },
29
+
30
+ parseHTML() {
31
+ return [
32
+ {
33
+ tag: 'img[data-type="image-upload-node"]',
34
+ },
35
+ ];
36
+ },
37
+
38
+ renderHTML({ HTMLAttributes }) {
39
+ return ['img', mergeAttributes(HTMLAttributes, { 'data-type': 'image-upload-node' })];
40
+ },
41
+
42
+ addNodeView() {
43
+ return ReactNodeViewRenderer((props) => <ImageComponent {...{ ...props, options: this.options }} />);
44
+ },
45
+
46
+ addCommands() {
47
+ return {
48
+ setImageUploadNode:
49
+ () =>
50
+ ({ commands }) => {
51
+ return commands.insertContent({
52
+ type: this.name,
53
+ // adding a unique nodeKey attribute to help identify this node instance later due to issues with multiple images
54
+ attrs: { nodeKey: `img-${Date.now()}-${Math.random().toString(36).slice(2)}` },
55
+ });
56
+ },
57
+ };
58
+ },
59
+
60
+ addProseMirrorPlugins() {
61
+ const editor = this.editor;
62
+
63
+ return [
64
+ new Plugin({
65
+ props: {
66
+ handlePaste(view, event) {
67
+ const items = Array.from(event.clipboardData?.items || []);
68
+
69
+ const imageItem = items.find((item) => item.kind === 'file' && item.type.startsWith('image/'));
70
+
71
+ if (!imageItem) {
72
+ return false;
73
+ }
74
+
75
+ const file = imageItem.getAsFile();
76
+
77
+ if (!file) {
78
+ return false;
79
+ }
80
+
81
+ // Example 1: insert as base64 immediately
82
+ const reader = new FileReader();
83
+
84
+ reader.onload = () => {
85
+ const src = reader.result;
86
+
87
+ if (typeof src !== 'string') {
88
+ return;
89
+ }
90
+
91
+ editor.commands.insertContent({
92
+ type: 'imageUploadNode',
93
+ attrs: {
94
+ src,
95
+ loaded: true,
96
+ nodeKey: `img-${Date.now()}-${Math.random().toString(36).slice(2)}`,
97
+ },
98
+ });
99
+ };
100
+
101
+ reader.readAsDataURL(file);
102
+
103
+ return true;
104
+ },
105
+ },
106
+ }),
107
+ ];
108
+ },
109
+ });
@@ -0,0 +1,81 @@
1
+ import compact from 'lodash-es/compact';
2
+ import isEmpty from 'lodash-es/isEmpty';
3
+ import debug from 'debug';
4
+
5
+ const log = debug('@pie-lib:editable-html:plugins');
6
+ export const ALL_PLUGINS = [
7
+ 'bold',
8
+ // 'code',
9
+ 'html',
10
+ 'extraCSSRules',
11
+ 'italic',
12
+ 'underline',
13
+ 'strikethrough',
14
+ 'bulleted-list',
15
+ 'numbered-list',
16
+ 'image',
17
+ 'math',
18
+ 'languageCharacters',
19
+ 'text-align',
20
+ 'blockquote',
21
+ 'h3',
22
+ 'table',
23
+ 'video',
24
+ 'audio',
25
+ 'responseArea',
26
+ 'redo',
27
+ 'undo',
28
+ 'superscript',
29
+ 'subscript',
30
+ ];
31
+
32
+ export const PLUGINS_MAP = {
33
+ 'text-align': 'textAlign',
34
+ 'bulleted-list': 'ul_list',
35
+ 'numbered-list': 'ol_list',
36
+ };
37
+
38
+ export const DEFAULT_PLUGINS = ALL_PLUGINS.filter((plug) => !['responseArea', 'h3', 'blockquote'].includes(plug));
39
+
40
+ export const buildExtensions = (activeExtensions, customExtensions, opts) => {
41
+ log('[buildPlugins] opts: ', opts);
42
+
43
+ activeExtensions = activeExtensions || DEFAULT_PLUGINS;
44
+
45
+ const addIf = (key, shouldAdd = true) => activeExtensions.includes(key) && shouldAdd && key;
46
+
47
+ const imagePlugin = opts.image && opts.image.delete;
48
+ const mathPlugin = opts.math;
49
+ const respAreaPlugin = opts.responseArea && opts.responseArea.type;
50
+ const cssPlugin = !isEmpty(opts.extraCSSRules);
51
+
52
+ const languageCharactersPlugins = opts?.languageCharacters || [];
53
+
54
+ return compact([
55
+ addIf('table'),
56
+ addIf('bold'),
57
+ // addIf('code', MarkHotkey({ key: '`', type: 'code', icon: <Code /> })),
58
+ addIf('italic'),
59
+ addIf('strikethrough'),
60
+ addIf('underline'),
61
+ // icon should be modifies accordingly
62
+ addIf('superscript'),
63
+ // icon should be modifies accordingly
64
+ addIf('subscript'),
65
+ addIf('image', !!imagePlugin),
66
+ addIf('video'),
67
+ addIf('audio'),
68
+ addIf('math', !!mathPlugin),
69
+ ...languageCharactersPlugins.map((plugin) => addIf('languageCharacters', plugin)),
70
+ addIf('text-align'),
71
+ addIf('blockquote'),
72
+ addIf('h3'),
73
+ addIf('bulleted-list'),
74
+ addIf('numbered-list'),
75
+ addIf('undo'),
76
+ addIf('redo'),
77
+ addIf('responseArea', !!respAreaPlugin),
78
+ addIf('css', !!cssPlugin),
79
+ addIf('html', !!opts.html),
80
+ ]);
81
+ };