@firecms/editor 3.0.0-beta.8 → 3.0.0-beta.9
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/LICENSE +3 -2
- package/dist/components/index.d.ts +0 -3
- package/dist/editor.d.ts +12 -2
- package/dist/editor_extensions.d.ts +1 -0
- package/dist/extensions/HighlightDecorationExtension.d.ts +35 -0
- package/dist/extensions/Image.d.ts +6 -3
- package/dist/extensions/InlineAutocomplete.d.ts +7 -0
- package/dist/extensions/TextLoadingDecorationExtension.d.ts +18 -0
- package/dist/extensions/index.d.ts +0 -1
- package/dist/extensions/slashCommand.d.ts +92 -0
- package/dist/index.es.js +1360 -694
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1613 -8
- package/dist/index.umd.js.map +1 -1
- package/dist/selectors/node-selector.d.ts +2 -1
- package/dist/types.d.ts +3 -0
- package/package.json +7 -10
- package/dist/components/editor-command-item.d.ts +0 -19
- package/dist/components/editor-command.d.ts +0 -23
- package/dist/components/editor.d.ts +0 -43
- package/dist/extensions/slash-command.d.ts +0 -29
- package/dist/extensions/updated-image.d.ts +0 -2
package/dist/index.es.js
CHANGED
|
@@ -1,340 +1,253 @@
|
|
|
1
|
-
import { jsx
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { Color
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import { useCurrentEditor
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import
|
|
27
|
-
const
|
|
28
|
-
({ children
|
|
29
|
-
const { editor
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { forwardRef, useRef, useEffect, useMemo, useState, useImperativeHandle, useDeferredValue } from "react";
|
|
3
|
+
import { Underline } from "@tiptap/extension-underline";
|
|
4
|
+
import TextStyle from "@tiptap/extension-text-style";
|
|
5
|
+
import { Color } from "@tiptap/extension-color";
|
|
6
|
+
import BulletList from "@tiptap/extension-bullet-list";
|
|
7
|
+
import Highlight from "@tiptap/extension-highlight";
|
|
8
|
+
import { useCurrentEditor, BubbleMenu, isNodeSelection, Node, mergeAttributes, ReactRenderer, EditorProvider } from "@tiptap/react";
|
|
9
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
10
|
+
import { Popover, Button, ExpandMoreIcon, CheckIcon, TextFieldsIcon, LooksOneIcon, LooksTwoIcon, Looks3Icon, CheckBoxIcon, FormatListBulletedIcon, FormatListNumberedIcon, FormatQuoteIcon, CodeIcon, cls, focusedDisabled, DeleteIcon, FormatBoldIcon, FormatItalicIcon, FormatUnderlinedIcon, FormatStrikethroughIcon, defaultBorderMixin, AutoAwesomeIcon, ImageIcon, useInjectStyles, Separator } from "@firecms/ui";
|
|
11
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
12
|
+
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
|
13
|
+
import TiptapLink from "@tiptap/extension-link";
|
|
14
|
+
import TiptapImage from "@tiptap/extension-image";
|
|
15
|
+
import Placeholder from "@tiptap/extension-placeholder";
|
|
16
|
+
import { TaskItem } from "@tiptap/extension-task-item";
|
|
17
|
+
import { TaskList } from "@tiptap/extension-task-list";
|
|
18
|
+
import { Extension, InputRule } from "@tiptap/core";
|
|
19
|
+
import { PluginKey, Plugin } from "prosemirror-state";
|
|
20
|
+
import { DecorationSet, Decoration } from "prosemirror-view";
|
|
21
|
+
import { Markdown } from "tiptap-markdown";
|
|
22
|
+
import { DecorationSet as DecorationSet$1, Decoration as Decoration$1, __serializeForClipboard } from "@tiptap/pm/view";
|
|
23
|
+
import { PluginKey as PluginKey$1, Plugin as Plugin$1, NodeSelection } from "@tiptap/pm/state";
|
|
24
|
+
import Document from "@tiptap/extension-document";
|
|
25
|
+
import Suggestion from "@tiptap/suggestion";
|
|
26
|
+
import tippy from "tippy.js";
|
|
27
|
+
const EditorBubble = forwardRef(
|
|
28
|
+
({ children, tippyOptions, ...rest }, ref) => {
|
|
29
|
+
const { editor } = useCurrentEditor();
|
|
30
|
+
const instanceRef = useRef(null);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!instanceRef.current || !tippyOptions?.placement) return;
|
|
33
|
+
instanceRef.current.setProps({ placement: tippyOptions.placement });
|
|
34
|
+
instanceRef.current.popperInstance?.update();
|
|
35
|
+
}, [tippyOptions?.placement]);
|
|
36
|
+
const bubbleMenuProps = useMemo(() => {
|
|
37
|
+
const shouldShow = ({ editor: editor2, state }) => {
|
|
38
|
+
const { selection } = state;
|
|
39
|
+
const { empty } = selection;
|
|
40
|
+
if (editor2.isActive("image") || empty || isNodeSelection(selection)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
shouldShow,
|
|
47
|
+
tippyOptions: {
|
|
48
|
+
onCreate: (val) => {
|
|
49
|
+
instanceRef.current = val;
|
|
50
|
+
},
|
|
51
|
+
moveTransition: "transform 0.15s ease-out",
|
|
52
|
+
...tippyOptions
|
|
41
53
|
},
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return a ? (
|
|
54
|
+
...rest
|
|
55
|
+
};
|
|
56
|
+
}, [rest, tippyOptions]);
|
|
57
|
+
if (!editor) return null;
|
|
58
|
+
return (
|
|
48
59
|
//We need to add this because of https://github.com/ueberdosis/tiptap/issues/2658
|
|
49
|
-
/* @__PURE__ */
|
|
50
|
-
)
|
|
60
|
+
/* @__PURE__ */ jsx("div", { ref, children: /* @__PURE__ */ jsx(BubbleMenu, { editor, ...bubbleMenuProps, children }) })
|
|
61
|
+
);
|
|
51
62
|
}
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
);
|
|
64
|
+
const EditorBubbleItem = forwardRef(({ children, asChild, onSelect, ...rest }, ref) => {
|
|
65
|
+
const { editor } = useCurrentEditor();
|
|
66
|
+
const Comp = asChild ? Slot : "div";
|
|
67
|
+
if (!editor) return null;
|
|
68
|
+
return /* @__PURE__ */ jsx(Comp, { ref, ...rest, onClick: () => onSelect?.(editor), children });
|
|
55
69
|
});
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
query: e,
|
|
59
|
-
range: o
|
|
60
|
-
}) => {
|
|
61
|
-
const t = U(ne, { store: P }), r = U(ae, { store: P });
|
|
62
|
-
return C(() => {
|
|
63
|
-
t(e);
|
|
64
|
-
}, [e, t]), C(() => {
|
|
65
|
-
r(o);
|
|
66
|
-
}, [o, r]), C(() => {
|
|
67
|
-
const a = ["ArrowUp", "ArrowDown", "Enter"], c = (n) => {
|
|
68
|
-
if (a.includes(n.key)) {
|
|
69
|
-
n.preventDefault();
|
|
70
|
-
const i = document.querySelector("#slash-command");
|
|
71
|
-
i && i.dispatchEvent(
|
|
72
|
-
new KeyboardEvent("keydown", { key: n.key, cancelable: !0, bubbles: !0 })
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
return document.addEventListener("keydown", c), () => {
|
|
77
|
-
document.removeEventListener("keydown", c);
|
|
78
|
-
};
|
|
79
|
-
}, []), /* @__PURE__ */ s(re.Out, {});
|
|
80
|
-
}, tt = T(
|
|
81
|
-
({ children: e, className: o, ...t }, r) => {
|
|
82
|
-
const a = D(null), [c, n] = we(ne);
|
|
83
|
-
return /* @__PURE__ */ s(re.In, { children: /* @__PURE__ */ b(
|
|
84
|
-
S,
|
|
85
|
-
{
|
|
86
|
-
ref: r,
|
|
87
|
-
onKeyDown: (i) => {
|
|
88
|
-
i.stopPropagation();
|
|
89
|
-
},
|
|
90
|
-
id: "slash-command",
|
|
91
|
-
className: o,
|
|
92
|
-
...t,
|
|
93
|
-
children: [
|
|
94
|
-
/* @__PURE__ */ s(S.Input, { value: c, onValueChange: n, style: { display: "none" } }),
|
|
95
|
-
/* @__PURE__ */ s(S.List, { ref: a, children: e })
|
|
96
|
-
]
|
|
97
|
-
}
|
|
98
|
-
) });
|
|
99
|
-
}
|
|
100
|
-
), se = T(({ children: e, onCommand: o, ...t }, r) => {
|
|
101
|
-
const { editor: a } = y(), c = Ae(ae);
|
|
102
|
-
return !a || !c ? null : /* @__PURE__ */ s(Ee, { ref: r, ...t, onSelect: () => o({ editor: a, range: c }), children: e });
|
|
103
|
-
});
|
|
104
|
-
se.displayName = "EditorCommandItem";
|
|
105
|
-
const ot = Me, rt = B.create({
|
|
106
|
-
name: "slash-command",
|
|
107
|
-
addOptions() {
|
|
108
|
-
return {
|
|
109
|
-
suggestion: {
|
|
110
|
-
char: "/",
|
|
111
|
-
command: ({ editor: e, range: o, props: t }) => {
|
|
112
|
-
t.command({ editor: e, range: o });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
},
|
|
117
|
-
addProseMirrorPlugins() {
|
|
118
|
-
return [
|
|
119
|
-
ze({
|
|
120
|
-
editor: this.editor,
|
|
121
|
-
...this.options.suggestion
|
|
122
|
-
})
|
|
123
|
-
];
|
|
124
|
-
}
|
|
125
|
-
}), nt = () => {
|
|
126
|
-
let e = null, o = null;
|
|
127
|
-
return {
|
|
128
|
-
onStart: (t) => {
|
|
129
|
-
e = new xe(et, {
|
|
130
|
-
props: t,
|
|
131
|
-
editor: t.editor
|
|
132
|
-
}), o = Fe("body", {
|
|
133
|
-
getReferenceClientRect: t.clientRect,
|
|
134
|
-
appendTo: () => document.body,
|
|
135
|
-
content: e.element,
|
|
136
|
-
showOnCreate: !0,
|
|
137
|
-
interactive: !0,
|
|
138
|
-
trigger: "manual",
|
|
139
|
-
placement: "bottom-start"
|
|
140
|
-
});
|
|
141
|
-
},
|
|
142
|
-
onUpdate: (t) => {
|
|
143
|
-
e?.updateProps(t), o && o[0].setProps({
|
|
144
|
-
getReferenceClientRect: t.clientRect
|
|
145
|
-
});
|
|
146
|
-
},
|
|
147
|
-
onKeyDown: (t) => t.event.key === "Escape" ? (o?.[0].hide(), !0) : e?.ref?.onKeyDown(t),
|
|
148
|
-
onExit: () => {
|
|
149
|
-
o?.[0].destroy(), e?.destroy();
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
}, at = (e) => e, st = Pe.configure({
|
|
153
|
-
placeholder: ({ node: e }) => e.type.name === "heading" ? `Heading ${e.attrs.level}` : "Press '/' for commands",
|
|
154
|
-
includeChildren: !0
|
|
155
|
-
}), it = Se.extend({
|
|
156
|
-
addInputRules() {
|
|
157
|
-
return [
|
|
158
|
-
new Be({
|
|
159
|
-
find: /^(?:---|—-|___\s|\*\*\*\s)$/,
|
|
160
|
-
handler: ({ state: e, range: o }) => {
|
|
161
|
-
const t = {}, { tr: r } = e, a = o.from, c = o.to;
|
|
162
|
-
r.insert(a - 1, this.type.create(t)).delete(
|
|
163
|
-
r.mapping.map(a),
|
|
164
|
-
r.mapping.map(c)
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
];
|
|
169
|
-
}
|
|
170
|
-
}), q = [
|
|
70
|
+
EditorBubbleItem.displayName = "EditorBubbleItem";
|
|
71
|
+
const items = [
|
|
171
72
|
{
|
|
172
73
|
name: "Text",
|
|
173
|
-
icon:
|
|
174
|
-
command: (
|
|
74
|
+
icon: TextFieldsIcon,
|
|
75
|
+
command: (editor) => editor?.chain().focus().toggleNode("paragraph", "paragraph").run(),
|
|
175
76
|
// I feel like there has to be a more efficient way to do this – feel free to PR if you know how!
|
|
176
|
-
isActive: (
|
|
77
|
+
isActive: (editor) => (editor?.isActive("paragraph") && !editor?.isActive("bulletList") && !editor?.isActive("orderedList")) ?? false
|
|
177
78
|
},
|
|
178
79
|
{
|
|
179
80
|
name: "Heading 1",
|
|
180
|
-
icon:
|
|
181
|
-
command: (
|
|
182
|
-
isActive: (
|
|
81
|
+
icon: LooksOneIcon,
|
|
82
|
+
command: (editor) => editor?.chain().focus().toggleHeading({ level: 1 }).run(),
|
|
83
|
+
isActive: (editor) => editor?.isActive("heading", { level: 1 }) ?? false
|
|
183
84
|
},
|
|
184
85
|
{
|
|
185
86
|
name: "Heading 2",
|
|
186
|
-
icon:
|
|
187
|
-
command: (
|
|
188
|
-
isActive: (
|
|
87
|
+
icon: LooksTwoIcon,
|
|
88
|
+
command: (editor) => editor?.chain().focus().toggleHeading({ level: 2 }).run(),
|
|
89
|
+
isActive: (editor) => editor?.isActive("heading", { level: 2 }) ?? false
|
|
189
90
|
},
|
|
190
91
|
{
|
|
191
92
|
name: "Heading 3",
|
|
192
|
-
icon:
|
|
193
|
-
command: (
|
|
194
|
-
isActive: (
|
|
93
|
+
icon: Looks3Icon,
|
|
94
|
+
command: (editor) => editor?.chain().focus().toggleHeading({ level: 3 }).run(),
|
|
95
|
+
isActive: (editor) => editor?.isActive("heading", { level: 3 }) ?? false
|
|
195
96
|
},
|
|
196
97
|
{
|
|
197
98
|
name: "To-do List",
|
|
198
|
-
icon:
|
|
199
|
-
command: (
|
|
200
|
-
isActive: (
|
|
99
|
+
icon: CheckBoxIcon,
|
|
100
|
+
command: (editor) => editor?.chain().focus().toggleTaskList().run(),
|
|
101
|
+
isActive: (editor) => editor?.isActive("taskItem") ?? false
|
|
201
102
|
},
|
|
202
103
|
{
|
|
203
104
|
name: "Bullet List",
|
|
204
|
-
icon:
|
|
205
|
-
command: (
|
|
206
|
-
isActive: (
|
|
105
|
+
icon: FormatListBulletedIcon,
|
|
106
|
+
command: (editor) => editor?.chain().focus().toggleBulletList().run(),
|
|
107
|
+
isActive: (editor) => editor?.isActive("bulletList") ?? false
|
|
207
108
|
},
|
|
208
109
|
{
|
|
209
110
|
name: "Numbered List",
|
|
210
|
-
icon:
|
|
211
|
-
command: (
|
|
212
|
-
isActive: (
|
|
111
|
+
icon: FormatListNumberedIcon,
|
|
112
|
+
command: (editor) => editor?.chain().focus().toggleOrderedList().run(),
|
|
113
|
+
isActive: (editor) => editor?.isActive("orderedList") ?? false
|
|
213
114
|
},
|
|
214
115
|
{
|
|
215
116
|
name: "Quote",
|
|
216
|
-
icon:
|
|
217
|
-
command: (
|
|
218
|
-
isActive: (
|
|
117
|
+
icon: FormatQuoteIcon,
|
|
118
|
+
command: (editor) => editor?.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
|
|
119
|
+
isActive: (editor) => editor?.isActive("blockquote") ?? false
|
|
219
120
|
},
|
|
220
121
|
{
|
|
221
122
|
name: "Code",
|
|
222
|
-
icon:
|
|
223
|
-
command: (
|
|
224
|
-
isActive: (
|
|
123
|
+
icon: CodeIcon,
|
|
124
|
+
command: (editor) => editor?.chain().focus().toggleCodeBlock().run(),
|
|
125
|
+
isActive: (editor) => editor?.isActive("codeBlock") ?? false
|
|
225
126
|
}
|
|
226
|
-
]
|
|
227
|
-
|
|
228
|
-
|
|
127
|
+
];
|
|
128
|
+
const NodeSelector = ({
|
|
129
|
+
open,
|
|
130
|
+
onOpenChange,
|
|
131
|
+
portalContainer
|
|
229
132
|
}) => {
|
|
230
|
-
const { editor
|
|
231
|
-
if (!
|
|
232
|
-
const
|
|
133
|
+
const { editor } = useCurrentEditor();
|
|
134
|
+
if (!editor) return null;
|
|
135
|
+
const activeItem = items.filter((item) => item.isActive(editor)).pop() ?? {
|
|
233
136
|
name: "Multiple"
|
|
234
137
|
};
|
|
235
|
-
return /* @__PURE__ */
|
|
236
|
-
|
|
138
|
+
return /* @__PURE__ */ jsx(
|
|
139
|
+
Popover,
|
|
237
140
|
{
|
|
238
141
|
sideOffset: 5,
|
|
239
142
|
align: "start",
|
|
143
|
+
portalContainer,
|
|
240
144
|
className: "w-48 p-1",
|
|
241
|
-
trigger: /* @__PURE__ */
|
|
242
|
-
|
|
145
|
+
trigger: /* @__PURE__ */ jsxs(
|
|
146
|
+
Button,
|
|
243
147
|
{
|
|
244
148
|
variant: "text",
|
|
245
149
|
className: "gap-2 rounded-none",
|
|
246
150
|
color: "text",
|
|
247
151
|
children: [
|
|
248
|
-
/* @__PURE__ */
|
|
249
|
-
/* @__PURE__ */
|
|
152
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-sm", children: activeItem.name }),
|
|
153
|
+
/* @__PURE__ */ jsx(ExpandMoreIcon, { size: "small" })
|
|
250
154
|
]
|
|
251
155
|
}
|
|
252
156
|
),
|
|
253
|
-
modal:
|
|
254
|
-
open
|
|
255
|
-
onOpenChange
|
|
256
|
-
children:
|
|
257
|
-
|
|
157
|
+
modal: true,
|
|
158
|
+
open,
|
|
159
|
+
onOpenChange,
|
|
160
|
+
children: items.map((item, index) => /* @__PURE__ */ jsxs(
|
|
161
|
+
EditorBubbleItem,
|
|
258
162
|
{
|
|
259
|
-
onSelect: (
|
|
260
|
-
|
|
163
|
+
onSelect: (editor2) => {
|
|
164
|
+
item.command(editor2);
|
|
165
|
+
onOpenChange(false);
|
|
261
166
|
},
|
|
262
167
|
className: "flex cursor-pointer items-center justify-between rounded px-2 py-1 text-sm hover:bg-blue-50 hover:dark:bg-gray-700 text-gray-900 dark:text-white",
|
|
263
168
|
children: [
|
|
264
|
-
/* @__PURE__ */
|
|
265
|
-
/* @__PURE__ */
|
|
266
|
-
/* @__PURE__ */
|
|
169
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
|
|
170
|
+
/* @__PURE__ */ jsx(item.icon, { size: "smallest" }),
|
|
171
|
+
/* @__PURE__ */ jsx("span", { children: item.name })
|
|
267
172
|
] }),
|
|
268
|
-
|
|
173
|
+
activeItem.name === item.name && /* @__PURE__ */ jsx(CheckIcon, { size: "smallest" })
|
|
269
174
|
]
|
|
270
175
|
},
|
|
271
|
-
|
|
176
|
+
index
|
|
272
177
|
))
|
|
273
178
|
}
|
|
274
179
|
);
|
|
275
180
|
};
|
|
276
|
-
function
|
|
181
|
+
function isValidUrl(url) {
|
|
277
182
|
try {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
183
|
+
new URL(url);
|
|
184
|
+
return true;
|
|
185
|
+
} catch (e) {
|
|
186
|
+
return false;
|
|
281
187
|
}
|
|
282
188
|
}
|
|
283
|
-
function
|
|
284
|
-
if (
|
|
189
|
+
function getUrlFromString(str) {
|
|
190
|
+
if (isValidUrl(str)) return str;
|
|
285
191
|
try {
|
|
286
|
-
|
|
287
|
-
|
|
192
|
+
if (str.includes(".") && !str.includes(" ")) {
|
|
193
|
+
return new URL(`https://${str}`).toString();
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
} catch (e) {
|
|
288
197
|
return null;
|
|
289
198
|
}
|
|
290
199
|
}
|
|
291
|
-
const
|
|
292
|
-
open
|
|
293
|
-
onOpenChange
|
|
200
|
+
const LinkSelector = ({
|
|
201
|
+
open,
|
|
202
|
+
onOpenChange
|
|
294
203
|
}) => {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
204
|
+
const inputRef = useRef(null);
|
|
205
|
+
const { editor } = useCurrentEditor();
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
inputRef.current && inputRef.current?.focus();
|
|
208
|
+
});
|
|
209
|
+
if (!editor) return null;
|
|
210
|
+
return /* @__PURE__ */ jsx(
|
|
211
|
+
Popover,
|
|
300
212
|
{
|
|
301
|
-
modal:
|
|
302
|
-
open
|
|
303
|
-
onOpenChange
|
|
304
|
-
trigger: /* @__PURE__ */
|
|
305
|
-
|
|
213
|
+
modal: true,
|
|
214
|
+
open,
|
|
215
|
+
onOpenChange,
|
|
216
|
+
trigger: /* @__PURE__ */ jsx(
|
|
217
|
+
Button,
|
|
306
218
|
{
|
|
307
219
|
variant: "text",
|
|
308
220
|
className: "gap-2 rounded-none",
|
|
309
221
|
color: "text",
|
|
310
|
-
children: /* @__PURE__ */
|
|
311
|
-
"text-blue-500":
|
|
222
|
+
children: /* @__PURE__ */ jsx("p", { className: cls("underline decoration-stone-400 underline-offset-4", {
|
|
223
|
+
"text-blue-500": editor.isActive("link")
|
|
312
224
|
}), children: "Link" })
|
|
313
225
|
}
|
|
314
226
|
),
|
|
315
|
-
children: /* @__PURE__ */
|
|
227
|
+
children: /* @__PURE__ */ jsxs(
|
|
316
228
|
"form",
|
|
317
229
|
{
|
|
318
|
-
onSubmit: (
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
|
|
230
|
+
onSubmit: (e) => {
|
|
231
|
+
const target = e.currentTarget;
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
const input = target[0];
|
|
234
|
+
const url = getUrlFromString(input.value);
|
|
235
|
+
url && editor.chain().focus().setLink({ href: url }).run();
|
|
323
236
|
},
|
|
324
237
|
className: "flex p-1",
|
|
325
238
|
children: [
|
|
326
|
-
/* @__PURE__ */
|
|
239
|
+
/* @__PURE__ */ jsx(
|
|
327
240
|
"input",
|
|
328
241
|
{
|
|
329
|
-
ref:
|
|
330
|
-
autoFocus:
|
|
242
|
+
ref: inputRef,
|
|
243
|
+
autoFocus: open,
|
|
331
244
|
placeholder: "Paste a link",
|
|
332
|
-
defaultValue:
|
|
333
|
-
className: "text-gray-900 dark:text-white flex-grow bg-transparent p-1 text-sm outline-none"
|
|
245
|
+
defaultValue: editor.getAttributes("link").href || "",
|
|
246
|
+
className: cls("text-gray-900 dark:text-white flex-grow bg-transparent p-1 text-sm outline-none", focusedDisabled)
|
|
334
247
|
}
|
|
335
248
|
),
|
|
336
|
-
|
|
337
|
-
|
|
249
|
+
editor.getAttributes("link").href ? /* @__PURE__ */ jsx(
|
|
250
|
+
Button,
|
|
338
251
|
{
|
|
339
252
|
size: "small",
|
|
340
253
|
variant: "text",
|
|
@@ -342,623 +255,1374 @@ const ut = ({
|
|
|
342
255
|
color: "text",
|
|
343
256
|
className: "flex items-center",
|
|
344
257
|
onClick: () => {
|
|
345
|
-
|
|
258
|
+
editor.chain().focus().unsetLink().run();
|
|
346
259
|
},
|
|
347
|
-
children: /* @__PURE__ */
|
|
260
|
+
children: /* @__PURE__ */ jsx(DeleteIcon, { size: "small" })
|
|
348
261
|
}
|
|
349
|
-
) : /* @__PURE__ */
|
|
350
|
-
|
|
262
|
+
) : /* @__PURE__ */ jsx(
|
|
263
|
+
Button,
|
|
351
264
|
{
|
|
352
265
|
size: "small",
|
|
353
266
|
variant: "text",
|
|
354
|
-
children: /* @__PURE__ */
|
|
267
|
+
children: /* @__PURE__ */ jsx(CheckIcon, { size: "small" })
|
|
355
268
|
}
|
|
356
269
|
)
|
|
357
270
|
]
|
|
358
271
|
}
|
|
359
272
|
)
|
|
360
273
|
}
|
|
361
|
-
)
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
const TextButtons = () => {
|
|
277
|
+
const { editor } = useCurrentEditor();
|
|
278
|
+
if (!editor) return null;
|
|
279
|
+
const items2 = [
|
|
365
280
|
{
|
|
366
281
|
name: "bold",
|
|
367
|
-
isActive: (
|
|
368
|
-
command: (
|
|
369
|
-
icon:
|
|
282
|
+
isActive: (editor2) => editor2?.isActive("bold") ?? false,
|
|
283
|
+
command: (editor2) => editor2?.chain().focus().toggleBold().run(),
|
|
284
|
+
icon: FormatBoldIcon
|
|
370
285
|
},
|
|
371
286
|
{
|
|
372
287
|
name: "italic",
|
|
373
|
-
isActive: (
|
|
374
|
-
command: (
|
|
375
|
-
icon:
|
|
288
|
+
isActive: (editor2) => editor2?.isActive("italic") ?? false,
|
|
289
|
+
command: (editor2) => editor2?.chain().focus().toggleItalic().run(),
|
|
290
|
+
icon: FormatItalicIcon
|
|
376
291
|
},
|
|
377
292
|
{
|
|
378
293
|
name: "underline",
|
|
379
|
-
isActive: (
|
|
380
|
-
command: (
|
|
381
|
-
icon:
|
|
294
|
+
isActive: (editor2) => editor2?.isActive("underline") ?? false,
|
|
295
|
+
command: (editor2) => editor2?.chain().focus().toggleUnderline().run(),
|
|
296
|
+
icon: FormatUnderlinedIcon
|
|
382
297
|
},
|
|
383
298
|
{
|
|
384
299
|
name: "strike",
|
|
385
|
-
isActive: (
|
|
386
|
-
command: (
|
|
387
|
-
icon:
|
|
300
|
+
isActive: (editor2) => editor2?.isActive("strike") ?? false,
|
|
301
|
+
command: (editor2) => editor2?.chain().focus().toggleStrike().run(),
|
|
302
|
+
icon: FormatStrikethroughIcon
|
|
388
303
|
},
|
|
389
304
|
{
|
|
390
305
|
name: "code",
|
|
391
|
-
isActive: (
|
|
392
|
-
command: (
|
|
393
|
-
icon:
|
|
306
|
+
isActive: (editor2) => editor2?.isActive("code") ?? false,
|
|
307
|
+
command: (editor2) => editor2?.chain().focus().toggleCode().run(),
|
|
308
|
+
icon: CodeIcon
|
|
394
309
|
}
|
|
395
|
-
]
|
|
396
|
-
|
|
310
|
+
];
|
|
311
|
+
return /* @__PURE__ */ jsx("div", { className: "flex", children: items2.map((item, index) => /* @__PURE__ */ jsx(
|
|
312
|
+
EditorBubbleItem,
|
|
397
313
|
{
|
|
398
|
-
onSelect: (
|
|
399
|
-
|
|
314
|
+
onSelect: (editor2) => {
|
|
315
|
+
item.command(editor2);
|
|
400
316
|
},
|
|
401
|
-
children: /* @__PURE__ */
|
|
402
|
-
|
|
317
|
+
children: /* @__PURE__ */ jsx(
|
|
318
|
+
Button,
|
|
403
319
|
{
|
|
404
320
|
size: "small",
|
|
405
321
|
color: "text",
|
|
406
322
|
className: "gap-2 rounded-none h-full",
|
|
407
323
|
variant: "text",
|
|
408
|
-
children: /* @__PURE__ */
|
|
409
|
-
|
|
324
|
+
children: /* @__PURE__ */ jsx(
|
|
325
|
+
item.icon,
|
|
410
326
|
{
|
|
411
|
-
className:
|
|
412
|
-
"text-inherit": !
|
|
413
|
-
"text-blue-500":
|
|
327
|
+
className: cls({
|
|
328
|
+
"text-inherit": !item.isActive(editor),
|
|
329
|
+
"text-blue-500": item.isActive(editor)
|
|
414
330
|
})
|
|
415
331
|
}
|
|
416
332
|
)
|
|
417
333
|
}
|
|
418
334
|
)
|
|
419
335
|
},
|
|
420
|
-
|
|
421
|
-
)) })
|
|
336
|
+
index
|
|
337
|
+
)) });
|
|
422
338
|
};
|
|
423
|
-
function
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
return
|
|
435
|
-
e[o] = H(e[o]);
|
|
436
|
-
})), e);
|
|
339
|
+
function removeClassesFromJson(jsonObj) {
|
|
340
|
+
if (Array.isArray(jsonObj)) {
|
|
341
|
+
return jsonObj.map((item) => removeClassesFromJson(item));
|
|
342
|
+
} else if (typeof jsonObj === "object" && jsonObj !== null) {
|
|
343
|
+
if (jsonObj.attrs && typeof jsonObj.attrs === "object" && "class" in jsonObj.attrs) {
|
|
344
|
+
delete jsonObj.attrs.class;
|
|
345
|
+
}
|
|
346
|
+
Object.keys(jsonObj).forEach((key) => {
|
|
347
|
+
jsonObj[key] = removeClassesFromJson(jsonObj[key]);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return jsonObj;
|
|
437
351
|
}
|
|
438
|
-
const
|
|
352
|
+
const loadingDecorationKey = new PluginKey("loadingDecoration");
|
|
353
|
+
const TextLoadingDecorationExtension = Extension.create({
|
|
354
|
+
name: "loadingDecoration",
|
|
355
|
+
addOptions() {
|
|
356
|
+
return {
|
|
357
|
+
pluginKey: loadingDecorationKey
|
|
358
|
+
};
|
|
359
|
+
},
|
|
360
|
+
addProseMirrorPlugins() {
|
|
361
|
+
const pluginKey = this.options.pluginKey;
|
|
362
|
+
return [
|
|
363
|
+
new Plugin({
|
|
364
|
+
key: pluginKey,
|
|
365
|
+
state: {
|
|
366
|
+
init() {
|
|
367
|
+
return {
|
|
368
|
+
decorationSet: DecorationSet.empty,
|
|
369
|
+
hasDecoration: false
|
|
370
|
+
};
|
|
371
|
+
},
|
|
372
|
+
apply(tr, oldState) {
|
|
373
|
+
const action = tr.getMeta(pluginKey);
|
|
374
|
+
if (action?.type === "loadingDecoration") {
|
|
375
|
+
const { pos, remove, loadingHtml } = action;
|
|
376
|
+
if (remove) {
|
|
377
|
+
return {
|
|
378
|
+
decorationSet: DecorationSet.empty,
|
|
379
|
+
hasDecoration: false
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
const decoration = Decoration.widget(pos, () => {
|
|
383
|
+
const container = document.createElement("span");
|
|
384
|
+
container.className = "loading-decoration";
|
|
385
|
+
if (loadingHtml) {
|
|
386
|
+
container.innerHTML = loadingHtml;
|
|
387
|
+
} else {
|
|
388
|
+
const span = document.createElement("span");
|
|
389
|
+
span.innerText = "loading...";
|
|
390
|
+
container.appendChild(span);
|
|
391
|
+
}
|
|
392
|
+
return container;
|
|
393
|
+
});
|
|
394
|
+
return {
|
|
395
|
+
decorationSet: DecorationSet.empty.add(tr.doc, [decoration]),
|
|
396
|
+
hasDecoration: true
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
decorationSet: oldState.decorationSet.map(tr.mapping, tr.doc),
|
|
401
|
+
hasDecoration: oldState.hasDecoration
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
props: {
|
|
406
|
+
decorations(state) {
|
|
407
|
+
return this.getState(state)?.decorationSet || DecorationSet.empty;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
})
|
|
411
|
+
];
|
|
412
|
+
},
|
|
413
|
+
addCommands() {
|
|
414
|
+
return {
|
|
415
|
+
toggleLoadingDecoration: (loadingHtml) => ({ state, dispatch }) => {
|
|
416
|
+
const { selection } = state;
|
|
417
|
+
const pos = selection.from;
|
|
418
|
+
if (!dispatch) return false;
|
|
419
|
+
const pluginKey = this.options.pluginKey;
|
|
420
|
+
const tr = state.tr.setMeta(pluginKey, {
|
|
421
|
+
pos,
|
|
422
|
+
type: "loadingDecoration",
|
|
423
|
+
remove: false,
|
|
424
|
+
loadingHtml
|
|
425
|
+
});
|
|
426
|
+
dispatch(tr);
|
|
427
|
+
return true;
|
|
428
|
+
},
|
|
429
|
+
removeLoadingDecoration: () => ({ state, dispatch }) => {
|
|
430
|
+
if (!dispatch) return false;
|
|
431
|
+
const pluginKey = this.options.pluginKey;
|
|
432
|
+
const tr = state.tr.setMeta(pluginKey, {
|
|
433
|
+
pos: 0,
|
|
434
|
+
// We can pass any position as it will remove the entire decoration set
|
|
435
|
+
type: "loadingDecoration",
|
|
436
|
+
remove: true
|
|
437
|
+
});
|
|
438
|
+
dispatch(tr);
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
const PlaceholderExtension = Placeholder.configure({
|
|
445
|
+
placeholder: ({
|
|
446
|
+
node,
|
|
447
|
+
editor
|
|
448
|
+
}) => {
|
|
449
|
+
editor.state.selection;
|
|
450
|
+
function hasLoadingDecoration(editor2) {
|
|
451
|
+
const pluginState = loadingDecorationKey.get(editor2.state);
|
|
452
|
+
return pluginState?.getState(editor2.state)?.hasDecoration ?? false;
|
|
453
|
+
}
|
|
454
|
+
const hasDecoration = hasLoadingDecoration(editor);
|
|
455
|
+
if (hasDecoration) {
|
|
456
|
+
return "";
|
|
457
|
+
}
|
|
458
|
+
if (node.type.name === "heading") {
|
|
459
|
+
return `Heading ${node.attrs.level}`;
|
|
460
|
+
}
|
|
461
|
+
return "Press '/' for commands";
|
|
462
|
+
},
|
|
463
|
+
includeChildren: true
|
|
464
|
+
});
|
|
465
|
+
const Horizontal = HorizontalRule.extend({
|
|
466
|
+
addInputRules() {
|
|
467
|
+
return [
|
|
468
|
+
new InputRule({
|
|
469
|
+
find: /^(?:---|—-|___\s|\*\*\*\s)$/,
|
|
470
|
+
handler: ({
|
|
471
|
+
state,
|
|
472
|
+
range
|
|
473
|
+
}) => {
|
|
474
|
+
const attributes = {};
|
|
475
|
+
const { tr } = state;
|
|
476
|
+
const start = range.from;
|
|
477
|
+
const end = range.to;
|
|
478
|
+
tr.insert(start - 1, this.type.create(attributes)).delete(
|
|
479
|
+
tr.mapping.map(start),
|
|
480
|
+
tr.mapping.map(end)
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
})
|
|
484
|
+
];
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
const placeholder = PlaceholderExtension;
|
|
488
|
+
const tiptapLink = TiptapLink.configure({
|
|
439
489
|
HTMLAttributes: {
|
|
440
|
-
class:
|
|
490
|
+
class: cls(
|
|
441
491
|
"text-gray-600 dark:text-slate-300 underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer"
|
|
442
492
|
)
|
|
443
493
|
}
|
|
444
|
-
})
|
|
494
|
+
});
|
|
495
|
+
const taskList = TaskList.configure({
|
|
445
496
|
HTMLAttributes: {
|
|
446
|
-
class:
|
|
497
|
+
class: cls("not-prose")
|
|
447
498
|
}
|
|
448
|
-
})
|
|
499
|
+
});
|
|
500
|
+
const taskItem = TaskItem.configure({
|
|
449
501
|
HTMLAttributes: {
|
|
450
|
-
class:
|
|
502
|
+
class: cls("flex items-start my-4")
|
|
451
503
|
},
|
|
452
|
-
nested:
|
|
453
|
-
})
|
|
504
|
+
nested: true
|
|
505
|
+
});
|
|
506
|
+
const markdownExtension = Markdown.configure({
|
|
507
|
+
html: true
|
|
508
|
+
});
|
|
509
|
+
const horizontalRule = Horizontal.configure({
|
|
454
510
|
HTMLAttributes: {
|
|
455
|
-
class:
|
|
511
|
+
class: cls("mt-4 mb-6 border-t", defaultBorderMixin)
|
|
456
512
|
}
|
|
457
|
-
})
|
|
513
|
+
});
|
|
514
|
+
const starterKit = StarterKit.configure({
|
|
458
515
|
bulletList: {
|
|
459
516
|
HTMLAttributes: {
|
|
460
|
-
class:
|
|
517
|
+
class: cls("list-disc list-outside leading-3 -mt-2")
|
|
461
518
|
}
|
|
462
519
|
},
|
|
463
520
|
orderedList: {
|
|
464
521
|
HTMLAttributes: {
|
|
465
|
-
class:
|
|
522
|
+
class: cls("list-decimal list-outside leading-3 -mt-2")
|
|
466
523
|
}
|
|
467
524
|
},
|
|
468
525
|
listItem: {
|
|
469
526
|
HTMLAttributes: {
|
|
470
|
-
class:
|
|
527
|
+
class: cls("leading-normal -mb-2")
|
|
471
528
|
}
|
|
472
529
|
},
|
|
473
530
|
blockquote: {
|
|
474
531
|
HTMLAttributes: {
|
|
475
|
-
class:
|
|
532
|
+
class: cls("border-l-4 border-primary")
|
|
476
533
|
}
|
|
477
534
|
},
|
|
478
535
|
codeBlock: {
|
|
479
536
|
HTMLAttributes: {
|
|
480
|
-
class:
|
|
537
|
+
class: cls("rounded bg-blue-50 dark:bg-gray-700 border p-5 font-mono font-medium", defaultBorderMixin)
|
|
481
538
|
}
|
|
482
539
|
},
|
|
483
540
|
code: {
|
|
484
541
|
HTMLAttributes: {
|
|
485
|
-
class:
|
|
542
|
+
class: cls("rounded-md bg-slate-50 dark:bg-gray-700 px-1.5 py-1 font-mono font-medium"),
|
|
486
543
|
spellcheck: "false"
|
|
487
544
|
}
|
|
488
545
|
},
|
|
489
|
-
|
|
546
|
+
document: false,
|
|
547
|
+
horizontalRule: false,
|
|
490
548
|
dropcursor: {
|
|
491
549
|
color: "#DBEAFE",
|
|
492
550
|
width: 4
|
|
493
551
|
},
|
|
494
|
-
gapcursor:
|
|
552
|
+
gapcursor: false
|
|
495
553
|
});
|
|
496
|
-
async function
|
|
497
|
-
const { schema
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const
|
|
506
|
-
|
|
554
|
+
async function onFileRead(view, readerEvent, pos, upload, image) {
|
|
555
|
+
const { schema } = view.state;
|
|
556
|
+
const plugin = view.state.plugins.find((p) => p.key === ImagePluginKey.key);
|
|
557
|
+
if (!plugin) {
|
|
558
|
+
console.error("Image plugin not found");
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
let decorationSet = plugin.getState(view.state);
|
|
562
|
+
const placeholder2 = document.createElement("div");
|
|
563
|
+
const imageElement = document.createElement("img");
|
|
564
|
+
imageElement.setAttribute("class", "opacity-40 rounded-lg border " + defaultBorderMixin);
|
|
565
|
+
imageElement.src = readerEvent.target?.result;
|
|
566
|
+
placeholder2.appendChild(imageElement);
|
|
567
|
+
const deco = Decoration$1.widget(pos, placeholder2);
|
|
568
|
+
decorationSet = decorationSet?.add(view.state.doc, [deco]);
|
|
569
|
+
view.dispatch(view.state.tr.setMeta(plugin, { decorationSet }));
|
|
570
|
+
const src = await upload(image);
|
|
571
|
+
console.debug("Uploaded image", src);
|
|
572
|
+
const imageNode = schema.nodes.image.create({ src });
|
|
573
|
+
const tr = view.state.tr.replaceWith(pos, pos, imageNode);
|
|
574
|
+
decorationSet = decorationSet?.remove([deco]);
|
|
575
|
+
tr.setMeta(plugin, { decorationSet });
|
|
576
|
+
view.dispatch(tr);
|
|
507
577
|
}
|
|
508
|
-
const
|
|
509
|
-
|
|
578
|
+
const ImagePluginKey = new PluginKey$1("imagePlugin");
|
|
579
|
+
const createDropImagePlugin = (upload) => {
|
|
580
|
+
const plugin = new Plugin$1({
|
|
581
|
+
key: ImagePluginKey,
|
|
510
582
|
state: {
|
|
511
583
|
// Initialize the plugin state with an empty DecorationSet
|
|
512
|
-
init: () =>
|
|
584
|
+
init: () => DecorationSet$1.empty,
|
|
513
585
|
// Apply transactions to update the state
|
|
514
|
-
apply: (
|
|
515
|
-
const
|
|
516
|
-
|
|
586
|
+
apply: (tr, old) => {
|
|
587
|
+
const meta = tr.getMeta(plugin);
|
|
588
|
+
if (meta && meta.decorationSet) {
|
|
589
|
+
return meta.decorationSet;
|
|
590
|
+
}
|
|
591
|
+
return old.map(tr.mapping, tr.doc);
|
|
517
592
|
}
|
|
518
593
|
},
|
|
519
594
|
props: {
|
|
520
595
|
handleDOMEvents: {
|
|
521
|
-
drop: (
|
|
522
|
-
if (
|
|
523
|
-
return
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
596
|
+
drop: (view, event) => {
|
|
597
|
+
if (!event.dataTransfer?.files || event.dataTransfer?.files.length === 0) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
event.preventDefault();
|
|
601
|
+
const files = Array.from(event.dataTransfer.files);
|
|
602
|
+
const images = files.filter((file) => /image/i.test(file.type));
|
|
603
|
+
if (images.length === 0) {
|
|
604
|
+
console.log("No images found in dropped files");
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
images.forEach((image) => {
|
|
608
|
+
const position = view.posAtCoords({
|
|
609
|
+
left: event.clientX,
|
|
610
|
+
top: event.clientY
|
|
611
|
+
});
|
|
612
|
+
if (!position) return;
|
|
613
|
+
const reader = new FileReader();
|
|
614
|
+
reader.onload = async (readerEvent) => {
|
|
615
|
+
await onFileRead(view, readerEvent, position.pos, upload, image);
|
|
616
|
+
};
|
|
617
|
+
reader.readAsDataURL(image);
|
|
618
|
+
});
|
|
619
|
+
return true;
|
|
534
620
|
}
|
|
535
621
|
},
|
|
536
|
-
handlePaste(
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
let
|
|
540
|
-
|
|
541
|
-
const
|
|
542
|
-
if (
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
await
|
|
547
|
-
}
|
|
622
|
+
handlePaste(view, event, slice) {
|
|
623
|
+
const items2 = Array.from(event.clipboardData?.items || []);
|
|
624
|
+
const pos = view.state.selection.from;
|
|
625
|
+
let anyImageFound = false;
|
|
626
|
+
items2.forEach((item) => {
|
|
627
|
+
const image = item.getAsFile();
|
|
628
|
+
if (image) {
|
|
629
|
+
anyImageFound = true;
|
|
630
|
+
const reader = new FileReader();
|
|
631
|
+
reader.onload = async (readerEvent) => {
|
|
632
|
+
await onFileRead(view, readerEvent, pos, upload, image);
|
|
633
|
+
};
|
|
634
|
+
reader.readAsDataURL(image);
|
|
548
635
|
}
|
|
549
|
-
})
|
|
636
|
+
});
|
|
637
|
+
return anyImageFound;
|
|
550
638
|
},
|
|
551
|
-
decorations(
|
|
552
|
-
return
|
|
639
|
+
decorations(state) {
|
|
640
|
+
return plugin.getState(state);
|
|
553
641
|
}
|
|
554
642
|
},
|
|
555
|
-
view(
|
|
643
|
+
view(editorView) {
|
|
556
644
|
return {
|
|
557
|
-
update(
|
|
558
|
-
const
|
|
559
|
-
|
|
645
|
+
update(view, prevState) {
|
|
646
|
+
const prevDecos = plugin.getState(prevState);
|
|
647
|
+
const newDecos = plugin.getState(view.state);
|
|
648
|
+
if (prevDecos !== newDecos) {
|
|
649
|
+
view.updateState(view.state);
|
|
650
|
+
}
|
|
560
651
|
}
|
|
561
652
|
};
|
|
562
653
|
}
|
|
563
654
|
});
|
|
564
|
-
return
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
655
|
+
return plugin;
|
|
656
|
+
};
|
|
657
|
+
const createImageExtension = (dropImagePlugin) => {
|
|
658
|
+
return TiptapImage.extend({
|
|
659
|
+
addProseMirrorPlugins() {
|
|
660
|
+
return [dropImagePlugin];
|
|
661
|
+
}
|
|
662
|
+
}).configure({
|
|
663
|
+
allowBase64: true,
|
|
664
|
+
HTMLAttributes: {
|
|
665
|
+
class: cls("rounded-lg border", defaultBorderMixin)
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
};
|
|
669
|
+
const CustomKeymap = Extension.create({
|
|
575
670
|
name: "CustomKeymap",
|
|
576
671
|
addCommands() {
|
|
577
672
|
return {
|
|
578
|
-
selectTextWithinNodeBoundaries: () => ({ editor
|
|
579
|
-
const { state
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
673
|
+
selectTextWithinNodeBoundaries: () => ({ editor, commands }) => {
|
|
674
|
+
const { state } = editor;
|
|
675
|
+
const { tr } = state;
|
|
676
|
+
const startNodePos = tr.selection.$from.start();
|
|
677
|
+
const endNodePos = tr.selection.$to.end();
|
|
678
|
+
return commands.setTextSelection({
|
|
679
|
+
from: startNodePos,
|
|
680
|
+
to: endNodePos
|
|
583
681
|
});
|
|
584
682
|
}
|
|
585
683
|
};
|
|
586
684
|
},
|
|
587
685
|
addKeyboardShortcuts() {
|
|
588
686
|
return {
|
|
589
|
-
"Mod-a": ({ editor
|
|
590
|
-
const { state
|
|
591
|
-
|
|
687
|
+
"Mod-a": ({ editor }) => {
|
|
688
|
+
const { state } = editor;
|
|
689
|
+
const { tr } = state;
|
|
690
|
+
const startSelectionPos = tr.selection.from;
|
|
691
|
+
const endSelectionPos = tr.selection.to;
|
|
692
|
+
const startNodePos = tr.selection.$from.start();
|
|
693
|
+
const endNodePos = tr.selection.$to.end();
|
|
694
|
+
const isCurrentTextSelectionNotExtendedToNodeBoundaries = startSelectionPos > startNodePos || endSelectionPos < endNodePos;
|
|
695
|
+
if (isCurrentTextSelectionNotExtendedToNodeBoundaries) {
|
|
696
|
+
editor.chain().selectTextWithinNodeBoundaries().run();
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
return false;
|
|
592
700
|
}
|
|
593
701
|
};
|
|
594
702
|
}
|
|
595
703
|
});
|
|
596
|
-
function
|
|
597
|
-
const
|
|
704
|
+
function absoluteRect(element) {
|
|
705
|
+
const data = element.getBoundingClientRect();
|
|
706
|
+
let ancestor = element.parentElement;
|
|
707
|
+
while (ancestor && window.getComputedStyle(ancestor).position === "static") {
|
|
708
|
+
ancestor = ancestor.parentElement;
|
|
709
|
+
}
|
|
710
|
+
const ancestorRect = ancestor?.getBoundingClientRect();
|
|
598
711
|
return {
|
|
599
|
-
top:
|
|
600
|
-
left:
|
|
601
|
-
width:
|
|
712
|
+
top: data.top - (ancestorRect?.top ?? 0),
|
|
713
|
+
left: data.left - (ancestorRect?.left ?? 0),
|
|
714
|
+
width: data.width
|
|
602
715
|
};
|
|
603
716
|
}
|
|
604
|
-
function
|
|
605
|
-
return document.elementsFromPoint(
|
|
606
|
-
(
|
|
717
|
+
function nodeDOMAtCoords(coords) {
|
|
718
|
+
return document.elementsFromPoint(coords.x, coords.y).find(
|
|
719
|
+
(elem) => elem.parentElement?.matches?.(".ProseMirror") || elem.matches(
|
|
607
720
|
["li", "p:not(:first-child)", "pre", "blockquote", "h1, h2, h3, h4, h5, h6"].join(", ")
|
|
608
721
|
)
|
|
609
722
|
);
|
|
610
723
|
}
|
|
611
|
-
function
|
|
612
|
-
const
|
|
613
|
-
return
|
|
614
|
-
left:
|
|
615
|
-
top:
|
|
724
|
+
function nodePosAtDOM(node, view, options) {
|
|
725
|
+
const boundingRect = node.getBoundingClientRect();
|
|
726
|
+
return view.posAtCoords({
|
|
727
|
+
left: boundingRect.left + 50 + options.dragHandleWidth,
|
|
728
|
+
top: boundingRect.top + 1
|
|
616
729
|
})?.inside;
|
|
617
730
|
}
|
|
618
|
-
function
|
|
619
|
-
function
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
731
|
+
function DragHandle(options) {
|
|
732
|
+
function handleDragStart(event, view) {
|
|
733
|
+
view.focus();
|
|
734
|
+
if (!event.dataTransfer) return;
|
|
735
|
+
const node = nodeDOMAtCoords({
|
|
736
|
+
x: event.clientX + 50 + options.dragHandleWidth,
|
|
737
|
+
y: event.clientY
|
|
624
738
|
});
|
|
625
|
-
if (!(
|
|
626
|
-
const
|
|
627
|
-
if (
|
|
628
|
-
|
|
629
|
-
const
|
|
630
|
-
|
|
739
|
+
if (!(node instanceof Element)) return;
|
|
740
|
+
const nodePos = nodePosAtDOM(node, view, options);
|
|
741
|
+
if (nodePos == null || nodePos < 0) return;
|
|
742
|
+
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)));
|
|
743
|
+
const slice = view.state.selection.content();
|
|
744
|
+
const {
|
|
745
|
+
dom,
|
|
746
|
+
text
|
|
747
|
+
} = __serializeForClipboard(view, slice);
|
|
748
|
+
event.dataTransfer.clearData();
|
|
749
|
+
event.dataTransfer.setData("text/html", dom.innerHTML);
|
|
750
|
+
event.dataTransfer.setData("text/plain", text);
|
|
751
|
+
event.dataTransfer.effectAllowed = "copyMove";
|
|
752
|
+
event.dataTransfer.setDragImage(node, 0, 0);
|
|
753
|
+
view.dragging = {
|
|
754
|
+
slice,
|
|
755
|
+
move: event.ctrlKey
|
|
756
|
+
};
|
|
631
757
|
}
|
|
632
|
-
function
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
758
|
+
function handleClick(event, view) {
|
|
759
|
+
view.focus();
|
|
760
|
+
view.dom.classList.remove("dragging");
|
|
761
|
+
const node = nodeDOMAtCoords({
|
|
762
|
+
x: event.clientX + 50 + options.dragHandleWidth,
|
|
763
|
+
y: event.clientY
|
|
637
764
|
});
|
|
638
|
-
if (!(
|
|
639
|
-
const
|
|
640
|
-
|
|
765
|
+
if (!(node instanceof Element)) return;
|
|
766
|
+
const nodePos = nodePosAtDOM(node, view, options);
|
|
767
|
+
if (!nodePos) return;
|
|
768
|
+
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)));
|
|
641
769
|
}
|
|
642
|
-
let
|
|
643
|
-
function
|
|
644
|
-
|
|
770
|
+
let dragHandleElement = null;
|
|
771
|
+
function hideDragHandle() {
|
|
772
|
+
if (dragHandleElement) {
|
|
773
|
+
dragHandleElement.classList.add("hide");
|
|
774
|
+
}
|
|
645
775
|
}
|
|
646
|
-
function
|
|
647
|
-
|
|
776
|
+
function showDragHandle() {
|
|
777
|
+
if (dragHandleElement) {
|
|
778
|
+
dragHandleElement.classList.remove("hide");
|
|
779
|
+
}
|
|
648
780
|
}
|
|
649
|
-
return new
|
|
650
|
-
view: (
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
|
|
781
|
+
return new Plugin$1({
|
|
782
|
+
view: (view) => {
|
|
783
|
+
dragHandleElement = document.createElement("div");
|
|
784
|
+
dragHandleElement.draggable = true;
|
|
785
|
+
dragHandleElement.dataset.dragHandle = "";
|
|
786
|
+
dragHandleElement.classList.add("drag-handle");
|
|
787
|
+
dragHandleElement.addEventListener("dragstart", (e) => {
|
|
788
|
+
handleDragStart(e, view);
|
|
789
|
+
});
|
|
790
|
+
dragHandleElement.addEventListener("click", (e) => {
|
|
791
|
+
handleClick(e, view);
|
|
792
|
+
});
|
|
793
|
+
hideDragHandle();
|
|
794
|
+
view?.dom?.parentElement?.appendChild(dragHandleElement);
|
|
795
|
+
return {
|
|
796
|
+
destroy: () => {
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
},
|
|
659
800
|
props: {
|
|
660
801
|
handleDOMEvents: {
|
|
661
|
-
mousemove: (
|
|
662
|
-
if (!
|
|
802
|
+
mousemove: (view, event) => {
|
|
803
|
+
if (!view.editable) {
|
|
663
804
|
return;
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
805
|
+
}
|
|
806
|
+
const node = nodeDOMAtCoords({
|
|
807
|
+
x: event.clientX + 50 + options.dragHandleWidth,
|
|
808
|
+
y: event.clientY
|
|
667
809
|
});
|
|
668
|
-
if (!(
|
|
669
|
-
|
|
810
|
+
if (!(node instanceof Element)) {
|
|
811
|
+
hideDragHandle();
|
|
670
812
|
return;
|
|
671
813
|
}
|
|
672
|
-
const
|
|
673
|
-
|
|
814
|
+
const compStyle = window.getComputedStyle(node);
|
|
815
|
+
const lineHeight = parseInt(compStyle.lineHeight, 10);
|
|
816
|
+
const paddingTop = parseInt(compStyle.paddingTop, 10);
|
|
817
|
+
const rect = absoluteRect(node);
|
|
818
|
+
rect.top += (lineHeight - 24) / 2;
|
|
819
|
+
rect.top += paddingTop;
|
|
820
|
+
if (node.matches("ul:not([data-type=taskList]) li, ol li")) {
|
|
821
|
+
rect.left -= options.dragHandleWidth;
|
|
822
|
+
}
|
|
823
|
+
rect.width = options.dragHandleWidth;
|
|
824
|
+
if (!dragHandleElement) return;
|
|
825
|
+
dragHandleElement.style.left = `${rect.left - rect.width}px`;
|
|
826
|
+
dragHandleElement.style.top = `${rect.top}px`;
|
|
827
|
+
showDragHandle();
|
|
674
828
|
},
|
|
675
829
|
keydown: () => {
|
|
676
|
-
|
|
830
|
+
hideDragHandle();
|
|
677
831
|
},
|
|
678
832
|
mousewheel: () => {
|
|
679
|
-
|
|
833
|
+
hideDragHandle();
|
|
680
834
|
},
|
|
681
835
|
// dragging class is used for CSS
|
|
682
|
-
dragstart: (
|
|
683
|
-
|
|
836
|
+
dragstart: (view) => {
|
|
837
|
+
view.dom.classList.add("dragging");
|
|
684
838
|
},
|
|
685
|
-
drop: (
|
|
686
|
-
|
|
839
|
+
drop: (view) => {
|
|
840
|
+
view.dom.classList.remove("dragging");
|
|
687
841
|
},
|
|
688
|
-
dragend: (
|
|
689
|
-
|
|
842
|
+
dragend: (view) => {
|
|
843
|
+
view.dom.classList.remove("dragging");
|
|
690
844
|
}
|
|
691
845
|
}
|
|
692
846
|
}
|
|
693
847
|
});
|
|
694
848
|
}
|
|
695
|
-
const
|
|
849
|
+
const DragAndDrop = Extension.create({
|
|
696
850
|
name: "dragAndDrop",
|
|
697
851
|
addProseMirrorPlugins() {
|
|
698
852
|
return [
|
|
699
|
-
|
|
853
|
+
DragHandle({
|
|
700
854
|
dragHandleWidth: 24
|
|
701
855
|
})
|
|
702
856
|
];
|
|
703
857
|
}
|
|
704
|
-
})
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
858
|
+
});
|
|
859
|
+
const CommandPluginKey = new PluginKey$1("slash-command");
|
|
860
|
+
const SlashCommand = Node.create({
|
|
861
|
+
name: "command",
|
|
862
|
+
addOptions() {
|
|
863
|
+
return {
|
|
864
|
+
HTMLAttributes: {},
|
|
865
|
+
renderText({
|
|
866
|
+
options,
|
|
867
|
+
node
|
|
868
|
+
}) {
|
|
869
|
+
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`;
|
|
870
|
+
},
|
|
871
|
+
deleteTriggerWithBackspace: false,
|
|
872
|
+
renderHTML({
|
|
873
|
+
options,
|
|
874
|
+
node
|
|
875
|
+
}) {
|
|
876
|
+
return [
|
|
877
|
+
"span",
|
|
878
|
+
mergeAttributes(this.HTMLAttributes, options.HTMLAttributes),
|
|
879
|
+
`${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
|
|
880
|
+
];
|
|
881
|
+
},
|
|
882
|
+
suggestion: {
|
|
883
|
+
char: "/",
|
|
884
|
+
pluginKey: CommandPluginKey,
|
|
885
|
+
command: ({
|
|
886
|
+
editor,
|
|
887
|
+
range,
|
|
888
|
+
props
|
|
889
|
+
}) => {
|
|
890
|
+
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
|
|
891
|
+
const overrideSpace = nodeAfter?.text?.startsWith(" ");
|
|
892
|
+
if (overrideSpace) {
|
|
893
|
+
range.to += 1;
|
|
894
|
+
}
|
|
895
|
+
editor.chain().focus().insertContentAt(range, [
|
|
896
|
+
{
|
|
897
|
+
type: this.name,
|
|
898
|
+
attrs: props
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
type: "text",
|
|
902
|
+
text: " "
|
|
903
|
+
}
|
|
904
|
+
]).run();
|
|
905
|
+
window.getSelection()?.collapseToEnd();
|
|
906
|
+
},
|
|
907
|
+
allow: ({
|
|
908
|
+
state,
|
|
909
|
+
range
|
|
910
|
+
}) => {
|
|
911
|
+
const $from = state.doc.resolve(range.from);
|
|
912
|
+
const type = state.schema.nodes[this.name];
|
|
913
|
+
const allow = !!$from.parent.type.contentMatch.matchType(type);
|
|
914
|
+
return allow;
|
|
915
|
+
}
|
|
755
916
|
}
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
917
|
+
};
|
|
918
|
+
},
|
|
919
|
+
group: "inline",
|
|
920
|
+
inline: true,
|
|
921
|
+
selectable: false,
|
|
922
|
+
atom: true,
|
|
923
|
+
addAttributes() {
|
|
924
|
+
return {
|
|
925
|
+
id: {
|
|
926
|
+
default: null,
|
|
927
|
+
parseHTML: (element) => element.getAttribute("data-id"),
|
|
928
|
+
renderHTML: (attributes) => {
|
|
929
|
+
if (!attributes.id) {
|
|
930
|
+
return {};
|
|
931
|
+
}
|
|
932
|
+
return {
|
|
933
|
+
"data-id": attributes.id
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
label: {
|
|
938
|
+
default: null,
|
|
939
|
+
parseHTML: (element) => element.getAttribute("data-label"),
|
|
940
|
+
renderHTML: (attributes) => {
|
|
941
|
+
if (!attributes.label) {
|
|
942
|
+
return {};
|
|
943
|
+
}
|
|
944
|
+
return {
|
|
945
|
+
"data-label": attributes.label
|
|
946
|
+
};
|
|
947
|
+
}
|
|
764
948
|
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
command: ({ editor: l, range: m }) => {
|
|
772
|
-
l.chain().focus().deleteRange(m).setNode("heading", { level: 1 }).run();
|
|
949
|
+
};
|
|
950
|
+
},
|
|
951
|
+
parseHTML() {
|
|
952
|
+
return [
|
|
953
|
+
{
|
|
954
|
+
tag: `span[data-type="${this.name}"]`
|
|
773
955
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
956
|
+
];
|
|
957
|
+
},
|
|
958
|
+
renderHTML({
|
|
959
|
+
node,
|
|
960
|
+
HTMLAttributes
|
|
961
|
+
}) {
|
|
962
|
+
if (this.options.renderLabel !== void 0) {
|
|
963
|
+
console.warn("renderLabel is deprecated use renderText and renderHTML instead");
|
|
964
|
+
return [
|
|
965
|
+
"span",
|
|
966
|
+
mergeAttributes({ "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes),
|
|
967
|
+
this.options.renderLabel({
|
|
968
|
+
options: this.options,
|
|
969
|
+
node
|
|
970
|
+
})
|
|
971
|
+
];
|
|
972
|
+
}
|
|
973
|
+
const mergedOptions = { ...this.options };
|
|
974
|
+
mergedOptions.HTMLAttributes = mergeAttributes({ "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes);
|
|
975
|
+
const html = this.options.renderHTML({
|
|
976
|
+
options: mergedOptions,
|
|
977
|
+
node
|
|
978
|
+
});
|
|
979
|
+
if (typeof html === "string") {
|
|
980
|
+
return [
|
|
981
|
+
"span",
|
|
982
|
+
mergeAttributes({ "data-type": this.name }, this.options.HTMLAttributes, HTMLAttributes),
|
|
983
|
+
html
|
|
984
|
+
];
|
|
985
|
+
}
|
|
986
|
+
return html;
|
|
987
|
+
},
|
|
988
|
+
renderText({ node }) {
|
|
989
|
+
return this.options.renderText({
|
|
990
|
+
options: this.options,
|
|
991
|
+
node
|
|
992
|
+
});
|
|
993
|
+
},
|
|
994
|
+
addKeyboardShortcuts() {
|
|
995
|
+
return {
|
|
996
|
+
Backspace: () => this.editor.commands.command(({
|
|
997
|
+
tr,
|
|
998
|
+
state
|
|
999
|
+
}) => {
|
|
1000
|
+
let isCommand = false;
|
|
1001
|
+
const { selection } = state;
|
|
1002
|
+
const {
|
|
1003
|
+
empty,
|
|
1004
|
+
anchor
|
|
1005
|
+
} = selection;
|
|
1006
|
+
if (!empty) {
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1009
|
+
state.doc.nodesBetween(anchor - 1, anchor, (node, pos) => {
|
|
1010
|
+
if (node.type.name === this.name) {
|
|
1011
|
+
isCommand = true;
|
|
1012
|
+
tr.insertText(
|
|
1013
|
+
this.options.deleteTriggerWithBackspace ? "" : this.options.suggestion.char || "",
|
|
1014
|
+
pos,
|
|
1015
|
+
pos + node.nodeSize
|
|
1016
|
+
);
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
return true;
|
|
1020
|
+
});
|
|
1021
|
+
return isCommand;
|
|
1022
|
+
})
|
|
1023
|
+
};
|
|
1024
|
+
},
|
|
1025
|
+
addProseMirrorPlugins() {
|
|
1026
|
+
return [
|
|
1027
|
+
Suggestion({
|
|
1028
|
+
editor: this.editor,
|
|
1029
|
+
...this.options.suggestion
|
|
1030
|
+
})
|
|
1031
|
+
];
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
const suggestion = (ref, {
|
|
1035
|
+
upload,
|
|
1036
|
+
onDisabledAutocompleteClick,
|
|
1037
|
+
aiController
|
|
1038
|
+
}) => ({
|
|
1039
|
+
items: ({ query }) => {
|
|
1040
|
+
const availableSuggestionItems = [...suggestionItems];
|
|
1041
|
+
if (!onDisabledAutocompleteClick && aiController) {
|
|
1042
|
+
availableSuggestionItems.push(autocompleteSuggestionItem);
|
|
1043
|
+
}
|
|
1044
|
+
if (onDisabledAutocompleteClick) {
|
|
1045
|
+
availableSuggestionItems.push({
|
|
1046
|
+
title: "Autocomplete",
|
|
1047
|
+
description: "Add text based on the context.",
|
|
1048
|
+
searchTerms: ["ai"],
|
|
1049
|
+
icon: /* @__PURE__ */ jsx(AutoAwesomeIcon, { size: 18 }),
|
|
1050
|
+
command: onDisabledAutocompleteClick
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
return availableSuggestionItems.filter((item) => {
|
|
1054
|
+
const inTitle = item.title.toLowerCase().startsWith(query.toLowerCase());
|
|
1055
|
+
if (inTitle) return inTitle;
|
|
1056
|
+
const inSearchTerms = item.searchTerms?.some((term) => term.toLowerCase().startsWith(query.toLowerCase()));
|
|
1057
|
+
return inSearchTerms;
|
|
1058
|
+
});
|
|
1059
|
+
},
|
|
1060
|
+
render: () => {
|
|
1061
|
+
let component;
|
|
1062
|
+
let popup;
|
|
1063
|
+
return {
|
|
1064
|
+
onStart: (props) => {
|
|
1065
|
+
component = new ReactRenderer(CommandList, {
|
|
1066
|
+
props: {
|
|
1067
|
+
...props,
|
|
1068
|
+
upload,
|
|
1069
|
+
aiController
|
|
1070
|
+
},
|
|
1071
|
+
editor: props.editor
|
|
1072
|
+
});
|
|
1073
|
+
if (!props.clientRect) {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
popup = tippy("body", {
|
|
1077
|
+
getReferenceClientRect: props.clientRect,
|
|
1078
|
+
appendTo: ref?.current,
|
|
1079
|
+
content: component.element,
|
|
1080
|
+
showOnCreate: true,
|
|
1081
|
+
interactive: true,
|
|
1082
|
+
trigger: "manual",
|
|
1083
|
+
placement: "bottom-start"
|
|
1084
|
+
});
|
|
1085
|
+
},
|
|
1086
|
+
onUpdate(props) {
|
|
1087
|
+
component.updateProps(props);
|
|
1088
|
+
if (!props.clientRect) {
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
popup[0].setProps({
|
|
1092
|
+
getReferenceClientRect: props.clientRect
|
|
1093
|
+
});
|
|
1094
|
+
},
|
|
1095
|
+
onKeyDown(props) {
|
|
1096
|
+
if (props.event.key === "Escape") {
|
|
1097
|
+
popup[0].hide();
|
|
1098
|
+
props.event.preventDefault();
|
|
1099
|
+
return true;
|
|
1100
|
+
}
|
|
1101
|
+
return component.ref?.onKeyDown(props);
|
|
1102
|
+
},
|
|
1103
|
+
onExit() {
|
|
1104
|
+
if (popup && popup[0])
|
|
1105
|
+
popup[0].destroy();
|
|
1106
|
+
component?.destroy();
|
|
782
1107
|
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
const CommandList = forwardRef((props, ref) => {
|
|
1112
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
1113
|
+
const { editor } = useCurrentEditor();
|
|
1114
|
+
const selectItem = (item) => {
|
|
1115
|
+
if (!editor) return;
|
|
1116
|
+
item?.command?.({
|
|
1117
|
+
editor,
|
|
1118
|
+
range: props.range,
|
|
1119
|
+
upload: props.upload,
|
|
1120
|
+
aiController: props.aiController
|
|
1121
|
+
});
|
|
1122
|
+
};
|
|
1123
|
+
const upHandler = () => {
|
|
1124
|
+
setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length);
|
|
1125
|
+
};
|
|
1126
|
+
const downHandler = () => {
|
|
1127
|
+
setSelectedIndex((selectedIndex + 1) % props.items.length);
|
|
1128
|
+
};
|
|
1129
|
+
const enterHandler = () => {
|
|
1130
|
+
const item = props.items[selectedIndex];
|
|
1131
|
+
selectItem(item);
|
|
1132
|
+
};
|
|
1133
|
+
useEffect(() => setSelectedIndex(0), [props.items]);
|
|
1134
|
+
useImperativeHandle(ref, () => ({
|
|
1135
|
+
onKeyDown: ({ event }) => {
|
|
1136
|
+
if (event.key === "ArrowUp") {
|
|
1137
|
+
upHandler();
|
|
1138
|
+
return true;
|
|
791
1139
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
description: "Create a simple bullet list.",
|
|
796
|
-
searchTerms: ["unordered", "point"],
|
|
797
|
-
icon: /* @__PURE__ */ s(ee, { size: 18 }),
|
|
798
|
-
command: ({ editor: l, range: m }) => {
|
|
799
|
-
l.chain().focus().deleteRange(m).toggleBulletList().run();
|
|
1140
|
+
if (event.key === "ArrowDown") {
|
|
1141
|
+
downHandler();
|
|
1142
|
+
return true;
|
|
800
1143
|
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
description: "Create a list with numbering.",
|
|
805
|
-
searchTerms: ["ordered"],
|
|
806
|
-
icon: /* @__PURE__ */ s(te, { size: 18 }),
|
|
807
|
-
command: ({ editor: l, range: m }) => {
|
|
808
|
-
l.chain().focus().deleteRange(m).toggleOrderedList().run();
|
|
1144
|
+
if (event.key === "Enter") {
|
|
1145
|
+
enterHandler();
|
|
1146
|
+
return true;
|
|
809
1147
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
command: ({ editor: l, range: m }) => l.chain().focus().deleteRange(m).toggleCodeBlock().run()
|
|
824
|
-
},
|
|
1148
|
+
return false;
|
|
1149
|
+
}
|
|
1150
|
+
}));
|
|
1151
|
+
const itemRefs = useRef([]);
|
|
1152
|
+
useEffect(() => {
|
|
1153
|
+
if (itemRefs.current[selectedIndex]) {
|
|
1154
|
+
itemRefs.current[selectedIndex].scrollIntoView({
|
|
1155
|
+
block: "nearest"
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
}, [selectedIndex]);
|
|
1159
|
+
return /* @__PURE__ */ jsx(
|
|
1160
|
+
"div",
|
|
825
1161
|
{
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1162
|
+
className: cls("text-gray-900 dark:text-white z-50 max-h-[280px] h-auto w-72 overflow-y-auto rounded-md border bg-white dark:bg-gray-900 px-1 py-2 shadow transition-all", defaultBorderMixin),
|
|
1163
|
+
children: props.items.length ? props.items.map((item, index) => /* @__PURE__ */ jsxs(
|
|
1164
|
+
"button",
|
|
1165
|
+
{
|
|
1166
|
+
value: item.title,
|
|
1167
|
+
ref: (el) => {
|
|
1168
|
+
if (!el) return;
|
|
1169
|
+
return itemRefs.current[index] = el;
|
|
1170
|
+
},
|
|
1171
|
+
onClick: () => selectItem(item),
|
|
1172
|
+
tabIndex: index === selectedIndex ? 0 : -1,
|
|
1173
|
+
"aria-selected": index === selectedIndex,
|
|
1174
|
+
className: cls(
|
|
1175
|
+
"flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm hover:bg-blue-50 hover:dark:bg-gray-700 aria-selected:bg-blue-50 aria-selected:dark:bg-gray-700",
|
|
1176
|
+
index === selectedIndex ? "bg-blue-100 dark:bg-slate-950" : ""
|
|
1177
|
+
),
|
|
1178
|
+
children: [
|
|
1179
|
+
/* @__PURE__ */ jsx(
|
|
1180
|
+
"div",
|
|
1181
|
+
{
|
|
1182
|
+
className: cls("flex h-10 w-10 items-center justify-center rounded-md border bg-white dark:bg-gray-900", defaultBorderMixin),
|
|
1183
|
+
children: item.icon
|
|
1184
|
+
}
|
|
1185
|
+
),
|
|
1186
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1187
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: item.title }),
|
|
1188
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-700 dark:text-slate-300", children: item.description })
|
|
1189
|
+
] })
|
|
1190
|
+
]
|
|
1191
|
+
},
|
|
1192
|
+
item.title
|
|
1193
|
+
)) : /* @__PURE__ */ jsx("div", { className: "item", children: "No result" })
|
|
1194
|
+
}
|
|
1195
|
+
);
|
|
1196
|
+
});
|
|
1197
|
+
const autocompleteSuggestionItem = {
|
|
1198
|
+
title: "Autocomplete",
|
|
1199
|
+
description: "Add text based on the context.",
|
|
1200
|
+
searchTerms: ["ai"],
|
|
1201
|
+
icon: /* @__PURE__ */ jsx(AutoAwesomeIcon, { size: 18 }),
|
|
1202
|
+
command: async ({
|
|
1203
|
+
editor,
|
|
1204
|
+
range,
|
|
1205
|
+
aiController
|
|
1206
|
+
}) => {
|
|
1207
|
+
if (!aiController)
|
|
1208
|
+
throw Error("No AiController");
|
|
1209
|
+
editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
|
|
1210
|
+
const { state } = editor;
|
|
1211
|
+
const {
|
|
1212
|
+
from,
|
|
1213
|
+
to
|
|
1214
|
+
} = state.selection;
|
|
1215
|
+
const textBeforeCursor = state.doc.textBetween(0, from, "\n");
|
|
1216
|
+
const textAfterCursor = state.doc.textBetween(to, state.doc.content.size, "\n");
|
|
1217
|
+
let buffer = "";
|
|
1218
|
+
const result = await aiController.autocomplete(textBeforeCursor, textAfterCursor, (delta) => {
|
|
1219
|
+
buffer += delta;
|
|
1220
|
+
if (delta.length !== 0) {
|
|
1221
|
+
editor.chain().focus().toggleLoadingDecoration(buffer).run();
|
|
1222
|
+
}
|
|
1223
|
+
});
|
|
1224
|
+
editor.chain().focus().insertContent(result, {
|
|
1225
|
+
applyInputRules: false,
|
|
1226
|
+
applyPasteRules: false,
|
|
1227
|
+
parseOptions: {
|
|
1228
|
+
preserveWhitespace: false
|
|
1229
|
+
}
|
|
1230
|
+
}).run();
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
const suggestionItems = [
|
|
1234
|
+
{
|
|
1235
|
+
title: "Text",
|
|
1236
|
+
description: "Just start typing with plain text.",
|
|
1237
|
+
searchTerms: ["p", "paragraph"],
|
|
1238
|
+
icon: /* @__PURE__ */ jsx(TextFieldsIcon, { size: 18 }),
|
|
1239
|
+
command: ({
|
|
1240
|
+
editor,
|
|
1241
|
+
range
|
|
1242
|
+
}) => {
|
|
1243
|
+
editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
|
|
1244
|
+
}
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
title: "To-do List",
|
|
1248
|
+
description: "Track tasks with a to-do list.",
|
|
1249
|
+
searchTerms: ["todo", "task", "list", "check", "checkbox"],
|
|
1250
|
+
icon: /* @__PURE__ */ jsx(CheckBoxIcon, { size: 18 }),
|
|
1251
|
+
command: ({
|
|
1252
|
+
editor,
|
|
1253
|
+
range
|
|
1254
|
+
}) => {
|
|
1255
|
+
editor.chain().focus().deleteRange(range).toggleTaskList().run();
|
|
1256
|
+
}
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
title: "Heading 1",
|
|
1260
|
+
description: "Big section heading.",
|
|
1261
|
+
searchTerms: ["title", "big", "large"],
|
|
1262
|
+
icon: /* @__PURE__ */ jsx(LooksOneIcon, { size: 18 }),
|
|
1263
|
+
command: ({
|
|
1264
|
+
editor,
|
|
1265
|
+
range
|
|
1266
|
+
}) => {
|
|
1267
|
+
editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
|
|
1268
|
+
}
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
title: "Heading 2",
|
|
1272
|
+
description: "Medium section heading.",
|
|
1273
|
+
searchTerms: ["subtitle", "medium"],
|
|
1274
|
+
icon: /* @__PURE__ */ jsx(LooksTwoIcon, { size: 18 }),
|
|
1275
|
+
command: ({
|
|
1276
|
+
editor,
|
|
1277
|
+
range
|
|
1278
|
+
}) => {
|
|
1279
|
+
editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
|
|
1280
|
+
}
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
title: "Heading 3",
|
|
1284
|
+
description: "Small section heading.",
|
|
1285
|
+
searchTerms: ["subtitle", "small"],
|
|
1286
|
+
icon: /* @__PURE__ */ jsx(Looks3Icon, { size: 18 }),
|
|
1287
|
+
command: ({
|
|
1288
|
+
editor,
|
|
1289
|
+
range
|
|
1290
|
+
}) => {
|
|
1291
|
+
editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
|
|
1292
|
+
}
|
|
1293
|
+
},
|
|
1294
|
+
{
|
|
1295
|
+
title: "Bullet List",
|
|
1296
|
+
description: "Create a simple bullet list.",
|
|
1297
|
+
searchTerms: ["unordered", "point"],
|
|
1298
|
+
icon: /* @__PURE__ */ jsx(FormatListBulletedIcon, { size: 18 }),
|
|
1299
|
+
command: ({
|
|
1300
|
+
editor,
|
|
1301
|
+
range
|
|
1302
|
+
}) => {
|
|
1303
|
+
editor.chain().focus().deleteRange(range).toggleBulletList().run();
|
|
1304
|
+
}
|
|
1305
|
+
},
|
|
1306
|
+
{
|
|
1307
|
+
title: "Numbered List",
|
|
1308
|
+
description: "Create a list with numbering.",
|
|
1309
|
+
searchTerms: ["ordered"],
|
|
1310
|
+
icon: /* @__PURE__ */ jsx(FormatListNumberedIcon, { size: 18 }),
|
|
1311
|
+
command: ({
|
|
1312
|
+
editor,
|
|
1313
|
+
range
|
|
1314
|
+
}) => {
|
|
1315
|
+
editor.chain().focus().deleteRange(range).toggleOrderedList().run();
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
title: "Quote",
|
|
1320
|
+
description: "Capture a quote.",
|
|
1321
|
+
searchTerms: ["blockquote"],
|
|
1322
|
+
icon: /* @__PURE__ */ jsx(FormatQuoteIcon, { size: 18 }),
|
|
1323
|
+
command: ({
|
|
1324
|
+
editor,
|
|
1325
|
+
range
|
|
1326
|
+
}) => editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run()
|
|
1327
|
+
},
|
|
1328
|
+
{
|
|
1329
|
+
title: "Code",
|
|
1330
|
+
description: "Capture a code snippet.",
|
|
1331
|
+
searchTerms: ["codeblock"],
|
|
1332
|
+
icon: /* @__PURE__ */ jsx(CodeIcon, { size: 18 }),
|
|
1333
|
+
command: ({
|
|
1334
|
+
editor,
|
|
1335
|
+
range
|
|
1336
|
+
}) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run()
|
|
1337
|
+
},
|
|
1338
|
+
{
|
|
1339
|
+
title: "Image",
|
|
1340
|
+
description: "Upload an image from your computer.",
|
|
1341
|
+
searchTerms: ["photo", "picture", "media", "upload", "file"],
|
|
1342
|
+
icon: /* @__PURE__ */ jsx(ImageIcon, { size: 18 }),
|
|
1343
|
+
command: ({
|
|
1344
|
+
editor,
|
|
1345
|
+
range,
|
|
1346
|
+
upload
|
|
1347
|
+
}) => {
|
|
1348
|
+
editor.chain().focus().deleteRange(range).run();
|
|
1349
|
+
const input = document.createElement("input");
|
|
1350
|
+
input.type = "file";
|
|
1351
|
+
input.accept = "image/*";
|
|
1352
|
+
input.onchange = async () => {
|
|
1353
|
+
if (input.files?.length) {
|
|
1354
|
+
const file = input.files[0];
|
|
1355
|
+
if (!file) return;
|
|
1356
|
+
const pos = editor.view.state.selection.from;
|
|
1357
|
+
const fileList = input.files;
|
|
1358
|
+
const files = Array.from(fileList);
|
|
1359
|
+
const images = files.filter((file2) => /image/i.test(file2.type));
|
|
1360
|
+
if (images.length === 0) {
|
|
1361
|
+
console.log("No images found in uploaded files");
|
|
1362
|
+
return false;
|
|
1363
|
+
}
|
|
1364
|
+
const view = editor.view;
|
|
1365
|
+
images.forEach((image) => {
|
|
1366
|
+
const reader = new FileReader();
|
|
1367
|
+
reader.onload = async (readerEvent) => {
|
|
1368
|
+
await onFileRead(view, readerEvent, pos, upload, image);
|
|
1369
|
+
};
|
|
1370
|
+
reader.readAsDataURL(image);
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
return true;
|
|
1374
|
+
};
|
|
1375
|
+
input.click();
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
];
|
|
1379
|
+
function buildDecorationSet(highlight, doc) {
|
|
1380
|
+
const decorations = [];
|
|
1381
|
+
if (highlight) {
|
|
1382
|
+
decorations.push(
|
|
1383
|
+
Decoration$1.inline(highlight.from, highlight.to, {
|
|
1384
|
+
class: "dark:bg-slate-700 bg-slate-300"
|
|
1385
|
+
})
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
const decorationSet = DecorationSet$1.create(doc, decorations);
|
|
1389
|
+
return decorationSet;
|
|
1390
|
+
}
|
|
1391
|
+
const HighlightDecorationExtension = (initialHighlight) => Extension.create({
|
|
1392
|
+
name: "highlightDecoration",
|
|
1393
|
+
addOptions() {
|
|
1394
|
+
return {
|
|
1395
|
+
pluginKey: new PluginKey$1("highlightDecoration"),
|
|
1396
|
+
highlight: initialHighlight
|
|
1397
|
+
};
|
|
1398
|
+
},
|
|
1399
|
+
addProseMirrorPlugins() {
|
|
1400
|
+
const pluginKey = this.options.pluginKey;
|
|
1401
|
+
return [
|
|
1402
|
+
new Plugin$1({
|
|
1403
|
+
key: pluginKey,
|
|
1404
|
+
state: {
|
|
1405
|
+
init: (_, { doc }) => {
|
|
1406
|
+
const highlight = this.options.highlight;
|
|
1407
|
+
const decorationSet = highlight && doc ? buildDecorationSet(highlight, doc) : DecorationSet$1.empty;
|
|
1408
|
+
return {
|
|
1409
|
+
decorationSet,
|
|
1410
|
+
highlight
|
|
1411
|
+
};
|
|
1412
|
+
},
|
|
1413
|
+
apply(transaction, oldState) {
|
|
1414
|
+
const action = transaction.getMeta(pluginKey);
|
|
1415
|
+
const highlight = action?.range;
|
|
1416
|
+
if (action?.type === "highlightDecoration") {
|
|
1417
|
+
const doc = transaction.doc;
|
|
1418
|
+
const { remove } = action;
|
|
1419
|
+
if (remove) {
|
|
1420
|
+
return {
|
|
1421
|
+
decorationSet: DecorationSet$1.empty
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
const decorationSet = buildDecorationSet(highlight, doc);
|
|
1425
|
+
return {
|
|
1426
|
+
decorationSet,
|
|
1427
|
+
highlight
|
|
1428
|
+
};
|
|
1429
|
+
} else {
|
|
1430
|
+
return oldState;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
},
|
|
1434
|
+
props: {
|
|
1435
|
+
decorations(state) {
|
|
1436
|
+
const autocompleteState = this.getState(state);
|
|
1437
|
+
if (autocompleteState?.decorationSet) {
|
|
1438
|
+
return autocompleteState.decorationSet;
|
|
1439
|
+
} else {
|
|
1440
|
+
return DecorationSet$1.empty;
|
|
1441
|
+
}
|
|
837
1442
|
}
|
|
838
|
-
}
|
|
1443
|
+
}
|
|
1444
|
+
})
|
|
1445
|
+
];
|
|
1446
|
+
},
|
|
1447
|
+
addCommands() {
|
|
1448
|
+
return {
|
|
1449
|
+
toggleAutocompleteHighlight: (range) => ({
|
|
1450
|
+
state,
|
|
1451
|
+
dispatch
|
|
1452
|
+
}) => {
|
|
1453
|
+
const { selection } = state;
|
|
1454
|
+
const pos = selection.from;
|
|
1455
|
+
if (!dispatch) return false;
|
|
1456
|
+
const pluginKey = this.options.pluginKey;
|
|
1457
|
+
const tr = state.tr.setMeta(pluginKey, {
|
|
1458
|
+
pos,
|
|
1459
|
+
type: "highlightDecoration",
|
|
1460
|
+
remove: false,
|
|
1461
|
+
range
|
|
1462
|
+
});
|
|
1463
|
+
dispatch(tr);
|
|
1464
|
+
return true;
|
|
1465
|
+
},
|
|
1466
|
+
removeAutocompleteHighlight: () => ({
|
|
1467
|
+
state,
|
|
1468
|
+
dispatch
|
|
1469
|
+
}) => {
|
|
1470
|
+
if (!dispatch) return false;
|
|
1471
|
+
const pluginKey = this.options.pluginKey;
|
|
1472
|
+
const tr = state.tr.setMeta(pluginKey, {
|
|
1473
|
+
pos: 0,
|
|
1474
|
+
// We can pass any position as it will remove the entire decoration set
|
|
1475
|
+
type: "highlightDecoration",
|
|
1476
|
+
remove: true
|
|
1477
|
+
});
|
|
1478
|
+
dispatch(tr);
|
|
1479
|
+
return true;
|
|
1480
|
+
}
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1484
|
+
const CustomDocument = Document.extend({
|
|
1485
|
+
// content: 'heading block*',
|
|
1486
|
+
});
|
|
1487
|
+
const proseClasses = {
|
|
1488
|
+
"sm": "prose-sm",
|
|
1489
|
+
"base": "prose-base",
|
|
1490
|
+
"lg": "prose-lg"
|
|
1491
|
+
};
|
|
1492
|
+
const FireCMSEditor = ({
|
|
1493
|
+
content,
|
|
1494
|
+
onJsonContentChange,
|
|
1495
|
+
onHtmlContentChange,
|
|
1496
|
+
onMarkdownContentChange,
|
|
1497
|
+
version,
|
|
1498
|
+
textSize = "base",
|
|
1499
|
+
highlight,
|
|
1500
|
+
handleImageUpload,
|
|
1501
|
+
aiController,
|
|
1502
|
+
onDisabledAutocompleteClick
|
|
1503
|
+
}) => {
|
|
1504
|
+
const ref = React.useRef(null);
|
|
1505
|
+
const editorRef = React.useRef(null);
|
|
1506
|
+
const imagePlugin = createDropImagePlugin(handleImageUpload);
|
|
1507
|
+
const imageExtension = useMemo(() => createImageExtension(imagePlugin), []);
|
|
1508
|
+
const [openNode, setOpenNode] = useState(false);
|
|
1509
|
+
const [openLink, setOpenLink] = useState(false);
|
|
1510
|
+
useInjectStyles("Editor", cssStyles);
|
|
1511
|
+
const deferredHighlight = useDeferredValue(highlight);
|
|
1512
|
+
useEffect(() => {
|
|
1513
|
+
if (version === void 0) return;
|
|
1514
|
+
if (version > -1 && editorRef.current) {
|
|
1515
|
+
editorRef.current?.commands.setContent(content ?? "");
|
|
1516
|
+
}
|
|
1517
|
+
}, [version]);
|
|
1518
|
+
useEffect(() => {
|
|
1519
|
+
if (version === void 0) return;
|
|
1520
|
+
if (editorRef.current && version > 0) {
|
|
1521
|
+
const chain = editorRef.current.chain();
|
|
1522
|
+
if (deferredHighlight) {
|
|
1523
|
+
chain.focus().toggleAutocompleteHighlight(deferredHighlight).run();
|
|
1524
|
+
} else {
|
|
1525
|
+
chain.focus().removeAutocompleteHighlight().run();
|
|
839
1526
|
}
|
|
840
1527
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1528
|
+
}, [deferredHighlight?.from, deferredHighlight?.to]);
|
|
1529
|
+
const onEditorUpdate = (editor) => {
|
|
1530
|
+
editorRef.current = editor;
|
|
1531
|
+
if (onMarkdownContentChange) {
|
|
1532
|
+
const markdown = editorRef.current.storage.markdown.getMarkdown();
|
|
1533
|
+
onMarkdownContentChange?.(addLineBreakAfterImages(markdown));
|
|
845
1534
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
}),
|
|
853
|
-
fe.configure({
|
|
854
|
-
html: !1,
|
|
855
|
-
transformCopiedText: !0
|
|
856
|
-
}),
|
|
857
|
-
vt,
|
|
858
|
-
At,
|
|
859
|
-
kt,
|
|
860
|
-
gt,
|
|
861
|
-
pt,
|
|
862
|
-
// tiptapImage,
|
|
863
|
-
d,
|
|
864
|
-
// updatedImage,
|
|
865
|
-
ft,
|
|
866
|
-
ht,
|
|
867
|
-
bt,
|
|
868
|
-
i
|
|
869
|
-
], [p, h] = v(!1), [f, A] = v(!1);
|
|
870
|
-
Ze("Editor", Tt);
|
|
871
|
-
const E = L.useRef(null), [ie, ce] = v(null), [M, le] = v(null), [N, de] = v(null), ue = (l) => {
|
|
872
|
-
E.current = l, a && ce(l.storage.markdown.getMarkdown()), t && le(H(l.getJSON())), r && de(l.getHTML());
|
|
873
|
-
};
|
|
874
|
-
return R(ie, () => {
|
|
875
|
-
if (E.current) {
|
|
876
|
-
const l = E.current.storage.markdown.getMarkdown();
|
|
877
|
-
a?.(Lt(l));
|
|
1535
|
+
if (onJsonContentChange) {
|
|
1536
|
+
const jsonContent = removeClassesFromJson(editor.getJSON());
|
|
1537
|
+
onJsonContentChange(jsonContent);
|
|
1538
|
+
}
|
|
1539
|
+
if (onHtmlContentChange) {
|
|
1540
|
+
onHtmlContentChange?.(editor.getHTML());
|
|
878
1541
|
}
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1542
|
+
};
|
|
1543
|
+
const proseClass = proseClasses[textSize];
|
|
1544
|
+
const extensions = useMemo(() => [
|
|
1545
|
+
starterKit,
|
|
1546
|
+
CustomDocument,
|
|
1547
|
+
HighlightDecorationExtension(highlight),
|
|
1548
|
+
TextLoadingDecorationExtension,
|
|
1549
|
+
Underline,
|
|
1550
|
+
TextStyle,
|
|
1551
|
+
Color,
|
|
1552
|
+
BulletList,
|
|
1553
|
+
Highlight.configure({
|
|
1554
|
+
multicolor: true
|
|
1555
|
+
}),
|
|
1556
|
+
CustomKeymap,
|
|
1557
|
+
DragAndDrop,
|
|
1558
|
+
placeholder,
|
|
1559
|
+
tiptapLink,
|
|
1560
|
+
imageExtension,
|
|
1561
|
+
taskList,
|
|
1562
|
+
taskItem,
|
|
1563
|
+
markdownExtension,
|
|
1564
|
+
horizontalRule,
|
|
1565
|
+
SlashCommand.configure({
|
|
1566
|
+
HTMLAttributes: {
|
|
1567
|
+
class: "mention"
|
|
1568
|
+
},
|
|
1569
|
+
suggestion: suggestion(ref, {
|
|
1570
|
+
upload: handleImageUpload,
|
|
1571
|
+
aiController,
|
|
1572
|
+
onDisabledAutocompleteClick
|
|
1573
|
+
})
|
|
1574
|
+
})
|
|
1575
|
+
], []);
|
|
1576
|
+
return /* @__PURE__ */ jsx(
|
|
884
1577
|
"div",
|
|
885
1578
|
{
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1579
|
+
ref,
|
|
1580
|
+
className: "relative min-h-[300px] w-full",
|
|
1581
|
+
children: /* @__PURE__ */ jsx(
|
|
1582
|
+
EditorProvider,
|
|
889
1583
|
{
|
|
890
|
-
content:
|
|
891
|
-
extensions
|
|
1584
|
+
content: content ?? "",
|
|
1585
|
+
extensions,
|
|
892
1586
|
editorProps: {
|
|
893
|
-
...c,
|
|
894
1587
|
attributes: {
|
|
895
|
-
class: "prose-
|
|
1588
|
+
class: cls(proseClass, "prose-headings:font-title font-default focus:outline-none max-w-full p-12")
|
|
896
1589
|
}
|
|
897
1590
|
},
|
|
898
|
-
|
|
899
|
-
|
|
1591
|
+
onCreate: ({ editor }) => {
|
|
1592
|
+
editorRef.current = editor;
|
|
900
1593
|
},
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
921
|
-
),
|
|
922
|
-
/* @__PURE__ */ b("div", { children: [
|
|
923
|
-
/* @__PURE__ */ s("p", { className: "font-medium", children: l.title }),
|
|
924
|
-
/* @__PURE__ */ s("p", { className: "text-xs text-gray-700 dark:text-slate-300", children: l.description })
|
|
925
|
-
] })
|
|
926
|
-
]
|
|
927
|
-
},
|
|
928
|
-
l.title
|
|
929
|
-
))
|
|
930
|
-
]
|
|
931
|
-
}
|
|
932
|
-
),
|
|
933
|
-
/* @__PURE__ */ b(
|
|
934
|
-
je,
|
|
935
|
-
{
|
|
936
|
-
tippyOptions: {
|
|
937
|
-
placement: "top"
|
|
938
|
-
},
|
|
939
|
-
className: g("flex w-fit max-w-[90vw] h-10 overflow-hidden rounded border bg-white dark:bg-gray-900 shadow", x),
|
|
940
|
-
children: [
|
|
941
|
-
/* @__PURE__ */ s(ct, { open: p, onOpenChange: h }),
|
|
942
|
-
/* @__PURE__ */ s(K, { orientation: "vertical" }),
|
|
943
|
-
/* @__PURE__ */ s(ut, { open: f, onOpenChange: A }),
|
|
944
|
-
/* @__PURE__ */ s(K, { orientation: "vertical" }),
|
|
945
|
-
/* @__PURE__ */ s(mt, {})
|
|
946
|
-
]
|
|
947
|
-
}
|
|
948
|
-
)
|
|
949
|
-
]
|
|
1594
|
+
onUpdate: ({ editor }) => {
|
|
1595
|
+
onEditorUpdate(editor);
|
|
1596
|
+
},
|
|
1597
|
+
children: /* @__PURE__ */ jsxs(
|
|
1598
|
+
EditorBubble,
|
|
1599
|
+
{
|
|
1600
|
+
tippyOptions: {
|
|
1601
|
+
placement: "top"
|
|
1602
|
+
},
|
|
1603
|
+
className: cls("flex w-fit max-w-[90vw] h-10 overflow-hidden rounded border bg-white dark:bg-gray-900 shadow", defaultBorderMixin),
|
|
1604
|
+
children: [
|
|
1605
|
+
/* @__PURE__ */ jsx(NodeSelector, { portalContainer: ref.current, open: openNode, onOpenChange: setOpenNode }),
|
|
1606
|
+
/* @__PURE__ */ jsx(Separator, { orientation: "vertical" }),
|
|
1607
|
+
/* @__PURE__ */ jsx(LinkSelector, { open: openLink, onOpenChange: setOpenLink }),
|
|
1608
|
+
/* @__PURE__ */ jsx(Separator, { orientation: "vertical" }),
|
|
1609
|
+
/* @__PURE__ */ jsx(TextButtons, {})
|
|
1610
|
+
]
|
|
1611
|
+
}
|
|
1612
|
+
)
|
|
950
1613
|
}
|
|
951
1614
|
)
|
|
952
1615
|
}
|
|
953
|
-
)
|
|
1616
|
+
);
|
|
954
1617
|
};
|
|
955
|
-
function
|
|
956
|
-
const
|
|
957
|
-
return
|
|
958
|
-
|
|
1618
|
+
function addLineBreakAfterImages(markdown) {
|
|
1619
|
+
const imageRegex = /!\[.*?\]\(.*?\)/g;
|
|
1620
|
+
return markdown.replace(imageRegex, (match) => `${match}`);
|
|
1621
|
+
}
|
|
1622
|
+
const cssStyles = `
|
|
1623
|
+
.ProseMirror {
|
|
1624
|
+
box-shadow: none !important;
|
|
959
1625
|
}
|
|
960
|
-
const Tt = `
|
|
961
|
-
|
|
962
1626
|
.ProseMirror .is-editor-empty:first-child::before {
|
|
963
1627
|
content: attr(data-placeholder);
|
|
964
1628
|
float: left;
|
|
@@ -981,6 +1645,7 @@ const Tt = `
|
|
|
981
1645
|
}
|
|
982
1646
|
|
|
983
1647
|
.is-empty {
|
|
1648
|
+
cursor: text;
|
|
984
1649
|
color: rgb(100 116 139); //500
|
|
985
1650
|
}
|
|
986
1651
|
|
|
@@ -998,6 +1663,7 @@ const Tt = `
|
|
|
998
1663
|
&.ProseMirror-selectednode {
|
|
999
1664
|
outline: 3px solid #5abbf7;
|
|
1000
1665
|
filter: brightness(90%);
|
|
1666
|
+
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000) !important;
|
|
1001
1667
|
}
|
|
1002
1668
|
}
|
|
1003
1669
|
|
|
@@ -1025,7 +1691,7 @@ ul[data-type="taskList"] li > label {
|
|
|
1025
1691
|
}
|
|
1026
1692
|
|
|
1027
1693
|
&:active {
|
|
1028
|
-
background-color: rgb(71 85 105)
|
|
1694
|
+
background-color: rgb(71 85 105);
|
|
1029
1695
|
}
|
|
1030
1696
|
}
|
|
1031
1697
|
}
|
|
@@ -1092,7 +1758,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
|
1092
1758
|
}
|
|
1093
1759
|
|
|
1094
1760
|
.ProseMirror:not(.dragging) .ProseMirror-selectednode {
|
|
1095
|
-
outline: none !important;
|
|
1761
|
+
// outline: none !important;
|
|
1096
1762
|
background-color: rgb(219 234 254); // blue 100
|
|
1097
1763
|
transition: background-color 0.2s;
|
|
1098
1764
|
box-shadow: none;
|
|
@@ -1103,7 +1769,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
|
1103
1769
|
}
|
|
1104
1770
|
|
|
1105
1771
|
.drag-handle {
|
|
1106
|
-
position:
|
|
1772
|
+
position: absolute;
|
|
1107
1773
|
opacity: 1;
|
|
1108
1774
|
transition: opacity ease-in 0.2s;
|
|
1109
1775
|
border-radius: 0.25rem;
|
|
@@ -1114,7 +1780,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
|
1114
1780
|
background-position: center;
|
|
1115
1781
|
width: 1.2rem;
|
|
1116
1782
|
height: 1.5rem;
|
|
1117
|
-
z-index:
|
|
1783
|
+
z-index: 100;
|
|
1118
1784
|
cursor: grab;
|
|
1119
1785
|
|
|
1120
1786
|
&:hover {
|
|
@@ -1149,6 +1815,6 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
|
1149
1815
|
}
|
|
1150
1816
|
`;
|
|
1151
1817
|
export {
|
|
1152
|
-
|
|
1818
|
+
FireCMSEditor
|
|
1153
1819
|
};
|
|
1154
1820
|
//# sourceMappingURL=index.es.js.map
|