@pega/cosmos-react-rte 9.0.0-build.9.9 → 9.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 (191) hide show
  1. package/lib/components/DynamicContentEditor/DynamicContentEditor.d.ts +4 -2
  2. package/lib/components/DynamicContentEditor/DynamicContentEditor.d.ts.map +1 -1
  3. package/lib/components/DynamicContentEditor/DynamicContentEditor.js +64 -59
  4. package/lib/components/DynamicContentEditor/DynamicContentEditor.js.map +1 -1
  5. package/lib/components/Editor/Editor.context.d.ts +6 -6
  6. package/lib/components/Editor/Editor.context.d.ts.map +1 -1
  7. package/lib/components/Editor/Editor.context.js +1 -1
  8. package/lib/components/Editor/Editor.context.js.map +1 -1
  9. package/lib/components/Editor/Editor.d.ts +1 -10
  10. package/lib/components/Editor/Editor.d.ts.map +1 -1
  11. package/lib/components/Editor/Editor.js +301 -490
  12. package/lib/components/Editor/Editor.js.map +1 -1
  13. package/lib/components/Editor/Editor.styles.d.ts +37 -7
  14. package/lib/components/Editor/Editor.styles.d.ts.map +1 -1
  15. package/lib/components/Editor/Editor.styles.js +60 -30
  16. package/lib/components/Editor/Editor.styles.js.map +1 -1
  17. package/lib/components/Editor/Editor.test-ids.d.ts +2 -1
  18. package/lib/components/Editor/Editor.test-ids.d.ts.map +1 -1
  19. package/lib/components/Editor/Editor.test-ids.js +2 -0
  20. package/lib/components/Editor/Editor.test-ids.js.map +1 -1
  21. package/lib/components/Editor/Editor.types.d.ts +34 -14
  22. package/lib/components/Editor/Editor.types.d.ts.map +1 -1
  23. package/lib/components/Editor/Editor.types.js.map +1 -1
  24. package/lib/components/Editor/IframeTiptapEditor.d.ts +30 -0
  25. package/lib/components/Editor/IframeTiptapEditor.d.ts.map +1 -0
  26. package/lib/components/Editor/IframeTiptapEditor.js +695 -0
  27. package/lib/components/Editor/IframeTiptapEditor.js.map +1 -0
  28. package/lib/components/Editor/ImageActionButtons.d.ts +20 -0
  29. package/lib/components/Editor/ImageActionButtons.d.ts.map +1 -0
  30. package/lib/components/Editor/ImageActionButtons.js +84 -0
  31. package/lib/components/Editor/ImageActionButtons.js.map +1 -0
  32. package/lib/components/Editor/ImageEditDialog.d.ts +17 -0
  33. package/lib/components/Editor/ImageEditDialog.d.ts.map +1 -0
  34. package/lib/components/Editor/ImageEditDialog.js +90 -0
  35. package/lib/components/Editor/ImageEditDialog.js.map +1 -0
  36. package/lib/components/Editor/TableCellMenu.d.ts +35 -0
  37. package/lib/components/Editor/TableCellMenu.d.ts.map +1 -0
  38. package/lib/components/Editor/TableCellMenu.js +120 -0
  39. package/lib/components/Editor/TableCellMenu.js.map +1 -0
  40. package/lib/components/Editor/Toolbar/AIRewriteButton.d.ts +17 -0
  41. package/lib/components/Editor/Toolbar/AIRewriteButton.d.ts.map +1 -0
  42. package/lib/components/Editor/Toolbar/AIRewriteButton.js +79 -0
  43. package/lib/components/Editor/Toolbar/AIRewriteButton.js.map +1 -0
  44. package/lib/components/Editor/Toolbar/AlignmentSelect.d.ts +8 -0
  45. package/lib/components/Editor/Toolbar/AlignmentSelect.d.ts.map +1 -0
  46. package/lib/components/Editor/Toolbar/AlignmentSelect.js +137 -0
  47. package/lib/components/Editor/Toolbar/AlignmentSelect.js.map +1 -0
  48. package/lib/components/Editor/Toolbar/AnchorButton.d.ts +3 -4
  49. package/lib/components/Editor/Toolbar/AnchorButton.d.ts.map +1 -1
  50. package/lib/components/Editor/Toolbar/AnchorButton.js +156 -82
  51. package/lib/components/Editor/Toolbar/AnchorButton.js.map +1 -1
  52. package/lib/components/Editor/Toolbar/ColorPickerButton.d.ts +9 -0
  53. package/lib/components/Editor/Toolbar/ColorPickerButton.d.ts.map +1 -0
  54. package/lib/components/Editor/Toolbar/ColorPickerButton.js +190 -0
  55. package/lib/components/Editor/Toolbar/ColorPickerButton.js.map +1 -0
  56. package/lib/components/Editor/Toolbar/FontFamilySelect.d.ts +8 -0
  57. package/lib/components/Editor/Toolbar/FontFamilySelect.d.ts.map +1 -0
  58. package/lib/components/Editor/Toolbar/FontFamilySelect.js +150 -0
  59. package/lib/components/Editor/Toolbar/FontFamilySelect.js.map +1 -0
  60. package/lib/components/Editor/Toolbar/FontSizeSelect.d.ts +8 -0
  61. package/lib/components/Editor/Toolbar/FontSizeSelect.d.ts.map +1 -0
  62. package/lib/components/Editor/Toolbar/FontSizeSelect.js +145 -0
  63. package/lib/components/Editor/Toolbar/FontSizeSelect.js.map +1 -0
  64. package/lib/components/Editor/Toolbar/ImageButton.d.ts +5 -5
  65. package/lib/components/Editor/Toolbar/ImageButton.d.ts.map +1 -1
  66. package/lib/components/Editor/Toolbar/ImageButton.js +131 -18
  67. package/lib/components/Editor/Toolbar/ImageButton.js.map +1 -1
  68. package/lib/components/Editor/Toolbar/SourceCodeButton.d.ts +8 -0
  69. package/lib/components/Editor/Toolbar/SourceCodeButton.d.ts.map +1 -0
  70. package/lib/components/Editor/Toolbar/SourceCodeButton.js +49 -0
  71. package/lib/components/Editor/Toolbar/SourceCodeButton.js.map +1 -0
  72. package/lib/components/Editor/Toolbar/TableButton.d.ts +8 -0
  73. package/lib/components/Editor/Toolbar/TableButton.d.ts.map +1 -0
  74. package/lib/components/Editor/Toolbar/TableButton.js +291 -0
  75. package/lib/components/Editor/Toolbar/TableButton.js.map +1 -0
  76. package/lib/components/Editor/Toolbar/TextSelect.d.ts +4 -5
  77. package/lib/components/Editor/Toolbar/TextSelect.d.ts.map +1 -1
  78. package/lib/components/Editor/Toolbar/TextSelect.js +61 -30
  79. package/lib/components/Editor/Toolbar/TextSelect.js.map +1 -1
  80. package/lib/components/Editor/Toolbar/Toolbar.d.ts +17 -6
  81. package/lib/components/Editor/Toolbar/Toolbar.d.ts.map +1 -1
  82. package/lib/components/Editor/Toolbar/Toolbar.js +169 -47
  83. package/lib/components/Editor/Toolbar/Toolbar.js.map +1 -1
  84. package/lib/components/Editor/Toolbar/Toolbar.test-ids.d.ts +2 -2
  85. package/lib/components/Editor/Toolbar/Toolbar.test-ids.d.ts.map +1 -1
  86. package/lib/components/Editor/Toolbar/Toolbar.test-ids.js +17 -1
  87. package/lib/components/Editor/Toolbar/Toolbar.test-ids.js.map +1 -1
  88. package/lib/components/Editor/Toolbar/WordCount.d.ts +8 -0
  89. package/lib/components/Editor/Toolbar/WordCount.d.ts.map +1 -0
  90. package/lib/components/Editor/Toolbar/WordCount.js +31 -0
  91. package/lib/components/Editor/Toolbar/WordCount.js.map +1 -0
  92. package/lib/components/Editor/extensions/FontSize.d.ts +21 -0
  93. package/lib/components/Editor/extensions/FontSize.d.ts.map +1 -0
  94. package/lib/components/Editor/extensions/FontSize.js +42 -0
  95. package/lib/components/Editor/extensions/FontSize.js.map +1 -0
  96. package/lib/components/Editor/extensions/PreserveDiv.d.ts +13 -0
  97. package/lib/components/Editor/extensions/PreserveDiv.d.ts.map +1 -0
  98. package/lib/components/Editor/extensions/PreserveDiv.js +73 -0
  99. package/lib/components/Editor/extensions/PreserveDiv.js.map +1 -0
  100. package/lib/components/Editor/extensions/TableCellSelection.d.ts +4 -0
  101. package/lib/components/Editor/extensions/TableCellSelection.d.ts.map +1 -0
  102. package/lib/components/Editor/extensions/TableCellSelection.js +53 -0
  103. package/lib/components/Editor/extensions/TableCellSelection.js.map +1 -0
  104. package/lib/components/Editor/extensions/TextIndent.d.ts +22 -0
  105. package/lib/components/Editor/extensions/TextIndent.d.ts.map +1 -0
  106. package/lib/components/Editor/extensions/TextIndent.js +137 -0
  107. package/lib/components/Editor/extensions/TextIndent.js.map +1 -0
  108. package/lib/components/Editor/hooks/useCloseOnEditorClick.d.ts +5 -0
  109. package/lib/components/Editor/hooks/useCloseOnEditorClick.d.ts.map +1 -0
  110. package/lib/components/Editor/hooks/useCloseOnEditorClick.js +18 -0
  111. package/lib/components/Editor/hooks/useCloseOnEditorClick.js.map +1 -0
  112. package/lib/components/Editor/hooks/useEscapeKey.d.ts +4 -0
  113. package/lib/components/Editor/hooks/useEscapeKey.d.ts.map +1 -0
  114. package/lib/components/Editor/hooks/useEscapeKey.js +24 -0
  115. package/lib/components/Editor/hooks/useEscapeKey.js.map +1 -0
  116. package/lib/components/Editor/hooks/useIframeSetup.d.ts +54 -0
  117. package/lib/components/Editor/hooks/useIframeSetup.d.ts.map +1 -0
  118. package/lib/components/Editor/hooks/useIframeSetup.js +284 -0
  119. package/lib/components/Editor/hooks/useIframeSetup.js.map +1 -0
  120. package/lib/components/Editor/hooks/useImageActions.d.ts +19 -0
  121. package/lib/components/Editor/hooks/useImageActions.d.ts.map +1 -0
  122. package/lib/components/Editor/hooks/useImageActions.js +198 -0
  123. package/lib/components/Editor/hooks/useImageActions.js.map +1 -0
  124. package/lib/components/Editor/hooks/useTableCellMenu.d.ts +22 -0
  125. package/lib/components/Editor/hooks/useTableCellMenu.d.ts.map +1 -0
  126. package/lib/components/Editor/hooks/useTableCellMenu.js +120 -0
  127. package/lib/components/Editor/hooks/useTableCellMenu.js.map +1 -0
  128. package/lib/components/Editor/iframeContentStyles.d.ts +10 -0
  129. package/lib/components/Editor/iframeContentStyles.d.ts.map +1 -0
  130. package/lib/components/Editor/iframeContentStyles.js +162 -0
  131. package/lib/components/Editor/iframeContentStyles.js.map +1 -0
  132. package/lib/components/Editor/index.d.ts +2 -0
  133. package/lib/components/Editor/index.d.ts.map +1 -1
  134. package/lib/components/Editor/index.js +1 -0
  135. package/lib/components/Editor/index.js.map +1 -1
  136. package/lib/components/Editor/sanitize.d.ts +3 -0
  137. package/lib/components/Editor/sanitize.d.ts.map +1 -0
  138. package/lib/components/Editor/sanitize.js +11 -0
  139. package/lib/components/Editor/sanitize.js.map +1 -0
  140. package/lib/components/Editor/utils/htmlPlaceholder.d.ts +69 -0
  141. package/lib/components/Editor/utils/htmlPlaceholder.d.ts.map +1 -0
  142. package/lib/components/Editor/utils/htmlPlaceholder.js +154 -0
  143. package/lib/components/Editor/utils/htmlPlaceholder.js.map +1 -0
  144. package/lib/components/RichTextEditor/DecoratorComponents/Table.d.ts +6 -4
  145. package/lib/components/RichTextEditor/DecoratorComponents/Table.d.ts.map +1 -1
  146. package/lib/components/RichTextEditor/DecoratorComponents/Table.js +10 -8
  147. package/lib/components/RichTextEditor/DecoratorComponents/Table.js.map +1 -1
  148. package/lib/components/RichTextEditor/RichTextEditor.d.ts.map +1 -1
  149. package/lib/components/RichTextEditor/RichTextEditor.js +15 -2
  150. package/lib/components/RichTextEditor/RichTextEditor.js.map +1 -1
  151. package/lib/components/RichTextEditor/RichTextEditor.styles.d.ts +5 -5
  152. package/lib/components/RichTextEditor/RichTextEditor.styles.d.ts.map +1 -1
  153. package/lib/components/RichTextEditor/RichTextEditor.styles.js +3 -5
  154. package/lib/components/RichTextEditor/RichTextEditor.styles.js.map +1 -1
  155. package/lib/components/RichTextEditor/RichTextEditor.types.d.ts +5 -0
  156. package/lib/components/RichTextEditor/RichTextEditor.types.d.ts.map +1 -1
  157. package/lib/components/RichTextEditor/RichTextEditor.types.js.map +1 -1
  158. package/lib/components/RichTextEditor/RichTextViewer.d.ts.map +1 -1
  159. package/lib/components/RichTextEditor/RichTextViewer.js +9 -2
  160. package/lib/components/RichTextEditor/RichTextViewer.js.map +1 -1
  161. package/lib/components/RichTextEditor/Toolbar/Toolbar.js +1 -1
  162. package/lib/components/RichTextEditor/Toolbar/Toolbar.js.map +1 -1
  163. package/lib/components/RichTextEditor/Toolbar/Toolbar.types.d.ts +4 -4
  164. package/lib/components/RichTextEditor/Toolbar/Toolbar.types.d.ts.map +1 -1
  165. package/lib/components/RichTextEditor/Toolbar/Toolbar.types.js.map +1 -1
  166. package/lib/components/RichTextEditor/Toolbar/ToolbarButton.d.ts.map +1 -1
  167. package/lib/components/RichTextEditor/Toolbar/ToolbarButton.js +41 -26
  168. package/lib/components/RichTextEditor/Toolbar/ToolbarButton.js.map +1 -1
  169. package/lib/components/RichTextEditor/utils/htmlConverter.d.ts +2 -0
  170. package/lib/components/RichTextEditor/utils/htmlConverter.d.ts.map +1 -1
  171. package/lib/components/RichTextEditor/utils/htmlConverter.js +12 -0
  172. package/lib/components/RichTextEditor/utils/htmlConverter.js.map +1 -1
  173. package/lib/components/RichTextEditor/utils/interactionRenderer.d.ts.map +1 -1
  174. package/lib/components/RichTextEditor/utils/interactionRenderer.js +20 -19
  175. package/lib/components/RichTextEditor/utils/interactionRenderer.js.map +1 -1
  176. package/lib/components/RichTextEditor/utils/markdownConverter.d.ts.map +1 -1
  177. package/lib/components/RichTextEditor/utils/markdownConverter.js +131 -30
  178. package/lib/components/RichTextEditor/utils/markdownConverter.js.map +1 -1
  179. package/lib/components/RichTextEditor/utils/renderers.d.ts +5 -3
  180. package/lib/components/RichTextEditor/utils/renderers.d.ts.map +1 -1
  181. package/lib/components/RichTextEditor/utils/renderers.js +62 -34
  182. package/lib/components/RichTextEditor/utils/renderers.js.map +1 -1
  183. package/lib/components/RichTextEditor/utils/slateConverter.d.ts +4 -3
  184. package/lib/components/RichTextEditor/utils/slateConverter.d.ts.map +1 -1
  185. package/lib/components/RichTextEditor/utils/slateConverter.js +86 -38
  186. package/lib/components/RichTextEditor/utils/slateConverter.js.map +1 -1
  187. package/package.json +30 -8
  188. package/lib/components/Editor/ImageEditor.d.ts +0 -10
  189. package/lib/components/Editor/ImageEditor.d.ts.map +0 -1
  190. package/lib/components/Editor/ImageEditor.js +0 -292
  191. package/lib/components/Editor/ImageEditor.js.map +0 -1
@@ -0,0 +1,695 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEditor, EditorContent } from '@tiptap/react';
3
+ import { Extension, Node } from '@tiptap/core';
4
+ import StarterKit from '@tiptap/starter-kit';
5
+ import Placeholder from '@tiptap/extension-placeholder';
6
+ import Link from '@tiptap/extension-link';
7
+ import Image from '@tiptap/extension-image';
8
+ import Blockquote from '@tiptap/extension-blockquote';
9
+ import Paragraph from '@tiptap/extension-paragraph';
10
+ import Underline from '@tiptap/extension-underline';
11
+ import Subscript from '@tiptap/extension-subscript';
12
+ import Superscript from '@tiptap/extension-superscript';
13
+ import { TextStyle } from '@tiptap/extension-text-style';
14
+ import { Color } from '@tiptap/extension-color';
15
+ import Highlight from '@tiptap/extension-highlight';
16
+ import FontFamily from '@tiptap/extension-font-family';
17
+ import TextAlign from '@tiptap/extension-text-align';
18
+ import { Table } from '@tiptap/extension-table';
19
+ import TableRow from '@tiptap/extension-table-row';
20
+ import TableCell from '@tiptap/extension-table-cell';
21
+ import TableHeader from '@tiptap/extension-table-header';
22
+ import OfficePaste from '@intevation/tiptap-extension-office-paste';
23
+ import { useEffect, useImperativeHandle, useMemo, useRef } from 'react';
24
+ import { createUID } from '@pega/cosmos-react-core';
25
+ import { TextIndent } from './extensions/TextIndent';
26
+ import { PreserveDiv } from './extensions/PreserveDiv';
27
+ import { FontSize } from './extensions/FontSize';
28
+ import TableCellSelection from './extensions/TableCellSelection';
29
+ import sanitizeHtml from './sanitize';
30
+ // Extend Blockquote to preserve inline styles (e.g., border-left from email chains)
31
+ const BlockquoteWithStyle = Blockquote.extend({
32
+ addAttributes() {
33
+ return {
34
+ ...this.parent?.(),
35
+ style: {
36
+ default: null,
37
+ parseHTML: element => element.getAttribute('style'),
38
+ renderHTML: attributes => {
39
+ if (!attributes.style)
40
+ return {};
41
+ return { style: attributes.style };
42
+ }
43
+ }
44
+ };
45
+ }
46
+ });
47
+ // Extend Image to preserve custom attribute data-attachment-id in img tag
48
+ const ImageWithDataAttachmentId = Image.extend({
49
+ addAttributes() {
50
+ return {
51
+ ...this.parent?.(),
52
+ 'data-attachment-id': {
53
+ default: null,
54
+ parseHTML: element => element.getAttribute('data-attachment-id'),
55
+ renderHTML: attributes => {
56
+ if (!attributes['data-attachment-id'])
57
+ return {};
58
+ return { 'data-attachment-id': attributes['data-attachment-id'] };
59
+ }
60
+ }
61
+ };
62
+ }
63
+ });
64
+ // Extend Paragraph to preserve class and style attributes (e.g., pega-email-reply-header)
65
+ const ParagraphWithStyle = Paragraph.extend({
66
+ addAttributes() {
67
+ return {
68
+ ...this.parent?.(),
69
+ class: {
70
+ default: null,
71
+ parseHTML: element => element.getAttribute('class'),
72
+ renderHTML: attributes => {
73
+ if (!attributes.class)
74
+ return {};
75
+ return { class: attributes.class };
76
+ }
77
+ },
78
+ style: {
79
+ default: null,
80
+ parseHTML: element => element.getAttribute('style'),
81
+ renderHTML: attributes => {
82
+ if (!attributes.style)
83
+ return {};
84
+ return { style: attributes.style };
85
+ }
86
+ }
87
+ };
88
+ }
89
+ });
90
+ // Helper to check if cursor is at end of table cell content
91
+ const isAtEndOfCell = (editor) => {
92
+ const { state } = editor;
93
+ const { selection } = state;
94
+ if (!selection.empty)
95
+ return false;
96
+ const isInTable = editor.isActive('tableCell') || editor.isActive('tableHeader');
97
+ if (!isInTable)
98
+ return false;
99
+ const $pos = selection.$from;
100
+ // Check if there's any content after the cursor within the cell
101
+ // by checking if we're at the end of the parent (paragraph) and it's the last child of the cell
102
+ const parentEnd = $pos.end($pos.depth);
103
+ const atEndOfParagraph = $pos.pos === parentEnd;
104
+ if (!atEndOfParagraph)
105
+ return false;
106
+ // Check if this paragraph is the last block in the cell
107
+ let cellDepth = $pos.depth;
108
+ while (cellDepth > 0) {
109
+ const node = $pos.node(cellDepth);
110
+ if (node.type.name === 'tableCell' || node.type.name === 'tableHeader') {
111
+ break;
112
+ }
113
+ cellDepth -= 1;
114
+ }
115
+ if (cellDepth === 0)
116
+ return false;
117
+ const cellNode = $pos.node(cellDepth);
118
+ const indexInCell = $pos.index(cellDepth);
119
+ const isLastBlock = indexInCell === cellNode.childCount - 1;
120
+ return atEndOfParagraph && isLastBlock;
121
+ };
122
+ // Helper to check if cursor is at start of table cell content
123
+ const isAtStartOfCell = (editor) => {
124
+ const { state } = editor;
125
+ const { selection } = state;
126
+ if (!selection.empty)
127
+ return false;
128
+ const isInTable = editor.isActive('tableCell') || editor.isActive('tableHeader');
129
+ if (!isInTable)
130
+ return false;
131
+ const $pos = selection.$from;
132
+ // Check if we're at the start of the parent (paragraph)
133
+ const parentStart = $pos.start($pos.depth);
134
+ const atStartOfParagraph = $pos.pos === parentStart;
135
+ if (!atStartOfParagraph)
136
+ return false;
137
+ // Check if this paragraph is the first block in the cell
138
+ let cellDepth = $pos.depth;
139
+ while (cellDepth > 0) {
140
+ const node = $pos.node(cellDepth);
141
+ if (node.type.name === 'tableCell' || node.type.name === 'tableHeader') {
142
+ break;
143
+ }
144
+ cellDepth -= 1;
145
+ }
146
+ if (cellDepth === 0)
147
+ return false;
148
+ const indexInCell = $pos.index(cellDepth);
149
+ const isFirstBlock = indexInCell === 0;
150
+ return atStartOfParagraph && isFirstBlock;
151
+ };
152
+ // Custom keyboard shortcuts that match the toolbar tooltips
153
+ const CustomKeyboardShortcuts = Extension.create({
154
+ name: 'customKeyboardShortcuts',
155
+ addOptions() {
156
+ return {
157
+ onFocusTableMenu: undefined,
158
+ onFocusPreviousCellMenu: undefined
159
+ };
160
+ },
161
+ addKeyboardShortcuts() {
162
+ return {
163
+ 'Mod-Shift-x': () => this.editor.commands.toggleStrike(),
164
+ 'Mod-Shift-l': () => this.editor.commands.toggleBulletList(),
165
+ // Override Tab in tables to prevent auto-adding rows
166
+ Tab: () => {
167
+ if (this.editor.isActive('table')) {
168
+ // Just navigate, don't add rows - goToNextCell returns false at last cell
169
+ this.editor.commands.goToNextCell();
170
+ return true; // Consume the event either way
171
+ }
172
+ return false;
173
+ },
174
+ 'Shift-Tab': () => {
175
+ if (this.editor.isActive('table')) {
176
+ this.editor.commands.goToPreviousCell();
177
+ return true;
178
+ }
179
+ return false;
180
+ },
181
+ ArrowRight: () => {
182
+ if (isAtEndOfCell(this.editor) && this.options.onFocusTableMenu) {
183
+ this.options.onFocusTableMenu();
184
+ return true;
185
+ }
186
+ return false;
187
+ },
188
+ ArrowLeft: () => {
189
+ if (isAtStartOfCell(this.editor) && this.options.onFocusPreviousCellMenu) {
190
+ this.options.onFocusPreviousCellMenu();
191
+ return true;
192
+ }
193
+ return false;
194
+ }
195
+ };
196
+ }
197
+ });
198
+ // Extension to delete empty table on repeated delete/backspace in empty cell
199
+ const TableDeleteOnEmpty = Extension.create({
200
+ name: 'tableDeleteOnEmpty',
201
+ addStorage() {
202
+ return {
203
+ deleteCount: 0,
204
+ lastDeleteTime: 0
205
+ };
206
+ },
207
+ addKeyboardShortcuts() {
208
+ const handleDelete = () => {
209
+ const { editor } = this;
210
+ const { state } = editor;
211
+ const { selection } = state;
212
+ // Check if we're in a table cell
213
+ const isInTable = editor.isActive('tableCell') || editor.isActive('tableHeader');
214
+ if (!isInTable) {
215
+ this.storage.deleteCount = 0;
216
+ return false;
217
+ }
218
+ // Find the current cell node
219
+ const $pos = selection.$from;
220
+ let cellDepth = $pos.depth;
221
+ while (cellDepth > 0) {
222
+ const node = $pos.node(cellDepth);
223
+ if (node.type.name === 'tableCell' || node.type.name === 'tableHeader') {
224
+ break;
225
+ }
226
+ cellDepth -= 1;
227
+ }
228
+ if (cellDepth === 0) {
229
+ this.storage.deleteCount = 0;
230
+ return false;
231
+ }
232
+ const cellNode = $pos.node(cellDepth);
233
+ const cellContent = cellNode.textContent.trim();
234
+ // If cell has content, reset counter and let default behavior handle it
235
+ if (cellContent.length > 0) {
236
+ this.storage.deleteCount = 0;
237
+ return false;
238
+ }
239
+ // Find the table node and check if entire table is empty
240
+ let tableDepth = cellDepth;
241
+ while (tableDepth > 0) {
242
+ const node = $pos.node(tableDepth);
243
+ if (node.type.name === 'table') {
244
+ break;
245
+ }
246
+ tableDepth -= 1;
247
+ }
248
+ if (tableDepth === 0) {
249
+ this.storage.deleteCount = 0;
250
+ return false;
251
+ }
252
+ const tableNode = $pos.node(tableDepth);
253
+ const tableContent = tableNode.textContent.trim();
254
+ // Only allow table deletion if entire table is empty
255
+ if (tableContent.length > 0) {
256
+ this.storage.deleteCount = 0;
257
+ return false;
258
+ }
259
+ // Table is empty - increment delete counter
260
+ const now = Date.now();
261
+ // Reset counter if more than 1 second since last delete
262
+ if (now - this.storage.lastDeleteTime > 1000) {
263
+ this.storage.deleteCount = 0;
264
+ }
265
+ this.storage.deleteCount += 1;
266
+ this.storage.lastDeleteTime = now;
267
+ // After 3 deletes in empty table, delete the table
268
+ if (this.storage.deleteCount >= 3) {
269
+ this.storage.deleteCount = 0;
270
+ editor.chain().focus().deleteTable().run();
271
+ return true;
272
+ }
273
+ return false;
274
+ };
275
+ return {
276
+ Backspace: handleDelete,
277
+ Delete: handleDelete
278
+ };
279
+ }
280
+ });
281
+ const getFileFromUrl = async (url, name) => {
282
+ try {
283
+ const response = await fetch(url);
284
+ if (!response.ok) {
285
+ return null;
286
+ }
287
+ const data = await response.blob();
288
+ return new File([data], name, {
289
+ type: data.type
290
+ });
291
+ }
292
+ catch {
293
+ return null;
294
+ }
295
+ };
296
+ // Component that renders inside the iframe
297
+ export const IframeTiptapEditor = ({ placeholder, defaultValue, disabled, readOnly, onChange, onKeyDown, onFocus, onBlur, onInit, editorRef, spellcheck = true, customElements = [], initOptions, onImageAdded, imagesEnabled = false, linksEnabled = true, imageInsertionMode = 'all', pastedImagesRef, editorId = '', secure = false, onFocusTableMenu, onFocusPreviousCellMenu }) => {
298
+ // Keep callbacks in refs so the extension always has access to the latest callback
299
+ const onFocusTableMenuRef = useRef(onFocusTableMenu);
300
+ onFocusTableMenuRef.current = onFocusTableMenu;
301
+ const onFocusPreviousCellMenuRef = useRef(onFocusPreviousCellMenu);
302
+ onFocusPreviousCellMenuRef.current = onFocusPreviousCellMenu;
303
+ // Process pasted image URLs and convert to File objects
304
+ const processPastedImgUrls = async (infoArr) => {
305
+ const files = await Promise.all(infoArr.map(({ id: imgId, url }) => getFileFromUrl(url, imgId)));
306
+ files.forEach((file, i) => {
307
+ if (file) {
308
+ onImageAdded?.(file, infoArr[i].id);
309
+ }
310
+ });
311
+ };
312
+ // Generate Tiptap extensions for custom components
313
+ const customComponentExtensions = useMemo(() => {
314
+ return customElements.map((component) => {
315
+ return Node.create({
316
+ name: component.name,
317
+ group: 'inline',
318
+ inline: true,
319
+ atom: true,
320
+ content: 'text*', // Allow text content inside
321
+ addAttributes() {
322
+ const attrs = {};
323
+ if (component.extensionAttributes) {
324
+ component.extensionAttributes.forEach((attr) => {
325
+ attrs[attr] = { default: null };
326
+ });
327
+ }
328
+ // Add common attributes for pega-file
329
+ attrs['data-id'] = { default: null };
330
+ attrs['data-name'] = { default: null };
331
+ attrs['data-url'] = { default: null };
332
+ attrs['data-progress'] = { default: null };
333
+ attrs['data-error'] = { default: null };
334
+ attrs['data-alt'] = { default: null };
335
+ // Add attributes for pega-reference
336
+ attrs['data-rule-id'] = { default: null };
337
+ attrs['data-rule-type'] = { default: null };
338
+ attrs['data-rule-namespace'] = { default: null };
339
+ attrs.role = { default: null };
340
+ return attrs;
341
+ },
342
+ parseHTML() {
343
+ return [
344
+ {
345
+ tag: component.name,
346
+ getAttrs: (node) => {
347
+ if (typeof node === 'string')
348
+ return null;
349
+ const attrs = {};
350
+ // Get all data attributes and role
351
+ Array.from(node.attributes).forEach(attr => {
352
+ if (attr.name.startsWith('data-') ||
353
+ attr.name === 'role' ||
354
+ component.extensionAttributes?.includes(attr.name)) {
355
+ attrs[attr.name] = attr.value;
356
+ }
357
+ });
358
+ return attrs;
359
+ }
360
+ }
361
+ ];
362
+ },
363
+ renderHTML({ HTMLAttributes, node }) {
364
+ // Get text content from the node
365
+ const textContent = node.textContent || '';
366
+ return [component.name, HTMLAttributes, textContent];
367
+ }
368
+ });
369
+ });
370
+ }, [customElements]);
371
+ const editor = useEditor({
372
+ extensions: [
373
+ StarterKit.configure({
374
+ // Exclude link from StarterKit since we're configuring it separately
375
+ link: false,
376
+ // Exclude blockquote so we can use our extended version with style preservation
377
+ blockquote: false,
378
+ // Exclude paragraph so we can use our extended version with class/style preservation
379
+ paragraph: false,
380
+ // Disable text patterns if specified in initOptions
381
+ ...(initOptions?.textPatterns === false && {
382
+ typography: false
383
+ })
384
+ }),
385
+ BlockquoteWithStyle,
386
+ ParagraphWithStyle,
387
+ Placeholder.configure({
388
+ placeholder: placeholder ?? ''
389
+ }),
390
+ TextIndent.configure({
391
+ types: ['paragraph', 'heading']
392
+ }),
393
+ // Link extension is always loaded to preserve existing links in content
394
+ // When linksEnabled is false, autolink is disabled to prevent auto-converting URLs
395
+ Link.configure({
396
+ openOnClick: false,
397
+ autolink: linksEnabled,
398
+ linkOnPaste: linksEnabled,
399
+ HTMLAttributes: {
400
+ target: '_blank',
401
+ rel: 'noopener noreferrer'
402
+ }
403
+ }),
404
+ // Image extension is always loaded to render existing images in content
405
+ // The imagesEnabled flag controls whether NEW images can be inserted (via paste/drop handlers)
406
+ ImageWithDataAttachmentId.configure({
407
+ inline: true,
408
+ allowBase64: true
409
+ }),
410
+ // Text formatting extensions
411
+ Underline,
412
+ Subscript,
413
+ Superscript,
414
+ TextStyle,
415
+ Color,
416
+ Highlight.configure({
417
+ multicolor: true
418
+ }),
419
+ FontFamily,
420
+ FontSize,
421
+ // Alignment
422
+ TextAlign.configure({
423
+ types: ['heading', 'paragraph']
424
+ }),
425
+ // Table extensions
426
+ Table.configure({
427
+ resizable: true
428
+ }),
429
+ TableRow,
430
+ TableHeader,
431
+ TableCell,
432
+ TableDeleteOnEmpty,
433
+ TableCellSelection,
434
+ CustomKeyboardShortcuts.configure({
435
+ onFocusTableMenu: () => onFocusTableMenuRef.current?.(),
436
+ onFocusPreviousCellMenu: () => onFocusPreviousCellMenuRef.current?.()
437
+ }),
438
+ PreserveDiv,
439
+ OfficePaste,
440
+ ...customComponentExtensions
441
+ ],
442
+ content: defaultValue,
443
+ editable: !disabled && !readOnly,
444
+ editorProps: {
445
+ attributes: {
446
+ class: 'ProseMirror',
447
+ spellcheck: spellcheck ? 'true' : 'false'
448
+ },
449
+ handleDOMEvents: {
450
+ keydown: (_view, event) => {
451
+ onKeyDown?.(event);
452
+ return false; // Allow other handlers to process
453
+ },
454
+ paste: (_view, event) => {
455
+ // Block pasted image files when images are disabled
456
+ if (!imagesEnabled && event.clipboardData?.files) {
457
+ const hasImageFile = Array.from(event.clipboardData.files).some(file => file.type.startsWith('image/'));
458
+ if (hasImageFile) {
459
+ event.preventDefault();
460
+ return true;
461
+ }
462
+ }
463
+ // Track pasted files for later processing
464
+ if (event.clipboardData?.files &&
465
+ event.clipboardData.files.length > 0 &&
466
+ pastedImagesRef) {
467
+ pastedImagesRef.current = Array.from(event.clipboardData.files);
468
+ }
469
+ return false; // Let handlePaste process
470
+ },
471
+ drop: (_view, event) => {
472
+ // Block dropped image files when images are disabled
473
+ if (!imagesEnabled && event.dataTransfer?.files) {
474
+ const hasImageFile = Array.from(event.dataTransfer.files).some(file => file.type.startsWith('image/'));
475
+ if (hasImageFile) {
476
+ event.preventDefault();
477
+ return true;
478
+ }
479
+ }
480
+ return false;
481
+ }
482
+ },
483
+ // Handle paste behavior based on initOptions
484
+ handlePaste: (view, event) => {
485
+ // If pasteAsText is enabled, convert to plain text
486
+ if (initOptions?.pasteAsText) {
487
+ event.preventDefault();
488
+ const text = event.clipboardData?.getData('text/plain');
489
+ if (text) {
490
+ view.dispatch(view.state.tr.insertText(text));
491
+ }
492
+ return true;
493
+ }
494
+ let html = event.clipboardData?.getData('text/html');
495
+ const text = event.clipboardData?.getData('text/plain');
496
+ const shouldPreferTextPaste = Boolean(text?.trim()) || Boolean(html?.trim());
497
+ // If secure mode is enabled, sanitize pasted HTML content first
498
+ if (secure && html) {
499
+ html = sanitizeHtml(html);
500
+ }
501
+ const isImg = html && html.includes('<img');
502
+ const hasLinks = html && /<a\s/i.test(html);
503
+ // If links are disabled, handle paste to prevent link creation
504
+ if (!linksEnabled) {
505
+ // Check if pasting HTML with <a> tags - strip them
506
+ if (hasLinks && html) {
507
+ event.preventDefault();
508
+ // Replace <a> tags with their text content
509
+ const strippedHtml = html.replace(/<a\s[^>]*>([\s\S]*?)<\/a>/gi, '$1');
510
+ const currentEditor = editorRef.current?.getEditor();
511
+ if (currentEditor) {
512
+ currentEditor.chain().focus().insertContent(strippedHtml).run();
513
+ }
514
+ return true;
515
+ }
516
+ // Check if pasting plain text that looks like a URL - insert as plain text
517
+ const urlPattern = /^(https?:\/\/|www\.)\S+$/i;
518
+ if (text && urlPattern.test(text.trim())) {
519
+ event.preventDefault();
520
+ view.dispatch(view.state.tr.insertText(text));
521
+ return true;
522
+ }
523
+ }
524
+ // If images are disabled or mode is url-only, strip images from pasted content
525
+ if ((!imagesEnabled || imageInsertionMode === 'url') && isImg && html) {
526
+ event.preventDefault();
527
+ const cleanedHtml = html.replace(/<img[^>]*\/?>/g, '');
528
+ const tempDiv = document.createElement('div');
529
+ tempDiv.innerHTML = cleanedHtml;
530
+ view.dispatch(view.state.tr.replaceSelectionWith(view.state.schema.nodes.paragraph.create(null, view.state.schema.text(tempDiv.textContent || ''))));
531
+ return true;
532
+ }
533
+ // If pasteDataImages is disabled, strip images from pasted content
534
+ if (initOptions?.pasteDataImages === false && isImg && html) {
535
+ event.preventDefault();
536
+ const cleanedHtml = html.replace(/<img[^>]*>/g, '');
537
+ const tempDiv = document.createElement('div');
538
+ tempDiv.innerHTML = cleanedHtml;
539
+ view.dispatch(view.state.tr.replaceSelectionWith(view.state.schema.nodes.paragraph.create(null, view.state.schema.text(tempDiv.textContent || ''))));
540
+ return true;
541
+ }
542
+ // Handle raw image file pasting (e.g., screenshot, file from disk)
543
+ // Prioritize raw files over HTML with blob URLs since blob URLs may fail to fetch
544
+ const files = event.clipboardData?.files;
545
+ if (imagesEnabled &&
546
+ imageInsertionMode !== 'url' &&
547
+ !shouldPreferTextPaste &&
548
+ files &&
549
+ files.length > 0) {
550
+ const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/'));
551
+ if (imageFiles.length > 0) {
552
+ event.preventDefault();
553
+ imageFiles.forEach(file => {
554
+ const imgId = createUID();
555
+ // Create pega-file element to show upload progress
556
+ const pegaFileHtml = `<br/><pega-file data-id='${imgId}' data-progress='0' data-name='${file.name}' data-alt='${file.name}' contenteditable='false'></pega-file><br/>`;
557
+ // Insert the pega-file element
558
+ const currentEditor = editorRef.current?.getEditor();
559
+ if (currentEditor) {
560
+ currentEditor.chain().focus().insertContent(pegaFileHtml).run();
561
+ }
562
+ // Trigger the onImageAdded callback so consumer can upload and call appendImage
563
+ onImageAdded?.(file, imgId, file.name);
564
+ });
565
+ return true;
566
+ }
567
+ }
568
+ // Process pasted images from HTML (fallback when no raw files available)
569
+ if (imagesEnabled && isImg && html) {
570
+ event.preventDefault();
571
+ const pastedHtml = new DOMParser().parseFromString(html, 'text/html').body;
572
+ const imgInfo = [];
573
+ pastedHtml.querySelectorAll('img').forEach(imgEl => {
574
+ const imgId = createUID();
575
+ const url = imgEl.src;
576
+ const name = pastedImagesRef?.current.shift()?.name || editorId || 'pasted-image';
577
+ imgInfo.push({ id: imgId, url, name });
578
+ // Create pega-file element to show upload progress
579
+ const uploadEl = document.createElement('pega-file');
580
+ uploadEl.setAttribute('data-id', imgId);
581
+ uploadEl.setAttribute('data-name', name);
582
+ uploadEl.setAttribute('data-url', url);
583
+ uploadEl.setAttribute('data-progress', '0');
584
+ uploadEl.setAttribute('contenteditable', 'false');
585
+ const uploadElString = `<br/>${uploadEl.outerHTML}<br/>`;
586
+ const uploadElHtml = new DOMParser().parseFromString(uploadElString, 'text/html').body;
587
+ imgEl.replaceWith(uploadElHtml);
588
+ });
589
+ // Insert the modified HTML
590
+ const modifiedHtml = pastedHtml.innerHTML;
591
+ view.dispatch(view.state.tr.insertText(''));
592
+ // Use setTimeout to ensure the DOM is ready
593
+ setTimeout(() => {
594
+ const currentEditor = editorRef.current?.getEditor();
595
+ if (currentEditor) {
596
+ currentEditor.chain().focus().insertContent(modifiedHtml).run();
597
+ }
598
+ }, 0);
599
+ // Process the pasted image URLs
600
+ processPastedImgUrls(imgInfo).catch(() => {
601
+ // Errors are handled inside processPastedImgUrls
602
+ });
603
+ return true;
604
+ }
605
+ return false;
606
+ },
607
+ // Handle drag-and-drop of image files from the system
608
+ handleDrop: (view, event, _slice, moved) => {
609
+ // Only handle drops from outside the editor (not internal moves)
610
+ if (moved) {
611
+ return false;
612
+ }
613
+ const files = event.dataTransfer?.files;
614
+ if (!imagesEnabled || imageInsertionMode === 'url' || !files || files.length === 0) {
615
+ return false;
616
+ }
617
+ const imageFiles = Array.from(files).filter(file => file.type.startsWith('image/'));
618
+ if (imageFiles.length === 0) {
619
+ return false;
620
+ }
621
+ event.preventDefault();
622
+ const dropEditor = editorRef.current?.getEditor();
623
+ if (!dropEditor) {
624
+ return false;
625
+ }
626
+ // Set cursor position to where the drop occurred
627
+ const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY });
628
+ if (coordinates) {
629
+ dropEditor.commands.setTextSelection(coordinates.pos);
630
+ }
631
+ imageFiles.forEach(file => {
632
+ const imgId = createUID();
633
+ // Create pega-file element to show upload progress
634
+ const pegaFileHtml = `<br/><pega-file data-id='${imgId}' data-progress='0' data-name='${file.name}' data-alt='${file.name}' contenteditable='false'></pega-file><br/>`;
635
+ // Insert the pega-file element
636
+ dropEditor.chain().focus().insertContent(pegaFileHtml).run();
637
+ // Trigger the onImageAdded callback so consumer can upload and call appendImage
638
+ onImageAdded?.(file, imgId, file.name);
639
+ });
640
+ return true;
641
+ }
642
+ },
643
+ onUpdate: ({ editor: currentEditor }) => {
644
+ // Clear stored link mark if cursor is not inside actual linked text
645
+ // This prevents link styling from persisting after deleting linked text
646
+ const { selection, storedMarks } = currentEditor.state;
647
+ const linkMark = currentEditor.schema.marks.link;
648
+ if (selection.empty && storedMarks?.some(mark => mark.type === linkMark)) {
649
+ const hasLinkInDoc = selection.$from.marks().some(mark => mark.type === linkMark);
650
+ if (!hasLinkInDoc) {
651
+ currentEditor.commands.unsetMark('link');
652
+ // Return early - onChange will fire on the subsequent update with cleaned state
653
+ return;
654
+ }
655
+ }
656
+ onChange?.(currentEditor);
657
+ },
658
+ onFocus: () => {
659
+ onFocus?.();
660
+ },
661
+ onBlur: () => {
662
+ onBlur?.();
663
+ }
664
+ });
665
+ useEffect(() => {
666
+ if (editor && onInit) {
667
+ // Call initInstanceCallback if provided
668
+ if (initOptions?.initInstanceCallback) {
669
+ initOptions.initInstanceCallback(editor);
670
+ }
671
+ onInit(editor);
672
+ }
673
+ }, [editor, onInit, initOptions]);
674
+ useImperativeHandle(editorRef, () => ({
675
+ focus: () => editor?.chain().focus().run(),
676
+ getPlainText: () => editor?.getText() ?? '',
677
+ getRichText: () => (editor ? JSON.stringify(editor.getJSON()) : ''),
678
+ getHtml: () => editor?.getHTML() ?? '',
679
+ clear: () => editor?.chain().clearContent().run(),
680
+ insertText: (text) => editor?.chain().insertContent(text).run(),
681
+ setCursorLocationToStart: () => editor?.chain().focus('start').run(),
682
+ insertHtml: (html, overwrite = false) => {
683
+ if (overwrite) {
684
+ editor?.chain().setContent(html).run();
685
+ }
686
+ else {
687
+ editor?.chain().insertContent(html).run();
688
+ }
689
+ },
690
+ getEditor: () => editor,
691
+ setEditable: (editable) => editor?.setEditable(editable)
692
+ }), [editor]);
693
+ return _jsx(EditorContent, { editor: editor });
694
+ };
695
+ //# sourceMappingURL=IframeTiptapEditor.js.map