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
@@ -31,13 +31,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/react/index.ts
32
32
  var react_exports = {};
33
33
  __export(react_exports, {
34
- Editor: () => Editor3,
34
+ Editor: () => Editor5,
35
+ TableOfContents: () => TableOfContents,
35
36
  TextMenu: () => TextMenu
36
37
  });
37
38
  module.exports = __toCommonJS(react_exports);
38
39
 
39
40
  // src/headless/index.ts
40
- var import_react11 = require("@tiptap/react");
41
+ var import_react10 = require("@tiptap/react");
41
42
 
42
43
  // src/headless/components/editor.tsx
43
44
  var import_react = require("@tiptap/react");
@@ -69,109 +70,177 @@ EditorContent.displayName = "EditorContent";
69
70
  // src/headless/components/editor-command.tsx
70
71
  var import_jotai4 = require("jotai");
71
72
  var import_react3 = require("react");
72
- var import_cmdk = require("cmdk");
73
+ var import_react_dom = require("react-dom");
73
74
 
74
75
  // src/headless/utils/atoms.ts
75
76
  var import_jotai3 = require("jotai");
76
77
  var queryAtom = (0, import_jotai3.atom)("");
77
78
  var rangeAtom = (0, import_jotai3.atom)(null);
79
+ var slashMenuOpenAtom = (0, import_jotai3.atom)(false);
80
+ var slashMenuRectAtom = (0, import_jotai3.atom)(null);
78
81
 
79
82
  // src/headless/components/editor-command.tsx
80
- var import_tunnel_rat = __toESM(require("tunnel-rat"), 1);
81
83
  var import_jsx_runtime2 = require("react/jsx-runtime");
82
- var commandTunnel = (0, import_tunnel_rat.default)();
83
- var EditorCommandOut = ({
84
- query,
85
- range
86
- }) => {
87
- const setQuery = (0, import_jotai4.useSetAtom)(queryAtom, { store: novelStore });
88
- const setRange = (0, import_jotai4.useSetAtom)(rangeAtom, { store: novelStore });
89
- (0, import_react3.useEffect)(() => {
90
- setQuery(query);
91
- }, [query, setQuery]);
92
- (0, import_react3.useEffect)(() => {
93
- setRange(range);
94
- }, [range, setRange]);
95
- (0, import_react3.useEffect)(() => {
96
- const navigationKeys = ["ArrowUp", "ArrowDown", "Enter"];
97
- const onKeyDown = (e) => {
98
- if (navigationKeys.includes(e.key)) {
99
- e.preventDefault();
100
- const commandRef = document.querySelector("#slash-command");
101
- if (commandRef)
102
- commandRef.dispatchEvent(
103
- new KeyboardEvent("keydown", {
104
- key: e.key,
105
- cancelable: true,
106
- bubbles: true
107
- })
108
- );
109
- return false;
110
- }
111
- };
112
- document.addEventListener("keydown", onKeyDown);
113
- return () => {
114
- document.removeEventListener("keydown", onKeyDown);
115
- };
116
- }, []);
117
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(commandTunnel.Out, {});
118
- };
119
- var CommandAny = import_cmdk.Command;
120
84
  var EditorCommand = (0, import_react3.forwardRef)(
121
85
  ({ children, className, ...rest }, ref) => {
122
- const [query, setQuery] = (0, import_jotai4.useAtom)(queryAtom);
123
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(commandTunnel.In, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
124
- CommandAny,
125
- {
126
- ref,
127
- onKeyDown: (e) => {
128
- e.stopPropagation();
129
- },
130
- id: "slash-command",
131
- className,
132
- ...rest,
133
- children: [
134
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
135
- CommandAny.Input,
136
- {
137
- value: query,
138
- onValueChange: setQuery,
139
- style: { display: "none" }
140
- }
141
- ),
142
- children
143
- ]
86
+ const isOpen = (0, import_jotai4.useAtomValue)(slashMenuOpenAtom, { store: novelStore });
87
+ const rect = (0, import_jotai4.useAtomValue)(slashMenuRectAtom, { store: novelStore });
88
+ const containerRef = (0, import_react3.useRef)(null);
89
+ const [activeIndex, setActiveIndex] = (0, import_react3.useState)(0);
90
+ const contentRef = (0, import_react3.useRef)(null);
91
+ const query = (0, import_jotai4.useAtomValue)(queryAtom, { store: novelStore });
92
+ (0, import_react3.useEffect)(() => {
93
+ setActiveIndex(0);
94
+ }, [query]);
95
+ if (typeof document !== "undefined" && !containerRef.current) {
96
+ const el = document.createElement("div");
97
+ el.style.position = "fixed";
98
+ el.style.zIndex = "9999";
99
+ el.style.minWidth = "240px";
100
+ el.style.display = "none";
101
+ containerRef.current = el;
102
+ }
103
+ (0, import_react3.useEffect)(() => {
104
+ const el = containerRef.current;
105
+ if (!el) return;
106
+ document.body.appendChild(el);
107
+ return () => {
108
+ el.remove();
109
+ };
110
+ }, []);
111
+ (0, import_react3.useLayoutEffect)(() => {
112
+ const container = containerRef.current;
113
+ if (!container) return;
114
+ if (!isOpen || !rect) {
115
+ container.style.display = "none";
116
+ return;
117
+ }
118
+ container.style.display = "";
119
+ const menuHeight = container.offsetHeight || 360;
120
+ const viewportHeight = window.innerHeight;
121
+ const spaceBelow = viewportHeight - rect.bottom;
122
+ const spaceAbove = rect.top;
123
+ let top;
124
+ if (spaceBelow < menuHeight + 16 && spaceAbove > spaceBelow) {
125
+ top = Math.round(rect.top - menuHeight - 8);
126
+ if (top < 8) top = 8;
127
+ } else {
128
+ top = Math.round(rect.bottom + 8);
144
129
  }
145
- ) });
130
+ let left = Math.round(rect.left);
131
+ const menuWidth = container.offsetWidth || 280;
132
+ if (left + menuWidth > window.innerWidth - 8) {
133
+ left = window.innerWidth - menuWidth - 8;
134
+ }
135
+ if (left < 8) left = 8;
136
+ container.style.top = `${top}px`;
137
+ container.style.left = `${left}px`;
138
+ }, [isOpen, rect]);
139
+ const handleKeyDown = (0, import_react3.useCallback)(
140
+ (e) => {
141
+ if (!isOpen || !contentRef.current) return;
142
+ const items = contentRef.current.querySelectorAll("[role='option']");
143
+ if (items.length === 0) return;
144
+ if (e.key === "ArrowDown") {
145
+ e.preventDefault();
146
+ setActiveIndex((prev) => {
147
+ const next = Math.min(prev + 1, items.length - 1);
148
+ items[next]?.scrollIntoView({ block: "nearest" });
149
+ return next;
150
+ });
151
+ } else if (e.key === "ArrowUp") {
152
+ e.preventDefault();
153
+ setActiveIndex((prev) => {
154
+ const next = Math.max(prev - 1, 0);
155
+ items[next]?.scrollIntoView({ block: "nearest" });
156
+ return next;
157
+ });
158
+ } else if (e.key === "Enter") {
159
+ e.preventDefault();
160
+ const activeItem = items[activeIndex];
161
+ activeItem?.click();
162
+ }
163
+ },
164
+ [isOpen, activeIndex]
165
+ );
166
+ (0, import_react3.useEffect)(() => {
167
+ if (!isOpen) return;
168
+ document.addEventListener("keydown", handleKeyDown);
169
+ return () => document.removeEventListener("keydown", handleKeyDown);
170
+ }, [isOpen, handleKeyDown]);
171
+ (0, import_react3.useEffect)(() => {
172
+ if (!contentRef.current) return;
173
+ const items = contentRef.current.querySelectorAll("[role='option']");
174
+ items.forEach((item, i) => {
175
+ if (i === activeIndex) {
176
+ item.setAttribute("aria-selected", "true");
177
+ } else {
178
+ item.removeAttribute("aria-selected");
179
+ }
180
+ });
181
+ });
182
+ if (!isOpen || !containerRef.current) return null;
183
+ return (0, import_react_dom.createPortal)(
184
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
185
+ "div",
186
+ {
187
+ ref: (el) => {
188
+ contentRef.current = el;
189
+ if (typeof ref === "function") ref(el);
190
+ else if (ref) ref.current = el;
191
+ },
192
+ id: "slash-command",
193
+ className,
194
+ ...rest,
195
+ children
196
+ }
197
+ ),
198
+ containerRef.current
199
+ );
200
+ }
201
+ );
202
+ var EditorCommandList = (0, import_react3.forwardRef)(
203
+ ({ children, ...rest }, ref) => {
204
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref, role: "listbox", ...rest, children });
146
205
  }
147
206
  );
148
- var EditorCommandList = import_cmdk.Command.List;
149
207
  EditorCommand.displayName = "EditorCommand";
208
+ EditorCommandList.displayName = "EditorCommandList";
150
209
 
151
210
  // src/headless/components/editor-command-item.tsx
152
211
  var import_react4 = require("react");
153
- var import_cmdk2 = require("cmdk");
154
212
  var import_react5 = require("@tiptap/react");
155
213
  var import_jotai5 = require("jotai");
156
214
  var import_jsx_runtime3 = require("react/jsx-runtime");
157
- var CommandItemAny = import_cmdk2.CommandItem;
158
- var CommandEmptyAny = import_cmdk2.CommandEmpty;
159
- var EditorCommandItem = (0, import_react4.forwardRef)(({ children, onCommand, ...rest }, ref) => {
215
+ var EditorCommandItem = (0, import_react4.forwardRef)(({ children, onCommand, value, className, ...rest }, ref) => {
160
216
  const { editor } = (0, import_react5.useCurrentEditor)();
161
- const range = (0, import_jotai5.useAtomValue)(rangeAtom);
217
+ const range = (0, import_jotai5.useAtomValue)(rangeAtom, { store: novelStore });
218
+ const query = (0, import_jotai5.useAtomValue)(queryAtom, { store: novelStore });
162
219
  if (!editor || !range) return null;
220
+ if (query && value) {
221
+ const searchText = value.toLowerCase();
222
+ const q = query.toLowerCase();
223
+ if (!searchText.includes(q)) return null;
224
+ }
163
225
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
164
- CommandItemAny,
226
+ "div",
165
227
  {
166
228
  ref,
229
+ role: "option",
230
+ className,
231
+ onClick: () => onCommand({ editor, range }),
167
232
  ...rest,
168
- onSelect: () => onCommand({ editor, range }),
169
233
  children
170
234
  }
171
235
  );
172
236
  });
173
237
  EditorCommandItem.displayName = "EditorCommandItem";
174
- var EditorCommandEmpty = CommandEmptyAny;
238
+ var EditorCommandEmpty = (0, import_react4.forwardRef)(
239
+ ({ children, ...rest }, ref) => {
240
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref, ...rest, children });
241
+ }
242
+ );
243
+ EditorCommandEmpty.displayName = "EditorCommandEmpty";
175
244
 
176
245
  // src/headless/extensions/index.ts
177
246
  var import_starter_kit = require("@tiptap/starter-kit");
@@ -919,11 +988,218 @@ var renderMentionSuggestion = () => {
919
988
  };
920
989
  };
921
990
 
991
+ // src/headless/extensions/DragHandle/DragHandle.ts
992
+ var import_extension_drag_handle = __toESM(require("@tiptap/extension-drag-handle"), 1);
993
+ var currentCallbacks = {};
994
+ var currentNode = null;
995
+ var currentEditor = null;
996
+ function setDragHandleCallbacks(callbacks) {
997
+ currentCallbacks = callbacks;
998
+ }
999
+ function createDragHandleElement() {
1000
+ const container = document.createElement("div");
1001
+ container.className = "nph-drag-handle";
1002
+ const plusBtn = document.createElement("button");
1003
+ plusBtn.className = "nph-drag-handle__btn";
1004
+ plusBtn.type = "button";
1005
+ plusBtn.setAttribute("aria-label", "Add block");
1006
+ plusBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>`;
1007
+ plusBtn.addEventListener("click", (e) => {
1008
+ e.preventDefault();
1009
+ e.stopPropagation();
1010
+ if (currentEditor) {
1011
+ currentCallbacks.onAddBlock?.(currentEditor, currentNode);
1012
+ }
1013
+ });
1014
+ const gripBtn = document.createElement("button");
1015
+ gripBtn.className = "nph-drag-handle__btn nph-drag-handle__grip";
1016
+ gripBtn.type = "button";
1017
+ gripBtn.setAttribute("aria-label", "Drag to reorder");
1018
+ gripBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" 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>`;
1019
+ gripBtn.addEventListener("click", (e) => {
1020
+ e.preventDefault();
1021
+ e.stopPropagation();
1022
+ if (currentEditor) {
1023
+ currentCallbacks.onGripClick?.(currentEditor, currentNode, container);
1024
+ }
1025
+ });
1026
+ container.appendChild(plusBtn);
1027
+ container.appendChild(gripBtn);
1028
+ return container;
1029
+ }
1030
+ var DragHandle = import_extension_drag_handle.default.configure({
1031
+ render: createDragHandleElement,
1032
+ nested: true,
1033
+ onNodeChange: ({ node, editor }) => {
1034
+ currentNode = node;
1035
+ currentEditor = editor;
1036
+ }
1037
+ });
1038
+
1039
+ // src/headless/extensions/Table/index.ts
1040
+ var import_extension_table = require("@tiptap/extension-table");
1041
+ var import_extension_table2 = require("@tiptap/extension-table");
1042
+
1043
+ // src/headless/extensions/MarkdownPaste.ts
1044
+ var import_core3 = require("@tiptap/core");
1045
+ var import_state3 = require("@tiptap/pm/state");
1046
+ var import_markdown = require("@tiptap/pm/markdown");
1047
+ var markdownPastePluginKey = new import_state3.PluginKey("markdownPaste");
1048
+ function looksLikeMarkdown(text) {
1049
+ const patterns = [
1050
+ /^#{1,6}\s/m,
1051
+ // headings
1052
+ /^\s*[-*+]\s/m,
1053
+ // unordered list
1054
+ /^\s*\d+\.\s/m,
1055
+ // ordered list
1056
+ /^\s*>\s/m,
1057
+ // blockquote
1058
+ /\|.+\|/m,
1059
+ // table
1060
+ /^```/m,
1061
+ // fenced code block
1062
+ /\*\*.+\*\*/,
1063
+ // bold
1064
+ /\*.+\*/,
1065
+ // italic
1066
+ /~~.+~~/,
1067
+ // strikethrough
1068
+ /`[^`]+`/,
1069
+ // inline code
1070
+ /^\s*---\s*$/m,
1071
+ // horizontal rule
1072
+ /^\s*\*\*\*\s*$/m,
1073
+ // horizontal rule alt
1074
+ /\[.+\]\(.+\)/,
1075
+ // links
1076
+ /!\[.*\]\(.+\)/
1077
+ // images
1078
+ ];
1079
+ const hasMarkdown = patterns.some((p) => p.test(text));
1080
+ const isHtml = /^<[a-z][\s\S]*>/i.test(text.trim());
1081
+ return hasMarkdown && !isHtml;
1082
+ }
1083
+ function buildParser(schema) {
1084
+ const md = import_markdown.defaultMarkdownParser.tokenizer;
1085
+ const tokens = {};
1086
+ if (schema.nodes.paragraph) tokens.paragraph = { block: "paragraph" };
1087
+ if (schema.nodes.heading) {
1088
+ tokens.heading = {
1089
+ block: "heading",
1090
+ getAttrs: (tok) => ({ level: Number(tok.tag.slice(1)) })
1091
+ };
1092
+ }
1093
+ if (schema.nodes.blockquote) tokens.blockquote = { block: "blockquote" };
1094
+ if (schema.nodes.bulletList) tokens.bullet_list = { block: "bulletList" };
1095
+ if (schema.nodes.orderedList) {
1096
+ tokens.ordered_list = {
1097
+ block: "orderedList",
1098
+ getAttrs: (tok) => ({ start: Number(tok.attrGet("start") || 1) })
1099
+ };
1100
+ }
1101
+ if (schema.nodes.listItem) tokens.list_item = { block: "listItem" };
1102
+ if (schema.nodes.codeBlock) {
1103
+ tokens.code_block = { block: "codeBlock", noCloseToken: true };
1104
+ tokens.fence = {
1105
+ block: "codeBlock",
1106
+ getAttrs: (tok) => ({ language: tok.info || "" }),
1107
+ noCloseToken: true
1108
+ };
1109
+ }
1110
+ if (schema.nodes.horizontalRule) {
1111
+ tokens.hr = { node: "horizontalRule" };
1112
+ }
1113
+ if (schema.nodes.hardBreak) {
1114
+ tokens.hardbreak = { node: "hardBreak" };
1115
+ }
1116
+ if (schema.nodes.image) {
1117
+ tokens.image = {
1118
+ node: "image",
1119
+ getAttrs: (tok) => ({
1120
+ src: tok.attrGet("src"),
1121
+ title: tok.attrGet("title") || null,
1122
+ alt: tok.children?.[0]?.content || null
1123
+ })
1124
+ };
1125
+ }
1126
+ if (schema.nodes.table) {
1127
+ tokens.table = { block: "table" };
1128
+ tokens.thead = { ignore: true };
1129
+ tokens.tbody = { ignore: true };
1130
+ tokens.tr = { block: "tableRow" };
1131
+ tokens.th = { block: "tableHeader" };
1132
+ tokens.td = { block: "tableCell" };
1133
+ }
1134
+ if (schema.marks.bold || schema.marks.strong) {
1135
+ tokens.strong = { mark: schema.marks.bold ? "bold" : "strong" };
1136
+ }
1137
+ if (schema.marks.italic || schema.marks.em) {
1138
+ tokens.em = { mark: schema.marks.italic ? "italic" : "em" };
1139
+ }
1140
+ if (schema.marks.code) {
1141
+ tokens.code_inline = { mark: "code", noCloseToken: true };
1142
+ }
1143
+ if (schema.marks.link) {
1144
+ tokens.link = {
1145
+ mark: "link",
1146
+ getAttrs: (tok) => ({
1147
+ href: tok.attrGet("href"),
1148
+ title: tok.attrGet("title") || null
1149
+ })
1150
+ };
1151
+ }
1152
+ if (schema.marks.strike || schema.marks.strikethrough) {
1153
+ tokens.s = { mark: schema.marks.strike ? "strike" : "strikethrough" };
1154
+ }
1155
+ try {
1156
+ return new import_markdown.MarkdownParser(schema, md, tokens);
1157
+ } catch {
1158
+ return null;
1159
+ }
1160
+ }
1161
+ var MarkdownPaste = import_core3.Extension.create({
1162
+ name: "markdownPaste",
1163
+ addProseMirrorPlugins() {
1164
+ const schema = this.editor.schema;
1165
+ let parser = null;
1166
+ return [
1167
+ new import_state3.Plugin({
1168
+ key: markdownPastePluginKey,
1169
+ props: {
1170
+ handlePaste(view, event) {
1171
+ const clipboardData = event.clipboardData;
1172
+ if (!clipboardData) return false;
1173
+ const html = clipboardData.getData("text/html");
1174
+ if (html && html.trim().length > 0) return false;
1175
+ const text = clipboardData.getData("text/plain");
1176
+ if (!text || !looksLikeMarkdown(text)) return false;
1177
+ if (!parser) {
1178
+ parser = buildParser(schema);
1179
+ }
1180
+ if (!parser) return false;
1181
+ try {
1182
+ const doc = parser.parse(text);
1183
+ if (!doc || doc.content.size === 0) return false;
1184
+ const { tr } = view.state;
1185
+ const slice = doc.slice(0, doc.content.size);
1186
+ tr.replaceSelection(slice);
1187
+ view.dispatch(tr);
1188
+ return true;
1189
+ } catch {
1190
+ return false;
1191
+ }
1192
+ }
1193
+ }
1194
+ })
1195
+ ];
1196
+ }
1197
+ });
1198
+
922
1199
  // src/headless/extensions/slash-command.tsx
923
- var import_react10 = require("@tiptap/react");
924
1200
  var import_suggestion = __toESM(require("@tiptap/suggestion"), 1);
925
- var import_core3 = require("@tiptap/core");
926
- var Command2 = import_core3.Extension.create({
1201
+ var import_core4 = require("@tiptap/core");
1202
+ var Command = import_core4.Extension.create({
927
1203
  name: "slash-command",
928
1204
  addOptions() {
929
1205
  return {
@@ -941,7 +1217,6 @@ var Command2 = import_core3.Extension.create({
941
1217
  (0, import_suggestion.default)({
942
1218
  editor: this.editor,
943
1219
  char: base.char ?? "/",
944
- // Only trigger slash command at start of line or after whitespace
945
1220
  startOfLine: base.startOfLine ?? true,
946
1221
  items: base.items ?? (() => ["/"]),
947
1222
  command: (ctx) => {
@@ -949,76 +1224,63 @@ var Command2 = import_core3.Extension.create({
949
1224
  ctx.props.command({ editor: ctx.editor, range: ctx.range });
950
1225
  }
951
1226
  },
952
- ...base
1227
+ ...base,
1228
+ render: () => {
1229
+ return {
1230
+ onStart: (props) => {
1231
+ const { selection } = props.editor.state;
1232
+ const parentNode = selection.$from.node(selection.$from.depth);
1233
+ const blockType = parentNode.type.name;
1234
+ if (blockType === "codeBlock") return false;
1235
+ const { $from } = selection;
1236
+ const marks = $from.marks();
1237
+ if (marks.some((mark) => mark.type.name === "code" || mark.type.name === "link")) {
1238
+ return false;
1239
+ }
1240
+ novelStore.set(queryAtom, props.query ?? "");
1241
+ novelStore.set(rangeAtom, props.range ?? null);
1242
+ novelStore.set(slashMenuOpenAtom, true);
1243
+ const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
1244
+ novelStore.set(slashMenuRectAtom, rect);
1245
+ },
1246
+ onUpdate: (props) => {
1247
+ novelStore.set(queryAtom, props.query ?? "");
1248
+ novelStore.set(rangeAtom, props.range ?? null);
1249
+ const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
1250
+ novelStore.set(slashMenuRectAtom, rect);
1251
+ },
1252
+ onKeyDown: ({ event }) => {
1253
+ if (event.key === "Escape") {
1254
+ novelStore.set(slashMenuOpenAtom, false);
1255
+ return true;
1256
+ }
1257
+ if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
1258
+ const slashCommand = document.querySelector("#slash-command");
1259
+ if (slashCommand) {
1260
+ slashCommand.dispatchEvent(
1261
+ new KeyboardEvent("keydown", {
1262
+ key: event.key,
1263
+ cancelable: true,
1264
+ bubbles: true
1265
+ })
1266
+ );
1267
+ return true;
1268
+ }
1269
+ }
1270
+ return false;
1271
+ },
1272
+ onExit: () => {
1273
+ novelStore.set(slashMenuOpenAtom, false);
1274
+ novelStore.set(queryAtom, "");
1275
+ novelStore.set(rangeAtom, null);
1276
+ novelStore.set(slashMenuRectAtom, null);
1277
+ }
1278
+ };
1279
+ }
953
1280
  })
954
1281
  ];
955
1282
  }
956
1283
  });
957
- var renderItems = (elementRef) => {
958
- let component = null;
959
- let container = null;
960
- const destroy = () => {
961
- component?.destroy();
962
- component = null;
963
- if (container) {
964
- container.remove();
965
- container = null;
966
- }
967
- };
968
- const updatePosition = (clientRect) => {
969
- if (!container || !clientRect) return;
970
- const top = Math.round(clientRect.bottom + 8);
971
- const left = Math.round(clientRect.left);
972
- container.style.top = `${top}px`;
973
- container.style.left = `${left}px`;
974
- };
975
- return {
976
- onStart: (props) => {
977
- const { selection } = props.editor.state;
978
- const parentNode = selection.$from.node(selection.$from.depth);
979
- const blockType = parentNode.type.name;
980
- if (blockType === "codeBlock") return false;
981
- const { $from } = selection;
982
- const marks = $from.marks();
983
- if (marks.some((mark) => mark.type.name === "code" || mark.type.name === "link")) {
984
- return false;
985
- }
986
- component = new import_react10.ReactRenderer(EditorCommandOut, {
987
- props: {
988
- query: props.query ?? "",
989
- range: props.range
990
- },
991
- editor: props.editor
992
- });
993
- container = document.createElement("div");
994
- container.style.position = "fixed";
995
- container.style.zIndex = "9999";
996
- container.style.minWidth = "240px";
997
- (elementRef?.current ?? document.body).appendChild(container);
998
- container.appendChild(component.element);
999
- const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
1000
- if (rect) updatePosition(rect);
1001
- },
1002
- onUpdate: (props) => {
1003
- component?.updateProps({
1004
- query: props.query ?? "",
1005
- range: props.range
1006
- });
1007
- const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
1008
- if (rect) updatePosition(rect);
1009
- },
1010
- onKeyDown: ({ event }) => {
1011
- if (event.key === "Escape") {
1012
- destroy();
1013
- return true;
1014
- }
1015
- return false;
1016
- },
1017
- onExit: () => {
1018
- destroy();
1019
- }
1020
- };
1021
- };
1022
1284
  var handleCommandNavigation = (event) => {
1023
1285
  if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
1024
1286
  const slashCommand = document.querySelector("#slash-command");
@@ -1028,11 +1290,13 @@ var handleCommandNavigation = (event) => {
1028
1290
 
1029
1291
  // src/headless/extensions/extension-kit.ts
1030
1292
  var import_extension_collaboration = __toESM(require("@tiptap/extension-collaboration"), 1);
1293
+ var import_extension_collaboration_caret = __toESM(require("@tiptap/extension-collaboration-caret"), 1);
1294
+ var import_extension_underline = __toESM(require("@tiptap/extension-underline"), 1);
1031
1295
 
1032
1296
  // src/headless/extensions/VideoBlock/VideoBlock.ts
1033
- var import_core4 = require("@tiptap/core");
1034
- var import_react12 = require("@tiptap/react");
1035
- var VideoBlock = import_core4.Node.create({
1297
+ var import_core5 = require("@tiptap/core");
1298
+ var import_react11 = require("@tiptap/react");
1299
+ var VideoBlock = import_core5.Node.create({
1036
1300
  name: "videoBlock",
1037
1301
  group: "block",
1038
1302
  defining: true,
@@ -1078,7 +1342,7 @@ var VideoBlock = import_core4.Node.create({
1078
1342
  renderHTML({ HTMLAttributes }) {
1079
1343
  return [
1080
1344
  "div",
1081
- (0, import_core4.mergeAttributes)(HTMLAttributes, { "data-type": "video-block" })
1345
+ (0, import_core5.mergeAttributes)(HTMLAttributes, { "data-type": "video-block" })
1082
1346
  ];
1083
1347
  },
1084
1348
  addCommands() {
@@ -1097,7 +1361,7 @@ var VideoBlock = import_core4.Node.create({
1097
1361
  },
1098
1362
  addNodeView() {
1099
1363
  if (this.options.nodeView) {
1100
- return (0, import_react12.ReactNodeViewRenderer)(this.options.nodeView);
1364
+ return (0, import_react11.ReactNodeViewRenderer)(this.options.nodeView);
1101
1365
  }
1102
1366
  return null;
1103
1367
  }
@@ -1110,6 +1374,7 @@ var ExtensionKit = (options) => {
1110
1374
  import_starter_kit.StarterKit.configure({ codeBlock: false, link: false }),
1111
1375
  CodeBlock,
1112
1376
  Link,
1377
+ import_extension_underline.default,
1113
1378
  ImageBlock.configure({
1114
1379
  uploadImage: options?.uploadImage,
1115
1380
  nodeView: options?.imageBlockView
@@ -1128,14 +1393,29 @@ var ExtensionKit = (options) => {
1128
1393
  return enableSlashCommand ? "Press '/' for commands" : "";
1129
1394
  },
1130
1395
  includeChildren: true
1131
- })
1396
+ }),
1397
+ MarkdownPaste
1132
1398
  ];
1399
+ if (options?.table !== false) {
1400
+ extensions.push(
1401
+ import_extension_table.TableKit.configure({
1402
+ resizable: true,
1403
+ lastColumnResizable: true,
1404
+ allowTableNodeSelection: true
1405
+ })
1406
+ );
1407
+ }
1408
+ if (options?.dragHandle !== false) {
1409
+ extensions.push(DragHandle);
1410
+ if (options?.dragHandleCallbacks) {
1411
+ setDragHandleCallbacks(options.dragHandleCallbacks);
1412
+ }
1413
+ }
1133
1414
  if (enableSlashCommand) {
1134
1415
  extensions.push(
1135
- Command2.configure({
1416
+ Command.configure({
1136
1417
  suggestion: {
1137
1418
  char: "/",
1138
- render: renderItems,
1139
1419
  allowSpaces: true,
1140
1420
  allowedPrefixes: null
1141
1421
  }
@@ -1161,19 +1441,44 @@ var ExtensionKit = (options) => {
1161
1441
  field: options.collaboration.field
1162
1442
  })
1163
1443
  );
1444
+ if (options.collaboration.provider && options.collaboration.user) {
1445
+ extensions.push(
1446
+ import_extension_collaboration_caret.default.configure({
1447
+ provider: options.collaboration.provider,
1448
+ user: options.collaboration.user,
1449
+ render: (user) => {
1450
+ const cursor = document.createElement("span");
1451
+ cursor.classList.add("nph-collab-caret");
1452
+ cursor.setAttribute("style", `border-color: ${user.color || "#3b82f6"}`);
1453
+ const label = document.createElement("div");
1454
+ label.classList.add("nph-collab-caret__label");
1455
+ label.setAttribute("style", `background-color: ${user.color || "#3b82f6"}`);
1456
+ label.insertBefore(document.createTextNode(user.name || "Anonymous"), null);
1457
+ cursor.insertBefore(label, null);
1458
+ return cursor;
1459
+ },
1460
+ selectionRender: (user) => ({
1461
+ nodeName: "span",
1462
+ class: "nph-collab-selection",
1463
+ style: `background-color: ${user.color || "#3b82f6"}20`
1464
+ })
1465
+ })
1466
+ );
1467
+ }
1164
1468
  }
1165
1469
  return extensions;
1166
1470
  };
1167
1471
  var extension_kit_default = ExtensionKit;
1168
1472
 
1169
1473
  // src/react/menus/TextMenu.tsx
1170
- var import_react14 = require("@tiptap/react");
1474
+ var import_state4 = require("@tiptap/pm/state");
1475
+ var import_react13 = require("@tiptap/react");
1171
1476
  var import_menus = require("@tiptap/react/menus");
1172
1477
  var import_icons_react2 = require("@tabler/icons-react");
1173
- var import_react15 = require("react");
1478
+ var import_react14 = require("react");
1174
1479
 
1175
1480
  // src/react/menus/MenuList.tsx
1176
- var import_react13 = require("react");
1481
+ var import_react12 = require("react");
1177
1482
  var import_icons_react = require("@tabler/icons-react");
1178
1483
  var import_jsx_runtime6 = require("react/jsx-runtime");
1179
1484
  function MenuList({
@@ -1183,10 +1488,10 @@ function MenuList({
1183
1488
  onSelect,
1184
1489
  buttonClassName
1185
1490
  }) {
1186
- const [open, setOpen] = (0, import_react13.useState)(false);
1187
- const [label, setLabel] = (0, import_react13.useState)("Paragraph");
1188
- const rootRef = (0, import_react13.useRef)(null);
1189
- const computeLabel = (0, import_react13.useCallback)(() => {
1491
+ const [open, setOpen] = (0, import_react12.useState)(false);
1492
+ const [label, setLabel] = (0, import_react12.useState)("Paragraph");
1493
+ const rootRef = (0, import_react12.useRef)(null);
1494
+ const computeLabel = (0, import_react12.useCallback)(() => {
1190
1495
  if (!editor) return "Paragraph";
1191
1496
  if (editor.isActive("heading", { level: 1 })) return "Heading 1";
1192
1497
  if (editor.isActive("heading", { level: 2 })) return "Heading 2";
@@ -1199,7 +1504,7 @@ function MenuList({
1199
1504
  if (editor.isActive("codeBlock")) return "Code Block";
1200
1505
  return "Paragraph";
1201
1506
  }, [editor]);
1202
- (0, import_react13.useEffect)(() => {
1507
+ (0, import_react12.useEffect)(() => {
1203
1508
  setLabel(computeLabel());
1204
1509
  if (!editor) return;
1205
1510
  const update = () => setLabel(computeLabel());
@@ -1212,7 +1517,7 @@ function MenuList({
1212
1517
  editor.off("update", update);
1213
1518
  };
1214
1519
  }, [editor, computeLabel]);
1215
- (0, import_react13.useEffect)(() => {
1520
+ (0, import_react12.useEffect)(() => {
1216
1521
  if (!editor) return;
1217
1522
  const close = () => setOpen(false);
1218
1523
  editor.on("selectionUpdate", close);
@@ -1222,7 +1527,7 @@ function MenuList({
1222
1527
  editor.off("blur", close);
1223
1528
  };
1224
1529
  }, [editor]);
1225
- (0, import_react13.useEffect)(() => {
1530
+ (0, import_react12.useEffect)(() => {
1226
1531
  const handlePointerDown = (e) => {
1227
1532
  if (!open) return;
1228
1533
  const el = rootRef.current;
@@ -1237,7 +1542,7 @@ function MenuList({
1237
1542
  onSelect?.();
1238
1543
  setOpen(false);
1239
1544
  };
1240
- const isActive = (0, import_react13.useCallback)((name) => label === name, [label]);
1545
+ const isActive = (0, import_react12.useCallback)((name) => label === name, [label]);
1241
1546
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { ref: rootRef, className: "nph-dropdown", children: [
1242
1547
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1243
1548
  "button",
@@ -1425,22 +1730,26 @@ function MenuList({
1425
1730
 
1426
1731
  // src/react/menus/TextMenu.tsx
1427
1732
  var import_jsx_runtime7 = require("react/jsx-runtime");
1733
+ function Separator() {
1734
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "nph-bubble-separator" });
1735
+ }
1428
1736
  function TextMenu({
1429
1737
  className,
1430
1738
  leadingExtras,
1431
1739
  trailingExtras
1432
1740
  }) {
1433
- const { editor } = (0, import_react14.useCurrentEditor)();
1434
- const [isAddingLink, setIsAddingLink] = (0, import_react15.useState)(false);
1435
- const [linkUrl, setLinkUrl] = (0, import_react15.useState)("");
1436
- const linkInputRef = (0, import_react15.useRef)(null);
1437
- const editorState = (0, import_react14.useEditorState)({
1741
+ const { editor } = (0, import_react13.useCurrentEditor)();
1742
+ const [isAddingLink, setIsAddingLink] = (0, import_react14.useState)(false);
1743
+ const [linkUrl, setLinkUrl] = (0, import_react14.useState)("");
1744
+ const linkInputRef = (0, import_react14.useRef)(null);
1745
+ const editorState = (0, import_react13.useEditorState)({
1438
1746
  editor,
1439
1747
  selector: (ctx) => {
1440
1748
  if (!ctx.editor) return null;
1441
1749
  return {
1442
1750
  isBold: ctx.editor.isActive("bold"),
1443
1751
  isItalic: ctx.editor.isActive("italic"),
1752
+ isUnderline: ctx.editor.isActive("underline"),
1444
1753
  isStrike: ctx.editor.isActive("strike"),
1445
1754
  isCode: ctx.editor.isActive("code"),
1446
1755
  isCodeBlock: ctx.editor.isActive("codeBlock"),
@@ -1451,7 +1760,7 @@ function TextMenu({
1451
1760
  };
1452
1761
  }
1453
1762
  });
1454
- (0, import_react15.useEffect)(() => {
1763
+ (0, import_react14.useEffect)(() => {
1455
1764
  if (isAddingLink && linkInputRef.current) {
1456
1765
  linkInputRef.current.focus();
1457
1766
  }
@@ -1486,6 +1795,7 @@ function TextMenu({
1486
1795
  }
1487
1796
  return has;
1488
1797
  };
1798
+ const isInCode = editorState.isCode || editorState.isCodeBlock;
1489
1799
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1490
1800
  import_menus.BubbleMenu,
1491
1801
  {
@@ -1494,7 +1804,7 @@ function TextMenu({
1494
1804
  if (e.isActive("imageBlock")) return false;
1495
1805
  if (e.isActive("videoBlock")) return false;
1496
1806
  const { selection } = state;
1497
- if (selection.constructor.name === "NodeSelection") return false;
1807
+ if (selection instanceof import_state4.NodeSelection) return false;
1498
1808
  if (from === to) return false;
1499
1809
  if (e.isActive("link")) return false;
1500
1810
  let hasImage = false;
@@ -1508,8 +1818,9 @@ function TextMenu({
1508
1818
  return true;
1509
1819
  },
1510
1820
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: className ? `bubble-menu ${className}` : "bubble-menu", children: [
1511
- leadingExtras && editor ? leadingExtras.map((renderExtra, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react15.Fragment, { children: renderExtra(editor) }, `leading-extra-${index}`)) : null,
1821
+ leadingExtras && editor ? leadingExtras.map((renderExtra, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react14.Fragment, { children: renderExtra(editor) }, `leading-extra-${index}`)) : null,
1512
1822
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MenuList, { editor }),
1823
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Separator, {}),
1513
1824
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1514
1825
  "button",
1515
1826
  {
@@ -1517,8 +1828,8 @@ function TextMenu({
1517
1828
  onMouseDown: (e) => e.preventDefault(),
1518
1829
  onClick: () => editor.chain().focus().toggleBold().run(),
1519
1830
  className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isBold ? " is-active" : ""}`,
1520
- disabled: editorState.isCode || editorState.isCodeBlock,
1521
- "aria-disabled": editorState.isCode || editorState.isCodeBlock,
1831
+ disabled: isInCode,
1832
+ "aria-disabled": isInCode,
1522
1833
  "aria-pressed": editorState.isBold,
1523
1834
  "aria-label": "Toggle bold",
1524
1835
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_icons_react2.IconBold, { size: 16 })
@@ -1531,13 +1842,27 @@ function TextMenu({
1531
1842
  onMouseDown: (e) => e.preventDefault(),
1532
1843
  onClick: () => editor.chain().focus().toggleItalic().run(),
1533
1844
  className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isItalic ? " is-active" : ""}`,
1534
- disabled: editorState.isCode || editorState.isCodeBlock,
1535
- "aria-disabled": editorState.isCode || editorState.isCodeBlock,
1845
+ disabled: isInCode,
1846
+ "aria-disabled": isInCode,
1536
1847
  "aria-pressed": editorState.isItalic,
1537
1848
  "aria-label": "Toggle italic",
1538
1849
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_icons_react2.IconItalic, { size: 16 })
1539
1850
  }
1540
1851
  ),
1852
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1853
+ "button",
1854
+ {
1855
+ type: "button",
1856
+ onMouseDown: (e) => e.preventDefault(),
1857
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
1858
+ className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isUnderline ? " is-active" : ""}`,
1859
+ disabled: isInCode,
1860
+ "aria-disabled": isInCode,
1861
+ "aria-pressed": editorState.isUnderline,
1862
+ "aria-label": "Toggle underline",
1863
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_icons_react2.IconUnderline, { size: 16 })
1864
+ }
1865
+ ),
1541
1866
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1542
1867
  "button",
1543
1868
  {
@@ -1545,13 +1870,28 @@ function TextMenu({
1545
1870
  onMouseDown: (e) => e.preventDefault(),
1546
1871
  onClick: () => editor.chain().focus().toggleStrike().run(),
1547
1872
  className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isStrike ? " is-active" : ""}`,
1548
- disabled: editorState.isCode || editorState.isCodeBlock,
1549
- "aria-disabled": editorState.isCode || editorState.isCodeBlock,
1873
+ disabled: isInCode,
1874
+ "aria-disabled": isInCode,
1550
1875
  "aria-pressed": editorState.isStrike,
1551
1876
  "aria-label": "Toggle strike",
1552
1877
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_icons_react2.IconStrikethrough, { size: 16 })
1553
1878
  }
1554
1879
  ),
1880
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1881
+ "button",
1882
+ {
1883
+ type: "button",
1884
+ onMouseDown: (e) => e.preventDefault(),
1885
+ onClick: () => editor.chain().focus().toggleCode().run(),
1886
+ className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editorState.isCode ? " is-active" : ""}`,
1887
+ disabled: editorState.isCodeBlock,
1888
+ "aria-disabled": editorState.isCodeBlock,
1889
+ "aria-pressed": editorState.isCode,
1890
+ "aria-label": "Toggle inline code",
1891
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_icons_react2.IconCode, { size: 16 })
1892
+ }
1893
+ ),
1894
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Separator, {}),
1555
1895
  !isAddingLink ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1556
1896
  "button",
1557
1897
  {
@@ -1559,8 +1899,8 @@ function TextMenu({
1559
1899
  onMouseDown: (e) => e.preventDefault(),
1560
1900
  onClick: () => setIsAddingLink(true),
1561
1901
  className: `nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon${editor.isActive("link") ? " is-active" : ""}`,
1562
- disabled: editorState.isCode || editorState.isCodeBlock,
1563
- "aria-disabled": editorState.isCode || editorState.isCodeBlock,
1902
+ disabled: isInCode,
1903
+ "aria-disabled": isInCode,
1564
1904
  "aria-pressed": editor.isActive("link"),
1565
1905
  "aria-label": "Add link",
1566
1906
  children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_icons_react2.IconLink, { size: 16 })
@@ -1622,342 +1962,202 @@ function TextMenu({
1622
1962
  (() => {
1623
1963
  const hasInlineMarks = hasAnyMarksInSelection();
1624
1964
  const isPlainParagraph = editorState.isParagraph && !editorState.isHeading && !editorState.isList && !editorState.isBlockquote && !editorState.isCodeBlock && !hasInlineMarks;
1625
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1626
- "button",
1627
- {
1628
- type: "button",
1629
- onMouseDown: (e) => e.preventDefault(),
1630
- onClick: () => editor.chain().focus().clearNodes().setParagraph().unsetAllMarks().run(),
1631
- className: "nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon",
1632
- "aria-label": "Revert to paragraph",
1633
- title: "Revert to paragraph",
1634
- disabled: isPlainParagraph,
1635
- "aria-disabled": isPlainParagraph,
1636
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_icons_react2.IconArrowBackUp, { size: 16 })
1637
- }
1638
- );
1965
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
1966
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Separator, {}),
1967
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1968
+ "button",
1969
+ {
1970
+ type: "button",
1971
+ onMouseDown: (e) => e.preventDefault(),
1972
+ onClick: () => editor.chain().focus().clearNodes().setParagraph().unsetAllMarks().run(),
1973
+ className: "nph-btn nph-btn-ghost nph-btn-xs nph-btn-icon",
1974
+ "aria-label": "Revert to paragraph",
1975
+ title: "Revert to paragraph",
1976
+ disabled: isPlainParagraph,
1977
+ "aria-disabled": isPlainParagraph,
1978
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_icons_react2.IconArrowBackUp, { size: 16 })
1979
+ }
1980
+ )
1981
+ ] });
1639
1982
  })(),
1640
- trailingExtras && editor ? trailingExtras.map((renderExtra, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react15.Fragment, { children: renderExtra(editor) }, `trailing-extra-${index}`)) : null
1983
+ trailingExtras && editor ? trailingExtras.map((renderExtra, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react14.Fragment, { children: renderExtra(editor) }, `trailing-extra-${index}`)) : null
1641
1984
  ] })
1642
1985
  }
1643
1986
  );
1644
1987
  }
1645
1988
 
1646
1989
  // src/react/menus/SlashMenu.tsx
1647
- var import_react16 = require("@tiptap/react");
1990
+ var import_react15 = require("@tiptap/react");
1991
+ var import_jotai6 = require("jotai");
1648
1992
  var import_icons_react3 = require("@tabler/icons-react");
1993
+ var import_react16 = require("react");
1649
1994
  var import_jsx_runtime8 = require("react/jsx-runtime");
1995
+ var SLASH_COMMANDS = [
1996
+ {
1997
+ group: "Format",
1998
+ items: [
1999
+ {
2000
+ value: "paragraph text",
2001
+ label: "Paragraph",
2002
+ description: "Plain text block",
2003
+ icon: import_icons_react3.IconTypography,
2004
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).setParagraph().run()
2005
+ },
2006
+ {
2007
+ value: "heading1 h1",
2008
+ label: "Heading 1",
2009
+ description: "Large section heading",
2010
+ icon: import_icons_react3.IconH1,
2011
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleHeading({ level: 1 }).run()
2012
+ },
2013
+ {
2014
+ value: "heading2 h2",
2015
+ label: "Heading 2",
2016
+ description: "Medium section heading",
2017
+ icon: import_icons_react3.IconH2,
2018
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleHeading({ level: 2 }).run()
2019
+ },
2020
+ {
2021
+ value: "heading3 h3",
2022
+ label: "Heading 3",
2023
+ description: "Small section heading",
2024
+ icon: import_icons_react3.IconH3,
2025
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleHeading({ level: 3 }).run()
2026
+ },
2027
+ {
2028
+ value: "heading4 h4",
2029
+ label: "Heading 4",
2030
+ description: "Subsection heading",
2031
+ icon: import_icons_react3.IconH4,
2032
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleHeading({ level: 4 }).run()
2033
+ }
2034
+ ]
2035
+ },
2036
+ {
2037
+ group: "Lists",
2038
+ items: [
2039
+ {
2040
+ value: "bullet list ul",
2041
+ label: "Bullet List",
2042
+ description: "Unordered list of items",
2043
+ icon: import_icons_react3.IconList,
2044
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleBulletList().run()
2045
+ },
2046
+ {
2047
+ value: "ordered list ol numbered",
2048
+ label: "Numbered List",
2049
+ description: "Ordered list of items",
2050
+ icon: import_icons_react3.IconListNumbers,
2051
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleOrderedList().run()
2052
+ },
2053
+ {
2054
+ value: "task list todo checklist",
2055
+ label: "Task List",
2056
+ description: "Checklist of to-do items",
2057
+ icon: import_icons_react3.IconListCheck,
2058
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleTaskList().run()
2059
+ },
2060
+ {
2061
+ value: "quote blockquote",
2062
+ label: "Blockquote",
2063
+ description: "Highlight a quote or excerpt",
2064
+ icon: import_icons_react3.IconBlockquote,
2065
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleBlockquote().run()
2066
+ }
2067
+ ]
2068
+ },
2069
+ {
2070
+ group: "Insert",
2071
+ items: [
2072
+ {
2073
+ value: "image photo picture",
2074
+ label: "Image",
2075
+ description: "Upload or embed an image",
2076
+ icon: import_icons_react3.IconPhoto,
2077
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).setImageBlock({ src: "" }).run()
2078
+ },
2079
+ {
2080
+ value: "video embed youtube vimeo",
2081
+ label: "Video",
2082
+ description: "Embed a YouTube or Vimeo video",
2083
+ icon: import_icons_react3.IconVideo,
2084
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).setVideoBlock({ src: "" }).run()
2085
+ },
2086
+ {
2087
+ value: "table grid",
2088
+ label: "Table",
2089
+ description: "Insert a table with rows and columns",
2090
+ icon: import_icons_react3.IconTable,
2091
+ onCommand: ({ editor, range }) => {
2092
+ editor.chain().focus().deleteRange(range).run();
2093
+ if (editor.commands.insertTable) {
2094
+ editor.commands.insertTable({ rows: 3, cols: 3, withHeaderRow: true });
2095
+ }
2096
+ }
2097
+ },
2098
+ {
2099
+ value: "code block codeblock",
2100
+ label: "Code Block",
2101
+ description: "Syntax-highlighted code block",
2102
+ icon: import_icons_react3.IconSourceCode,
2103
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run()
2104
+ },
2105
+ {
2106
+ value: "code inline",
2107
+ label: "Inline Code",
2108
+ description: "Mark text as inline code",
2109
+ icon: import_icons_react3.IconCode,
2110
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCode().run()
2111
+ },
2112
+ {
2113
+ value: "divider horizontal rule separator",
2114
+ label: "Divider",
2115
+ description: "Horizontal separator line",
2116
+ icon: import_icons_react3.IconMinus,
2117
+ onCommand: ({ editor, range }) => editor.chain().focus().deleteRange(range).setHorizontalRule().run()
2118
+ }
2119
+ ]
2120
+ }
2121
+ ];
1650
2122
  function SlashMenu({ className }) {
1651
- const { editor } = (0, import_react16.useCurrentEditor)();
2123
+ const { editor } = (0, import_react15.useCurrentEditor)();
2124
+ const query = (0, import_jotai6.useAtomValue)(queryAtom, { store: novelStore });
2125
+ const filteredGroups = (0, import_react16.useMemo)(() => {
2126
+ if (!query) return SLASH_COMMANDS;
2127
+ const q = query.toLowerCase();
2128
+ return SLASH_COMMANDS.map((group) => ({
2129
+ ...group,
2130
+ items: group.items.filter((item) => item.value.toLowerCase().includes(q))
2131
+ })).filter((group) => group.items.length > 0);
2132
+ }, [query]);
1652
2133
  if (!editor) return null;
1653
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditorCommand, { className: className ?? "nph-command", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2134
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditorCommand, { className: className ?? "nph-command", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1654
2135
  EditorCommandList,
1655
2136
  {
1656
2137
  className: "nph-command__list",
1657
2138
  style: { display: "flex", flexDirection: "column", gap: 2 },
1658
- children: [
1659
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(EditorCommandEmpty, { style: { padding: "8px 12px", fontSize: 14 }, children: "No commands found" }),
1660
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1661
- EditorCommandItem,
1662
- {
1663
- value: "bold",
1664
- className: "nph-command__item",
1665
- onCommand: ({
1666
- editor: editor2,
1667
- range
1668
- }) => editor2.chain().focus().deleteRange(range).toggleBold().run(),
1669
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1670
- "span",
1671
- {
1672
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1673
- children: [
1674
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconBold, { size: 16 }),
1675
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Bold" })
1676
- ]
1677
- }
1678
- )
1679
- }
1680
- ),
1681
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1682
- EditorCommandItem,
1683
- {
1684
- value: "italic",
1685
- className: "nph-command__item",
1686
- onCommand: ({
1687
- editor: editor2,
1688
- range
1689
- }) => editor2.chain().focus().deleteRange(range).toggleItalic().run(),
1690
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1691
- "span",
1692
- {
1693
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1694
- children: [
1695
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconItalic, { size: 16 }),
1696
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Italic" })
1697
- ]
1698
- }
1699
- )
1700
- }
1701
- ),
1702
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1703
- EditorCommandItem,
1704
- {
1705
- value: "strike",
1706
- className: "nph-command__item",
1707
- onCommand: ({
1708
- editor: editor2,
1709
- range
1710
- }) => editor2.chain().focus().deleteRange(range).toggleStrike().run(),
1711
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1712
- "span",
1713
- {
1714
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1715
- children: [
1716
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconStrikethrough, { size: 16 }),
1717
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Strike" })
1718
- ]
1719
- }
1720
- )
1721
- }
1722
- ),
1723
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1724
- EditorCommandItem,
1725
- {
1726
- value: "heading1 h1",
1727
- className: "nph-command__item",
1728
- onCommand: ({
1729
- editor: editor2,
1730
- range
1731
- }) => editor2.chain().focus().deleteRange(range).toggleHeading({ level: 1 }).run(),
1732
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1733
- "span",
1734
- {
1735
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1736
- children: [
1737
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconH1, { size: 16 }),
1738
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Heading 1" })
1739
- ]
1740
- }
1741
- )
1742
- }
1743
- ),
1744
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1745
- EditorCommandItem,
1746
- {
1747
- value: "heading2 h2",
1748
- className: "nph-command__item",
1749
- onCommand: ({
1750
- editor: editor2,
1751
- range
1752
- }) => editor2.chain().focus().deleteRange(range).toggleHeading({ level: 2 }).run(),
1753
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1754
- "span",
1755
- {
1756
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1757
- children: [
1758
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconH2, { size: 16 }),
1759
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Heading 2" })
1760
- ]
1761
- }
1762
- )
1763
- }
1764
- ),
1765
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1766
- EditorCommandItem,
1767
- {
1768
- value: "heading3 h3",
1769
- className: "nph-command__item",
1770
- onCommand: ({
1771
- editor: editor2,
1772
- range
1773
- }) => editor2.chain().focus().deleteRange(range).toggleHeading({ level: 3 }).run(),
1774
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1775
- "span",
1776
- {
1777
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1778
- children: [
1779
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconH3, { size: 16 }),
1780
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Heading 3" })
1781
- ]
1782
- }
1783
- )
1784
- }
1785
- ),
1786
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1787
- EditorCommandItem,
1788
- {
1789
- value: "heading4 h4",
1790
- className: "nph-command__item",
1791
- onCommand: ({
1792
- editor: editor2,
1793
- range
1794
- }) => editor2.chain().focus().deleteRange(range).toggleHeading({ level: 4 }).run(),
1795
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1796
- "span",
1797
- {
1798
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1799
- children: [
1800
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconH4, { size: 16 }),
1801
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Heading 4" })
1802
- ]
1803
- }
1804
- )
1805
- }
1806
- ),
1807
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1808
- EditorCommandItem,
1809
- {
1810
- value: "bullet list ul",
1811
- className: "nph-command__item",
1812
- onCommand: ({
1813
- editor: editor2,
1814
- range
1815
- }) => editor2.chain().focus().deleteRange(range).toggleBulletList().run(),
1816
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1817
- "span",
1818
- {
1819
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1820
- children: [
1821
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconList, { size: 16 }),
1822
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Bullet list" })
1823
- ]
1824
- }
1825
- )
1826
- }
1827
- ),
1828
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1829
- EditorCommandItem,
1830
- {
1831
- value: "ordered list ol",
1832
- className: "nph-command__item",
1833
- onCommand: ({
1834
- editor: editor2,
1835
- range
1836
- }) => editor2.chain().focus().deleteRange(range).toggleOrderedList().run(),
1837
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1838
- "span",
1839
- {
1840
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1841
- children: [
1842
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconListNumbers, { size: 16 }),
1843
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Ordered list" })
1844
- ]
1845
- }
1846
- )
1847
- }
1848
- ),
1849
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1850
- EditorCommandItem,
1851
- {
1852
- value: "quote blockquote",
1853
- className: "nph-command__item",
1854
- onCommand: ({
1855
- editor: editor2,
1856
- range
1857
- }) => editor2.chain().focus().deleteRange(range).toggleBlockquote().run(),
1858
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1859
- "span",
1860
- {
1861
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1862
- children: [
1863
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconBlockquote, { size: 16 }),
1864
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Quote" })
1865
- ]
1866
- }
1867
- )
1868
- }
1869
- ),
1870
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1871
- EditorCommandItem,
1872
- {
1873
- value: "code inline",
1874
- className: "nph-command__item",
1875
- onCommand: ({
1876
- editor: editor2,
1877
- range
1878
- }) => editor2.chain().focus().deleteRange(range).toggleCode().run(),
1879
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1880
- "span",
1881
- {
1882
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1883
- children: [
1884
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconCode, { size: 16 }),
1885
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Code" })
1886
- ]
1887
- }
1888
- )
1889
- }
1890
- ),
1891
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1892
- EditorCommandItem,
1893
- {
1894
- value: "code block codeblock",
1895
- className: "nph-command__item",
1896
- onCommand: ({
1897
- editor: editor2,
1898
- range
1899
- }) => editor2.chain().focus().deleteRange(range).toggleCodeBlock().run(),
1900
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1901
- "span",
1902
- {
1903
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1904
- children: [
1905
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconSourceCode, { size: 16 }),
1906
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Code Block" })
1907
- ]
1908
- }
1909
- )
1910
- }
1911
- ),
1912
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1913
- EditorCommandItem,
1914
- {
1915
- value: "image photo picture block",
1916
- className: "nph-command__item",
1917
- onCommand: ({
1918
- editor: editor2,
1919
- range
1920
- }) => {
1921
- ;
1922
- editor2.chain().focus().deleteRange(range).setImageBlock({ src: "" }).run();
1923
- },
1924
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1925
- "span",
1926
- {
1927
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1928
- children: [
1929
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconPhoto, { size: 16 }),
1930
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Image" })
1931
- ]
1932
- }
1933
- )
1934
- }
1935
- ),
1936
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1937
- EditorCommandItem,
1938
- {
1939
- value: "video embed youtube vimeo",
1940
- className: "nph-command__item",
1941
- onCommand: ({
1942
- editor: editor2,
1943
- range
1944
- }) => {
1945
- ;
1946
- editor2.chain().focus().deleteRange(range).setVideoBlock({ src: "" }).run();
2139
+ children: filteredGroups.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { padding: "8px 12px", fontSize: 14, color: "var(--muted-foreground, #6b7280)" }, children: "No commands found" }) : filteredGroups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { children: [
2140
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "nph-command__group-header", children: group.group }),
2141
+ group.items.map((item) => {
2142
+ const Icon = item.icon;
2143
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2144
+ EditorCommandItem,
2145
+ {
2146
+ value: item.value,
2147
+ className: "nph-command__item",
2148
+ onCommand: item.onCommand,
2149
+ children: [
2150
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "nph-command__item-icon", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, { size: 18 }) }),
2151
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "nph-command__item-content", children: [
2152
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "nph-command__item-title", children: item.label }),
2153
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "nph-command__item-description", children: item.description })
2154
+ ] })
2155
+ ]
1947
2156
  },
1948
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1949
- "span",
1950
- {
1951
- style: { display: "inline-flex", alignItems: "center", gap: 8 },
1952
- children: [
1953
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons_react3.IconVideo, { size: 16 }),
1954
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: "Video" })
1955
- ]
1956
- }
1957
- )
1958
- }
1959
- )
1960
- ]
2157
+ item.value
2158
+ );
2159
+ })
2160
+ ] }, group.group))
1961
2161
  }
1962
2162
  ) });
1963
2163
  }
@@ -2108,6 +2308,7 @@ function LinkMenu() {
2108
2308
  }
2109
2309
 
2110
2310
  // src/react/menus/ImageMenu.tsx
2311
+ var import_state5 = require("@tiptap/pm/state");
2111
2312
  var import_react19 = require("@tiptap/react");
2112
2313
  var import_menus3 = require("@tiptap/react/menus");
2113
2314
  var import_icons_react5 = require("@tabler/icons-react");
@@ -2169,7 +2370,7 @@ function ImageMenu({
2169
2370
  shouldShow: ({ editor: e, state }) => {
2170
2371
  if (!e.isActive("imageBlock")) return false;
2171
2372
  const { selection } = state;
2172
- const isNodeSelection = selection.constructor.name === "NodeSelection";
2373
+ const isNodeSelection = selection instanceof import_state5.NodeSelection;
2173
2374
  return isNodeSelection;
2174
2375
  },
2175
2376
  updateDelay: 0,
@@ -2281,12 +2482,13 @@ function ImageMenu({
2281
2482
  }
2282
2483
 
2283
2484
  // src/react/menus/ImageBlock/ImageBlockView.tsx
2284
- var import_react24 = require("@tiptap/react");
2285
- var import_react25 = require("react");
2485
+ var import_react26 = require("@tiptap/react");
2486
+ var import_react27 = require("react");
2286
2487
 
2287
2488
  // src/react/menus/ImageBlock/ImageBlockMenu.tsx
2288
- var import_menus4 = require("@tiptap/react/menus");
2289
- var import_react22 = require("react");
2489
+ var import_state6 = require("@tiptap/pm/state");
2490
+ var import_react22 = require("@tiptap/react");
2491
+ var import_react23 = require("react");
2290
2492
 
2291
2493
  // src/react/menus/ImageBlock/ImageBlockWidth.tsx
2292
2494
  var import_react21 = require("react");
@@ -2351,82 +2553,60 @@ ImageBlockWidth.displayName = "ImageBlockWidth";
2351
2553
  // src/react/menus/ImageBlock/ImageBlockMenu.tsx
2352
2554
  var import_icons_react6 = require("@tabler/icons-react");
2353
2555
  var import_jsx_runtime12 = require("react/jsx-runtime");
2354
- var ImageBlockMenu = ({ editor, appendTo }) => {
2355
- const menuRef = (0, import_react22.useRef)(null);
2356
- const [align, setAlign] = (0, import_react22.useState)("center");
2357
- const [width, setWidth] = (0, import_react22.useState)(100);
2358
- (0, import_react22.useEffect)(() => {
2359
- if (!editor) return;
2360
- const update = () => {
2361
- if (!editor.isActive("imageBlock")) return;
2362
- const attrs = editor.getAttributes("imageBlock");
2363
- setAlign(attrs.align || "center");
2364
- const widthStr = attrs.width || "100%";
2365
- setWidth(parseInt(widthStr) || 100);
2366
- };
2367
- update();
2368
- editor.on("selectionUpdate", update);
2369
- editor.on("transaction", update);
2370
- return () => {
2371
- editor.off("selectionUpdate", update);
2372
- editor.off("transaction", update);
2373
- };
2374
- }, [editor]);
2375
- const getReferenceClientRect = (0, import_react22.useCallback)(() => {
2376
- if (!editor) return new DOMRect(-1e3, -1e3, 0, 0);
2377
- const { view } = editor;
2378
- const { state } = view;
2379
- const { selection } = state;
2380
- const node = selection instanceof window.ProseMirror?.state?.NodeSelection ? selection.node : null;
2381
- if (node && node.type.name === "imageBlock") {
2382
- const nodePos = selection.from;
2383
- const domNode = view.nodeDOM(nodePos);
2384
- if (domNode && domNode instanceof HTMLElement) {
2385
- return domNode.getBoundingClientRect();
2386
- }
2387
- }
2388
- const imageBlockElements = document.querySelectorAll("[data-node-view-wrapper]");
2389
- for (const el of Array.from(imageBlockElements)) {
2390
- if (el.querySelector("img")) {
2391
- return el.getBoundingClientRect();
2556
+ var ImageBlockMenu = ({ editor, getPos, appendTo }) => {
2557
+ const menuRef = (0, import_react23.useRef)(null);
2558
+ const { isVisible, align, width } = (0, import_react22.useEditorState)({
2559
+ editor,
2560
+ selector: (ctx) => {
2561
+ if (!ctx.editor) return { isVisible: false, align: "center", width: 100 };
2562
+ const { state } = ctx.editor;
2563
+ const { selection } = state;
2564
+ const isNodeSel = selection instanceof import_state6.NodeSelection;
2565
+ const isThisNode = isNodeSel && selection.from === getPos();
2566
+ const visible = isThisNode;
2567
+ let currentAlign = "center";
2568
+ let currentWidth = 100;
2569
+ if (visible) {
2570
+ const attrs = ctx.editor.getAttributes("imageBlock");
2571
+ currentAlign = attrs.align || "center";
2572
+ const widthStr = attrs.width || "100%";
2573
+ currentWidth = parseInt(widthStr) || 100;
2392
2574
  }
2575
+ return { isVisible: visible, align: currentAlign, width: currentWidth };
2393
2576
  }
2394
- return new DOMRect(-1e3, -1e3, 0, 0);
2395
- }, [editor]);
2396
- const shouldShow = (0, import_react22.useCallback)(() => {
2397
- if (!editor) return false;
2398
- const isActive = editor.isActive("imageBlock");
2399
- if (!isActive) return false;
2400
- const { state } = editor;
2401
- const { selection } = state;
2402
- const isNodeSelection = selection.constructor.name === "NodeSelection";
2403
- return isNodeSelection;
2404
- }, [editor]);
2405
- const onAlignImageLeft = (0, import_react22.useCallback)(() => {
2577
+ });
2578
+ const onAlignImageLeft = (0, import_react23.useCallback)(() => {
2406
2579
  editor.chain().focus(void 0, { scrollIntoView: false }).setImageBlockAlign("left").run();
2407
2580
  }, [editor]);
2408
- const onAlignImageCenter = (0, import_react22.useCallback)(() => {
2581
+ const onAlignImageCenter = (0, import_react23.useCallback)(() => {
2409
2582
  editor.chain().focus(void 0, { scrollIntoView: false }).setImageBlockAlign("center").run();
2410
2583
  }, [editor]);
2411
- const onAlignImageRight = (0, import_react22.useCallback)(() => {
2584
+ const onAlignImageRight = (0, import_react23.useCallback)(() => {
2412
2585
  editor.chain().focus(void 0, { scrollIntoView: false }).setImageBlockAlign("right").run();
2413
2586
  }, [editor]);
2414
- const onWidthChange = (0, import_react22.useCallback)(
2587
+ const onWidthChange = (0, import_react23.useCallback)(
2415
2588
  (value) => {
2416
2589
  editor.chain().focus(void 0, { scrollIntoView: false }).setImageBlockWidth(value).run();
2417
2590
  },
2418
2591
  [editor]
2419
2592
  );
2420
- const onRemoveImage = (0, import_react22.useCallback)(() => {
2593
+ const onRemoveImage = (0, import_react23.useCallback)(() => {
2421
2594
  editor.chain().focus(void 0, { scrollIntoView: false }).deleteSelection().run();
2422
2595
  }, [editor]);
2423
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2424
- import_menus4.BubbleMenu,
2596
+ if (!isVisible) return null;
2597
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2598
+ "div",
2425
2599
  {
2426
- editor,
2427
- shouldShow,
2428
- updateDelay: 0,
2429
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "bubble-menu", ref: menuRef, children: [
2600
+ className: "bubble-menu",
2601
+ ref: menuRef,
2602
+ style: {
2603
+ position: "absolute",
2604
+ top: "-40px",
2605
+ left: "50%",
2606
+ transform: "translateX(-50%)",
2607
+ zIndex: "var(--nph-z, 50)"
2608
+ },
2609
+ children: [
2430
2610
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2431
2611
  "button",
2432
2612
  {
@@ -2486,14 +2666,14 @@ var ImageBlockMenu = ({ editor, appendTo }) => {
2486
2666
  children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_icons_react6.IconTrash, { size: 16 })
2487
2667
  }
2488
2668
  )
2489
- ] })
2669
+ ]
2490
2670
  }
2491
2671
  );
2492
2672
  };
2493
2673
 
2494
2674
  // src/react/menus/ImageBlock/ImageUploader.tsx
2495
2675
  var import_icons_react8 = require("@tabler/icons-react");
2496
- var import_react23 = require("react");
2676
+ var import_react24 = require("react");
2497
2677
 
2498
2678
  // src/react/menus/ImageBlock/ImageBlockLoading.tsx
2499
2679
  var import_icons_react7 = require("@tabler/icons-react");
@@ -2511,10 +2691,10 @@ var ImageBlockLoading = () => {
2511
2691
  // src/react/menus/ImageBlock/ImageUploader.tsx
2512
2692
  var import_jsx_runtime14 = require("react/jsx-runtime");
2513
2693
  var ImageUploader = ({ onUpload, editor }) => {
2514
- const [loading, setLoading] = (0, import_react23.useState)(false);
2515
- const [draggedInside, setDraggedInside] = (0, import_react23.useState)(false);
2516
- const fileInputRef = (0, import_react23.useRef)(null);
2517
- const uploadFile = (0, import_react23.useCallback)(
2694
+ const [loading, setLoading] = (0, import_react24.useState)(false);
2695
+ const [draggedInside, setDraggedInside] = (0, import_react24.useState)(false);
2696
+ const fileInputRef = (0, import_react24.useRef)(null);
2697
+ const uploadFile = (0, import_react24.useCallback)(
2518
2698
  async (file) => {
2519
2699
  setLoading(true);
2520
2700
  try {
@@ -2536,10 +2716,10 @@ var ImageUploader = ({ onUpload, editor }) => {
2536
2716
  },
2537
2717
  [editor, onUpload]
2538
2718
  );
2539
- const handleUploadClick = (0, import_react23.useCallback)(() => {
2719
+ const handleUploadClick = (0, import_react24.useCallback)(() => {
2540
2720
  fileInputRef.current?.click();
2541
2721
  }, []);
2542
- const onFileChange = (0, import_react23.useCallback)(
2722
+ const onFileChange = (0, import_react24.useCallback)(
2543
2723
  (e) => {
2544
2724
  const file = e.target.files?.[0];
2545
2725
  if (file) {
@@ -2548,7 +2728,7 @@ var ImageUploader = ({ onUpload, editor }) => {
2548
2728
  },
2549
2729
  [uploadFile]
2550
2730
  );
2551
- const onDrop = (0, import_react23.useCallback)(
2731
+ const onDrop = (0, import_react24.useCallback)(
2552
2732
  (e) => {
2553
2733
  e.preventDefault();
2554
2734
  e.stopPropagation();
@@ -2560,12 +2740,12 @@ var ImageUploader = ({ onUpload, editor }) => {
2560
2740
  },
2561
2741
  [uploadFile]
2562
2742
  );
2563
- const onDragEnter = (0, import_react23.useCallback)((e) => {
2743
+ const onDragEnter = (0, import_react24.useCallback)((e) => {
2564
2744
  e.preventDefault();
2565
2745
  e.stopPropagation();
2566
2746
  setDraggedInside(true);
2567
2747
  }, []);
2568
- const onDragLeave = (0, import_react23.useCallback)((e) => {
2748
+ const onDragLeave = (0, import_react24.useCallback)((e) => {
2569
2749
  e.preventDefault();
2570
2750
  e.stopPropagation();
2571
2751
  setDraggedInside(false);
@@ -2614,21 +2794,93 @@ var ImageUploader = ({ onUpload, editor }) => {
2614
2794
  );
2615
2795
  };
2616
2796
 
2617
- // src/react/menus/ImageBlock/ImageBlockView.tsx
2797
+ // src/react/menus/ImageBlock/ImageResizeHandle.tsx
2798
+ var import_react25 = require("react");
2618
2799
  var import_jsx_runtime15 = require("react/jsx-runtime");
2800
+ function ImageResizeHandle({
2801
+ children,
2802
+ onResize,
2803
+ currentWidth
2804
+ }) {
2805
+ const containerRef = (0, import_react25.useRef)(null);
2806
+ const [isResizing, setIsResizing] = (0, import_react25.useState)(false);
2807
+ const handleMouseDown = (0, import_react25.useCallback)(
2808
+ (e, side) => {
2809
+ e.preventDefault();
2810
+ e.stopPropagation();
2811
+ setIsResizing(true);
2812
+ const startX = e.clientX;
2813
+ const containerEl = containerRef.current;
2814
+ if (!containerEl) return;
2815
+ const editorEl = containerEl.closest(".nph-editor") || containerEl.parentElement;
2816
+ if (!editorEl) return;
2817
+ const editorWidth = editorEl.getBoundingClientRect().width;
2818
+ const startWidth = containerEl.getBoundingClientRect().width;
2819
+ const startPercent = startWidth / editorWidth * 100;
2820
+ const handleMouseMove = (moveEvent) => {
2821
+ const deltaX = moveEvent.clientX - startX;
2822
+ const direction = side === "right" ? 1 : -1;
2823
+ const deltaPercent = deltaX * direction / editorWidth * 100 * 2;
2824
+ const newPercent = Math.max(10, Math.min(100, startPercent + deltaPercent));
2825
+ onResize(Math.round(newPercent));
2826
+ };
2827
+ const handleMouseUp = () => {
2828
+ setIsResizing(false);
2829
+ document.removeEventListener("mousemove", handleMouseMove);
2830
+ document.removeEventListener("mouseup", handleMouseUp);
2831
+ };
2832
+ document.addEventListener("mousemove", handleMouseMove);
2833
+ document.addEventListener("mouseup", handleMouseUp);
2834
+ },
2835
+ [onResize]
2836
+ );
2837
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2838
+ "div",
2839
+ {
2840
+ ref: containerRef,
2841
+ className: `nph-resize-wrapper${isResizing ? " nph-resize-wrapper--active" : ""}`,
2842
+ children: [
2843
+ children,
2844
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2845
+ "div",
2846
+ {
2847
+ className: "nph-resize-handle nph-resize-handle--left",
2848
+ onMouseDown: (e) => handleMouseDown(e, "left")
2849
+ }
2850
+ ),
2851
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2852
+ "div",
2853
+ {
2854
+ className: "nph-resize-handle nph-resize-handle--right",
2855
+ onMouseDown: (e) => handleMouseDown(e, "right")
2856
+ }
2857
+ )
2858
+ ]
2859
+ }
2860
+ );
2861
+ }
2862
+
2863
+ // src/react/menus/ImageBlock/ImageBlockView.tsx
2864
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2619
2865
  var ImageBlockView = (props) => {
2620
2866
  const { editor, getPos, node, updateAttributes } = props;
2621
- const imageWrapperRef = (0, import_react25.useRef)(null);
2867
+ const imageWrapperRef = (0, import_react27.useRef)(null);
2622
2868
  const { src, width, align, alt, loading } = node.attrs;
2623
- const handleUpload = (0, import_react25.useCallback)(
2869
+ const handleUpload = (0, import_react27.useCallback)(
2624
2870
  (url) => {
2625
2871
  updateAttributes({ src: url, loading: false });
2626
2872
  },
2627
2873
  [updateAttributes]
2628
2874
  );
2629
- const onClick = (0, import_react25.useCallback)(() => {
2875
+ const onClick = (0, import_react27.useCallback)(() => {
2630
2876
  editor.commands.setNodeSelection(getPos());
2631
2877
  }, [getPos, editor.commands]);
2878
+ const handleResize = (0, import_react27.useCallback)(
2879
+ (widthPercent) => {
2880
+ updateAttributes({ width: `${widthPercent}%` });
2881
+ },
2882
+ [updateAttributes]
2883
+ );
2632
2884
  const getWrapperStyle = () => {
2633
2885
  const baseStyle = {
2634
2886
  width: width || "100%",
@@ -2642,14 +2894,17 @@ var ImageBlockView = (props) => {
2642
2894
  return { ...baseStyle, marginLeft: "auto", marginRight: "auto" };
2643
2895
  }
2644
2896
  };
2897
+ const getContentStyle = () => ({
2898
+ position: "relative"
2899
+ });
2645
2900
  if (!src || src === "") {
2646
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react24.NodeViewWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: getWrapperStyle(), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { ref: imageWrapperRef, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ImageUploader, { onUpload: handleUpload, editor }) }) }) });
2901
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react26.NodeViewWrapper, { style: { width: "100%", marginTop: "0.5rem", marginBottom: "0.5rem" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: imageWrapperRef, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ImageUploader, { onUpload: handleUpload, editor }) }) });
2647
2902
  }
2648
2903
  if (loading) {
2649
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react24.NodeViewWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: getWrapperStyle(), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { ref: imageWrapperRef, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ImageBlockLoading, {}) }) }) });
2904
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react26.NodeViewWrapper, { style: getWrapperStyle(), children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { ref: imageWrapperRef, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ImageBlockLoading, {}) }) });
2650
2905
  }
2651
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react24.NodeViewWrapper, { children: [
2652
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: getWrapperStyle(), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { contentEditable: false, ref: imageWrapperRef, style: { position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2906
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_react26.NodeViewWrapper, { style: getWrapperStyle(), children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { contentEditable: false, ref: imageWrapperRef, style: getContentStyle(), children: [
2907
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ImageResizeHandle, { onResize: handleResize, currentWidth: width, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2653
2908
  "img",
2654
2909
  {
2655
2910
  src,
@@ -2657,76 +2912,76 @@ var ImageBlockView = (props) => {
2657
2912
  onClick,
2658
2913
  className: "nph-image-block"
2659
2914
  }
2660
- ) }) }),
2661
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ImageBlockMenu, { editor, appendTo: imageWrapperRef })
2662
- ] });
2915
+ ) }),
2916
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ImageBlockMenu, { editor, getPos, appendTo: imageWrapperRef })
2917
+ ] }) });
2663
2918
  };
2664
2919
 
2665
2920
  // src/react/menus/VideoBlock/VideoBlockView.tsx
2666
- var import_react27 = require("@tiptap/react");
2667
- var import_react28 = require("react");
2921
+ var import_state8 = require("@tiptap/pm/state");
2922
+ var import_react30 = require("@tiptap/react");
2923
+ var import_react31 = require("react");
2668
2924
 
2669
2925
  // src/react/menus/VideoBlock/VideoBlockMenu.tsx
2670
- var import_menus5 = require("@tiptap/react/menus");
2671
- var import_react26 = require("react");
2926
+ var import_state7 = require("@tiptap/pm/state");
2927
+ var import_react28 = require("@tiptap/react");
2928
+ var import_react29 = require("react");
2672
2929
  var import_icons_react9 = require("@tabler/icons-react");
2673
- var import_jsx_runtime16 = require("react/jsx-runtime");
2674
- var VideoBlockMenu = ({ editor }) => {
2675
- const menuRef = (0, import_react26.useRef)(null);
2676
- const [align, setAlign] = (0, import_react26.useState)("center");
2677
- const [width, setWidth] = (0, import_react26.useState)(100);
2678
- (0, import_react26.useEffect)(() => {
2679
- if (!editor) return;
2680
- const update = () => {
2681
- if (!editor.isActive("videoBlock")) return;
2682
- const attrs = editor.getAttributes("videoBlock");
2683
- setAlign(attrs.align || "center");
2684
- const widthStr = attrs.width || "100%";
2685
- setWidth(parseInt(widthStr) || 100);
2686
- };
2687
- update();
2688
- editor.on("selectionUpdate", update);
2689
- editor.on("transaction", update);
2690
- return () => {
2691
- editor.off("selectionUpdate", update);
2692
- editor.off("transaction", update);
2693
- };
2694
- }, [editor]);
2695
- const shouldShow = (0, import_react26.useCallback)(() => {
2696
- if (!editor) return false;
2697
- const { state } = editor;
2698
- const { selection } = state;
2699
- if (selection.constructor.name !== "NodeSelection") return false;
2700
- const node = selection.node;
2701
- if (!node || node.type.name !== "videoBlock") return false;
2702
- return true;
2703
- }, [editor]);
2704
- const onAlignLeft = (0, import_react26.useCallback)(() => {
2930
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2931
+ var VideoBlockMenu = ({ editor, getPos }) => {
2932
+ const menuRef = (0, import_react29.useRef)(null);
2933
+ const { isVisible, align, width } = (0, import_react28.useEditorState)({
2934
+ editor,
2935
+ selector: (ctx) => {
2936
+ if (!ctx.editor) return { isVisible: false, align: "center", width: 100 };
2937
+ const { state } = ctx.editor;
2938
+ const { selection } = state;
2939
+ const isNodeSel = selection instanceof import_state7.NodeSelection;
2940
+ const isThisNode = isNodeSel && selection.from === getPos();
2941
+ let currentAlign = "center";
2942
+ let currentWidth = 100;
2943
+ if (isThisNode) {
2944
+ const attrs = ctx.editor.getAttributes("videoBlock");
2945
+ currentAlign = attrs.align || "center";
2946
+ const widthStr = attrs.width || "100%";
2947
+ currentWidth = parseInt(widthStr) || 100;
2948
+ }
2949
+ return { isVisible: isThisNode, align: currentAlign, width: currentWidth };
2950
+ }
2951
+ });
2952
+ const onAlignLeft = (0, import_react29.useCallback)(() => {
2705
2953
  editor.chain().focus(void 0, { scrollIntoView: false }).setVideoBlockAlign("left").run();
2706
2954
  }, [editor]);
2707
- const onAlignCenter = (0, import_react26.useCallback)(() => {
2955
+ const onAlignCenter = (0, import_react29.useCallback)(() => {
2708
2956
  editor.chain().focus(void 0, { scrollIntoView: false }).setVideoBlockAlign("center").run();
2709
2957
  }, [editor]);
2710
- const onAlignRight = (0, import_react26.useCallback)(() => {
2958
+ const onAlignRight = (0, import_react29.useCallback)(() => {
2711
2959
  editor.chain().focus(void 0, { scrollIntoView: false }).setVideoBlockAlign("right").run();
2712
2960
  }, [editor]);
2713
- const onWidthChange = (0, import_react26.useCallback)(
2961
+ const onWidthChange = (0, import_react29.useCallback)(
2714
2962
  (value) => {
2715
2963
  editor.chain().focus(void 0, { scrollIntoView: false }).setVideoBlockWidth(value).run();
2716
2964
  },
2717
2965
  [editor]
2718
2966
  );
2719
- const onRemove = (0, import_react26.useCallback)(() => {
2967
+ const onRemove = (0, import_react29.useCallback)(() => {
2720
2968
  editor.chain().focus(void 0, { scrollIntoView: false }).deleteSelection().run();
2721
2969
  }, [editor]);
2722
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2723
- import_menus5.BubbleMenu,
2970
+ if (!isVisible) return null;
2971
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2972
+ "div",
2724
2973
  {
2725
- editor,
2726
- shouldShow,
2727
- updateDelay: 0,
2728
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "bubble-menu", ref: menuRef, children: [
2729
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2974
+ className: "bubble-menu",
2975
+ ref: menuRef,
2976
+ style: {
2977
+ position: "absolute",
2978
+ top: "-40px",
2979
+ left: "50%",
2980
+ transform: "translateX(-50%)",
2981
+ zIndex: "var(--nph-z, 50)"
2982
+ },
2983
+ children: [
2984
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2730
2985
  "button",
2731
2986
  {
2732
2987
  type: "button",
@@ -2734,10 +2989,10 @@ var VideoBlockMenu = ({ editor }) => {
2734
2989
  title: "Align left",
2735
2990
  onMouseDown: (e) => e.preventDefault(),
2736
2991
  onClick: onAlignLeft,
2737
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_icons_react9.IconAlignLeft, { size: 16 })
2992
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_icons_react9.IconAlignLeft, { size: 16 })
2738
2993
  }
2739
2994
  ),
2740
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2995
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2741
2996
  "button",
2742
2997
  {
2743
2998
  type: "button",
@@ -2745,10 +3000,10 @@ var VideoBlockMenu = ({ editor }) => {
2745
3000
  title: "Align center",
2746
3001
  onMouseDown: (e) => e.preventDefault(),
2747
3002
  onClick: onAlignCenter,
2748
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_icons_react9.IconAlignCenter, { size: 16 })
3003
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_icons_react9.IconAlignCenter, { size: 16 })
2749
3004
  }
2750
3005
  ),
2751
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3006
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2752
3007
  "button",
2753
3008
  {
2754
3009
  type: "button",
@@ -2756,25 +3011,25 @@ var VideoBlockMenu = ({ editor }) => {
2756
3011
  title: "Align right",
2757
3012
  onMouseDown: (e) => e.preventDefault(),
2758
3013
  onClick: onAlignRight,
2759
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_icons_react9.IconAlignRight, { size: 16 })
3014
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_icons_react9.IconAlignRight, { size: 16 })
2760
3015
  }
2761
3016
  ),
2762
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3017
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2763
3018
  "div",
2764
3019
  {
2765
3020
  className: "nph-link-popover__divider",
2766
3021
  style: { margin: "0 4px" }
2767
3022
  }
2768
3023
  ),
2769
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ImageBlockWidth, { onChange: onWidthChange, value: width }),
2770
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3024
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ImageBlockWidth, { onChange: onWidthChange, value: width }),
3025
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2771
3026
  "div",
2772
3027
  {
2773
3028
  className: "nph-link-popover__divider",
2774
3029
  style: { margin: "0 4px" }
2775
3030
  }
2776
3031
  ),
2777
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3032
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2778
3033
  "button",
2779
3034
  {
2780
3035
  type: "button",
@@ -2782,17 +3037,17 @@ var VideoBlockMenu = ({ editor }) => {
2782
3037
  title: "Remove video",
2783
3038
  onMouseDown: (e) => e.preventDefault(),
2784
3039
  onClick: onRemove,
2785
- children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_icons_react9.IconTrash, { size: 16 })
3040
+ children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_icons_react9.IconTrash, { size: 16 })
2786
3041
  }
2787
3042
  )
2788
- ] })
3043
+ ]
2789
3044
  }
2790
3045
  );
2791
3046
  };
2792
3047
 
2793
3048
  // src/react/menus/VideoBlock/VideoBlockView.tsx
2794
3049
  var import_icons_react10 = require("@tabler/icons-react");
2795
- var import_jsx_runtime17 = require("react/jsx-runtime");
3050
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2796
3051
  function toEmbedUrl(url) {
2797
3052
  const ytMatch = url.match(
2798
3053
  /(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/
@@ -2808,15 +3063,23 @@ function toEmbedUrl(url) {
2808
3063
  }
2809
3064
  var VideoBlockView = (props) => {
2810
3065
  const { editor, getPos, node, updateAttributes } = props;
2811
- const wrapperRef = (0, import_react28.useRef)(null);
3066
+ const wrapperRef = (0, import_react31.useRef)(null);
2812
3067
  const { src, width, align } = node.attrs;
2813
- const [inputUrl, setInputUrl] = (0, import_react28.useState)("");
2814
- const handleEmbed = (0, import_react28.useCallback)(() => {
3068
+ const [inputUrl, setInputUrl] = (0, import_react31.useState)("");
3069
+ const isSelected = (0, import_react30.useEditorState)({
3070
+ editor,
3071
+ selector: (ctx) => {
3072
+ if (!ctx.editor) return false;
3073
+ const { selection } = ctx.editor.state;
3074
+ return selection instanceof import_state8.NodeSelection && selection.from === getPos();
3075
+ }
3076
+ });
3077
+ const handleEmbed = (0, import_react31.useCallback)(() => {
2815
3078
  if (!inputUrl.trim()) return;
2816
3079
  const embedUrl = toEmbedUrl(inputUrl.trim());
2817
3080
  updateAttributes({ src: embedUrl });
2818
3081
  }, [inputUrl, updateAttributes]);
2819
- const handleKeyDown = (0, import_react28.useCallback)(
3082
+ const handleKeyDown = (0, import_react31.useCallback)(
2820
3083
  (e) => {
2821
3084
  if (e.key === "Enter") {
2822
3085
  e.preventDefault();
@@ -2825,13 +3088,13 @@ var VideoBlockView = (props) => {
2825
3088
  },
2826
3089
  [handleEmbed]
2827
3090
  );
2828
- const onClick = (0, import_react28.useCallback)(() => {
3091
+ const onClick = (0, import_react31.useCallback)(() => {
2829
3092
  editor.commands.setNodeSelection(getPos());
2830
3093
  }, [getPos, editor.commands]);
2831
3094
  const getWrapperStyle = () => {
2832
3095
  const baseStyle = {
2833
- width: width || "100%",
2834
- maxWidth: "100%"
3096
+ width: "fit-content",
3097
+ maxWidth: width || "100%"
2835
3098
  };
2836
3099
  if (align === "left") {
2837
3100
  return { ...baseStyle, marginLeft: 0, marginRight: "auto" };
@@ -2842,10 +3105,10 @@ var VideoBlockView = (props) => {
2842
3105
  }
2843
3106
  };
2844
3107
  if (!src || src === "") {
2845
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_react27.NodeViewWrapper, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: getWrapperStyle(), children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "nph-video-input", ref: wrapperRef, children: [
2846
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "nph-video-input__icon", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_icons_react10.IconVideo, { size: 24 }) }),
2847
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "nph-video-input__content", children: [
2848
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3108
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react30.NodeViewWrapper, { style: getWrapperStyle(), children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { ref: wrapperRef, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "nph-video-input", children: [
3109
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "nph-video-input__icon", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_icons_react10.IconVideo, { size: 24 }) }),
3110
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "nph-video-input__content", children: [
3111
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2849
3112
  "input",
2850
3113
  {
2851
3114
  type: "text",
@@ -2856,7 +3119,7 @@ var VideoBlockView = (props) => {
2856
3119
  onKeyDown: handleKeyDown
2857
3120
  }
2858
3121
  ),
2859
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
3122
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2860
3123
  "button",
2861
3124
  {
2862
3125
  type: "button",
@@ -2869,38 +3132,730 @@ var VideoBlockView = (props) => {
2869
3132
  ] })
2870
3133
  ] }) }) });
2871
3134
  }
2872
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_react27.NodeViewWrapper, { children: [
2873
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { style: getWrapperStyle(), children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2874
- "div",
3135
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_react30.NodeViewWrapper, { style: getWrapperStyle(), children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
3136
+ "div",
3137
+ {
3138
+ contentEditable: false,
3139
+ ref: wrapperRef,
3140
+ style: { position: "relative" },
3141
+ children: [
3142
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "nph-video-block", children: [
3143
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3144
+ "iframe",
3145
+ {
3146
+ src,
3147
+ className: "nph-video-block__iframe",
3148
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
3149
+ allowFullScreen: true
3150
+ }
3151
+ ),
3152
+ !isSelected && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3153
+ "div",
3154
+ {
3155
+ className: "nph-video-block__overlay",
3156
+ onClick
3157
+ }
3158
+ )
3159
+ ] }),
3160
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VideoBlockMenu, { editor, getPos })
3161
+ ]
3162
+ }
3163
+ ) });
3164
+ };
3165
+
3166
+ // src/react/menus/DragHandle/BlockActionMenu.tsx
3167
+ var import_icons_react11 = require("@tabler/icons-react");
3168
+ var import_react32 = require("react");
3169
+ var import_jsx_runtime19 = require("react/jsx-runtime");
3170
+ function BlockActionMenu({ editor, onClose }) {
3171
+ const executeAndClose = (0, import_react32.useCallback)(
3172
+ (fn) => {
3173
+ fn();
3174
+ onClose();
3175
+ },
3176
+ [onClose]
3177
+ );
3178
+ const handleDelete = (0, import_react32.useCallback)(() => {
3179
+ executeAndClose(() => {
3180
+ editor.commands.deleteSelection();
3181
+ });
3182
+ }, [editor, executeAndClose]);
3183
+ const handleDuplicate = (0, import_react32.useCallback)(() => {
3184
+ executeAndClose(() => {
3185
+ const { state } = editor;
3186
+ const { selection } = state;
3187
+ const { $anchor } = selection;
3188
+ const depth = $anchor.depth > 0 ? 1 : 0;
3189
+ const start = $anchor.start(depth);
3190
+ const end = $anchor.end(depth);
3191
+ const node = state.doc.nodeAt(start - 1);
3192
+ if (node) {
3193
+ const insertPos = end + 1;
3194
+ editor.chain().focus().insertContentAt(insertPos, node.toJSON()).run();
3195
+ }
3196
+ });
3197
+ }, [editor, executeAndClose]);
3198
+ const handleMoveUp = (0, import_react32.useCallback)(() => {
3199
+ executeAndClose(() => {
3200
+ const { state } = editor;
3201
+ const { selection } = state;
3202
+ const { $anchor } = selection;
3203
+ const depth = $anchor.depth > 0 ? 1 : 0;
3204
+ const blockStart = $anchor.start(depth) - 1;
3205
+ if (blockStart <= 0) return;
3206
+ const node = state.doc.nodeAt(blockStart);
3207
+ if (!node) return;
3208
+ const $pos = state.doc.resolve(blockStart);
3209
+ const index = $pos.index($pos.depth);
3210
+ if (index === 0) return;
3211
+ const prevNode = $pos.node($pos.depth).child(index - 1);
3212
+ const prevStart = blockStart - prevNode.nodeSize;
3213
+ editor.chain().focus().command(({ tr }) => {
3214
+ const currentSlice = state.doc.slice(blockStart, blockStart + node.nodeSize);
3215
+ tr.delete(blockStart, blockStart + node.nodeSize);
3216
+ tr.insert(prevStart, currentSlice.content);
3217
+ return true;
3218
+ }).run();
3219
+ });
3220
+ }, [editor, executeAndClose]);
3221
+ const handleMoveDown = (0, import_react32.useCallback)(() => {
3222
+ executeAndClose(() => {
3223
+ const { state } = editor;
3224
+ const { selection } = state;
3225
+ const { $anchor } = selection;
3226
+ const depth = $anchor.depth > 0 ? 1 : 0;
3227
+ const blockStart = $anchor.start(depth) - 1;
3228
+ const node = state.doc.nodeAt(blockStart);
3229
+ if (!node) return;
3230
+ const blockEnd = blockStart + node.nodeSize;
3231
+ const $pos = state.doc.resolve(blockStart);
3232
+ const parent = $pos.node($pos.depth);
3233
+ const index = $pos.index($pos.depth);
3234
+ if (index >= parent.childCount - 1) return;
3235
+ const nextNode = parent.child(index + 1);
3236
+ const nextEnd = blockEnd + nextNode.nodeSize;
3237
+ editor.chain().focus().command(({ tr }) => {
3238
+ const currentSlice = state.doc.slice(blockStart, blockEnd);
3239
+ tr.delete(blockStart, blockEnd);
3240
+ const insertPos = blockStart + nextNode.nodeSize;
3241
+ tr.insert(insertPos, currentSlice.content);
3242
+ return true;
3243
+ }).run();
3244
+ });
3245
+ }, [editor, executeAndClose]);
3246
+ const handleCopyToClipboard = (0, import_react32.useCallback)(() => {
3247
+ executeAndClose(() => {
3248
+ const { state } = editor;
3249
+ const { selection } = state;
3250
+ const { $anchor } = selection;
3251
+ const depth = $anchor.depth > 0 ? 1 : 0;
3252
+ const start = $anchor.start(depth) - 1;
3253
+ const node = state.doc.nodeAt(start);
3254
+ if (node) {
3255
+ const text = node.textContent;
3256
+ navigator.clipboard.writeText(text).catch(() => {
3257
+ });
3258
+ }
3259
+ });
3260
+ }, [editor, executeAndClose]);
3261
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "nph-block-action-menu nph-command", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "nph-command__list", style: { maxHeight: "none" }, children: [
3262
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
3263
+ "button",
2875
3264
  {
2876
- contentEditable: false,
2877
- ref: wrapperRef,
2878
- style: { position: "relative" },
2879
- children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "nph-video-block", onClick, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2880
- "iframe",
2881
- {
2882
- src,
2883
- className: "nph-video-block__iframe",
2884
- allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
2885
- allowFullScreen: true
3265
+ type: "button",
3266
+ className: "nph-command__item",
3267
+ onClick: handleDelete,
3268
+ onMouseDown: (e) => e.preventDefault(),
3269
+ children: [
3270
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_icons_react11.IconTrash, { size: 16 }),
3271
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: "Delete" })
3272
+ ]
3273
+ }
3274
+ ),
3275
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
3276
+ "button",
3277
+ {
3278
+ type: "button",
3279
+ className: "nph-command__item",
3280
+ onClick: handleDuplicate,
3281
+ onMouseDown: (e) => e.preventDefault(),
3282
+ children: [
3283
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_icons_react11.IconCopy, { size: 16 }),
3284
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: "Duplicate" })
3285
+ ]
3286
+ }
3287
+ ),
3288
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
3289
+ "button",
3290
+ {
3291
+ type: "button",
3292
+ className: "nph-command__item",
3293
+ onClick: handleCopyToClipboard,
3294
+ onMouseDown: (e) => e.preventDefault(),
3295
+ children: [
3296
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_icons_react11.IconClipboard, { size: 16 }),
3297
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: "Copy to clipboard" })
3298
+ ]
3299
+ }
3300
+ ),
3301
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
3302
+ "button",
3303
+ {
3304
+ type: "button",
3305
+ className: "nph-command__item",
3306
+ onClick: handleMoveUp,
3307
+ onMouseDown: (e) => e.preventDefault(),
3308
+ children: [
3309
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_icons_react11.IconArrowUp, { size: 16 }),
3310
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: "Move up" })
3311
+ ]
3312
+ }
3313
+ ),
3314
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
3315
+ "button",
3316
+ {
3317
+ type: "button",
3318
+ className: "nph-command__item",
3319
+ onClick: handleMoveDown,
3320
+ onMouseDown: (e) => e.preventDefault(),
3321
+ children: [
3322
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_icons_react11.IconArrowDown, { size: 16 }),
3323
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { children: "Move down" })
3324
+ ]
3325
+ }
3326
+ )
3327
+ ] }) });
3328
+ }
3329
+
3330
+ // src/react/menus/TableMenu.tsx
3331
+ var import_react33 = require("@tiptap/react");
3332
+ var import_icons_react12 = require("@tabler/icons-react");
3333
+ var import_react34 = require("react");
3334
+ var import_react_dom2 = require("react-dom");
3335
+ var import_jsx_runtime20 = require("react/jsx-runtime");
3336
+ function TableMenu({ className: _className }) {
3337
+ const { editor } = (0, import_react33.useCurrentEditor)();
3338
+ const tableInfo = (0, import_react33.useEditorState)({
3339
+ editor,
3340
+ selector: (ctx) => {
3341
+ if (!ctx.editor) return null;
3342
+ const { $from } = ctx.editor.state.selection;
3343
+ for (let d = $from.depth; d > 0; d--) {
3344
+ if ($from.node(d).type.name === "table") {
3345
+ return { pos: $from.start(d) - 1, depth: d };
3346
+ }
3347
+ }
3348
+ return null;
3349
+ }
3350
+ });
3351
+ const [colGrips, setColGrips] = (0, import_react34.useState)([]);
3352
+ const [rowGrips, setRowGrips] = (0, import_react34.useState)([]);
3353
+ const [dropdown, setDropdown] = (0, import_react34.useState)(null);
3354
+ const [tableRect, setTableRect] = (0, import_react34.useState)(null);
3355
+ const [isHovering, setIsHovering] = (0, import_react34.useState)(false);
3356
+ const [drag, setDrag] = (0, import_react34.useState)(null);
3357
+ const dropdownRef = (0, import_react34.useRef)(null);
3358
+ const dragRef = (0, import_react34.useRef)(null);
3359
+ const getTableDom = (0, import_react34.useCallback)(() => {
3360
+ if (!editor || !tableInfo) return null;
3361
+ return editor.view.nodeDOM(tableInfo.pos);
3362
+ }, [editor, tableInfo]);
3363
+ const measureGrips = (0, import_react34.useCallback)(() => {
3364
+ const tableDom = getTableDom();
3365
+ if (!tableDom) {
3366
+ setColGrips([]);
3367
+ setRowGrips([]);
3368
+ setTableRect(null);
3369
+ return;
3370
+ }
3371
+ const rect = tableDom.getBoundingClientRect();
3372
+ setTableRect(rect);
3373
+ const firstRow = tableDom.querySelector("tr");
3374
+ if (!firstRow) return;
3375
+ const cells = firstRow.querySelectorAll("th, td");
3376
+ const newColGrips = [];
3377
+ cells.forEach((cell) => {
3378
+ const cellRect = cell.getBoundingClientRect();
3379
+ newColGrips.push({
3380
+ left: cellRect.left,
3381
+ top: rect.top,
3382
+ width: cellRect.width,
3383
+ height: 0
3384
+ });
3385
+ });
3386
+ setColGrips(newColGrips);
3387
+ const rows = tableDom.querySelectorAll("tr");
3388
+ const newRowGrips = [];
3389
+ rows.forEach((row) => {
3390
+ const rowRect = row.getBoundingClientRect();
3391
+ newRowGrips.push({
3392
+ left: rect.left,
3393
+ top: rowRect.top,
3394
+ width: 0,
3395
+ height: rowRect.height
3396
+ });
3397
+ });
3398
+ setRowGrips(newRowGrips);
3399
+ }, [getTableDom]);
3400
+ (0, import_react34.useEffect)(() => {
3401
+ if (!tableInfo) {
3402
+ setColGrips([]);
3403
+ setRowGrips([]);
3404
+ setTableRect(null);
3405
+ setDropdown(null);
3406
+ return;
3407
+ }
3408
+ measureGrips();
3409
+ const tableDom = getTableDom();
3410
+ if (!tableDom) return;
3411
+ const ro = new ResizeObserver(() => measureGrips());
3412
+ ro.observe(tableDom);
3413
+ const HOVER_PAD = 32;
3414
+ const handleMouseMove = (e) => {
3415
+ const r = tableDom.getBoundingClientRect();
3416
+ 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;
3417
+ setIsHovering(inside);
3418
+ };
3419
+ document.addEventListener("mousemove", handleMouseMove, { passive: true });
3420
+ const scrollParent = tableDom.closest(".nph-editor") || window;
3421
+ const handleScroll = () => {
3422
+ measureGrips();
3423
+ setDropdown(null);
3424
+ };
3425
+ scrollParent.addEventListener("scroll", handleScroll, { passive: true });
3426
+ window.addEventListener("scroll", handleScroll, { passive: true });
3427
+ return () => {
3428
+ ro.disconnect();
3429
+ document.removeEventListener("mousemove", handleMouseMove);
3430
+ scrollParent.removeEventListener("scroll", handleScroll);
3431
+ window.removeEventListener("scroll", handleScroll);
3432
+ };
3433
+ }, [tableInfo, getTableDom, measureGrips]);
3434
+ (0, import_react34.useEffect)(() => {
3435
+ if (!dropdown) return;
3436
+ const handlePointerDown = (e) => {
3437
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
3438
+ setDropdown(null);
3439
+ }
3440
+ };
3441
+ document.addEventListener("pointerdown", handlePointerDown);
3442
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
3443
+ }, [dropdown]);
3444
+ (0, import_react34.useEffect)(() => {
3445
+ if (!editor || !dropdown) return;
3446
+ const handleTransaction = () => setDropdown(null);
3447
+ editor.on("transaction", handleTransaction);
3448
+ return () => {
3449
+ editor.off("transaction", handleTransaction);
3450
+ };
3451
+ }, [editor, dropdown]);
3452
+ const handleColGripClick = (0, import_react34.useCallback)(
3453
+ (index, e) => {
3454
+ e.preventDefault();
3455
+ e.stopPropagation();
3456
+ if (!editor || !tableInfo) return;
3457
+ const tableDom = getTableDom();
3458
+ if (!tableDom) return;
3459
+ const firstRow = tableDom.querySelector("tr");
3460
+ if (!firstRow) return;
3461
+ const cells = firstRow.querySelectorAll("th, td");
3462
+ const cell = cells[index];
3463
+ if (!cell) return;
3464
+ const pos = editor.view.posAtDOM(cell, 0);
3465
+ editor.chain().focus().setTextSelection(pos).run();
3466
+ const rect = cell.getBoundingClientRect();
3467
+ setDropdown({
3468
+ type: "column",
3469
+ index,
3470
+ x: rect.left,
3471
+ y: rect.top - 4
3472
+ });
3473
+ },
3474
+ [editor, tableInfo, getTableDom]
3475
+ );
3476
+ const handleRowGripClick = (0, import_react34.useCallback)(
3477
+ (index, e) => {
3478
+ e.preventDefault();
3479
+ e.stopPropagation();
3480
+ if (!editor || !tableInfo) return;
3481
+ const tableDom = getTableDom();
3482
+ if (!tableDom) return;
3483
+ const rows = tableDom.querySelectorAll("tr");
3484
+ const row = rows[index];
3485
+ if (!row) return;
3486
+ const firstCell = row.querySelector("th, td");
3487
+ if (!firstCell) return;
3488
+ const pos = editor.view.posAtDOM(firstCell, 0);
3489
+ editor.chain().focus().setTextSelection(pos).run();
3490
+ const gripEl = e.currentTarget;
3491
+ const gripRect = gripEl.getBoundingClientRect();
3492
+ setDropdown({
3493
+ type: "row",
3494
+ index,
3495
+ x: gripRect.left,
3496
+ y: gripRect.bottom + 4
3497
+ });
3498
+ },
3499
+ [editor, tableInfo, getTableDom]
3500
+ );
3501
+ const moveColumn = (0, import_react34.useCallback)(
3502
+ (from, to) => {
3503
+ if (!editor || !tableInfo || from === to) return;
3504
+ const { state } = editor;
3505
+ const tableStart = tableInfo.pos;
3506
+ const tableNode = state.doc.nodeAt(tableStart);
3507
+ if (!tableNode) return;
3508
+ const tr = state.tr;
3509
+ tableNode.forEach((row, rowOffset) => {
3510
+ if (row.type.name !== "tableRow") return;
3511
+ const cells = [];
3512
+ row.forEach((cell, cellOffset) => {
3513
+ cells.push({ node: cell, pos: tableStart + 1 + rowOffset + 1 + cellOffset });
3514
+ });
3515
+ if (from >= cells.length || to >= cells.length) return;
3516
+ const reordered = [...cells];
3517
+ const [moved] = reordered.splice(from, 1);
3518
+ reordered.splice(to, 0, moved);
3519
+ const rowPos = tableStart + 1 + rowOffset;
3520
+ const mappedRowPos = tr.mapping.map(rowPos);
3521
+ const newRow = row.type.create(row.attrs, reordered.map((c) => c.node));
3522
+ tr.replaceWith(mappedRowPos, mappedRowPos + row.nodeSize, newRow);
3523
+ });
3524
+ editor.view.dispatch(tr);
3525
+ },
3526
+ [editor, tableInfo]
3527
+ );
3528
+ const moveRow = (0, import_react34.useCallback)(
3529
+ (from, to) => {
3530
+ if (!editor || !tableInfo || from === to) return;
3531
+ const { state } = editor;
3532
+ const tableStart = tableInfo.pos;
3533
+ const tableNode = state.doc.nodeAt(tableStart);
3534
+ if (!tableNode) return;
3535
+ const rows = [];
3536
+ tableNode.forEach((row) => rows.push(row));
3537
+ if (from >= rows.length || to >= rows.length) return;
3538
+ const reordered = [...rows];
3539
+ const [moved] = reordered.splice(from, 1);
3540
+ reordered.splice(to, 0, moved);
3541
+ const tr = state.tr;
3542
+ tr.replaceWith(
3543
+ tableStart + 1,
3544
+ tableStart + 1 + tableNode.content.size,
3545
+ reordered
3546
+ );
3547
+ editor.view.dispatch(tr);
3548
+ },
3549
+ [editor, tableInfo]
3550
+ );
3551
+ const handleGripDragStart = (0, import_react34.useCallback)(
3552
+ (type, index, e) => {
3553
+ e.preventDefault();
3554
+ e.stopPropagation();
3555
+ setDropdown(null);
3556
+ const startX = e.clientX;
3557
+ const startY = e.clientY;
3558
+ let hasMoved = false;
3559
+ const dragState = { type, fromIndex: index, toIndex: index };
3560
+ dragRef.current = dragState;
3561
+ setDrag(dragState);
3562
+ const handleMouseMove = (ev) => {
3563
+ const dx = ev.clientX - startX;
3564
+ const dy = ev.clientY - startY;
3565
+ if (!hasMoved && Math.abs(type === "column" ? dx : dy) < 5) return;
3566
+ hasMoved = true;
3567
+ let newIndex = index;
3568
+ if (type === "column") {
3569
+ for (let i = 0; i < colGrips.length; i++) {
3570
+ const mid = colGrips[i].left + colGrips[i].width / 2;
3571
+ if (ev.clientX < mid) {
3572
+ newIndex = i;
3573
+ break;
3574
+ }
3575
+ newIndex = i;
3576
+ }
3577
+ } else {
3578
+ for (let i = 0; i < rowGrips.length; i++) {
3579
+ const mid = rowGrips[i].top + rowGrips[i].height / 2;
3580
+ if (ev.clientY < mid) {
3581
+ newIndex = i;
3582
+ break;
3583
+ }
3584
+ newIndex = i;
3585
+ }
3586
+ }
3587
+ const updated = { type, fromIndex: index, toIndex: newIndex };
3588
+ dragRef.current = updated;
3589
+ setDrag(updated);
3590
+ };
3591
+ const handleMouseUp = () => {
3592
+ document.removeEventListener("mousemove", handleMouseMove);
3593
+ document.removeEventListener("mouseup", handleMouseUp);
3594
+ const finalDrag = dragRef.current;
3595
+ dragRef.current = null;
3596
+ setDrag(null);
3597
+ if (finalDrag && hasMoved && finalDrag.fromIndex !== finalDrag.toIndex) {
3598
+ if (type === "column") {
3599
+ moveColumn(finalDrag.fromIndex, finalDrag.toIndex);
3600
+ } else {
3601
+ moveRow(finalDrag.fromIndex, finalDrag.toIndex);
2886
3602
  }
2887
- ) })
3603
+ }
3604
+ };
3605
+ document.addEventListener("mousemove", handleMouseMove);
3606
+ document.addEventListener("mouseup", handleMouseUp);
3607
+ },
3608
+ [colGrips, rowGrips, moveColumn, moveRow]
3609
+ );
3610
+ if (!editor || !tableInfo || colGrips.length === 0) return null;
3611
+ const GRIP_SIZE = 20;
3612
+ const GRIP_GAP = 4;
3613
+ const gripsVisible = isHovering || !!dropdown;
3614
+ const columnDropdownItems = [
3615
+ {
3616
+ label: "Toggle header column",
3617
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconTableColumn, { size: 16 }),
3618
+ action: () => {
3619
+ editor.chain().focus().toggleHeaderColumn().run();
3620
+ setDropdown(null);
2888
3621
  }
2889
- ) }),
2890
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VideoBlockMenu, { editor })
2891
- ] });
2892
- };
3622
+ },
3623
+ {
3624
+ label: "Insert column before",
3625
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconColumnInsertLeft, { size: 16 }),
3626
+ action: () => {
3627
+ editor.chain().focus().addColumnBefore().run();
3628
+ setDropdown(null);
3629
+ },
3630
+ separator: true
3631
+ },
3632
+ {
3633
+ label: "Insert column after",
3634
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconColumnInsertRight, { size: 16 }),
3635
+ action: () => {
3636
+ editor.chain().focus().addColumnAfter().run();
3637
+ setDropdown(null);
3638
+ }
3639
+ },
3640
+ {
3641
+ label: "Merge cells",
3642
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconArrowMerge, { size: 16 }),
3643
+ action: () => {
3644
+ editor.chain().focus().mergeCells().run();
3645
+ setDropdown(null);
3646
+ },
3647
+ separator: true
3648
+ },
3649
+ {
3650
+ label: "Split cell",
3651
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconArrowsSplit, { size: 16 }),
3652
+ action: () => {
3653
+ editor.chain().focus().splitCell().run();
3654
+ setDropdown(null);
3655
+ }
3656
+ },
3657
+ {
3658
+ label: "Delete column",
3659
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconColumnRemove, { size: 16 }),
3660
+ action: () => {
3661
+ editor.chain().focus().deleteColumn().run();
3662
+ setDropdown(null);
3663
+ },
3664
+ destructive: true,
3665
+ separator: true
3666
+ }
3667
+ ];
3668
+ const rowDropdownItems = [
3669
+ {
3670
+ label: "Toggle header row",
3671
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconTableRow, { size: 16 }),
3672
+ action: () => {
3673
+ editor.chain().focus().toggleHeaderRow().run();
3674
+ setDropdown(null);
3675
+ }
3676
+ },
3677
+ {
3678
+ label: "Insert row above",
3679
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconRowInsertTop, { size: 16 }),
3680
+ action: () => {
3681
+ editor.chain().focus().addRowBefore().run();
3682
+ setDropdown(null);
3683
+ },
3684
+ separator: true
3685
+ },
3686
+ {
3687
+ label: "Insert row below",
3688
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconRowInsertBottom, { size: 16 }),
3689
+ action: () => {
3690
+ editor.chain().focus().addRowAfter().run();
3691
+ setDropdown(null);
3692
+ }
3693
+ },
3694
+ {
3695
+ label: "Merge cells",
3696
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconArrowMerge, { size: 16 }),
3697
+ action: () => {
3698
+ editor.chain().focus().mergeCells().run();
3699
+ setDropdown(null);
3700
+ },
3701
+ separator: true
3702
+ },
3703
+ {
3704
+ label: "Split cell",
3705
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconArrowsSplit, { size: 16 }),
3706
+ action: () => {
3707
+ editor.chain().focus().splitCell().run();
3708
+ setDropdown(null);
3709
+ }
3710
+ },
3711
+ {
3712
+ label: "Delete row",
3713
+ icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconRowRemove, { size: 16 }),
3714
+ action: () => {
3715
+ editor.chain().focus().deleteRow().run();
3716
+ setDropdown(null);
3717
+ },
3718
+ destructive: true,
3719
+ separator: true
3720
+ }
3721
+ ];
3722
+ const dropdownItems = dropdown?.type === "column" ? columnDropdownItems : rowDropdownItems;
3723
+ return (0, import_react_dom2.createPortal)(
3724
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
3725
+ colGrips.map((grip, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3726
+ "button",
3727
+ {
3728
+ type: "button",
3729
+ className: `nph-table-grip nph-table-grip--col${gripsVisible ? " nph-table-grip--visible" : ""}${drag?.type === "column" && drag.fromIndex === i ? " nph-table-grip--dragging" : ""}`,
3730
+ style: {
3731
+ position: "fixed",
3732
+ left: grip.left + grip.width / 2 - GRIP_SIZE / 2,
3733
+ top: grip.top - GRIP_SIZE - GRIP_GAP,
3734
+ width: GRIP_SIZE,
3735
+ height: GRIP_SIZE,
3736
+ cursor: "grab"
3737
+ },
3738
+ onMouseDown: (e) => handleGripDragStart("column", i, e),
3739
+ onClick: (e) => handleColGripClick(i, e),
3740
+ "aria-label": `Column ${i + 1} options`,
3741
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconGripHorizontal, { size: 14 })
3742
+ },
3743
+ `col-${i}`
3744
+ )),
3745
+ rowGrips.map((grip, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3746
+ "button",
3747
+ {
3748
+ type: "button",
3749
+ className: `nph-table-grip nph-table-grip--row${gripsVisible ? " nph-table-grip--visible" : ""}${drag?.type === "row" && drag.fromIndex === i ? " nph-table-grip--dragging" : ""}`,
3750
+ style: {
3751
+ position: "fixed",
3752
+ left: grip.left - GRIP_SIZE - GRIP_GAP,
3753
+ top: grip.top + grip.height / 2 - GRIP_SIZE / 2,
3754
+ width: GRIP_SIZE,
3755
+ height: GRIP_SIZE,
3756
+ cursor: "grab"
3757
+ },
3758
+ onMouseDown: (e) => handleGripDragStart("row", i, e),
3759
+ onClick: (e) => handleRowGripClick(i, e),
3760
+ "aria-label": `Row ${i + 1} options`,
3761
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconGripVertical, { size: 14 })
3762
+ },
3763
+ `row-${i}`
3764
+ )),
3765
+ tableRect && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
3766
+ "button",
3767
+ {
3768
+ type: "button",
3769
+ className: `nph-table-grip nph-table-grip--delete${gripsVisible ? " nph-table-grip--visible" : ""}`,
3770
+ style: {
3771
+ position: "fixed",
3772
+ left: tableRect.left + tableRect.width / 2 - 60,
3773
+ top: tableRect.bottom + GRIP_GAP,
3774
+ width: 120,
3775
+ height: 24
3776
+ },
3777
+ onMouseDown: (e) => e.preventDefault(),
3778
+ onClick: () => {
3779
+ editor.chain().focus().deleteTable().run();
3780
+ },
3781
+ "aria-label": "Delete table",
3782
+ children: [
3783
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_react12.IconTableOff, { size: 14 }),
3784
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { style: { fontSize: 12 }, children: "Delete table" })
3785
+ ]
3786
+ }
3787
+ ),
3788
+ dropdown && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3789
+ "div",
3790
+ {
3791
+ ref: dropdownRef,
3792
+ className: "nph-table-dropdown",
3793
+ style: {
3794
+ position: "fixed",
3795
+ left: dropdown.x,
3796
+ top: dropdown.y,
3797
+ transform: dropdown.type === "column" ? "translateY(-100%)" : void 0
3798
+ },
3799
+ children: dropdownItems.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { children: [
3800
+ item.separator && i > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "nph-table-dropdown__separator" }),
3801
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
3802
+ "button",
3803
+ {
3804
+ type: "button",
3805
+ className: `nph-table-dropdown__item ${item.destructive ? "nph-table-dropdown__item--destructive" : ""}`,
3806
+ onMouseDown: (e) => e.preventDefault(),
3807
+ onClick: item.action,
3808
+ children: [
3809
+ item.icon,
3810
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: item.label })
3811
+ ]
3812
+ }
3813
+ )
3814
+ ] }, i))
3815
+ }
3816
+ ),
3817
+ drag && drag.fromIndex !== drag.toIndex && tableRect && (drag.type === "column" ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3818
+ "div",
3819
+ {
3820
+ className: "nph-table-drop-indicator nph-table-drop-indicator--col",
3821
+ style: {
3822
+ position: "fixed",
3823
+ left: drag.toIndex < colGrips.length ? colGrips[drag.toIndex].left - 1 : colGrips[colGrips.length - 1].left + colGrips[colGrips.length - 1].width,
3824
+ top: tableRect.top,
3825
+ width: 2,
3826
+ height: tableRect.height
3827
+ }
3828
+ }
3829
+ ) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3830
+ "div",
3831
+ {
3832
+ className: "nph-table-drop-indicator nph-table-drop-indicator--row",
3833
+ style: {
3834
+ position: "fixed",
3835
+ left: tableRect.left,
3836
+ top: drag.toIndex < rowGrips.length ? rowGrips[drag.toIndex].top - 1 : rowGrips[rowGrips.length - 1].top + rowGrips[rowGrips.length - 1].height,
3837
+ width: tableRect.width,
3838
+ height: 2
3839
+ }
3840
+ }
3841
+ ))
3842
+ ] }),
3843
+ document.body
3844
+ );
3845
+ }
2893
3846
 
2894
3847
  // src/react/Editor.tsx
2895
- var import_jsx_runtime18 = require("react/jsx-runtime");
2896
- function Editor3({
3848
+ var import_react35 = require("react");
3849
+ var import_jsx_runtime21 = require("react/jsx-runtime");
3850
+ function Editor5({
2897
3851
  content,
2898
3852
  className,
2899
3853
  editable = true,
2900
3854
  immediatelyRender = false,
2901
3855
  showTextMenu = true,
2902
3856
  showSlashMenu = true,
2903
- showImageMenu = true,
3857
+ showImageMenu = false,
3858
+ showDragHandle = true,
2904
3859
  extensions,
2905
3860
  bubbleMenuExtras,
2906
3861
  onUpdate,
@@ -2912,6 +3867,41 @@ function Editor3({
2912
3867
  slashCommand,
2913
3868
  placeholder
2914
3869
  }) {
3870
+ const [actionMenuAnchor, setActionMenuAnchor] = (0, import_react35.useState)(null);
3871
+ const [actionMenuEditor, setActionMenuEditor] = (0, import_react35.useState)(null);
3872
+ const actionMenuRef = (0, import_react35.useRef)(null);
3873
+ (0, import_react35.useEffect)(() => {
3874
+ if (!actionMenuAnchor) return;
3875
+ const handlePointerDown = (e) => {
3876
+ if (actionMenuRef.current && !actionMenuRef.current.contains(e.target)) {
3877
+ setActionMenuAnchor(null);
3878
+ }
3879
+ };
3880
+ document.addEventListener("pointerdown", handlePointerDown);
3881
+ return () => document.removeEventListener("pointerdown", handlePointerDown);
3882
+ }, [actionMenuAnchor]);
3883
+ const dragHandleCallbacks = (0, import_react35.useMemo)(
3884
+ () => ({
3885
+ onAddBlock: (editor) => {
3886
+ const { state } = editor;
3887
+ const { selection } = state;
3888
+ const { $anchor } = selection;
3889
+ const topDepth = Math.min($anchor.depth, 1);
3890
+ const endOfBlock = $anchor.end(topDepth);
3891
+ const insertPos = endOfBlock + 1;
3892
+ editor.chain().focus().insertContentAt(insertPos, { type: "paragraph" }).focus(insertPos + 1).run();
3893
+ requestAnimationFrame(() => {
3894
+ editor.commands.insertContent("/");
3895
+ });
3896
+ },
3897
+ onGripClick: (editor, _node, element) => {
3898
+ setActionMenuEditor(editor);
3899
+ setActionMenuAnchor((prev) => prev === element ? null : element);
3900
+ }
3901
+ }),
3902
+ []
3903
+ );
3904
+ const enableDragHandle = showDragHandle && editable;
2915
3905
  const normalizeExtras = (extras) => {
2916
3906
  const result = {
2917
3907
  start: [],
@@ -2928,59 +3918,185 @@ function Editor3({
2928
3918
  };
2929
3919
  const textExtras = normalizeExtras(bubbleMenuExtras?.text);
2930
3920
  const imageExtras = normalizeExtras(bubbleMenuExtras?.image);
2931
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(EditorRoot, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
2932
- EditorContent,
2933
- {
2934
- onUpdate,
2935
- onCreate,
2936
- immediatelyRender,
2937
- editable,
2938
- content,
2939
- extensions: [
2940
- ...extension_kit_default({
2941
- uploadImage,
2942
- collaboration,
2943
- imageBlockView: ImageBlockView,
2944
- videoBlockView: VideoBlockView,
2945
- mention: mentionOptions,
2946
- reference: referenceOptions,
2947
- slashCommand,
2948
- placeholder
2949
- }),
2950
- ...extensions ?? []
2951
- ],
2952
- editorProps: {
2953
- attributes: {
2954
- class: "nph-editor max-w-none outline-none"
2955
- },
2956
- handleKeyDown: (view, event) => {
2957
- return !!handleCommandNavigation?.(event);
2958
- }
2959
- },
2960
- children: [
2961
- showTextMenu ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2962
- TextMenu,
2963
- {
2964
- leadingExtras: textExtras.start,
2965
- trailingExtras: textExtras.end
3921
+ const handleCloseActionMenu = (0, import_react35.useCallback)(() => {
3922
+ setActionMenuAnchor(null);
3923
+ }, []);
3924
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className, children: [
3925
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EditorRoot, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
3926
+ EditorContent,
3927
+ {
3928
+ onUpdate,
3929
+ onCreate,
3930
+ immediatelyRender,
3931
+ editable,
3932
+ content,
3933
+ extensions: [
3934
+ ...extension_kit_default({
3935
+ uploadImage,
3936
+ collaboration,
3937
+ imageBlockView: ImageBlockView,
3938
+ videoBlockView: VideoBlockView,
3939
+ mention: mentionOptions,
3940
+ reference: referenceOptions,
3941
+ slashCommand,
3942
+ dragHandle: enableDragHandle,
3943
+ dragHandleCallbacks: enableDragHandle ? dragHandleCallbacks : void 0,
3944
+ placeholder
3945
+ }),
3946
+ ...extensions ?? []
3947
+ ],
3948
+ editorProps: {
3949
+ attributes: {
3950
+ class: "nph-editor max-w-none outline-none"
3951
+ },
3952
+ handleKeyDown: (view, event) => {
3953
+ return !!handleCommandNavigation?.(event);
2966
3954
  }
2967
- ) : null,
2968
- showImageMenu ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2969
- ImageMenu,
3955
+ },
3956
+ children: [
3957
+ showTextMenu ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3958
+ TextMenu,
3959
+ {
3960
+ leadingExtras: textExtras.start,
3961
+ trailingExtras: textExtras.end
3962
+ }
3963
+ ) : null,
3964
+ showImageMenu ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3965
+ ImageMenu,
3966
+ {
3967
+ leadingExtras: imageExtras.start,
3968
+ trailingExtras: imageExtras.end
3969
+ }
3970
+ ) : null,
3971
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(LinkMenu, {}),
3972
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(TableMenu, {}),
3973
+ showSlashMenu ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(SlashMenu, {}) : null
3974
+ ]
3975
+ }
3976
+ ) }),
3977
+ actionMenuAnchor && actionMenuEditor && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3978
+ "div",
3979
+ {
3980
+ ref: actionMenuRef,
3981
+ style: {
3982
+ position: "fixed",
3983
+ zIndex: 1e4,
3984
+ top: actionMenuAnchor.getBoundingClientRect().bottom + 4,
3985
+ left: actionMenuAnchor.getBoundingClientRect().left
3986
+ },
3987
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3988
+ BlockActionMenu,
2970
3989
  {
2971
- leadingExtras: imageExtras.start,
2972
- trailingExtras: imageExtras.end
3990
+ editor: actionMenuEditor,
3991
+ onClose: handleCloseActionMenu
2973
3992
  }
2974
- ) : null,
2975
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(LinkMenu, {}),
2976
- showSlashMenu ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(SlashMenu, {}) : null
2977
- ]
3993
+ )
3994
+ }
3995
+ )
3996
+ ] });
3997
+ }
3998
+
3999
+ // src/react/TableOfContents.tsx
4000
+ var import_react36 = require("@tiptap/react");
4001
+ var import_react37 = require("react");
4002
+ var import_jsx_runtime22 = require("react/jsx-runtime");
4003
+ function TableOfContents({
4004
+ className,
4005
+ itemClassName,
4006
+ activeClassName
4007
+ }) {
4008
+ const { editor } = (0, import_react36.useCurrentEditor)();
4009
+ const [activeId, setActiveId] = (0, import_react37.useState)(null);
4010
+ const observerRef = (0, import_react37.useRef)(null);
4011
+ const headings = (0, import_react36.useEditorState)({
4012
+ editor,
4013
+ selector: (ctx) => {
4014
+ if (!ctx.editor) return [];
4015
+ const items = [];
4016
+ ctx.editor.state.doc.descendants((node, pos) => {
4017
+ if (node.type.name === "heading") {
4018
+ const id = `heading-${pos}`;
4019
+ items.push({
4020
+ id,
4021
+ level: node.attrs.level,
4022
+ text: node.textContent,
4023
+ pos
4024
+ });
4025
+ }
4026
+ });
4027
+ return items;
2978
4028
  }
2979
- ) }) });
4029
+ });
4030
+ (0, import_react37.useEffect)(() => {
4031
+ if (!editor || !headings || headings.length === 0) return;
4032
+ observerRef.current?.disconnect();
4033
+ const callback = (entries) => {
4034
+ const visibleEntries = entries.filter((e) => e.isIntersecting);
4035
+ if (visibleEntries.length > 0) {
4036
+ const firstVisible = visibleEntries[0];
4037
+ const id = firstVisible.target.getAttribute("data-toc-id");
4038
+ if (id) setActiveId(id);
4039
+ }
4040
+ };
4041
+ const observer = new IntersectionObserver(callback, {
4042
+ rootMargin: "-80px 0px -70% 0px",
4043
+ threshold: 0
4044
+ });
4045
+ observerRef.current = observer;
4046
+ const editorEl = editor.view.dom;
4047
+ headings.forEach((heading) => {
4048
+ try {
4049
+ const domNode = editor.view.nodeDOM(heading.pos);
4050
+ if (domNode && domNode instanceof HTMLElement) {
4051
+ domNode.setAttribute("data-toc-id", heading.id);
4052
+ observer.observe(domNode);
4053
+ }
4054
+ } catch {
4055
+ }
4056
+ });
4057
+ return () => {
4058
+ observer.disconnect();
4059
+ };
4060
+ }, [editor, headings]);
4061
+ const handleClick = (0, import_react37.useCallback)(
4062
+ (pos) => {
4063
+ if (!editor) return;
4064
+ editor.chain().focus().setTextSelection(pos + 1).run();
4065
+ try {
4066
+ const domNode = editor.view.nodeDOM(pos);
4067
+ if (domNode && domNode instanceof HTMLElement) {
4068
+ domNode.scrollIntoView({ behavior: "smooth", block: "start" });
4069
+ }
4070
+ } catch {
4071
+ }
4072
+ },
4073
+ [editor]
4074
+ );
4075
+ if (!editor || !headings || headings.length === 0) return null;
4076
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("nav", { className: className ?? "nph-toc", children: headings.map((heading) => {
4077
+ const isActive = activeId === heading.id;
4078
+ const itemClass = [
4079
+ itemClassName ?? "nph-toc__item",
4080
+ isActive ? activeClassName ?? "nph-toc__item--active" : ""
4081
+ ].filter(Boolean).join(" ");
4082
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4083
+ "button",
4084
+ {
4085
+ type: "button",
4086
+ className: itemClass,
4087
+ style: { paddingLeft: `${(heading.level - 1) * 12 + 8}px` },
4088
+ onClick: () => handleClick(heading.pos),
4089
+ title: heading.text,
4090
+ children: heading.text || `Heading ${heading.level}`
4091
+ },
4092
+ heading.id
4093
+ );
4094
+ }) });
2980
4095
  }
2981
4096
  // Annotate the CommonJS export names for ESM import in node:
2982
4097
  0 && (module.exports = {
2983
4098
  Editor,
4099
+ TableOfContents,
2984
4100
  TextMenu
2985
4101
  });
2986
4102
  //# sourceMappingURL=index.cjs.map