@primeui/texteditor-core 0.0.1-alpha.1

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 (166) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +1 -0
  3. package/dist/chunk-22B454VC.mjs +83 -0
  4. package/dist/chunk-22B454VC.mjs.map +1 -0
  5. package/dist/chunk-3RKMABZR.mjs +320 -0
  6. package/dist/chunk-3RKMABZR.mjs.map +1 -0
  7. package/dist/chunk-6GY64GPU.mjs +18 -0
  8. package/dist/chunk-6GY64GPU.mjs.map +1 -0
  9. package/dist/chunk-6YDCG63C.mjs +833 -0
  10. package/dist/chunk-6YDCG63C.mjs.map +1 -0
  11. package/dist/chunk-6ZN5XRH2.mjs +45 -0
  12. package/dist/chunk-6ZN5XRH2.mjs.map +1 -0
  13. package/dist/chunk-72T5I4O4.mjs +76 -0
  14. package/dist/chunk-72T5I4O4.mjs.map +1 -0
  15. package/dist/chunk-7QCHSEN2.mjs +16 -0
  16. package/dist/chunk-7QCHSEN2.mjs.map +1 -0
  17. package/dist/chunk-AJOKUIQD.mjs +33 -0
  18. package/dist/chunk-AJOKUIQD.mjs.map +1 -0
  19. package/dist/chunk-AUTGNC2Q.mjs +197 -0
  20. package/dist/chunk-AUTGNC2Q.mjs.map +1 -0
  21. package/dist/chunk-CRAB2MFE.mjs +19 -0
  22. package/dist/chunk-CRAB2MFE.mjs.map +1 -0
  23. package/dist/chunk-DXPG5XMX.mjs +18 -0
  24. package/dist/chunk-DXPG5XMX.mjs.map +1 -0
  25. package/dist/chunk-E2KXUP3F.mjs +9 -0
  26. package/dist/chunk-E2KXUP3F.mjs.map +1 -0
  27. package/dist/chunk-EA2YI7LA.mjs +23 -0
  28. package/dist/chunk-EA2YI7LA.mjs.map +1 -0
  29. package/dist/chunk-F7BJWPT6.mjs +401 -0
  30. package/dist/chunk-F7BJWPT6.mjs.map +1 -0
  31. package/dist/chunk-FFCQLWS5.mjs +32 -0
  32. package/dist/chunk-FFCQLWS5.mjs.map +1 -0
  33. package/dist/chunk-GG56GLXC.mjs +42 -0
  34. package/dist/chunk-GG56GLXC.mjs.map +1 -0
  35. package/dist/chunk-GGPBV4BM.mjs +37 -0
  36. package/dist/chunk-GGPBV4BM.mjs.map +1 -0
  37. package/dist/chunk-H3RFOYCT.mjs +27 -0
  38. package/dist/chunk-H3RFOYCT.mjs.map +1 -0
  39. package/dist/chunk-IUQKSDKG.mjs +23 -0
  40. package/dist/chunk-IUQKSDKG.mjs.map +1 -0
  41. package/dist/chunk-J5LGTIGS.mjs +9 -0
  42. package/dist/chunk-J5LGTIGS.mjs.map +1 -0
  43. package/dist/chunk-KNZAMA6H.mjs +198 -0
  44. package/dist/chunk-KNZAMA6H.mjs.map +1 -0
  45. package/dist/chunk-MNKMKFLQ.mjs +27 -0
  46. package/dist/chunk-MNKMKFLQ.mjs.map +1 -0
  47. package/dist/chunk-MWT5PK3Z.mjs +52 -0
  48. package/dist/chunk-MWT5PK3Z.mjs.map +1 -0
  49. package/dist/chunk-NWRVBNBW.mjs +65 -0
  50. package/dist/chunk-NWRVBNBW.mjs.map +1 -0
  51. package/dist/chunk-OGEMGWLR.mjs +180 -0
  52. package/dist/chunk-OGEMGWLR.mjs.map +1 -0
  53. package/dist/chunk-PRFYR67X.mjs +60 -0
  54. package/dist/chunk-PRFYR67X.mjs.map +1 -0
  55. package/dist/chunk-PT2O4RV7.mjs +143 -0
  56. package/dist/chunk-PT2O4RV7.mjs.map +1 -0
  57. package/dist/chunk-PT5V6UYE.mjs +10 -0
  58. package/dist/chunk-PT5V6UYE.mjs.map +1 -0
  59. package/dist/chunk-RH45QRSV.mjs +26 -0
  60. package/dist/chunk-RH45QRSV.mjs.map +1 -0
  61. package/dist/chunk-RQ2HHNI6.mjs +55 -0
  62. package/dist/chunk-RQ2HHNI6.mjs.map +1 -0
  63. package/dist/chunk-TB7ZSSR2.mjs +6 -0
  64. package/dist/chunk-TB7ZSSR2.mjs.map +1 -0
  65. package/dist/chunk-TBQVZVKA.mjs +1891 -0
  66. package/dist/chunk-TBQVZVKA.mjs.map +1 -0
  67. package/dist/chunk-VWGQMUPD.mjs +182 -0
  68. package/dist/chunk-VWGQMUPD.mjs.map +1 -0
  69. package/dist/chunk-XKY2VYPW.mjs +26 -0
  70. package/dist/chunk-XKY2VYPW.mjs.map +1 -0
  71. package/dist/chunk-XSVIGDQR.mjs +22 -0
  72. package/dist/chunk-XSVIGDQR.mjs.map +1 -0
  73. package/dist/chunk-YHMP6AN3.mjs +231 -0
  74. package/dist/chunk-YHMP6AN3.mjs.map +1 -0
  75. package/dist/commands/index.d.mts +13 -0
  76. package/dist/commands/index.mjs +13 -0
  77. package/dist/commands/index.mjs.map +1 -0
  78. package/dist/createTextEditor.d.mts +7 -0
  79. package/dist/createTextEditor.mjs +31 -0
  80. package/dist/createTextEditor.mjs.map +1 -0
  81. package/dist/index.d.mts +17 -0
  82. package/dist/index.mjs +45 -0
  83. package/dist/index.mjs.map +1 -0
  84. package/dist/plugins/columnWidthPin.d.mts +5 -0
  85. package/dist/plugins/columnWidthPin.mjs +4 -0
  86. package/dist/plugins/columnWidthPin.mjs.map +1 -0
  87. package/dist/plugins/contextToolbar.d.mts +9 -0
  88. package/dist/plugins/contextToolbar.mjs +5 -0
  89. package/dist/plugins/contextToolbar.mjs.map +1 -0
  90. package/dist/plugins/defineTextEditorPlugin.d.mts +20 -0
  91. package/dist/plugins/defineTextEditorPlugin.mjs +4 -0
  92. package/dist/plugins/defineTextEditorPlugin.mjs.map +1 -0
  93. package/dist/plugins/inputRules.d.mts +14 -0
  94. package/dist/plugins/inputRules.mjs +8 -0
  95. package/dist/plugins/inputRules.mjs.map +1 -0
  96. package/dist/plugins/keymap.d.mts +16 -0
  97. package/dist/plugins/keymap.mjs +6 -0
  98. package/dist/plugins/keymap.mjs.map +1 -0
  99. package/dist/plugins/mention.d.mts +24 -0
  100. package/dist/plugins/mention.mjs +5 -0
  101. package/dist/plugins/mention.mjs.map +1 -0
  102. package/dist/plugins/mentionController.d.mts +16 -0
  103. package/dist/plugins/mentionController.mjs +4 -0
  104. package/dist/plugins/mentionController.mjs.map +1 -0
  105. package/dist/plugins/placeholder.d.mts +9 -0
  106. package/dist/plugins/placeholder.mjs +5 -0
  107. package/dist/plugins/placeholder.mjs.map +1 -0
  108. package/dist/plugins/registry.d.mts +7 -0
  109. package/dist/plugins/registry.mjs +4 -0
  110. package/dist/plugins/registry.mjs.map +1 -0
  111. package/dist/plugins/selectionHighlight.d.mts +14 -0
  112. package/dist/plugins/selectionHighlight.mjs +5 -0
  113. package/dist/plugins/selectionHighlight.mjs.map +1 -0
  114. package/dist/plugins/slashCommands.d.mts +28 -0
  115. package/dist/plugins/slashCommands.mjs +5 -0
  116. package/dist/plugins/slashCommands.mjs.map +1 -0
  117. package/dist/plugins/tableOverlay.d.mts +15 -0
  118. package/dist/plugins/tableOverlay.mjs +6 -0
  119. package/dist/plugins/tableOverlay.mjs.map +1 -0
  120. package/dist/plugins/trailingNode.d.mts +6 -0
  121. package/dist/plugins/trailingNode.mjs +4 -0
  122. package/dist/plugins/trailingNode.mjs.map +1 -0
  123. package/dist/position-DbkbTrFL.d.mts +50 -0
  124. package/dist/schema/index.d.mts +89 -0
  125. package/dist/schema/index.mjs +8 -0
  126. package/dist/schema/index.mjs.map +1 -0
  127. package/dist/serialization/index.d.mts +8 -0
  128. package/dist/serialization/index.mjs +5 -0
  129. package/dist/serialization/index.mjs.map +1 -0
  130. package/dist/texteditor.classes.d.mts +34 -0
  131. package/dist/texteditor.classes.mjs +4 -0
  132. package/dist/texteditor.classes.mjs.map +1 -0
  133. package/dist/texteditor.constants.d.mts +36 -0
  134. package/dist/texteditor.constants.mjs +4 -0
  135. package/dist/texteditor.constants.mjs.map +1 -0
  136. package/dist/texteditor.props.d.mts +5 -0
  137. package/dist/texteditor.props.mjs +4 -0
  138. package/dist/texteditor.props.mjs.map +1 -0
  139. package/dist/texteditor.refs.d.mts +5 -0
  140. package/dist/texteditor.refs.mjs +4 -0
  141. package/dist/texteditor.refs.mjs.map +1 -0
  142. package/dist/texteditor.state.d.mts +5 -0
  143. package/dist/texteditor.state.mjs +4 -0
  144. package/dist/texteditor.state.mjs.map +1 -0
  145. package/dist/ui/focus.d.mts +5 -0
  146. package/dist/ui/focus.mjs +4 -0
  147. package/dist/ui/focus.mjs.map +1 -0
  148. package/dist/ui/index.d.mts +6 -0
  149. package/dist/ui/index.mjs +9 -0
  150. package/dist/ui/index.mjs.map +1 -0
  151. package/dist/ui/keyboard.d.mts +4 -0
  152. package/dist/ui/keyboard.mjs +4 -0
  153. package/dist/ui/keyboard.mjs.map +1 -0
  154. package/dist/ui/menu.d.mts +10 -0
  155. package/dist/ui/menu.mjs +4 -0
  156. package/dist/ui/menu.mjs.map +1 -0
  157. package/dist/ui/positioning.d.mts +30 -0
  158. package/dist/ui/positioning.mjs +5 -0
  159. package/dist/ui/positioning.mjs.map +1 -0
  160. package/dist/ui/scroll.d.mts +6 -0
  161. package/dist/ui/scroll.mjs +4 -0
  162. package/dist/ui/scroll.mjs.map +1 -0
  163. package/dist/utils/index.d.mts +18 -0
  164. package/dist/utils/index.mjs +18 -0
  165. package/dist/utils/index.mjs.map +1 -0
  166. package/package.json +73 -0
@@ -0,0 +1,1891 @@
1
+ import { createMentionPlugin, insertMention } from './chunk-PT2O4RV7.mjs';
2
+ import { createEditorKeymap, keymap, baseKeymap } from './chunk-YHMP6AN3.mjs';
3
+ import { createMarkdownInputRules } from './chunk-OGEMGWLR.mjs';
4
+ import { createContextToolbarPlugin } from './chunk-IUQKSDKG.mjs';
5
+ import { createColumnWidthPinPlugin } from './chunk-MWT5PK3Z.mjs';
6
+ import { createTrailingNodePlugin } from './chunk-EA2YI7LA.mjs';
7
+ import { createTableOverlayPlugin } from './chunk-3RKMABZR.mjs';
8
+ import { createSlashPlugin, executeSlashCommand } from './chunk-KNZAMA6H.mjs';
9
+ import { addProseMirrorPlugin, removeProseMirrorPlugin } from './chunk-6GY64GPU.mjs';
10
+ import { createPlaceholderPlugin } from './chunk-NWRVBNBW.mjs';
11
+ import { createMentionController } from './chunk-GGPBV4BM.mjs';
12
+ import { defaultTextEditorProps } from './chunk-AJOKUIQD.mjs';
13
+ import { defaultTextEditorRefs } from './chunk-E2KXUP3F.mjs';
14
+ import { defaultTextEditorState } from './chunk-PT5V6UYE.mjs';
15
+ import { htmlExceedsMaxDepth, htmlToSlice, htmlToDoc, htmlToInertFragment } from './chunk-PRFYR67X.mjs';
16
+ import { createEditorSchema } from './chunk-AUTGNC2Q.mjs';
17
+ import { selectRow, selectColumn, createCommands, createTableCellCommands, createTableRowCommands, createTableColumnCommands } from './chunk-6YDCG63C.mjs';
18
+ import { getSelectionPosition, getMarkAttrs, isNodeActive, isMarkActive } from './chunk-72T5I4O4.mjs';
19
+ import { createSelectionHighlightPlugin, preserveSelection } from './chunk-RQ2HHNI6.mjs';
20
+ import { docToHtml, withoutPlaceholders, nodeToHtml } from './chunk-6ZN5XRH2.mjs';
21
+ import { sanitizeImageUrl, sanitizeUrl } from './chunk-MNKMKFLQ.mjs';
22
+ import { textEditorClasses } from './chunk-GG56GLXC.mjs';
23
+ import { defineComponent } from '@primeui/core';
24
+ import { dropCursor } from 'prosemirror-dropcursor';
25
+ import { gapCursor } from 'prosemirror-gapcursor';
26
+ import { history, redo, undo } from 'prosemirror-history';
27
+ import { TextSelection, EditorState } from 'prosemirror-state';
28
+ import { columnResizing, tableEditing, addRowAfter, addColumnAfter, CellSelection } from 'prosemirror-tables';
29
+ import { EditorView } from 'prosemirror-view';
30
+ import { DOMSerializer, Fragment, DOMParser } from 'prosemirror-model';
31
+
32
+ function getBlockIndex(view, pos) {
33
+ let index = 0;
34
+ view.state.doc.forEach((_child, childOffset) => {
35
+ if (childOffset < pos) {
36
+ index++;
37
+ }
38
+ });
39
+ return index;
40
+ }
41
+ function renderFromSchema(node) {
42
+ const spec = node.type.spec.toDOM?.(node);
43
+ if (!spec) return { dom: document.createElement("div"), contentDOM: null };
44
+ const { dom, contentDOM } = DOMSerializer.renderSpec(document, spec);
45
+ return { dom, contentDOM: contentDOM ?? null };
46
+ }
47
+ function createBlockNodeView(node, view, getPos, onHoverChange) {
48
+ const pos = getPos();
49
+ const isTopLevel = pos != null && view.state.doc.resolve(pos).depth === 0;
50
+ const attrsRenderEqual = (a, b) => JSON.stringify(a.attrs) === JSON.stringify(b.attrs);
51
+ if (!isTopLevel) {
52
+ const { dom: dom2, contentDOM: contentDOM2 } = renderFromSchema(node);
53
+ return {
54
+ dom: dom2,
55
+ contentDOM: contentDOM2,
56
+ update(updatedNode) {
57
+ if (updatedNode.type !== node.type || !attrsRenderEqual(node, updatedNode)) return false;
58
+ node = updatedNode;
59
+ return true;
60
+ }
61
+ };
62
+ }
63
+ const dom = document.createElement("div");
64
+ dom.className = textEditorClasses.block;
65
+ const contentWrapper = document.createElement("div");
66
+ contentWrapper.className = textEditorClasses.blockContent;
67
+ dom.appendChild(contentWrapper);
68
+ const { dom: contentEl, contentDOM } = renderFromSchema(node);
69
+ contentWrapper.appendChild(contentEl);
70
+ const onMouseEnter = () => {
71
+ const hoverPos = getPos();
72
+ if (hoverPos != null) {
73
+ onHoverChange(getBlockIndex(view, hoverPos), dom);
74
+ }
75
+ };
76
+ const onMouseLeave = (event) => {
77
+ const related = event.relatedTarget;
78
+ if (related?.closest(`.${textEditorClasses.blockControls}`)) return;
79
+ if (related?.closest(`.${textEditorClasses.block}`)) return;
80
+ if (related?.closest(`.${textEditorClasses.popoverMenu}`)) return;
81
+ onHoverChange(-1, null);
82
+ };
83
+ dom.addEventListener("mouseenter", onMouseEnter);
84
+ dom.addEventListener("mouseleave", onMouseLeave);
85
+ const removeHoverListeners = () => {
86
+ dom.removeEventListener("mouseenter", onMouseEnter);
87
+ dom.removeEventListener("mouseleave", onMouseLeave);
88
+ };
89
+ if (node.type.spec.atom || !contentDOM) {
90
+ return {
91
+ dom,
92
+ update(updatedNode) {
93
+ if (updatedNode.type !== node.type || !attrsRenderEqual(node, updatedNode)) return false;
94
+ node = updatedNode;
95
+ return true;
96
+ },
97
+ destroy() {
98
+ removeHoverListeners();
99
+ }
100
+ };
101
+ }
102
+ return {
103
+ dom,
104
+ contentDOM,
105
+ update(updatedNode) {
106
+ if (updatedNode.type !== node.type || !attrsRenderEqual(node, updatedNode)) return false;
107
+ node = updatedNode;
108
+ return true;
109
+ },
110
+ destroy() {
111
+ removeHoverListeners();
112
+ }
113
+ };
114
+ }
115
+
116
+ // src/utils/blockPosition.ts
117
+ function blockStartPos(doc, index) {
118
+ if (index < 0 || index >= doc.childCount) return -1;
119
+ let pos = 0;
120
+ for (let i = 0; i < index; i++) pos += doc.child(i).nodeSize;
121
+ return pos;
122
+ }
123
+ function blockEndPos(doc, index) {
124
+ const start = blockStartPos(doc, index);
125
+ return start < 0 ? -1 : start + doc.child(index).nodeSize;
126
+ }
127
+ function posAfterBlock(doc, index) {
128
+ if (index >= doc.childCount - 1) return doc.content.size;
129
+ return blockEndPos(doc, index);
130
+ }
131
+ function posToBlockIndex(doc, pos) {
132
+ let offset = 0;
133
+ for (let i = 0; i < doc.childCount; i++) {
134
+ const size = doc.child(i).nodeSize;
135
+ if (pos < offset + size) return i;
136
+ offset += size;
137
+ }
138
+ return doc.childCount - 1;
139
+ }
140
+
141
+ // src/nodeviews/tableNodeView.ts
142
+ function updateColumns(node, colgroup, table, defaultCellMinWidth) {
143
+ let totalWidth = 0;
144
+ let fixedWidth = true;
145
+ let nextDOM = colgroup.firstChild;
146
+ const row = node.firstChild;
147
+ if (!row) return;
148
+ for (let i = 0, col = 0; i < row.childCount; i++) {
149
+ const { colspan, colwidth } = row.child(i).attrs;
150
+ for (let j = 0; j < colspan; j++, col++) {
151
+ const hasWidth = colwidth && colwidth[j];
152
+ const cssWidth = hasWidth ? `${hasWidth}px` : "";
153
+ totalWidth += hasWidth || defaultCellMinWidth;
154
+ if (!hasWidth) fixedWidth = false;
155
+ if (!nextDOM) {
156
+ colgroup.appendChild(document.createElement("col")).style.width = cssWidth;
157
+ } else {
158
+ if (nextDOM.style.width !== cssWidth) nextDOM.style.width = cssWidth;
159
+ nextDOM = nextDOM.nextSibling;
160
+ }
161
+ }
162
+ }
163
+ while (nextDOM) {
164
+ const after = nextDOM.nextSibling;
165
+ nextDOM.remove();
166
+ nextDOM = after;
167
+ }
168
+ if (fixedWidth) {
169
+ table.style.width = `${totalWidth}px`;
170
+ table.style.minWidth = "";
171
+ } else {
172
+ table.style.width = "";
173
+ table.style.minWidth = `${totalWidth}px`;
174
+ }
175
+ }
176
+ function createTableNodeView(node, _view, _getPos, options) {
177
+ const dom = document.createElement("div");
178
+ dom.className = "tableWrapper";
179
+ const table = dom.appendChild(document.createElement("table"));
180
+ table.style.setProperty("--default-cell-min-width", `${options.defaultCellWidth}px`);
181
+ const colgroup = table.appendChild(document.createElement("colgroup"));
182
+ updateColumns(node, colgroup, table, options.defaultCellWidth);
183
+ const contentDOM = table.appendChild(document.createElement("tbody"));
184
+ return {
185
+ dom,
186
+ contentDOM,
187
+ update(updatedNode) {
188
+ if (updatedNode.type !== node.type) return false;
189
+ node = updatedNode;
190
+ updateColumns(node, colgroup, table, options.defaultCellWidth);
191
+ return true;
192
+ },
193
+ ignoreMutation(mutation) {
194
+ const target = mutation.target;
195
+ if (mutation.type === "attributes" && (target === table || colgroup.contains(target))) return true;
196
+ if (target === dom && mutation.type === "childList") {
197
+ const isOurElement = (el) => el.classList?.contains(textEditorClasses.tableSelectionOutline) || el.classList?.contains(textEditorClasses.tableTrigger) || el.classList?.contains(textEditorClasses.tableAdd);
198
+ for (let i = 0; i < mutation.addedNodes.length; i++) {
199
+ if (isOurElement(mutation.addedNodes[i])) return true;
200
+ }
201
+ for (let i = 0; i < mutation.removedNodes.length; i++) {
202
+ if (isOurElement(mutation.removedNodes[i])) return true;
203
+ }
204
+ }
205
+ if (target.closest?.(`.${textEditorClasses.tableSelectionOutline}, .${textEditorClasses.tableTrigger}, .${textEditorClasses.tableAdd}`)) return true;
206
+ return false;
207
+ }
208
+ };
209
+ }
210
+
211
+ // src/nodeviews/blockTableNodeView.ts
212
+ function createBlockTableNodeView(node, view, getPos, options) {
213
+ const tableNV = createTableNodeView(node, view, getPos, { defaultCellWidth: options.defaultCellWidth });
214
+ const dom = document.createElement("div");
215
+ dom.className = textEditorClasses.block;
216
+ const contentWrapper = document.createElement("div");
217
+ contentWrapper.className = textEditorClasses.blockContent;
218
+ contentWrapper.appendChild(tableNV.dom);
219
+ dom.appendChild(contentWrapper);
220
+ dom.addEventListener("mouseenter", () => {
221
+ const pos = getPos();
222
+ if (pos != null) options.onBlockHoverChange(posToBlockIndex(view.state.doc, pos), dom);
223
+ });
224
+ dom.addEventListener("mouseleave", (event) => {
225
+ const related = event.relatedTarget;
226
+ if (related?.closest(`.${textEditorClasses.blockControls}`)) return;
227
+ if (related?.closest(`.${textEditorClasses.block}`)) return;
228
+ if (related?.closest(`.${textEditorClasses.popoverMenu}`)) return;
229
+ options.onBlockHoverChange(-1, null);
230
+ });
231
+ return {
232
+ dom,
233
+ contentDOM: tableNV.contentDOM,
234
+ update: tableNV.update?.bind(tableNV),
235
+ ignoreMutation: tableNV.ignoreMutation?.bind(tableNV)
236
+ };
237
+ }
238
+
239
+ // src/nodeviews/checkList.ts
240
+ function checkListItemNodeView(node, view, getPos) {
241
+ const li = document.createElement("li");
242
+ li.setAttribute("data-p-checked", node.attrs.checked ? "true" : "false");
243
+ const label = document.createElement("label");
244
+ label.contentEditable = "false";
245
+ const checkbox = document.createElement("input");
246
+ checkbox.type = "checkbox";
247
+ checkbox.checked = node.attrs.checked;
248
+ checkbox.setAttribute("aria-label", node.textContent || "To-do item");
249
+ checkbox.addEventListener("change", () => {
250
+ const pos = getPos();
251
+ if (pos == null) return;
252
+ const tr = view.state.tr.setNodeMarkup(pos, null, {
253
+ ...node.attrs,
254
+ checked: checkbox.checked
255
+ });
256
+ view.dispatch(tr);
257
+ });
258
+ const span = document.createElement("span");
259
+ label.appendChild(checkbox);
260
+ label.appendChild(span);
261
+ li.appendChild(label);
262
+ const contentDOM = document.createElement("div");
263
+ li.appendChild(contentDOM);
264
+ return {
265
+ dom: li,
266
+ contentDOM,
267
+ update(updatedNode) {
268
+ if (updatedNode.type.name !== "check_list_item") return false;
269
+ li.setAttribute("data-p-checked", updatedNode.attrs.checked ? "true" : "false");
270
+ checkbox.checked = updatedNode.attrs.checked;
271
+ checkbox.setAttribute("aria-label", updatedNode.textContent || "To-do item");
272
+ return true;
273
+ }
274
+ };
275
+ }
276
+
277
+ // src/upload/uploadManager.ts
278
+ function formatFileSize(bytes) {
279
+ if (bytes < 1024) return bytes + " B";
280
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
281
+ return (bytes / (1024 * 1024)).toFixed(1) + " MB";
282
+ }
283
+ var UploadManager = class {
284
+ constructor(callbacks) {
285
+ this.entries = [];
286
+ this.results = /* @__PURE__ */ new Map();
287
+ this.nextIndex = 0;
288
+ this.onStateChange = callbacks.onStateChange;
289
+ this.onAllComplete = callbacks.onAllComplete;
290
+ this.onAllDismissed = callbacks.onAllDismissed;
291
+ this.onError = callbacks.onError;
292
+ }
293
+ start(files, uploadHandler, mapResult) {
294
+ for (const file of files) {
295
+ const id = Math.random().toString(36).slice(2, 10);
296
+ const index = this.nextIndex++;
297
+ const controller = new AbortController();
298
+ const entry = {
299
+ id,
300
+ file,
301
+ progress: 0,
302
+ status: "uploading",
303
+ cancelled: false,
304
+ controller,
305
+ cancel: () => {
306
+ entry.cancelled = true;
307
+ entry.status = "cancelled";
308
+ controller.abort();
309
+ this.emitState();
310
+ this.checkAllComplete();
311
+ }
312
+ };
313
+ this.entries.push(entry);
314
+ this.emitState();
315
+ uploadHandler(file, {
316
+ signal: controller.signal,
317
+ onProgress: (percent) => {
318
+ if (!entry.cancelled) {
319
+ entry.progress = percent;
320
+ this.emitState();
321
+ }
322
+ }
323
+ }).then((url) => {
324
+ if (!entry.cancelled) {
325
+ entry.status = "complete";
326
+ entry.progress = 100;
327
+ this.results.set(index, mapResult(url, file));
328
+ this.emitState();
329
+ }
330
+ }).catch((error) => {
331
+ if (!entry.cancelled) {
332
+ entry.status = "error";
333
+ this.emitState();
334
+ this.onError?.(file, error);
335
+ }
336
+ }).finally(() => this.checkAllComplete());
337
+ }
338
+ }
339
+ dismiss() {
340
+ this.entries.forEach((e) => {
341
+ if (e.status === "uploading") {
342
+ e.cancelled = true;
343
+ e.status = "cancelled";
344
+ e.controller.abort();
345
+ }
346
+ });
347
+ this.reset();
348
+ this.onAllDismissed();
349
+ }
350
+ getEntries() {
351
+ return this.entries.map((e) => ({
352
+ id: e.id,
353
+ fileName: e.file.name,
354
+ fileSize: e.file.size,
355
+ fileSizeFormatted: formatFileSize(e.file.size),
356
+ progress: e.progress,
357
+ status: e.status,
358
+ cancel: e.cancel
359
+ }));
360
+ }
361
+ checkAllComplete() {
362
+ if (this.entries.length === 0) return;
363
+ if (this.entries.every((e) => e.status !== "uploading")) {
364
+ const ordered = [...this.results.keys()].sort((a, b) => a - b).map((k) => this.results.get(k));
365
+ if (ordered.length > 0) {
366
+ this.onAllComplete(ordered);
367
+ } else {
368
+ this.onAllDismissed();
369
+ }
370
+ this.reset();
371
+ }
372
+ }
373
+ reset() {
374
+ this.entries = [];
375
+ this.results = /* @__PURE__ */ new Map();
376
+ this.nextIndex = 0;
377
+ this.emitState();
378
+ }
379
+ emitState() {
380
+ this.onStateChange(this.getEntries());
381
+ }
382
+ };
383
+
384
+ // src/upload/accept.ts
385
+ function matchesAccept(file, accept) {
386
+ if (!accept) return true;
387
+ const tokens = accept.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
388
+ if (tokens.length === 0) return true;
389
+ const fileType = (file.type || "").toLowerCase();
390
+ const fileName = (file.name || "").toLowerCase();
391
+ return tokens.some((token) => {
392
+ if (token.startsWith(".")) return fileName.endsWith(token);
393
+ if (token.endsWith("/*")) return fileType.startsWith(token.slice(0, token.indexOf("/") + 1));
394
+ return fileType === token;
395
+ });
396
+ }
397
+
398
+ // src/upload/validate.ts
399
+ function validateUploadFiles(files, constraints, onReject) {
400
+ const { maxCount, maxSize, accept } = constraints;
401
+ if (files.length > maxCount) {
402
+ onReject(files, "max-file-count");
403
+ return [];
404
+ }
405
+ const valid = [];
406
+ for (const file of files) {
407
+ if (!matchesAccept(file, accept)) {
408
+ onReject([file], "file-type");
409
+ } else if (file.size > maxSize) {
410
+ onReject([file], "max-file-size");
411
+ } else {
412
+ valid.push(file);
413
+ }
414
+ }
415
+ return valid;
416
+ }
417
+
418
+ // src/logic/uploads.ts
419
+ function createUploadModule(ctx) {
420
+ const { props, emit, schema, findPlaceholderPos, removePlaceholder } = ctx;
421
+ const imageConstraints = () => ({
422
+ maxCount: props.imageMaxFileCount ?? 3,
423
+ maxSize: props.imageMaxFileSize ?? 1024 * 1024,
424
+ accept: props.allowedImageTypes ?? "image/*"
425
+ });
426
+ const documentConstraints = () => ({
427
+ maxCount: props.documentMaxFileCount ?? 3,
428
+ maxSize: props.documentMaxFileSize ?? 10 * 1024 * 1024,
429
+ accept: props.allowedDocumentTypes
430
+ });
431
+ const rejectImage = (files, reason) => emit("image-reject", { files, reason });
432
+ const rejectDocument = (files, reason) => emit("document-reject", { files, reason });
433
+ const imageManager = new UploadManager({
434
+ onStateChange: (entries) => emit("image-upload-state-change", entries),
435
+ onError: (file, error) => emit("image-upload-error", { file, error }),
436
+ onAllComplete: (results) => {
437
+ const view = ctx.getView();
438
+ if (!view || results.length === 0) return;
439
+ const pos = findPlaceholderPos(schema.nodes.image_upload_placeholder);
440
+ if (pos >= 0) {
441
+ const imageNodes = results.map(({ src, alt }) => ({ src: sanitizeImageUrl(src), alt })).filter((r) => r.src != null).map(({ src, alt }) => schema.nodes.image.create({ src, alt: alt || null }));
442
+ view.dispatch(view.state.tr.replaceWith(pos, pos + 1, schema.nodes.paragraph.create(null, imageNodes)));
443
+ }
444
+ emit("image-upload-complete");
445
+ },
446
+ onAllDismissed: () => {
447
+ removePlaceholder(schema.nodes.image_upload_placeholder);
448
+ emit("image-upload-complete");
449
+ }
450
+ });
451
+ const documentManager = new UploadManager({
452
+ onStateChange: (entries) => emit("document-upload-state-change", entries),
453
+ onError: (file, error) => emit("document-upload-error", { file, error }),
454
+ onAllComplete: (results) => {
455
+ const view = ctx.getView();
456
+ if (!view || results.length === 0) return;
457
+ const pos = findPlaceholderPos(schema.nodes.document_upload_placeholder);
458
+ if (pos >= 0) {
459
+ const nodes = results.map(({ url, fileName }) => {
460
+ const href = sanitizeUrl(url);
461
+ const marks = href ? [schema.marks.link.create({ href })] : [];
462
+ return schema.nodes.paragraph.create(null, schema.text(fileName, marks));
463
+ });
464
+ view.dispatch(view.state.tr.replaceWith(pos, pos + 1, nodes));
465
+ }
466
+ emit("document-upload-complete");
467
+ },
468
+ onAllDismissed: () => {
469
+ removePlaceholder(schema.nodes.document_upload_placeholder);
470
+ emit("document-upload-complete");
471
+ }
472
+ });
473
+ const imageHandler = () => props.imageUploadHandler ?? props.uploadHandler;
474
+ const documentHandler = () => props.documentUploadHandler ?? props.uploadHandler;
475
+ return {
476
+ startImageUploads: (files) => {
477
+ const handler = imageHandler();
478
+ if (!handler) return;
479
+ const valid = validateUploadFiles(files, imageConstraints(), rejectImage);
480
+ if (valid.length > 0) imageManager.start(valid, handler, (url, file) => ({ src: url, alt: file.name }));
481
+ else removePlaceholder(schema.nodes.image_upload_placeholder);
482
+ },
483
+ dismissImageUpload: () => imageManager.dismiss(),
484
+ validateImageFiles: (files) => validateUploadFiles(files, imageConstraints(), rejectImage),
485
+ startDocumentUploads: (files) => {
486
+ const handler = documentHandler();
487
+ if (!handler) return;
488
+ const valid = validateUploadFiles(files, documentConstraints(), rejectDocument);
489
+ if (valid.length > 0) documentManager.start(valid, handler, (url, file) => ({ url, fileName: file.name }));
490
+ else removePlaceholder(schema.nodes.document_upload_placeholder);
491
+ },
492
+ dismissDocumentUpload: () => documentManager.dismiss(),
493
+ validateDocumentFiles: (files) => validateUploadFiles(files, documentConstraints(), rejectDocument)
494
+ };
495
+ }
496
+
497
+ // src/utils/debounce.ts
498
+ function createDebouncer(wait) {
499
+ let timer = null;
500
+ let pending = null;
501
+ const cancel = () => {
502
+ if (timer != null) clearTimeout(timer);
503
+ timer = null;
504
+ pending = null;
505
+ };
506
+ const flush = () => {
507
+ if (timer != null) clearTimeout(timer);
508
+ timer = null;
509
+ const fn = pending;
510
+ pending = null;
511
+ fn?.();
512
+ };
513
+ const schedule = (fn) => {
514
+ if (wait <= 0) {
515
+ fn();
516
+ return;
517
+ }
518
+ pending = fn;
519
+ if (timer != null) clearTimeout(timer);
520
+ timer = setTimeout(() => {
521
+ timer = null;
522
+ const run = pending;
523
+ pending = null;
524
+ run?.();
525
+ }, wait);
526
+ };
527
+ return { schedule, flush, cancel };
528
+ }
529
+
530
+ // src/logic/navigator.ts
531
+ function createNavigatorModule(ctx) {
532
+ const { getView, getElement, emit, schema, props } = ctx;
533
+ const getDocumentHeadings = () => {
534
+ const view = getView();
535
+ if (!view) return [];
536
+ const headings = [];
537
+ view.state.doc.descendants((node, pos) => {
538
+ if (node.type === schema.nodes.heading) {
539
+ headings.push({ pos, level: node.attrs.level, text: node.textContent });
540
+ return false;
541
+ }
542
+ return void 0;
543
+ });
544
+ return headings;
545
+ };
546
+ const debouncer = createDebouncer(props.valueChangeDebounce ?? 0);
547
+ const scheduleHeadings = () => debouncer.schedule(() => emit("navigator-headings-change", getDocumentHeadings()));
548
+ const activeIndexFromSelection = (headings) => {
549
+ const view = getView();
550
+ if (!view || headings.length === 0) return null;
551
+ const caret = view.state.selection.from;
552
+ let idx = -1;
553
+ for (let i = 0; i < headings.length; i++) {
554
+ if (headings[i].pos <= caret) idx = i;
555
+ else break;
556
+ }
557
+ return idx >= 0 ? idx : null;
558
+ };
559
+ const computeActiveIndex = (headings) => {
560
+ const el = getElement();
561
+ if (!el || headings.length === 0) return null;
562
+ const canScroll = el.scrollHeight - el.clientHeight > 1;
563
+ if (!canScroll) return activeIndexFromSelection(headings);
564
+ const editorTop = el.getBoundingClientRect().top;
565
+ const headingEls = el.querySelectorAll("h1, h2, h3, h4, h5, h6");
566
+ let activeIdx = 0;
567
+ headingEls.forEach((node, i) => {
568
+ if (i >= headings.length) return;
569
+ if (node.getBoundingClientRect().top - editorTop <= 50) activeIdx = i;
570
+ });
571
+ return activeIdx;
572
+ };
573
+ let rafId = 0;
574
+ return {
575
+ getDocumentHeadings,
576
+ scheduleHeadings,
577
+ cancel: () => {
578
+ debouncer.cancel();
579
+ cancelAnimationFrame(rafId);
580
+ },
581
+ focusHeading: (pos) => {
582
+ const view = getView();
583
+ if (!view) return;
584
+ view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, pos + 1)));
585
+ view.focus();
586
+ },
587
+ scrollToHeading: (pos, headings) => {
588
+ const view = getView();
589
+ if (!view) return;
590
+ view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, pos + 1)));
591
+ const el = getElement();
592
+ const headingEls = el?.querySelectorAll("h1, h2, h3, h4, h5, h6");
593
+ const idx = headings.findIndex((h) => h.pos === pos);
594
+ const target = headingEls && idx >= 0 ? headingEls[idx] : void 0;
595
+ if (el && target) {
596
+ const raw = el.scrollTop + (target.getBoundingClientRect().top - el.getBoundingClientRect().top);
597
+ const top = Math.max(0, Math.min(raw, el.scrollHeight - el.clientHeight));
598
+ el.scrollTo({ top, behavior: "smooth" });
599
+ }
600
+ },
601
+ updateActiveHeading: (headings) => {
602
+ const idx = computeActiveIndex(headings);
603
+ if (idx !== null) emit("navigator-active-index-change", idx);
604
+ },
605
+ // Re-resolve the active heading from the caret when the selection moves
606
+ // (no doc change). Keeps the minimap in sync as the user clicks/arrows
607
+ // around, including in short documents that can't scroll.
608
+ syncActiveFromSelection: () => {
609
+ const idx = activeIndexFromSelection(getDocumentHeadings());
610
+ if (idx !== null) emit("navigator-active-index-change", idx);
611
+ },
612
+ onNavigatorScroll: (headings) => {
613
+ cancelAnimationFrame(rafId);
614
+ rafId = requestAnimationFrame(() => {
615
+ const idx = computeActiveIndex(headings);
616
+ if (idx !== null) emit("navigator-active-index-change", idx);
617
+ });
618
+ }
619
+ };
620
+ }
621
+ function collectInlineContent(block, schema) {
622
+ if (block.isTextblock) return block.content;
623
+ const lines = [];
624
+ block.descendants((node) => {
625
+ if (node.isTextblock) {
626
+ lines.push(node.content);
627
+ return false;
628
+ }
629
+ return true;
630
+ });
631
+ if (lines.length === 0) return Fragment.empty;
632
+ const hardBreak = schema.nodes.hard_break;
633
+ let result = Fragment.empty;
634
+ lines.forEach((line, i) => {
635
+ if (i > 0 && hardBreak) result = result.addToEnd(hardBreak.create());
636
+ result = result.append(line);
637
+ });
638
+ return result;
639
+ }
640
+ function collectTextLines(block) {
641
+ if (block.isTextblock) return block.textContent;
642
+ const lines = [];
643
+ block.descendants((node) => {
644
+ if (node.isTextblock) {
645
+ lines.push(node.textContent);
646
+ return false;
647
+ }
648
+ return true;
649
+ });
650
+ return lines.join("\n");
651
+ }
652
+
653
+ // src/logic/menuCommands.ts
654
+ function createMenuCommands(ctx) {
655
+ const { getView, schema, emit, props } = ctx;
656
+ const buildCommands = (view) => createCommands(
657
+ view,
658
+ schema,
659
+ {
660
+ onUploadImages: () => emit("image-upload-request"),
661
+ onUploadDocuments: () => emit("document-upload-request")
662
+ },
663
+ { defaultCellWidth: props.defaultTableColumnWidth ?? 120 }
664
+ );
665
+ const blockPos = (view, blockIndex) => {
666
+ let pos = 0;
667
+ view.state.doc.forEach((_child, offset, i) => {
668
+ if (i === blockIndex) pos = offset;
669
+ });
670
+ return pos;
671
+ };
672
+ const getBlockMenuCommands = (blockIndex, onDismiss) => {
673
+ const view = getView();
674
+ if (!view) return {};
675
+ const commands = buildCommands(view);
676
+ const selectBlock = () => {
677
+ if (!view) return;
678
+ const doc = view.state.doc;
679
+ if (blockIndex >= doc.childCount) return;
680
+ const startPos = blockPos(view, blockIndex);
681
+ const endPos = startPos + doc.child(blockIndex).nodeSize;
682
+ view.dispatch(view.state.tr.setSelection(TextSelection.create(doc, startPos + 1, endPos - 1)));
683
+ };
684
+ const deleteBlock = () => {
685
+ if (!view) return;
686
+ const doc = view.state.doc;
687
+ if (blockIndex >= doc.childCount) return;
688
+ const pos = blockPos(view, blockIndex);
689
+ const tr = view.state.tr.delete(pos, pos + doc.child(blockIndex).nodeSize);
690
+ if (tr.doc.childCount === 0) tr.insert(0, schema.nodes.paragraph.create());
691
+ view.dispatch(tr);
692
+ view.focus();
693
+ onDismiss();
694
+ };
695
+ const duplicateBlock = () => {
696
+ if (!view) return;
697
+ const doc = view.state.doc;
698
+ if (blockIndex >= doc.childCount) return;
699
+ const pos = blockPos(view, blockIndex);
700
+ const block = doc.child(blockIndex);
701
+ view.dispatch(view.state.tr.insert(pos + block.nodeSize, block.copy(block.content)));
702
+ view.focus();
703
+ onDismiss();
704
+ };
705
+ const copyToClipboard = () => {
706
+ if (!view) return;
707
+ selectBlock();
708
+ const { from, to } = view.state.selection;
709
+ const plain = view.state.doc.textBetween(from, to, "\n");
710
+ const slice = view.state.selection.content();
711
+ const fragment = DOMSerializer.fromSchema(schema).serializeFragment(slice.content);
712
+ const div = document.createElement("div");
713
+ div.appendChild(fragment);
714
+ const html = div.innerHTML;
715
+ if (typeof ClipboardItem !== "undefined" && navigator.clipboard?.write) {
716
+ navigator.clipboard.write([new ClipboardItem({ "text/html": new Blob([html], { type: "text/html" }), "text/plain": new Blob([plain], { type: "text/plain" }) })]).catch(() => navigator.clipboard.writeText(plain));
717
+ } else {
718
+ navigator.clipboard.writeText(plain);
719
+ }
720
+ onDismiss();
721
+ };
722
+ const turnBlockInto = (targetType, attrs) => {
723
+ if (!view) return;
724
+ const doc = view.state.doc;
725
+ if (blockIndex >= doc.childCount) return;
726
+ const pos = blockPos(view, blockIndex);
727
+ const block = doc.child(blockIndex);
728
+ const blockEnd = pos + block.nodeSize;
729
+ const getInlineContent = () => collectInlineContent(block, schema);
730
+ const tr = view.state.tr;
731
+ let newBlock;
732
+ if (targetType === "paragraph") {
733
+ newBlock = schema.nodes.paragraph.create(attrs || null, getInlineContent());
734
+ } else if (targetType === "heading") {
735
+ newBlock = schema.nodes.heading.create(attrs || { level: 1 }, getInlineContent());
736
+ } else if (targetType === "code_block") {
737
+ const text = collectTextLines(block);
738
+ newBlock = schema.nodes.code_block.create(null, text ? schema.text(text) : void 0);
739
+ } else if (targetType === "blockquote") {
740
+ newBlock = schema.nodes.blockquote.create(null, schema.nodes.paragraph.create(null, getInlineContent()));
741
+ } else if (targetType === "bullet_list" || targetType === "ordered_list" || targetType === "check_list") {
742
+ const listType = schema.nodes[targetType];
743
+ if (block.type === schema.nodes.bullet_list || block.type === schema.nodes.ordered_list || block.type === schema.nodes.check_list) {
744
+ const targetItemType = targetType === "check_list" ? schema.nodes.check_list_item : schema.nodes.list_item;
745
+ const items = [];
746
+ block.forEach((item) => {
747
+ const itemAttrs = targetItemType === schema.nodes.check_list_item ? { checked: false } : null;
748
+ items.push(targetItemType.create(itemAttrs, item.content));
749
+ });
750
+ newBlock = listType.create(null, items);
751
+ } else {
752
+ const itemType = targetType === "check_list" ? schema.nodes.check_list_item : schema.nodes.list_item;
753
+ const itemAttrs = targetType === "check_list" ? { checked: false } : null;
754
+ const item = itemType.create(itemAttrs, schema.nodes.paragraph.create(null, getInlineContent()));
755
+ newBlock = listType.create(null, item);
756
+ }
757
+ } else {
758
+ return;
759
+ }
760
+ tr.replaceWith(pos, blockEnd, newBlock);
761
+ const selection = TextSelection.findFrom(tr.doc.resolve(pos + 1), 1, true);
762
+ if (selection) tr.setSelection(selection);
763
+ view.dispatch(tr);
764
+ view.focus();
765
+ onDismiss();
766
+ };
767
+ const applyColor = (color, property) => {
768
+ selectBlock();
769
+ if (property === "color") commands.foregroundColor(color);
770
+ else commands.backgroundColor(color);
771
+ if (view) {
772
+ const { to } = view.state.selection;
773
+ view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, to)));
774
+ }
775
+ onDismiss();
776
+ };
777
+ return {
778
+ duplicate: duplicateBlock,
779
+ copyToClipboard,
780
+ deleteBlock,
781
+ turnInto: {
782
+ text: () => turnBlockInto("paragraph"),
783
+ heading1: () => turnBlockInto("heading", { level: 1 }),
784
+ heading2: () => turnBlockInto("heading", { level: 2 }),
785
+ heading3: () => turnBlockInto("heading", { level: 3 }),
786
+ bulletList: () => turnBlockInto("bullet_list"),
787
+ orderedList: () => turnBlockInto("ordered_list"),
788
+ checkList: () => turnBlockInto("check_list"),
789
+ blockquote: () => turnBlockInto("blockquote"),
790
+ codeBlock: () => turnBlockInto("code_block")
791
+ },
792
+ foregroundColor: (color) => applyColor(color, "color"),
793
+ backgroundColor: (color) => applyColor(color, "backgroundColor")
794
+ };
795
+ };
796
+ const getSlashMenuCommands = (_blockIndex, onDismiss) => {
797
+ const view = getView();
798
+ if (!view) return {};
799
+ const commands = buildCommands(view);
800
+ const exec = (fn, options) => {
801
+ if (!view) return;
802
+ executeSlashCommand(view, fn);
803
+ onDismiss();
804
+ };
805
+ return {
806
+ text: () => exec(() => commands.paragraph()),
807
+ heading: (level) => exec(() => commands.heading(level)),
808
+ bulletList: () => exec(() => commands.bulletList()),
809
+ orderedList: () => exec(() => commands.orderedList()),
810
+ checkList: () => exec(() => commands.checkList()),
811
+ blockquote: () => exec(() => commands.blockquote()),
812
+ code: () => exec(() => commands.codeBlock()),
813
+ divider: () => exec(() => commands.insertHorizontalRule()),
814
+ uploadImages: () => exec(() => commands.uploadImages()),
815
+ uploadDocuments: () => exec(() => commands.uploadDocuments()),
816
+ table: () => exec(() => commands.table(3, 3))
817
+ };
818
+ };
819
+ return { getBlockMenuCommands, getSlashMenuCommands };
820
+ }
821
+ var EMPTY_BLOCK = "<p><br></p>";
822
+ function docToBlocks(doc, schema) {
823
+ const serializer = DOMSerializer.fromSchema(schema);
824
+ const blocks = [];
825
+ withoutPlaceholders(doc.content, schema).forEach((child) => {
826
+ const fragment = serializer.serializeFragment(Fragment.from(child));
827
+ const container = document.createElement("div");
828
+ container.appendChild(fragment);
829
+ const html = container.innerHTML;
830
+ blocks.push(html || EMPTY_BLOCK);
831
+ });
832
+ return blocks.length > 0 ? blocks : [EMPTY_BLOCK];
833
+ }
834
+ function blocksToDoc(blocks, schema) {
835
+ const parser = DOMParser.fromSchema(schema);
836
+ const nodes = [];
837
+ for (const html of blocks) {
838
+ const parsed = parser.parse(htmlToInertFragment(html || EMPTY_BLOCK));
839
+ parsed.content.forEach((child) => {
840
+ nodes.push(child);
841
+ });
842
+ }
843
+ if (nodes.length === 0) {
844
+ nodes.push(schema.nodes.paragraph.create());
845
+ }
846
+ return schema.node("doc", null, nodes);
847
+ }
848
+
849
+ // src/texteditor.internal.ts
850
+ var TABLE_MIN_COLUMN_WIDTH = 40;
851
+ var TABLE_DEFAULT_COLUMN_WIDTH = 120;
852
+ var VALUE_CHANGE_AUTO_DEFER_THRESHOLD = 8e3;
853
+ var MENTION_MENU_ID = "p-te-mention-menu";
854
+ var SLASH_MENU_ID = "p-te-slash-menu";
855
+
856
+ // src/logic/valueEmitter.ts
857
+ function createValueEmitter({ getView, schema, emit, isBlockMode, debounceMs }) {
858
+ const debouncer = createDebouncer(debounceMs);
859
+ const hasExplicitDebounce = debounceMs > 0;
860
+ let valueRaf = 0;
861
+ const emitValueChange = () => {
862
+ const view = getView();
863
+ if (!view) return;
864
+ emit(isBlockMode() ? docToBlocks(view.state.doc, schema) : docToHtml(view.state.doc, schema));
865
+ };
866
+ const scheduleValueChange = () => {
867
+ if (hasExplicitDebounce) {
868
+ debouncer.schedule(emitValueChange);
869
+ return;
870
+ }
871
+ const view = getView();
872
+ if (view && view.state.doc.content.size > VALUE_CHANGE_AUTO_DEFER_THRESHOLD) {
873
+ if (valueRaf) return;
874
+ valueRaf = requestAnimationFrame(() => {
875
+ valueRaf = 0;
876
+ emitValueChange();
877
+ });
878
+ return;
879
+ }
880
+ emitValueChange();
881
+ };
882
+ const flushValueChange = () => {
883
+ if (valueRaf) {
884
+ cancelAnimationFrame(valueRaf);
885
+ valueRaf = 0;
886
+ emitValueChange();
887
+ }
888
+ debouncer.flush();
889
+ };
890
+ return { emitValueChange, scheduleValueChange, flushValueChange };
891
+ }
892
+
893
+ // src/serialization/markdownParser.ts
894
+ function escapeHtml(s) {
895
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
896
+ }
897
+ function inlineToHtml(text) {
898
+ let out = escapeHtml(text);
899
+ out = out.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, (_m, alt, src) => `<img src="${src}" alt="${alt}">`);
900
+ out = out.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_m, t, href) => `<a href="${href}">${t}</a>`);
901
+ out = out.replace(/&lt;((?:https?|mailto):[^\s&]+)&gt;/g, (_m, url) => `<a href="${url}">${url}</a>`);
902
+ out = out.replace(/`([^`]+)`/g, (_m, c) => `<code>${c}</code>`);
903
+ out = out.replace(/\*\*\*([^*]+)\*\*\*/g, "<strong><em>$1</em></strong>");
904
+ out = out.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
905
+ out = out.replace(/(^|[^*])\*([^*]+)\*/g, "$1<em>$2</em>");
906
+ out = out.replace(/~~([^~]+)~~/g, "<del>$1</del>");
907
+ out = out.replace(/==([^=]+)==/g, '<span style="background-color: rgba(242, 201, 76, 0.4)">$1</span>');
908
+ return out;
909
+ }
910
+ function isTableSeparator(line) {
911
+ return /^\s*\|?\s*:?-{1,}:?\s*(\|\s*:?-{1,}:?\s*)+\|?\s*$/.test(line);
912
+ }
913
+ function splitRow(line) {
914
+ return line.trim().replace(/^\||\|$/g, "").split("|").map((c) => c.trim());
915
+ }
916
+ function markdownToHtml(md) {
917
+ const lines = md.replace(/\r\n?/g, "\n").split("\n");
918
+ const html = [];
919
+ let i = 0;
920
+ const flushParagraph = (buf) => {
921
+ if (buf.length) html.push(`<p>${inlineToHtml(buf.join(" "))}</p>`);
922
+ };
923
+ let para = [];
924
+ while (i < lines.length) {
925
+ const line = lines[i];
926
+ const fence = line.match(/^(```|~~~)([\w-]*)/);
927
+ if (fence) {
928
+ flushParagraph(para);
929
+ para = [];
930
+ const code = [];
931
+ const lang = fence[2];
932
+ i++;
933
+ while (i < lines.length && !lines[i].startsWith(fence[1])) code.push(lines[i++]);
934
+ i++;
935
+ const openTag = lang ? `<pre data-language="${lang}"><code class="language-${lang}">` : "<pre><code>";
936
+ html.push(`${openTag}${escapeHtml(code.join("\n"))}</code></pre>`);
937
+ continue;
938
+ }
939
+ if (line.includes("|") && i + 1 < lines.length && isTableSeparator(lines[i + 1])) {
940
+ flushParagraph(para);
941
+ para = [];
942
+ const headers = splitRow(line);
943
+ const rows = [];
944
+ i += 2;
945
+ while (i < lines.length && lines[i].includes("|") && lines[i].trim()) rows.push(splitRow(lines[i++]));
946
+ const head = `<tr>${headers.map((h) => `<th>${inlineToHtml(h)}</th>`).join("")}</tr>`;
947
+ const body = rows.map((r) => `<tr>${r.map((c) => `<td>${inlineToHtml(c)}</td>`).join("")}</tr>`).join("");
948
+ html.push(`<table>${head}${body}</table>`);
949
+ continue;
950
+ }
951
+ const heading = line.match(/^(#{1,6})\s+(.*)$/);
952
+ if (heading) {
953
+ flushParagraph(para);
954
+ para = [];
955
+ html.push(`<h${heading[1].length}>${inlineToHtml(heading[2])}</h${heading[1].length}>`);
956
+ i++;
957
+ continue;
958
+ }
959
+ if (/^\s*([-*_])\s*\1\s*\1[\s\S]*$/.test(line) || /^---+$/.test(line.trim())) {
960
+ flushParagraph(para);
961
+ para = [];
962
+ html.push("<hr>");
963
+ i++;
964
+ continue;
965
+ }
966
+ if (/^\s*>\s?/.test(line)) {
967
+ flushParagraph(para);
968
+ para = [];
969
+ const quote = [];
970
+ while (i < lines.length && /^\s*>\s?/.test(lines[i])) quote.push(lines[i++].replace(/^\s*>\s?/, ""));
971
+ html.push(`<blockquote><p>${inlineToHtml(quote.join(" "))}</p></blockquote>`);
972
+ continue;
973
+ }
974
+ if (/^\s*[-*]\s+\[[ xX]\]\s+/.test(line)) {
975
+ flushParagraph(para);
976
+ para = [];
977
+ const items = [];
978
+ while (i < lines.length && /^\s*[-*]\s+\[[ xX]\]\s+/.test(lines[i])) {
979
+ const m = lines[i].match(/^\s*[-*]\s+\[([ xX])\]\s+(.*)$/);
980
+ const checked = m[1].toLowerCase() === "x";
981
+ items.push(`<li data-p-checked="${checked}"><p>${inlineToHtml(m[2])}</p></li>`);
982
+ i++;
983
+ }
984
+ html.push(`<ul data-p-checked-list>${items.join("")}</ul>`);
985
+ continue;
986
+ }
987
+ const bullet = line.match(/^\s*([-*+])\s+(.*)$/);
988
+ const ordered = line.match(/^\s*(\d+)\.\s+(.*)$/);
989
+ if (bullet || ordered) {
990
+ flushParagraph(para);
991
+ para = [];
992
+ const tag = ordered ? "ol" : "ul";
993
+ const items = [];
994
+ while (i < lines.length) {
995
+ const b = lines[i].match(/^\s*[-*+]\s+(.*)$/);
996
+ const o = lines[i].match(/^\s*\d+\.\s+(.*)$/);
997
+ if (ordered && o) items.push(`<li><p>${inlineToHtml(o[1])}</p></li>`);
998
+ else if (!ordered && b && !/^\s*[-*]\s+\[[ xX]\]/.test(lines[i])) items.push(`<li><p>${inlineToHtml(b[1])}</p></li>`);
999
+ else break;
1000
+ i++;
1001
+ }
1002
+ html.push(`<${tag}>${items.join("")}</${tag}>`);
1003
+ continue;
1004
+ }
1005
+ if (!line.trim()) {
1006
+ flushParagraph(para);
1007
+ para = [];
1008
+ i++;
1009
+ continue;
1010
+ }
1011
+ para.push(line.trim());
1012
+ i++;
1013
+ }
1014
+ flushParagraph(para);
1015
+ return html.join("");
1016
+ }
1017
+ function looksLikeMarkdown(text) {
1018
+ return /(^|\n)\s*(#{1,6}\s|[-*+]\s|\d+\.\s|>\s|```|\|.*\|)/.test(text) || /\*\*[^*]+\*\*|\[[^\]]+\]\([^)]+\)|`[^`]+`/.test(text);
1019
+ }
1020
+ var STRUCTURAL_TAGS = /<(h[1-6]|ul|ol|li|table|tr|td|th|thead|tbody|blockquote|pre|code|img|a|hr|strong|em|b|i|s|del|mark|input)\b/i;
1021
+ function isTrivialHtmlWrapper(html, plain) {
1022
+ if (!plain) return false;
1023
+ const body = html.replace(/^\s*<meta[^>]*>/i, "");
1024
+ if (STRUCTURAL_TAGS.test(body)) return false;
1025
+ const stripped = body.replace(/<\/(p|div|li|tr)>/gi, "\n").replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]+>/g, "").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&nbsp;/g, " ");
1026
+ const norm = (s) => s.replace(/\s+/g, " ").trim();
1027
+ return norm(stripped) === norm(plain);
1028
+ }
1029
+
1030
+ // src/serialization/markdownSerializer.ts
1031
+ function isKnownInline(node, schema) {
1032
+ return node.isText || node.type === schema.nodes.hard_break || node.type === schema.nodes.image;
1033
+ }
1034
+ var MARKDOWN_BLOCKS = ["paragraph", "heading", "blockquote", "code_block", "horizontal_rule", "bullet_list", "ordered_list", "check_list", "table"];
1035
+ function isKnownBlock(node, schema) {
1036
+ return MARKDOWN_BLOCKS.some((name) => node.type === schema.nodes[name]);
1037
+ }
1038
+ function escapeText(text) {
1039
+ return text.replace(/([\\`*_~[\]])/g, "\\$1");
1040
+ }
1041
+ function serializeInline(node, schema) {
1042
+ if (node.type === schema.nodes.hard_break) return "\n";
1043
+ if (node.type === schema.nodes.image) {
1044
+ const alt = node.attrs.alt || "";
1045
+ return `![${alt}](${node.attrs.src})`;
1046
+ }
1047
+ if (!node.isText && !isKnownInline(node, schema)) return nodeToHtml(node, schema);
1048
+ if (!node.isText) return node.textContent;
1049
+ let text = escapeText(node.text ?? "");
1050
+ const marks = node.marks;
1051
+ if (marks.some((m) => m.type === schema.marks.code)) text = `\`${node.text ?? ""}\``;
1052
+ if (marks.some((m) => m.type === schema.marks.bold) && marks.some((m) => m.type === schema.marks.italic)) text = `***${text}***`;
1053
+ else if (marks.some((m) => m.type === schema.marks.bold)) text = `**${text}**`;
1054
+ else if (marks.some((m) => m.type === schema.marks.italic)) text = `*${text}*`;
1055
+ if (marks.some((m) => m.type === schema.marks.strikethrough)) text = `~~${text}~~`;
1056
+ const link = marks.find((m) => m.type === schema.marks.link);
1057
+ if (link) text = `[${text}](${link.attrs.href})`;
1058
+ return text;
1059
+ }
1060
+ function serializeInlineContent(node, schema) {
1061
+ let out = "";
1062
+ node.forEach((child) => {
1063
+ out += serializeInline(child, schema);
1064
+ });
1065
+ return out;
1066
+ }
1067
+ function serializeTableRow(row, schema) {
1068
+ const cells = [];
1069
+ row.forEach((cell) => {
1070
+ cells.push(
1071
+ serializeInlineContent(cell.firstChild ?? cell, schema).replace(/\n/g, " ").trim()
1072
+ );
1073
+ });
1074
+ return `| ${cells.join(" | ")} |`;
1075
+ }
1076
+ function serializeBlock(node, schema, indent = "") {
1077
+ const n = node.type;
1078
+ if (n === schema.nodes.paragraph) return indent + serializeInlineContent(node, schema);
1079
+ if (n === schema.nodes.heading) return `${"#".repeat(node.attrs.level)} ${serializeInlineContent(node, schema)}`;
1080
+ if (n === schema.nodes.blockquote) {
1081
+ return node.content.content.map((child) => `> ${serializeBlock(child, schema)}`).join("\n");
1082
+ }
1083
+ if (n === schema.nodes.code_block) return "```" + (node.attrs.language || "") + "\n" + node.textContent + "\n```";
1084
+ if (n === schema.nodes.horizontal_rule) return "---";
1085
+ if (n === schema.nodes.bullet_list || n === schema.nodes.ordered_list) {
1086
+ const ordered = n === schema.nodes.ordered_list;
1087
+ const start = ordered ? node.attrs.start || 1 : 0;
1088
+ const lines = [];
1089
+ node.content.forEach((item, _o, i) => {
1090
+ const bullet = ordered ? `${start + i}.` : "-";
1091
+ const inner = item.content.content.map((c) => serializeBlock(c, schema, indent + " ")).join("\n");
1092
+ lines.push(`${indent}${bullet} ${inner.replace(new RegExp(`^${indent} `), "").trimStart()}`);
1093
+ });
1094
+ return lines.join("\n");
1095
+ }
1096
+ if (n === schema.nodes.check_list) {
1097
+ const lines = [];
1098
+ node.content.forEach((item) => {
1099
+ const checked = item.attrs.checked ? "x" : " ";
1100
+ const inner = item.content.content.map((c) => serializeBlock(c, schema)).join("\n");
1101
+ lines.push(`${indent}- [${checked}] ${inner.trimStart()}`);
1102
+ });
1103
+ return lines.join("\n");
1104
+ }
1105
+ if (n === schema.nodes.table) {
1106
+ const rows = [];
1107
+ let isFirst = true;
1108
+ node.content.forEach((row) => {
1109
+ rows.push(serializeTableRow(row, schema));
1110
+ if (isFirst) {
1111
+ const cols = row.childCount;
1112
+ rows.push(`| ${Array(cols).fill("---").join(" | ")} |`);
1113
+ isFirst = false;
1114
+ }
1115
+ });
1116
+ return rows.join("\n");
1117
+ }
1118
+ if (!isKnownBlock(node, schema)) return nodeToHtml(node, schema);
1119
+ return serializeInlineContent(node, schema);
1120
+ }
1121
+ function docToMarkdown(doc, schema) {
1122
+ const blocks = [];
1123
+ doc.forEach((node) => {
1124
+ blocks.push(serializeBlock(node, schema));
1125
+ });
1126
+ return blocks.join("\n\n");
1127
+ }
1128
+ function sliceToMarkdown(slice, schema) {
1129
+ const blocks = [];
1130
+ slice.content.forEach((node) => {
1131
+ blocks.push(node.isTextblock || node.isBlock ? serializeBlock(node, schema) : serializeInline(node, schema));
1132
+ });
1133
+ return blocks.join("\n\n");
1134
+ }
1135
+ function deriveFormatState(state, schema, view, cachedPlaceholders) {
1136
+ const { $from } = state.selection;
1137
+ const textStyleAttrs = getMarkAttrs(state, schema.marks.textStyle);
1138
+ let headingLevel = null;
1139
+ for (let depth = $from.depth; depth > 0; depth--) {
1140
+ if ($from.node(depth).type === schema.nodes.heading) {
1141
+ headingLevel = $from.node(depth).attrs.level;
1142
+ break;
1143
+ }
1144
+ }
1145
+ let textAlign = "left";
1146
+ for (let depth = $from.depth; depth > 0; depth--) {
1147
+ const node = $from.node(depth);
1148
+ if (node.type === schema.nodes.paragraph || node.type === schema.nodes.heading) {
1149
+ textAlign = node.attrs.textAlign || "left";
1150
+ break;
1151
+ }
1152
+ }
1153
+ let linkMark = schema.marks.link.isInSet(state.storedMarks || $from.marks());
1154
+ if (!linkMark && !state.selection.empty) {
1155
+ const { from, to } = state.selection;
1156
+ state.doc.nodesBetween(from, to, (node) => {
1157
+ if (linkMark) return false;
1158
+ const mark = schema.marks.link.isInSet(node.marks);
1159
+ if (mark) linkMark = mark;
1160
+ return void 0;
1161
+ });
1162
+ }
1163
+ const placeholders = cachedPlaceholders ?? findPlaceholders(state, schema);
1164
+ const computedFont = textStyleAttrs?.fontFamily && textStyleAttrs?.fontSize ? null : getComputedFont(state, view);
1165
+ const fontFamily = textStyleAttrs?.fontFamily || computedFont?.fontFamily || null;
1166
+ const fontSize = textStyleAttrs?.fontSize || computedFont?.fontSize || null;
1167
+ return {
1168
+ bold: isMarkActive(state, schema.marks.bold),
1169
+ italic: isMarkActive(state, schema.marks.italic),
1170
+ underline: isMarkActive(state, schema.marks.underline),
1171
+ strikethrough: isMarkActive(state, schema.marks.strikethrough),
1172
+ subscript: isMarkActive(state, schema.marks.subscript),
1173
+ superscript: isMarkActive(state, schema.marks.superscript),
1174
+ code: isMarkActive(state, schema.marks.code),
1175
+ codeBlock: isNodeActive(state, schema.nodes.code_block),
1176
+ blockquote: isNodeActive(state, schema.nodes.blockquote),
1177
+ heading: headingLevel,
1178
+ bulletList: isNodeActive(state, schema.nodes.bullet_list),
1179
+ orderedList: isNodeActive(state, schema.nodes.ordered_list),
1180
+ checkList: isNodeActive(state, schema.nodes.check_list),
1181
+ textAlign,
1182
+ fontFamily,
1183
+ fontSize,
1184
+ foregroundColor: textStyleAttrs?.color || null,
1185
+ backgroundColor: textStyleAttrs?.backgroundColor || null,
1186
+ highlight: !!textStyleAttrs?.backgroundColor,
1187
+ inTable: isNodeActive(state, schema.nodes.table),
1188
+ hasSelection: !state.selection.empty,
1189
+ hasFocus: view?.hasFocus() ?? false,
1190
+ link: !!linkMark,
1191
+ linkUrl: linkMark?.attrs.href || null,
1192
+ canUndo: undo(state),
1193
+ canRedo: redo(state),
1194
+ hasImageUploadPlaceholder: placeholders.image,
1195
+ hasDocumentUploadPlaceholder: placeholders.document,
1196
+ isMultiCellSelected: state.selection instanceof CellSelection,
1197
+ isCellMerged: isCellMergedCheck($from)
1198
+ };
1199
+ }
1200
+ var fontCacheEl = null;
1201
+ var fontCacheVal = null;
1202
+ function getComputedFont(state, view) {
1203
+ if (!view || typeof window === "undefined") return null;
1204
+ try {
1205
+ const { $from } = state.selection;
1206
+ const at = view.domAtPos($from.pos);
1207
+ let node = at.node;
1208
+ if (node && node.nodeType === Node.TEXT_NODE) node = node.parentElement;
1209
+ const el = node;
1210
+ if (!el || !el.isConnected) return null;
1211
+ if (fontCacheEl?.deref() === el) return fontCacheVal;
1212
+ const cs = window.getComputedStyle(el);
1213
+ const fontFamily = (cs.fontFamily.split(",")[0] || "").trim().replace(/^["']|["']$/g, "");
1214
+ const fontSize = cs.fontSize;
1215
+ const result = !fontFamily && !fontSize ? null : { fontFamily, fontSize };
1216
+ fontCacheEl = new WeakRef(el);
1217
+ fontCacheVal = result;
1218
+ return result;
1219
+ } catch {
1220
+ return null;
1221
+ }
1222
+ }
1223
+ function isCellMergedCheck($from) {
1224
+ for (let depth = $from.depth; depth > 0; depth--) {
1225
+ const node = $from.node(depth);
1226
+ if (node.type.name === "table_cell" || node.type.name === "table_header") {
1227
+ return (node.attrs.colspan ?? 1) > 1 || (node.attrs.rowspan ?? 1) > 1;
1228
+ }
1229
+ }
1230
+ return false;
1231
+ }
1232
+ function findPlaceholders(state, schema) {
1233
+ const imageType = schema.nodes.image_upload_placeholder;
1234
+ const documentType = schema.nodes.document_upload_placeholder;
1235
+ let image = false;
1236
+ let document2 = false;
1237
+ state.doc.descendants((node) => {
1238
+ if (node.type === imageType) image = true;
1239
+ else if (node.type === documentType) document2 = true;
1240
+ return !(image && document2);
1241
+ });
1242
+ return { image, document: document2 };
1243
+ }
1244
+ function replaceDocPreservingSelection(view, doc) {
1245
+ const prev = view.state.selection;
1246
+ const tr = view.state.tr.replaceWith(0, view.state.doc.content.size, doc.content);
1247
+ const max = tr.doc.content.size;
1248
+ const anchor = Math.min(prev.anchor, max);
1249
+ const head = Math.min(prev.head, max);
1250
+ tr.setSelection(TextSelection.between(tr.doc.resolve(anchor), tr.doc.resolve(head)));
1251
+ view.dispatch(tr);
1252
+ }
1253
+
1254
+ // src/texteditor.logic.ts
1255
+ var isRtl = (el) => {
1256
+ const target = el ?? document.documentElement;
1257
+ return target.closest?.('[dir="rtl"]') != null || getComputedStyle(target).direction === "rtl";
1258
+ };
1259
+ function useTextEditorLogic(instance) {
1260
+ const { $refs, $props, $state, $emit, onMounted, onUnmounted } = instance;
1261
+ const collectSchemaExtensions = () => {
1262
+ const plugins = $props.plugins || [];
1263
+ return plugins.map((entry) => Array.isArray(entry) ? entry[0] : entry).map((p) => p?.schema).filter((s) => !!s);
1264
+ };
1265
+ const schema = createEditorSchema(collectSchemaExtensions());
1266
+ const collectProseMirrorPlugins = () => {
1267
+ const plugins = $props.plugins || [];
1268
+ const result = [];
1269
+ for (const entry of plugins) {
1270
+ const plugin = Array.isArray(entry) ? entry[0] : entry;
1271
+ const options = Array.isArray(entry) ? entry[1] : void 0;
1272
+ const factory = plugin?.prosemirrorPlugins;
1273
+ if (typeof factory === "function") result.push(...factory(schema, options));
1274
+ }
1275
+ return result;
1276
+ };
1277
+ let view = null;
1278
+ let tableActiveState = null;
1279
+ let tableOverlayRect = null;
1280
+ const pendingProseMirrorPlugins = [];
1281
+ let pendingMouseUpRemover = null;
1282
+ const emit = (event, ...args) => {
1283
+ $emit(event, ...args);
1284
+ };
1285
+ const isBlockMode = () => $props.mode === "block";
1286
+ const isMarkdownEnabled = () => !!$props.markdown;
1287
+ const { scheduleValueChange, flushValueChange } = createValueEmitter({
1288
+ getView: () => view,
1289
+ schema,
1290
+ emit: (value) => emit("value-change", value),
1291
+ isBlockMode,
1292
+ debounceMs: $props.valueChangeDebounce ?? 0
1293
+ });
1294
+ let placeholderCache = { image: false, document: false };
1295
+ const emitFormatState = () => {
1296
+ if (!view) return;
1297
+ emit("format-state-change", deriveFormatState(view.state, schema, view, placeholderCache));
1298
+ };
1299
+ const findPlaceholderPos = (nodeType) => {
1300
+ if (!view) return -1;
1301
+ let pos = -1;
1302
+ view.state.doc.descendants((node, p) => {
1303
+ if (pos !== -1) return false;
1304
+ if (node.type === nodeType) pos = p;
1305
+ return void 0;
1306
+ });
1307
+ return pos;
1308
+ };
1309
+ const removePlaceholder = (nodeType) => {
1310
+ const pos = findPlaceholderPos(nodeType);
1311
+ if (view && pos >= 0) view.dispatch(view.state.tr.delete(pos, pos + 1));
1312
+ };
1313
+ const ctx = {
1314
+ getView: () => view,
1315
+ getElement: () => $refs.element,
1316
+ props: $props,
1317
+ emit,
1318
+ schema,
1319
+ findPlaceholderPos,
1320
+ removePlaceholder
1321
+ };
1322
+ const uploadModule = createUploadModule(ctx);
1323
+ const navigatorModule = createNavigatorModule(ctx);
1324
+ const menuCommands = createMenuCommands(ctx);
1325
+ let comboboxState = null;
1326
+ const editorAttributes = () => {
1327
+ const attrs = {
1328
+ class: textEditorClasses.content,
1329
+ role: "textbox",
1330
+ "aria-multiline": "true"
1331
+ };
1332
+ if ($props.ariaLabelledby) {
1333
+ attrs["aria-labelledby"] = $props.ariaLabelledby;
1334
+ } else {
1335
+ attrs["aria-label"] = $props.ariaLabel || $props.placeholder || "Rich text editor";
1336
+ }
1337
+ if ($props.disabled) attrs["aria-disabled"] = "true";
1338
+ if ($props.disabled || $props.readonly) attrs["aria-readonly"] = "true";
1339
+ if (comboboxState) {
1340
+ attrs["aria-expanded"] = "true";
1341
+ attrs["aria-controls"] = comboboxState.controls;
1342
+ if (comboboxState.activedescendant) attrs["aria-activedescendant"] = comboboxState.activedescendant;
1343
+ } else {
1344
+ attrs["aria-expanded"] = "false";
1345
+ }
1346
+ return attrs;
1347
+ };
1348
+ const setComboboxState = (state) => {
1349
+ let next = state;
1350
+ if (state && state.activedescendant === void 0 && state.controls === comboboxState?.controls) {
1351
+ next = { controls: state.controls, activedescendant: comboboxState.activedescendant };
1352
+ }
1353
+ if (next?.controls === comboboxState?.controls && next?.activedescendant === comboboxState?.activedescendant) return;
1354
+ comboboxState = next;
1355
+ view?.setProps({ attributes: editorAttributes });
1356
+ };
1357
+ const setComboboxActiveDescendant = (id) => {
1358
+ if (!comboboxState) return;
1359
+ setComboboxState({ controls: comboboxState.controls, activedescendant: id ?? void 0 });
1360
+ };
1361
+ const parseValueToDoc = (value) => {
1362
+ const emptyDoc = () => schema.node("doc", null, [schema.node("paragraph")]);
1363
+ try {
1364
+ if (isBlockMode()) {
1365
+ const blocks = Array.isArray(value) ? value : [];
1366
+ return blocksToDoc(blocks.length > 0 ? blocks : ["<p></p>"], schema);
1367
+ }
1368
+ const html = value || "";
1369
+ return html ? htmlToDoc(html, schema) : emptyDoc();
1370
+ } catch (error) {
1371
+ emit("parse-error", { error });
1372
+ return emptyDoc();
1373
+ }
1374
+ };
1375
+ const mentionController = createMentionController({
1376
+ getHandler: () => $props.mentionHandler,
1377
+ getFilterField: () => $props.mentionFilterField || "name",
1378
+ emit: (update) => {
1379
+ setComboboxState(update.active ? { controls: MENTION_MENU_ID } : null);
1380
+ emit("mention-request", update);
1381
+ }
1382
+ });
1383
+ const markdownPlugin = createMarkdownInputRules(schema);
1384
+ const createEditorView = (mountEl) => {
1385
+ const doc = parseValueToDoc($props.value);
1386
+ const state = EditorState.create({
1387
+ doc,
1388
+ plugins: [
1389
+ createEditorKeymap(schema, { blockMode: isBlockMode() }),
1390
+ keymap(baseKeymap),
1391
+ history(),
1392
+ ...isBlockMode() ? [createTrailingNodePlugin(schema), gapCursor()] : [dropCursor(), createTrailingNodePlugin(schema), gapCursor()],
1393
+ ...$props.markdown ? [markdownPlugin] : [],
1394
+ // prosemirror-tables columnResizing drags from the wrong edge under RTL,
1395
+ // so it is disabled there. RTL column resizing is a known limitation.
1396
+ ...isRtl(mountEl) ? [] : [
1397
+ columnResizing({ View: null, cellMinWidth: $props.minTableColumnWidth ?? TABLE_MIN_COLUMN_WIDTH, defaultCellMinWidth: $props.defaultTableColumnWidth ?? TABLE_DEFAULT_COLUMN_WIDTH }),
1398
+ // eslint-disable-line @typescript-eslint/no-explicit-any
1399
+ // Pin every unsized column to its rendered width when a
1400
+ // resize starts, so a single drag (or even a plain click on
1401
+ // the handle) doesn't snap the other columns to the default.
1402
+ createColumnWidthPinPlugin()
1403
+ ],
1404
+ tableEditing(),
1405
+ createTableOverlayPlugin({
1406
+ onActiveStateChange: (state2) => {
1407
+ tableActiveState = state2;
1408
+ emit("table-active-state-change", state2);
1409
+ },
1410
+ onOverlayRectChange: (rect) => {
1411
+ tableOverlayRect = rect;
1412
+ emit("table-overlay-rect-change", rect);
1413
+ },
1414
+ onColumnTriggerClick: (colIndex, event) => {
1415
+ if (view) selectColumn(view, colIndex);
1416
+ emit("table-column-menu-request", colIndex, event);
1417
+ },
1418
+ onRowTriggerClick: (rowIndex, event) => {
1419
+ if (view) selectRow(view, rowIndex);
1420
+ emit("table-row-menu-request", rowIndex, event);
1421
+ },
1422
+ onCellTriggerClick: (event) => {
1423
+ emit("table-cell-menu-request", event);
1424
+ },
1425
+ onAddColumn: () => {
1426
+ if (view) addColumnAfter(view.state, view.dispatch);
1427
+ },
1428
+ onAddRow: () => {
1429
+ if (view) addRowAfter(view.state, view.dispatch);
1430
+ }
1431
+ }),
1432
+ createPlaceholderPlugin({
1433
+ text: () => $props.placeholder,
1434
+ blockMode: isBlockMode()
1435
+ }),
1436
+ createContextToolbarPlugin({
1437
+ onRequest: (position) => {
1438
+ emit("context-toolbar-request", position);
1439
+ }
1440
+ }),
1441
+ ...$state.isMentionEnabled ? [
1442
+ createMentionPlugin(schema, {
1443
+ onUpdate: (update) => mentionController.onUpdate(update)
1444
+ })
1445
+ ] : [],
1446
+ createSelectionHighlightPlugin(),
1447
+ ...isBlockMode() && $state.isSlashCommandsEnabled ? [
1448
+ createSlashPlugin({
1449
+ onUpdate: (update) => {
1450
+ setComboboxState(update.active ? { controls: SLASH_MENU_ID } : null);
1451
+ emit("slash-menu-request", update);
1452
+ },
1453
+ placeholder: $props.slashPlaceholder ?? void 0
1454
+ })
1455
+ ] : [],
1456
+ ...collectProseMirrorPlugins()
1457
+ ]
1458
+ });
1459
+ view = new EditorView(mountEl, {
1460
+ state,
1461
+ editable: () => !($props.disabled || $props.readonly),
1462
+ dispatchTransaction(tr) {
1463
+ if (!view) return;
1464
+ if (tr.docChanged && ($props.disabled || $props.readonly)) return;
1465
+ const newState = view.state.apply(tr);
1466
+ view.updateState(newState);
1467
+ if (tr.docChanged) {
1468
+ placeholderCache = findPlaceholders(newState, schema);
1469
+ scheduleValueChange();
1470
+ if ($props.navigator) {
1471
+ navigatorModule.scheduleHeadings();
1472
+ }
1473
+ } else if (tr.selectionSet) {
1474
+ if ($props.navigator) {
1475
+ navigatorModule.syncActiveFromSelection();
1476
+ }
1477
+ emit("selection-update");
1478
+ }
1479
+ emitFormatState();
1480
+ },
1481
+ nodeViews: {
1482
+ ...!isBlockMode() ? {
1483
+ table: (node, tableView, getPos) => createTableNodeView(node, tableView, getPos, {
1484
+ defaultCellWidth: $props.defaultTableColumnWidth ?? TABLE_DEFAULT_COLUMN_WIDTH
1485
+ })
1486
+ } : {},
1487
+ check_list_item: (node, checkView, getPos) => checkListItemNodeView(node, checkView, getPos),
1488
+ ...isBlockMode() ? {
1489
+ ...Object.fromEntries(
1490
+ ["paragraph", "heading", "blockquote", "code_block", "horizontal_rule", "bullet_list", "ordered_list", "check_list", "image_upload_placeholder", "document_upload_placeholder"].map((typeName) => [
1491
+ typeName,
1492
+ (node, blockView, getPos) => createBlockNodeView(node, blockView, getPos, (index, el) => emit("block-hover-change", { index, element: el }))
1493
+ ])
1494
+ ),
1495
+ table: (node, tableView, getPos) => createBlockTableNodeView(node, tableView, getPos, {
1496
+ defaultCellWidth: $props.defaultTableColumnWidth ?? TABLE_DEFAULT_COLUMN_WIDTH,
1497
+ onBlockHoverChange: (index, el) => emit("block-hover-change", { index, element: el })
1498
+ })
1499
+ } : {}
1500
+ },
1501
+ attributes: editorAttributes,
1502
+ // Surface focus/blur as granular events without intercepting them
1503
+ // (return false so ProseMirror keeps its default handling).
1504
+ handleDOMEvents: {
1505
+ focus: () => {
1506
+ emit("editor-focus");
1507
+ return false;
1508
+ },
1509
+ blur: () => {
1510
+ emit("editor-blur");
1511
+ return false;
1512
+ }
1513
+ },
1514
+ // Bound pasted/dropped HTML to the same depth guard as setValue, so a
1515
+ // deeply-nested clipboard payload can't freeze the main thread.
1516
+ transformPastedHTML: (html) => htmlExceedsMaxDepth(html) ? "" : html,
1517
+ // Modifier-click (Cmd/Ctrl) opens a link without leaving the editor; a
1518
+ // plain click still positions the caret for editing.
1519
+ handleClickOn: (clickView, pos, _node, _nodePos, event) => {
1520
+ if (!(event.metaKey || event.ctrlKey)) return false;
1521
+ const mark = schema.marks.link.isInSet(clickView.state.doc.resolve(pos).marks());
1522
+ const href = mark?.attrs.href ? sanitizeUrl(mark.attrs.href) : null;
1523
+ if (!href) return false;
1524
+ window.open(href, "_blank", "noopener,noreferrer");
1525
+ return true;
1526
+ },
1527
+ // Copy: in markdown mode put markdown (not HTML) on text/plain.
1528
+ clipboardTextSerializer: (slice) => isMarkdownEnabled() ? sliceToMarkdown(slice, schema) : slice.content.textBetween(0, slice.content.size, "\n\n"),
1529
+ // Paste: in markdown mode, convert pasted markdown text into rich content.
1530
+ handlePaste: (pasteView, event) => {
1531
+ if (!isMarkdownEnabled()) return false;
1532
+ const html = event.clipboardData?.getData("text/html");
1533
+ const text = event.clipboardData?.getData("text/plain");
1534
+ if (html && !isTrivialHtmlWrapper(html, text)) return false;
1535
+ if (!text || !looksLikeMarkdown(text)) return false;
1536
+ const generated = markdownToHtml(text);
1537
+ if (htmlExceedsMaxDepth(generated)) return false;
1538
+ const slice = htmlToSlice(generated, schema);
1539
+ pasteView.dispatch(pasteView.state.tr.replaceSelection(slice).scrollIntoView());
1540
+ return true;
1541
+ },
1542
+ handleDrop: (_view, event) => {
1543
+ const html = event.dataTransfer?.getData("text/html");
1544
+ if (html && htmlExceedsMaxDepth(html)) {
1545
+ event.preventDefault();
1546
+ return true;
1547
+ }
1548
+ return false;
1549
+ }
1550
+ });
1551
+ placeholderCache = findPlaceholders(view.state, schema);
1552
+ if (pendingProseMirrorPlugins.length) {
1553
+ const queued = pendingProseMirrorPlugins.splice(0);
1554
+ for (const plugin of queued) addProseMirrorPlugin(view, plugin);
1555
+ }
1556
+ emit("editor-create");
1557
+ };
1558
+ onMounted(() => {
1559
+ const mountEl = $refs.element;
1560
+ if (mountEl) {
1561
+ createEditorView(mountEl);
1562
+ }
1563
+ });
1564
+ onUnmounted(() => {
1565
+ flushValueChange();
1566
+ navigatorModule.cancel();
1567
+ pendingMouseUpRemover?.();
1568
+ pendingMouseUpRemover = null;
1569
+ if (view) {
1570
+ view.destroy();
1571
+ view = null;
1572
+ }
1573
+ });
1574
+ return {
1575
+ /**
1576
+ * Re-evaluates the EditorView's `editable` predicate. Call after
1577
+ * mutating `$props.disabled` or `$props.readonly` so ProseMirror
1578
+ * picks up the change without remounting.
1579
+ */
1580
+ refreshEditable: () => {
1581
+ view?.setProps({ editable: () => !($props.disabled || $props.readonly), attributes: editorAttributes });
1582
+ },
1583
+ /**
1584
+ * Add/remove the markdown input-rules plugin live so toggling the
1585
+ * `markdown` prop at runtime takes effect without remounting.
1586
+ */
1587
+ refreshMarkdown: () => {
1588
+ if (!view) return;
1589
+ const active = view.state.plugins.includes(markdownPlugin);
1590
+ if ($props.markdown && !active) addProseMirrorPlugin(view, markdownPlugin);
1591
+ else if (!$props.markdown && active) removeProseMirrorPlugin(view, markdownPlugin);
1592
+ },
1593
+ onEditorKeyDown: () => {
1594
+ if (isBlockMode()) {
1595
+ emit("block-hover-change", { index: -1, element: null });
1596
+ }
1597
+ },
1598
+ onEditorMouseDown: () => {
1599
+ const before = view ? view.state.selection : null;
1600
+ const beforeFrom = before?.from ?? -1;
1601
+ const beforeTo = before?.to ?? -1;
1602
+ const onMouseUp = () => {
1603
+ document.removeEventListener("mouseup", onMouseUp);
1604
+ pendingMouseUpRemover = null;
1605
+ if (!view || view.state.selection.empty) return;
1606
+ const { from, to } = view.state.selection;
1607
+ if (from === beforeFrom && to === beforeTo) return;
1608
+ const position = getSelectionPosition(view);
1609
+ if (position) {
1610
+ emit("context-toolbar-request", position);
1611
+ }
1612
+ };
1613
+ pendingMouseUpRemover?.();
1614
+ document.addEventListener("mouseup", onMouseUp);
1615
+ pendingMouseUpRemover = () => document.removeEventListener("mouseup", onMouseUp);
1616
+ },
1617
+ // Upload (image + document) — delegated to the upload module
1618
+ startImageUploads: uploadModule.startImageUploads,
1619
+ dismissImageUpload: uploadModule.dismissImageUpload,
1620
+ validateImageFiles: uploadModule.validateImageFiles,
1621
+ startDocumentUploads: uploadModule.startDocumentUploads,
1622
+ dismissDocumentUpload: uploadModule.dismissDocumentUpload,
1623
+ validateDocumentFiles: uploadModule.validateDocumentFiles,
1624
+ // Block mode (Phase 6)
1625
+ addBlockAfter: (index) => {
1626
+ if (!view) return;
1627
+ const targetPos = posAfterBlock(view.state.doc, index);
1628
+ const paragraph = schema.nodes.paragraph.create();
1629
+ const tr = view.state.tr.insert(targetPos, paragraph);
1630
+ tr.setSelection(TextSelection.create(tr.doc, targetPos + 1));
1631
+ view.dispatch(tr);
1632
+ view.focus();
1633
+ },
1634
+ getBlockType: (index) => {
1635
+ if (!view || index >= view.state.doc.childCount) return "text";
1636
+ const node = view.state.doc.child(index);
1637
+ if (node.type.name === "paragraph") return "text";
1638
+ if (node.type.name === "heading") return `heading:${node.attrs.level}`;
1639
+ return node.type.name;
1640
+ },
1641
+ onBlockDragStart: (index, event) => {
1642
+ if (!event.dataTransfer) return;
1643
+ event.dataTransfer.effectAllowed = "move";
1644
+ event.dataTransfer.setData("application/x-block-drag", String(index));
1645
+ if (view && index < view.state.doc.childCount) {
1646
+ const dom = view.nodeDOM(blockStartPos(view.state.doc, index));
1647
+ const blockEl = dom instanceof HTMLElement ? dom : dom?.parentElement ?? null;
1648
+ if (blockEl) {
1649
+ const rect = blockEl.getBoundingClientRect();
1650
+ event.dataTransfer.setDragImage(blockEl, event.clientX - rect.left, event.clientY - rect.top);
1651
+ }
1652
+ }
1653
+ emit("block-drag-start", index);
1654
+ },
1655
+ moveBlock: (fromIndex, toIndex) => {
1656
+ if (!view || fromIndex === toIndex || fromIndex < 0 || toIndex < 0) return;
1657
+ const doc = view.state.doc;
1658
+ if (fromIndex >= doc.childCount || toIndex > doc.childCount) return;
1659
+ const fromPos = blockStartPos(doc, fromIndex);
1660
+ const blockNode = doc.child(fromIndex);
1661
+ const tr = view.state.tr;
1662
+ tr.delete(fromPos, blockEndPos(doc, fromIndex));
1663
+ const adjustedTarget = toIndex > fromIndex ? toIndex - 1 : toIndex;
1664
+ const insertPos = adjustedTarget > 0 ? blockEndPos(tr.doc, adjustedTarget - 1) : 0;
1665
+ tr.insert(insertPos, blockNode);
1666
+ tr.setSelection(TextSelection.create(tr.doc, insertPos + 1));
1667
+ view.dispatch(tr);
1668
+ view.focus();
1669
+ emit("block-drag-end");
1670
+ },
1671
+ onBlockDragEnd: () => {
1672
+ emit("block-drag-end");
1673
+ },
1674
+ getCommands: () => {
1675
+ if (!view) return {};
1676
+ return createCommands(
1677
+ view,
1678
+ schema,
1679
+ {
1680
+ onUploadImages: () => emit("image-upload-request"),
1681
+ onUploadDocuments: () => emit("document-upload-request")
1682
+ },
1683
+ { defaultCellWidth: $props.defaultTableColumnWidth ?? TABLE_DEFAULT_COLUMN_WIDTH, highlightColor: $props.defaultHighlightColor ?? null }
1684
+ );
1685
+ },
1686
+ getBlockMenuCommands: menuCommands.getBlockMenuCommands,
1687
+ getSlashMenuCommands: menuCommands.getSlashMenuCommands,
1688
+ // Recent colors (deferred)
1689
+ // Table — insert after the selected cell's row/column.
1690
+ onTableAddRow: () => {
1691
+ if (view) addRowAfter(view.state, view.dispatch);
1692
+ },
1693
+ onTableAddColumn: () => {
1694
+ if (view) addColumnAfter(view.state, view.dispatch);
1695
+ },
1696
+ getTableColumnCommands: (colIndex, onDismiss) => {
1697
+ if (!view) return {};
1698
+ return createTableColumnCommands(view, schema, colIndex, onDismiss);
1699
+ },
1700
+ getTableRowCommands: (rowIndex, onDismiss) => {
1701
+ if (!view) return {};
1702
+ return createTableRowCommands(view, schema, rowIndex, onDismiss);
1703
+ },
1704
+ getTableCellCommands: (onDismiss) => {
1705
+ if (!view) return {};
1706
+ return createTableCellCommands(view, schema, onDismiss);
1707
+ },
1708
+ getTableOverlayRect: () => tableOverlayRect,
1709
+ getTableActiveState: () => tableActiveState,
1710
+ getIsMultiCellSelected: () => {
1711
+ if (!view) return false;
1712
+ return view.state.selection instanceof CellSelection;
1713
+ },
1714
+ getIsCellMerged: () => {
1715
+ if (!view) return false;
1716
+ const { $from } = view.state.selection;
1717
+ for (let depth = $from.depth; depth > 0; depth--) {
1718
+ const node = $from.node(depth);
1719
+ if (node.type.name === "table_cell" || node.type.name === "table_header") {
1720
+ return (node.attrs.colspan ?? 1) > 1 || (node.attrs.rowspan ?? 1) > 1;
1721
+ }
1722
+ }
1723
+ return false;
1724
+ },
1725
+ updateTableOverlayRect: () => {
1726
+ if (tableActiveState) {
1727
+ const rect = tableActiveState.table.getBoundingClientRect();
1728
+ if (rect.width > 0) {
1729
+ emit("table-overlay-rect-change", tableOverlayRect);
1730
+ }
1731
+ }
1732
+ },
1733
+ // Mention
1734
+ getMentionCommands: (onDismiss, options) => ({
1735
+ select: (data) => {
1736
+ if (!view) return;
1737
+ insertMention(view, schema, data, options);
1738
+ onDismiss();
1739
+ }
1740
+ }),
1741
+ getFilteredMentionItems: (items, filterField, filterText) => {
1742
+ if (!filterText) return items;
1743
+ const query = filterText.toLowerCase();
1744
+ return items.filter((item) => {
1745
+ if (!item || typeof item !== "object") return false;
1746
+ const value = String(item[filterField || "name"] || "");
1747
+ return value.toLowerCase().includes(query);
1748
+ });
1749
+ },
1750
+ // Plugin helpers
1751
+ getSelectedText: () => {
1752
+ if (!view) return "";
1753
+ const { from, to } = view.state.selection;
1754
+ return view.state.doc.textBetween(from, to, "\n");
1755
+ },
1756
+ replaceSelection: (content, asHtml) => {
1757
+ if (!view) return;
1758
+ const { from, to } = view.state.selection;
1759
+ if (asHtml) {
1760
+ const slice = htmlToSlice(content, schema);
1761
+ view.dispatch(view.state.tr.replaceSelection(slice));
1762
+ } else {
1763
+ view.dispatch(view.state.tr.insertText(content, from, to));
1764
+ }
1765
+ },
1766
+ getEditorElement: () => view?.dom || null,
1767
+ // Direct ProseMirror access for extension authors (additive, opt-in)
1768
+ getView: () => view,
1769
+ getState: () => view?.state ?? null,
1770
+ registerProseMirrorPlugin: (plugin) => {
1771
+ if (!view) {
1772
+ pendingProseMirrorPlugins.push(plugin);
1773
+ return () => {
1774
+ const idx = pendingProseMirrorPlugins.indexOf(plugin);
1775
+ if (idx !== -1) pendingProseMirrorPlugins.splice(idx, 1);
1776
+ if (view) removeProseMirrorPlugin(view, plugin);
1777
+ };
1778
+ }
1779
+ return addProseMirrorPlugin(view, plugin);
1780
+ },
1781
+ runCommand: (command) => {
1782
+ if (!view) return false;
1783
+ return command(view.state, view.dispatch, view);
1784
+ },
1785
+ // Selection highlight
1786
+ preserveSelection: () => {
1787
+ if (view) preserveSelection(view);
1788
+ },
1789
+ /** Points aria-activedescendant at an open menu's active option; null clears. */
1790
+ setComboboxActiveDescendant,
1791
+ /**
1792
+ * Serialize the current document to HTML. Works in both modes;
1793
+ * in block mode the result is the concatenated HTML of every
1794
+ * block (use `getBlocks` for the block-split array).
1795
+ */
1796
+ getHTML: () => {
1797
+ if (!view) return "";
1798
+ return docToHtml(view.state.doc, schema);
1799
+ },
1800
+ /**
1801
+ * Serialize as the array-of-block-HTML representation used by
1802
+ * block-mode `v-model`. In classic mode returns a single-item
1803
+ * array for parity.
1804
+ */
1805
+ getBlocks: () => {
1806
+ if (!view) return [];
1807
+ const result = docToBlocks(view.state.doc, schema);
1808
+ return Array.isArray(result) ? result : [result];
1809
+ },
1810
+ /** ProseMirror document as plain JSON (loss-less). */
1811
+ getJSON: () => {
1812
+ if (!view) return null;
1813
+ return view.state.doc.toJSON();
1814
+ },
1815
+ /** Plain-text projection. Useful for previews and full-text search indexes. */
1816
+ getText: () => {
1817
+ if (!view) return "";
1818
+ return view.state.doc.textContent;
1819
+ },
1820
+ /** Markdown projection of the document (same serializer as markdown copy). */
1821
+ getMarkdown: () => {
1822
+ if (!view) return "";
1823
+ return docToMarkdown(view.state.doc, schema);
1824
+ },
1825
+ // Value
1826
+ setValue: (value) => {
1827
+ if (!view) return;
1828
+ if (view.composing) return;
1829
+ if (isBlockMode()) {
1830
+ const blocks = Array.isArray(value) ? value : [];
1831
+ if (JSON.stringify(docToBlocks(view.state.doc, schema)) === JSON.stringify(blocks.length > 0 ? blocks : ["<p></p>"])) return;
1832
+ } else {
1833
+ const html = (typeof value === "string" ? value : "") || "";
1834
+ if (docToHtml(view.state.doc, schema) === html) return;
1835
+ }
1836
+ replaceDocPreservingSelection(view, parseValueToDoc(value));
1837
+ },
1838
+ // Navigator — delegated to the navigator module
1839
+ getDocumentHeadings: navigatorModule.getDocumentHeadings,
1840
+ focusHeading: navigatorModule.focusHeading,
1841
+ scrollToHeading: navigatorModule.scrollToHeading,
1842
+ updateActiveHeading: navigatorModule.updateActiveHeading,
1843
+ onNavigatorScroll: navigatorModule.onNavigatorScroll
1844
+ };
1845
+ }
1846
+
1847
+ // src/createTextEditor.ts
1848
+ var createTextEditor = defineComponent("TextEditor", ({ getCurrentInstance, defineRefs, defineProps, defineState, defineEmits, defineExpose }) => {
1849
+ defineRefs(defaultTextEditorRefs);
1850
+ defineProps(defaultTextEditorProps);
1851
+ defineState(defaultTextEditorState);
1852
+ defineEmits([
1853
+ "value-change",
1854
+ "format-state-change",
1855
+ "table-overlay-rect-change",
1856
+ "table-active-state-change",
1857
+ "context-toolbar-request",
1858
+ "image-upload-request",
1859
+ "document-upload-request",
1860
+ "image-reject",
1861
+ "document-reject",
1862
+ "image-upload-error",
1863
+ "document-upload-error",
1864
+ "parse-error",
1865
+ "mention-request",
1866
+ "block-hover-change",
1867
+ "block-drag-start",
1868
+ "block-drag-end",
1869
+ "slash-menu-request",
1870
+ "table-column-menu-request",
1871
+ "table-row-menu-request",
1872
+ "table-cell-menu-request",
1873
+ "navigator-headings-change",
1874
+ "navigator-active-index-change",
1875
+ "image-upload-state-change",
1876
+ "document-upload-state-change",
1877
+ "image-upload-complete",
1878
+ "document-upload-complete",
1879
+ "selection-update",
1880
+ "editor-focus",
1881
+ "editor-blur",
1882
+ "editor-create"
1883
+ ]);
1884
+ const currentInstance = getCurrentInstance();
1885
+ const expose = useTextEditorLogic(currentInstance);
1886
+ defineExpose(expose);
1887
+ });
1888
+
1889
+ export { createTextEditor };
1890
+ //# sourceMappingURL=chunk-TBQVZVKA.mjs.map
1891
+ //# sourceMappingURL=chunk-TBQVZVKA.mjs.map