neuphlo-editor 2.5.0 → 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 (171) 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-4L7FHW5M.js → chunk-ABL4D65Z.js} +113 -47
  4. package/dist/chunk-ABL4D65Z.js.map +1 -0
  5. package/dist/{chunk-BVFTQKY7.js → chunk-Z2DR2FK3.js} +245 -50
  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 +77 -1
  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 +3 -1
  128. package/dist/chunk-2DWEJI45.js +0 -1296
  129. package/dist/chunk-2DWEJI45.js.map +0 -1
  130. package/dist/chunk-3CLWAYRP.js +0 -1387
  131. package/dist/chunk-3CLWAYRP.js.map +0 -1
  132. package/dist/chunk-457ETWB6.js +0 -1351
  133. package/dist/chunk-457ETWB6.js.map +0 -1
  134. package/dist/chunk-4L7FHW5M.js.map +0 -1
  135. package/dist/chunk-4MV3UUZN.js +0 -1073
  136. package/dist/chunk-4MV3UUZN.js.map +0 -1
  137. package/dist/chunk-62DYB7FY.js +0 -1305
  138. package/dist/chunk-62DYB7FY.js.map +0 -1
  139. package/dist/chunk-BVFTQKY7.js.map +0 -1
  140. package/dist/chunk-DWGPGRTQ.js +0 -1302
  141. package/dist/chunk-DWGPGRTQ.js.map +0 -1
  142. package/dist/chunk-EG7NQJRA.js +0 -1324
  143. package/dist/chunk-EG7NQJRA.js.map +0 -1
  144. package/dist/chunk-FLLPFFI5.js +0 -1296
  145. package/dist/chunk-FLLPFFI5.js.map +0 -1
  146. package/dist/chunk-FVQHB6VC.js +0 -1128
  147. package/dist/chunk-FVQHB6VC.js.map +0 -1
  148. package/dist/chunk-G5E32MLB.js +0 -1352
  149. package/dist/chunk-G5E32MLB.js.map +0 -1
  150. package/dist/chunk-GXJGZHKR.js +0 -1326
  151. package/dist/chunk-GXJGZHKR.js.map +0 -1
  152. package/dist/chunk-KCPPTLGY.js +0 -1299
  153. package/dist/chunk-KCPPTLGY.js.map +0 -1
  154. package/dist/chunk-LHG2NX6C.js +0 -1123
  155. package/dist/chunk-LHG2NX6C.js.map +0 -1
  156. package/dist/chunk-MSNTVITF.js +0 -1352
  157. package/dist/chunk-MSNTVITF.js.map +0 -1
  158. package/dist/chunk-N4RDYJ5Z.js +0 -1351
  159. package/dist/chunk-N4RDYJ5Z.js.map +0 -1
  160. package/dist/chunk-OCNM37WJ.js +0 -1289
  161. package/dist/chunk-OCNM37WJ.js.map +0 -1
  162. package/dist/chunk-P3YFYEUB.js +0 -1351
  163. package/dist/chunk-P3YFYEUB.js.map +0 -1
  164. package/dist/chunk-RW6QBMJB.js +0 -1300
  165. package/dist/chunk-RW6QBMJB.js.map +0 -1
  166. package/dist/chunk-SJQQVB3M.js +0 -1353
  167. package/dist/chunk-SJQQVB3M.js.map +0 -1
  168. package/dist/chunk-SOXTEP7H.js +0 -6705
  169. package/dist/chunk-SOXTEP7H.js.map +0 -1
  170. package/dist/chunk-VPI26I4P.js +0 -1350
  171. package/dist/chunk-VPI26I4P.js.map +0 -1
@@ -0,0 +1,7 @@
1
+ import type { Editor } from "@tiptap/react";
2
+ export type BlockActionMenuProps = {
3
+ editor: Editor;
4
+ onClose: () => void;
5
+ };
6
+ export declare function BlockActionMenu({ editor, onClose }: BlockActionMenuProps): import("react/jsx-runtime").JSX.Element;
7
+ export default BlockActionMenu;
@@ -0,0 +1,111 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { IconCopy, IconTrash, IconArrowUp, IconArrowDown, IconClipboard, } from "@tabler/icons-react";
3
+ import { useCallback } from "react";
4
+ export function BlockActionMenu({ editor, onClose }) {
5
+ const executeAndClose = useCallback((fn) => {
6
+ fn();
7
+ onClose();
8
+ }, [onClose]);
9
+ const handleDelete = useCallback(() => {
10
+ executeAndClose(() => {
11
+ editor.commands.deleteSelection();
12
+ });
13
+ }, [editor, executeAndClose]);
14
+ const handleDuplicate = useCallback(() => {
15
+ executeAndClose(() => {
16
+ const { state } = editor;
17
+ const { selection } = state;
18
+ const { $anchor } = selection;
19
+ // Find the top-level block
20
+ const depth = $anchor.depth > 0 ? 1 : 0;
21
+ const start = $anchor.start(depth);
22
+ const end = $anchor.end(depth);
23
+ const node = state.doc.nodeAt(start - 1);
24
+ if (node) {
25
+ const insertPos = end + 1;
26
+ editor
27
+ .chain()
28
+ .focus()
29
+ .insertContentAt(insertPos, node.toJSON())
30
+ .run();
31
+ }
32
+ });
33
+ }, [editor, executeAndClose]);
34
+ const handleMoveUp = useCallback(() => {
35
+ executeAndClose(() => {
36
+ const { state } = editor;
37
+ const { selection } = state;
38
+ const { $anchor } = selection;
39
+ const depth = $anchor.depth > 0 ? 1 : 0;
40
+ const blockStart = $anchor.start(depth) - 1;
41
+ if (blockStart <= 0)
42
+ return; // Already at top
43
+ const node = state.doc.nodeAt(blockStart);
44
+ if (!node)
45
+ return;
46
+ // Find previous sibling
47
+ const $pos = state.doc.resolve(blockStart);
48
+ const index = $pos.index($pos.depth);
49
+ if (index === 0)
50
+ return; // First child
51
+ const prevNode = $pos.node($pos.depth).child(index - 1);
52
+ const prevStart = blockStart - prevNode.nodeSize;
53
+ editor.chain().focus()
54
+ .command(({ tr }) => {
55
+ const currentSlice = state.doc.slice(blockStart, blockStart + node.nodeSize);
56
+ tr.delete(blockStart, blockStart + node.nodeSize);
57
+ tr.insert(prevStart, currentSlice.content);
58
+ return true;
59
+ })
60
+ .run();
61
+ });
62
+ }, [editor, executeAndClose]);
63
+ const handleMoveDown = useCallback(() => {
64
+ executeAndClose(() => {
65
+ const { state } = editor;
66
+ const { selection } = state;
67
+ const { $anchor } = selection;
68
+ const depth = $anchor.depth > 0 ? 1 : 0;
69
+ const blockStart = $anchor.start(depth) - 1;
70
+ const node = state.doc.nodeAt(blockStart);
71
+ if (!node)
72
+ return;
73
+ const blockEnd = blockStart + node.nodeSize;
74
+ // Check if there's a next sibling
75
+ const $pos = state.doc.resolve(blockStart);
76
+ const parent = $pos.node($pos.depth);
77
+ const index = $pos.index($pos.depth);
78
+ if (index >= parent.childCount - 1)
79
+ return; // Last child
80
+ const nextNode = parent.child(index + 1);
81
+ const nextEnd = blockEnd + nextNode.nodeSize;
82
+ editor.chain().focus()
83
+ .command(({ tr }) => {
84
+ const currentSlice = state.doc.slice(blockStart, blockEnd);
85
+ tr.delete(blockStart, blockEnd);
86
+ const insertPos = blockStart + nextNode.nodeSize;
87
+ tr.insert(insertPos, currentSlice.content);
88
+ return true;
89
+ })
90
+ .run();
91
+ });
92
+ }, [editor, executeAndClose]);
93
+ const handleCopyToClipboard = useCallback(() => {
94
+ executeAndClose(() => {
95
+ const { state } = editor;
96
+ const { selection } = state;
97
+ const { $anchor } = selection;
98
+ const depth = $anchor.depth > 0 ? 1 : 0;
99
+ const start = $anchor.start(depth) - 1;
100
+ const node = state.doc.nodeAt(start);
101
+ if (node) {
102
+ const text = node.textContent;
103
+ navigator.clipboard.writeText(text).catch(() => {
104
+ // Fallback: silently fail
105
+ });
106
+ }
107
+ });
108
+ }, [editor, executeAndClose]);
109
+ return (_jsx("div", { className: "nph-block-action-menu nph-command", children: _jsxs("div", { className: "nph-command__list", style: { maxHeight: "none" }, children: [_jsxs("button", { type: "button", className: "nph-command__item", onClick: handleDelete, onMouseDown: (e) => e.preventDefault(), children: [_jsx(IconTrash, { size: 16 }), _jsx("span", { children: "Delete" })] }), _jsxs("button", { type: "button", className: "nph-command__item", onClick: handleDuplicate, onMouseDown: (e) => e.preventDefault(), children: [_jsx(IconCopy, { size: 16 }), _jsx("span", { children: "Duplicate" })] }), _jsxs("button", { type: "button", className: "nph-command__item", onClick: handleCopyToClipboard, onMouseDown: (e) => e.preventDefault(), children: [_jsx(IconClipboard, { size: 16 }), _jsx("span", { children: "Copy to clipboard" })] }), _jsxs("button", { type: "button", className: "nph-command__item", onClick: handleMoveUp, onMouseDown: (e) => e.preventDefault(), children: [_jsx(IconArrowUp, { size: 16 }), _jsx("span", { children: "Move up" })] }), _jsxs("button", { type: "button", className: "nph-command__item", onClick: handleMoveDown, onMouseDown: (e) => e.preventDefault(), children: [_jsx(IconArrowDown, { size: 16 }), _jsx("span", { children: "Move down" })] })] }) }));
110
+ }
111
+ export default BlockActionMenu;
@@ -0,0 +1,7 @@
1
+ export declare function DragHandleMenu(): import("react/jsx-runtime").JSX.Element | null;
2
+ /**
3
+ * Creates the drag handle DOM element with + and grip buttons.
4
+ * Called by the DragHandle extension's render() option.
5
+ */
6
+ export declare function createDragHandleElement(onAddBlock: () => void, onGripClick: () => void): HTMLElement;
7
+ export default DragHandleMenu;
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCurrentEditor } from "@tiptap/react";
3
+ import { useCallback, useEffect, useRef, useState } from "react";
4
+ import { BlockActionMenu } from "./BlockActionMenu";
5
+ export function DragHandleMenu() {
6
+ const { editor } = useCurrentEditor();
7
+ const [hoveredNode, setHoveredNode] = useState(null);
8
+ const [showActions, setShowActions] = useState(false);
9
+ const menuRef = useRef(null);
10
+ useEffect(() => {
11
+ if (!editor)
12
+ return;
13
+ const handler = ({ node }) => {
14
+ setHoveredNode(node);
15
+ if (!node)
16
+ setShowActions(false);
17
+ };
18
+ // The drag handle extension calls onNodeChange
19
+ // We use the editor's storage to communicate
20
+ const ext = editor.extensionManager.extensions.find((e) => e.name === "dragHandle");
21
+ if (ext) {
22
+ const origOnNodeChange = ext.options.onNodeChange;
23
+ ext.options.onNodeChange = (opts) => {
24
+ origOnNodeChange?.(opts);
25
+ handler(opts);
26
+ };
27
+ }
28
+ return () => {
29
+ // Cleanup
30
+ };
31
+ }, [editor]);
32
+ const handleAddBlock = useCallback(() => {
33
+ if (!editor)
34
+ return;
35
+ // Get current position and insert a new paragraph after the current block
36
+ const { state } = editor;
37
+ const { selection } = state;
38
+ const pos = selection.$anchor.end();
39
+ editor
40
+ .chain()
41
+ .focus()
42
+ .insertContentAt(pos + 1, { type: "paragraph" })
43
+ .focus(pos + 2)
44
+ .run();
45
+ }, [editor]);
46
+ const handleGripClick = useCallback(() => {
47
+ setShowActions((prev) => !prev);
48
+ }, []);
49
+ // Close action menu on click outside
50
+ useEffect(() => {
51
+ if (!showActions)
52
+ return;
53
+ const handleClick = (e) => {
54
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
55
+ setShowActions(false);
56
+ }
57
+ };
58
+ document.addEventListener("pointerdown", handleClick);
59
+ return () => document.removeEventListener("pointerdown", handleClick);
60
+ }, [showActions]);
61
+ if (!editor)
62
+ return null;
63
+ return (_jsx("div", { ref: menuRef, style: { position: "relative" }, children: showActions && editor && (_jsx(BlockActionMenu, { editor: editor, onClose: () => setShowActions(false) })) }));
64
+ }
65
+ /**
66
+ * Creates the drag handle DOM element with + and grip buttons.
67
+ * Called by the DragHandle extension's render() option.
68
+ */
69
+ export function createDragHandleElement(onAddBlock, onGripClick) {
70
+ const container = document.createElement("div");
71
+ container.className = "nph-drag-handle";
72
+ const plusBtn = document.createElement("button");
73
+ plusBtn.className = "nph-drag-handle__btn";
74
+ plusBtn.type = "button";
75
+ plusBtn.setAttribute("aria-label", "Add block");
76
+ plusBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>`;
77
+ plusBtn.addEventListener("click", (e) => {
78
+ e.preventDefault();
79
+ e.stopPropagation();
80
+ onAddBlock();
81
+ });
82
+ const gripBtn = document.createElement("button");
83
+ gripBtn.className = "nph-drag-handle__btn nph-drag-handle__grip";
84
+ gripBtn.type = "button";
85
+ gripBtn.setAttribute("aria-label", "Drag to reorder");
86
+ gripBtn.setAttribute("draggable", "true");
87
+ gripBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="5" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="9" cy="19" r="1"/><circle cx="15" cy="5" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="19" r="1"/></svg>`;
88
+ gripBtn.addEventListener("click", (e) => {
89
+ e.preventDefault();
90
+ e.stopPropagation();
91
+ onGripClick();
92
+ });
93
+ container.appendChild(plusBtn);
94
+ container.appendChild(gripBtn);
95
+ return container;
96
+ }
97
+ export default DragHandleMenu;
@@ -0,0 +1,2 @@
1
+ export declare const ImageBlockLoading: () => import("react/jsx-runtime").JSX.Element;
2
+ export default ImageBlockLoading;
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { IconLoader2 } from "@tabler/icons-react";
3
+ export const ImageBlockLoading = () => {
4
+ return (_jsxs("div", { className: "nph-image-block-loading", children: [_jsx("div", { className: "nph-image-block-loading__overlay", children: _jsxs("div", { className: "nph-image-block-loading__content", children: [_jsx(IconLoader2, { size: 24, className: "nph-image-block-loading__spinner" }), _jsx("p", { className: "nph-image-block-loading__text", children: "Uploading image..." })] }) }), _jsx("div", { className: "nph-image-block-loading__placeholder" })] }));
5
+ };
6
+ export default ImageBlockLoading;
@@ -0,0 +1,8 @@
1
+ import { Editor } from "@tiptap/react";
2
+ export type ImageBlockMenuProps = {
3
+ editor: Editor;
4
+ getPos: () => number;
5
+ appendTo?: React.RefObject<HTMLElement | null>;
6
+ };
7
+ export declare const ImageBlockMenu: ({ editor, getPos, appendTo }: ImageBlockMenuProps) => import("react/jsx-runtime").JSX.Element | null;
8
+ export default ImageBlockMenu;
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { NodeSelection } from "@tiptap/pm/state";
3
+ import { useEditorState } from "@tiptap/react";
4
+ import { useCallback, useRef } from "react";
5
+ import { ImageBlockWidth } from "./ImageBlockWidth";
6
+ import { IconAlignLeft, IconAlignCenter, IconAlignRight, IconTrash, } from "@tabler/icons-react";
7
+ export const ImageBlockMenu = ({ editor, getPos, appendTo }) => {
8
+ const menuRef = useRef(null);
9
+ const { isVisible, align, width } = useEditorState({
10
+ editor,
11
+ selector: (ctx) => {
12
+ if (!ctx.editor)
13
+ return { isVisible: false, align: "center", width: 100 };
14
+ const { state } = ctx.editor;
15
+ const { selection } = state;
16
+ if (!ctx.editor.isEditable)
17
+ return { isVisible: false, align: "center", width: 100 };
18
+ if (!ctx.editor.isFocused)
19
+ return { isVisible: false, align: "center", width: 100 };
20
+ const isNodeSel = selection instanceof NodeSelection;
21
+ const isThisNode = isNodeSel && selection.from === getPos();
22
+ const visible = isThisNode;
23
+ let currentAlign = "center";
24
+ let currentWidth = 100;
25
+ if (visible) {
26
+ const attrs = ctx.editor.getAttributes("imageBlock");
27
+ currentAlign = attrs.align || "center";
28
+ const widthStr = attrs.width || "100%";
29
+ currentWidth = parseInt(widthStr) || 100;
30
+ }
31
+ return { isVisible: visible, align: currentAlign, width: currentWidth };
32
+ },
33
+ });
34
+ const onAlignImageLeft = useCallback(() => {
35
+ editor
36
+ .chain()
37
+ .focus(undefined, { scrollIntoView: false })
38
+ .setImageBlockAlign("left")
39
+ .run();
40
+ }, [editor]);
41
+ const onAlignImageCenter = useCallback(() => {
42
+ editor
43
+ .chain()
44
+ .focus(undefined, { scrollIntoView: false })
45
+ .setImageBlockAlign("center")
46
+ .run();
47
+ }, [editor]);
48
+ const onAlignImageRight = useCallback(() => {
49
+ editor
50
+ .chain()
51
+ .focus(undefined, { scrollIntoView: false })
52
+ .setImageBlockAlign("right")
53
+ .run();
54
+ }, [editor]);
55
+ const onWidthChange = useCallback((value) => {
56
+ editor
57
+ .chain()
58
+ .focus(undefined, { scrollIntoView: false })
59
+ .setImageBlockWidth(value)
60
+ .run();
61
+ }, [editor]);
62
+ const onRemoveImage = useCallback(() => {
63
+ editor.chain().focus(undefined, { scrollIntoView: false }).deleteSelection().run();
64
+ }, [editor]);
65
+ if (!isVisible)
66
+ return null;
67
+ return (_jsxs("div", { className: "bubble-menu", ref: menuRef, style: {
68
+ position: "absolute",
69
+ top: "-40px",
70
+ left: "50%",
71
+ transform: "translateX(-50%)",
72
+ zIndex: "var(--nph-z, 50)",
73
+ }, children: [_jsx("button", { type: "button", className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${align === "left" ? " is-active" : ""}`, title: "Align left", onMouseDown: (e) => e.preventDefault(), onClick: onAlignImageLeft, children: _jsx(IconAlignLeft, { size: 16 }) }), _jsx("button", { type: "button", className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${align === "center" ? " is-active" : ""}`, title: "Align center", onMouseDown: (e) => e.preventDefault(), onClick: onAlignImageCenter, children: _jsx(IconAlignCenter, { size: 16 }) }), _jsx("button", { type: "button", className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${align === "right" ? " is-active" : ""}`, title: "Align right", onMouseDown: (e) => e.preventDefault(), onClick: onAlignImageRight, children: _jsx(IconAlignRight, { size: 16 }) }), _jsx("div", { className: "nph-link-popover__divider", style: { margin: "0 4px" } }), _jsx(ImageBlockWidth, { onChange: onWidthChange, value: width }), _jsx("div", { className: "nph-link-popover__divider", style: { margin: "0 4px" } }), _jsx("button", { type: "button", className: "nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon", title: "Remove image", onMouseDown: (e) => e.preventDefault(), onClick: onRemoveImage, children: _jsx(IconTrash, { size: 16 }) })] }));
74
+ };
75
+ export default ImageBlockMenu;
@@ -0,0 +1,10 @@
1
+ import { Node } from "@tiptap/pm/model";
2
+ import { Editor } from "@tiptap/react";
3
+ interface ImageBlockViewProps {
4
+ editor: Editor;
5
+ getPos: () => number;
6
+ node: Node;
7
+ updateAttributes: (attrs: Record<string, any>) => void;
8
+ }
9
+ export declare const ImageBlockView: (props: ImageBlockViewProps) => import("react/jsx-runtime").JSX.Element;
10
+ export default ImageBlockView;
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { NodeViewWrapper } from "@tiptap/react";
3
+ import { useCallback, useRef, useState } from "react";
4
+ import { IconPhotoOff } from "@tabler/icons-react";
5
+ import { ImageBlockMenu } from "./ImageBlockMenu";
6
+ import { ImageUploader } from "./ImageUploader";
7
+ import { ImageBlockLoading } from "./ImageBlockLoading";
8
+ import { ImageResizeHandle } from "./ImageResizeHandle";
9
+ export const ImageBlockView = (props) => {
10
+ const { editor, getPos, node, updateAttributes } = props;
11
+ const imageWrapperRef = useRef(null);
12
+ const [imageError, setImageError] = useState(false);
13
+ const { src, width, align, alt, loading } = node.attrs;
14
+ const handleUpload = useCallback((url) => {
15
+ updateAttributes({ src: url, loading: false });
16
+ }, [updateAttributes]);
17
+ const onClick = useCallback(() => {
18
+ editor.commands.setNodeSelection(getPos());
19
+ }, [getPos, editor.commands]);
20
+ const handleResize = useCallback((widthPercent) => {
21
+ updateAttributes({ width: `${widthPercent}%` });
22
+ }, [updateAttributes]);
23
+ // Calculate wrapper style based on alignment and width
24
+ const getWrapperStyle = () => {
25
+ const baseStyle = {
26
+ width: width || "100%",
27
+ maxWidth: "100%",
28
+ };
29
+ if (align === "left") {
30
+ return { ...baseStyle, marginLeft: 0, marginRight: "auto" };
31
+ }
32
+ else if (align === "right") {
33
+ return { ...baseStyle, marginLeft: "auto", marginRight: 0 };
34
+ }
35
+ else {
36
+ return { ...baseStyle, marginLeft: "auto", marginRight: "auto" };
37
+ }
38
+ };
39
+ // The inner content wrapper for positioning the menu
40
+ const getContentStyle = () => ({
41
+ position: "relative",
42
+ });
43
+ // Show uploader if no src (only when editable)
44
+ if (!src || src === "") {
45
+ if (!editor.isEditable)
46
+ return _jsx(NodeViewWrapper, {});
47
+ return (_jsx(NodeViewWrapper, { style: { width: "100%", marginTop: "0.5rem", marginBottom: "0.5rem" }, children: _jsx("div", { ref: imageWrapperRef, children: _jsx(ImageUploader, { onUpload: handleUpload, editor: editor }) }) }));
48
+ }
49
+ // Show loading placeholder
50
+ if (loading) {
51
+ return (_jsx(NodeViewWrapper, { style: getWrapperStyle(), children: _jsx("div", { ref: imageWrapperRef, children: _jsx(ImageBlockLoading, {}) }) }));
52
+ }
53
+ // Show error state for broken images
54
+ if (imageError) {
55
+ return (_jsx(NodeViewWrapper, { style: getWrapperStyle(), children: _jsxs("div", { contentEditable: false, ref: imageWrapperRef, style: getContentStyle(), children: [_jsxs("div", { className: "nph-image-block-error", onClick: onClick, children: [_jsx(IconPhotoOff, { size: 32 }), _jsx("span", { children: "Image could not be loaded" })] }), editor.isEditable && _jsx(ImageBlockMenu, { editor: editor, getPos: getPos, appendTo: imageWrapperRef })] }) }));
56
+ }
57
+ // Show the actual image
58
+ return (_jsx(NodeViewWrapper, { style: getWrapperStyle(), children: _jsxs("div", { contentEditable: false, ref: imageWrapperRef, style: getContentStyle(), children: [editor.isEditable ? (_jsx(ImageResizeHandle, { onResize: handleResize, currentWidth: width, children: _jsx("img", { src: src, alt: alt || "", onClick: onClick, onError: () => setImageError(true), className: "nph-image-block" }) })) : (_jsx("img", { src: src, alt: alt || "", onError: () => setImageError(true), className: "nph-image-block" })), editor.isEditable && _jsx(ImageBlockMenu, { editor: editor, getPos: getPos, appendTo: imageWrapperRef })] }) }));
59
+ };
60
+ export default ImageBlockView;
@@ -0,0 +1,6 @@
1
+ export type ImageBlockWidthProps = {
2
+ onChange: (value: number) => void;
3
+ value: number;
4
+ };
5
+ export declare const ImageBlockWidth: import("react").MemoExoticComponent<({ onChange, value }: ImageBlockWidthProps) => import("react/jsx-runtime").JSX.Element>;
6
+ export default ImageBlockWidth;
@@ -0,0 +1,29 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo, useCallback, useEffect, useState } from "react";
3
+ export const ImageBlockWidth = memo(({ onChange, value }) => {
4
+ const [currentValue, setCurrentValue] = useState(value);
5
+ useEffect(() => {
6
+ setCurrentValue(value);
7
+ }, [value]);
8
+ const handleChange = useCallback((e) => {
9
+ const nextValue = parseInt(e.target.value);
10
+ onChange(nextValue);
11
+ setCurrentValue(nextValue);
12
+ }, [onChange]);
13
+ return (_jsxs("div", { style: {
14
+ display: "flex",
15
+ alignItems: "center",
16
+ gap: 8,
17
+ minWidth: 140,
18
+ }, children: [_jsxs("span", { style: { fontSize: "12px", whiteSpace: "nowrap", width: 40 }, children: [currentValue, "%"] }), _jsx("input", { type: "range", min: "25", max: "100", step: "25", value: currentValue, onChange: handleChange, onMouseDown: (e) => e.stopPropagation(), style: {
19
+ flex: 1,
20
+ height: 3,
21
+ borderRadius: 9999,
22
+ appearance: "none",
23
+ background: "linear-gradient(to right, #333 0%, #333 " + currentValue + "%, #ddd " + currentValue + "%, #ddd 100%)",
24
+ outline: "none",
25
+ cursor: "pointer",
26
+ }, className: "nph-image-width-slider" })] }));
27
+ });
28
+ ImageBlockWidth.displayName = "ImageBlockWidth";
29
+ export default ImageBlockWidth;
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from "react";
2
+ export type ImageResizeHandleProps = {
3
+ children: ReactNode;
4
+ onResize: (widthPercent: number) => void;
5
+ currentWidth?: string;
6
+ };
7
+ export declare function ImageResizeHandle({ children, onResize, currentWidth, }: ImageResizeHandleProps): import("react/jsx-runtime").JSX.Element;
8
+ export default ImageResizeHandle;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useRef, useState } from "react";
3
+ export function ImageResizeHandle({ children, onResize, currentWidth, }) {
4
+ const containerRef = useRef(null);
5
+ const [isResizing, setIsResizing] = useState(false);
6
+ const handleMouseDown = useCallback((e, side) => {
7
+ e.preventDefault();
8
+ e.stopPropagation();
9
+ setIsResizing(true);
10
+ const startX = e.clientX;
11
+ const containerEl = containerRef.current;
12
+ if (!containerEl)
13
+ return;
14
+ const editorEl = containerEl.closest(".nph-editor") || containerEl.parentElement;
15
+ if (!editorEl)
16
+ return;
17
+ const editorWidth = editorEl.getBoundingClientRect().width;
18
+ const startWidth = containerEl.getBoundingClientRect().width;
19
+ const startPercent = (startWidth / editorWidth) * 100;
20
+ const handleMouseMove = (moveEvent) => {
21
+ const deltaX = moveEvent.clientX - startX;
22
+ const direction = side === "right" ? 1 : -1;
23
+ const deltaPercent = (deltaX * direction / editorWidth) * 100 * 2; // *2 because resizing from center
24
+ const newPercent = Math.max(10, Math.min(100, startPercent + deltaPercent));
25
+ onResize(Math.round(newPercent));
26
+ };
27
+ const handleMouseUp = () => {
28
+ setIsResizing(false);
29
+ document.removeEventListener("mousemove", handleMouseMove);
30
+ document.removeEventListener("mouseup", handleMouseUp);
31
+ };
32
+ document.addEventListener("mousemove", handleMouseMove);
33
+ document.addEventListener("mouseup", handleMouseUp);
34
+ }, [onResize]);
35
+ return (_jsxs("div", { ref: containerRef, className: `nph-resize-wrapper${isResizing ? " nph-resize-wrapper--active" : ""}`, children: [children, _jsx("div", { className: "nph-resize-handle nph-resize-handle--left", onMouseDown: (e) => handleMouseDown(e, "left") }), _jsx("div", { className: "nph-resize-handle nph-resize-handle--right", onMouseDown: (e) => handleMouseDown(e, "right") })] }));
36
+ }
37
+ export default ImageResizeHandle;
@@ -0,0 +1,7 @@
1
+ import { Editor } from "@tiptap/react";
2
+ export type ImageUploaderProps = {
3
+ onUpload: (url: string) => void;
4
+ editor: Editor;
5
+ };
6
+ export declare const ImageUploader: ({ onUpload, editor }: ImageUploaderProps) => import("react/jsx-runtime").JSX.Element;
7
+ export default ImageUploader;
@@ -0,0 +1,73 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { IconPhoto, IconUpload } from "@tabler/icons-react";
3
+ import { useCallback, useRef, useState } from "react";
4
+ import { ImageBlockLoading } from "./ImageBlockLoading";
5
+ export const ImageUploader = ({ onUpload, editor }) => {
6
+ const [loading, setLoading] = useState(false);
7
+ const [draggedInside, setDraggedInside] = useState(false);
8
+ const fileInputRef = useRef(null);
9
+ const imageExtension = editor.extensionManager.extensions.find((ext) => ext.name === "imageBlock");
10
+ const browseAssets = imageExtension?.options?.browseAssets;
11
+ const uploadFile = useCallback(async (file) => {
12
+ setLoading(true);
13
+ try {
14
+ const uploadImage = imageExtension?.options?.uploadImage;
15
+ if (uploadImage) {
16
+ const url = await uploadImage(file);
17
+ onUpload(url);
18
+ }
19
+ else {
20
+ console.error("No uploadImage handler provided");
21
+ }
22
+ }
23
+ catch (error) {
24
+ console.error("Failed to upload image:", error);
25
+ }
26
+ finally {
27
+ setLoading(false);
28
+ }
29
+ }, [imageExtension, onUpload]);
30
+ const handleUploadClick = useCallback(() => {
31
+ fileInputRef.current?.click();
32
+ }, []);
33
+ const onFileChange = useCallback((e) => {
34
+ const file = e.target.files?.[0];
35
+ if (file) {
36
+ uploadFile(file);
37
+ }
38
+ }, [uploadFile]);
39
+ const onDrop = useCallback((e) => {
40
+ e.preventDefault();
41
+ e.stopPropagation();
42
+ setDraggedInside(false);
43
+ const file = e.dataTransfer.files[0];
44
+ if (file && /image/i.test(file.type)) {
45
+ uploadFile(file);
46
+ }
47
+ }, [uploadFile]);
48
+ const onDragEnter = useCallback((e) => {
49
+ e.preventDefault();
50
+ e.stopPropagation();
51
+ setDraggedInside(true);
52
+ }, []);
53
+ const onDragLeave = useCallback((e) => {
54
+ e.preventDefault();
55
+ e.stopPropagation();
56
+ setDraggedInside(false);
57
+ }, []);
58
+ if (loading) {
59
+ return _jsx(ImageBlockLoading, {});
60
+ }
61
+ // When browseAssets is provided, show a clean asset browser CTA
62
+ if (browseAssets) {
63
+ return (_jsx("div", { className: "nph-image-uploader nph-image-uploader--browse-only", contentEditable: false, onClick: () => browseAssets(onUpload), role: "button", tabIndex: 0, onKeyDown: (e) => {
64
+ if (e.key === "Enter" || e.key === " ") {
65
+ e.preventDefault();
66
+ browseAssets(onUpload);
67
+ }
68
+ }, children: _jsxs("div", { className: "nph-image-uploader__browse-cta", children: [_jsx("div", { className: "nph-image-uploader__browse-icon-wrapper", children: _jsx(IconPhoto, { size: 28 }) }), _jsxs("div", { className: "nph-image-uploader__browse-text", children: [_jsx("span", { className: "nph-image-uploader__browse-title", children: "Choose from assets" }), _jsx("span", { className: "nph-image-uploader__browse-subtitle", children: "Select an image from your library" })] })] }) }));
69
+ }
70
+ // Fallback: standard drag-and-drop / upload flow
71
+ return (_jsxs("div", { className: `nph-image-uploader${draggedInside ? " nph-image-uploader--dragging" : ""}`, onDrop: onDrop, onDragOver: onDragEnter, onDragLeave: onDragLeave, contentEditable: false, children: [_jsx(IconPhoto, { size: 48, className: "nph-image-uploader__icon" }), _jsxs("div", { className: "nph-image-uploader__content", children: [_jsx("div", { className: "nph-image-uploader__text", children: draggedInside ? "Drop image here" : "Drag and drop or" }), _jsx("div", { className: "nph-image-uploader__actions", children: _jsxs("button", { type: "button", disabled: draggedInside, onClick: handleUploadClick, className: "nph-btn nph-btn-ghost nph-btn-sm nph-image-uploader__button", children: [_jsx(IconUpload, { size: 16 }), "Upload an image"] }) })] }), _jsx("input", { ref: fileInputRef, type: "file", accept: ".jpg,.jpeg,.png,.webp,.gif", onChange: onFileChange, className: "nph-image-uploader__input" })] }));
72
+ };
73
+ export default ImageUploader;
@@ -0,0 +1,4 @@
1
+ export { ImageBlockView } from "./ImageBlockView";
2
+ export { ImageBlockMenu } from "./ImageBlockMenu";
3
+ export { ImageBlockWidth } from "./ImageBlockWidth";
4
+ export { ImageUploader } from "./ImageUploader";
@@ -0,0 +1,4 @@
1
+ export { ImageBlockView } from "./ImageBlockView";
2
+ export { ImageBlockMenu } from "./ImageBlockMenu";
3
+ export { ImageBlockWidth } from "./ImageBlockWidth";
4
+ export { ImageUploader } from "./ImageUploader";
@@ -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 ImageMenuProps = {
5
+ className?: string;
6
+ leadingExtras?: ExtraRenderer[];
7
+ trailingExtras?: ExtraRenderer[];
8
+ };
9
+ export declare function ImageMenu({ className, leadingExtras, trailingExtras, }: ImageMenuProps): import("react/jsx-runtime").JSX.Element | null;
10
+ export default ImageMenu;