@firecms/editor 3.0.0-beta.7 → 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/_image-resizer.d.ts +0 -1
- package/dist/extensions/index.d.ts +0 -1
- package/dist/extensions/slashCommand.d.ts +92 -0
- package/dist/index.es.js +1360 -703
- 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 +23 -26
- package/dist/components/editor-command-item.d.ts +0 -20
- package/dist/components/editor-command.d.ts +0 -24
- 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,342 +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
|
-
|
|
233
|
-
const r = q.filter((a) => a.isActive(t)).pop() ?? {
|
|
133
|
+
const { editor } = useCurrentEditor();
|
|
134
|
+
if (!editor) return null;
|
|
135
|
+
const activeItem = items.filter((item) => item.isActive(editor)).pop() ?? {
|
|
234
136
|
name: "Multiple"
|
|
235
137
|
};
|
|
236
|
-
return /* @__PURE__ */
|
|
237
|
-
|
|
138
|
+
return /* @__PURE__ */ jsx(
|
|
139
|
+
Popover,
|
|
238
140
|
{
|
|
239
141
|
sideOffset: 5,
|
|
240
142
|
align: "start",
|
|
143
|
+
portalContainer,
|
|
241
144
|
className: "w-48 p-1",
|
|
242
|
-
trigger: /* @__PURE__ */
|
|
243
|
-
|
|
145
|
+
trigger: /* @__PURE__ */ jsxs(
|
|
146
|
+
Button,
|
|
244
147
|
{
|
|
245
148
|
variant: "text",
|
|
246
149
|
className: "gap-2 rounded-none",
|
|
247
150
|
color: "text",
|
|
248
151
|
children: [
|
|
249
|
-
/* @__PURE__ */
|
|
250
|
-
/* @__PURE__ */
|
|
152
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-sm", children: activeItem.name }),
|
|
153
|
+
/* @__PURE__ */ jsx(ExpandMoreIcon, { size: "small" })
|
|
251
154
|
]
|
|
252
155
|
}
|
|
253
156
|
),
|
|
254
|
-
modal:
|
|
255
|
-
open
|
|
256
|
-
onOpenChange
|
|
257
|
-
children:
|
|
258
|
-
|
|
157
|
+
modal: true,
|
|
158
|
+
open,
|
|
159
|
+
onOpenChange,
|
|
160
|
+
children: items.map((item, index) => /* @__PURE__ */ jsxs(
|
|
161
|
+
EditorBubbleItem,
|
|
259
162
|
{
|
|
260
|
-
onSelect: (
|
|
261
|
-
|
|
163
|
+
onSelect: (editor2) => {
|
|
164
|
+
item.command(editor2);
|
|
165
|
+
onOpenChange(false);
|
|
262
166
|
},
|
|
263
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",
|
|
264
168
|
children: [
|
|
265
|
-
/* @__PURE__ */
|
|
266
|
-
/* @__PURE__ */
|
|
267
|
-
/* @__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 })
|
|
268
172
|
] }),
|
|
269
|
-
|
|
173
|
+
activeItem.name === item.name && /* @__PURE__ */ jsx(CheckIcon, { size: "smallest" })
|
|
270
174
|
]
|
|
271
175
|
},
|
|
272
|
-
|
|
176
|
+
index
|
|
273
177
|
))
|
|
274
178
|
}
|
|
275
179
|
);
|
|
276
180
|
};
|
|
277
|
-
function
|
|
181
|
+
function isValidUrl(url) {
|
|
278
182
|
try {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
183
|
+
new URL(url);
|
|
184
|
+
return true;
|
|
185
|
+
} catch (e) {
|
|
186
|
+
return false;
|
|
282
187
|
}
|
|
283
188
|
}
|
|
284
|
-
function
|
|
285
|
-
if (
|
|
286
|
-
return e;
|
|
189
|
+
function getUrlFromString(str) {
|
|
190
|
+
if (isValidUrl(str)) return str;
|
|
287
191
|
try {
|
|
288
|
-
|
|
289
|
-
|
|
192
|
+
if (str.includes(".") && !str.includes(" ")) {
|
|
193
|
+
return new URL(`https://${str}`).toString();
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
} catch (e) {
|
|
290
197
|
return null;
|
|
291
198
|
}
|
|
292
199
|
}
|
|
293
|
-
const
|
|
294
|
-
open
|
|
295
|
-
onOpenChange
|
|
200
|
+
const LinkSelector = ({
|
|
201
|
+
open,
|
|
202
|
+
onOpenChange
|
|
296
203
|
}) => {
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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,
|
|
302
212
|
{
|
|
303
|
-
modal:
|
|
304
|
-
open
|
|
305
|
-
onOpenChange
|
|
306
|
-
trigger: /* @__PURE__ */
|
|
307
|
-
|
|
213
|
+
modal: true,
|
|
214
|
+
open,
|
|
215
|
+
onOpenChange,
|
|
216
|
+
trigger: /* @__PURE__ */ jsx(
|
|
217
|
+
Button,
|
|
308
218
|
{
|
|
309
219
|
variant: "text",
|
|
310
220
|
className: "gap-2 rounded-none",
|
|
311
221
|
color: "text",
|
|
312
|
-
children: /* @__PURE__ */
|
|
313
|
-
"text-blue-500":
|
|
222
|
+
children: /* @__PURE__ */ jsx("p", { className: cls("underline decoration-stone-400 underline-offset-4", {
|
|
223
|
+
"text-blue-500": editor.isActive("link")
|
|
314
224
|
}), children: "Link" })
|
|
315
225
|
}
|
|
316
226
|
),
|
|
317
|
-
children: /* @__PURE__ */
|
|
227
|
+
children: /* @__PURE__ */ jsxs(
|
|
318
228
|
"form",
|
|
319
229
|
{
|
|
320
|
-
onSubmit: (
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
|
|
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();
|
|
325
236
|
},
|
|
326
237
|
className: "flex p-1",
|
|
327
238
|
children: [
|
|
328
|
-
/* @__PURE__ */
|
|
239
|
+
/* @__PURE__ */ jsx(
|
|
329
240
|
"input",
|
|
330
241
|
{
|
|
331
|
-
ref:
|
|
332
|
-
autoFocus:
|
|
242
|
+
ref: inputRef,
|
|
243
|
+
autoFocus: open,
|
|
333
244
|
placeholder: "Paste a link",
|
|
334
|
-
defaultValue:
|
|
335
|
-
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)
|
|
336
247
|
}
|
|
337
248
|
),
|
|
338
|
-
|
|
339
|
-
|
|
249
|
+
editor.getAttributes("link").href ? /* @__PURE__ */ jsx(
|
|
250
|
+
Button,
|
|
340
251
|
{
|
|
341
252
|
size: "small",
|
|
342
253
|
variant: "text",
|
|
@@ -344,630 +255,1374 @@ const ut = ({
|
|
|
344
255
|
color: "text",
|
|
345
256
|
className: "flex items-center",
|
|
346
257
|
onClick: () => {
|
|
347
|
-
|
|
258
|
+
editor.chain().focus().unsetLink().run();
|
|
348
259
|
},
|
|
349
|
-
children: /* @__PURE__ */
|
|
260
|
+
children: /* @__PURE__ */ jsx(DeleteIcon, { size: "small" })
|
|
350
261
|
}
|
|
351
|
-
) : /* @__PURE__ */
|
|
352
|
-
|
|
262
|
+
) : /* @__PURE__ */ jsx(
|
|
263
|
+
Button,
|
|
353
264
|
{
|
|
354
265
|
size: "small",
|
|
355
266
|
variant: "text",
|
|
356
|
-
children: /* @__PURE__ */
|
|
267
|
+
children: /* @__PURE__ */ jsx(CheckIcon, { size: "small" })
|
|
357
268
|
}
|
|
358
269
|
)
|
|
359
270
|
]
|
|
360
271
|
}
|
|
361
272
|
)
|
|
362
273
|
}
|
|
363
|
-
)
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
const TextButtons = () => {
|
|
277
|
+
const { editor } = useCurrentEditor();
|
|
278
|
+
if (!editor) return null;
|
|
279
|
+
const items2 = [
|
|
367
280
|
{
|
|
368
281
|
name: "bold",
|
|
369
|
-
isActive: (
|
|
370
|
-
command: (
|
|
371
|
-
icon:
|
|
282
|
+
isActive: (editor2) => editor2?.isActive("bold") ?? false,
|
|
283
|
+
command: (editor2) => editor2?.chain().focus().toggleBold().run(),
|
|
284
|
+
icon: FormatBoldIcon
|
|
372
285
|
},
|
|
373
286
|
{
|
|
374
287
|
name: "italic",
|
|
375
|
-
isActive: (
|
|
376
|
-
command: (
|
|
377
|
-
icon:
|
|
288
|
+
isActive: (editor2) => editor2?.isActive("italic") ?? false,
|
|
289
|
+
command: (editor2) => editor2?.chain().focus().toggleItalic().run(),
|
|
290
|
+
icon: FormatItalicIcon
|
|
378
291
|
},
|
|
379
292
|
{
|
|
380
293
|
name: "underline",
|
|
381
|
-
isActive: (
|
|
382
|
-
command: (
|
|
383
|
-
icon:
|
|
294
|
+
isActive: (editor2) => editor2?.isActive("underline") ?? false,
|
|
295
|
+
command: (editor2) => editor2?.chain().focus().toggleUnderline().run(),
|
|
296
|
+
icon: FormatUnderlinedIcon
|
|
384
297
|
},
|
|
385
298
|
{
|
|
386
299
|
name: "strike",
|
|
387
|
-
isActive: (
|
|
388
|
-
command: (
|
|
389
|
-
icon:
|
|
300
|
+
isActive: (editor2) => editor2?.isActive("strike") ?? false,
|
|
301
|
+
command: (editor2) => editor2?.chain().focus().toggleStrike().run(),
|
|
302
|
+
icon: FormatStrikethroughIcon
|
|
390
303
|
},
|
|
391
304
|
{
|
|
392
305
|
name: "code",
|
|
393
|
-
isActive: (
|
|
394
|
-
command: (
|
|
395
|
-
icon:
|
|
306
|
+
isActive: (editor2) => editor2?.isActive("code") ?? false,
|
|
307
|
+
command: (editor2) => editor2?.chain().focus().toggleCode().run(),
|
|
308
|
+
icon: CodeIcon
|
|
396
309
|
}
|
|
397
|
-
]
|
|
398
|
-
|
|
310
|
+
];
|
|
311
|
+
return /* @__PURE__ */ jsx("div", { className: "flex", children: items2.map((item, index) => /* @__PURE__ */ jsx(
|
|
312
|
+
EditorBubbleItem,
|
|
399
313
|
{
|
|
400
|
-
onSelect: (
|
|
401
|
-
|
|
314
|
+
onSelect: (editor2) => {
|
|
315
|
+
item.command(editor2);
|
|
402
316
|
},
|
|
403
|
-
children: /* @__PURE__ */
|
|
404
|
-
|
|
317
|
+
children: /* @__PURE__ */ jsx(
|
|
318
|
+
Button,
|
|
405
319
|
{
|
|
406
320
|
size: "small",
|
|
407
321
|
color: "text",
|
|
408
322
|
className: "gap-2 rounded-none h-full",
|
|
409
323
|
variant: "text",
|
|
410
|
-
children: /* @__PURE__ */
|
|
411
|
-
|
|
324
|
+
children: /* @__PURE__ */ jsx(
|
|
325
|
+
item.icon,
|
|
412
326
|
{
|
|
413
|
-
className:
|
|
414
|
-
"text-inherit": !
|
|
415
|
-
"text-blue-500":
|
|
327
|
+
className: cls({
|
|
328
|
+
"text-inherit": !item.isActive(editor),
|
|
329
|
+
"text-blue-500": item.isActive(editor)
|
|
416
330
|
})
|
|
417
331
|
}
|
|
418
332
|
)
|
|
419
333
|
}
|
|
420
334
|
)
|
|
421
335
|
},
|
|
422
|
-
|
|
423
|
-
)) })
|
|
336
|
+
index
|
|
337
|
+
)) });
|
|
424
338
|
};
|
|
425
|
-
function
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return Array.isArray(e) ? e.map((o) => H(o)) : (typeof e == "object" && e !== null && (e.attrs && typeof e.attrs == "object" && "class" in e.attrs && delete e.attrs.class, Object.keys(e).forEach((o) => {
|
|
438
|
-
e[o] = H(e[o]);
|
|
439
|
-
})), 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;
|
|
440
351
|
}
|
|
441
|
-
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({
|
|
442
489
|
HTMLAttributes: {
|
|
443
|
-
class:
|
|
490
|
+
class: cls(
|
|
444
491
|
"text-gray-600 dark:text-slate-300 underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer"
|
|
445
492
|
)
|
|
446
493
|
}
|
|
447
|
-
})
|
|
494
|
+
});
|
|
495
|
+
const taskList = TaskList.configure({
|
|
448
496
|
HTMLAttributes: {
|
|
449
|
-
class:
|
|
497
|
+
class: cls("not-prose")
|
|
450
498
|
}
|
|
451
|
-
})
|
|
499
|
+
});
|
|
500
|
+
const taskItem = TaskItem.configure({
|
|
452
501
|
HTMLAttributes: {
|
|
453
|
-
class:
|
|
502
|
+
class: cls("flex items-start my-4")
|
|
454
503
|
},
|
|
455
|
-
nested:
|
|
456
|
-
})
|
|
504
|
+
nested: true
|
|
505
|
+
});
|
|
506
|
+
const markdownExtension = Markdown.configure({
|
|
507
|
+
html: true
|
|
508
|
+
});
|
|
509
|
+
const horizontalRule = Horizontal.configure({
|
|
457
510
|
HTMLAttributes: {
|
|
458
|
-
class:
|
|
511
|
+
class: cls("mt-4 mb-6 border-t", defaultBorderMixin)
|
|
459
512
|
}
|
|
460
|
-
})
|
|
513
|
+
});
|
|
514
|
+
const starterKit = StarterKit.configure({
|
|
461
515
|
bulletList: {
|
|
462
516
|
HTMLAttributes: {
|
|
463
|
-
class:
|
|
517
|
+
class: cls("list-disc list-outside leading-3 -mt-2")
|
|
464
518
|
}
|
|
465
519
|
},
|
|
466
520
|
orderedList: {
|
|
467
521
|
HTMLAttributes: {
|
|
468
|
-
class:
|
|
522
|
+
class: cls("list-decimal list-outside leading-3 -mt-2")
|
|
469
523
|
}
|
|
470
524
|
},
|
|
471
525
|
listItem: {
|
|
472
526
|
HTMLAttributes: {
|
|
473
|
-
class:
|
|
527
|
+
class: cls("leading-normal -mb-2")
|
|
474
528
|
}
|
|
475
529
|
},
|
|
476
530
|
blockquote: {
|
|
477
531
|
HTMLAttributes: {
|
|
478
|
-
class:
|
|
532
|
+
class: cls("border-l-4 border-primary")
|
|
479
533
|
}
|
|
480
534
|
},
|
|
481
535
|
codeBlock: {
|
|
482
536
|
HTMLAttributes: {
|
|
483
|
-
class:
|
|
537
|
+
class: cls("rounded bg-blue-50 dark:bg-gray-700 border p-5 font-mono font-medium", defaultBorderMixin)
|
|
484
538
|
}
|
|
485
539
|
},
|
|
486
540
|
code: {
|
|
487
541
|
HTMLAttributes: {
|
|
488
|
-
class:
|
|
542
|
+
class: cls("rounded-md bg-slate-50 dark:bg-gray-700 px-1.5 py-1 font-mono font-medium"),
|
|
489
543
|
spellcheck: "false"
|
|
490
544
|
}
|
|
491
545
|
},
|
|
492
|
-
|
|
546
|
+
document: false,
|
|
547
|
+
horizontalRule: false,
|
|
493
548
|
dropcursor: {
|
|
494
549
|
color: "#DBEAFE",
|
|
495
550
|
width: 4
|
|
496
551
|
},
|
|
497
|
-
gapcursor:
|
|
552
|
+
gapcursor: false
|
|
498
553
|
});
|
|
499
|
-
async function
|
|
500
|
-
const { schema
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const
|
|
509
|
-
|
|
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);
|
|
510
577
|
}
|
|
511
|
-
const
|
|
512
|
-
|
|
578
|
+
const ImagePluginKey = new PluginKey$1("imagePlugin");
|
|
579
|
+
const createDropImagePlugin = (upload) => {
|
|
580
|
+
const plugin = new Plugin$1({
|
|
581
|
+
key: ImagePluginKey,
|
|
513
582
|
state: {
|
|
514
583
|
// Initialize the plugin state with an empty DecorationSet
|
|
515
|
-
init: () =>
|
|
584
|
+
init: () => DecorationSet$1.empty,
|
|
516
585
|
// Apply transactions to update the state
|
|
517
|
-
apply: (
|
|
518
|
-
const
|
|
519
|
-
|
|
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);
|
|
520
592
|
}
|
|
521
593
|
},
|
|
522
594
|
props: {
|
|
523
595
|
handleDOMEvents: {
|
|
524
|
-
drop: (
|
|
525
|
-
if (
|
|
526
|
-
return
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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;
|
|
538
620
|
}
|
|
539
621
|
},
|
|
540
|
-
handlePaste(
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
let
|
|
544
|
-
|
|
545
|
-
const
|
|
546
|
-
if (
|
|
547
|
-
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
await
|
|
551
|
-
}
|
|
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);
|
|
552
635
|
}
|
|
553
|
-
})
|
|
636
|
+
});
|
|
637
|
+
return anyImageFound;
|
|
554
638
|
},
|
|
555
|
-
decorations(
|
|
556
|
-
return
|
|
639
|
+
decorations(state) {
|
|
640
|
+
return plugin.getState(state);
|
|
557
641
|
}
|
|
558
642
|
},
|
|
559
|
-
view(
|
|
643
|
+
view(editorView) {
|
|
560
644
|
return {
|
|
561
|
-
update(
|
|
562
|
-
const
|
|
563
|
-
|
|
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
|
+
}
|
|
564
651
|
}
|
|
565
652
|
};
|
|
566
653
|
}
|
|
567
654
|
});
|
|
568
|
-
return
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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({
|
|
579
670
|
name: "CustomKeymap",
|
|
580
671
|
addCommands() {
|
|
581
672
|
return {
|
|
582
|
-
selectTextWithinNodeBoundaries: () => ({ editor
|
|
583
|
-
const { state
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
|
587
681
|
});
|
|
588
682
|
}
|
|
589
683
|
};
|
|
590
684
|
},
|
|
591
685
|
addKeyboardShortcuts() {
|
|
592
686
|
return {
|
|
593
|
-
"Mod-a": ({ editor
|
|
594
|
-
const { state
|
|
595
|
-
|
|
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;
|
|
596
700
|
}
|
|
597
701
|
};
|
|
598
702
|
}
|
|
599
703
|
});
|
|
600
|
-
function
|
|
601
|
-
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();
|
|
602
711
|
return {
|
|
603
|
-
top:
|
|
604
|
-
left:
|
|
605
|
-
width:
|
|
712
|
+
top: data.top - (ancestorRect?.top ?? 0),
|
|
713
|
+
left: data.left - (ancestorRect?.left ?? 0),
|
|
714
|
+
width: data.width
|
|
606
715
|
};
|
|
607
716
|
}
|
|
608
|
-
function
|
|
609
|
-
return document.elementsFromPoint(
|
|
610
|
-
(
|
|
717
|
+
function nodeDOMAtCoords(coords) {
|
|
718
|
+
return document.elementsFromPoint(coords.x, coords.y).find(
|
|
719
|
+
(elem) => elem.parentElement?.matches?.(".ProseMirror") || elem.matches(
|
|
611
720
|
["li", "p:not(:first-child)", "pre", "blockquote", "h1, h2, h3, h4, h5, h6"].join(", ")
|
|
612
721
|
)
|
|
613
722
|
);
|
|
614
723
|
}
|
|
615
|
-
function
|
|
616
|
-
const
|
|
617
|
-
return
|
|
618
|
-
left:
|
|
619
|
-
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
|
|
620
729
|
})?.inside;
|
|
621
730
|
}
|
|
622
|
-
function
|
|
623
|
-
function
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const
|
|
627
|
-
x:
|
|
628
|
-
y:
|
|
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
|
|
629
738
|
});
|
|
630
|
-
if (!(
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
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
|
+
};
|
|
638
757
|
}
|
|
639
|
-
function
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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
|
|
644
764
|
});
|
|
645
|
-
if (!(
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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)));
|
|
649
769
|
}
|
|
650
|
-
let
|
|
651
|
-
function
|
|
652
|
-
|
|
770
|
+
let dragHandleElement = null;
|
|
771
|
+
function hideDragHandle() {
|
|
772
|
+
if (dragHandleElement) {
|
|
773
|
+
dragHandleElement.classList.add("hide");
|
|
774
|
+
}
|
|
653
775
|
}
|
|
654
|
-
function
|
|
655
|
-
|
|
776
|
+
function showDragHandle() {
|
|
777
|
+
if (dragHandleElement) {
|
|
778
|
+
dragHandleElement.classList.remove("hide");
|
|
779
|
+
}
|
|
656
780
|
}
|
|
657
|
-
return new
|
|
658
|
-
view: (
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}
|
|
666
|
-
|
|
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
|
+
},
|
|
667
800
|
props: {
|
|
668
801
|
handleDOMEvents: {
|
|
669
|
-
mousemove: (
|
|
670
|
-
if (!
|
|
802
|
+
mousemove: (view, event) => {
|
|
803
|
+
if (!view.editable) {
|
|
671
804
|
return;
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
805
|
+
}
|
|
806
|
+
const node = nodeDOMAtCoords({
|
|
807
|
+
x: event.clientX + 50 + options.dragHandleWidth,
|
|
808
|
+
y: event.clientY
|
|
675
809
|
});
|
|
676
|
-
if (!(
|
|
677
|
-
|
|
810
|
+
if (!(node instanceof Element)) {
|
|
811
|
+
hideDragHandle();
|
|
678
812
|
return;
|
|
679
813
|
}
|
|
680
|
-
const
|
|
681
|
-
|
|
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();
|
|
682
828
|
},
|
|
683
829
|
keydown: () => {
|
|
684
|
-
|
|
830
|
+
hideDragHandle();
|
|
685
831
|
},
|
|
686
832
|
mousewheel: () => {
|
|
687
|
-
|
|
833
|
+
hideDragHandle();
|
|
688
834
|
},
|
|
689
835
|
// dragging class is used for CSS
|
|
690
|
-
dragstart: (
|
|
691
|
-
|
|
836
|
+
dragstart: (view) => {
|
|
837
|
+
view.dom.classList.add("dragging");
|
|
692
838
|
},
|
|
693
|
-
drop: (
|
|
694
|
-
|
|
839
|
+
drop: (view) => {
|
|
840
|
+
view.dom.classList.remove("dragging");
|
|
695
841
|
},
|
|
696
|
-
dragend: (
|
|
697
|
-
|
|
842
|
+
dragend: (view) => {
|
|
843
|
+
view.dom.classList.remove("dragging");
|
|
698
844
|
}
|
|
699
845
|
}
|
|
700
846
|
}
|
|
701
847
|
});
|
|
702
848
|
}
|
|
703
|
-
const
|
|
849
|
+
const DragAndDrop = Extension.create({
|
|
704
850
|
name: "dragAndDrop",
|
|
705
851
|
addProseMirrorPlugins() {
|
|
706
852
|
return [
|
|
707
|
-
|
|
853
|
+
DragHandle({
|
|
708
854
|
dragHandleWidth: 24
|
|
709
855
|
})
|
|
710
856
|
];
|
|
711
857
|
}
|
|
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
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
+
}
|
|
763
916
|
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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
|
+
}
|
|
772
948
|
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
command: ({ editor: l, range: m }) => {
|
|
780
|
-
l.chain().focus().deleteRange(m).setNode("heading", { level: 1 }).run();
|
|
949
|
+
};
|
|
950
|
+
},
|
|
951
|
+
parseHTML() {
|
|
952
|
+
return [
|
|
953
|
+
{
|
|
954
|
+
tag: `span[data-type="${this.name}"]`
|
|
781
955
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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();
|
|
790
1107
|
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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;
|
|
799
1139
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
description: "Create a simple bullet list.",
|
|
804
|
-
searchTerms: ["unordered", "point"],
|
|
805
|
-
icon: /* @__PURE__ */ s(ee, { size: 18 }),
|
|
806
|
-
command: ({ editor: l, range: m }) => {
|
|
807
|
-
l.chain().focus().deleteRange(m).toggleBulletList().run();
|
|
1140
|
+
if (event.key === "ArrowDown") {
|
|
1141
|
+
downHandler();
|
|
1142
|
+
return true;
|
|
808
1143
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
description: "Create a list with numbering.",
|
|
813
|
-
searchTerms: ["ordered"],
|
|
814
|
-
icon: /* @__PURE__ */ s(te, { size: 18 }),
|
|
815
|
-
command: ({ editor: l, range: m }) => {
|
|
816
|
-
l.chain().focus().deleteRange(m).toggleOrderedList().run();
|
|
1144
|
+
if (event.key === "Enter") {
|
|
1145
|
+
enterHandler();
|
|
1146
|
+
return true;
|
|
817
1147
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
command: ({ editor: l, range: m }) => l.chain().focus().deleteRange(m).toggleCodeBlock().run()
|
|
832
|
-
},
|
|
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",
|
|
833
1161
|
{
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
+
}
|
|
846
1442
|
}
|
|
847
|
-
}
|
|
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();
|
|
848
1526
|
}
|
|
849
1527
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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));
|
|
854
1534
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}),
|
|
862
|
-
fe.configure({
|
|
863
|
-
html: !1,
|
|
864
|
-
transformCopiedText: !0
|
|
865
|
-
}),
|
|
866
|
-
vt,
|
|
867
|
-
At,
|
|
868
|
-
kt,
|
|
869
|
-
gt,
|
|
870
|
-
pt,
|
|
871
|
-
// tiptapImage,
|
|
872
|
-
d,
|
|
873
|
-
// updatedImage,
|
|
874
|
-
ft,
|
|
875
|
-
ht,
|
|
876
|
-
bt,
|
|
877
|
-
i
|
|
878
|
-
], [p, h] = v(!1), [f, A] = v(!1);
|
|
879
|
-
Ze("Editor", Tt);
|
|
880
|
-
const E = L.useRef(null), [ie, ce] = v(null), [M, le] = v(null), [N, de] = v(null), ue = (l) => {
|
|
881
|
-
E.current = l, a && ce(l.storage.markdown.getMarkdown()), t && le(H(l.getJSON())), r && de(l.getHTML());
|
|
882
|
-
};
|
|
883
|
-
return R(ie, () => {
|
|
884
|
-
if (E.current) {
|
|
885
|
-
const l = E.current.storage.markdown.getMarkdown();
|
|
886
|
-
a?.(Lt(l));
|
|
1535
|
+
if (onJsonContentChange) {
|
|
1536
|
+
const jsonContent = removeClassesFromJson(editor.getJSON());
|
|
1537
|
+
onJsonContentChange(jsonContent);
|
|
1538
|
+
}
|
|
1539
|
+
if (onHtmlContentChange) {
|
|
1540
|
+
onHtmlContentChange?.(editor.getHTML());
|
|
887
1541
|
}
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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(
|
|
893
1577
|
"div",
|
|
894
1578
|
{
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1579
|
+
ref,
|
|
1580
|
+
className: "relative min-h-[300px] w-full",
|
|
1581
|
+
children: /* @__PURE__ */ jsx(
|
|
1582
|
+
EditorProvider,
|
|
898
1583
|
{
|
|
899
|
-
content:
|
|
900
|
-
extensions
|
|
1584
|
+
content: content ?? "",
|
|
1585
|
+
extensions,
|
|
901
1586
|
editorProps: {
|
|
902
|
-
...c,
|
|
903
1587
|
attributes: {
|
|
904
|
-
class: "prose-
|
|
1588
|
+
class: cls(proseClass, "prose-headings:font-title font-default focus:outline-none max-w-full p-12")
|
|
905
1589
|
}
|
|
906
1590
|
},
|
|
907
|
-
|
|
908
|
-
|
|
1591
|
+
onCreate: ({ editor }) => {
|
|
1592
|
+
editorRef.current = editor;
|
|
909
1593
|
},
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
}
|
|
930
|
-
),
|
|
931
|
-
/* @__PURE__ */ b("div", { children: [
|
|
932
|
-
/* @__PURE__ */ s("p", { className: "font-medium", children: l.title }),
|
|
933
|
-
/* @__PURE__ */ s("p", { className: "text-xs text-gray-700 dark:text-slate-300", children: l.description })
|
|
934
|
-
] })
|
|
935
|
-
]
|
|
936
|
-
},
|
|
937
|
-
l.title
|
|
938
|
-
))
|
|
939
|
-
]
|
|
940
|
-
}
|
|
941
|
-
),
|
|
942
|
-
/* @__PURE__ */ b(
|
|
943
|
-
je,
|
|
944
|
-
{
|
|
945
|
-
tippyOptions: {
|
|
946
|
-
placement: "top"
|
|
947
|
-
},
|
|
948
|
-
className: g("flex w-fit max-w-[90vw] h-10 overflow-hidden rounded border bg-white dark:bg-gray-900 shadow", x),
|
|
949
|
-
children: [
|
|
950
|
-
/* @__PURE__ */ s(ct, { open: p, onOpenChange: h }),
|
|
951
|
-
/* @__PURE__ */ s(K, { orientation: "vertical" }),
|
|
952
|
-
/* @__PURE__ */ s(ut, { open: f, onOpenChange: A }),
|
|
953
|
-
/* @__PURE__ */ s(K, { orientation: "vertical" }),
|
|
954
|
-
/* @__PURE__ */ s(mt, {})
|
|
955
|
-
]
|
|
956
|
-
}
|
|
957
|
-
)
|
|
958
|
-
]
|
|
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
|
+
)
|
|
959
1613
|
}
|
|
960
1614
|
)
|
|
961
1615
|
}
|
|
962
|
-
)
|
|
1616
|
+
);
|
|
963
1617
|
};
|
|
964
|
-
function
|
|
965
|
-
const
|
|
966
|
-
return
|
|
967
|
-
|
|
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;
|
|
968
1625
|
}
|
|
969
|
-
const Tt = `
|
|
970
|
-
|
|
971
1626
|
.ProseMirror .is-editor-empty:first-child::before {
|
|
972
1627
|
content: attr(data-placeholder);
|
|
973
1628
|
float: left;
|
|
@@ -990,6 +1645,7 @@ const Tt = `
|
|
|
990
1645
|
}
|
|
991
1646
|
|
|
992
1647
|
.is-empty {
|
|
1648
|
+
cursor: text;
|
|
993
1649
|
color: rgb(100 116 139); //500
|
|
994
1650
|
}
|
|
995
1651
|
|
|
@@ -1007,6 +1663,7 @@ const Tt = `
|
|
|
1007
1663
|
&.ProseMirror-selectednode {
|
|
1008
1664
|
outline: 3px solid #5abbf7;
|
|
1009
1665
|
filter: brightness(90%);
|
|
1666
|
+
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000) !important;
|
|
1010
1667
|
}
|
|
1011
1668
|
}
|
|
1012
1669
|
|
|
@@ -1034,7 +1691,7 @@ ul[data-type="taskList"] li > label {
|
|
|
1034
1691
|
}
|
|
1035
1692
|
|
|
1036
1693
|
&:active {
|
|
1037
|
-
background-color: rgb(71 85 105)
|
|
1694
|
+
background-color: rgb(71 85 105);
|
|
1038
1695
|
}
|
|
1039
1696
|
}
|
|
1040
1697
|
}
|
|
@@ -1101,7 +1758,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
|
1101
1758
|
}
|
|
1102
1759
|
|
|
1103
1760
|
.ProseMirror:not(.dragging) .ProseMirror-selectednode {
|
|
1104
|
-
outline: none !important;
|
|
1761
|
+
// outline: none !important;
|
|
1105
1762
|
background-color: rgb(219 234 254); // blue 100
|
|
1106
1763
|
transition: background-color 0.2s;
|
|
1107
1764
|
box-shadow: none;
|
|
@@ -1112,7 +1769,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
|
1112
1769
|
}
|
|
1113
1770
|
|
|
1114
1771
|
.drag-handle {
|
|
1115
|
-
position:
|
|
1772
|
+
position: absolute;
|
|
1116
1773
|
opacity: 1;
|
|
1117
1774
|
transition: opacity ease-in 0.2s;
|
|
1118
1775
|
border-radius: 0.25rem;
|
|
@@ -1123,7 +1780,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
|
1123
1780
|
background-position: center;
|
|
1124
1781
|
width: 1.2rem;
|
|
1125
1782
|
height: 1.5rem;
|
|
1126
|
-
z-index:
|
|
1783
|
+
z-index: 100;
|
|
1127
1784
|
cursor: grab;
|
|
1128
1785
|
|
|
1129
1786
|
&:hover {
|
|
@@ -1158,6 +1815,6 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
|
1158
1815
|
}
|
|
1159
1816
|
`;
|
|
1160
1817
|
export {
|
|
1161
|
-
|
|
1818
|
+
FireCMSEditor
|
|
1162
1819
|
};
|
|
1163
1820
|
//# sourceMappingURL=index.es.js.map
|