neuphlo-editor 2.5.1 → 2.6.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 (128) hide show
  1. package/dist/Markdown-BAW0cb-U.d.cts +153 -0
  2. package/dist/Markdown-BAW0cb-U.d.ts +153 -0
  3. package/dist/chunk-ABL4D65Z.js +1454 -0
  4. package/dist/chunk-ABL4D65Z.js.map +1 -0
  5. package/dist/chunk-Z2DR2FK3.js +1582 -0
  6. package/dist/chunk-Z2DR2FK3.js.map +1 -0
  7. package/dist/headless/components/editor-bubble-item.d.ts +9 -0
  8. package/dist/headless/components/editor-bubble-item.js +29 -0
  9. package/dist/headless/components/editor-bubble.d.ts +9 -0
  10. package/dist/headless/components/editor-bubble.js +9 -0
  11. package/dist/headless/components/editor-command-item.d.ts +3 -0
  12. package/dist/headless/components/editor-command-item.js +27 -0
  13. package/dist/headless/components/editor-command.d.ts +3 -0
  14. package/dist/headless/components/editor-command.js +136 -0
  15. package/dist/headless/components/editor.d.ts +24 -0
  16. package/dist/headless/components/editor.js +13 -0
  17. package/dist/headless/extensions/AISuggestion/AISuggestion.d.ts +82 -0
  18. package/dist/headless/extensions/AISuggestion/AISuggestion.js +244 -0
  19. package/dist/headless/extensions/AISuggestion/index.d.ts +2 -0
  20. package/dist/headless/extensions/AISuggestion/index.js +1 -0
  21. package/dist/headless/extensions/CodeBlock/CodeBlock.d.ts +1 -0
  22. package/dist/headless/extensions/CodeBlock/CodeBlock.js +6 -0
  23. package/dist/headless/extensions/CodeBlock/index.d.ts +1 -0
  24. package/dist/headless/extensions/CodeBlock/index.js +1 -0
  25. package/dist/headless/extensions/DragHandle/DragHandle.d.ts +9 -0
  26. package/dist/headless/extensions/DragHandle/DragHandle.js +47 -0
  27. package/dist/headless/extensions/DragHandle/index.d.ts +2 -0
  28. package/dist/headless/extensions/DragHandle/index.js +1 -0
  29. package/dist/headless/extensions/Image/Image.d.ts +6 -0
  30. package/dist/headless/extensions/Image/Image.js +197 -0
  31. package/dist/headless/extensions/ImageBlock/ImageBlock.d.ts +23 -0
  32. package/dist/headless/extensions/ImageBlock/ImageBlock.js +242 -0
  33. package/dist/headless/extensions/ImageBlock/index.d.ts +2 -0
  34. package/dist/headless/extensions/ImageBlock/index.js +1 -0
  35. package/dist/headless/extensions/Link/Link.d.ts +2 -0
  36. package/dist/headless/extensions/Link/Link.js +44 -0
  37. package/dist/headless/extensions/Link/index.d.ts +1 -0
  38. package/dist/headless/extensions/Link/index.js +1 -0
  39. package/dist/headless/extensions/Markdown.d.ts +9 -0
  40. package/dist/headless/extensions/Markdown.js +138 -0
  41. package/dist/headless/extensions/MarkdownPaste.d.ts +3 -0
  42. package/dist/headless/extensions/MarkdownPaste.js +168 -0
  43. package/dist/headless/extensions/Mention/index.d.ts +4 -0
  44. package/dist/headless/extensions/Mention/index.js +3 -0
  45. package/dist/headless/extensions/Mention/mention-command.d.ts +8 -0
  46. package/dist/headless/extensions/Mention/mention-command.js +165 -0
  47. package/dist/headless/extensions/Mention/mention-list.d.ts +8 -0
  48. package/dist/headless/extensions/Mention/mention-list.js +150 -0
  49. package/dist/headless/extensions/Mention/mention-node-view.d.ts +2 -0
  50. package/dist/headless/extensions/Mention/mention-node-view.js +92 -0
  51. package/dist/headless/extensions/Mention/mention.d.ts +46 -0
  52. package/dist/headless/extensions/Mention/mention.js +218 -0
  53. package/dist/headless/extensions/Table/index.d.ts +2 -0
  54. package/dist/headless/extensions/Table/index.js +2 -0
  55. package/dist/headless/extensions/VideoBlock/VideoBlock.d.ts +17 -0
  56. package/dist/headless/extensions/VideoBlock/VideoBlock.js +73 -0
  57. package/dist/headless/extensions/VideoBlock/index.d.ts +2 -0
  58. package/dist/headless/extensions/VideoBlock/index.js +1 -0
  59. package/dist/headless/extensions/extension-kit.d.ts +29 -0
  60. package/dist/headless/extensions/extension-kit.js +132 -0
  61. package/dist/headless/extensions/index.d.ts +16 -0
  62. package/dist/headless/extensions/index.js +12 -0
  63. package/dist/headless/extensions/slash-command.d.ts +24 -0
  64. package/dist/headless/extensions/slash-command.js +99 -0
  65. package/dist/headless/index.cjs +143 -2
  66. package/dist/headless/index.cjs.map +1 -1
  67. package/dist/headless/index.d.cts +3 -2
  68. package/dist/headless/index.d.ts +3 -2
  69. package/dist/headless/index.js +5 -1
  70. package/dist/headless/utils/atoms.d.ts +13 -0
  71. package/dist/headless/utils/atoms.js +5 -0
  72. package/dist/headless/utils/store.d.ts +2 -0
  73. package/dist/headless/utils/store.js +4 -0
  74. package/dist/highlight.css +0 -6
  75. package/dist/react/Editor.d.ts +43 -0
  76. package/dist/react/Editor.js +119 -0
  77. package/dist/react/TableOfContents.d.ts +7 -0
  78. package/dist/react/TableOfContents.js +96 -0
  79. package/dist/react/index.cjs +156 -9
  80. package/dist/react/index.cjs.map +1 -1
  81. package/dist/react/index.d.cts +5 -2
  82. package/dist/react/index.d.ts +5 -2
  83. package/dist/react/index.js +14 -4
  84. package/dist/react/index.js.map +1 -1
  85. package/dist/react/menus/DragHandle/BlockActionMenu.d.ts +7 -0
  86. package/dist/react/menus/DragHandle/BlockActionMenu.js +111 -0
  87. package/dist/react/menus/DragHandle/DragHandleMenu.d.ts +7 -0
  88. package/dist/react/menus/DragHandle/DragHandleMenu.js +97 -0
  89. package/dist/react/menus/ImageBlock/ImageBlockLoading.d.ts +2 -0
  90. package/dist/react/menus/ImageBlock/ImageBlockLoading.js +6 -0
  91. package/dist/react/menus/ImageBlock/ImageBlockMenu.d.ts +8 -0
  92. package/dist/react/menus/ImageBlock/ImageBlockMenu.js +75 -0
  93. package/dist/react/menus/ImageBlock/ImageBlockView.d.ts +10 -0
  94. package/dist/react/menus/ImageBlock/ImageBlockView.js +60 -0
  95. package/dist/react/menus/ImageBlock/ImageBlockWidth.d.ts +6 -0
  96. package/dist/react/menus/ImageBlock/ImageBlockWidth.js +29 -0
  97. package/dist/react/menus/ImageBlock/ImageResizeHandle.d.ts +8 -0
  98. package/dist/react/menus/ImageBlock/ImageResizeHandle.js +37 -0
  99. package/dist/react/menus/ImageBlock/ImageUploader.d.ts +7 -0
  100. package/dist/react/menus/ImageBlock/ImageUploader.js +73 -0
  101. package/dist/react/menus/ImageBlock/index.d.ts +4 -0
  102. package/dist/react/menus/ImageBlock/index.js +4 -0
  103. package/dist/react/menus/ImageMenu.d.ts +10 -0
  104. package/dist/react/menus/ImageMenu.js +77 -0
  105. package/dist/react/menus/ImagePopover.d.ts +8 -0
  106. package/dist/react/menus/ImagePopover.js +146 -0
  107. package/dist/react/menus/LinkMenu.d.ts +2 -0
  108. package/dist/react/menus/LinkMenu.js +69 -0
  109. package/dist/react/menus/MenuList.d.ts +10 -0
  110. package/dist/react/menus/MenuList.js +94 -0
  111. package/dist/react/menus/SlashMenu.d.ts +5 -0
  112. package/dist/react/menus/SlashMenu.js +156 -0
  113. package/dist/react/menus/TableMenu.d.ts +5 -0
  114. package/dist/react/menus/TableMenu.js +462 -0
  115. package/dist/react/menus/TaskItem/TaskItemView.d.ts +12 -0
  116. package/dist/react/menus/TaskItem/TaskItemView.js +11 -0
  117. package/dist/react/menus/TextMenu.d.ts +10 -0
  118. package/dist/react/menus/TextMenu.js +139 -0
  119. package/dist/react/menus/VideoBlock/VideoBlockMenu.d.ts +7 -0
  120. package/dist/react/menus/VideoBlock/VideoBlockMenu.js +72 -0
  121. package/dist/react/menus/VideoBlock/VideoBlockView.d.ts +10 -0
  122. package/dist/react/menus/VideoBlock/VideoBlockView.js +87 -0
  123. package/dist/react/menus/VideoBlock/index.d.ts +2 -0
  124. package/dist/react/menus/VideoBlock/index.js +2 -0
  125. package/dist/react/menus/index.d.ts +15 -0
  126. package/dist/react/menus/index.js +9 -0
  127. package/package.json +13 -9
  128. package/LICENSE +0 -21
@@ -0,0 +1,462 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCurrentEditor, useEditorState } from "@tiptap/react";
3
+ import { IconRowInsertBottom, IconColumnInsertRight, IconColumnInsertLeft, IconRowRemove, IconColumnRemove, IconTableRow, IconTableColumn, IconArrowMerge, IconArrowsSplit, IconTableOff, IconGripVertical, IconGripHorizontal, IconRowInsertTop, } from "@tabler/icons-react";
4
+ import { useState, useEffect, useRef, useCallback, } from "react";
5
+ import { createPortal } from "react-dom";
6
+ export function TableMenu({ className: _className }) {
7
+ const { editor } = useCurrentEditor();
8
+ const tableInfo = useEditorState({
9
+ editor,
10
+ selector: (ctx) => {
11
+ if (!ctx.editor)
12
+ return null;
13
+ const { $from } = ctx.editor.state.selection;
14
+ for (let d = $from.depth; d > 0; d--) {
15
+ if ($from.node(d).type.name === "table") {
16
+ return { pos: $from.start(d) - 1, depth: d };
17
+ }
18
+ }
19
+ return null;
20
+ },
21
+ });
22
+ const [colGrips, setColGrips] = useState([]);
23
+ const [rowGrips, setRowGrips] = useState([]);
24
+ const [dropdown, setDropdown] = useState(null);
25
+ const [tableRect, setTableRect] = useState(null);
26
+ const [isHovering, setIsHovering] = useState(false);
27
+ const [drag, setDrag] = useState(null);
28
+ const dropdownRef = useRef(null);
29
+ const dragRef = useRef(null);
30
+ // Find the table DOM element
31
+ const getTableDom = useCallback(() => {
32
+ if (!editor || !tableInfo)
33
+ return null;
34
+ return editor.view.nodeDOM(tableInfo.pos);
35
+ }, [editor, tableInfo]);
36
+ // Measure grip positions from the table DOM
37
+ const measureGrips = useCallback(() => {
38
+ const tableDom = getTableDom();
39
+ if (!tableDom) {
40
+ setColGrips([]);
41
+ setRowGrips([]);
42
+ setTableRect(null);
43
+ return;
44
+ }
45
+ const rect = tableDom.getBoundingClientRect();
46
+ setTableRect(rect);
47
+ // Get first row cells for column positions
48
+ const firstRow = tableDom.querySelector("tr");
49
+ if (!firstRow)
50
+ return;
51
+ const cells = firstRow.querySelectorAll("th, td");
52
+ const newColGrips = [];
53
+ cells.forEach((cell) => {
54
+ const cellRect = cell.getBoundingClientRect();
55
+ newColGrips.push({
56
+ left: cellRect.left,
57
+ top: rect.top,
58
+ width: cellRect.width,
59
+ height: 0,
60
+ });
61
+ });
62
+ setColGrips(newColGrips);
63
+ // Get all rows for row positions
64
+ const rows = tableDom.querySelectorAll("tr");
65
+ const newRowGrips = [];
66
+ rows.forEach((row) => {
67
+ const rowRect = row.getBoundingClientRect();
68
+ newRowGrips.push({
69
+ left: rect.left,
70
+ top: rowRect.top,
71
+ width: 0,
72
+ height: rowRect.height,
73
+ });
74
+ });
75
+ setRowGrips(newRowGrips);
76
+ }, [getTableDom]);
77
+ // Observe table geometry changes
78
+ useEffect(() => {
79
+ if (!tableInfo) {
80
+ setColGrips([]);
81
+ setRowGrips([]);
82
+ setTableRect(null);
83
+ setDropdown(null);
84
+ return;
85
+ }
86
+ measureGrips();
87
+ const tableDom = getTableDom();
88
+ if (!tableDom)
89
+ return;
90
+ const ro = new ResizeObserver(() => measureGrips());
91
+ ro.observe(tableDom);
92
+ // Track hover state — expand hit area to include grip zones around the table
93
+ const HOVER_PAD = 32; // px beyond table edges to keep grips visible
94
+ const handleMouseMove = (e) => {
95
+ const r = tableDom.getBoundingClientRect();
96
+ const inside = e.clientX >= r.left - HOVER_PAD &&
97
+ e.clientX <= r.right + HOVER_PAD &&
98
+ e.clientY >= r.top - HOVER_PAD &&
99
+ e.clientY <= r.bottom + HOVER_PAD;
100
+ setIsHovering(inside);
101
+ };
102
+ document.addEventListener("mousemove", handleMouseMove, { passive: true });
103
+ // Also re-measure on scroll
104
+ const scrollParent = tableDom.closest(".nph-editor") || window;
105
+ const handleScroll = () => {
106
+ measureGrips();
107
+ setDropdown(null);
108
+ };
109
+ scrollParent.addEventListener("scroll", handleScroll, { passive: true });
110
+ window.addEventListener("scroll", handleScroll, { passive: true });
111
+ return () => {
112
+ ro.disconnect();
113
+ document.removeEventListener("mousemove", handleMouseMove);
114
+ scrollParent.removeEventListener("scroll", handleScroll);
115
+ window.removeEventListener("scroll", handleScroll);
116
+ };
117
+ }, [tableInfo, getTableDom, measureGrips]);
118
+ // Close dropdown on click outside
119
+ useEffect(() => {
120
+ if (!dropdown)
121
+ return;
122
+ const handlePointerDown = (e) => {
123
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
124
+ setDropdown(null);
125
+ }
126
+ };
127
+ document.addEventListener("pointerdown", handlePointerDown);
128
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
129
+ }, [dropdown]);
130
+ // Close dropdown on editor transaction (typing, etc.)
131
+ useEffect(() => {
132
+ if (!editor || !dropdown)
133
+ return;
134
+ const handleTransaction = () => setDropdown(null);
135
+ editor.on("transaction", handleTransaction);
136
+ return () => { editor.off("transaction", handleTransaction); };
137
+ }, [editor, dropdown]);
138
+ const handleColGripClick = useCallback((index, e) => {
139
+ e.preventDefault();
140
+ e.stopPropagation();
141
+ if (!editor || !tableInfo)
142
+ return;
143
+ // Select the column by clicking the first cell in this column
144
+ const tableDom = getTableDom();
145
+ if (!tableDom)
146
+ return;
147
+ const firstRow = tableDom.querySelector("tr");
148
+ if (!firstRow)
149
+ return;
150
+ const cells = firstRow.querySelectorAll("th, td");
151
+ const cell = cells[index];
152
+ if (!cell)
153
+ return;
154
+ // Find ProseMirror position for this cell
155
+ const pos = editor.view.posAtDOM(cell, 0);
156
+ editor.chain().focus().setTextSelection(pos).run();
157
+ const rect = cell.getBoundingClientRect();
158
+ setDropdown({
159
+ type: "column",
160
+ index,
161
+ x: rect.left,
162
+ y: rect.top - 4,
163
+ });
164
+ }, [editor, tableInfo, getTableDom]);
165
+ const handleRowGripClick = useCallback((index, e) => {
166
+ e.preventDefault();
167
+ e.stopPropagation();
168
+ if (!editor || !tableInfo)
169
+ return;
170
+ // Select the row by clicking the first cell in this row
171
+ const tableDom = getTableDom();
172
+ if (!tableDom)
173
+ return;
174
+ const rows = tableDom.querySelectorAll("tr");
175
+ const row = rows[index];
176
+ if (!row)
177
+ return;
178
+ const firstCell = row.querySelector("th, td");
179
+ if (!firstCell)
180
+ return;
181
+ const pos = editor.view.posAtDOM(firstCell, 0);
182
+ editor.chain().focus().setTextSelection(pos).run();
183
+ // Position dropdown below the grip button
184
+ const gripEl = e.currentTarget;
185
+ const gripRect = gripEl.getBoundingClientRect();
186
+ setDropdown({
187
+ type: "row",
188
+ index,
189
+ x: gripRect.left,
190
+ y: gripRect.bottom + 4,
191
+ });
192
+ }, [editor, tableInfo, getTableDom]);
193
+ // --- Drag-to-reorder logic ---
194
+ const moveColumn = useCallback((from, to) => {
195
+ if (!editor || !tableInfo || from === to)
196
+ return;
197
+ const { state } = editor;
198
+ const tableStart = tableInfo.pos;
199
+ const tableNode = state.doc.nodeAt(tableStart);
200
+ if (!tableNode)
201
+ return;
202
+ const tr = state.tr;
203
+ // For each row, move the cell from `from` to `to`
204
+ tableNode.forEach((row, rowOffset) => {
205
+ if (row.type.name !== "tableRow")
206
+ return;
207
+ const cells = [];
208
+ row.forEach((cell, cellOffset) => {
209
+ cells.push({ node: cell, pos: tableStart + 1 + rowOffset + 1 + cellOffset });
210
+ });
211
+ if (from >= cells.length || to >= cells.length)
212
+ return;
213
+ // Build new row content with reordered cells
214
+ const reordered = [...cells];
215
+ const [moved] = reordered.splice(from, 1);
216
+ reordered.splice(to, 0, moved);
217
+ const rowPos = tableStart + 1 + rowOffset;
218
+ const mappedRowPos = tr.mapping.map(rowPos);
219
+ // Replace the row content
220
+ const newRow = row.type.create(row.attrs, reordered.map((c) => c.node));
221
+ tr.replaceWith(mappedRowPos, mappedRowPos + row.nodeSize, newRow);
222
+ });
223
+ editor.view.dispatch(tr);
224
+ }, [editor, tableInfo]);
225
+ const moveRow = useCallback((from, to) => {
226
+ if (!editor || !tableInfo || from === to)
227
+ return;
228
+ const { state } = editor;
229
+ const tableStart = tableInfo.pos;
230
+ const tableNode = state.doc.nodeAt(tableStart);
231
+ if (!tableNode)
232
+ return;
233
+ const rows = [];
234
+ tableNode.forEach((row) => rows.push(row));
235
+ if (from >= rows.length || to >= rows.length)
236
+ return;
237
+ const reordered = [...rows];
238
+ const [moved] = reordered.splice(from, 1);
239
+ reordered.splice(to, 0, moved);
240
+ const tr = state.tr;
241
+ tr.replaceWith(tableStart + 1, tableStart + 1 + tableNode.content.size, reordered);
242
+ editor.view.dispatch(tr);
243
+ }, [editor, tableInfo]);
244
+ const handleGripDragStart = useCallback((type, index, e) => {
245
+ e.preventDefault();
246
+ e.stopPropagation();
247
+ setDropdown(null);
248
+ const startX = e.clientX;
249
+ const startY = e.clientY;
250
+ let hasMoved = false;
251
+ const dragState = { type, fromIndex: index, toIndex: index };
252
+ dragRef.current = dragState;
253
+ setDrag(dragState);
254
+ const handleMouseMove = (ev) => {
255
+ const dx = ev.clientX - startX;
256
+ const dy = ev.clientY - startY;
257
+ if (!hasMoved && Math.abs(type === "column" ? dx : dy) < 5)
258
+ return;
259
+ hasMoved = true;
260
+ let newIndex = index;
261
+ if (type === "column") {
262
+ for (let i = 0; i < colGrips.length; i++) {
263
+ const mid = colGrips[i].left + colGrips[i].width / 2;
264
+ if (ev.clientX < mid) {
265
+ newIndex = i;
266
+ break;
267
+ }
268
+ newIndex = i;
269
+ }
270
+ }
271
+ else {
272
+ for (let i = 0; i < rowGrips.length; i++) {
273
+ const mid = rowGrips[i].top + rowGrips[i].height / 2;
274
+ if (ev.clientY < mid) {
275
+ newIndex = i;
276
+ break;
277
+ }
278
+ newIndex = i;
279
+ }
280
+ }
281
+ const updated = { type, fromIndex: index, toIndex: newIndex };
282
+ dragRef.current = updated;
283
+ setDrag(updated);
284
+ };
285
+ const handleMouseUp = () => {
286
+ document.removeEventListener("mousemove", handleMouseMove);
287
+ document.removeEventListener("mouseup", handleMouseUp);
288
+ const finalDrag = dragRef.current;
289
+ dragRef.current = null;
290
+ setDrag(null);
291
+ if (finalDrag && hasMoved && finalDrag.fromIndex !== finalDrag.toIndex) {
292
+ if (type === "column") {
293
+ moveColumn(finalDrag.fromIndex, finalDrag.toIndex);
294
+ }
295
+ else {
296
+ moveRow(finalDrag.fromIndex, finalDrag.toIndex);
297
+ }
298
+ }
299
+ };
300
+ document.addEventListener("mousemove", handleMouseMove);
301
+ document.addEventListener("mouseup", handleMouseUp);
302
+ }, [colGrips, rowGrips, moveColumn, moveRow]);
303
+ if (!editor || !tableInfo || colGrips.length === 0)
304
+ return null;
305
+ const GRIP_SIZE = 20;
306
+ const GRIP_GAP = 4;
307
+ const gripsVisible = isHovering || !!dropdown;
308
+ const columnDropdownItems = [
309
+ {
310
+ label: "Toggle header column",
311
+ icon: _jsx(IconTableColumn, { size: 16 }),
312
+ action: () => {
313
+ editor.chain().focus().toggleHeaderColumn().run();
314
+ setDropdown(null);
315
+ },
316
+ },
317
+ {
318
+ label: "Insert column before",
319
+ icon: _jsx(IconColumnInsertLeft, { size: 16 }),
320
+ action: () => {
321
+ editor.chain().focus().addColumnBefore().run();
322
+ setDropdown(null);
323
+ },
324
+ separator: true,
325
+ },
326
+ {
327
+ label: "Insert column after",
328
+ icon: _jsx(IconColumnInsertRight, { size: 16 }),
329
+ action: () => {
330
+ editor.chain().focus().addColumnAfter().run();
331
+ setDropdown(null);
332
+ },
333
+ },
334
+ {
335
+ label: "Merge cells",
336
+ icon: _jsx(IconArrowMerge, { size: 16 }),
337
+ action: () => {
338
+ editor.chain().focus().mergeCells().run();
339
+ setDropdown(null);
340
+ },
341
+ separator: true,
342
+ },
343
+ {
344
+ label: "Split cell",
345
+ icon: _jsx(IconArrowsSplit, { size: 16 }),
346
+ action: () => {
347
+ editor.chain().focus().splitCell().run();
348
+ setDropdown(null);
349
+ },
350
+ },
351
+ {
352
+ label: "Delete column",
353
+ icon: _jsx(IconColumnRemove, { size: 16 }),
354
+ action: () => {
355
+ editor.chain().focus().deleteColumn().run();
356
+ setDropdown(null);
357
+ },
358
+ destructive: true,
359
+ separator: true,
360
+ },
361
+ ];
362
+ const rowDropdownItems = [
363
+ {
364
+ label: "Toggle header row",
365
+ icon: _jsx(IconTableRow, { size: 16 }),
366
+ action: () => {
367
+ editor.chain().focus().toggleHeaderRow().run();
368
+ setDropdown(null);
369
+ },
370
+ },
371
+ {
372
+ label: "Insert row above",
373
+ icon: _jsx(IconRowInsertTop, { size: 16 }),
374
+ action: () => {
375
+ editor.chain().focus().addRowBefore().run();
376
+ setDropdown(null);
377
+ },
378
+ separator: true,
379
+ },
380
+ {
381
+ label: "Insert row below",
382
+ icon: _jsx(IconRowInsertBottom, { size: 16 }),
383
+ action: () => {
384
+ editor.chain().focus().addRowAfter().run();
385
+ setDropdown(null);
386
+ },
387
+ },
388
+ {
389
+ label: "Merge cells",
390
+ icon: _jsx(IconArrowMerge, { size: 16 }),
391
+ action: () => {
392
+ editor.chain().focus().mergeCells().run();
393
+ setDropdown(null);
394
+ },
395
+ separator: true,
396
+ },
397
+ {
398
+ label: "Split cell",
399
+ icon: _jsx(IconArrowsSplit, { size: 16 }),
400
+ action: () => {
401
+ editor.chain().focus().splitCell().run();
402
+ setDropdown(null);
403
+ },
404
+ },
405
+ {
406
+ label: "Delete row",
407
+ icon: _jsx(IconRowRemove, { size: 16 }),
408
+ action: () => {
409
+ editor.chain().focus().deleteRow().run();
410
+ setDropdown(null);
411
+ },
412
+ destructive: true,
413
+ separator: true,
414
+ },
415
+ ];
416
+ const dropdownItems = dropdown?.type === "column" ? columnDropdownItems : rowDropdownItems;
417
+ return createPortal(_jsxs(_Fragment, { children: [colGrips.map((grip, i) => (_jsx("button", { type: "button", className: `nph-table-grip nph-table-grip--col${gripsVisible ? " nph-table-grip--visible" : ""}${drag?.type === "column" && drag.fromIndex === i ? " nph-table-grip--dragging" : ""}`, style: {
418
+ position: "fixed",
419
+ left: grip.left + grip.width / 2 - GRIP_SIZE / 2,
420
+ top: grip.top - GRIP_SIZE - GRIP_GAP,
421
+ width: GRIP_SIZE,
422
+ height: GRIP_SIZE,
423
+ cursor: "grab",
424
+ }, onMouseDown: (e) => handleGripDragStart("column", i, e), onClick: (e) => handleColGripClick(i, e), "aria-label": `Column ${i + 1} options`, children: _jsx(IconGripHorizontal, { size: 14 }) }, `col-${i}`))), rowGrips.map((grip, i) => (_jsx("button", { type: "button", className: `nph-table-grip nph-table-grip--row${gripsVisible ? " nph-table-grip--visible" : ""}${drag?.type === "row" && drag.fromIndex === i ? " nph-table-grip--dragging" : ""}`, style: {
425
+ position: "fixed",
426
+ left: grip.left - GRIP_SIZE - GRIP_GAP,
427
+ top: grip.top + grip.height / 2 - GRIP_SIZE / 2,
428
+ width: GRIP_SIZE,
429
+ height: GRIP_SIZE,
430
+ cursor: "grab",
431
+ }, onMouseDown: (e) => handleGripDragStart("row", i, e), onClick: (e) => handleRowGripClick(i, e), "aria-label": `Row ${i + 1} options`, children: _jsx(IconGripVertical, { size: 14 }) }, `row-${i}`))), tableRect && (_jsxs("button", { type: "button", className: `nph-table-grip nph-table-grip--delete${gripsVisible ? " nph-table-grip--visible" : ""}`, style: {
432
+ position: "fixed",
433
+ left: tableRect.left + tableRect.width / 2 - 60,
434
+ top: tableRect.bottom + GRIP_GAP,
435
+ width: 120,
436
+ height: 24,
437
+ }, onMouseDown: (e) => e.preventDefault(), onClick: () => {
438
+ editor.chain().focus().deleteTable().run();
439
+ }, "aria-label": "Delete table", children: [_jsx(IconTableOff, { size: 14 }), _jsx("span", { style: { fontSize: 12 }, children: "Delete table" })] })), dropdown && (_jsx("div", { ref: dropdownRef, className: "nph-table-dropdown", style: {
440
+ position: "fixed",
441
+ left: dropdown.x,
442
+ top: dropdown.y,
443
+ transform: dropdown.type === "column" ? "translateY(-100%)" : undefined,
444
+ }, children: dropdownItems.map((item, i) => (_jsxs("div", { children: [item.separator && i > 0 && _jsx("div", { className: "nph-table-dropdown__separator" }), _jsxs("button", { type: "button", className: `nph-table-dropdown__item ${item.destructive ? "nph-table-dropdown__item--destructive" : ""}`, onMouseDown: (e) => e.preventDefault(), onClick: item.action, children: [item.icon, _jsx("span", { children: item.label })] })] }, i))) })), drag && drag.fromIndex !== drag.toIndex && tableRect && (drag.type === "column" ? (_jsx("div", { className: "nph-table-drop-indicator nph-table-drop-indicator--col", style: {
445
+ position: "fixed",
446
+ left: drag.toIndex < colGrips.length
447
+ ? colGrips[drag.toIndex].left - 1
448
+ : colGrips[colGrips.length - 1].left + colGrips[colGrips.length - 1].width,
449
+ top: tableRect.top,
450
+ width: 2,
451
+ height: tableRect.height,
452
+ } })) : (_jsx("div", { className: "nph-table-drop-indicator nph-table-drop-indicator--row", style: {
453
+ position: "fixed",
454
+ left: tableRect.left,
455
+ top: drag.toIndex < rowGrips.length
456
+ ? rowGrips[drag.toIndex].top - 1
457
+ : rowGrips[rowGrips.length - 1].top + rowGrips[rowGrips.length - 1].height,
458
+ width: tableRect.width,
459
+ height: 2,
460
+ } })))] }), document.body);
461
+ }
462
+ export default TableMenu;
@@ -0,0 +1,12 @@
1
+ import type { ComponentType } from "react";
2
+ import { type NodeViewProps } from "@tiptap/react";
3
+ export type TaskItemCheckboxProps = {
4
+ checked: boolean;
5
+ onCheckedChange: (checked: boolean) => void;
6
+ className?: string;
7
+ };
8
+ export type TaskItemCheckboxComponent = ComponentType<TaskItemCheckboxProps>;
9
+ export declare const createTaskItemView: (Checkbox: TaskItemCheckboxComponent) => {
10
+ ({ node, updateAttributes }: NodeViewProps): import("react/jsx-runtime").JSX.Element;
11
+ displayName: string;
12
+ };
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { NodeViewContent, NodeViewWrapper, } from "@tiptap/react";
3
+ export const createTaskItemView = (Checkbox) => {
4
+ const TaskItemView = ({ node, updateAttributes }) => {
5
+ const checked = !!node.attrs.checked;
6
+ return (_jsxs(NodeViewWrapper, { as: "li", "data-checked": checked, className: "nph-task-item", children: [_jsx("label", { contentEditable: false, className: "nph-task-item__checkbox", children: _jsx(Checkbox, { checked: checked, onCheckedChange: (value) => updateAttributes({ checked: value === true }) }) }), _jsx(NodeViewContent, { as: "div", className: "nph-task-item__content" +
7
+ (checked ? " nph-task-item__content--checked" : "") })] }));
8
+ };
9
+ TaskItemView.displayName = "TaskItemView";
10
+ return TaskItemView;
11
+ };
@@ -0,0 +1,10 @@
1
+ import type { Editor as TiptapEditor } from "@tiptap/react";
2
+ import type { ReactNode } from "react";
3
+ type ExtraRenderer = (editor: TiptapEditor) => ReactNode;
4
+ export type TextMenuProps = {
5
+ className?: string;
6
+ leadingExtras?: ExtraRenderer[];
7
+ trailingExtras?: ExtraRenderer[];
8
+ };
9
+ export declare function TextMenu({ className, leadingExtras, trailingExtras, }: TextMenuProps): import("react/jsx-runtime").JSX.Element | null;
10
+ export default TextMenu;
@@ -0,0 +1,139 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { NodeSelection } from "@tiptap/pm/state";
3
+ import { useCurrentEditor, useEditorState } from "@tiptap/react";
4
+ import { BubbleMenu } from "@tiptap/react/menus";
5
+ import { IconBold, IconItalic, IconUnderline, IconStrikethrough, IconCode, IconArrowBackUp, IconLink, IconCheck, IconX, } from "@tabler/icons-react";
6
+ import { Fragment, useState, useRef, useEffect } from "react";
7
+ import { MenuList } from "./MenuList";
8
+ function Separator() {
9
+ return _jsx("div", { className: "nph-bubble-separator" });
10
+ }
11
+ export function TextMenu({ className, leadingExtras, trailingExtras, }) {
12
+ const { editor } = useCurrentEditor();
13
+ const [isAddingLink, setIsAddingLink] = useState(false);
14
+ const [linkUrl, setLinkUrl] = useState("");
15
+ const linkInputRef = useRef(null);
16
+ const editorState = useEditorState({
17
+ editor,
18
+ selector: (ctx) => {
19
+ if (!ctx.editor)
20
+ return null;
21
+ return {
22
+ isBold: ctx.editor.isActive("bold"),
23
+ isItalic: ctx.editor.isActive("italic"),
24
+ isUnderline: ctx.editor.isActive("underline"),
25
+ isStrike: ctx.editor.isActive("strike"),
26
+ isCode: ctx.editor.isActive("code"),
27
+ isCodeBlock: ctx.editor.isActive("codeBlock"),
28
+ isParagraph: ctx.editor.isActive("paragraph"),
29
+ isHeading: ctx.editor.isActive("heading"),
30
+ isList: ctx.editor.isActive("bulletList") ||
31
+ ctx.editor.isActive("orderedList"),
32
+ isBlockquote: ctx.editor.isActive("blockquote"),
33
+ };
34
+ },
35
+ });
36
+ useEffect(() => {
37
+ if (isAddingLink && linkInputRef.current) {
38
+ linkInputRef.current.focus();
39
+ }
40
+ }, [isAddingLink]);
41
+ if (!editor || !editorState)
42
+ return null;
43
+ const handleSetLink = () => {
44
+ if (!linkUrl)
45
+ return;
46
+ editor.chain().focus().extendMarkRange("link").setLink({ href: linkUrl }).run();
47
+ setIsAddingLink(false);
48
+ setLinkUrl("");
49
+ };
50
+ const handleCancelLink = () => {
51
+ setIsAddingLink(false);
52
+ setLinkUrl("");
53
+ editor.chain().focus().run();
54
+ };
55
+ const hasAnyMarksInSelection = () => {
56
+ const anyEditor = editor;
57
+ const state = anyEditor.state;
58
+ const sel = state.selection;
59
+ let has = false;
60
+ state.doc.nodesBetween(sel.from, sel.to, (node) => {
61
+ if (node.isText && node.marks && node.marks.length > 0) {
62
+ has = true;
63
+ return false;
64
+ }
65
+ return undefined;
66
+ });
67
+ if (!has && sel.empty) {
68
+ const marks = state.storedMarks || sel.$from.marks();
69
+ if (marks && marks.length > 0)
70
+ has = true;
71
+ }
72
+ return has;
73
+ };
74
+ const isInCode = editorState.isCode || editorState.isCodeBlock;
75
+ return (_jsx(BubbleMenu, { editor: editor, pluginKey: "textBubbleMenu", shouldShow: ({ editor: e, state, from, to, view }) => {
76
+ // Don't show if imageBlock or videoBlock is active
77
+ if (e.isActive("imageBlock"))
78
+ return false;
79
+ if (e.isActive("videoBlock"))
80
+ return false;
81
+ // Don't show for node selections
82
+ const { selection } = state;
83
+ if (selection instanceof NodeSelection)
84
+ return false;
85
+ // Don't show if selection is empty
86
+ if (from === to)
87
+ return false;
88
+ // Don't show if link is active (LinkMenu will handle this)
89
+ if (e.isActive("link"))
90
+ return false;
91
+ // Check if the selection contains an imageBlock node
92
+ let hasImage = false;
93
+ state.doc.nodesBetween(from, to, (node) => {
94
+ if (node.type.name === "imageBlock" || node.type.name === "videoBlock") {
95
+ hasImage = true;
96
+ return false;
97
+ }
98
+ });
99
+ if (hasImage)
100
+ return false;
101
+ // Show only for text selections
102
+ return true;
103
+ }, children: _jsxs("div", { className: className ? `bubble-menu ${className}` : "bubble-menu", children: [leadingExtras && editor
104
+ ? leadingExtras.map((renderExtra, index) => (_jsx(Fragment, { children: renderExtra(editor) }, `leading-extra-${index}`)))
105
+ : null, _jsx(MenuList, { editor: editor }), _jsx(Separator, {}), _jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => editor.chain().focus().toggleBold().run(), className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isBold ? " is-active" : ""}`, disabled: isInCode, "aria-disabled": isInCode, "aria-pressed": editorState.isBold, "aria-label": "Toggle bold", children: _jsx(IconBold, { size: 16 }) }), _jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => editor.chain().focus().toggleItalic().run(), className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isItalic ? " is-active" : ""}`, disabled: isInCode, "aria-disabled": isInCode, "aria-pressed": editorState.isItalic, "aria-label": "Toggle italic", children: _jsx(IconItalic, { size: 16 }) }), _jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => editor.chain().focus().toggleUnderline().run(), className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isUnderline ? " is-active" : ""}`, disabled: isInCode, "aria-disabled": isInCode, "aria-pressed": editorState.isUnderline, "aria-label": "Toggle underline", children: _jsx(IconUnderline, { size: 16 }) }), _jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => editor.chain().focus().toggleStrike().run(), className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isStrike ? " is-active" : ""}`, disabled: isInCode, "aria-disabled": isInCode, "aria-pressed": editorState.isStrike, "aria-label": "Toggle strike", children: _jsx(IconStrikethrough, { size: 16 }) }), _jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => editor.chain().focus().toggleCode().run(), className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isCode ? " is-active" : ""}`, disabled: editorState.isCodeBlock, "aria-disabled": editorState.isCodeBlock, "aria-pressed": editorState.isCode, "aria-label": "Toggle inline code", children: _jsx(IconCode, { size: 16 }) }), _jsx(Separator, {}), !isAddingLink ? (_jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => setIsAddingLink(true), className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editor.isActive("link") ? " is-active" : ""}`, disabled: isInCode, "aria-disabled": isInCode, "aria-pressed": editor.isActive("link"), "aria-label": "Add link", children: _jsx(IconLink, { size: 16 }) })) : (_jsxs(_Fragment, { children: [_jsx("input", { ref: linkInputRef, type: "url", value: linkUrl, onChange: (e) => setLinkUrl(e.target.value), onKeyDown: (e) => {
106
+ if (e.key === "Enter") {
107
+ e.preventDefault();
108
+ handleSetLink();
109
+ }
110
+ else if (e.key === "Escape") {
111
+ e.preventDefault();
112
+ handleCancelLink();
113
+ }
114
+ }, placeholder: "https://example.com", className: "nph-link-input", style: {
115
+ padding: "4px 8px",
116
+ border: "1px solid #ccc",
117
+ borderRadius: "4px",
118
+ minWidth: "200px",
119
+ fontSize: "14px",
120
+ } }), _jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: handleSetLink, className: "nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon", "aria-label": "Apply", title: "Apply", disabled: !linkUrl, children: _jsx(IconCheck, { size: 16 }) }), _jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: handleCancelLink, className: "nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon", "aria-label": "Cancel", title: "Cancel", children: _jsx(IconX, { size: 16 }) })] })), (() => {
121
+ const hasInlineMarks = hasAnyMarksInSelection();
122
+ const isPlainParagraph = editorState.isParagraph &&
123
+ !editorState.isHeading &&
124
+ !editorState.isList &&
125
+ !editorState.isBlockquote &&
126
+ !editorState.isCodeBlock &&
127
+ !hasInlineMarks;
128
+ return (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => editor
129
+ .chain()
130
+ .focus()
131
+ .clearNodes()
132
+ .setParagraph()
133
+ .unsetAllMarks()
134
+ .run(), className: "nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon", "aria-label": "Revert to paragraph", title: "Revert to paragraph", disabled: isPlainParagraph, "aria-disabled": isPlainParagraph, children: _jsx(IconArrowBackUp, { size: 16 }) })] }));
135
+ })(), trailingExtras && editor
136
+ ? trailingExtras.map((renderExtra, index) => (_jsx(Fragment, { children: renderExtra(editor) }, `trailing-extra-${index}`)))
137
+ : null] }) }));
138
+ }
139
+ export default TextMenu;
@@ -0,0 +1,7 @@
1
+ import { Editor } from "@tiptap/react";
2
+ export type VideoBlockMenuProps = {
3
+ editor: Editor;
4
+ getPos: () => number;
5
+ };
6
+ export declare const VideoBlockMenu: ({ editor, getPos }: VideoBlockMenuProps) => import("react/jsx-runtime").JSX.Element | null;
7
+ export default VideoBlockMenu;