neuphlo-editor 1.8.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/chunk-2DWEJI45.js +1296 -0
  2. package/dist/chunk-2DWEJI45.js.map +1 -0
  3. package/dist/chunk-457ETWB6.js +1351 -0
  4. package/dist/chunk-457ETWB6.js.map +1 -0
  5. package/dist/chunk-62DYB7FY.js +1305 -0
  6. package/dist/chunk-62DYB7FY.js.map +1 -0
  7. package/dist/chunk-DWGPGRTQ.js +1302 -0
  8. package/dist/chunk-DWGPGRTQ.js.map +1 -0
  9. package/dist/chunk-EG7NQJRA.js +1324 -0
  10. package/dist/chunk-EG7NQJRA.js.map +1 -0
  11. package/dist/chunk-FLLPFFI5.js +1296 -0
  12. package/dist/chunk-FLLPFFI5.js.map +1 -0
  13. package/dist/chunk-FVQHB6VC.js +1128 -0
  14. package/dist/chunk-FVQHB6VC.js.map +1 -0
  15. package/dist/chunk-GXJGZHKR.js +1326 -0
  16. package/dist/chunk-GXJGZHKR.js.map +1 -0
  17. package/dist/chunk-KCPPTLGY.js +1299 -0
  18. package/dist/chunk-KCPPTLGY.js.map +1 -0
  19. package/dist/chunk-LHG2NX6C.js +1123 -0
  20. package/dist/chunk-LHG2NX6C.js.map +1 -0
  21. package/dist/chunk-OCNM37WJ.js +1289 -0
  22. package/dist/chunk-OCNM37WJ.js.map +1 -0
  23. package/dist/chunk-RW6QBMJB.js +1300 -0
  24. package/dist/chunk-RW6QBMJB.js.map +1 -0
  25. package/dist/chunk-SOXTEP7H.js +6705 -0
  26. package/dist/chunk-SOXTEP7H.js.map +1 -0
  27. package/dist/headless/index.cjs +207 -144
  28. package/dist/headless/index.cjs.map +1 -1
  29. package/dist/headless/index.d.cts +9 -25
  30. package/dist/headless/index.d.ts +9 -25
  31. package/dist/headless/index.js +1 -1
  32. package/dist/react/index.cjs +1839 -723
  33. package/dist/react/index.cjs.map +1 -1
  34. package/dist/react/index.css +364 -8
  35. package/dist/react/index.css.map +1 -1
  36. package/dist/react/index.d.cts +10 -2
  37. package/dist/react/index.d.ts +10 -2
  38. package/dist/react/index.js +1434 -547
  39. package/dist/react/index.js.map +1 -1
  40. package/dist/styles.css +410 -8
  41. package/package.json +7 -2
@@ -1,23 +1,30 @@
1
1
  import {
2
2
  CodeBlock,
3
3
  Command,
4
+ DragHandle,
4
5
  EditorCommand,
5
- EditorCommandEmpty,
6
6
  EditorCommandItem,
7
7
  EditorCommandList,
8
8
  EditorContent,
9
9
  EditorRoot,
10
10
  ImageBlock,
11
11
  Link,
12
+ MarkdownPaste,
12
13
  Placeholder,
13
14
  StarterKit,
15
+ TableKit,
14
16
  createMentionExtension,
15
17
  handleCommandNavigation,
16
- renderItems
17
- } from "../chunk-4MV3UUZN.js";
18
+ novelStore,
19
+ queryAtom,
20
+ setDragHandleCallbacks,
21
+ store_exports
22
+ } from "../chunk-457ETWB6.js";
18
23
 
19
24
  // src/headless/extensions/extension-kit.ts
20
25
  import Collaboration from "@tiptap/extension-collaboration";
26
+ import CollaborationCaret from "@tiptap/extension-collaboration-caret";
27
+ import Underline from "@tiptap/extension-underline";
21
28
 
22
29
  // src/headless/extensions/VideoBlock/VideoBlock.ts
23
30
  import { mergeAttributes, Node } from "@tiptap/core";
@@ -100,6 +107,7 @@ var ExtensionKit = (options) => {
100
107
  StarterKit.configure({ codeBlock: false, link: false }),
101
108
  CodeBlock,
102
109
  Link,
110
+ Underline,
103
111
  ImageBlock.configure({
104
112
  uploadImage: options?.uploadImage,
105
113
  nodeView: options?.imageBlockView
@@ -118,14 +126,29 @@ var ExtensionKit = (options) => {
118
126
  return enableSlashCommand ? "Press '/' for commands" : "";
119
127
  },
120
128
  includeChildren: true
121
- })
129
+ }),
130
+ MarkdownPaste
122
131
  ];
132
+ if (options?.table !== false) {
133
+ extensions.push(
134
+ TableKit.configure({
135
+ resizable: true,
136
+ lastColumnResizable: true,
137
+ allowTableNodeSelection: true
138
+ })
139
+ );
140
+ }
141
+ if (options?.dragHandle !== false) {
142
+ extensions.push(DragHandle);
143
+ if (options?.dragHandleCallbacks) {
144
+ setDragHandleCallbacks(options.dragHandleCallbacks);
145
+ }
146
+ }
123
147
  if (enableSlashCommand) {
124
148
  extensions.push(
125
149
  Command.configure({
126
150
  suggestion: {
127
151
  char: "/",
128
- render: renderItems,
129
152
  allowSpaces: true,
130
153
  allowedPrefixes: null
131
154
  }
@@ -151,18 +174,45 @@ var ExtensionKit = (options) => {
151
174
  field: options.collaboration.field
152
175
  })
153
176
  );
177
+ if (options.collaboration.provider && options.collaboration.user) {
178
+ extensions.push(
179
+ CollaborationCaret.configure({
180
+ provider: options.collaboration.provider,
181
+ user: options.collaboration.user,
182
+ render: (user) => {
183
+ const cursor = document.createElement("span");
184
+ cursor.classList.add("nph-collab-caret");
185
+ cursor.setAttribute("style", `border-color: ${user.color || "#3b82f6"}`);
186
+ const label = document.createElement("div");
187
+ label.classList.add("nph-collab-caret__label");
188
+ label.setAttribute("style", `background-color: ${user.color || "#3b82f6"}`);
189
+ label.insertBefore(document.createTextNode(user.name || "Anonymous"), null);
190
+ cursor.insertBefore(label, null);
191
+ return cursor;
192
+ },
193
+ selectionRender: (user) => ({
194
+ nodeName: "span",
195
+ class: "nph-collab-selection",
196
+ style: `background-color: ${user.color || "#3b82f6"}20`
197
+ })
198
+ })
199
+ );
200
+ }
154
201
  }
155
202
  return extensions;
156
203
  };
157
204
  var extension_kit_default = ExtensionKit;
158
205
 
159
206
  // src/react/menus/TextMenu.tsx
207
+ import { NodeSelection } from "@tiptap/pm/state";
160
208
  import { useCurrentEditor, useEditorState } from "@tiptap/react";
161
209
  import { BubbleMenu } from "@tiptap/react/menus";
162
210
  import {
163
211
  IconBold,
164
212
  IconItalic,
213
+ IconUnderline,
165
214
  IconStrikethrough,
215
+ IconCode as IconCode2,
166
216
  IconArrowBackUp,
167
217
  IconLink,
168
218
  IconCheck,
@@ -435,6 +485,9 @@ function MenuList({
435
485
 
436
486
  // src/react/menus/TextMenu.tsx
437
487
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
488
+ function Separator() {
489
+ return /* @__PURE__ */ jsx2("div", { className: "nph-bubble-separator" });
490
+ }
438
491
  function TextMenu({
439
492
  className,
440
493
  leadingExtras,
@@ -451,6 +504,7 @@ function TextMenu({
451
504
  return {
452
505
  isBold: ctx.editor.isActive("bold"),
453
506
  isItalic: ctx.editor.isActive("italic"),
507
+ isUnderline: ctx.editor.isActive("underline"),
454
508
  isStrike: ctx.editor.isActive("strike"),
455
509
  isCode: ctx.editor.isActive("code"),
456
510
  isCodeBlock: ctx.editor.isActive("codeBlock"),
@@ -496,6 +550,7 @@ function TextMenu({
496
550
  }
497
551
  return has;
498
552
  };
553
+ const isInCode = editorState.isCode || editorState.isCodeBlock;
499
554
  return /* @__PURE__ */ jsx2(
500
555
  BubbleMenu,
501
556
  {
@@ -504,7 +559,7 @@ function TextMenu({
504
559
  if (e.isActive("imageBlock")) return false;
505
560
  if (e.isActive("videoBlock")) return false;
506
561
  const { selection } = state;
507
- if (selection.constructor.name === "NodeSelection") return false;
562
+ if (selection instanceof NodeSelection) return false;
508
563
  if (from === to) return false;
509
564
  if (e.isActive("link")) return false;
510
565
  let hasImage = false;
@@ -520,6 +575,7 @@ function TextMenu({
520
575
  children: /* @__PURE__ */ jsxs2("div", { className: className ? `bubble-menu ${className}` : "bubble-menu", children: [
521
576
  leadingExtras && editor ? leadingExtras.map((renderExtra, index) => /* @__PURE__ */ jsx2(Fragment, { children: renderExtra(editor) }, `leading-extra-${index}`)) : null,
522
577
  /* @__PURE__ */ jsx2(MenuList, { editor }),
578
+ /* @__PURE__ */ jsx2(Separator, {}),
523
579
  /* @__PURE__ */ jsx2(
524
580
  "button",
525
581
  {
@@ -527,8 +583,8 @@ function TextMenu({
527
583
  onMouseDown: (e) => e.preventDefault(),
528
584
  onClick: () => editor.chain().focus().toggleBold().run(),
529
585
  className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isBold ? " is-active" : ""}`,
530
- disabled: editorState.isCode || editorState.isCodeBlock,
531
- "aria-disabled": editorState.isCode || editorState.isCodeBlock,
586
+ disabled: isInCode,
587
+ "aria-disabled": isInCode,
532
588
  "aria-pressed": editorState.isBold,
533
589
  "aria-label": "Toggle bold",
534
590
  children: /* @__PURE__ */ jsx2(IconBold, { size: 16 })
@@ -541,13 +597,27 @@ function TextMenu({
541
597
  onMouseDown: (e) => e.preventDefault(),
542
598
  onClick: () => editor.chain().focus().toggleItalic().run(),
543
599
  className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isItalic ? " is-active" : ""}`,
544
- disabled: editorState.isCode || editorState.isCodeBlock,
545
- "aria-disabled": editorState.isCode || editorState.isCodeBlock,
600
+ disabled: isInCode,
601
+ "aria-disabled": isInCode,
546
602
  "aria-pressed": editorState.isItalic,
547
603
  "aria-label": "Toggle italic",
548
604
  children: /* @__PURE__ */ jsx2(IconItalic, { size: 16 })
549
605
  }
550
606
  ),
607
+ /* @__PURE__ */ jsx2(
608
+ "button",
609
+ {
610
+ type: "button",
611
+ onMouseDown: (e) => e.preventDefault(),
612
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
613
+ className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isUnderline ? " is-active" : ""}`,
614
+ disabled: isInCode,
615
+ "aria-disabled": isInCode,
616
+ "aria-pressed": editorState.isUnderline,
617
+ "aria-label": "Toggle underline",
618
+ children: /* @__PURE__ */ jsx2(IconUnderline, { size: 16 })
619
+ }
620
+ ),
551
621
  /* @__PURE__ */ jsx2(
552
622
  "button",
553
623
  {
@@ -555,13 +625,28 @@ function TextMenu({
555
625
  onMouseDown: (e) => e.preventDefault(),
556
626
  onClick: () => editor.chain().focus().toggleStrike().run(),
557
627
  className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isStrike ? " is-active" : ""}`,
558
- disabled: editorState.isCode || editorState.isCodeBlock,
559
- "aria-disabled": editorState.isCode || editorState.isCodeBlock,
628
+ disabled: isInCode,
629
+ "aria-disabled": isInCode,
560
630
  "aria-pressed": editorState.isStrike,
561
631
  "aria-label": "Toggle strike",
562
632
  children: /* @__PURE__ */ jsx2(IconStrikethrough, { size: 16 })
563
633
  }
564
634
  ),
635
+ /* @__PURE__ */ jsx2(
636
+ "button",
637
+ {
638
+ type: "button",
639
+ onMouseDown: (e) => e.preventDefault(),
640
+ onClick: () => editor.chain().focus().toggleCode().run(),
641
+ className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isCode ? " is-active" : ""}`,
642
+ disabled: editorState.isCodeBlock,
643
+ "aria-disabled": editorState.isCodeBlock,
644
+ "aria-pressed": editorState.isCode,
645
+ "aria-label": "Toggle inline code",
646
+ children: /* @__PURE__ */ jsx2(IconCode2, { size: 16 })
647
+ }
648
+ ),
649
+ /* @__PURE__ */ jsx2(Separator, {}),
565
650
  !isAddingLink ? /* @__PURE__ */ jsx2(
566
651
  "button",
567
652
  {
@@ -569,8 +654,8 @@ function TextMenu({
569
654
  onMouseDown: (e) => e.preventDefault(),
570
655
  onClick: () => setIsAddingLink(true),
571
656
  className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editor.isActive("link") ? " is-active" : ""}`,
572
- disabled: editorState.isCode || editorState.isCodeBlock,
573
- "aria-disabled": editorState.isCode || editorState.isCodeBlock,
657
+ disabled: isInCode,
658
+ "aria-disabled": isInCode,
574
659
  "aria-pressed": editor.isActive("link"),
575
660
  "aria-label": "Add link",
576
661
  children: /* @__PURE__ */ jsx2(IconLink, { size: 16 })
@@ -632,20 +717,23 @@ function TextMenu({
632
717
  (() => {
633
718
  const hasInlineMarks = hasAnyMarksInSelection();
634
719
  const isPlainParagraph = editorState.isParagraph && !editorState.isHeading && !editorState.isList && !editorState.isBlockquote && !editorState.isCodeBlock && !hasInlineMarks;
635
- return /* @__PURE__ */ jsx2(
636
- "button",
637
- {
638
- type: "button",
639
- onMouseDown: (e) => e.preventDefault(),
640
- onClick: () => editor.chain().focus().clearNodes().setParagraph().unsetAllMarks().run(),
641
- className: "nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon",
642
- "aria-label": "Revert to paragraph",
643
- title: "Revert to paragraph",
644
- disabled: isPlainParagraph,
645
- "aria-disabled": isPlainParagraph,
646
- children: /* @__PURE__ */ jsx2(IconArrowBackUp, { size: 16 })
647
- }
648
- );
720
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
721
+ /* @__PURE__ */ jsx2(Separator, {}),
722
+ /* @__PURE__ */ jsx2(
723
+ "button",
724
+ {
725
+ type: "button",
726
+ onMouseDown: (e) => e.preventDefault(),
727
+ onClick: () => editor.chain().focus().clearNodes().setParagraph().unsetAllMarks().run(),
728
+ className: "nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon",
729
+ "aria-label": "Revert to paragraph",
730
+ title: "Revert to paragraph",
731
+ disabled: isPlainParagraph,
732
+ "aria-disabled": isPlainParagraph,
733
+ children: /* @__PURE__ */ jsx2(IconArrowBackUp, { size: 16 })
734
+ }
735
+ )
736
+ ] });
649
737
  })(),
650
738
  trailingExtras && editor ? trailingExtras.map((renderExtra, index) => /* @__PURE__ */ jsx2(Fragment, { children: renderExtra(editor) }, `trailing-extra-${index}`)) : null
651
739
  ] })
@@ -655,10 +743,9 @@ function TextMenu({
655
743
 
656
744
  // src/react/menus/SlashMenu.tsx
657
745
  import { useCurrentEditor as useCurrentEditor2 } from "@tiptap/react";
746
+ import { useAtomValue } from "jotai";
658
747
  import {
659
- IconBold as IconBold2,
660
- IconItalic as IconItalic2,
661
- IconStrikethrough as IconStrikethrough2,
748
+ IconTypography as IconTypography2,
662
749
  IconH1 as IconH12,
663
750
  IconH2 as IconH22,
664
751
  IconH3 as IconH32,
@@ -666,323 +753,182 @@ import {
666
753
  IconList as IconList2,
667
754
  IconListNumbers as IconListNumbers2,
668
755
  IconBlockquote as IconBlockquote2,
669
- IconCode as IconCode2,
756
+ IconCode as IconCode3,
670
757
  IconSourceCode as IconSourceCode2,
671
758
  IconPhoto,
672
- IconVideo
759
+ IconVideo,
760
+ IconMinus,
761
+ IconTable,
762
+ IconListCheck
673
763
  } from "@tabler/icons-react";
764
+ import { useMemo } from "react";
674
765
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
766
+ var SLASH_COMMANDS = [
767
+ {
768
+ group: "Format",
769
+ items: [
770
+ {
771
+ value: "paragraph text",
772
+ label: "Paragraph",
773
+ description: "Plain text block",
774
+ icon: IconTypography2,
775
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).setParagraph().run()
776
+ },
777
+ {
778
+ value: "heading1 h1",
779
+ label: "Heading 1",
780
+ description: "Large section heading",
781
+ icon: IconH12,
782
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleHeading({ level: 1 }).run()
783
+ },
784
+ {
785
+ value: "heading2 h2",
786
+ label: "Heading 2",
787
+ description: "Medium section heading",
788
+ icon: IconH22,
789
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleHeading({ level: 2 }).run()
790
+ },
791
+ {
792
+ value: "heading3 h3",
793
+ label: "Heading 3",
794
+ description: "Small section heading",
795
+ icon: IconH32,
796
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleHeading({ level: 3 }).run()
797
+ },
798
+ {
799
+ value: "heading4 h4",
800
+ label: "Heading 4",
801
+ description: "Subsection heading",
802
+ icon: IconH42,
803
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleHeading({ level: 4 }).run()
804
+ }
805
+ ]
806
+ },
807
+ {
808
+ group: "Lists",
809
+ items: [
810
+ {
811
+ value: "bullet list ul",
812
+ label: "Bullet List",
813
+ description: "Unordered list of items",
814
+ icon: IconList2,
815
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleBulletList().run()
816
+ },
817
+ {
818
+ value: "ordered list ol numbered",
819
+ label: "Numbered List",
820
+ description: "Ordered list of items",
821
+ icon: IconListNumbers2,
822
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleOrderedList().run()
823
+ },
824
+ {
825
+ value: "task list todo checklist",
826
+ label: "Task List",
827
+ description: "Checklist of to-do items",
828
+ icon: IconListCheck,
829
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleTaskList().run()
830
+ },
831
+ {
832
+ value: "quote blockquote",
833
+ label: "Blockquote",
834
+ description: "Highlight a quote or excerpt",
835
+ icon: IconBlockquote2,
836
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleBlockquote().run()
837
+ }
838
+ ]
839
+ },
840
+ {
841
+ group: "Insert",
842
+ items: [
843
+ {
844
+ value: "image photo picture",
845
+ label: "Image",
846
+ description: "Upload or embed an image",
847
+ icon: IconPhoto,
848
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).setImageBlock({ src: "" }).run()
849
+ },
850
+ {
851
+ value: "video embed youtube vimeo",
852
+ label: "Video",
853
+ description: "Embed a YouTube or Vimeo video",
854
+ icon: IconVideo,
855
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).setVideoBlock({ src: "" }).run()
856
+ },
857
+ {
858
+ value: "table grid",
859
+ label: "Table",
860
+ description: "Insert a table with rows and columns",
861
+ icon: IconTable,
862
+ onCommand: ({ editor, range }) => {
863
+ editor.chain().focus().deleteRange(range).run();
864
+ if (editor.commands.insertTable) {
865
+ editor.commands.insertTable({ rows: 3, cols: 3, withHeaderRow: true });
866
+ }
867
+ }
868
+ },
869
+ {
870
+ value: "code block codeblock",
871
+ label: "Code Block",
872
+ description: "Syntax-highlighted code block",
873
+ icon: IconSourceCode2,
874
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run()
875
+ },
876
+ {
877
+ value: "code inline",
878
+ label: "Inline Code",
879
+ description: "Mark text as inline code",
880
+ icon: IconCode3,
881
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCode().run()
882
+ },
883
+ {
884
+ value: "divider horizontal rule separator",
885
+ label: "Divider",
886
+ description: "Horizontal separator line",
887
+ icon: IconMinus,
888
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).setHorizontalRule().run()
889
+ }
890
+ ]
891
+ }
892
+ ];
675
893
  function SlashMenu({ className }) {
676
894
  const { editor } = useCurrentEditor2();
895
+ const query = useAtomValue(queryAtom, { store: novelStore });
896
+ const filteredGroups = useMemo(() => {
897
+ if (!query) return SLASH_COMMANDS;
898
+ const q = query.toLowerCase();
899
+ return SLASH_COMMANDS.map((group) => ({
900
+ ...group,
901
+ items: group.items.filter((item) => item.value.toLowerCase().includes(q))
902
+ })).filter((group) => group.items.length > 0);
903
+ }, [query]);
677
904
  if (!editor) return null;
678
- return /* @__PURE__ */ jsx3(EditorCommand, { className: className ?? "nph-command", children: /* @__PURE__ */ jsxs3(
905
+ return /* @__PURE__ */ jsx3(EditorCommand, { className: className ?? "nph-command", children: /* @__PURE__ */ jsx3(
679
906
  EditorCommandList,
680
907
  {
681
908
  className: "nph-command__list",
682
909
  style: { display: "flex", flexDirection: "column", gap: 2 },
683
- children: [
684
- /* @__PURE__ */ jsx3(EditorCommandEmpty, { style: { padding: "8px 12px", fontSize: 14 }, children: "No commands found" }),
685
- /* @__PURE__ */ jsx3(
686
- EditorCommandItem,
687
- {
688
- value: "bold",
689
- className: "nph-command__item",
690
- onCommand: ({
691
- editor: editor2,
692
- range
693
- }) => editor2.chain().focus().deleteRange(range).toggleBold().run(),
694
- children: /* @__PURE__ */ jsxs3(
695
- "span",
696
- {
697
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
698
- children: [
699
- /* @__PURE__ */ jsx3(IconBold2, { size: 16 }),
700
- /* @__PURE__ */ jsx3("span", { children: "Bold" })
701
- ]
702
- }
703
- )
704
- }
705
- ),
706
- /* @__PURE__ */ jsx3(
707
- EditorCommandItem,
708
- {
709
- value: "italic",
710
- className: "nph-command__item",
711
- onCommand: ({
712
- editor: editor2,
713
- range
714
- }) => editor2.chain().focus().deleteRange(range).toggleItalic().run(),
715
- children: /* @__PURE__ */ jsxs3(
716
- "span",
717
- {
718
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
719
- children: [
720
- /* @__PURE__ */ jsx3(IconItalic2, { size: 16 }),
721
- /* @__PURE__ */ jsx3("span", { children: "Italic" })
722
- ]
723
- }
724
- )
725
- }
726
- ),
727
- /* @__PURE__ */ jsx3(
728
- EditorCommandItem,
729
- {
730
- value: "strike",
731
- className: "nph-command__item",
732
- onCommand: ({
733
- editor: editor2,
734
- range
735
- }) => editor2.chain().focus().deleteRange(range).toggleStrike().run(),
736
- children: /* @__PURE__ */ jsxs3(
737
- "span",
738
- {
739
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
740
- children: [
741
- /* @__PURE__ */ jsx3(IconStrikethrough2, { size: 16 }),
742
- /* @__PURE__ */ jsx3("span", { children: "Strike" })
743
- ]
744
- }
745
- )
746
- }
747
- ),
748
- /* @__PURE__ */ jsx3(
749
- EditorCommandItem,
750
- {
751
- value: "heading1 h1",
752
- className: "nph-command__item",
753
- onCommand: ({
754
- editor: editor2,
755
- range
756
- }) => editor2.chain().focus().deleteRange(range).toggleHeading({ level: 1 }).run(),
757
- children: /* @__PURE__ */ jsxs3(
758
- "span",
759
- {
760
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
761
- children: [
762
- /* @__PURE__ */ jsx3(IconH12, { size: 16 }),
763
- /* @__PURE__ */ jsx3("span", { children: "Heading 1" })
764
- ]
765
- }
766
- )
767
- }
768
- ),
769
- /* @__PURE__ */ jsx3(
770
- EditorCommandItem,
771
- {
772
- value: "heading2 h2",
773
- className: "nph-command__item",
774
- onCommand: ({
775
- editor: editor2,
776
- range
777
- }) => editor2.chain().focus().deleteRange(range).toggleHeading({ level: 2 }).run(),
778
- children: /* @__PURE__ */ jsxs3(
779
- "span",
780
- {
781
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
782
- children: [
783
- /* @__PURE__ */ jsx3(IconH22, { size: 16 }),
784
- /* @__PURE__ */ jsx3("span", { children: "Heading 2" })
785
- ]
786
- }
787
- )
788
- }
789
- ),
790
- /* @__PURE__ */ jsx3(
791
- EditorCommandItem,
792
- {
793
- value: "heading3 h3",
794
- className: "nph-command__item",
795
- onCommand: ({
796
- editor: editor2,
797
- range
798
- }) => editor2.chain().focus().deleteRange(range).toggleHeading({ level: 3 }).run(),
799
- children: /* @__PURE__ */ jsxs3(
800
- "span",
801
- {
802
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
803
- children: [
804
- /* @__PURE__ */ jsx3(IconH32, { size: 16 }),
805
- /* @__PURE__ */ jsx3("span", { children: "Heading 3" })
806
- ]
807
- }
808
- )
809
- }
810
- ),
811
- /* @__PURE__ */ jsx3(
812
- EditorCommandItem,
813
- {
814
- value: "heading4 h4",
815
- className: "nph-command__item",
816
- onCommand: ({
817
- editor: editor2,
818
- range
819
- }) => editor2.chain().focus().deleteRange(range).toggleHeading({ level: 4 }).run(),
820
- children: /* @__PURE__ */ jsxs3(
821
- "span",
822
- {
823
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
824
- children: [
825
- /* @__PURE__ */ jsx3(IconH42, { size: 16 }),
826
- /* @__PURE__ */ jsx3("span", { children: "Heading 4" })
827
- ]
828
- }
829
- )
830
- }
831
- ),
832
- /* @__PURE__ */ jsx3(
833
- EditorCommandItem,
834
- {
835
- value: "bullet list ul",
836
- className: "nph-command__item",
837
- onCommand: ({
838
- editor: editor2,
839
- range
840
- }) => editor2.chain().focus().deleteRange(range).toggleBulletList().run(),
841
- children: /* @__PURE__ */ jsxs3(
842
- "span",
843
- {
844
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
845
- children: [
846
- /* @__PURE__ */ jsx3(IconList2, { size: 16 }),
847
- /* @__PURE__ */ jsx3("span", { children: "Bullet list" })
848
- ]
849
- }
850
- )
851
- }
852
- ),
853
- /* @__PURE__ */ jsx3(
854
- EditorCommandItem,
855
- {
856
- value: "ordered list ol",
857
- className: "nph-command__item",
858
- onCommand: ({
859
- editor: editor2,
860
- range
861
- }) => editor2.chain().focus().deleteRange(range).toggleOrderedList().run(),
862
- children: /* @__PURE__ */ jsxs3(
863
- "span",
864
- {
865
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
866
- children: [
867
- /* @__PURE__ */ jsx3(IconListNumbers2, { size: 16 }),
868
- /* @__PURE__ */ jsx3("span", { children: "Ordered list" })
869
- ]
870
- }
871
- )
872
- }
873
- ),
874
- /* @__PURE__ */ jsx3(
875
- EditorCommandItem,
876
- {
877
- value: "quote blockquote",
878
- className: "nph-command__item",
879
- onCommand: ({
880
- editor: editor2,
881
- range
882
- }) => editor2.chain().focus().deleteRange(range).toggleBlockquote().run(),
883
- children: /* @__PURE__ */ jsxs3(
884
- "span",
885
- {
886
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
887
- children: [
888
- /* @__PURE__ */ jsx3(IconBlockquote2, { size: 16 }),
889
- /* @__PURE__ */ jsx3("span", { children: "Quote" })
890
- ]
891
- }
892
- )
893
- }
894
- ),
895
- /* @__PURE__ */ jsx3(
896
- EditorCommandItem,
897
- {
898
- value: "code inline",
899
- className: "nph-command__item",
900
- onCommand: ({
901
- editor: editor2,
902
- range
903
- }) => editor2.chain().focus().deleteRange(range).toggleCode().run(),
904
- children: /* @__PURE__ */ jsxs3(
905
- "span",
906
- {
907
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
908
- children: [
909
- /* @__PURE__ */ jsx3(IconCode2, { size: 16 }),
910
- /* @__PURE__ */ jsx3("span", { children: "Code" })
911
- ]
912
- }
913
- )
914
- }
915
- ),
916
- /* @__PURE__ */ jsx3(
917
- EditorCommandItem,
918
- {
919
- value: "code block codeblock",
920
- className: "nph-command__item",
921
- onCommand: ({
922
- editor: editor2,
923
- range
924
- }) => editor2.chain().focus().deleteRange(range).toggleCodeBlock().run(),
925
- children: /* @__PURE__ */ jsxs3(
926
- "span",
927
- {
928
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
929
- children: [
930
- /* @__PURE__ */ jsx3(IconSourceCode2, { size: 16 }),
931
- /* @__PURE__ */ jsx3("span", { children: "Code Block" })
932
- ]
933
- }
934
- )
935
- }
936
- ),
937
- /* @__PURE__ */ jsx3(
938
- EditorCommandItem,
939
- {
940
- value: "image photo picture block",
941
- className: "nph-command__item",
942
- onCommand: ({
943
- editor: editor2,
944
- range
945
- }) => {
946
- ;
947
- editor2.chain().focus().deleteRange(range).setImageBlock({ src: "" }).run();
948
- },
949
- children: /* @__PURE__ */ jsxs3(
950
- "span",
951
- {
952
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
953
- children: [
954
- /* @__PURE__ */ jsx3(IconPhoto, { size: 16 }),
955
- /* @__PURE__ */ jsx3("span", { children: "Image" })
956
- ]
957
- }
958
- )
959
- }
960
- ),
961
- /* @__PURE__ */ jsx3(
962
- EditorCommandItem,
963
- {
964
- value: "video embed youtube vimeo",
965
- className: "nph-command__item",
966
- onCommand: ({
967
- editor: editor2,
968
- range
969
- }) => {
970
- ;
971
- editor2.chain().focus().deleteRange(range).setVideoBlock({ src: "" }).run();
910
+ children: filteredGroups.length === 0 ? /* @__PURE__ */ jsx3("div", { style: { padding: "8px 12px", fontSize: 14, color: "var(--muted-foreground, #6b7280)" }, children: "No commands found" }) : filteredGroups.map((group) => /* @__PURE__ */ jsxs3("div", { children: [
911
+ /* @__PURE__ */ jsx3("div", { className: "nph-command__group-header", children: group.group }),
912
+ group.items.map((item) => {
913
+ const Icon = item.icon;
914
+ return /* @__PURE__ */ jsxs3(
915
+ EditorCommandItem,
916
+ {
917
+ value: item.value,
918
+ className: "nph-command__item",
919
+ onCommand: item.onCommand,
920
+ children: [
921
+ /* @__PURE__ */ jsx3("div", { className: "nph-command__item-icon", children: /* @__PURE__ */ jsx3(Icon, { size: 18 }) }),
922
+ /* @__PURE__ */ jsxs3("div", { className: "nph-command__item-content", children: [
923
+ /* @__PURE__ */ jsx3("span", { className: "nph-command__item-title", children: item.label }),
924
+ /* @__PURE__ */ jsx3("span", { className: "nph-command__item-description", children: item.description })
925
+ ] })
926
+ ]
972
927
  },
973
- children: /* @__PURE__ */ jsxs3(
974
- "span",
975
- {
976
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
977
- children: [
978
- /* @__PURE__ */ jsx3(IconVideo, { size: 16 }),
979
- /* @__PURE__ */ jsx3("span", { children: "Video" })
980
- ]
981
- }
982
- )
983
- }
984
- )
985
- ]
928
+ item.value
929
+ );
930
+ })
931
+ ] }, group.group))
986
932
  }
987
933
  ) });
988
934
  }
@@ -1139,6 +1085,7 @@ function LinkMenu() {
1139
1085
  }
1140
1086
 
1141
1087
  // src/react/menus/ImageMenu.tsx
1088
+ import { NodeSelection as NodeSelection2 } from "@tiptap/pm/state";
1142
1089
  import { useCurrentEditor as useCurrentEditor4 } from "@tiptap/react";
1143
1090
  import { BubbleMenu as BubbleMenu3 } from "@tiptap/react/menus";
1144
1091
  import {
@@ -1206,7 +1153,7 @@ function ImageMenu({
1206
1153
  shouldShow: ({ editor: e, state }) => {
1207
1154
  if (!e.isActive("imageBlock")) return false;
1208
1155
  const { selection } = state;
1209
- const isNodeSelection = selection.constructor.name === "NodeSelection";
1156
+ const isNodeSelection = selection instanceof NodeSelection2;
1210
1157
  return isNodeSelection;
1211
1158
  },
1212
1159
  updateDelay: 0,
@@ -1319,11 +1266,12 @@ function ImageMenu({
1319
1266
 
1320
1267
  // src/react/menus/ImageBlock/ImageBlockView.tsx
1321
1268
  import { NodeViewWrapper } from "@tiptap/react";
1322
- import { useCallback as useCallback6, useRef as useRef6 } from "react";
1269
+ import { useCallback as useCallback7, useRef as useRef7 } from "react";
1323
1270
 
1324
1271
  // src/react/menus/ImageBlock/ImageBlockMenu.tsx
1325
- import { BubbleMenu as BubbleMenu4 } from "@tiptap/react/menus";
1326
- import { useCallback as useCallback4, useRef as useRef4, useState as useState6, useEffect as useEffect6 } from "react";
1272
+ import { NodeSelection as NodeSelection3 } from "@tiptap/pm/state";
1273
+ import { useEditorState as useEditorState2 } from "@tiptap/react";
1274
+ import { useCallback as useCallback4, useRef as useRef4 } from "react";
1327
1275
 
1328
1276
  // src/react/menus/ImageBlock/ImageBlockWidth.tsx
1329
1277
  import { memo, useCallback as useCallback3, useEffect as useEffect5, useState as useState5 } from "react";
@@ -1393,57 +1341,28 @@ import {
1393
1341
  IconTrash as IconTrash3
1394
1342
  } from "@tabler/icons-react";
1395
1343
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1396
- var ImageBlockMenu = ({ editor, appendTo }) => {
1344
+ var ImageBlockMenu = ({ editor, getPos, appendTo }) => {
1397
1345
  const menuRef = useRef4(null);
1398
- const [align, setAlign] = useState6("center");
1399
- const [width, setWidth] = useState6(100);
1400
- useEffect6(() => {
1401
- if (!editor) return;
1402
- const update = () => {
1403
- if (!editor.isActive("imageBlock")) return;
1404
- const attrs = editor.getAttributes("imageBlock");
1405
- setAlign(attrs.align || "center");
1406
- const widthStr = attrs.width || "100%";
1407
- setWidth(parseInt(widthStr) || 100);
1408
- };
1409
- update();
1410
- editor.on("selectionUpdate", update);
1411
- editor.on("transaction", update);
1412
- return () => {
1413
- editor.off("selectionUpdate", update);
1414
- editor.off("transaction", update);
1415
- };
1416
- }, [editor]);
1417
- const getReferenceClientRect = useCallback4(() => {
1418
- if (!editor) return new DOMRect(-1e3, -1e3, 0, 0);
1419
- const { view } = editor;
1420
- const { state } = view;
1421
- const { selection } = state;
1422
- const node = selection instanceof window.ProseMirror?.state?.NodeSelection ? selection.node : null;
1423
- if (node && node.type.name === "imageBlock") {
1424
- const nodePos = selection.from;
1425
- const domNode = view.nodeDOM(nodePos);
1426
- if (domNode && domNode instanceof HTMLElement) {
1427
- return domNode.getBoundingClientRect();
1428
- }
1429
- }
1430
- const imageBlockElements = document.querySelectorAll("[data-node-view-wrapper]");
1431
- for (const el of Array.from(imageBlockElements)) {
1432
- if (el.querySelector("img")) {
1433
- return el.getBoundingClientRect();
1346
+ const { isVisible, align, width } = useEditorState2({
1347
+ editor,
1348
+ selector: (ctx) => {
1349
+ if (!ctx.editor) return { isVisible: false, align: "center", width: 100 };
1350
+ const { state } = ctx.editor;
1351
+ const { selection } = state;
1352
+ const isNodeSel = selection instanceof NodeSelection3;
1353
+ const isThisNode = isNodeSel && selection.from === getPos();
1354
+ const visible = isThisNode;
1355
+ let currentAlign = "center";
1356
+ let currentWidth = 100;
1357
+ if (visible) {
1358
+ const attrs = ctx.editor.getAttributes("imageBlock");
1359
+ currentAlign = attrs.align || "center";
1360
+ const widthStr = attrs.width || "100%";
1361
+ currentWidth = parseInt(widthStr) || 100;
1434
1362
  }
1363
+ return { isVisible: visible, align: currentAlign, width: currentWidth };
1435
1364
  }
1436
- return new DOMRect(-1e3, -1e3, 0, 0);
1437
- }, [editor]);
1438
- const shouldShow = useCallback4(() => {
1439
- if (!editor) return false;
1440
- const isActive = editor.isActive("imageBlock");
1441
- if (!isActive) return false;
1442
- const { state } = editor;
1443
- const { selection } = state;
1444
- const isNodeSelection = selection.constructor.name === "NodeSelection";
1445
- return isNodeSelection;
1446
- }, [editor]);
1365
+ });
1447
1366
  const onAlignImageLeft = useCallback4(() => {
1448
1367
  editor.chain().focus(void 0, { scrollIntoView: false }).setImageBlockAlign("left").run();
1449
1368
  }, [editor]);
@@ -1462,13 +1381,20 @@ var ImageBlockMenu = ({ editor, appendTo }) => {
1462
1381
  const onRemoveImage = useCallback4(() => {
1463
1382
  editor.chain().focus(void 0, { scrollIntoView: false }).deleteSelection().run();
1464
1383
  }, [editor]);
1465
- return /* @__PURE__ */ jsx7(
1466
- BubbleMenu4,
1384
+ if (!isVisible) return null;
1385
+ return /* @__PURE__ */ jsxs7(
1386
+ "div",
1467
1387
  {
1468
- editor,
1469
- shouldShow,
1470
- updateDelay: 0,
1471
- children: /* @__PURE__ */ jsxs7("div", { className: "bubble-menu", ref: menuRef, children: [
1388
+ className: "bubble-menu",
1389
+ ref: menuRef,
1390
+ style: {
1391
+ position: "absolute",
1392
+ top: "-40px",
1393
+ left: "50%",
1394
+ transform: "translateX(-50%)",
1395
+ zIndex: "var(--nph-z, 50)"
1396
+ },
1397
+ children: [
1472
1398
  /* @__PURE__ */ jsx7(
1473
1399
  "button",
1474
1400
  {
@@ -1528,14 +1454,14 @@ var ImageBlockMenu = ({ editor, appendTo }) => {
1528
1454
  children: /* @__PURE__ */ jsx7(IconTrash3, { size: 16 })
1529
1455
  }
1530
1456
  )
1531
- ] })
1457
+ ]
1532
1458
  }
1533
1459
  );
1534
1460
  };
1535
1461
 
1536
1462
  // src/react/menus/ImageBlock/ImageUploader.tsx
1537
1463
  import { IconPhoto as IconPhoto2, IconUpload as IconUpload2 } from "@tabler/icons-react";
1538
- import { useCallback as useCallback5, useRef as useRef5, useState as useState7 } from "react";
1464
+ import { useCallback as useCallback5, useRef as useRef5, useState as useState6 } from "react";
1539
1465
 
1540
1466
  // src/react/menus/ImageBlock/ImageBlockLoading.tsx
1541
1467
  import { IconLoader2 } from "@tabler/icons-react";
@@ -1553,8 +1479,8 @@ var ImageBlockLoading = () => {
1553
1479
  // src/react/menus/ImageBlock/ImageUploader.tsx
1554
1480
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1555
1481
  var ImageUploader = ({ onUpload, editor }) => {
1556
- const [loading, setLoading] = useState7(false);
1557
- const [draggedInside, setDraggedInside] = useState7(false);
1482
+ const [loading, setLoading] = useState6(false);
1483
+ const [draggedInside, setDraggedInside] = useState6(false);
1558
1484
  const fileInputRef = useRef5(null);
1559
1485
  const uploadFile = useCallback5(
1560
1486
  async (file) => {
@@ -1656,21 +1582,93 @@ var ImageUploader = ({ onUpload, editor }) => {
1656
1582
  );
1657
1583
  };
1658
1584
 
1659
- // src/react/menus/ImageBlock/ImageBlockView.tsx
1585
+ // src/react/menus/ImageBlock/ImageResizeHandle.tsx
1586
+ import { useCallback as useCallback6, useRef as useRef6, useState as useState7 } from "react";
1660
1587
  import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1588
+ function ImageResizeHandle({
1589
+ children,
1590
+ onResize,
1591
+ currentWidth
1592
+ }) {
1593
+ const containerRef = useRef6(null);
1594
+ const [isResizing, setIsResizing] = useState7(false);
1595
+ const handleMouseDown = useCallback6(
1596
+ (e, side) => {
1597
+ e.preventDefault();
1598
+ e.stopPropagation();
1599
+ setIsResizing(true);
1600
+ const startX = e.clientX;
1601
+ const containerEl = containerRef.current;
1602
+ if (!containerEl) return;
1603
+ const editorEl = containerEl.closest(".nph-editor") || containerEl.parentElement;
1604
+ if (!editorEl) return;
1605
+ const editorWidth = editorEl.getBoundingClientRect().width;
1606
+ const startWidth = containerEl.getBoundingClientRect().width;
1607
+ const startPercent = startWidth / editorWidth * 100;
1608
+ const handleMouseMove = (moveEvent) => {
1609
+ const deltaX = moveEvent.clientX - startX;
1610
+ const direction = side === "right" ? 1 : -1;
1611
+ const deltaPercent = deltaX * direction / editorWidth * 100 * 2;
1612
+ const newPercent = Math.max(10, Math.min(100, startPercent + deltaPercent));
1613
+ onResize(Math.round(newPercent));
1614
+ };
1615
+ const handleMouseUp = () => {
1616
+ setIsResizing(false);
1617
+ document.removeEventListener("mousemove", handleMouseMove);
1618
+ document.removeEventListener("mouseup", handleMouseUp);
1619
+ };
1620
+ document.addEventListener("mousemove", handleMouseMove);
1621
+ document.addEventListener("mouseup", handleMouseUp);
1622
+ },
1623
+ [onResize]
1624
+ );
1625
+ return /* @__PURE__ */ jsxs10(
1626
+ "div",
1627
+ {
1628
+ ref: containerRef,
1629
+ className: `nph-resize-wrapper${isResizing ? " nph-resize-wrapper--active" : ""}`,
1630
+ children: [
1631
+ children,
1632
+ /* @__PURE__ */ jsx10(
1633
+ "div",
1634
+ {
1635
+ className: "nph-resize-handle nph-resize-handle--left",
1636
+ onMouseDown: (e) => handleMouseDown(e, "left")
1637
+ }
1638
+ ),
1639
+ /* @__PURE__ */ jsx10(
1640
+ "div",
1641
+ {
1642
+ className: "nph-resize-handle nph-resize-handle--right",
1643
+ onMouseDown: (e) => handleMouseDown(e, "right")
1644
+ }
1645
+ )
1646
+ ]
1647
+ }
1648
+ );
1649
+ }
1650
+
1651
+ // src/react/menus/ImageBlock/ImageBlockView.tsx
1652
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1661
1653
  var ImageBlockView = (props) => {
1662
1654
  const { editor, getPos, node, updateAttributes } = props;
1663
- const imageWrapperRef = useRef6(null);
1655
+ const imageWrapperRef = useRef7(null);
1664
1656
  const { src, width, align, alt, loading } = node.attrs;
1665
- const handleUpload = useCallback6(
1657
+ const handleUpload = useCallback7(
1666
1658
  (url) => {
1667
1659
  updateAttributes({ src: url, loading: false });
1668
1660
  },
1669
1661
  [updateAttributes]
1670
1662
  );
1671
- const onClick = useCallback6(() => {
1663
+ const onClick = useCallback7(() => {
1672
1664
  editor.commands.setNodeSelection(getPos());
1673
1665
  }, [getPos, editor.commands]);
1666
+ const handleResize = useCallback7(
1667
+ (widthPercent) => {
1668
+ updateAttributes({ width: `${widthPercent}%` });
1669
+ },
1670
+ [updateAttributes]
1671
+ );
1674
1672
  const getWrapperStyle = () => {
1675
1673
  const baseStyle = {
1676
1674
  width: width || "100%",
@@ -1684,14 +1682,17 @@ var ImageBlockView = (props) => {
1684
1682
  return { ...baseStyle, marginLeft: "auto", marginRight: "auto" };
1685
1683
  }
1686
1684
  };
1685
+ const getContentStyle = () => ({
1686
+ position: "relative"
1687
+ });
1687
1688
  if (!src || src === "") {
1688
- return /* @__PURE__ */ jsx10(NodeViewWrapper, { children: /* @__PURE__ */ jsx10("div", { style: getWrapperStyle(), children: /* @__PURE__ */ jsx10("div", { ref: imageWrapperRef, children: /* @__PURE__ */ jsx10(ImageUploader, { onUpload: handleUpload, editor }) }) }) });
1689
+ return /* @__PURE__ */ jsx11(NodeViewWrapper, { style: { width: "100%", marginTop: "0.5rem", marginBottom: "0.5rem" }, children: /* @__PURE__ */ jsx11("div", { ref: imageWrapperRef, children: /* @__PURE__ */ jsx11(ImageUploader, { onUpload: handleUpload, editor }) }) });
1689
1690
  }
1690
1691
  if (loading) {
1691
- return /* @__PURE__ */ jsx10(NodeViewWrapper, { children: /* @__PURE__ */ jsx10("div", { style: getWrapperStyle(), children: /* @__PURE__ */ jsx10("div", { ref: imageWrapperRef, children: /* @__PURE__ */ jsx10(ImageBlockLoading, {}) }) }) });
1692
+ return /* @__PURE__ */ jsx11(NodeViewWrapper, { style: getWrapperStyle(), children: /* @__PURE__ */ jsx11("div", { ref: imageWrapperRef, children: /* @__PURE__ */ jsx11(ImageBlockLoading, {}) }) });
1692
1693
  }
1693
- return /* @__PURE__ */ jsxs10(NodeViewWrapper, { children: [
1694
- /* @__PURE__ */ jsx10("div", { style: getWrapperStyle(), children: /* @__PURE__ */ jsx10("div", { contentEditable: false, ref: imageWrapperRef, style: { position: "relative" }, children: /* @__PURE__ */ jsx10(
1694
+ return /* @__PURE__ */ jsx11(NodeViewWrapper, { style: getWrapperStyle(), children: /* @__PURE__ */ jsxs11("div", { contentEditable: false, ref: imageWrapperRef, style: getContentStyle(), children: [
1695
+ /* @__PURE__ */ jsx11(ImageResizeHandle, { onResize: handleResize, currentWidth: width, children: /* @__PURE__ */ jsx11(
1695
1696
  "img",
1696
1697
  {
1697
1698
  src,
@@ -1699,81 +1700,81 @@ var ImageBlockView = (props) => {
1699
1700
  onClick,
1700
1701
  className: "nph-image-block"
1701
1702
  }
1702
- ) }) }),
1703
- /* @__PURE__ */ jsx10(ImageBlockMenu, { editor, appendTo: imageWrapperRef })
1704
- ] });
1703
+ ) }),
1704
+ /* @__PURE__ */ jsx11(ImageBlockMenu, { editor, getPos, appendTo: imageWrapperRef })
1705
+ ] }) });
1705
1706
  };
1706
1707
 
1707
1708
  // src/react/menus/VideoBlock/VideoBlockView.tsx
1708
- import { NodeViewWrapper as NodeViewWrapper2 } from "@tiptap/react";
1709
- import { useCallback as useCallback8, useRef as useRef8, useState as useState9 } from "react";
1709
+ import { NodeSelection as NodeSelection5 } from "@tiptap/pm/state";
1710
+ import { NodeViewWrapper as NodeViewWrapper2, useEditorState as useEditorState4 } from "@tiptap/react";
1711
+ import { useCallback as useCallback9, useRef as useRef9, useState as useState8 } from "react";
1710
1712
 
1711
1713
  // src/react/menus/VideoBlock/VideoBlockMenu.tsx
1712
- import { BubbleMenu as BubbleMenu5 } from "@tiptap/react/menus";
1713
- import { useCallback as useCallback7, useRef as useRef7, useState as useState8, useEffect as useEffect7 } from "react";
1714
+ import { NodeSelection as NodeSelection4 } from "@tiptap/pm/state";
1715
+ import { useEditorState as useEditorState3 } from "@tiptap/react";
1716
+ import { useCallback as useCallback8, useRef as useRef8 } from "react";
1714
1717
  import {
1715
1718
  IconAlignLeft as IconAlignLeft3,
1716
1719
  IconAlignCenter as IconAlignCenter3,
1717
1720
  IconAlignRight as IconAlignRight3,
1718
1721
  IconTrash as IconTrash4
1719
1722
  } from "@tabler/icons-react";
1720
- import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1721
- var VideoBlockMenu = ({ editor }) => {
1722
- const menuRef = useRef7(null);
1723
- const [align, setAlign] = useState8("center");
1724
- const [width, setWidth] = useState8(100);
1725
- useEffect7(() => {
1726
- if (!editor) return;
1727
- const update = () => {
1728
- if (!editor.isActive("videoBlock")) return;
1729
- const attrs = editor.getAttributes("videoBlock");
1730
- setAlign(attrs.align || "center");
1731
- const widthStr = attrs.width || "100%";
1732
- setWidth(parseInt(widthStr) || 100);
1733
- };
1734
- update();
1735
- editor.on("selectionUpdate", update);
1736
- editor.on("transaction", update);
1737
- return () => {
1738
- editor.off("selectionUpdate", update);
1739
- editor.off("transaction", update);
1740
- };
1741
- }, [editor]);
1742
- const shouldShow = useCallback7(() => {
1743
- if (!editor) return false;
1744
- const { state } = editor;
1745
- const { selection } = state;
1746
- if (selection.constructor.name !== "NodeSelection") return false;
1747
- const node = selection.node;
1748
- if (!node || node.type.name !== "videoBlock") return false;
1749
- return true;
1750
- }, [editor]);
1751
- const onAlignLeft = useCallback7(() => {
1723
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1724
+ var VideoBlockMenu = ({ editor, getPos }) => {
1725
+ const menuRef = useRef8(null);
1726
+ const { isVisible, align, width } = useEditorState3({
1727
+ editor,
1728
+ selector: (ctx) => {
1729
+ if (!ctx.editor) return { isVisible: false, align: "center", width: 100 };
1730
+ const { state } = ctx.editor;
1731
+ const { selection } = state;
1732
+ const isNodeSel = selection instanceof NodeSelection4;
1733
+ const isThisNode = isNodeSel && selection.from === getPos();
1734
+ let currentAlign = "center";
1735
+ let currentWidth = 100;
1736
+ if (isThisNode) {
1737
+ const attrs = ctx.editor.getAttributes("videoBlock");
1738
+ currentAlign = attrs.align || "center";
1739
+ const widthStr = attrs.width || "100%";
1740
+ currentWidth = parseInt(widthStr) || 100;
1741
+ }
1742
+ return { isVisible: isThisNode, align: currentAlign, width: currentWidth };
1743
+ }
1744
+ });
1745
+ const onAlignLeft = useCallback8(() => {
1752
1746
  editor.chain().focus(void 0, { scrollIntoView: false }).setVideoBlockAlign("left").run();
1753
1747
  }, [editor]);
1754
- const onAlignCenter = useCallback7(() => {
1748
+ const onAlignCenter = useCallback8(() => {
1755
1749
  editor.chain().focus(void 0, { scrollIntoView: false }).setVideoBlockAlign("center").run();
1756
1750
  }, [editor]);
1757
- const onAlignRight = useCallback7(() => {
1751
+ const onAlignRight = useCallback8(() => {
1758
1752
  editor.chain().focus(void 0, { scrollIntoView: false }).setVideoBlockAlign("right").run();
1759
1753
  }, [editor]);
1760
- const onWidthChange = useCallback7(
1754
+ const onWidthChange = useCallback8(
1761
1755
  (value) => {
1762
1756
  editor.chain().focus(void 0, { scrollIntoView: false }).setVideoBlockWidth(value).run();
1763
1757
  },
1764
1758
  [editor]
1765
1759
  );
1766
- const onRemove = useCallback7(() => {
1760
+ const onRemove = useCallback8(() => {
1767
1761
  editor.chain().focus(void 0, { scrollIntoView: false }).deleteSelection().run();
1768
1762
  }, [editor]);
1769
- return /* @__PURE__ */ jsx11(
1770
- BubbleMenu5,
1763
+ if (!isVisible) return null;
1764
+ return /* @__PURE__ */ jsxs12(
1765
+ "div",
1771
1766
  {
1772
- editor,
1773
- shouldShow,
1774
- updateDelay: 0,
1775
- children: /* @__PURE__ */ jsxs11("div", { className: "bubble-menu", ref: menuRef, children: [
1776
- /* @__PURE__ */ jsx11(
1767
+ className: "bubble-menu",
1768
+ ref: menuRef,
1769
+ style: {
1770
+ position: "absolute",
1771
+ top: "-40px",
1772
+ left: "50%",
1773
+ transform: "translateX(-50%)",
1774
+ zIndex: "var(--nph-z, 50)"
1775
+ },
1776
+ children: [
1777
+ /* @__PURE__ */ jsx12(
1777
1778
  "button",
1778
1779
  {
1779
1780
  type: "button",
@@ -1781,10 +1782,10 @@ var VideoBlockMenu = ({ editor }) => {
1781
1782
  title: "Align left",
1782
1783
  onMouseDown: (e) => e.preventDefault(),
1783
1784
  onClick: onAlignLeft,
1784
- children: /* @__PURE__ */ jsx11(IconAlignLeft3, { size: 16 })
1785
+ children: /* @__PURE__ */ jsx12(IconAlignLeft3, { size: 16 })
1785
1786
  }
1786
1787
  ),
1787
- /* @__PURE__ */ jsx11(
1788
+ /* @__PURE__ */ jsx12(
1788
1789
  "button",
1789
1790
  {
1790
1791
  type: "button",
@@ -1792,10 +1793,10 @@ var VideoBlockMenu = ({ editor }) => {
1792
1793
  title: "Align center",
1793
1794
  onMouseDown: (e) => e.preventDefault(),
1794
1795
  onClick: onAlignCenter,
1795
- children: /* @__PURE__ */ jsx11(IconAlignCenter3, { size: 16 })
1796
+ children: /* @__PURE__ */ jsx12(IconAlignCenter3, { size: 16 })
1796
1797
  }
1797
1798
  ),
1798
- /* @__PURE__ */ jsx11(
1799
+ /* @__PURE__ */ jsx12(
1799
1800
  "button",
1800
1801
  {
1801
1802
  type: "button",
@@ -1803,25 +1804,25 @@ var VideoBlockMenu = ({ editor }) => {
1803
1804
  title: "Align right",
1804
1805
  onMouseDown: (e) => e.preventDefault(),
1805
1806
  onClick: onAlignRight,
1806
- children: /* @__PURE__ */ jsx11(IconAlignRight3, { size: 16 })
1807
+ children: /* @__PURE__ */ jsx12(IconAlignRight3, { size: 16 })
1807
1808
  }
1808
1809
  ),
1809
- /* @__PURE__ */ jsx11(
1810
+ /* @__PURE__ */ jsx12(
1810
1811
  "div",
1811
1812
  {
1812
1813
  className: "nph-link-popover__divider",
1813
1814
  style: { margin: "0 4px" }
1814
1815
  }
1815
1816
  ),
1816
- /* @__PURE__ */ jsx11(ImageBlockWidth, { onChange: onWidthChange, value: width }),
1817
- /* @__PURE__ */ jsx11(
1817
+ /* @__PURE__ */ jsx12(ImageBlockWidth, { onChange: onWidthChange, value: width }),
1818
+ /* @__PURE__ */ jsx12(
1818
1819
  "div",
1819
1820
  {
1820
1821
  className: "nph-link-popover__divider",
1821
1822
  style: { margin: "0 4px" }
1822
1823
  }
1823
1824
  ),
1824
- /* @__PURE__ */ jsx11(
1825
+ /* @__PURE__ */ jsx12(
1825
1826
  "button",
1826
1827
  {
1827
1828
  type: "button",
@@ -1829,17 +1830,17 @@ var VideoBlockMenu = ({ editor }) => {
1829
1830
  title: "Remove video",
1830
1831
  onMouseDown: (e) => e.preventDefault(),
1831
1832
  onClick: onRemove,
1832
- children: /* @__PURE__ */ jsx11(IconTrash4, { size: 16 })
1833
+ children: /* @__PURE__ */ jsx12(IconTrash4, { size: 16 })
1833
1834
  }
1834
1835
  )
1835
- ] })
1836
+ ]
1836
1837
  }
1837
1838
  );
1838
1839
  };
1839
1840
 
1840
1841
  // src/react/menus/VideoBlock/VideoBlockView.tsx
1841
1842
  import { IconVideo as IconVideo2 } from "@tabler/icons-react";
1842
- import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1843
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1843
1844
  function toEmbedUrl(url) {
1844
1845
  const ytMatch = url.match(
1845
1846
  /(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/
@@ -1855,15 +1856,23 @@ function toEmbedUrl(url) {
1855
1856
  }
1856
1857
  var VideoBlockView = (props) => {
1857
1858
  const { editor, getPos, node, updateAttributes } = props;
1858
- const wrapperRef = useRef8(null);
1859
+ const wrapperRef = useRef9(null);
1859
1860
  const { src, width, align } = node.attrs;
1860
- const [inputUrl, setInputUrl] = useState9("");
1861
- const handleEmbed = useCallback8(() => {
1861
+ const [inputUrl, setInputUrl] = useState8("");
1862
+ const isSelected = useEditorState4({
1863
+ editor,
1864
+ selector: (ctx) => {
1865
+ if (!ctx.editor) return false;
1866
+ const { selection } = ctx.editor.state;
1867
+ return selection instanceof NodeSelection5 && selection.from === getPos();
1868
+ }
1869
+ });
1870
+ const handleEmbed = useCallback9(() => {
1862
1871
  if (!inputUrl.trim()) return;
1863
1872
  const embedUrl = toEmbedUrl(inputUrl.trim());
1864
1873
  updateAttributes({ src: embedUrl });
1865
1874
  }, [inputUrl, updateAttributes]);
1866
- const handleKeyDown = useCallback8(
1875
+ const handleKeyDown = useCallback9(
1867
1876
  (e) => {
1868
1877
  if (e.key === "Enter") {
1869
1878
  e.preventDefault();
@@ -1872,13 +1881,13 @@ var VideoBlockView = (props) => {
1872
1881
  },
1873
1882
  [handleEmbed]
1874
1883
  );
1875
- const onClick = useCallback8(() => {
1884
+ const onClick = useCallback9(() => {
1876
1885
  editor.commands.setNodeSelection(getPos());
1877
1886
  }, [getPos, editor.commands]);
1878
1887
  const getWrapperStyle = () => {
1879
1888
  const baseStyle = {
1880
- width: width || "100%",
1881
- maxWidth: "100%"
1889
+ width: "fit-content",
1890
+ maxWidth: width || "100%"
1882
1891
  };
1883
1892
  if (align === "left") {
1884
1893
  return { ...baseStyle, marginLeft: 0, marginRight: "auto" };
@@ -1889,10 +1898,10 @@ var VideoBlockView = (props) => {
1889
1898
  }
1890
1899
  };
1891
1900
  if (!src || src === "") {
1892
- return /* @__PURE__ */ jsx12(NodeViewWrapper2, { children: /* @__PURE__ */ jsx12("div", { style: getWrapperStyle(), children: /* @__PURE__ */ jsxs12("div", { className: "nph-video-input", ref: wrapperRef, children: [
1893
- /* @__PURE__ */ jsx12("div", { className: "nph-video-input__icon", children: /* @__PURE__ */ jsx12(IconVideo2, { size: 24 }) }),
1894
- /* @__PURE__ */ jsxs12("div", { className: "nph-video-input__content", children: [
1895
- /* @__PURE__ */ jsx12(
1901
+ return /* @__PURE__ */ jsx13(NodeViewWrapper2, { style: getWrapperStyle(), children: /* @__PURE__ */ jsx13("div", { ref: wrapperRef, children: /* @__PURE__ */ jsxs13("div", { className: "nph-video-input", children: [
1902
+ /* @__PURE__ */ jsx13("div", { className: "nph-video-input__icon", children: /* @__PURE__ */ jsx13(IconVideo2, { size: 24 }) }),
1903
+ /* @__PURE__ */ jsxs13("div", { className: "nph-video-input__content", children: [
1904
+ /* @__PURE__ */ jsx13(
1896
1905
  "input",
1897
1906
  {
1898
1907
  type: "text",
@@ -1903,7 +1912,7 @@ var VideoBlockView = (props) => {
1903
1912
  onKeyDown: handleKeyDown
1904
1913
  }
1905
1914
  ),
1906
- /* @__PURE__ */ jsx12(
1915
+ /* @__PURE__ */ jsx13(
1907
1916
  "button",
1908
1917
  {
1909
1918
  type: "button",
@@ -1916,38 +1925,755 @@ var VideoBlockView = (props) => {
1916
1925
  ] })
1917
1926
  ] }) }) });
1918
1927
  }
1919
- return /* @__PURE__ */ jsxs12(NodeViewWrapper2, { children: [
1920
- /* @__PURE__ */ jsx12("div", { style: getWrapperStyle(), children: /* @__PURE__ */ jsx12(
1921
- "div",
1928
+ return /* @__PURE__ */ jsx13(NodeViewWrapper2, { style: getWrapperStyle(), children: /* @__PURE__ */ jsxs13(
1929
+ "div",
1930
+ {
1931
+ contentEditable: false,
1932
+ ref: wrapperRef,
1933
+ style: { position: "relative" },
1934
+ children: [
1935
+ /* @__PURE__ */ jsxs13("div", { className: "nph-video-block", children: [
1936
+ /* @__PURE__ */ jsx13(
1937
+ "iframe",
1938
+ {
1939
+ src,
1940
+ className: "nph-video-block__iframe",
1941
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
1942
+ allowFullScreen: true
1943
+ }
1944
+ ),
1945
+ !isSelected && /* @__PURE__ */ jsx13(
1946
+ "div",
1947
+ {
1948
+ className: "nph-video-block__overlay",
1949
+ onClick
1950
+ }
1951
+ )
1952
+ ] }),
1953
+ /* @__PURE__ */ jsx13(VideoBlockMenu, { editor, getPos })
1954
+ ]
1955
+ }
1956
+ ) });
1957
+ };
1958
+
1959
+ // src/react/menus/DragHandle/BlockActionMenu.tsx
1960
+ import {
1961
+ IconCopy,
1962
+ IconTrash as IconTrash5,
1963
+ IconArrowUp,
1964
+ IconArrowDown,
1965
+ IconClipboard
1966
+ } from "@tabler/icons-react";
1967
+ import { useCallback as useCallback10 } from "react";
1968
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1969
+ function BlockActionMenu({ editor, onClose }) {
1970
+ const executeAndClose = useCallback10(
1971
+ (fn) => {
1972
+ fn();
1973
+ onClose();
1974
+ },
1975
+ [onClose]
1976
+ );
1977
+ const handleDelete = useCallback10(() => {
1978
+ executeAndClose(() => {
1979
+ editor.commands.deleteSelection();
1980
+ });
1981
+ }, [editor, executeAndClose]);
1982
+ const handleDuplicate = useCallback10(() => {
1983
+ executeAndClose(() => {
1984
+ const { state } = editor;
1985
+ const { selection } = state;
1986
+ const { $anchor } = selection;
1987
+ const depth = $anchor.depth > 0 ? 1 : 0;
1988
+ const start = $anchor.start(depth);
1989
+ const end = $anchor.end(depth);
1990
+ const node = state.doc.nodeAt(start - 1);
1991
+ if (node) {
1992
+ const insertPos = end + 1;
1993
+ editor.chain().focus().insertContentAt(insertPos, node.toJSON()).run();
1994
+ }
1995
+ });
1996
+ }, [editor, executeAndClose]);
1997
+ const handleMoveUp = useCallback10(() => {
1998
+ executeAndClose(() => {
1999
+ const { state } = editor;
2000
+ const { selection } = state;
2001
+ const { $anchor } = selection;
2002
+ const depth = $anchor.depth > 0 ? 1 : 0;
2003
+ const blockStart = $anchor.start(depth) - 1;
2004
+ if (blockStart <= 0) return;
2005
+ const node = state.doc.nodeAt(blockStart);
2006
+ if (!node) return;
2007
+ const $pos = state.doc.resolve(blockStart);
2008
+ const index = $pos.index($pos.depth);
2009
+ if (index === 0) return;
2010
+ const prevNode = $pos.node($pos.depth).child(index - 1);
2011
+ const prevStart = blockStart - prevNode.nodeSize;
2012
+ editor.chain().focus().command(({ tr }) => {
2013
+ const currentSlice = state.doc.slice(blockStart, blockStart + node.nodeSize);
2014
+ tr.delete(blockStart, blockStart + node.nodeSize);
2015
+ tr.insert(prevStart, currentSlice.content);
2016
+ return true;
2017
+ }).run();
2018
+ });
2019
+ }, [editor, executeAndClose]);
2020
+ const handleMoveDown = useCallback10(() => {
2021
+ executeAndClose(() => {
2022
+ const { state } = editor;
2023
+ const { selection } = state;
2024
+ const { $anchor } = selection;
2025
+ const depth = $anchor.depth > 0 ? 1 : 0;
2026
+ const blockStart = $anchor.start(depth) - 1;
2027
+ const node = state.doc.nodeAt(blockStart);
2028
+ if (!node) return;
2029
+ const blockEnd = blockStart + node.nodeSize;
2030
+ const $pos = state.doc.resolve(blockStart);
2031
+ const parent = $pos.node($pos.depth);
2032
+ const index = $pos.index($pos.depth);
2033
+ if (index >= parent.childCount - 1) return;
2034
+ const nextNode = parent.child(index + 1);
2035
+ const nextEnd = blockEnd + nextNode.nodeSize;
2036
+ editor.chain().focus().command(({ tr }) => {
2037
+ const currentSlice = state.doc.slice(blockStart, blockEnd);
2038
+ tr.delete(blockStart, blockEnd);
2039
+ const insertPos = blockStart + nextNode.nodeSize;
2040
+ tr.insert(insertPos, currentSlice.content);
2041
+ return true;
2042
+ }).run();
2043
+ });
2044
+ }, [editor, executeAndClose]);
2045
+ const handleCopyToClipboard = useCallback10(() => {
2046
+ executeAndClose(() => {
2047
+ const { state } = editor;
2048
+ const { selection } = state;
2049
+ const { $anchor } = selection;
2050
+ const depth = $anchor.depth > 0 ? 1 : 0;
2051
+ const start = $anchor.start(depth) - 1;
2052
+ const node = state.doc.nodeAt(start);
2053
+ if (node) {
2054
+ const text = node.textContent;
2055
+ navigator.clipboard.writeText(text).catch(() => {
2056
+ });
2057
+ }
2058
+ });
2059
+ }, [editor, executeAndClose]);
2060
+ return /* @__PURE__ */ jsx14("div", { className: "nph-block-action-menu nph-command", children: /* @__PURE__ */ jsxs14("div", { className: "nph-command__list", style: { maxHeight: "none" }, children: [
2061
+ /* @__PURE__ */ jsxs14(
2062
+ "button",
1922
2063
  {
1923
- contentEditable: false,
1924
- ref: wrapperRef,
1925
- style: { position: "relative" },
1926
- children: /* @__PURE__ */ jsx12("div", { className: "nph-video-block", onClick, children: /* @__PURE__ */ jsx12(
1927
- "iframe",
1928
- {
1929
- src,
1930
- className: "nph-video-block__iframe",
1931
- allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
1932
- allowFullScreen: true
2064
+ type: "button",
2065
+ className: "nph-command__item",
2066
+ onClick: handleDelete,
2067
+ onMouseDown: (e) => e.preventDefault(),
2068
+ children: [
2069
+ /* @__PURE__ */ jsx14(IconTrash5, { size: 16 }),
2070
+ /* @__PURE__ */ jsx14("span", { children: "Delete" })
2071
+ ]
2072
+ }
2073
+ ),
2074
+ /* @__PURE__ */ jsxs14(
2075
+ "button",
2076
+ {
2077
+ type: "button",
2078
+ className: "nph-command__item",
2079
+ onClick: handleDuplicate,
2080
+ onMouseDown: (e) => e.preventDefault(),
2081
+ children: [
2082
+ /* @__PURE__ */ jsx14(IconCopy, { size: 16 }),
2083
+ /* @__PURE__ */ jsx14("span", { children: "Duplicate" })
2084
+ ]
2085
+ }
2086
+ ),
2087
+ /* @__PURE__ */ jsxs14(
2088
+ "button",
2089
+ {
2090
+ type: "button",
2091
+ className: "nph-command__item",
2092
+ onClick: handleCopyToClipboard,
2093
+ onMouseDown: (e) => e.preventDefault(),
2094
+ children: [
2095
+ /* @__PURE__ */ jsx14(IconClipboard, { size: 16 }),
2096
+ /* @__PURE__ */ jsx14("span", { children: "Copy to clipboard" })
2097
+ ]
2098
+ }
2099
+ ),
2100
+ /* @__PURE__ */ jsxs14(
2101
+ "button",
2102
+ {
2103
+ type: "button",
2104
+ className: "nph-command__item",
2105
+ onClick: handleMoveUp,
2106
+ onMouseDown: (e) => e.preventDefault(),
2107
+ children: [
2108
+ /* @__PURE__ */ jsx14(IconArrowUp, { size: 16 }),
2109
+ /* @__PURE__ */ jsx14("span", { children: "Move up" })
2110
+ ]
2111
+ }
2112
+ ),
2113
+ /* @__PURE__ */ jsxs14(
2114
+ "button",
2115
+ {
2116
+ type: "button",
2117
+ className: "nph-command__item",
2118
+ onClick: handleMoveDown,
2119
+ onMouseDown: (e) => e.preventDefault(),
2120
+ children: [
2121
+ /* @__PURE__ */ jsx14(IconArrowDown, { size: 16 }),
2122
+ /* @__PURE__ */ jsx14("span", { children: "Move down" })
2123
+ ]
2124
+ }
2125
+ )
2126
+ ] }) });
2127
+ }
2128
+
2129
+ // src/react/menus/TableMenu.tsx
2130
+ import { useCurrentEditor as useCurrentEditor5, useEditorState as useEditorState5 } from "@tiptap/react";
2131
+ import {
2132
+ IconRowInsertBottom,
2133
+ IconColumnInsertRight,
2134
+ IconColumnInsertLeft,
2135
+ IconRowRemove,
2136
+ IconColumnRemove,
2137
+ IconTableRow,
2138
+ IconTableColumn,
2139
+ IconArrowMerge,
2140
+ IconArrowsSplit,
2141
+ IconTableOff,
2142
+ IconGripVertical,
2143
+ IconGripHorizontal,
2144
+ IconRowInsertTop
2145
+ } from "@tabler/icons-react";
2146
+ import {
2147
+ useState as useState9,
2148
+ useEffect as useEffect6,
2149
+ useRef as useRef10,
2150
+ useCallback as useCallback11
2151
+ } from "react";
2152
+ import { createPortal } from "react-dom";
2153
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2154
+ function TableMenu({ className: _className }) {
2155
+ const { editor } = useCurrentEditor5();
2156
+ const tableInfo = useEditorState5({
2157
+ editor,
2158
+ selector: (ctx) => {
2159
+ if (!ctx.editor) return null;
2160
+ const { $from } = ctx.editor.state.selection;
2161
+ for (let d = $from.depth; d > 0; d--) {
2162
+ if ($from.node(d).type.name === "table") {
2163
+ return { pos: $from.start(d) - 1, depth: d };
2164
+ }
2165
+ }
2166
+ return null;
2167
+ }
2168
+ });
2169
+ const [colGrips, setColGrips] = useState9([]);
2170
+ const [rowGrips, setRowGrips] = useState9([]);
2171
+ const [dropdown, setDropdown] = useState9(null);
2172
+ const [tableRect, setTableRect] = useState9(null);
2173
+ const [isHovering, setIsHovering] = useState9(false);
2174
+ const [drag, setDrag] = useState9(null);
2175
+ const dropdownRef = useRef10(null);
2176
+ const dragRef = useRef10(null);
2177
+ const getTableDom = useCallback11(() => {
2178
+ if (!editor || !tableInfo) return null;
2179
+ return editor.view.nodeDOM(tableInfo.pos);
2180
+ }, [editor, tableInfo]);
2181
+ const measureGrips = useCallback11(() => {
2182
+ const tableDom = getTableDom();
2183
+ if (!tableDom) {
2184
+ setColGrips([]);
2185
+ setRowGrips([]);
2186
+ setTableRect(null);
2187
+ return;
2188
+ }
2189
+ const rect = tableDom.getBoundingClientRect();
2190
+ setTableRect(rect);
2191
+ const firstRow = tableDom.querySelector("tr");
2192
+ if (!firstRow) return;
2193
+ const cells = firstRow.querySelectorAll("th, td");
2194
+ const newColGrips = [];
2195
+ cells.forEach((cell) => {
2196
+ const cellRect = cell.getBoundingClientRect();
2197
+ newColGrips.push({
2198
+ left: cellRect.left,
2199
+ top: rect.top,
2200
+ width: cellRect.width,
2201
+ height: 0
2202
+ });
2203
+ });
2204
+ setColGrips(newColGrips);
2205
+ const rows = tableDom.querySelectorAll("tr");
2206
+ const newRowGrips = [];
2207
+ rows.forEach((row) => {
2208
+ const rowRect = row.getBoundingClientRect();
2209
+ newRowGrips.push({
2210
+ left: rect.left,
2211
+ top: rowRect.top,
2212
+ width: 0,
2213
+ height: rowRect.height
2214
+ });
2215
+ });
2216
+ setRowGrips(newRowGrips);
2217
+ }, [getTableDom]);
2218
+ useEffect6(() => {
2219
+ if (!tableInfo) {
2220
+ setColGrips([]);
2221
+ setRowGrips([]);
2222
+ setTableRect(null);
2223
+ setDropdown(null);
2224
+ return;
2225
+ }
2226
+ measureGrips();
2227
+ const tableDom = getTableDom();
2228
+ if (!tableDom) return;
2229
+ const ro = new ResizeObserver(() => measureGrips());
2230
+ ro.observe(tableDom);
2231
+ const HOVER_PAD = 32;
2232
+ const handleMouseMove = (e) => {
2233
+ const r = tableDom.getBoundingClientRect();
2234
+ const inside = e.clientX >= r.left - HOVER_PAD && e.clientX <= r.right + HOVER_PAD && e.clientY >= r.top - HOVER_PAD && e.clientY <= r.bottom + HOVER_PAD;
2235
+ setIsHovering(inside);
2236
+ };
2237
+ document.addEventListener("mousemove", handleMouseMove, { passive: true });
2238
+ const scrollParent = tableDom.closest(".nph-editor") || window;
2239
+ const handleScroll = () => {
2240
+ measureGrips();
2241
+ setDropdown(null);
2242
+ };
2243
+ scrollParent.addEventListener("scroll", handleScroll, { passive: true });
2244
+ window.addEventListener("scroll", handleScroll, { passive: true });
2245
+ return () => {
2246
+ ro.disconnect();
2247
+ document.removeEventListener("mousemove", handleMouseMove);
2248
+ scrollParent.removeEventListener("scroll", handleScroll);
2249
+ window.removeEventListener("scroll", handleScroll);
2250
+ };
2251
+ }, [tableInfo, getTableDom, measureGrips]);
2252
+ useEffect6(() => {
2253
+ if (!dropdown) return;
2254
+ const handlePointerDown = (e) => {
2255
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
2256
+ setDropdown(null);
2257
+ }
2258
+ };
2259
+ document.addEventListener("pointerdown", handlePointerDown);
2260
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
2261
+ }, [dropdown]);
2262
+ useEffect6(() => {
2263
+ if (!editor || !dropdown) return;
2264
+ const handleTransaction = () => setDropdown(null);
2265
+ editor.on("transaction", handleTransaction);
2266
+ return () => {
2267
+ editor.off("transaction", handleTransaction);
2268
+ };
2269
+ }, [editor, dropdown]);
2270
+ const handleColGripClick = useCallback11(
2271
+ (index, e) => {
2272
+ e.preventDefault();
2273
+ e.stopPropagation();
2274
+ if (!editor || !tableInfo) return;
2275
+ const tableDom = getTableDom();
2276
+ if (!tableDom) return;
2277
+ const firstRow = tableDom.querySelector("tr");
2278
+ if (!firstRow) return;
2279
+ const cells = firstRow.querySelectorAll("th, td");
2280
+ const cell = cells[index];
2281
+ if (!cell) return;
2282
+ const pos = editor.view.posAtDOM(cell, 0);
2283
+ editor.chain().focus().setTextSelection(pos).run();
2284
+ const rect = cell.getBoundingClientRect();
2285
+ setDropdown({
2286
+ type: "column",
2287
+ index,
2288
+ x: rect.left,
2289
+ y: rect.top - 4
2290
+ });
2291
+ },
2292
+ [editor, tableInfo, getTableDom]
2293
+ );
2294
+ const handleRowGripClick = useCallback11(
2295
+ (index, e) => {
2296
+ e.preventDefault();
2297
+ e.stopPropagation();
2298
+ if (!editor || !tableInfo) return;
2299
+ const tableDom = getTableDom();
2300
+ if (!tableDom) return;
2301
+ const rows = tableDom.querySelectorAll("tr");
2302
+ const row = rows[index];
2303
+ if (!row) return;
2304
+ const firstCell = row.querySelector("th, td");
2305
+ if (!firstCell) return;
2306
+ const pos = editor.view.posAtDOM(firstCell, 0);
2307
+ editor.chain().focus().setTextSelection(pos).run();
2308
+ const gripEl = e.currentTarget;
2309
+ const gripRect = gripEl.getBoundingClientRect();
2310
+ setDropdown({
2311
+ type: "row",
2312
+ index,
2313
+ x: gripRect.left,
2314
+ y: gripRect.bottom + 4
2315
+ });
2316
+ },
2317
+ [editor, tableInfo, getTableDom]
2318
+ );
2319
+ const moveColumn = useCallback11(
2320
+ (from, to) => {
2321
+ if (!editor || !tableInfo || from === to) return;
2322
+ const { state } = editor;
2323
+ const tableStart = tableInfo.pos;
2324
+ const tableNode = state.doc.nodeAt(tableStart);
2325
+ if (!tableNode) return;
2326
+ const tr = state.tr;
2327
+ tableNode.forEach((row, rowOffset) => {
2328
+ if (row.type.name !== "tableRow") return;
2329
+ const cells = [];
2330
+ row.forEach((cell, cellOffset) => {
2331
+ cells.push({ node: cell, pos: tableStart + 1 + rowOffset + 1 + cellOffset });
2332
+ });
2333
+ if (from >= cells.length || to >= cells.length) return;
2334
+ const reordered = [...cells];
2335
+ const [moved] = reordered.splice(from, 1);
2336
+ reordered.splice(to, 0, moved);
2337
+ const rowPos = tableStart + 1 + rowOffset;
2338
+ const mappedRowPos = tr.mapping.map(rowPos);
2339
+ const newRow = row.type.create(row.attrs, reordered.map((c) => c.node));
2340
+ tr.replaceWith(mappedRowPos, mappedRowPos + row.nodeSize, newRow);
2341
+ });
2342
+ editor.view.dispatch(tr);
2343
+ },
2344
+ [editor, tableInfo]
2345
+ );
2346
+ const moveRow = useCallback11(
2347
+ (from, to) => {
2348
+ if (!editor || !tableInfo || from === to) return;
2349
+ const { state } = editor;
2350
+ const tableStart = tableInfo.pos;
2351
+ const tableNode = state.doc.nodeAt(tableStart);
2352
+ if (!tableNode) return;
2353
+ const rows = [];
2354
+ tableNode.forEach((row) => rows.push(row));
2355
+ if (from >= rows.length || to >= rows.length) return;
2356
+ const reordered = [...rows];
2357
+ const [moved] = reordered.splice(from, 1);
2358
+ reordered.splice(to, 0, moved);
2359
+ const tr = state.tr;
2360
+ tr.replaceWith(
2361
+ tableStart + 1,
2362
+ tableStart + 1 + tableNode.content.size,
2363
+ reordered
2364
+ );
2365
+ editor.view.dispatch(tr);
2366
+ },
2367
+ [editor, tableInfo]
2368
+ );
2369
+ const handleGripDragStart = useCallback11(
2370
+ (type, index, e) => {
2371
+ e.preventDefault();
2372
+ e.stopPropagation();
2373
+ setDropdown(null);
2374
+ const startX = e.clientX;
2375
+ const startY = e.clientY;
2376
+ let hasMoved = false;
2377
+ const dragState = { type, fromIndex: index, toIndex: index };
2378
+ dragRef.current = dragState;
2379
+ setDrag(dragState);
2380
+ const handleMouseMove = (ev) => {
2381
+ const dx = ev.clientX - startX;
2382
+ const dy = ev.clientY - startY;
2383
+ if (!hasMoved && Math.abs(type === "column" ? dx : dy) < 5) return;
2384
+ hasMoved = true;
2385
+ let newIndex = index;
2386
+ if (type === "column") {
2387
+ for (let i = 0; i < colGrips.length; i++) {
2388
+ const mid = colGrips[i].left + colGrips[i].width / 2;
2389
+ if (ev.clientX < mid) {
2390
+ newIndex = i;
2391
+ break;
2392
+ }
2393
+ newIndex = i;
1933
2394
  }
1934
- ) })
2395
+ } else {
2396
+ for (let i = 0; i < rowGrips.length; i++) {
2397
+ const mid = rowGrips[i].top + rowGrips[i].height / 2;
2398
+ if (ev.clientY < mid) {
2399
+ newIndex = i;
2400
+ break;
2401
+ }
2402
+ newIndex = i;
2403
+ }
2404
+ }
2405
+ const updated = { type, fromIndex: index, toIndex: newIndex };
2406
+ dragRef.current = updated;
2407
+ setDrag(updated);
2408
+ };
2409
+ const handleMouseUp = () => {
2410
+ document.removeEventListener("mousemove", handleMouseMove);
2411
+ document.removeEventListener("mouseup", handleMouseUp);
2412
+ const finalDrag = dragRef.current;
2413
+ dragRef.current = null;
2414
+ setDrag(null);
2415
+ if (finalDrag && hasMoved && finalDrag.fromIndex !== finalDrag.toIndex) {
2416
+ if (type === "column") {
2417
+ moveColumn(finalDrag.fromIndex, finalDrag.toIndex);
2418
+ } else {
2419
+ moveRow(finalDrag.fromIndex, finalDrag.toIndex);
2420
+ }
2421
+ }
2422
+ };
2423
+ document.addEventListener("mousemove", handleMouseMove);
2424
+ document.addEventListener("mouseup", handleMouseUp);
2425
+ },
2426
+ [colGrips, rowGrips, moveColumn, moveRow]
2427
+ );
2428
+ if (!editor || !tableInfo || colGrips.length === 0) return null;
2429
+ const GRIP_SIZE = 20;
2430
+ const GRIP_GAP = 4;
2431
+ const gripsVisible = isHovering || !!dropdown;
2432
+ const columnDropdownItems = [
2433
+ {
2434
+ label: "Toggle header column",
2435
+ icon: /* @__PURE__ */ jsx15(IconTableColumn, { size: 16 }),
2436
+ action: () => {
2437
+ editor.chain().focus().toggleHeaderColumn().run();
2438
+ setDropdown(null);
1935
2439
  }
1936
- ) }),
1937
- /* @__PURE__ */ jsx12(VideoBlockMenu, { editor })
1938
- ] });
1939
- };
2440
+ },
2441
+ {
2442
+ label: "Insert column before",
2443
+ icon: /* @__PURE__ */ jsx15(IconColumnInsertLeft, { size: 16 }),
2444
+ action: () => {
2445
+ editor.chain().focus().addColumnBefore().run();
2446
+ setDropdown(null);
2447
+ },
2448
+ separator: true
2449
+ },
2450
+ {
2451
+ label: "Insert column after",
2452
+ icon: /* @__PURE__ */ jsx15(IconColumnInsertRight, { size: 16 }),
2453
+ action: () => {
2454
+ editor.chain().focus().addColumnAfter().run();
2455
+ setDropdown(null);
2456
+ }
2457
+ },
2458
+ {
2459
+ label: "Merge cells",
2460
+ icon: /* @__PURE__ */ jsx15(IconArrowMerge, { size: 16 }),
2461
+ action: () => {
2462
+ editor.chain().focus().mergeCells().run();
2463
+ setDropdown(null);
2464
+ },
2465
+ separator: true
2466
+ },
2467
+ {
2468
+ label: "Split cell",
2469
+ icon: /* @__PURE__ */ jsx15(IconArrowsSplit, { size: 16 }),
2470
+ action: () => {
2471
+ editor.chain().focus().splitCell().run();
2472
+ setDropdown(null);
2473
+ }
2474
+ },
2475
+ {
2476
+ label: "Delete column",
2477
+ icon: /* @__PURE__ */ jsx15(IconColumnRemove, { size: 16 }),
2478
+ action: () => {
2479
+ editor.chain().focus().deleteColumn().run();
2480
+ setDropdown(null);
2481
+ },
2482
+ destructive: true,
2483
+ separator: true
2484
+ }
2485
+ ];
2486
+ const rowDropdownItems = [
2487
+ {
2488
+ label: "Toggle header row",
2489
+ icon: /* @__PURE__ */ jsx15(IconTableRow, { size: 16 }),
2490
+ action: () => {
2491
+ editor.chain().focus().toggleHeaderRow().run();
2492
+ setDropdown(null);
2493
+ }
2494
+ },
2495
+ {
2496
+ label: "Insert row above",
2497
+ icon: /* @__PURE__ */ jsx15(IconRowInsertTop, { size: 16 }),
2498
+ action: () => {
2499
+ editor.chain().focus().addRowBefore().run();
2500
+ setDropdown(null);
2501
+ },
2502
+ separator: true
2503
+ },
2504
+ {
2505
+ label: "Insert row below",
2506
+ icon: /* @__PURE__ */ jsx15(IconRowInsertBottom, { size: 16 }),
2507
+ action: () => {
2508
+ editor.chain().focus().addRowAfter().run();
2509
+ setDropdown(null);
2510
+ }
2511
+ },
2512
+ {
2513
+ label: "Merge cells",
2514
+ icon: /* @__PURE__ */ jsx15(IconArrowMerge, { size: 16 }),
2515
+ action: () => {
2516
+ editor.chain().focus().mergeCells().run();
2517
+ setDropdown(null);
2518
+ },
2519
+ separator: true
2520
+ },
2521
+ {
2522
+ label: "Split cell",
2523
+ icon: /* @__PURE__ */ jsx15(IconArrowsSplit, { size: 16 }),
2524
+ action: () => {
2525
+ editor.chain().focus().splitCell().run();
2526
+ setDropdown(null);
2527
+ }
2528
+ },
2529
+ {
2530
+ label: "Delete row",
2531
+ icon: /* @__PURE__ */ jsx15(IconRowRemove, { size: 16 }),
2532
+ action: () => {
2533
+ editor.chain().focus().deleteRow().run();
2534
+ setDropdown(null);
2535
+ },
2536
+ destructive: true,
2537
+ separator: true
2538
+ }
2539
+ ];
2540
+ const dropdownItems = dropdown?.type === "column" ? columnDropdownItems : rowDropdownItems;
2541
+ return createPortal(
2542
+ /* @__PURE__ */ jsxs15(Fragment5, { children: [
2543
+ colGrips.map((grip, i) => /* @__PURE__ */ jsx15(
2544
+ "button",
2545
+ {
2546
+ type: "button",
2547
+ className: `nph-table-grip nph-table-grip--col${gripsVisible ? " nph-table-grip--visible" : ""}${drag?.type === "column" && drag.fromIndex === i ? " nph-table-grip--dragging" : ""}`,
2548
+ style: {
2549
+ position: "fixed",
2550
+ left: grip.left + grip.width / 2 - GRIP_SIZE / 2,
2551
+ top: grip.top - GRIP_SIZE - GRIP_GAP,
2552
+ width: GRIP_SIZE,
2553
+ height: GRIP_SIZE,
2554
+ cursor: "grab"
2555
+ },
2556
+ onMouseDown: (e) => handleGripDragStart("column", i, e),
2557
+ onClick: (e) => handleColGripClick(i, e),
2558
+ "aria-label": `Column ${i + 1} options`,
2559
+ children: /* @__PURE__ */ jsx15(IconGripHorizontal, { size: 14 })
2560
+ },
2561
+ `col-${i}`
2562
+ )),
2563
+ rowGrips.map((grip, i) => /* @__PURE__ */ jsx15(
2564
+ "button",
2565
+ {
2566
+ type: "button",
2567
+ className: `nph-table-grip nph-table-grip--row${gripsVisible ? " nph-table-grip--visible" : ""}${drag?.type === "row" && drag.fromIndex === i ? " nph-table-grip--dragging" : ""}`,
2568
+ style: {
2569
+ position: "fixed",
2570
+ left: grip.left - GRIP_SIZE - GRIP_GAP,
2571
+ top: grip.top + grip.height / 2 - GRIP_SIZE / 2,
2572
+ width: GRIP_SIZE,
2573
+ height: GRIP_SIZE,
2574
+ cursor: "grab"
2575
+ },
2576
+ onMouseDown: (e) => handleGripDragStart("row", i, e),
2577
+ onClick: (e) => handleRowGripClick(i, e),
2578
+ "aria-label": `Row ${i + 1} options`,
2579
+ children: /* @__PURE__ */ jsx15(IconGripVertical, { size: 14 })
2580
+ },
2581
+ `row-${i}`
2582
+ )),
2583
+ tableRect && /* @__PURE__ */ jsxs15(
2584
+ "button",
2585
+ {
2586
+ type: "button",
2587
+ className: `nph-table-grip nph-table-grip--delete${gripsVisible ? " nph-table-grip--visible" : ""}`,
2588
+ style: {
2589
+ position: "fixed",
2590
+ left: tableRect.left + tableRect.width / 2 - 60,
2591
+ top: tableRect.bottom + GRIP_GAP,
2592
+ width: 120,
2593
+ height: 24
2594
+ },
2595
+ onMouseDown: (e) => e.preventDefault(),
2596
+ onClick: () => {
2597
+ editor.chain().focus().deleteTable().run();
2598
+ },
2599
+ "aria-label": "Delete table",
2600
+ children: [
2601
+ /* @__PURE__ */ jsx15(IconTableOff, { size: 14 }),
2602
+ /* @__PURE__ */ jsx15("span", { style: { fontSize: 12 }, children: "Delete table" })
2603
+ ]
2604
+ }
2605
+ ),
2606
+ dropdown && /* @__PURE__ */ jsx15(
2607
+ "div",
2608
+ {
2609
+ ref: dropdownRef,
2610
+ className: "nph-table-dropdown",
2611
+ style: {
2612
+ position: "fixed",
2613
+ left: dropdown.x,
2614
+ top: dropdown.y,
2615
+ transform: dropdown.type === "column" ? "translateY(-100%)" : void 0
2616
+ },
2617
+ children: dropdownItems.map((item, i) => /* @__PURE__ */ jsxs15("div", { children: [
2618
+ item.separator && i > 0 && /* @__PURE__ */ jsx15("div", { className: "nph-table-dropdown__separator" }),
2619
+ /* @__PURE__ */ jsxs15(
2620
+ "button",
2621
+ {
2622
+ type: "button",
2623
+ className: `nph-table-dropdown__item ${item.destructive ? "nph-table-dropdown__item--destructive" : ""}`,
2624
+ onMouseDown: (e) => e.preventDefault(),
2625
+ onClick: item.action,
2626
+ children: [
2627
+ item.icon,
2628
+ /* @__PURE__ */ jsx15("span", { children: item.label })
2629
+ ]
2630
+ }
2631
+ )
2632
+ ] }, i))
2633
+ }
2634
+ ),
2635
+ drag && drag.fromIndex !== drag.toIndex && tableRect && (drag.type === "column" ? /* @__PURE__ */ jsx15(
2636
+ "div",
2637
+ {
2638
+ className: "nph-table-drop-indicator nph-table-drop-indicator--col",
2639
+ style: {
2640
+ position: "fixed",
2641
+ left: drag.toIndex < colGrips.length ? colGrips[drag.toIndex].left - 1 : colGrips[colGrips.length - 1].left + colGrips[colGrips.length - 1].width,
2642
+ top: tableRect.top,
2643
+ width: 2,
2644
+ height: tableRect.height
2645
+ }
2646
+ }
2647
+ ) : /* @__PURE__ */ jsx15(
2648
+ "div",
2649
+ {
2650
+ className: "nph-table-drop-indicator nph-table-drop-indicator--row",
2651
+ style: {
2652
+ position: "fixed",
2653
+ left: tableRect.left,
2654
+ top: drag.toIndex < rowGrips.length ? rowGrips[drag.toIndex].top - 1 : rowGrips[rowGrips.length - 1].top + rowGrips[rowGrips.length - 1].height,
2655
+ width: tableRect.width,
2656
+ height: 2
2657
+ }
2658
+ }
2659
+ ))
2660
+ ] }),
2661
+ document.body
2662
+ );
2663
+ }
1940
2664
 
1941
2665
  // src/react/Editor.tsx
1942
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1943
- function Editor3({
2666
+ import { useMemo as useMemo2, useState as useState10, useCallback as useCallback12, useRef as useRef11, useEffect as useEffect7 } from "react";
2667
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2668
+ function Editor5({
1944
2669
  content,
1945
2670
  className,
1946
2671
  editable = true,
1947
2672
  immediatelyRender = false,
1948
2673
  showTextMenu = true,
1949
2674
  showSlashMenu = true,
1950
- showImageMenu = true,
2675
+ showImageMenu = false,
2676
+ showDragHandle = true,
1951
2677
  extensions,
1952
2678
  bubbleMenuExtras,
1953
2679
  onUpdate,
@@ -1959,6 +2685,41 @@ function Editor3({
1959
2685
  slashCommand,
1960
2686
  placeholder
1961
2687
  }) {
2688
+ const [actionMenuAnchor, setActionMenuAnchor] = useState10(null);
2689
+ const [actionMenuEditor, setActionMenuEditor] = useState10(null);
2690
+ const actionMenuRef = useRef11(null);
2691
+ useEffect7(() => {
2692
+ if (!actionMenuAnchor) return;
2693
+ const handlePointerDown = (e) => {
2694
+ if (actionMenuRef.current && !actionMenuRef.current.contains(e.target)) {
2695
+ setActionMenuAnchor(null);
2696
+ }
2697
+ };
2698
+ document.addEventListener("pointerdown", handlePointerDown);
2699
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
2700
+ }, [actionMenuAnchor]);
2701
+ const dragHandleCallbacks = useMemo2(
2702
+ () => ({
2703
+ onAddBlock: (editor) => {
2704
+ const { state } = editor;
2705
+ const { selection } = state;
2706
+ const { $anchor } = selection;
2707
+ const topDepth = Math.min($anchor.depth, 1);
2708
+ const endOfBlock = $anchor.end(topDepth);
2709
+ const insertPos = endOfBlock + 1;
2710
+ editor.chain().focus().insertContentAt(insertPos, { type: "paragraph" }).focus(insertPos + 1).run();
2711
+ requestAnimationFrame(() => {
2712
+ editor.commands.insertContent("/");
2713
+ });
2714
+ },
2715
+ onGripClick: (editor, _node, element) => {
2716
+ setActionMenuEditor(editor);
2717
+ setActionMenuAnchor((prev) => prev === element ? null : element);
2718
+ }
2719
+ }),
2720
+ []
2721
+ );
2722
+ const enableDragHandle = showDragHandle && editable;
1962
2723
  const normalizeExtras = (extras) => {
1963
2724
  const result = {
1964
2725
  start: [],
@@ -1975,58 +2736,184 @@ function Editor3({
1975
2736
  };
1976
2737
  const textExtras = normalizeExtras(bubbleMenuExtras?.text);
1977
2738
  const imageExtras = normalizeExtras(bubbleMenuExtras?.image);
1978
- return /* @__PURE__ */ jsx13("div", { className, children: /* @__PURE__ */ jsx13(EditorRoot, { children: /* @__PURE__ */ jsxs13(
1979
- EditorContent,
1980
- {
1981
- onUpdate,
1982
- onCreate,
1983
- immediatelyRender,
1984
- editable,
1985
- content,
1986
- extensions: [
1987
- ...extension_kit_default({
1988
- uploadImage,
1989
- collaboration,
1990
- imageBlockView: ImageBlockView,
1991
- videoBlockView: VideoBlockView,
1992
- mention: mentionOptions,
1993
- reference: referenceOptions,
1994
- slashCommand,
1995
- placeholder
1996
- }),
1997
- ...extensions ?? []
1998
- ],
1999
- editorProps: {
2000
- attributes: {
2001
- class: "nph-editor max-w-none outline-none"
2002
- },
2003
- handleKeyDown: (view, event) => {
2004
- return !!handleCommandNavigation?.(event);
2005
- }
2006
- },
2007
- children: [
2008
- showTextMenu ? /* @__PURE__ */ jsx13(
2009
- TextMenu,
2010
- {
2011
- leadingExtras: textExtras.start,
2012
- trailingExtras: textExtras.end
2739
+ const handleCloseActionMenu = useCallback12(() => {
2740
+ setActionMenuAnchor(null);
2741
+ }, []);
2742
+ return /* @__PURE__ */ jsxs16("div", { className, children: [
2743
+ /* @__PURE__ */ jsx16(EditorRoot, { children: /* @__PURE__ */ jsxs16(
2744
+ EditorContent,
2745
+ {
2746
+ onUpdate,
2747
+ onCreate,
2748
+ immediatelyRender,
2749
+ editable,
2750
+ content,
2751
+ extensions: [
2752
+ ...extension_kit_default({
2753
+ uploadImage,
2754
+ collaboration,
2755
+ imageBlockView: ImageBlockView,
2756
+ videoBlockView: VideoBlockView,
2757
+ mention: mentionOptions,
2758
+ reference: referenceOptions,
2759
+ slashCommand,
2760
+ dragHandle: enableDragHandle,
2761
+ dragHandleCallbacks: enableDragHandle ? dragHandleCallbacks : void 0,
2762
+ placeholder
2763
+ }),
2764
+ ...extensions ?? []
2765
+ ],
2766
+ editorProps: {
2767
+ attributes: {
2768
+ class: "nph-editor max-w-none outline-none"
2769
+ },
2770
+ handleKeyDown: (view, event) => {
2771
+ return !!handleCommandNavigation?.(event);
2013
2772
  }
2014
- ) : null,
2015
- showImageMenu ? /* @__PURE__ */ jsx13(
2016
- ImageMenu,
2773
+ },
2774
+ children: [
2775
+ showTextMenu ? /* @__PURE__ */ jsx16(
2776
+ TextMenu,
2777
+ {
2778
+ leadingExtras: textExtras.start,
2779
+ trailingExtras: textExtras.end
2780
+ }
2781
+ ) : null,
2782
+ showImageMenu ? /* @__PURE__ */ jsx16(
2783
+ ImageMenu,
2784
+ {
2785
+ leadingExtras: imageExtras.start,
2786
+ trailingExtras: imageExtras.end
2787
+ }
2788
+ ) : null,
2789
+ /* @__PURE__ */ jsx16(LinkMenu, {}),
2790
+ /* @__PURE__ */ jsx16(TableMenu, {}),
2791
+ showSlashMenu ? /* @__PURE__ */ jsx16(SlashMenu, {}) : null
2792
+ ]
2793
+ }
2794
+ ) }),
2795
+ actionMenuAnchor && actionMenuEditor && /* @__PURE__ */ jsx16(
2796
+ "div",
2797
+ {
2798
+ ref: actionMenuRef,
2799
+ style: {
2800
+ position: "fixed",
2801
+ zIndex: 1e4,
2802
+ top: actionMenuAnchor.getBoundingClientRect().bottom + 4,
2803
+ left: actionMenuAnchor.getBoundingClientRect().left
2804
+ },
2805
+ children: /* @__PURE__ */ jsx16(
2806
+ BlockActionMenu,
2017
2807
  {
2018
- leadingExtras: imageExtras.start,
2019
- trailingExtras: imageExtras.end
2808
+ editor: actionMenuEditor,
2809
+ onClose: handleCloseActionMenu
2020
2810
  }
2021
- ) : null,
2022
- /* @__PURE__ */ jsx13(LinkMenu, {}),
2023
- showSlashMenu ? /* @__PURE__ */ jsx13(SlashMenu, {}) : null
2024
- ]
2811
+ )
2812
+ }
2813
+ )
2814
+ ] });
2815
+ }
2816
+
2817
+ // src/react/TableOfContents.tsx
2818
+ import { useCurrentEditor as useCurrentEditor6, useEditorState as useEditorState6 } from "@tiptap/react";
2819
+ import { useCallback as useCallback13, useEffect as useEffect8, useRef as useRef12, useState as useState11 } from "react";
2820
+ import { jsx as jsx17 } from "react/jsx-runtime";
2821
+ function TableOfContents({
2822
+ className,
2823
+ itemClassName,
2824
+ activeClassName
2825
+ }) {
2826
+ const { editor } = useCurrentEditor6();
2827
+ const [activeId, setActiveId] = useState11(null);
2828
+ const observerRef = useRef12(null);
2829
+ const headings = useEditorState6({
2830
+ editor,
2831
+ selector: (ctx) => {
2832
+ if (!ctx.editor) return [];
2833
+ const items = [];
2834
+ ctx.editor.state.doc.descendants((node, pos) => {
2835
+ if (node.type.name === "heading") {
2836
+ const id = `heading-${pos}`;
2837
+ items.push({
2838
+ id,
2839
+ level: node.attrs.level,
2840
+ text: node.textContent,
2841
+ pos
2842
+ });
2843
+ }
2844
+ });
2845
+ return items;
2025
2846
  }
2026
- ) }) });
2847
+ });
2848
+ useEffect8(() => {
2849
+ if (!editor || !headings || headings.length === 0) return;
2850
+ observerRef.current?.disconnect();
2851
+ const callback = (entries) => {
2852
+ const visibleEntries = entries.filter((e) => e.isIntersecting);
2853
+ if (visibleEntries.length > 0) {
2854
+ const firstVisible = visibleEntries[0];
2855
+ const id = firstVisible.target.getAttribute("data-toc-id");
2856
+ if (id) setActiveId(id);
2857
+ }
2858
+ };
2859
+ const observer = new IntersectionObserver(callback, {
2860
+ rootMargin: "-80px 0px -70% 0px",
2861
+ threshold: 0
2862
+ });
2863
+ observerRef.current = observer;
2864
+ const editorEl = editor.view.dom;
2865
+ headings.forEach((heading) => {
2866
+ try {
2867
+ const domNode = editor.view.nodeDOM(heading.pos);
2868
+ if (domNode && domNode instanceof HTMLElement) {
2869
+ domNode.setAttribute("data-toc-id", heading.id);
2870
+ observer.observe(domNode);
2871
+ }
2872
+ } catch {
2873
+ }
2874
+ });
2875
+ return () => {
2876
+ observer.disconnect();
2877
+ };
2878
+ }, [editor, headings]);
2879
+ const handleClick = useCallback13(
2880
+ (pos) => {
2881
+ if (!editor) return;
2882
+ editor.chain().focus().setTextSelection(pos + 1).run();
2883
+ try {
2884
+ const domNode = editor.view.nodeDOM(pos);
2885
+ if (domNode && domNode instanceof HTMLElement) {
2886
+ domNode.scrollIntoView({ behavior: "smooth", block: "start" });
2887
+ }
2888
+ } catch {
2889
+ }
2890
+ },
2891
+ [editor]
2892
+ );
2893
+ if (!editor || !headings || headings.length === 0) return null;
2894
+ return /* @__PURE__ */ jsx17("nav", { className: className ?? "nph-toc", children: headings.map((heading) => {
2895
+ const isActive = activeId === heading.id;
2896
+ const itemClass = [
2897
+ itemClassName ?? "nph-toc__item",
2898
+ isActive ? activeClassName ?? "nph-toc__item--active" : ""
2899
+ ].filter(Boolean).join(" ");
2900
+ return /* @__PURE__ */ jsx17(
2901
+ "button",
2902
+ {
2903
+ type: "button",
2904
+ className: itemClass,
2905
+ style: { paddingLeft: `${(heading.level - 1) * 12 + 8}px` },
2906
+ onClick: () => handleClick(heading.pos),
2907
+ title: heading.text,
2908
+ children: heading.text || `Heading ${heading.level}`
2909
+ },
2910
+ heading.id
2911
+ );
2912
+ }) });
2027
2913
  }
2028
2914
  export {
2029
- Editor3 as Editor,
2915
+ Editor5 as Editor,
2916
+ TableOfContents,
2030
2917
  TextMenu
2031
2918
  };
2032
2919
  //# sourceMappingURL=index.js.map