neuphlo-editor 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Markdown-BAW0cb-U.d.cts +153 -0
- package/dist/Markdown-BAW0cb-U.d.ts +153 -0
- package/dist/{chunk-4L7FHW5M.js → chunk-ABL4D65Z.js} +113 -47
- package/dist/chunk-ABL4D65Z.js.map +1 -0
- package/dist/{chunk-BVFTQKY7.js → chunk-Z2DR2FK3.js} +245 -50
- package/dist/chunk-Z2DR2FK3.js.map +1 -0
- package/dist/headless/components/editor-bubble-item.d.ts +9 -0
- package/dist/headless/components/editor-bubble-item.js +29 -0
- package/dist/headless/components/editor-bubble.d.ts +9 -0
- package/dist/headless/components/editor-bubble.js +9 -0
- package/dist/headless/components/editor-command-item.d.ts +3 -0
- package/dist/headless/components/editor-command-item.js +27 -0
- package/dist/headless/components/editor-command.d.ts +3 -0
- package/dist/headless/components/editor-command.js +136 -0
- package/dist/headless/components/editor.d.ts +24 -0
- package/dist/headless/components/editor.js +13 -0
- package/dist/headless/extensions/AISuggestion/AISuggestion.d.ts +82 -0
- package/dist/headless/extensions/AISuggestion/AISuggestion.js +244 -0
- package/dist/headless/extensions/AISuggestion/index.d.ts +2 -0
- package/dist/headless/extensions/AISuggestion/index.js +1 -0
- package/dist/headless/extensions/CodeBlock/CodeBlock.d.ts +1 -0
- package/dist/headless/extensions/CodeBlock/CodeBlock.js +6 -0
- package/dist/headless/extensions/CodeBlock/index.d.ts +1 -0
- package/dist/headless/extensions/CodeBlock/index.js +1 -0
- package/dist/headless/extensions/DragHandle/DragHandle.d.ts +9 -0
- package/dist/headless/extensions/DragHandle/DragHandle.js +47 -0
- package/dist/headless/extensions/DragHandle/index.d.ts +2 -0
- package/dist/headless/extensions/DragHandle/index.js +1 -0
- package/dist/headless/extensions/Image/Image.d.ts +6 -0
- package/dist/headless/extensions/Image/Image.js +197 -0
- package/dist/headless/extensions/ImageBlock/ImageBlock.d.ts +23 -0
- package/dist/headless/extensions/ImageBlock/ImageBlock.js +242 -0
- package/dist/headless/extensions/ImageBlock/index.d.ts +2 -0
- package/dist/headless/extensions/ImageBlock/index.js +1 -0
- package/dist/headless/extensions/Link/Link.d.ts +2 -0
- package/dist/headless/extensions/Link/Link.js +44 -0
- package/dist/headless/extensions/Link/index.d.ts +1 -0
- package/dist/headless/extensions/Link/index.js +1 -0
- package/dist/headless/extensions/Markdown.d.ts +9 -0
- package/dist/headless/extensions/Markdown.js +138 -0
- package/dist/headless/extensions/MarkdownPaste.d.ts +3 -0
- package/dist/headless/extensions/MarkdownPaste.js +168 -0
- package/dist/headless/extensions/Mention/index.d.ts +4 -0
- package/dist/headless/extensions/Mention/index.js +3 -0
- package/dist/headless/extensions/Mention/mention-command.d.ts +8 -0
- package/dist/headless/extensions/Mention/mention-command.js +165 -0
- package/dist/headless/extensions/Mention/mention-list.d.ts +8 -0
- package/dist/headless/extensions/Mention/mention-list.js +150 -0
- package/dist/headless/extensions/Mention/mention-node-view.d.ts +2 -0
- package/dist/headless/extensions/Mention/mention-node-view.js +92 -0
- package/dist/headless/extensions/Mention/mention.d.ts +46 -0
- package/dist/headless/extensions/Mention/mention.js +218 -0
- package/dist/headless/extensions/Table/index.d.ts +2 -0
- package/dist/headless/extensions/Table/index.js +2 -0
- package/dist/headless/extensions/VideoBlock/VideoBlock.d.ts +17 -0
- package/dist/headless/extensions/VideoBlock/VideoBlock.js +73 -0
- package/dist/headless/extensions/VideoBlock/index.d.ts +2 -0
- package/dist/headless/extensions/VideoBlock/index.js +1 -0
- package/dist/headless/extensions/extension-kit.d.ts +29 -0
- package/dist/headless/extensions/extension-kit.js +132 -0
- package/dist/headless/extensions/index.d.ts +16 -0
- package/dist/headless/extensions/index.js +12 -0
- package/dist/headless/extensions/slash-command.d.ts +24 -0
- package/dist/headless/extensions/slash-command.js +99 -0
- package/dist/headless/index.cjs +143 -2
- package/dist/headless/index.cjs.map +1 -1
- package/dist/headless/index.d.cts +3 -2
- package/dist/headless/index.d.ts +3 -2
- package/dist/headless/index.js +5 -1
- package/dist/headless/utils/atoms.d.ts +13 -0
- package/dist/headless/utils/atoms.js +5 -0
- package/dist/headless/utils/store.d.ts +2 -0
- package/dist/headless/utils/store.js +4 -0
- package/dist/highlight.css +77 -1
- package/dist/react/Editor.d.ts +43 -0
- package/dist/react/Editor.js +119 -0
- package/dist/react/TableOfContents.d.ts +7 -0
- package/dist/react/TableOfContents.js +96 -0
- package/dist/react/index.cjs +156 -9
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +5 -2
- package/dist/react/index.d.ts +5 -2
- package/dist/react/index.js +14 -4
- package/dist/react/index.js.map +1 -1
- package/dist/react/menus/DragHandle/BlockActionMenu.d.ts +7 -0
- package/dist/react/menus/DragHandle/BlockActionMenu.js +111 -0
- package/dist/react/menus/DragHandle/DragHandleMenu.d.ts +7 -0
- package/dist/react/menus/DragHandle/DragHandleMenu.js +97 -0
- package/dist/react/menus/ImageBlock/ImageBlockLoading.d.ts +2 -0
- package/dist/react/menus/ImageBlock/ImageBlockLoading.js +6 -0
- package/dist/react/menus/ImageBlock/ImageBlockMenu.d.ts +8 -0
- package/dist/react/menus/ImageBlock/ImageBlockMenu.js +75 -0
- package/dist/react/menus/ImageBlock/ImageBlockView.d.ts +10 -0
- package/dist/react/menus/ImageBlock/ImageBlockView.js +60 -0
- package/dist/react/menus/ImageBlock/ImageBlockWidth.d.ts +6 -0
- package/dist/react/menus/ImageBlock/ImageBlockWidth.js +29 -0
- package/dist/react/menus/ImageBlock/ImageResizeHandle.d.ts +8 -0
- package/dist/react/menus/ImageBlock/ImageResizeHandle.js +37 -0
- package/dist/react/menus/ImageBlock/ImageUploader.d.ts +7 -0
- package/dist/react/menus/ImageBlock/ImageUploader.js +73 -0
- package/dist/react/menus/ImageBlock/index.d.ts +4 -0
- package/dist/react/menus/ImageBlock/index.js +4 -0
- package/dist/react/menus/ImageMenu.d.ts +10 -0
- package/dist/react/menus/ImageMenu.js +77 -0
- package/dist/react/menus/ImagePopover.d.ts +8 -0
- package/dist/react/menus/ImagePopover.js +146 -0
- package/dist/react/menus/LinkMenu.d.ts +2 -0
- package/dist/react/menus/LinkMenu.js +69 -0
- package/dist/react/menus/MenuList.d.ts +10 -0
- package/dist/react/menus/MenuList.js +94 -0
- package/dist/react/menus/SlashMenu.d.ts +5 -0
- package/dist/react/menus/SlashMenu.js +156 -0
- package/dist/react/menus/TableMenu.d.ts +5 -0
- package/dist/react/menus/TableMenu.js +462 -0
- package/dist/react/menus/TaskItem/TaskItemView.d.ts +12 -0
- package/dist/react/menus/TaskItem/TaskItemView.js +11 -0
- package/dist/react/menus/TextMenu.d.ts +10 -0
- package/dist/react/menus/TextMenu.js +139 -0
- package/dist/react/menus/VideoBlock/VideoBlockMenu.d.ts +7 -0
- package/dist/react/menus/VideoBlock/VideoBlockMenu.js +72 -0
- package/dist/react/menus/VideoBlock/VideoBlockView.d.ts +10 -0
- package/dist/react/menus/VideoBlock/VideoBlockView.js +87 -0
- package/dist/react/menus/VideoBlock/index.d.ts +2 -0
- package/dist/react/menus/VideoBlock/index.js +2 -0
- package/dist/react/menus/index.d.ts +15 -0
- package/dist/react/menus/index.js +9 -0
- package/package.json +3 -1
- package/dist/chunk-2DWEJI45.js +0 -1296
- package/dist/chunk-2DWEJI45.js.map +0 -1
- package/dist/chunk-3CLWAYRP.js +0 -1387
- package/dist/chunk-3CLWAYRP.js.map +0 -1
- package/dist/chunk-457ETWB6.js +0 -1351
- package/dist/chunk-457ETWB6.js.map +0 -1
- package/dist/chunk-4L7FHW5M.js.map +0 -1
- package/dist/chunk-4MV3UUZN.js +0 -1073
- package/dist/chunk-4MV3UUZN.js.map +0 -1
- package/dist/chunk-62DYB7FY.js +0 -1305
- package/dist/chunk-62DYB7FY.js.map +0 -1
- package/dist/chunk-BVFTQKY7.js.map +0 -1
- package/dist/chunk-DWGPGRTQ.js +0 -1302
- package/dist/chunk-DWGPGRTQ.js.map +0 -1
- package/dist/chunk-EG7NQJRA.js +0 -1324
- package/dist/chunk-EG7NQJRA.js.map +0 -1
- package/dist/chunk-FLLPFFI5.js +0 -1296
- package/dist/chunk-FLLPFFI5.js.map +0 -1
- package/dist/chunk-FVQHB6VC.js +0 -1128
- package/dist/chunk-FVQHB6VC.js.map +0 -1
- package/dist/chunk-G5E32MLB.js +0 -1352
- package/dist/chunk-G5E32MLB.js.map +0 -1
- package/dist/chunk-GXJGZHKR.js +0 -1326
- package/dist/chunk-GXJGZHKR.js.map +0 -1
- package/dist/chunk-KCPPTLGY.js +0 -1299
- package/dist/chunk-KCPPTLGY.js.map +0 -1
- package/dist/chunk-LHG2NX6C.js +0 -1123
- package/dist/chunk-LHG2NX6C.js.map +0 -1
- package/dist/chunk-MSNTVITF.js +0 -1352
- package/dist/chunk-MSNTVITF.js.map +0 -1
- package/dist/chunk-N4RDYJ5Z.js +0 -1351
- package/dist/chunk-N4RDYJ5Z.js.map +0 -1
- package/dist/chunk-OCNM37WJ.js +0 -1289
- package/dist/chunk-OCNM37WJ.js.map +0 -1
- package/dist/chunk-P3YFYEUB.js +0 -1351
- package/dist/chunk-P3YFYEUB.js.map +0 -1
- package/dist/chunk-RW6QBMJB.js +0 -1300
- package/dist/chunk-RW6QBMJB.js.map +0 -1
- package/dist/chunk-SJQQVB3M.js +0 -1353
- package/dist/chunk-SJQQVB3M.js.map +0 -1
- package/dist/chunk-SOXTEP7H.js +0 -6705
- package/dist/chunk-SOXTEP7H.js.map +0 -1
- package/dist/chunk-VPI26I4P.js +0 -1350
- package/dist/chunk-VPI26I4P.js.map +0 -1
|
@@ -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,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 @@
|
|
|
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
|
+
};
|