neuphlo-editor 2.4.0 → 2.4.2

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.
@@ -0,0 +1,1443 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all2) => {
6
+ for (var name in all2)
7
+ __defProp(target, name, { get: all2[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
18
+
19
+ // src/headless/index.ts
20
+ import { useCurrentEditor as useCurrentEditor4 } from "@tiptap/react";
21
+
22
+ // src/headless/components/editor.tsx
23
+ import { EditorProvider } from "@tiptap/react";
24
+ import { forwardRef } from "react";
25
+ import { Provider } from "jotai";
26
+
27
+ // src/headless/utils/store.ts
28
+ var store_exports = {};
29
+ __export(store_exports, {
30
+ novelStore: () => novelStore
31
+ });
32
+ __reExport(store_exports, jotai_star);
33
+ import { createStore } from "jotai";
34
+ import * as jotai_star from "jotai";
35
+ var novelStore = createStore();
36
+
37
+ // src/headless/components/editor.tsx
38
+ import { jsx } from "react/jsx-runtime";
39
+ var EditorRoot = ({ children }) => {
40
+ return /* @__PURE__ */ jsx(Provider, { store: novelStore, children });
41
+ };
42
+ var EditorContent = forwardRef(
43
+ ({ className, children, initialContent, content, ...rest }, ref) => {
44
+ const effectiveContent = content ?? initialContent;
45
+ return /* @__PURE__ */ jsx("div", { ref, className, children: /* @__PURE__ */ jsx(EditorProvider, { ...rest, content: effectiveContent, children }) });
46
+ }
47
+ );
48
+ EditorContent.displayName = "EditorContent";
49
+
50
+ // src/headless/components/editor-bubble.tsx
51
+ import { useCurrentEditor } from "@tiptap/react";
52
+ import { BubbleMenu as BubbleMenuReact } from "@tiptap/react/menus";
53
+ import { jsx as jsx2 } from "react/jsx-runtime";
54
+ function EditorBubble({ className, children, ...rest }) {
55
+ const { editor } = useCurrentEditor();
56
+ if (!editor) return null;
57
+ return /* @__PURE__ */ jsx2(BubbleMenuReact, { editor, ...rest, children: /* @__PURE__ */ jsx2("div", { className, children }) });
58
+ }
59
+
60
+ // src/headless/components/editor-bubble-item.tsx
61
+ import { forwardRef as forwardRef2, isValidElement, cloneElement } from "react";
62
+ import { useCurrentEditor as useCurrentEditor2 } from "@tiptap/react";
63
+ import { jsx as jsx3 } from "react/jsx-runtime";
64
+ var EditorBubbleItem = forwardRef2(({ children, asChild, onSelect, ...rest }, ref) => {
65
+ const { editor } = useCurrentEditor2();
66
+ if (!editor) return null;
67
+ const handleClick = (e) => {
68
+ e.preventDefault();
69
+ onSelect?.(editor);
70
+ };
71
+ if (asChild && isValidElement(children)) {
72
+ const child = children;
73
+ const childOnClick = child.props?.onClick;
74
+ const mergedOnClick = (e) => {
75
+ childOnClick?.(e);
76
+ if (!e?.defaultPrevented) onSelect?.(editor);
77
+ };
78
+ return cloneElement(child, {
79
+ ...rest,
80
+ ref: child.ref ?? ref,
81
+ onClick: mergedOnClick
82
+ });
83
+ }
84
+ return /* @__PURE__ */ jsx3("div", { ref, ...rest, onClick: handleClick, children });
85
+ });
86
+ EditorBubbleItem.displayName = "EditorBubbleItem";
87
+
88
+ // src/headless/components/editor-command.tsx
89
+ import { useAtomValue } from "jotai";
90
+ import { forwardRef as forwardRef3, useRef, useEffect, useLayoutEffect, useState, useCallback } from "react";
91
+ import { createPortal } from "react-dom";
92
+
93
+ // src/headless/utils/atoms.ts
94
+ import { atom } from "jotai";
95
+ var queryAtom = atom("");
96
+ var rangeAtom = atom(null);
97
+ var slashMenuOpenAtom = atom(false);
98
+ var slashMenuRectAtom = atom(null);
99
+
100
+ // src/headless/components/editor-command.tsx
101
+ import { jsx as jsx4 } from "react/jsx-runtime";
102
+ var EditorCommandOut = () => null;
103
+ var EditorCommand = forwardRef3(
104
+ ({ children, className, ...rest }, ref) => {
105
+ const isOpen = useAtomValue(slashMenuOpenAtom, { store: novelStore });
106
+ const rect = useAtomValue(slashMenuRectAtom, { store: novelStore });
107
+ const containerRef = useRef(null);
108
+ const [activeIndex, setActiveIndex] = useState(0);
109
+ const contentRef = useRef(null);
110
+ const query = useAtomValue(queryAtom, { store: novelStore });
111
+ useEffect(() => {
112
+ setActiveIndex(0);
113
+ }, [query]);
114
+ if (typeof document !== "undefined" && !containerRef.current) {
115
+ const el = document.createElement("div");
116
+ el.style.position = "fixed";
117
+ el.style.zIndex = "9999";
118
+ el.style.minWidth = "240px";
119
+ el.style.display = "none";
120
+ containerRef.current = el;
121
+ }
122
+ useEffect(() => {
123
+ const el = containerRef.current;
124
+ if (!el) return;
125
+ document.body.appendChild(el);
126
+ return () => {
127
+ el.remove();
128
+ };
129
+ }, []);
130
+ useLayoutEffect(() => {
131
+ const container = containerRef.current;
132
+ if (!container) return;
133
+ if (!isOpen || !rect) {
134
+ container.style.display = "none";
135
+ return;
136
+ }
137
+ container.style.display = "";
138
+ const menuHeight = container.offsetHeight || 360;
139
+ const viewportHeight = window.innerHeight;
140
+ const spaceBelow = viewportHeight - rect.bottom;
141
+ const spaceAbove = rect.top;
142
+ let top;
143
+ if (spaceBelow < menuHeight + 16 && spaceAbove > spaceBelow) {
144
+ top = Math.round(rect.top - menuHeight - 8);
145
+ if (top < 8) top = 8;
146
+ } else {
147
+ top = Math.round(rect.bottom + 8);
148
+ }
149
+ let left = Math.round(rect.left);
150
+ const menuWidth = container.offsetWidth || 280;
151
+ if (left + menuWidth > window.innerWidth - 8) {
152
+ left = window.innerWidth - menuWidth - 8;
153
+ }
154
+ if (left < 8) left = 8;
155
+ container.style.top = `${top}px`;
156
+ container.style.left = `${left}px`;
157
+ }, [isOpen, rect]);
158
+ const handleKeyDown = useCallback(
159
+ (e) => {
160
+ if (!isOpen || !contentRef.current) return;
161
+ const items = contentRef.current.querySelectorAll("[role='option']");
162
+ if (items.length === 0) return;
163
+ if (e.key === "ArrowDown") {
164
+ e.preventDefault();
165
+ setActiveIndex((prev) => {
166
+ const next = Math.min(prev + 1, items.length - 1);
167
+ items[next]?.scrollIntoView({ block: "nearest" });
168
+ return next;
169
+ });
170
+ } else if (e.key === "ArrowUp") {
171
+ e.preventDefault();
172
+ setActiveIndex((prev) => {
173
+ const next = Math.max(prev - 1, 0);
174
+ items[next]?.scrollIntoView({ block: "nearest" });
175
+ return next;
176
+ });
177
+ } else if (e.key === "Enter") {
178
+ e.preventDefault();
179
+ const activeItem = items[activeIndex];
180
+ activeItem?.click();
181
+ }
182
+ },
183
+ [isOpen, activeIndex]
184
+ );
185
+ useEffect(() => {
186
+ if (!isOpen) return;
187
+ document.addEventListener("keydown", handleKeyDown);
188
+ return () => document.removeEventListener("keydown", handleKeyDown);
189
+ }, [isOpen, handleKeyDown]);
190
+ useEffect(() => {
191
+ if (!contentRef.current) return;
192
+ const items = contentRef.current.querySelectorAll("[role='option']");
193
+ items.forEach((item, i) => {
194
+ if (i === activeIndex) {
195
+ item.setAttribute("aria-selected", "true");
196
+ } else {
197
+ item.removeAttribute("aria-selected");
198
+ }
199
+ });
200
+ });
201
+ if (!isOpen || !containerRef.current) return null;
202
+ return createPortal(
203
+ /* @__PURE__ */ jsx4(
204
+ "div",
205
+ {
206
+ ref: (el) => {
207
+ contentRef.current = el;
208
+ if (typeof ref === "function") ref(el);
209
+ else if (ref) ref.current = el;
210
+ },
211
+ id: "slash-command",
212
+ className,
213
+ ...rest,
214
+ children
215
+ }
216
+ ),
217
+ containerRef.current
218
+ );
219
+ }
220
+ );
221
+ var EditorCommandList = forwardRef3(
222
+ ({ children, ...rest }, ref) => {
223
+ return /* @__PURE__ */ jsx4("div", { ref, role: "listbox", ...rest, children });
224
+ }
225
+ );
226
+ EditorCommand.displayName = "EditorCommand";
227
+ EditorCommandList.displayName = "EditorCommandList";
228
+
229
+ // src/headless/components/editor-command-item.tsx
230
+ import { forwardRef as forwardRef4 } from "react";
231
+ import { useCurrentEditor as useCurrentEditor3 } from "@tiptap/react";
232
+ import { useAtomValue as useAtomValue2 } from "jotai";
233
+ import { jsx as jsx5 } from "react/jsx-runtime";
234
+ var EditorCommandItem = forwardRef4(({ children, onCommand, value, className, ...rest }, ref) => {
235
+ const { editor } = useCurrentEditor3();
236
+ const range = useAtomValue2(rangeAtom, { store: novelStore });
237
+ const query = useAtomValue2(queryAtom, { store: novelStore });
238
+ if (!editor || !range) return null;
239
+ if (query && value) {
240
+ const searchText = value.toLowerCase();
241
+ const q = query.toLowerCase();
242
+ if (!searchText.includes(q)) return null;
243
+ }
244
+ return /* @__PURE__ */ jsx5(
245
+ "div",
246
+ {
247
+ ref,
248
+ role: "option",
249
+ className,
250
+ onClick: () => onCommand({ editor, range }),
251
+ ...rest,
252
+ children
253
+ }
254
+ );
255
+ });
256
+ EditorCommandItem.displayName = "EditorCommandItem";
257
+ var EditorCommandEmpty = forwardRef4(
258
+ ({ children, ...rest }, ref) => {
259
+ return /* @__PURE__ */ jsx5("div", { ref, ...rest, children });
260
+ }
261
+ );
262
+ EditorCommandEmpty.displayName = "EditorCommandEmpty";
263
+
264
+ // src/headless/extensions/index.ts
265
+ import { StarterKit } from "@tiptap/starter-kit";
266
+ import { Placeholder } from "@tiptap/extension-placeholder";
267
+
268
+ // src/headless/extensions/CodeBlock/CodeBlock.ts
269
+ import { CodeBlockLowlight } from "@tiptap/extension-code-block-lowlight";
270
+ import { all, createLowlight } from "lowlight";
271
+ var lowlight = createLowlight(all);
272
+ var CodeBlock = CodeBlockLowlight.configure({
273
+ lowlight
274
+ });
275
+
276
+ // src/headless/extensions/Link/Link.ts
277
+ import { mergeAttributes } from "@tiptap/core";
278
+ import TiptapLink from "@tiptap/extension-link";
279
+ import { Plugin } from "@tiptap/pm/state";
280
+ var Link = TiptapLink.extend({
281
+ inclusive: false,
282
+ parseHTML() {
283
+ return [
284
+ {
285
+ tag: 'a[href]:not([data-type="button"]):not([href *= "javascript:" i])'
286
+ }
287
+ ];
288
+ },
289
+ renderHTML({ HTMLAttributes }) {
290
+ return [
291
+ "a",
292
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
293
+ class: "link"
294
+ }),
295
+ 0
296
+ ];
297
+ },
298
+ addProseMirrorPlugins() {
299
+ const { editor } = this;
300
+ return [
301
+ ...this.parent?.() || [],
302
+ new Plugin({
303
+ props: {
304
+ handleKeyDown: (view, event) => {
305
+ const { selection } = editor.state;
306
+ if (event.key === "Escape" && selection.empty !== true) {
307
+ editor.commands.focus(selection.to, { scrollIntoView: false });
308
+ }
309
+ return false;
310
+ }
311
+ }
312
+ })
313
+ ];
314
+ }
315
+ }).configure({
316
+ openOnClick: false,
317
+ autolink: true,
318
+ enableClickSelection: true
319
+ });
320
+
321
+ // src/headless/extensions/ImageBlock/ImageBlock.ts
322
+ import { mergeAttributes as mergeAttributes2 } from "@tiptap/core";
323
+ import { Image as TiptapImage } from "@tiptap/extension-image";
324
+ import { ReactNodeViewRenderer } from "@tiptap/react";
325
+ import { Plugin as Plugin2, PluginKey } from "@tiptap/pm/state";
326
+ var ImageBlock = TiptapImage.extend({
327
+ name: "imageBlock",
328
+ group: "block",
329
+ defining: true,
330
+ isolating: true,
331
+ addOptions() {
332
+ return {
333
+ ...this.parent?.(),
334
+ inline: false
335
+ };
336
+ },
337
+ addAttributes() {
338
+ return {
339
+ src: {
340
+ default: "",
341
+ parseHTML: (element) => element.getAttribute("src"),
342
+ renderHTML: (attributes) => ({
343
+ src: attributes.src
344
+ })
345
+ },
346
+ width: {
347
+ default: "100%",
348
+ parseHTML: (element) => element.getAttribute("data-width"),
349
+ renderHTML: (attributes) => ({
350
+ "data-width": attributes.width
351
+ })
352
+ },
353
+ align: {
354
+ default: "center",
355
+ parseHTML: (element) => element.getAttribute("data-align"),
356
+ renderHTML: (attributes) => ({
357
+ "data-align": attributes.align
358
+ })
359
+ },
360
+ alt: {
361
+ default: void 0,
362
+ parseHTML: (element) => element.getAttribute("alt"),
363
+ renderHTML: (attributes) => ({
364
+ alt: attributes.alt
365
+ })
366
+ },
367
+ loading: {
368
+ default: false,
369
+ parseHTML: () => false,
370
+ renderHTML: () => ({})
371
+ }
372
+ };
373
+ },
374
+ parseHTML() {
375
+ return [
376
+ {
377
+ tag: 'img[src]:not([src^="data:"])',
378
+ getAttrs: (element) => {
379
+ const el = element;
380
+ return {
381
+ src: el.getAttribute("src"),
382
+ alt: el.getAttribute("alt"),
383
+ width: el.getAttribute("data-width") || "100%",
384
+ align: el.getAttribute("data-align") || "center"
385
+ };
386
+ }
387
+ }
388
+ ];
389
+ },
390
+ renderHTML({ HTMLAttributes }) {
391
+ return ["img", mergeAttributes2(HTMLAttributes)];
392
+ },
393
+ addCommands() {
394
+ return {
395
+ setImageBlock: (attrs) => ({ commands }) => {
396
+ return commands.insertContent({
397
+ type: "imageBlock",
398
+ attrs: { src: attrs.src }
399
+ });
400
+ },
401
+ setImageBlockAt: (attrs) => ({ commands }) => {
402
+ return commands.insertContentAt(attrs.pos, {
403
+ type: "imageBlock",
404
+ attrs: { src: attrs.src }
405
+ });
406
+ },
407
+ setImageBlockAlign: (align) => ({ commands }) => commands.updateAttributes("imageBlock", { align }),
408
+ setImageBlockWidth: (width) => ({ commands }) => commands.updateAttributes("imageBlock", {
409
+ width: `${Math.max(0, Math.min(100, width))}%`
410
+ })
411
+ };
412
+ },
413
+ addNodeView() {
414
+ if (this.options.nodeView) {
415
+ return ReactNodeViewRenderer(this.options.nodeView);
416
+ }
417
+ return null;
418
+ },
419
+ addProseMirrorPlugins() {
420
+ return [
421
+ new Plugin2({
422
+ key: new PluginKey("imageBlockDrop"),
423
+ props: {
424
+ handleDOMEvents: {
425
+ drop: (view, event) => {
426
+ const hasFiles = event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files.length;
427
+ if (!hasFiles) {
428
+ return false;
429
+ }
430
+ const images = Array.from(event.dataTransfer.files).filter(
431
+ (file) => /image/i.test(file.type)
432
+ );
433
+ if (images.length === 0) {
434
+ return false;
435
+ }
436
+ event.preventDefault();
437
+ const { schema } = view.state;
438
+ const coordinates = view.posAtCoords({
439
+ left: event.clientX,
440
+ top: event.clientY
441
+ });
442
+ if (!coordinates) return false;
443
+ images.forEach(async (image) => {
444
+ if (this.options.uploadImage) {
445
+ try {
446
+ const placeholderNode = schema.nodes.imageBlock.create({
447
+ src: "",
448
+ loading: true
449
+ });
450
+ const placeholderTr = view.state.tr.insert(
451
+ coordinates.pos,
452
+ placeholderNode
453
+ );
454
+ view.dispatch(placeholderTr);
455
+ const url = await this.options.uploadImage(image);
456
+ const node = schema.nodes.imageBlock.create({ src: url });
457
+ const currentState = view.state;
458
+ let foundPos = -1;
459
+ currentState.doc.descendants((node2, pos) => {
460
+ if (node2.type.name === "imageBlock" && node2.attrs.loading) {
461
+ foundPos = pos;
462
+ return false;
463
+ }
464
+ });
465
+ if (foundPos !== -1) {
466
+ const transaction = view.state.tr.replaceWith(
467
+ foundPos,
468
+ foundPos + 1,
469
+ node
470
+ );
471
+ view.dispatch(transaction);
472
+ }
473
+ } catch (error) {
474
+ console.error("Failed to upload image:", error);
475
+ const currentState = view.state;
476
+ let foundPos = -1;
477
+ currentState.doc.descendants((node, pos) => {
478
+ if (node.type.name === "imageBlock" && node.attrs.loading) {
479
+ foundPos = pos;
480
+ return false;
481
+ }
482
+ });
483
+ if (foundPos !== -1) {
484
+ const transaction = view.state.tr.delete(
485
+ foundPos,
486
+ foundPos + 1
487
+ );
488
+ view.dispatch(transaction);
489
+ }
490
+ }
491
+ }
492
+ });
493
+ return true;
494
+ },
495
+ paste: (view, event) => {
496
+ const hasFiles = event.clipboardData && event.clipboardData.files && event.clipboardData.files.length;
497
+ if (!hasFiles) {
498
+ return false;
499
+ }
500
+ const images = Array.from(event.clipboardData.files).filter(
501
+ (file) => /image/i.test(file.type)
502
+ );
503
+ if (images.length === 0) {
504
+ return false;
505
+ }
506
+ event.preventDefault();
507
+ images.forEach(async (image) => {
508
+ if (this.options.uploadImage) {
509
+ try {
510
+ const placeholderNode = view.state.schema.nodes.imageBlock.create({
511
+ src: "",
512
+ loading: true
513
+ });
514
+ view.dispatch(
515
+ view.state.tr.replaceSelectionWith(placeholderNode)
516
+ );
517
+ const url = await this.options.uploadImage(image);
518
+ const node = view.state.schema.nodes.imageBlock.create({
519
+ src: url
520
+ });
521
+ const currentState = view.state;
522
+ let foundPos = -1;
523
+ currentState.doc.descendants((node2, pos) => {
524
+ if (node2.type.name === "imageBlock" && node2.attrs.loading) {
525
+ foundPos = pos;
526
+ return false;
527
+ }
528
+ });
529
+ if (foundPos !== -1) {
530
+ const transaction = view.state.tr.replaceWith(
531
+ foundPos,
532
+ foundPos + 1,
533
+ node
534
+ );
535
+ view.dispatch(transaction);
536
+ }
537
+ } catch (error) {
538
+ console.error("Failed to upload image:", error);
539
+ const currentState = view.state;
540
+ let foundPos = -1;
541
+ currentState.doc.descendants((node, pos) => {
542
+ if (node.type.name === "imageBlock" && node.attrs.loading) {
543
+ foundPos = pos;
544
+ return false;
545
+ }
546
+ });
547
+ if (foundPos !== -1) {
548
+ const transaction = view.state.tr.delete(
549
+ foundPos,
550
+ foundPos + 1
551
+ );
552
+ view.dispatch(transaction);
553
+ }
554
+ }
555
+ }
556
+ });
557
+ return true;
558
+ }
559
+ }
560
+ }
561
+ })
562
+ ];
563
+ }
564
+ });
565
+
566
+ // src/headless/extensions/Mention/mention.tsx
567
+ import { ReactRenderer, ReactNodeViewRenderer as ReactNodeViewRenderer2 } from "@tiptap/react";
568
+ import Mention from "@tiptap/extension-mention";
569
+
570
+ // src/headless/extensions/Mention/mention-command.tsx
571
+ import { forwardRef as forwardRef5, useEffect as useEffect2, useImperativeHandle, useState as useState2 } from "react";
572
+ import { jsx as jsx6, jsxs } from "react/jsx-runtime";
573
+ var GRADIENT_MAP = {
574
+ red: "linear-gradient(135deg, #ef4444, #fb7185)",
575
+ coral: "linear-gradient(135deg, #f87171, #fb923c)",
576
+ orange: "linear-gradient(135deg, #f97316, #fbbf24)",
577
+ amber: "linear-gradient(135deg, #f59e0b, #facc15)",
578
+ lime: "linear-gradient(135deg, #84cc16, #4ade80)",
579
+ green: "linear-gradient(135deg, #10b981, #2dd4bf)",
580
+ teal: "linear-gradient(135deg, #14b8a6, #22d3ee)",
581
+ cyan: "linear-gradient(135deg, #06b6d4, #60a5fa)",
582
+ blue: "linear-gradient(135deg, #3b82f6, #818cf8)",
583
+ indigo: "linear-gradient(135deg, #6366f1, #a78bfa)",
584
+ violet: "linear-gradient(135deg, #8b5cf6, #a855f7)",
585
+ purple: "linear-gradient(135deg, #a855f7, #d946ef)",
586
+ fuchsia: "linear-gradient(135deg, #d946ef, #f472b6)",
587
+ pink: "linear-gradient(135deg, #ec4899, #fb7185)",
588
+ slate: "linear-gradient(135deg, #64748b, #71717a)",
589
+ stone: "linear-gradient(135deg, #78716c, #a3a3a3)"
590
+ };
591
+ function isGradientKey(avatar) {
592
+ return !!avatar && avatar in GRADIENT_MAP;
593
+ }
594
+ function isAgentItem(item) {
595
+ return !!item.email?.endsWith("@ai.neuphlo.local");
596
+ }
597
+ function getInitials(name) {
598
+ if (!name) return "AG";
599
+ return name.split(/\s+/).map((w) => w[0]).join("").slice(0, 2).toUpperCase();
600
+ }
601
+ function Avatar({ src, fallback, isAgent }) {
602
+ const gradient = src && isGradientKey(src) ? GRADIENT_MAP[src] : isAgent ? GRADIENT_MAP.violet : null;
603
+ return /* @__PURE__ */ jsx6("div", { style: {
604
+ position: "relative",
605
+ display: "flex",
606
+ height: "24px",
607
+ width: "24px",
608
+ flexShrink: 0,
609
+ overflow: "hidden",
610
+ borderRadius: "9999px"
611
+ }, children: gradient ? /* @__PURE__ */ jsx6("div", { style: {
612
+ display: "flex",
613
+ height: "100%",
614
+ width: "100%",
615
+ alignItems: "center",
616
+ justifyContent: "center",
617
+ borderRadius: "9999px",
618
+ background: gradient,
619
+ color: "#ffffff",
620
+ fontSize: "10px",
621
+ fontWeight: 600
622
+ }, children: fallback }) : src ? /* @__PURE__ */ jsx6(
623
+ "img",
624
+ {
625
+ style: {
626
+ aspectRatio: "1",
627
+ height: "100%",
628
+ width: "100%"
629
+ },
630
+ src,
631
+ alt: fallback
632
+ }
633
+ ) : /* @__PURE__ */ jsx6("div", { style: {
634
+ display: "flex",
635
+ height: "100%",
636
+ width: "100%",
637
+ alignItems: "center",
638
+ justifyContent: "center",
639
+ borderRadius: "9999px",
640
+ backgroundColor: "var(--muted)",
641
+ color: "var(--muted-foreground)",
642
+ fontSize: "10px",
643
+ fontWeight: 600
644
+ }, children: fallback }) });
645
+ }
646
+ function ReferenceIcon({ type }) {
647
+ if (type === "node") {
648
+ return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0 }, children: [
649
+ /* @__PURE__ */ jsx6("path", { d: "M12 3l8 4.5v9l-8 4.5l-8-4.5v-9l8-4.5" }),
650
+ /* @__PURE__ */ jsx6("path", { d: "M12 12l8-4.5" }),
651
+ /* @__PURE__ */ jsx6("path", { d: "M12 12v9" }),
652
+ /* @__PURE__ */ jsx6("path", { d: "M12 12l-8-4.5" })
653
+ ] });
654
+ }
655
+ return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0 }, children: [
656
+ /* @__PURE__ */ jsx6("path", { d: "M14 3v4a1 1 0 0 0 1 1h4" }),
657
+ /* @__PURE__ */ jsx6("path", { d: "M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" }),
658
+ /* @__PURE__ */ jsx6("path", { d: "M9 9l1 0" }),
659
+ /* @__PURE__ */ jsx6("path", { d: "M9 13l6 0" }),
660
+ /* @__PURE__ */ jsx6("path", { d: "M9 17l6 0" })
661
+ ] });
662
+ }
663
+ var MentionCommand = forwardRef5((props, ref) => {
664
+ const [selectedIndex, setSelectedIndex] = useState2(0);
665
+ const selectItem = (index) => {
666
+ const item = props.items[index];
667
+ if (item) {
668
+ props.command(item);
669
+ }
670
+ };
671
+ const upHandler = () => {
672
+ setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length);
673
+ };
674
+ const downHandler = () => {
675
+ setSelectedIndex((selectedIndex + 1) % props.items.length);
676
+ };
677
+ const enterHandler = () => {
678
+ selectItem(selectedIndex);
679
+ };
680
+ useEffect2(() => setSelectedIndex(0), [props.items]);
681
+ useImperativeHandle(ref, () => ({
682
+ onKeyDown: ({ event }) => {
683
+ if (event.key === "ArrowUp") {
684
+ upHandler();
685
+ return true;
686
+ }
687
+ if (event.key === "ArrowDown") {
688
+ downHandler();
689
+ return true;
690
+ }
691
+ if (event.key === "Enter") {
692
+ enterHandler();
693
+ return true;
694
+ }
695
+ return false;
696
+ }
697
+ }));
698
+ return /* @__PURE__ */ jsx6(
699
+ "div",
700
+ {
701
+ id: "mention-list",
702
+ style: {
703
+ backgroundColor: "var(--popover)",
704
+ color: "var(--foreground)",
705
+ maxHeight: "300px",
706
+ minWidth: "280px",
707
+ overflow: "hidden",
708
+ overflowY: "auto",
709
+ borderRadius: "12px",
710
+ border: "1px solid var(--border)",
711
+ padding: "4px",
712
+ boxShadow: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)"
713
+ },
714
+ children: props.items.length ? (() => {
715
+ const agents = props.items.filter((i) => !i.type && isAgentItem(i));
716
+ const others = props.items.filter((i) => i.type || !isAgentItem(i));
717
+ const sections = [];
718
+ if (agents.length) sections.push({ label: "Agents", items: agents, startIndex: 0 });
719
+ if (others.length) sections.push({ label: agents.length ? "Members" : null, items: others, startIndex: agents.length });
720
+ return sections.map((section) => /* @__PURE__ */ jsxs("div", { children: [
721
+ section.label && /* @__PURE__ */ jsx6("div", { style: { padding: "6px 8px 2px", fontSize: "11px", fontWeight: 600, color: "var(--muted-foreground)", textTransform: "uppercase", letterSpacing: "0.05em" }, children: section.label }),
722
+ section.items.map((item, i) => {
723
+ const index = section.startIndex + i;
724
+ const agent = isAgentItem(item);
725
+ return /* @__PURE__ */ jsxs(
726
+ "button",
727
+ {
728
+ type: "button",
729
+ onClick: () => selectItem(index),
730
+ onMouseEnter: () => setSelectedIndex(index),
731
+ style: {
732
+ position: "relative",
733
+ display: "flex",
734
+ width: "100%",
735
+ cursor: "default",
736
+ userSelect: "none",
737
+ alignItems: "center",
738
+ gap: "8px",
739
+ borderRadius: "4px",
740
+ padding: "10px 8px",
741
+ fontSize: "14px",
742
+ outline: "none",
743
+ backgroundColor: index === selectedIndex ? "var(--accent)" : "transparent",
744
+ color: index === selectedIndex ? "var(--accent-foreground)" : "inherit",
745
+ border: "none",
746
+ textAlign: "left"
747
+ },
748
+ children: [
749
+ item.type ? /* @__PURE__ */ jsx6(ReferenceIcon, { type: item.type }) : /* @__PURE__ */ jsx6(
750
+ Avatar,
751
+ {
752
+ src: item.avatar,
753
+ fallback: getInitials(item.label),
754
+ isAgent: agent
755
+ }
756
+ ),
757
+ /* @__PURE__ */ jsx6("span", { style: {
758
+ flex: 1,
759
+ overflow: "hidden",
760
+ textOverflow: "ellipsis",
761
+ whiteSpace: "nowrap"
762
+ }, children: item.label })
763
+ ]
764
+ },
765
+ item.id
766
+ );
767
+ })
768
+ ] }, section.label ?? "default"));
769
+ })() : /* @__PURE__ */ jsx6("div", { style: {
770
+ padding: "24px 0",
771
+ textAlign: "center",
772
+ fontSize: "14px",
773
+ color: "var(--muted-foreground)"
774
+ }, children: "No results found" })
775
+ }
776
+ );
777
+ });
778
+ MentionCommand.displayName = "MentionCommand";
779
+
780
+ // src/headless/extensions/Mention/mention-node-view.tsx
781
+ import { NodeViewWrapper } from "@tiptap/react";
782
+ import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
783
+ var GRADIENT_MAP2 = {
784
+ red: "linear-gradient(135deg, #ef4444, #fb7185)",
785
+ coral: "linear-gradient(135deg, #f87171, #fb923c)",
786
+ orange: "linear-gradient(135deg, #f97316, #fbbf24)",
787
+ amber: "linear-gradient(135deg, #f59e0b, #facc15)",
788
+ lime: "linear-gradient(135deg, #84cc16, #4ade80)",
789
+ green: "linear-gradient(135deg, #10b981, #2dd4bf)",
790
+ teal: "linear-gradient(135deg, #14b8a6, #22d3ee)",
791
+ cyan: "linear-gradient(135deg, #06b6d4, #60a5fa)",
792
+ blue: "linear-gradient(135deg, #3b82f6, #818cf8)",
793
+ indigo: "linear-gradient(135deg, #6366f1, #a78bfa)",
794
+ violet: "linear-gradient(135deg, #8b5cf6, #a855f7)",
795
+ purple: "linear-gradient(135deg, #a855f7, #d946ef)",
796
+ fuchsia: "linear-gradient(135deg, #d946ef, #f472b6)",
797
+ pink: "linear-gradient(135deg, #ec4899, #fb7185)",
798
+ slate: "linear-gradient(135deg, #64748b, #71717a)",
799
+ stone: "linear-gradient(135deg, #78716c, #a3a3a3)"
800
+ };
801
+ function NodeIcon() {
802
+ return /* @__PURE__ */ jsxs2("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0 }, children: [
803
+ /* @__PURE__ */ jsx7("path", { d: "M12 3l8 4.5v9l-8 4.5l-8-4.5v-9l8-4.5" }),
804
+ /* @__PURE__ */ jsx7("path", { d: "M12 12l8-4.5" }),
805
+ /* @__PURE__ */ jsx7("path", { d: "M12 12v9" }),
806
+ /* @__PURE__ */ jsx7("path", { d: "M12 12l-8-4.5" })
807
+ ] });
808
+ }
809
+ function ArticleIcon() {
810
+ return /* @__PURE__ */ jsxs2("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { flexShrink: 0 }, children: [
811
+ /* @__PURE__ */ jsx7("path", { d: "M14 3v4a1 1 0 0 0 1 1h4" }),
812
+ /* @__PURE__ */ jsx7("path", { d: "M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" }),
813
+ /* @__PURE__ */ jsx7("path", { d: "M9 9l1 0" }),
814
+ /* @__PURE__ */ jsx7("path", { d: "M9 13l6 0" }),
815
+ /* @__PURE__ */ jsx7("path", { d: "M9 17l6 0" })
816
+ ] });
817
+ }
818
+ var MentionNodeView = (props) => {
819
+ const { node } = props;
820
+ const { id, label, avatar, type } = node.attrs;
821
+ const isNode = type === "node";
822
+ const isArticle = type === "article";
823
+ const isReference = isNode || isArticle;
824
+ const backgroundColor = isArticle ? "rgba(249, 115, 22, 0.15)" : isNode ? "rgba(107, 114, 128, 0.15)" : "var(--accent)";
825
+ const color = isArticle ? "rgb(234, 88, 12)" : isNode ? "rgb(75, 85, 99)" : "var(--accent-foreground)";
826
+ const border = isArticle ? "1px solid rgba(249, 115, 22, 0.3)" : isNode ? "1px solid rgba(107, 114, 128, 0.3)" : "none";
827
+ return /* @__PURE__ */ jsxs2(
828
+ NodeViewWrapper,
829
+ {
830
+ as: "span",
831
+ style: {
832
+ display: "inline-flex",
833
+ alignItems: "center",
834
+ gap: "3px",
835
+ backgroundColor,
836
+ color,
837
+ border,
838
+ padding: isReference ? "1px 6px" : "1px 6px 1px 1px",
839
+ borderRadius: "12px",
840
+ fontSize: "13px",
841
+ fontWeight: 500,
842
+ verticalAlign: "middle"
843
+ },
844
+ children: [
845
+ isNode && /* @__PURE__ */ jsx7(NodeIcon, {}),
846
+ isArticle && /* @__PURE__ */ jsx7(ArticleIcon, {}),
847
+ !isReference && avatar && avatar in GRADIENT_MAP2 && /* @__PURE__ */ jsx7(
848
+ "div",
849
+ {
850
+ style: {
851
+ width: "20px",
852
+ height: "20px",
853
+ borderRadius: "50%",
854
+ background: GRADIENT_MAP2[avatar],
855
+ color: "#ffffff",
856
+ display: "flex",
857
+ alignItems: "center",
858
+ justifyContent: "center",
859
+ fontSize: "8px",
860
+ fontWeight: 600,
861
+ flexShrink: 0
862
+ },
863
+ children: (label || id).split(/\s+/).map((w) => w[0]).join("").slice(0, 2).toUpperCase()
864
+ }
865
+ ),
866
+ !isReference && avatar && !(avatar in GRADIENT_MAP2) && /* @__PURE__ */ jsx7(
867
+ "img",
868
+ {
869
+ src: avatar,
870
+ alt: label || id,
871
+ style: {
872
+ width: "20px",
873
+ height: "20px",
874
+ borderRadius: "50%",
875
+ flexShrink: 0
876
+ }
877
+ }
878
+ ),
879
+ !isReference && !avatar && /* @__PURE__ */ jsx7(
880
+ "div",
881
+ {
882
+ style: {
883
+ width: "20px",
884
+ height: "20px",
885
+ borderRadius: "50%",
886
+ backgroundColor: "var(--muted)",
887
+ color: "var(--muted-foreground)",
888
+ display: "flex",
889
+ alignItems: "center",
890
+ justifyContent: "center",
891
+ fontSize: "10px",
892
+ fontWeight: 600,
893
+ flexShrink: 0
894
+ },
895
+ children: (label || id).slice(0, 2).toUpperCase()
896
+ }
897
+ ),
898
+ /* @__PURE__ */ jsx7("span", { children: label || id })
899
+ ]
900
+ }
901
+ );
902
+ };
903
+
904
+ // src/headless/extensions/Mention/mention.tsx
905
+ var createMentionExtension = (options) => {
906
+ const extensionName = options?.name ?? "mention";
907
+ return Mention.extend({
908
+ name: extensionName,
909
+ addAttributes() {
910
+ return {
911
+ id: {
912
+ default: null,
913
+ parseHTML: (element) => element.getAttribute("data-id"),
914
+ renderHTML: (attributes) => {
915
+ if (!attributes.id) {
916
+ return {};
917
+ }
918
+ return {
919
+ "data-id": attributes.id
920
+ };
921
+ }
922
+ },
923
+ label: {
924
+ default: null,
925
+ parseHTML: (element) => element.getAttribute("data-label"),
926
+ renderHTML: (attributes) => {
927
+ if (!attributes.label) {
928
+ return {};
929
+ }
930
+ return {
931
+ "data-label": attributes.label
932
+ };
933
+ }
934
+ },
935
+ avatar: {
936
+ default: null,
937
+ parseHTML: (element) => element.getAttribute("data-avatar"),
938
+ renderHTML: (attributes) => {
939
+ if (!attributes.avatar) {
940
+ return {};
941
+ }
942
+ return {
943
+ "data-avatar": attributes.avatar
944
+ };
945
+ }
946
+ },
947
+ type: {
948
+ default: null,
949
+ parseHTML: (element) => element.getAttribute("data-ref-type"),
950
+ renderHTML: (attributes) => {
951
+ if (!attributes.type) {
952
+ return {};
953
+ }
954
+ return {
955
+ "data-ref-type": attributes.type
956
+ };
957
+ }
958
+ },
959
+ nodeId: {
960
+ default: null,
961
+ parseHTML: (element) => element.getAttribute("data-node-id"),
962
+ renderHTML: (attributes) => {
963
+ if (!attributes.nodeId) {
964
+ return {};
965
+ }
966
+ return {
967
+ "data-node-id": attributes.nodeId
968
+ };
969
+ }
970
+ },
971
+ slug: {
972
+ default: null,
973
+ parseHTML: (element) => element.getAttribute("data-slug"),
974
+ renderHTML: (attributes) => {
975
+ if (!attributes.slug) {
976
+ return {};
977
+ }
978
+ return {
979
+ "data-slug": attributes.slug
980
+ };
981
+ }
982
+ }
983
+ };
984
+ },
985
+ addNodeView() {
986
+ return ReactNodeViewRenderer2(MentionNodeView);
987
+ }
988
+ }).configure({
989
+ HTMLAttributes: {
990
+ class: "mention"
991
+ },
992
+ suggestion: {
993
+ char: options?.char ?? "@",
994
+ items: async ({ query }) => {
995
+ if (!options?.items) return [];
996
+ const items = await options.items(query);
997
+ return items;
998
+ },
999
+ command: ({ editor, range, props: item }) => {
1000
+ editor.chain().focus().insertContentAt(range, [
1001
+ {
1002
+ type: extensionName,
1003
+ attrs: {
1004
+ id: item.id,
1005
+ label: item.label,
1006
+ avatar: item.avatar,
1007
+ type: item.type,
1008
+ nodeId: item.nodeId,
1009
+ slug: item.slug
1010
+ }
1011
+ },
1012
+ { type: "text", text: " " }
1013
+ ]).run();
1014
+ },
1015
+ render: renderMentionSuggestion
1016
+ },
1017
+ renderLabel: options?.renderLabel ?? ((props) => {
1018
+ return `@${props.node.attrs.label ?? props.node.attrs.id}`;
1019
+ })
1020
+ });
1021
+ };
1022
+ var renderMentionSuggestion = () => {
1023
+ let component = null;
1024
+ let container = null;
1025
+ const destroy = () => {
1026
+ component?.destroy();
1027
+ component = null;
1028
+ if (container) {
1029
+ container.remove();
1030
+ container = null;
1031
+ }
1032
+ };
1033
+ const updatePosition = (clientRect) => {
1034
+ if (!container || !clientRect) return;
1035
+ const gap = 8;
1036
+ const maxDropdownHeight = 300;
1037
+ const spaceBelow = window.innerHeight - clientRect.bottom;
1038
+ const spaceAbove = clientRect.top;
1039
+ const shouldPositionAbove = spaceBelow < maxDropdownHeight && spaceAbove > spaceBelow;
1040
+ const left = Math.round(clientRect.left);
1041
+ if (shouldPositionAbove) {
1042
+ const bottom = Math.round(window.innerHeight - clientRect.top + gap);
1043
+ container.style.bottom = `${bottom}px`;
1044
+ container.style.top = "auto";
1045
+ } else {
1046
+ const top = Math.round(clientRect.bottom + gap);
1047
+ container.style.top = `${top}px`;
1048
+ container.style.bottom = "auto";
1049
+ }
1050
+ container.style.left = `${left}px`;
1051
+ };
1052
+ return {
1053
+ onStart: (props) => {
1054
+ component = new ReactRenderer(MentionCommand, {
1055
+ props: {
1056
+ items: props.items ?? [],
1057
+ command: props.command ?? (() => {
1058
+ }),
1059
+ query: props.query ?? ""
1060
+ },
1061
+ editor: props.editor
1062
+ });
1063
+ container = document.createElement("div");
1064
+ container.style.position = "fixed";
1065
+ container.style.zIndex = "9999";
1066
+ document.body.appendChild(container);
1067
+ container.appendChild(component.element);
1068
+ const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
1069
+ if (rect) updatePosition(rect);
1070
+ },
1071
+ onUpdate: (props) => {
1072
+ component?.updateProps({
1073
+ items: props.items ?? [],
1074
+ command: props.command ?? (() => {
1075
+ }),
1076
+ query: props.query ?? ""
1077
+ });
1078
+ const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
1079
+ if (rect) updatePosition(rect);
1080
+ },
1081
+ onKeyDown: ({ event }) => {
1082
+ if (!component) return false;
1083
+ if (event.key === "Escape") {
1084
+ event.preventDefault();
1085
+ event.stopPropagation();
1086
+ destroy();
1087
+ return true;
1088
+ }
1089
+ if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
1090
+ event.preventDefault();
1091
+ event.stopPropagation();
1092
+ return component.ref?.onKeyDown?.({ event }) ?? false;
1093
+ }
1094
+ return false;
1095
+ },
1096
+ onExit: () => {
1097
+ destroy();
1098
+ }
1099
+ };
1100
+ };
1101
+
1102
+ // src/headless/extensions/DragHandle/DragHandle.ts
1103
+ import BaseDragHandle from "@tiptap/extension-drag-handle";
1104
+ var currentCallbacks = {};
1105
+ var currentNode = null;
1106
+ var currentEditor = null;
1107
+ function setDragHandleCallbacks(callbacks) {
1108
+ currentCallbacks = callbacks;
1109
+ }
1110
+ function createDragHandleElement() {
1111
+ const container = document.createElement("div");
1112
+ container.className = "nph-drag-handle";
1113
+ const plusBtn = document.createElement("button");
1114
+ plusBtn.className = "nph-drag-handle__btn";
1115
+ plusBtn.type = "button";
1116
+ plusBtn.setAttribute("aria-label", "Add block");
1117
+ 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>`;
1118
+ plusBtn.addEventListener("click", (e) => {
1119
+ e.preventDefault();
1120
+ e.stopPropagation();
1121
+ if (currentEditor) {
1122
+ currentCallbacks.onAddBlock?.(currentEditor, currentNode);
1123
+ }
1124
+ });
1125
+ const gripBtn = document.createElement("button");
1126
+ gripBtn.className = "nph-drag-handle__btn nph-drag-handle__grip";
1127
+ gripBtn.type = "button";
1128
+ gripBtn.setAttribute("aria-label", "Drag to reorder");
1129
+ 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>`;
1130
+ gripBtn.addEventListener("click", (e) => {
1131
+ e.preventDefault();
1132
+ e.stopPropagation();
1133
+ if (currentEditor) {
1134
+ currentCallbacks.onGripClick?.(currentEditor, currentNode, container);
1135
+ }
1136
+ });
1137
+ container.appendChild(plusBtn);
1138
+ container.appendChild(gripBtn);
1139
+ return container;
1140
+ }
1141
+ var DragHandle = BaseDragHandle.configure({
1142
+ render: createDragHandleElement,
1143
+ nested: true,
1144
+ onNodeChange: ({ node, editor }) => {
1145
+ currentNode = node;
1146
+ currentEditor = editor;
1147
+ }
1148
+ });
1149
+
1150
+ // src/headless/extensions/Table/index.ts
1151
+ import { TableKit } from "@tiptap/extension-table";
1152
+ import { Table, TableCell, TableHeader, TableRow } from "@tiptap/extension-table";
1153
+
1154
+ // src/headless/extensions/MarkdownPaste.ts
1155
+ import { Extension } from "@tiptap/core";
1156
+ import { Plugin as Plugin3, PluginKey as PluginKey2 } from "@tiptap/pm/state";
1157
+ import { MarkdownParser, defaultMarkdownParser } from "@tiptap/pm/markdown";
1158
+ var markdownPastePluginKey = new PluginKey2("markdownPaste");
1159
+ function looksLikeMarkdown(text) {
1160
+ const patterns = [
1161
+ /^#{1,6}\s/m,
1162
+ // headings
1163
+ /^\s*[-*+]\s/m,
1164
+ // unordered list
1165
+ /^\s*\d+\.\s/m,
1166
+ // ordered list
1167
+ /^\s*>\s/m,
1168
+ // blockquote
1169
+ /\|.+\|/m,
1170
+ // table
1171
+ /^```/m,
1172
+ // fenced code block
1173
+ /\*\*.+\*\*/,
1174
+ // bold
1175
+ /\*.+\*/,
1176
+ // italic
1177
+ /~~.+~~/,
1178
+ // strikethrough
1179
+ /`[^`]+`/,
1180
+ // inline code
1181
+ /^\s*---\s*$/m,
1182
+ // horizontal rule
1183
+ /^\s*\*\*\*\s*$/m,
1184
+ // horizontal rule alt
1185
+ /\[.+\]\(.+\)/,
1186
+ // links
1187
+ /!\[.*\]\(.+\)/
1188
+ // images
1189
+ ];
1190
+ const hasMarkdown = patterns.some((p) => p.test(text));
1191
+ const isHtml = /^<[a-z][\s\S]*>/i.test(text.trim());
1192
+ return hasMarkdown && !isHtml;
1193
+ }
1194
+ function buildParser(schema) {
1195
+ const md = defaultMarkdownParser.tokenizer;
1196
+ const tokens = {};
1197
+ if (schema.nodes.paragraph) tokens.paragraph = { block: "paragraph" };
1198
+ if (schema.nodes.heading) {
1199
+ tokens.heading = {
1200
+ block: "heading",
1201
+ getAttrs: (tok) => ({ level: Number(tok.tag.slice(1)) })
1202
+ };
1203
+ }
1204
+ if (schema.nodes.blockquote) tokens.blockquote = { block: "blockquote" };
1205
+ if (schema.nodes.bulletList) tokens.bullet_list = { block: "bulletList" };
1206
+ if (schema.nodes.orderedList) {
1207
+ tokens.ordered_list = {
1208
+ block: "orderedList",
1209
+ getAttrs: (tok) => ({ start: Number(tok.attrGet("start") || 1) })
1210
+ };
1211
+ }
1212
+ if (schema.nodes.listItem) tokens.list_item = { block: "listItem" };
1213
+ if (schema.nodes.codeBlock) {
1214
+ tokens.code_block = { block: "codeBlock", noCloseToken: true };
1215
+ tokens.fence = {
1216
+ block: "codeBlock",
1217
+ getAttrs: (tok) => ({ language: tok.info || "" }),
1218
+ noCloseToken: true
1219
+ };
1220
+ }
1221
+ if (schema.nodes.horizontalRule) {
1222
+ tokens.hr = { node: "horizontalRule" };
1223
+ }
1224
+ if (schema.nodes.hardBreak) {
1225
+ tokens.hardbreak = { node: "hardBreak" };
1226
+ }
1227
+ if (schema.nodes.image) {
1228
+ tokens.image = {
1229
+ node: "image",
1230
+ getAttrs: (tok) => ({
1231
+ src: tok.attrGet("src"),
1232
+ title: tok.attrGet("title") || null,
1233
+ alt: tok.children?.[0]?.content || null
1234
+ })
1235
+ };
1236
+ }
1237
+ if (schema.nodes.table) {
1238
+ tokens.table = { block: "table" };
1239
+ tokens.thead = { ignore: true };
1240
+ tokens.tbody = { ignore: true };
1241
+ tokens.tr = { block: "tableRow" };
1242
+ tokens.th = { block: "tableHeader" };
1243
+ tokens.td = { block: "tableCell" };
1244
+ }
1245
+ if (schema.marks.bold || schema.marks.strong) {
1246
+ tokens.strong = { mark: schema.marks.bold ? "bold" : "strong" };
1247
+ }
1248
+ if (schema.marks.italic || schema.marks.em) {
1249
+ tokens.em = { mark: schema.marks.italic ? "italic" : "em" };
1250
+ }
1251
+ if (schema.marks.code) {
1252
+ tokens.code_inline = { mark: "code", noCloseToken: true };
1253
+ }
1254
+ if (schema.marks.link) {
1255
+ tokens.link = {
1256
+ mark: "link",
1257
+ getAttrs: (tok) => ({
1258
+ href: tok.attrGet("href"),
1259
+ title: tok.attrGet("title") || null
1260
+ })
1261
+ };
1262
+ }
1263
+ if (schema.marks.strike || schema.marks.strikethrough) {
1264
+ tokens.s = { mark: schema.marks.strike ? "strike" : "strikethrough" };
1265
+ }
1266
+ try {
1267
+ return new MarkdownParser(schema, md, tokens);
1268
+ } catch {
1269
+ return null;
1270
+ }
1271
+ }
1272
+ var MarkdownPaste = Extension.create({
1273
+ name: "markdownPaste",
1274
+ addProseMirrorPlugins() {
1275
+ const schema = this.editor.schema;
1276
+ let parser = null;
1277
+ return [
1278
+ new Plugin3({
1279
+ key: markdownPastePluginKey,
1280
+ props: {
1281
+ handlePaste(view, event) {
1282
+ const clipboardData = event.clipboardData;
1283
+ if (!clipboardData) return false;
1284
+ const html = clipboardData.getData("text/html");
1285
+ if (html && html.trim().length > 0) return false;
1286
+ const text = clipboardData.getData("text/plain");
1287
+ if (!text || !looksLikeMarkdown(text)) return false;
1288
+ if (!parser) {
1289
+ parser = buildParser(schema);
1290
+ }
1291
+ if (!parser) return false;
1292
+ try {
1293
+ const doc = parser.parse(text);
1294
+ if (!doc || doc.content.size === 0) return false;
1295
+ const { tr } = view.state;
1296
+ const slice = doc.slice(0, doc.content.size);
1297
+ tr.replaceSelection(slice);
1298
+ view.dispatch(tr);
1299
+ return true;
1300
+ } catch {
1301
+ return false;
1302
+ }
1303
+ }
1304
+ }
1305
+ })
1306
+ ];
1307
+ }
1308
+ });
1309
+
1310
+ // src/headless/extensions/slash-command.tsx
1311
+ import Suggestion from "@tiptap/suggestion";
1312
+ import { Extension as Extension2 } from "@tiptap/core";
1313
+ var Command = Extension2.create({
1314
+ name: "slash-command",
1315
+ addOptions() {
1316
+ return {
1317
+ suggestion: {
1318
+ char: "/",
1319
+ command: (ctx) => {
1320
+ ctx.props.command({ editor: ctx.editor, range: ctx.range });
1321
+ }
1322
+ }
1323
+ };
1324
+ },
1325
+ addProseMirrorPlugins() {
1326
+ const base = this.options.suggestion ?? {};
1327
+ return [
1328
+ Suggestion({
1329
+ editor: this.editor,
1330
+ char: base.char ?? "/",
1331
+ startOfLine: base.startOfLine ?? true,
1332
+ items: base.items ?? (() => ["/"]),
1333
+ command: (ctx) => {
1334
+ if (typeof ctx?.props?.command === "function") {
1335
+ ctx.props.command({ editor: ctx.editor, range: ctx.range });
1336
+ }
1337
+ },
1338
+ ...base,
1339
+ render: () => {
1340
+ return {
1341
+ onStart: (props) => {
1342
+ const { selection } = props.editor.state;
1343
+ const parentNode = selection.$from.node(selection.$from.depth);
1344
+ const blockType = parentNode.type.name;
1345
+ if (blockType === "codeBlock") return false;
1346
+ const { $from } = selection;
1347
+ const marks = $from.marks();
1348
+ if (marks.some((mark) => mark.type.name === "code" || mark.type.name === "link")) {
1349
+ return false;
1350
+ }
1351
+ novelStore.set(queryAtom, props.query ?? "");
1352
+ novelStore.set(rangeAtom, props.range ?? null);
1353
+ novelStore.set(slashMenuOpenAtom, true);
1354
+ const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
1355
+ novelStore.set(slashMenuRectAtom, rect);
1356
+ },
1357
+ onUpdate: (props) => {
1358
+ novelStore.set(queryAtom, props.query ?? "");
1359
+ novelStore.set(rangeAtom, props.range ?? null);
1360
+ const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
1361
+ novelStore.set(slashMenuRectAtom, rect);
1362
+ },
1363
+ onKeyDown: ({ event }) => {
1364
+ if (event.key === "Escape") {
1365
+ novelStore.set(slashMenuOpenAtom, false);
1366
+ return true;
1367
+ }
1368
+ if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
1369
+ const slashCommand = document.querySelector("#slash-command");
1370
+ if (slashCommand) {
1371
+ slashCommand.dispatchEvent(
1372
+ new KeyboardEvent("keydown", {
1373
+ key: event.key,
1374
+ cancelable: true,
1375
+ bubbles: true
1376
+ })
1377
+ );
1378
+ return true;
1379
+ }
1380
+ }
1381
+ return false;
1382
+ },
1383
+ onExit: () => {
1384
+ novelStore.set(slashMenuOpenAtom, false);
1385
+ novelStore.set(queryAtom, "");
1386
+ novelStore.set(rangeAtom, null);
1387
+ novelStore.set(slashMenuRectAtom, null);
1388
+ }
1389
+ };
1390
+ }
1391
+ })
1392
+ ];
1393
+ }
1394
+ });
1395
+ var renderItems = () => ({
1396
+ onStart: () => {
1397
+ },
1398
+ onUpdate: () => {
1399
+ },
1400
+ onKeyDown: () => false,
1401
+ onExit: () => {
1402
+ }
1403
+ });
1404
+ var createSuggestionItems = (items) => items;
1405
+ var handleCommandNavigation = (event) => {
1406
+ if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
1407
+ const slashCommand = document.querySelector("#slash-command");
1408
+ if (slashCommand) return true;
1409
+ }
1410
+ };
1411
+
1412
+ export {
1413
+ novelStore,
1414
+ store_exports,
1415
+ EditorRoot,
1416
+ EditorContent,
1417
+ EditorBubble,
1418
+ EditorBubbleItem,
1419
+ queryAtom,
1420
+ EditorCommandOut,
1421
+ EditorCommand,
1422
+ EditorCommandList,
1423
+ EditorCommandItem,
1424
+ EditorCommandEmpty,
1425
+ CodeBlock,
1426
+ Link,
1427
+ ImageBlock,
1428
+ MentionCommand,
1429
+ createMentionExtension,
1430
+ renderMentionSuggestion,
1431
+ setDragHandleCallbacks,
1432
+ DragHandle,
1433
+ TableKit,
1434
+ MarkdownPaste,
1435
+ StarterKit,
1436
+ Placeholder,
1437
+ Command,
1438
+ renderItems,
1439
+ createSuggestionItems,
1440
+ handleCommandNavigation,
1441
+ useCurrentEditor4 as useCurrentEditor
1442
+ };
1443
+ //# sourceMappingURL=chunk-Y6MF4L42.js.map