neuphlo-editor 2.5.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/dist/Markdown-BAW0cb-U.d.cts +153 -0
  2. package/dist/Markdown-BAW0cb-U.d.ts +153 -0
  3. package/dist/chunk-ABL4D65Z.js +1454 -0
  4. package/dist/chunk-ABL4D65Z.js.map +1 -0
  5. package/dist/chunk-Z2DR2FK3.js +1582 -0
  6. package/dist/chunk-Z2DR2FK3.js.map +1 -0
  7. package/dist/headless/components/editor-bubble-item.d.ts +9 -0
  8. package/dist/headless/components/editor-bubble-item.js +29 -0
  9. package/dist/headless/components/editor-bubble.d.ts +9 -0
  10. package/dist/headless/components/editor-bubble.js +9 -0
  11. package/dist/headless/components/editor-command-item.d.ts +3 -0
  12. package/dist/headless/components/editor-command-item.js +27 -0
  13. package/dist/headless/components/editor-command.d.ts +3 -0
  14. package/dist/headless/components/editor-command.js +136 -0
  15. package/dist/headless/components/editor.d.ts +24 -0
  16. package/dist/headless/components/editor.js +13 -0
  17. package/dist/headless/extensions/AISuggestion/AISuggestion.d.ts +82 -0
  18. package/dist/headless/extensions/AISuggestion/AISuggestion.js +244 -0
  19. package/dist/headless/extensions/AISuggestion/index.d.ts +2 -0
  20. package/dist/headless/extensions/AISuggestion/index.js +1 -0
  21. package/dist/headless/extensions/CodeBlock/CodeBlock.d.ts +1 -0
  22. package/dist/headless/extensions/CodeBlock/CodeBlock.js +6 -0
  23. package/dist/headless/extensions/CodeBlock/index.d.ts +1 -0
  24. package/dist/headless/extensions/CodeBlock/index.js +1 -0
  25. package/dist/headless/extensions/DragHandle/DragHandle.d.ts +9 -0
  26. package/dist/headless/extensions/DragHandle/DragHandle.js +47 -0
  27. package/dist/headless/extensions/DragHandle/index.d.ts +2 -0
  28. package/dist/headless/extensions/DragHandle/index.js +1 -0
  29. package/dist/headless/extensions/Image/Image.d.ts +6 -0
  30. package/dist/headless/extensions/Image/Image.js +197 -0
  31. package/dist/headless/extensions/ImageBlock/ImageBlock.d.ts +23 -0
  32. package/dist/headless/extensions/ImageBlock/ImageBlock.js +242 -0
  33. package/dist/headless/extensions/ImageBlock/index.d.ts +2 -0
  34. package/dist/headless/extensions/ImageBlock/index.js +1 -0
  35. package/dist/headless/extensions/Link/Link.d.ts +2 -0
  36. package/dist/headless/extensions/Link/Link.js +44 -0
  37. package/dist/headless/extensions/Link/index.d.ts +1 -0
  38. package/dist/headless/extensions/Link/index.js +1 -0
  39. package/dist/headless/extensions/Markdown.d.ts +9 -0
  40. package/dist/headless/extensions/Markdown.js +138 -0
  41. package/dist/headless/extensions/MarkdownPaste.d.ts +3 -0
  42. package/dist/headless/extensions/MarkdownPaste.js +168 -0
  43. package/dist/headless/extensions/Mention/index.d.ts +4 -0
  44. package/dist/headless/extensions/Mention/index.js +3 -0
  45. package/dist/headless/extensions/Mention/mention-command.d.ts +8 -0
  46. package/dist/headless/extensions/Mention/mention-command.js +165 -0
  47. package/dist/headless/extensions/Mention/mention-list.d.ts +8 -0
  48. package/dist/headless/extensions/Mention/mention-list.js +150 -0
  49. package/dist/headless/extensions/Mention/mention-node-view.d.ts +2 -0
  50. package/dist/headless/extensions/Mention/mention-node-view.js +92 -0
  51. package/dist/headless/extensions/Mention/mention.d.ts +46 -0
  52. package/dist/headless/extensions/Mention/mention.js +218 -0
  53. package/dist/headless/extensions/Table/index.d.ts +2 -0
  54. package/dist/headless/extensions/Table/index.js +2 -0
  55. package/dist/headless/extensions/VideoBlock/VideoBlock.d.ts +17 -0
  56. package/dist/headless/extensions/VideoBlock/VideoBlock.js +73 -0
  57. package/dist/headless/extensions/VideoBlock/index.d.ts +2 -0
  58. package/dist/headless/extensions/VideoBlock/index.js +1 -0
  59. package/dist/headless/extensions/extension-kit.d.ts +29 -0
  60. package/dist/headless/extensions/extension-kit.js +132 -0
  61. package/dist/headless/extensions/index.d.ts +16 -0
  62. package/dist/headless/extensions/index.js +12 -0
  63. package/dist/headless/extensions/slash-command.d.ts +24 -0
  64. package/dist/headless/extensions/slash-command.js +99 -0
  65. package/dist/headless/index.cjs +143 -2
  66. package/dist/headless/index.cjs.map +1 -1
  67. package/dist/headless/index.d.cts +3 -2
  68. package/dist/headless/index.d.ts +3 -2
  69. package/dist/headless/index.js +5 -1
  70. package/dist/headless/utils/atoms.d.ts +13 -0
  71. package/dist/headless/utils/atoms.js +5 -0
  72. package/dist/headless/utils/store.d.ts +2 -0
  73. package/dist/headless/utils/store.js +4 -0
  74. package/dist/highlight.css +0 -6
  75. package/dist/react/Editor.d.ts +43 -0
  76. package/dist/react/Editor.js +119 -0
  77. package/dist/react/TableOfContents.d.ts +7 -0
  78. package/dist/react/TableOfContents.js +96 -0
  79. package/dist/react/index.cjs +156 -9
  80. package/dist/react/index.cjs.map +1 -1
  81. package/dist/react/index.d.cts +5 -2
  82. package/dist/react/index.d.ts +5 -2
  83. package/dist/react/index.js +14 -4
  84. package/dist/react/index.js.map +1 -1
  85. package/dist/react/menus/DragHandle/BlockActionMenu.d.ts +7 -0
  86. package/dist/react/menus/DragHandle/BlockActionMenu.js +111 -0
  87. package/dist/react/menus/DragHandle/DragHandleMenu.d.ts +7 -0
  88. package/dist/react/menus/DragHandle/DragHandleMenu.js +97 -0
  89. package/dist/react/menus/ImageBlock/ImageBlockLoading.d.ts +2 -0
  90. package/dist/react/menus/ImageBlock/ImageBlockLoading.js +6 -0
  91. package/dist/react/menus/ImageBlock/ImageBlockMenu.d.ts +8 -0
  92. package/dist/react/menus/ImageBlock/ImageBlockMenu.js +75 -0
  93. package/dist/react/menus/ImageBlock/ImageBlockView.d.ts +10 -0
  94. package/dist/react/menus/ImageBlock/ImageBlockView.js +60 -0
  95. package/dist/react/menus/ImageBlock/ImageBlockWidth.d.ts +6 -0
  96. package/dist/react/menus/ImageBlock/ImageBlockWidth.js +29 -0
  97. package/dist/react/menus/ImageBlock/ImageResizeHandle.d.ts +8 -0
  98. package/dist/react/menus/ImageBlock/ImageResizeHandle.js +37 -0
  99. package/dist/react/menus/ImageBlock/ImageUploader.d.ts +7 -0
  100. package/dist/react/menus/ImageBlock/ImageUploader.js +73 -0
  101. package/dist/react/menus/ImageBlock/index.d.ts +4 -0
  102. package/dist/react/menus/ImageBlock/index.js +4 -0
  103. package/dist/react/menus/ImageMenu.d.ts +10 -0
  104. package/dist/react/menus/ImageMenu.js +77 -0
  105. package/dist/react/menus/ImagePopover.d.ts +8 -0
  106. package/dist/react/menus/ImagePopover.js +146 -0
  107. package/dist/react/menus/LinkMenu.d.ts +2 -0
  108. package/dist/react/menus/LinkMenu.js +69 -0
  109. package/dist/react/menus/MenuList.d.ts +10 -0
  110. package/dist/react/menus/MenuList.js +94 -0
  111. package/dist/react/menus/SlashMenu.d.ts +5 -0
  112. package/dist/react/menus/SlashMenu.js +156 -0
  113. package/dist/react/menus/TableMenu.d.ts +5 -0
  114. package/dist/react/menus/TableMenu.js +462 -0
  115. package/dist/react/menus/TaskItem/TaskItemView.d.ts +12 -0
  116. package/dist/react/menus/TaskItem/TaskItemView.js +11 -0
  117. package/dist/react/menus/TextMenu.d.ts +10 -0
  118. package/dist/react/menus/TextMenu.js +139 -0
  119. package/dist/react/menus/VideoBlock/VideoBlockMenu.d.ts +7 -0
  120. package/dist/react/menus/VideoBlock/VideoBlockMenu.js +72 -0
  121. package/dist/react/menus/VideoBlock/VideoBlockView.d.ts +10 -0
  122. package/dist/react/menus/VideoBlock/VideoBlockView.js +87 -0
  123. package/dist/react/menus/VideoBlock/index.d.ts +2 -0
  124. package/dist/react/menus/VideoBlock/index.js +2 -0
  125. package/dist/react/menus/index.d.ts +15 -0
  126. package/dist/react/menus/index.js +9 -0
  127. package/package.json +13 -9
  128. package/LICENSE +0 -21
@@ -0,0 +1,218 @@
1
+ import { ReactRenderer, ReactNodeViewRenderer } from "@tiptap/react";
2
+ import Mention from "@tiptap/extension-mention";
3
+ import { MentionCommand } from "./mention-command";
4
+ import { MentionNodeView } from "./mention-node-view";
5
+ /**
6
+ * Create the mention extension with custom suggestion rendering
7
+ */
8
+ export const createMentionExtension = (options) => {
9
+ const extensionName = options?.name ?? "mention";
10
+ return Mention.extend({
11
+ name: extensionName,
12
+ addAttributes() {
13
+ return {
14
+ id: {
15
+ default: null,
16
+ parseHTML: (element) => element.getAttribute("data-id"),
17
+ renderHTML: (attributes) => {
18
+ if (!attributes.id) {
19
+ return {};
20
+ }
21
+ return {
22
+ "data-id": attributes.id,
23
+ };
24
+ },
25
+ },
26
+ label: {
27
+ default: null,
28
+ parseHTML: (element) => element.getAttribute("data-label"),
29
+ renderHTML: (attributes) => {
30
+ if (!attributes.label) {
31
+ return {};
32
+ }
33
+ return {
34
+ "data-label": attributes.label,
35
+ };
36
+ },
37
+ },
38
+ avatar: {
39
+ default: null,
40
+ parseHTML: (element) => element.getAttribute("data-avatar"),
41
+ renderHTML: (attributes) => {
42
+ if (!attributes.avatar) {
43
+ return {};
44
+ }
45
+ return {
46
+ "data-avatar": attributes.avatar,
47
+ };
48
+ },
49
+ },
50
+ type: {
51
+ default: null,
52
+ parseHTML: (element) => element.getAttribute("data-ref-type"),
53
+ renderHTML: (attributes) => {
54
+ if (!attributes.type) {
55
+ return {};
56
+ }
57
+ return {
58
+ "data-ref-type": attributes.type,
59
+ };
60
+ },
61
+ },
62
+ nodeId: {
63
+ default: null,
64
+ parseHTML: (element) => element.getAttribute("data-node-id"),
65
+ renderHTML: (attributes) => {
66
+ if (!attributes.nodeId) {
67
+ return {};
68
+ }
69
+ return {
70
+ "data-node-id": attributes.nodeId,
71
+ };
72
+ },
73
+ },
74
+ slug: {
75
+ default: null,
76
+ parseHTML: (element) => element.getAttribute("data-slug"),
77
+ renderHTML: (attributes) => {
78
+ if (!attributes.slug) {
79
+ return {};
80
+ }
81
+ return {
82
+ "data-slug": attributes.slug,
83
+ };
84
+ },
85
+ },
86
+ };
87
+ },
88
+ addNodeView() {
89
+ return ReactNodeViewRenderer(MentionNodeView);
90
+ },
91
+ }).configure({
92
+ HTMLAttributes: {
93
+ class: "mention",
94
+ },
95
+ suggestion: {
96
+ char: options?.char ?? "@",
97
+ items: async ({ query }) => {
98
+ if (!options?.items)
99
+ return [];
100
+ const items = await options.items(query);
101
+ return items;
102
+ },
103
+ command: ({ editor, range, props: item }) => {
104
+ editor
105
+ .chain()
106
+ .focus()
107
+ .insertContentAt(range, [
108
+ {
109
+ type: extensionName,
110
+ attrs: {
111
+ id: item.id,
112
+ label: item.label,
113
+ avatar: item.avatar,
114
+ type: item.type,
115
+ nodeId: item.nodeId,
116
+ slug: item.slug,
117
+ },
118
+ },
119
+ { type: "text", text: " " },
120
+ ])
121
+ .run();
122
+ },
123
+ render: renderMentionSuggestion,
124
+ },
125
+ renderLabel: options?.renderLabel ?? ((props) => {
126
+ return `@${props.node.attrs.label ?? props.node.attrs.id}`;
127
+ }),
128
+ });
129
+ };
130
+ /**
131
+ * Render function for mention suggestions (autocomplete dropdown)
132
+ */
133
+ export const renderMentionSuggestion = () => {
134
+ let component = null;
135
+ let container = null;
136
+ const destroy = () => {
137
+ component?.destroy();
138
+ component = null;
139
+ if (container) {
140
+ container.remove();
141
+ container = null;
142
+ }
143
+ };
144
+ const updatePosition = (clientRect) => {
145
+ if (!container || !clientRect)
146
+ return;
147
+ const gap = 8;
148
+ const maxDropdownHeight = 300; // Match the maxHeight from MentionCommand
149
+ const spaceBelow = window.innerHeight - clientRect.bottom;
150
+ const spaceAbove = clientRect.top;
151
+ // Position above if not enough space below, otherwise position below
152
+ const shouldPositionAbove = spaceBelow < maxDropdownHeight && spaceAbove > spaceBelow;
153
+ const left = Math.round(clientRect.left);
154
+ if (shouldPositionAbove) {
155
+ const bottom = Math.round(window.innerHeight - clientRect.top + gap);
156
+ container.style.bottom = `${bottom}px`;
157
+ container.style.top = 'auto';
158
+ }
159
+ else {
160
+ const top = Math.round(clientRect.bottom + gap);
161
+ container.style.top = `${top}px`;
162
+ container.style.bottom = 'auto';
163
+ }
164
+ container.style.left = `${left}px`;
165
+ };
166
+ return {
167
+ onStart: (props) => {
168
+ component = new ReactRenderer(MentionCommand, {
169
+ props: {
170
+ items: props.items ?? [],
171
+ command: props.command ?? (() => { }),
172
+ query: props.query ?? "",
173
+ },
174
+ editor: props.editor,
175
+ });
176
+ container = document.createElement("div");
177
+ container.style.position = "fixed";
178
+ container.style.zIndex = "9999";
179
+ document.body.appendChild(container);
180
+ container.appendChild(component.element);
181
+ const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
182
+ if (rect)
183
+ updatePosition(rect);
184
+ },
185
+ onUpdate: (props) => {
186
+ component?.updateProps({
187
+ items: props.items ?? [],
188
+ command: props.command ?? (() => { }),
189
+ query: props.query ?? "",
190
+ });
191
+ const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
192
+ if (rect)
193
+ updatePosition(rect);
194
+ },
195
+ onKeyDown: ({ event }) => {
196
+ if (!component)
197
+ return false;
198
+ if (event.key === "Escape") {
199
+ event.preventDefault();
200
+ event.stopPropagation();
201
+ destroy();
202
+ return true;
203
+ }
204
+ // Handle arrow keys and enter
205
+ if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
206
+ event.preventDefault();
207
+ event.stopPropagation();
208
+ // Let the MentionList component handle keyboard navigation
209
+ return component.ref?.onKeyDown?.({ event }) ?? false;
210
+ }
211
+ return false;
212
+ },
213
+ onExit: () => {
214
+ destroy();
215
+ },
216
+ };
217
+ };
218
+ export default createMentionExtension;
@@ -0,0 +1,2 @@
1
+ export { TableKit } from "@tiptap/extension-table";
2
+ export { Table, TableCell, TableHeader, TableRow } from "@tiptap/extension-table";
@@ -0,0 +1,2 @@
1
+ export { TableKit } from "@tiptap/extension-table";
2
+ export { Table, TableCell, TableHeader, TableRow } from "@tiptap/extension-table";
@@ -0,0 +1,17 @@
1
+ import { Node } from "@tiptap/core";
2
+ export interface VideoBlockOptions {
3
+ nodeView?: any;
4
+ }
5
+ declare module "@tiptap/core" {
6
+ interface Commands<ReturnType> {
7
+ videoBlock: {
8
+ setVideoBlock: (attributes: {
9
+ src: string;
10
+ }) => ReturnType;
11
+ setVideoBlockAlign: (align: "left" | "center" | "right") => ReturnType;
12
+ setVideoBlockWidth: (width: number) => ReturnType;
13
+ };
14
+ }
15
+ }
16
+ export declare const VideoBlock: Node<VideoBlockOptions, any>;
17
+ export default VideoBlock;
@@ -0,0 +1,73 @@
1
+ import { mergeAttributes, Node } from "@tiptap/core";
2
+ import { ReactNodeViewRenderer } from "@tiptap/react";
3
+ export const VideoBlock = Node.create({
4
+ name: "videoBlock",
5
+ group: "block",
6
+ defining: true,
7
+ isolating: true,
8
+ atom: true,
9
+ addOptions() {
10
+ return {
11
+ nodeView: undefined,
12
+ };
13
+ },
14
+ addAttributes() {
15
+ return {
16
+ src: {
17
+ default: "",
18
+ parseHTML: (element) => element.getAttribute("data-src"),
19
+ renderHTML: (attributes) => ({
20
+ "data-src": attributes.src,
21
+ }),
22
+ },
23
+ width: {
24
+ default: "100%",
25
+ parseHTML: (element) => element.getAttribute("data-width"),
26
+ renderHTML: (attributes) => ({
27
+ "data-width": attributes.width,
28
+ }),
29
+ },
30
+ align: {
31
+ default: "center",
32
+ parseHTML: (element) => element.getAttribute("data-align"),
33
+ renderHTML: (attributes) => ({
34
+ "data-align": attributes.align,
35
+ }),
36
+ },
37
+ };
38
+ },
39
+ parseHTML() {
40
+ return [
41
+ {
42
+ tag: 'div[data-type="video-block"]',
43
+ },
44
+ ];
45
+ },
46
+ renderHTML({ HTMLAttributes }) {
47
+ return [
48
+ "div",
49
+ mergeAttributes(HTMLAttributes, { "data-type": "video-block" }),
50
+ ];
51
+ },
52
+ addCommands() {
53
+ return {
54
+ setVideoBlock: (attrs) => ({ commands }) => {
55
+ return commands.insertContent({
56
+ type: "videoBlock",
57
+ attrs: { src: attrs.src },
58
+ });
59
+ },
60
+ setVideoBlockAlign: (align) => ({ commands }) => commands.updateAttributes("videoBlock", { align }),
61
+ setVideoBlockWidth: (width) => ({ commands }) => commands.updateAttributes("videoBlock", {
62
+ width: `${Math.max(0, Math.min(100, width))}%`,
63
+ }),
64
+ };
65
+ },
66
+ addNodeView() {
67
+ if (this.options.nodeView) {
68
+ return ReactNodeViewRenderer(this.options.nodeView);
69
+ }
70
+ return undefined;
71
+ },
72
+ });
73
+ export default VideoBlock;
@@ -0,0 +1,2 @@
1
+ export { VideoBlock } from "./VideoBlock";
2
+ export type { VideoBlockOptions } from "./VideoBlock";
@@ -0,0 +1 @@
1
+ export { VideoBlock } from "./VideoBlock";
@@ -0,0 +1,29 @@
1
+ import type { MentionOptions } from "./Mention";
2
+ import type { DragHandleCallbacks } from "./DragHandle";
3
+ export interface ExtensionKitOptions {
4
+ uploadImage?: (file: File) => Promise<string>;
5
+ browseAssets?: (onSelect: (url: string) => void) => void;
6
+ collaboration?: {
7
+ doc: any;
8
+ field: string;
9
+ awareness?: any;
10
+ provider?: any;
11
+ user?: {
12
+ name: string;
13
+ color: string;
14
+ [key: string]: any;
15
+ };
16
+ };
17
+ imageBlockView?: any;
18
+ videoBlockView?: any;
19
+ taskItemView?: any;
20
+ mention?: MentionOptions;
21
+ reference?: MentionOptions;
22
+ slashCommand?: boolean;
23
+ dragHandle?: boolean;
24
+ dragHandleCallbacks?: DragHandleCallbacks;
25
+ table?: boolean;
26
+ placeholder?: string;
27
+ }
28
+ export declare const ExtensionKit: (options?: ExtensionKitOptions) => (import("@tiptap/core").Node<import("@tiptap/extension-code-block-lowlight").CodeBlockLowlightOptions, any> | import("@tiptap/core").Mark<import("@tiptap/extension-link").LinkOptions, any> | import("@tiptap/core").Node<import("./ImageBlock").ImageBlockOptions, any> | import("@tiptap/core").Extension<any, any> | import("@tiptap/core").Extension<any, import("./Markdown").MarkdownStorage> | import("@tiptap/core").Node<import("./VideoBlock").VideoBlockOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-list").TaskItemOptions, any> | import("@tiptap/core").Extension<import("@tiptap/starter-kit").StarterKitOptions, any> | import("@tiptap/core").Mark<import("@tiptap/extension-underline").UnderlineOptions, any> | import("@tiptap/core").Node<import("@tiptap/extension-list").TaskListOptions, any> | import("@tiptap/core").Extension<import("@tiptap/extensions").PlaceholderOptions, any>)[];
29
+ export default ExtensionKit;
@@ -0,0 +1,132 @@
1
+ import Collaboration from "@tiptap/extension-collaboration";
2
+ import CollaborationCaret from "@tiptap/extension-collaboration-caret";
3
+ import { StarterKit, Placeholder, CodeBlock, Link } from ".";
4
+ import Underline from "@tiptap/extension-underline";
5
+ import { TaskList, TaskItem as BaseTaskItem } from "@tiptap/extension-list";
6
+ import { ReactNodeViewRenderer } from "@tiptap/react";
7
+ const TaskItem = BaseTaskItem.extend({
8
+ addOptions() {
9
+ return {
10
+ ...this.parent?.(),
11
+ nodeView: null,
12
+ };
13
+ },
14
+ addNodeView() {
15
+ const custom = this.options.nodeView;
16
+ if (custom) {
17
+ return ReactNodeViewRenderer(custom);
18
+ }
19
+ return this.parent?.();
20
+ },
21
+ });
22
+ import { Command as SlashCommand, } from "./slash-command";
23
+ import { ImageBlock } from "./ImageBlock/ImageBlock";
24
+ import { VideoBlock } from "./VideoBlock/VideoBlock";
25
+ import { createMentionExtension } from "./Mention";
26
+ import { DragHandle, setDragHandleCallbacks } from "./DragHandle";
27
+ import { TableKit } from "./Table";
28
+ import { MarkdownPaste } from "./MarkdownPaste";
29
+ import { Markdown } from "./Markdown";
30
+ export const ExtensionKit = (options) => {
31
+ const enableSlashCommand = options?.slashCommand !== false; // Default to true
32
+ const extensions = [
33
+ StarterKit.configure({ codeBlock: false, link: false }),
34
+ CodeBlock,
35
+ Link,
36
+ Underline,
37
+ TaskList,
38
+ TaskItem.configure({ nested: true, nodeView: options?.taskItemView }),
39
+ ImageBlock.configure({
40
+ uploadImage: options?.uploadImage,
41
+ browseAssets: options?.browseAssets,
42
+ nodeView: options?.imageBlockView,
43
+ }),
44
+ VideoBlock.configure({
45
+ nodeView: options?.videoBlockView,
46
+ }),
47
+ Placeholder.configure({
48
+ placeholder: ({ node }) => {
49
+ if (node.type.name === "heading") {
50
+ return `Heading ${node.attrs.level}`;
51
+ }
52
+ // Use custom placeholder if provided, otherwise show slash command hint if enabled
53
+ if (options?.placeholder) {
54
+ return options.placeholder;
55
+ }
56
+ return enableSlashCommand ? "Press '/' for commands" : "";
57
+ },
58
+ includeChildren: true,
59
+ }),
60
+ MarkdownPaste,
61
+ Markdown,
62
+ ];
63
+ // Add Table support if enabled
64
+ if (options?.table !== false) {
65
+ extensions.push(TableKit.configure({
66
+ resizable: true,
67
+ lastColumnResizable: true,
68
+ allowTableNodeSelection: true,
69
+ }));
70
+ }
71
+ // Add DragHandle if enabled
72
+ if (options?.dragHandle !== false) {
73
+ extensions.push(DragHandle);
74
+ if (options?.dragHandleCallbacks) {
75
+ setDragHandleCallbacks(options.dragHandleCallbacks);
76
+ }
77
+ }
78
+ // Add SlashCommand if enabled
79
+ if (enableSlashCommand) {
80
+ extensions.push(SlashCommand.configure({
81
+ suggestion: {
82
+ char: "/",
83
+ allowSpaces: true,
84
+ allowedPrefixes: null,
85
+ },
86
+ }));
87
+ }
88
+ // Add Mention extension if configured
89
+ if (options?.mention) {
90
+ const mentionExt = createMentionExtension(options.mention);
91
+ extensions.push(mentionExt);
92
+ }
93
+ // Add Reference extension if configured (uses same Mention extension with different config)
94
+ if (options?.reference) {
95
+ const referenceExt = createMentionExtension({
96
+ ...options.reference,
97
+ name: "reference", // Use unique name to avoid conflicts
98
+ });
99
+ extensions.push(referenceExt);
100
+ }
101
+ if (options?.collaboration?.doc) {
102
+ extensions.push(Collaboration.configure({
103
+ document: options.collaboration.doc,
104
+ field: options.collaboration.field,
105
+ }));
106
+ // Add collaboration carets if provider and user are available
107
+ if (options.collaboration.provider && options.collaboration.user) {
108
+ extensions.push(CollaborationCaret.configure({
109
+ provider: options.collaboration.provider,
110
+ user: options.collaboration.user,
111
+ render: (user) => {
112
+ const cursor = document.createElement("span");
113
+ cursor.classList.add("nph-collab-caret");
114
+ cursor.setAttribute("style", `border-color: ${user.color || "#3b82f6"}`);
115
+ const label = document.createElement("div");
116
+ label.classList.add("nph-collab-caret__label");
117
+ label.setAttribute("style", `background-color: ${user.color || "#3b82f6"}`);
118
+ label.insertBefore(document.createTextNode(user.name || "Anonymous"), null);
119
+ cursor.insertBefore(label, null);
120
+ return cursor;
121
+ },
122
+ selectionRender: (user) => ({
123
+ nodeName: "span",
124
+ class: "nph-collab-selection",
125
+ style: `background-color: ${user.color || "#3b82f6"}20`,
126
+ }),
127
+ }));
128
+ }
129
+ }
130
+ return extensions;
131
+ };
132
+ export default ExtensionKit;
@@ -0,0 +1,16 @@
1
+ export { StarterKit } from "@tiptap/starter-kit";
2
+ export { Placeholder } from "@tiptap/extension-placeholder";
3
+ export { CodeBlock } from "./CodeBlock";
4
+ export { Link } from "./Link";
5
+ export { ImageBlock } from "./ImageBlock";
6
+ export type { ImageBlockOptions } from "./ImageBlock";
7
+ export { AISuggestion, AISuggestionPluginKey } from "./AISuggestion";
8
+ export type { AISuggestionOptions } from "./AISuggestion";
9
+ export { createMentionExtension, renderMentionSuggestion, MentionCommand } from "./Mention";
10
+ export type { MentionItem, MentionOptions } from "./Mention";
11
+ export { DragHandle, setDragHandleCallbacks } from "./DragHandle";
12
+ export type { DragHandleCallbacks } from "./DragHandle";
13
+ export { TableKit, Table, TableCell, TableHeader, TableRow } from "./Table";
14
+ export { MarkdownPaste } from "./MarkdownPaste";
15
+ export { Markdown, markdownToHtml } from "./Markdown";
16
+ export type { MarkdownStorage } from "./Markdown";
@@ -0,0 +1,12 @@
1
+ export { StarterKit } from "@tiptap/starter-kit";
2
+ export { Placeholder } from "@tiptap/extension-placeholder";
3
+ // Custom
4
+ export { CodeBlock } from "./CodeBlock";
5
+ export { Link } from "./Link";
6
+ export { ImageBlock } from "./ImageBlock";
7
+ export { AISuggestion, AISuggestionPluginKey } from "./AISuggestion";
8
+ export { createMentionExtension, renderMentionSuggestion, MentionCommand } from "./Mention";
9
+ export { DragHandle, setDragHandleCallbacks } from "./DragHandle";
10
+ export { TableKit, Table, TableCell, TableHeader, TableRow } from "./Table";
11
+ export { MarkdownPaste } from "./MarkdownPaste";
12
+ export { Markdown, markdownToHtml } from "./Markdown";
@@ -0,0 +1,24 @@
1
+ import { Extension } from "@tiptap/core";
2
+ import type { ReactNode } from "react";
3
+ export declare const Command: Extension<any, any>;
4
+ export declare const renderItems: () => {
5
+ onStart: () => void;
6
+ onUpdate: () => void;
7
+ onKeyDown: () => boolean;
8
+ onExit: () => void;
9
+ };
10
+ export interface SuggestionItem {
11
+ title: string;
12
+ description: string;
13
+ icon: ReactNode;
14
+ searchTerms?: string[];
15
+ command?: (props: {
16
+ editor: any;
17
+ range: {
18
+ from: number;
19
+ to: number;
20
+ };
21
+ }) => void;
22
+ }
23
+ export declare const createSuggestionItems: (items: SuggestionItem[]) => SuggestionItem[];
24
+ export declare const handleCommandNavigation: (event: KeyboardEvent) => true | undefined;
@@ -0,0 +1,99 @@
1
+ import Suggestion from "@tiptap/suggestion";
2
+ import { Extension } from "@tiptap/core";
3
+ import { queryAtom, rangeAtom, slashMenuOpenAtom, slashMenuRectAtom } from "../utils/atoms";
4
+ import { novelStore } from "../utils/store";
5
+ export const Command = Extension.create({
6
+ name: "slash-command",
7
+ addOptions() {
8
+ return {
9
+ suggestion: {
10
+ char: "/",
11
+ command: (ctx) => {
12
+ ctx.props.command({ editor: ctx.editor, range: ctx.range });
13
+ },
14
+ },
15
+ };
16
+ },
17
+ addProseMirrorPlugins() {
18
+ const base = this.options.suggestion ?? {};
19
+ return [
20
+ Suggestion({
21
+ editor: this.editor,
22
+ char: base.char ?? "/",
23
+ startOfLine: base.startOfLine ?? true,
24
+ items: base.items ?? (() => ["/"]),
25
+ command: (ctx) => {
26
+ if (typeof ctx?.props?.command === "function") {
27
+ ctx.props.command({ editor: ctx.editor, range: ctx.range });
28
+ }
29
+ },
30
+ ...base,
31
+ render: () => {
32
+ return {
33
+ onStart: (props) => {
34
+ const { selection } = props.editor.state;
35
+ const parentNode = selection.$from.node(selection.$from.depth);
36
+ const blockType = parentNode.type.name;
37
+ if (blockType === "codeBlock")
38
+ return false;
39
+ const { $from } = selection;
40
+ const marks = $from.marks();
41
+ if (marks.some((mark) => mark.type.name === "code" || mark.type.name === "link")) {
42
+ return false;
43
+ }
44
+ novelStore.set(queryAtom, props.query ?? "");
45
+ novelStore.set(rangeAtom, props.range ?? null);
46
+ novelStore.set(slashMenuOpenAtom, true);
47
+ const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
48
+ novelStore.set(slashMenuRectAtom, rect);
49
+ },
50
+ onUpdate: (props) => {
51
+ novelStore.set(queryAtom, props.query ?? "");
52
+ novelStore.set(rangeAtom, props.range ?? null);
53
+ const rect = typeof props.clientRect === "function" ? props.clientRect() : null;
54
+ novelStore.set(slashMenuRectAtom, rect);
55
+ },
56
+ onKeyDown: ({ event }) => {
57
+ if (event.key === "Escape") {
58
+ novelStore.set(slashMenuOpenAtom, false);
59
+ return true;
60
+ }
61
+ if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
62
+ const slashCommand = document.querySelector("#slash-command");
63
+ if (slashCommand) {
64
+ slashCommand.dispatchEvent(new KeyboardEvent("keydown", {
65
+ key: event.key,
66
+ cancelable: true,
67
+ bubbles: true,
68
+ }));
69
+ return true;
70
+ }
71
+ }
72
+ return false;
73
+ },
74
+ onExit: () => {
75
+ novelStore.set(slashMenuOpenAtom, false);
76
+ novelStore.set(queryAtom, "");
77
+ novelStore.set(rangeAtom, null);
78
+ novelStore.set(slashMenuRectAtom, null);
79
+ },
80
+ };
81
+ },
82
+ }),
83
+ ];
84
+ },
85
+ });
86
+ export const renderItems = () => ({
87
+ onStart: () => { },
88
+ onUpdate: () => { },
89
+ onKeyDown: () => false,
90
+ onExit: () => { },
91
+ });
92
+ export const createSuggestionItems = (items) => items;
93
+ export const handleCommandNavigation = (event) => {
94
+ if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
95
+ const slashCommand = document.querySelector("#slash-command");
96
+ if (slashCommand)
97
+ return true;
98
+ }
99
+ };