@firecms/editor 3.0.0-canary.80 → 3.0.0-canary.82
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/index.es.js +727 -520
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1149 -3
- package/dist/index.umd.js.map +1 -1
- package/package.json +3 -3
package/dist/index.es.js
CHANGED
@@ -1,340 +1,407 @@
|
|
1
|
-
import { jsx
|
2
|
-
import
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import { Color
|
6
|
-
import { Markdown
|
7
|
-
import
|
8
|
-
import { useCurrentEditor
|
9
|
-
import { createStore
|
10
|
-
import { Slot
|
11
|
-
import
|
12
|
-
import { Command as
|
13
|
-
import
|
14
|
-
import
|
15
|
-
import
|
16
|
-
import
|
17
|
-
import
|
18
|
-
import { TaskItem
|
19
|
-
import { TaskList
|
20
|
-
import { Extension
|
21
|
-
import
|
22
|
-
import
|
23
|
-
import { Popover
|
24
|
-
import { Plugin
|
25
|
-
import { DecorationSet
|
26
|
-
import { Plugin as
|
27
|
-
const
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
const
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
}
|
38
|
-
|
39
|
-
|
40
|
-
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
2
|
+
import React, { forwardRef, useRef, useEffect, useMemo, useState } from "react";
|
3
|
+
import TiptapUnderline from "@tiptap/extension-underline";
|
4
|
+
import TextStyle from "@tiptap/extension-text-style";
|
5
|
+
import { Color } from "@tiptap/extension-color";
|
6
|
+
import { Markdown } from "tiptap-markdown";
|
7
|
+
import Highlight from "@tiptap/extension-highlight";
|
8
|
+
import { useCurrentEditor, BubbleMenu, isNodeSelection, ReactRenderer, EditorProvider } from "@tiptap/react";
|
9
|
+
import { createStore, Provider, atom, useAtom, useSetAtom, useAtomValue } from "jotai";
|
10
|
+
import { Slot } from "@radix-ui/react-slot";
|
11
|
+
import tunnel from "tunnel-rat";
|
12
|
+
import { Command as Command$1, CommandItem, CommandEmpty } from "cmdk";
|
13
|
+
import StarterKit from "@tiptap/starter-kit";
|
14
|
+
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
15
|
+
import TiptapLink from "@tiptap/extension-link";
|
16
|
+
import TiptapImage from "@tiptap/extension-image";
|
17
|
+
import Placeholder from "@tiptap/extension-placeholder";
|
18
|
+
import { TaskItem } from "@tiptap/extension-task-item";
|
19
|
+
import { TaskList } from "@tiptap/extension-task-list";
|
20
|
+
import { Extension, InputRule } from "@tiptap/core";
|
21
|
+
import Suggestion from "@tiptap/suggestion";
|
22
|
+
import tippy from "tippy.js";
|
23
|
+
import { Popover, Button, ExpandMoreIcon, CheckIcon, TextFieldsIcon, LooksOneIcon, LooksTwoIcon, Looks3Icon, CheckBoxIcon, FormatListBulletedIcon, FormatListNumberedIcon, FormatQuoteIcon, CodeIcon, cls, DeleteIcon, FormatBoldIcon, FormatItalicIcon, FormatUnderlinedIcon, FormatStrikethroughIcon, defaultBorderMixin, useInjectStyles, Separator, ImageIcon } from "@firecms/ui";
|
24
|
+
import { Plugin } from "prosemirror-state";
|
25
|
+
import { DecorationSet, Decoration, __serializeForClipboard } from "@tiptap/pm/view";
|
26
|
+
import { Plugin as Plugin$1, NodeSelection } from "@tiptap/pm/state";
|
27
|
+
const editorStore = createStore();
|
28
|
+
const EditorRoot = ({ children }) => {
|
29
|
+
return /* @__PURE__ */ jsx(Provider, { store: editorStore, children });
|
30
|
+
};
|
31
|
+
const EditorBubble = forwardRef(
|
32
|
+
({ children, tippyOptions, ...rest }, ref) => {
|
33
|
+
const { editor } = useCurrentEditor();
|
34
|
+
const instanceRef = useRef(null);
|
35
|
+
useEffect(() => {
|
36
|
+
if (!instanceRef.current || !tippyOptions?.placement) return;
|
37
|
+
instanceRef.current.setProps({ placement: tippyOptions.placement });
|
38
|
+
instanceRef.current.popperInstance?.update();
|
39
|
+
}, [tippyOptions?.placement]);
|
40
|
+
const bubbleMenuProps = useMemo(() => {
|
41
|
+
const shouldShow = ({ editor: editor2, state }) => {
|
42
|
+
const { selection } = state;
|
43
|
+
const { empty } = selection;
|
44
|
+
if (editor2.isActive("image") || empty || isNodeSelection(selection)) {
|
45
|
+
return false;
|
46
|
+
}
|
47
|
+
return true;
|
48
|
+
};
|
49
|
+
return {
|
50
|
+
shouldShow,
|
51
|
+
tippyOptions: {
|
52
|
+
onCreate: (val) => {
|
53
|
+
instanceRef.current = val;
|
54
|
+
},
|
55
|
+
moveTransition: "transform 0.15s ease-out",
|
56
|
+
...tippyOptions
|
41
57
|
},
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
return a ? (
|
58
|
+
...rest
|
59
|
+
};
|
60
|
+
}, [rest, tippyOptions]);
|
61
|
+
if (!editor) return null;
|
62
|
+
return (
|
48
63
|
//We need to add this because of https://github.com/ueberdosis/tiptap/issues/2658
|
49
|
-
/* @__PURE__ */
|
50
|
-
)
|
64
|
+
/* @__PURE__ */ jsx("div", { ref, children: /* @__PURE__ */ jsx(BubbleMenu, { editor, ...bubbleMenuProps, children }) })
|
65
|
+
);
|
51
66
|
}
|
52
|
-
)
|
53
|
-
|
54
|
-
|
67
|
+
);
|
68
|
+
const EditorBubbleItem = forwardRef(({ children, asChild, onSelect, ...rest }, ref) => {
|
69
|
+
const { editor } = useCurrentEditor();
|
70
|
+
const Comp = asChild ? Slot : "div";
|
71
|
+
if (!editor) return null;
|
72
|
+
return /* @__PURE__ */ jsx(Comp, { ref, ...rest, onClick: () => onSelect?.(editor), children });
|
55
73
|
});
|
56
|
-
|
57
|
-
const
|
58
|
-
|
59
|
-
|
74
|
+
EditorBubbleItem.displayName = "EditorBubbleItem";
|
75
|
+
const t = tunnel();
|
76
|
+
const queryAtom = atom("");
|
77
|
+
const rangeAtom = atom(null);
|
78
|
+
const EditorCommandOut = ({
|
79
|
+
query,
|
80
|
+
range
|
60
81
|
}) => {
|
61
|
-
const
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
);
|
82
|
+
const setQuery = useSetAtom(queryAtom, { store: editorStore });
|
83
|
+
const setRange = useSetAtom(rangeAtom, { store: editorStore });
|
84
|
+
useEffect(() => {
|
85
|
+
setQuery(query);
|
86
|
+
}, [query, setQuery]);
|
87
|
+
useEffect(() => {
|
88
|
+
setRange(range);
|
89
|
+
}, [range, setRange]);
|
90
|
+
useEffect(() => {
|
91
|
+
const navigationKeys = ["ArrowUp", "ArrowDown", "Enter"];
|
92
|
+
const onKeyDown = (e) => {
|
93
|
+
if (navigationKeys.includes(e.key)) {
|
94
|
+
e.preventDefault();
|
95
|
+
const commandRef = document.querySelector("#slash-command");
|
96
|
+
if (commandRef)
|
97
|
+
commandRef.dispatchEvent(
|
98
|
+
new KeyboardEvent("keydown", { key: e.key, cancelable: true, bubbles: true })
|
99
|
+
);
|
74
100
|
}
|
75
101
|
};
|
76
|
-
|
77
|
-
|
102
|
+
document.addEventListener("keydown", onKeyDown);
|
103
|
+
return () => {
|
104
|
+
document.removeEventListener("keydown", onKeyDown);
|
78
105
|
};
|
79
|
-
}, [])
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
106
|
+
}, []);
|
107
|
+
return /* @__PURE__ */ jsx(t.Out, {});
|
108
|
+
};
|
109
|
+
const EditorCommand = forwardRef(
|
110
|
+
({ children, className, ...rest }, ref) => {
|
111
|
+
const commandListRef = useRef(null);
|
112
|
+
const [query, setQuery] = useAtom(queryAtom);
|
113
|
+
return /* @__PURE__ */ jsx(t.In, { children: /* @__PURE__ */ jsxs(
|
114
|
+
Command$1,
|
85
115
|
{
|
86
|
-
ref
|
87
|
-
onKeyDown: (
|
88
|
-
|
116
|
+
ref,
|
117
|
+
onKeyDown: (e) => {
|
118
|
+
e.stopPropagation();
|
89
119
|
},
|
90
120
|
id: "slash-command",
|
91
|
-
className
|
92
|
-
...
|
121
|
+
className,
|
122
|
+
...rest,
|
93
123
|
children: [
|
94
|
-
/* @__PURE__ */
|
95
|
-
/* @__PURE__ */
|
124
|
+
/* @__PURE__ */ jsx(Command$1.Input, { value: query, onValueChange: setQuery, style: { display: "none" } }),
|
125
|
+
/* @__PURE__ */ jsx(Command$1.List, { ref: commandListRef, children })
|
96
126
|
]
|
97
127
|
}
|
98
128
|
) });
|
99
129
|
}
|
100
|
-
)
|
101
|
-
|
102
|
-
|
130
|
+
);
|
131
|
+
const EditorCommandItem = forwardRef(({ children, onCommand, ...rest }, ref) => {
|
132
|
+
const { editor } = useCurrentEditor();
|
133
|
+
const range = useAtomValue(rangeAtom);
|
134
|
+
if (!editor || !range) return null;
|
135
|
+
return /* @__PURE__ */ jsx(CommandItem, { ref, ...rest, onSelect: () => onCommand({ editor, range }), children });
|
103
136
|
});
|
104
|
-
|
105
|
-
const
|
137
|
+
EditorCommandItem.displayName = "EditorCommandItem";
|
138
|
+
const EditorCommandEmpty = CommandEmpty;
|
139
|
+
const Command = Extension.create({
|
106
140
|
name: "slash-command",
|
107
141
|
addOptions() {
|
108
142
|
return {
|
109
143
|
suggestion: {
|
110
144
|
char: "/",
|
111
|
-
command: ({ editor
|
112
|
-
|
145
|
+
command: ({ editor, range, props }) => {
|
146
|
+
props.command({ editor, range });
|
113
147
|
}
|
114
148
|
}
|
115
149
|
};
|
116
150
|
},
|
117
151
|
addProseMirrorPlugins() {
|
118
152
|
return [
|
119
|
-
|
153
|
+
Suggestion({
|
120
154
|
editor: this.editor,
|
121
155
|
...this.options.suggestion
|
122
156
|
})
|
123
157
|
];
|
124
158
|
}
|
125
|
-
})
|
126
|
-
|
159
|
+
});
|
160
|
+
const renderItems = () => {
|
161
|
+
let component = null;
|
162
|
+
let popup = null;
|
127
163
|
return {
|
128
|
-
onStart: (
|
129
|
-
|
130
|
-
props
|
131
|
-
editor:
|
132
|
-
})
|
133
|
-
|
164
|
+
onStart: (props) => {
|
165
|
+
component = new ReactRenderer(EditorCommandOut, {
|
166
|
+
props,
|
167
|
+
editor: props.editor
|
168
|
+
});
|
169
|
+
popup = tippy("body", {
|
170
|
+
getReferenceClientRect: props.clientRect,
|
134
171
|
appendTo: () => document.body,
|
135
|
-
content:
|
136
|
-
showOnCreate:
|
137
|
-
interactive:
|
172
|
+
content: component.element,
|
173
|
+
showOnCreate: true,
|
174
|
+
interactive: true,
|
138
175
|
trigger: "manual",
|
139
176
|
placement: "bottom-start"
|
140
177
|
});
|
141
178
|
},
|
142
|
-
onUpdate: (
|
143
|
-
|
144
|
-
|
179
|
+
onUpdate: (props) => {
|
180
|
+
component?.updateProps(props);
|
181
|
+
popup && popup[0].setProps({
|
182
|
+
getReferenceClientRect: props.clientRect
|
145
183
|
});
|
146
184
|
},
|
147
|
-
onKeyDown: (
|
185
|
+
onKeyDown: (props) => {
|
186
|
+
if (props.event.key === "Escape") {
|
187
|
+
popup?.[0].hide();
|
188
|
+
return true;
|
189
|
+
}
|
190
|
+
return component?.ref?.onKeyDown(props);
|
191
|
+
},
|
148
192
|
onExit: () => {
|
149
|
-
|
193
|
+
popup?.[0].destroy();
|
194
|
+
component?.destroy();
|
150
195
|
}
|
151
196
|
};
|
152
|
-
}
|
153
|
-
|
154
|
-
|
155
|
-
})
|
197
|
+
};
|
198
|
+
const createSuggestionItems = (items2) => items2;
|
199
|
+
const PlaceholderExtension = Placeholder.configure({
|
200
|
+
placeholder: ({ node }) => {
|
201
|
+
if (node.type.name === "heading") {
|
202
|
+
return `Heading ${node.attrs.level}`;
|
203
|
+
}
|
204
|
+
return "Press '/' for commands";
|
205
|
+
},
|
206
|
+
includeChildren: true
|
207
|
+
});
|
208
|
+
const Horizontal = HorizontalRule.extend({
|
156
209
|
addInputRules() {
|
157
210
|
return [
|
158
|
-
new
|
211
|
+
new InputRule({
|
159
212
|
find: /^(?:---|—-|___\s|\*\*\*\s)$/,
|
160
|
-
handler: ({ state
|
161
|
-
const
|
162
|
-
|
163
|
-
|
164
|
-
|
213
|
+
handler: ({ state, range }) => {
|
214
|
+
const attributes = {};
|
215
|
+
const { tr } = state;
|
216
|
+
const start = range.from;
|
217
|
+
const end = range.to;
|
218
|
+
tr.insert(start - 1, this.type.create(attributes)).delete(
|
219
|
+
tr.mapping.map(start),
|
220
|
+
tr.mapping.map(end)
|
165
221
|
);
|
166
222
|
}
|
167
223
|
})
|
168
224
|
];
|
169
225
|
}
|
170
|
-
})
|
226
|
+
});
|
227
|
+
const items = [
|
171
228
|
{
|
172
229
|
name: "Text",
|
173
|
-
icon:
|
174
|
-
command: (
|
230
|
+
icon: TextFieldsIcon,
|
231
|
+
command: (editor) => editor?.chain().focus().toggleNode("paragraph", "paragraph").run(),
|
175
232
|
// I feel like there has to be a more efficient way to do this – feel free to PR if you know how!
|
176
|
-
isActive: (
|
233
|
+
isActive: (editor) => (editor?.isActive("paragraph") && !editor?.isActive("bulletList") && !editor?.isActive("orderedList")) ?? false
|
177
234
|
},
|
178
235
|
{
|
179
236
|
name: "Heading 1",
|
180
|
-
icon:
|
181
|
-
command: (
|
182
|
-
isActive: (
|
237
|
+
icon: LooksOneIcon,
|
238
|
+
command: (editor) => editor?.chain().focus().toggleHeading({ level: 1 }).run(),
|
239
|
+
isActive: (editor) => editor?.isActive("heading", { level: 1 }) ?? false
|
183
240
|
},
|
184
241
|
{
|
185
242
|
name: "Heading 2",
|
186
|
-
icon:
|
187
|
-
command: (
|
188
|
-
isActive: (
|
243
|
+
icon: LooksTwoIcon,
|
244
|
+
command: (editor) => editor?.chain().focus().toggleHeading({ level: 2 }).run(),
|
245
|
+
isActive: (editor) => editor?.isActive("heading", { level: 2 }) ?? false
|
189
246
|
},
|
190
247
|
{
|
191
248
|
name: "Heading 3",
|
192
|
-
icon:
|
193
|
-
command: (
|
194
|
-
isActive: (
|
249
|
+
icon: Looks3Icon,
|
250
|
+
command: (editor) => editor?.chain().focus().toggleHeading({ level: 3 }).run(),
|
251
|
+
isActive: (editor) => editor?.isActive("heading", { level: 3 }) ?? false
|
195
252
|
},
|
196
253
|
{
|
197
254
|
name: "To-do List",
|
198
|
-
icon:
|
199
|
-
command: (
|
200
|
-
isActive: (
|
255
|
+
icon: CheckBoxIcon,
|
256
|
+
command: (editor) => editor?.chain().focus().toggleTaskList().run(),
|
257
|
+
isActive: (editor) => editor?.isActive("taskItem") ?? false
|
201
258
|
},
|
202
259
|
{
|
203
260
|
name: "Bullet List",
|
204
|
-
icon:
|
205
|
-
command: (
|
206
|
-
isActive: (
|
261
|
+
icon: FormatListBulletedIcon,
|
262
|
+
command: (editor) => editor?.chain().focus().toggleBulletList().run(),
|
263
|
+
isActive: (editor) => editor?.isActive("bulletList") ?? false
|
207
264
|
},
|
208
265
|
{
|
209
266
|
name: "Numbered List",
|
210
|
-
icon:
|
211
|
-
command: (
|
212
|
-
isActive: (
|
267
|
+
icon: FormatListNumberedIcon,
|
268
|
+
command: (editor) => editor?.chain().focus().toggleOrderedList().run(),
|
269
|
+
isActive: (editor) => editor?.isActive("orderedList") ?? false
|
213
270
|
},
|
214
271
|
{
|
215
272
|
name: "Quote",
|
216
|
-
icon:
|
217
|
-
command: (
|
218
|
-
isActive: (
|
273
|
+
icon: FormatQuoteIcon,
|
274
|
+
command: (editor) => editor?.chain().focus().toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
|
275
|
+
isActive: (editor) => editor?.isActive("blockquote") ?? false
|
219
276
|
},
|
220
277
|
{
|
221
278
|
name: "Code",
|
222
|
-
icon:
|
223
|
-
command: (
|
224
|
-
isActive: (
|
279
|
+
icon: CodeIcon,
|
280
|
+
command: (editor) => editor?.chain().focus().toggleCodeBlock().run(),
|
281
|
+
isActive: (editor) => editor?.isActive("codeBlock") ?? false
|
225
282
|
}
|
226
|
-
]
|
227
|
-
|
228
|
-
|
283
|
+
];
|
284
|
+
const NodeSelector = ({
|
285
|
+
open,
|
286
|
+
onOpenChange
|
229
287
|
}) => {
|
230
|
-
const { editor
|
231
|
-
if (!
|
232
|
-
const
|
288
|
+
const { editor } = useCurrentEditor();
|
289
|
+
if (!editor) return null;
|
290
|
+
const activeItem = items.filter((item) => item.isActive(editor)).pop() ?? {
|
233
291
|
name: "Multiple"
|
234
292
|
};
|
235
|
-
return /* @__PURE__ */
|
236
|
-
|
293
|
+
return /* @__PURE__ */ jsx(
|
294
|
+
Popover,
|
237
295
|
{
|
238
296
|
sideOffset: 5,
|
239
297
|
align: "start",
|
240
298
|
className: "w-48 p-1",
|
241
|
-
trigger: /* @__PURE__ */
|
242
|
-
|
299
|
+
trigger: /* @__PURE__ */ jsxs(
|
300
|
+
Button,
|
243
301
|
{
|
244
302
|
variant: "text",
|
245
303
|
className: "gap-2 rounded-none",
|
246
304
|
color: "text",
|
247
305
|
children: [
|
248
|
-
/* @__PURE__ */
|
249
|
-
/* @__PURE__ */
|
306
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-sm", children: activeItem.name }),
|
307
|
+
/* @__PURE__ */ jsx(ExpandMoreIcon, { size: "small" })
|
250
308
|
]
|
251
309
|
}
|
252
310
|
),
|
253
|
-
modal:
|
254
|
-
open
|
255
|
-
onOpenChange
|
256
|
-
children:
|
257
|
-
|
311
|
+
modal: true,
|
312
|
+
open,
|
313
|
+
onOpenChange,
|
314
|
+
children: items.map((item, index) => /* @__PURE__ */ jsxs(
|
315
|
+
EditorBubbleItem,
|
258
316
|
{
|
259
|
-
onSelect: (
|
260
|
-
|
317
|
+
onSelect: (editor2) => {
|
318
|
+
item.command(editor2);
|
319
|
+
onOpenChange(false);
|
261
320
|
},
|
262
321
|
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
322
|
children: [
|
264
|
-
/* @__PURE__ */
|
265
|
-
/* @__PURE__ */
|
266
|
-
/* @__PURE__ */
|
323
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [
|
324
|
+
/* @__PURE__ */ jsx(item.icon, { size: "smallest" }),
|
325
|
+
/* @__PURE__ */ jsx("span", { children: item.name })
|
267
326
|
] }),
|
268
|
-
|
327
|
+
activeItem.name === item.name && /* @__PURE__ */ jsx(CheckIcon, { size: "smallest" })
|
269
328
|
]
|
270
329
|
},
|
271
|
-
|
330
|
+
index
|
272
331
|
))
|
273
332
|
}
|
274
333
|
);
|
275
334
|
};
|
276
|
-
function
|
335
|
+
function isValidUrl(url) {
|
277
336
|
try {
|
278
|
-
|
279
|
-
|
280
|
-
|
337
|
+
new URL(url);
|
338
|
+
return true;
|
339
|
+
} catch (e) {
|
340
|
+
return false;
|
281
341
|
}
|
282
342
|
}
|
283
|
-
function
|
284
|
-
if (
|
343
|
+
function getUrlFromString(str) {
|
344
|
+
if (isValidUrl(str)) return str;
|
285
345
|
try {
|
286
|
-
|
287
|
-
|
346
|
+
if (str.includes(".") && !str.includes(" ")) {
|
347
|
+
return new URL(`https://${str}`).toString();
|
348
|
+
}
|
349
|
+
return null;
|
350
|
+
} catch (e) {
|
288
351
|
return null;
|
289
352
|
}
|
290
353
|
}
|
291
|
-
const
|
292
|
-
open
|
293
|
-
onOpenChange
|
354
|
+
const LinkSelector = ({
|
355
|
+
open,
|
356
|
+
onOpenChange
|
294
357
|
}) => {
|
295
|
-
const
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
358
|
+
const inputRef = useRef(null);
|
359
|
+
const { editor } = useCurrentEditor();
|
360
|
+
useEffect(() => {
|
361
|
+
inputRef.current && inputRef.current?.focus();
|
362
|
+
});
|
363
|
+
if (!editor) return null;
|
364
|
+
return /* @__PURE__ */ jsx(
|
365
|
+
Popover,
|
300
366
|
{
|
301
|
-
modal:
|
302
|
-
open
|
303
|
-
onOpenChange
|
304
|
-
trigger: /* @__PURE__ */
|
305
|
-
|
367
|
+
modal: true,
|
368
|
+
open,
|
369
|
+
onOpenChange,
|
370
|
+
trigger: /* @__PURE__ */ jsx(
|
371
|
+
Button,
|
306
372
|
{
|
307
373
|
variant: "text",
|
308
374
|
className: "gap-2 rounded-none",
|
309
375
|
color: "text",
|
310
|
-
children: /* @__PURE__ */
|
311
|
-
"text-blue-500":
|
376
|
+
children: /* @__PURE__ */ jsx("p", { className: cls("underline decoration-stone-400 underline-offset-4", {
|
377
|
+
"text-blue-500": editor.isActive("link")
|
312
378
|
}), children: "Link" })
|
313
379
|
}
|
314
380
|
),
|
315
|
-
children: /* @__PURE__ */
|
381
|
+
children: /* @__PURE__ */ jsxs(
|
316
382
|
"form",
|
317
383
|
{
|
318
|
-
onSubmit: (
|
319
|
-
const
|
320
|
-
|
321
|
-
const
|
322
|
-
|
384
|
+
onSubmit: (e) => {
|
385
|
+
const target = e.currentTarget;
|
386
|
+
e.preventDefault();
|
387
|
+
const input = target[0];
|
388
|
+
const url = getUrlFromString(input.value);
|
389
|
+
url && editor.chain().focus().setLink({ href: url }).run();
|
323
390
|
},
|
324
391
|
className: "flex p-1",
|
325
392
|
children: [
|
326
|
-
/* @__PURE__ */
|
393
|
+
/* @__PURE__ */ jsx(
|
327
394
|
"input",
|
328
395
|
{
|
329
|
-
ref:
|
330
|
-
autoFocus:
|
396
|
+
ref: inputRef,
|
397
|
+
autoFocus: open,
|
331
398
|
placeholder: "Paste a link",
|
332
|
-
defaultValue:
|
399
|
+
defaultValue: editor.getAttributes("link").href || "",
|
333
400
|
className: "text-gray-900 dark:text-white flex-grow bg-transparent p-1 text-sm outline-none"
|
334
401
|
}
|
335
402
|
),
|
336
|
-
|
337
|
-
|
403
|
+
editor.getAttributes("link").href ? /* @__PURE__ */ jsx(
|
404
|
+
Button,
|
338
405
|
{
|
339
406
|
size: "small",
|
340
407
|
variant: "text",
|
@@ -342,375 +409,485 @@ const ut = ({
|
|
342
409
|
color: "text",
|
343
410
|
className: "flex items-center",
|
344
411
|
onClick: () => {
|
345
|
-
|
412
|
+
editor.chain().focus().unsetLink().run();
|
346
413
|
},
|
347
|
-
children: /* @__PURE__ */
|
414
|
+
children: /* @__PURE__ */ jsx(DeleteIcon, { size: "small" })
|
348
415
|
}
|
349
|
-
) : /* @__PURE__ */
|
350
|
-
|
416
|
+
) : /* @__PURE__ */ jsx(
|
417
|
+
Button,
|
351
418
|
{
|
352
419
|
size: "small",
|
353
420
|
variant: "text",
|
354
|
-
children: /* @__PURE__ */
|
421
|
+
children: /* @__PURE__ */ jsx(CheckIcon, { size: "small" })
|
355
422
|
}
|
356
423
|
)
|
357
424
|
]
|
358
425
|
}
|
359
426
|
)
|
360
427
|
}
|
361
|
-
)
|
362
|
-
}
|
363
|
-
|
364
|
-
|
428
|
+
);
|
429
|
+
};
|
430
|
+
const TextButtons = () => {
|
431
|
+
const { editor } = useCurrentEditor();
|
432
|
+
if (!editor) return null;
|
433
|
+
const items2 = [
|
365
434
|
{
|
366
435
|
name: "bold",
|
367
|
-
isActive: (
|
368
|
-
command: (
|
369
|
-
icon:
|
436
|
+
isActive: (editor2) => editor2?.isActive("bold") ?? false,
|
437
|
+
command: (editor2) => editor2?.chain().focus().toggleBold().run(),
|
438
|
+
icon: FormatBoldIcon
|
370
439
|
},
|
371
440
|
{
|
372
441
|
name: "italic",
|
373
|
-
isActive: (
|
374
|
-
command: (
|
375
|
-
icon:
|
442
|
+
isActive: (editor2) => editor2?.isActive("italic") ?? false,
|
443
|
+
command: (editor2) => editor2?.chain().focus().toggleItalic().run(),
|
444
|
+
icon: FormatItalicIcon
|
376
445
|
},
|
377
446
|
{
|
378
447
|
name: "underline",
|
379
|
-
isActive: (
|
380
|
-
command: (
|
381
|
-
icon:
|
448
|
+
isActive: (editor2) => editor2?.isActive("underline") ?? false,
|
449
|
+
command: (editor2) => editor2?.chain().focus().toggleUnderline().run(),
|
450
|
+
icon: FormatUnderlinedIcon
|
382
451
|
},
|
383
452
|
{
|
384
453
|
name: "strike",
|
385
|
-
isActive: (
|
386
|
-
command: (
|
387
|
-
icon:
|
454
|
+
isActive: (editor2) => editor2?.isActive("strike") ?? false,
|
455
|
+
command: (editor2) => editor2?.chain().focus().toggleStrike().run(),
|
456
|
+
icon: FormatStrikethroughIcon
|
388
457
|
},
|
389
458
|
{
|
390
459
|
name: "code",
|
391
|
-
isActive: (
|
392
|
-
command: (
|
393
|
-
icon:
|
460
|
+
isActive: (editor2) => editor2?.isActive("code") ?? false,
|
461
|
+
command: (editor2) => editor2?.chain().focus().toggleCode().run(),
|
462
|
+
icon: CodeIcon
|
394
463
|
}
|
395
|
-
]
|
396
|
-
|
464
|
+
];
|
465
|
+
return /* @__PURE__ */ jsx("div", { className: "flex", children: items2.map((item, index) => /* @__PURE__ */ jsx(
|
466
|
+
EditorBubbleItem,
|
397
467
|
{
|
398
|
-
onSelect: (
|
399
|
-
|
468
|
+
onSelect: (editor2) => {
|
469
|
+
item.command(editor2);
|
400
470
|
},
|
401
|
-
children: /* @__PURE__ */
|
402
|
-
|
471
|
+
children: /* @__PURE__ */ jsx(
|
472
|
+
Button,
|
403
473
|
{
|
404
474
|
size: "small",
|
405
475
|
color: "text",
|
406
476
|
className: "gap-2 rounded-none h-full",
|
407
477
|
variant: "text",
|
408
|
-
children: /* @__PURE__ */
|
409
|
-
|
478
|
+
children: /* @__PURE__ */ jsx(
|
479
|
+
item.icon,
|
410
480
|
{
|
411
|
-
className:
|
412
|
-
"text-inherit": !
|
413
|
-
"text-blue-500":
|
481
|
+
className: cls({
|
482
|
+
"text-inherit": !item.isActive(editor),
|
483
|
+
"text-blue-500": item.isActive(editor)
|
414
484
|
})
|
415
485
|
}
|
416
486
|
)
|
417
487
|
}
|
418
488
|
)
|
419
489
|
},
|
420
|
-
|
421
|
-
)) })
|
490
|
+
index
|
491
|
+
)) });
|
422
492
|
};
|
423
|
-
function
|
424
|
-
const
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
493
|
+
function useDebouncedCallback(value, callback, immediate, timeoutMs = 300) {
|
494
|
+
const pendingUpdate = React.useRef(false);
|
495
|
+
const performUpdate = () => {
|
496
|
+
callback();
|
497
|
+
pendingUpdate.current = false;
|
498
|
+
};
|
499
|
+
const handlerRef = React.useRef(void 0);
|
500
|
+
React.useEffect(
|
501
|
+
() => {
|
502
|
+
pendingUpdate.current = true;
|
503
|
+
clearTimeout(handlerRef.current);
|
504
|
+
handlerRef.current = setTimeout(performUpdate, timeoutMs);
|
505
|
+
return () => {
|
506
|
+
};
|
507
|
+
},
|
508
|
+
[immediate, value]
|
431
509
|
);
|
432
510
|
}
|
433
|
-
function
|
434
|
-
|
435
|
-
|
436
|
-
})
|
511
|
+
function removeClassesFromJson(jsonObj) {
|
512
|
+
if (Array.isArray(jsonObj)) {
|
513
|
+
return jsonObj.map((item) => removeClassesFromJson(item));
|
514
|
+
} else if (typeof jsonObj === "object" && jsonObj !== null) {
|
515
|
+
if (jsonObj.attrs && typeof jsonObj.attrs === "object" && "class" in jsonObj.attrs) {
|
516
|
+
delete jsonObj.attrs.class;
|
517
|
+
}
|
518
|
+
Object.keys(jsonObj).forEach((key) => {
|
519
|
+
jsonObj[key] = removeClassesFromJson(jsonObj[key]);
|
520
|
+
});
|
521
|
+
}
|
522
|
+
return jsonObj;
|
437
523
|
}
|
438
|
-
const
|
524
|
+
const placeholder = PlaceholderExtension;
|
525
|
+
const tiptapLink = TiptapLink.configure({
|
439
526
|
HTMLAttributes: {
|
440
|
-
class:
|
527
|
+
class: cls(
|
441
528
|
"text-gray-600 dark:text-slate-300 underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer"
|
442
529
|
)
|
443
530
|
}
|
444
|
-
})
|
531
|
+
});
|
532
|
+
const taskList = TaskList.configure({
|
445
533
|
HTMLAttributes: {
|
446
|
-
class:
|
534
|
+
class: cls("not-prose")
|
447
535
|
}
|
448
|
-
})
|
536
|
+
});
|
537
|
+
const taskItem = TaskItem.configure({
|
449
538
|
HTMLAttributes: {
|
450
|
-
class:
|
539
|
+
class: cls("flex items-start my-4")
|
451
540
|
},
|
452
|
-
nested:
|
453
|
-
})
|
541
|
+
nested: true
|
542
|
+
});
|
543
|
+
const horizontalRule = Horizontal.configure({
|
454
544
|
HTMLAttributes: {
|
455
|
-
class:
|
545
|
+
class: cls("mt-4 mb-6 border-t", defaultBorderMixin)
|
456
546
|
}
|
457
|
-
})
|
547
|
+
});
|
548
|
+
const starterKit = StarterKit.configure({
|
458
549
|
bulletList: {
|
459
550
|
HTMLAttributes: {
|
460
|
-
class:
|
551
|
+
class: cls("list-disc list-outside leading-3 -mt-2")
|
461
552
|
}
|
462
553
|
},
|
463
554
|
orderedList: {
|
464
555
|
HTMLAttributes: {
|
465
|
-
class:
|
556
|
+
class: cls("list-decimal list-outside leading-3 -mt-2")
|
466
557
|
}
|
467
558
|
},
|
468
559
|
listItem: {
|
469
560
|
HTMLAttributes: {
|
470
|
-
class:
|
561
|
+
class: cls("leading-normal -mb-2")
|
471
562
|
}
|
472
563
|
},
|
473
564
|
blockquote: {
|
474
565
|
HTMLAttributes: {
|
475
|
-
class:
|
566
|
+
class: cls("border-l-4 border-primary")
|
476
567
|
}
|
477
568
|
},
|
478
569
|
codeBlock: {
|
479
570
|
HTMLAttributes: {
|
480
|
-
class:
|
571
|
+
class: cls("rounded bg-blue-50 dark:bg-gray-700 border p-5 font-mono font-medium", defaultBorderMixin)
|
481
572
|
}
|
482
573
|
},
|
483
574
|
code: {
|
484
575
|
HTMLAttributes: {
|
485
|
-
class:
|
576
|
+
class: cls("rounded-md bg-slate-50 dark:bg-gray-700 px-1.5 py-1 font-mono font-medium"),
|
486
577
|
spellcheck: "false"
|
487
578
|
}
|
488
579
|
},
|
489
|
-
horizontalRule:
|
580
|
+
horizontalRule: false,
|
490
581
|
dropcursor: {
|
491
582
|
color: "#DBEAFE",
|
492
583
|
width: 4
|
493
584
|
},
|
494
|
-
gapcursor:
|
585
|
+
gapcursor: false
|
495
586
|
});
|
496
|
-
async function
|
497
|
-
const { schema
|
498
|
-
let
|
499
|
-
const
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
587
|
+
async function onFileRead(plugin, view, readerEvent, pos, upload, image) {
|
588
|
+
const { schema } = view.state;
|
589
|
+
let decorationSet = plugin.getState(view.state);
|
590
|
+
const placeholder2 = document.createElement("div");
|
591
|
+
const imageElement = document.createElement("img");
|
592
|
+
imageElement.setAttribute("class", "opacity-40 rounded-lg border border-stone-200");
|
593
|
+
imageElement.src = readerEvent.target?.result;
|
594
|
+
placeholder2.appendChild(imageElement);
|
595
|
+
const deco = Decoration.widget(pos, placeholder2);
|
596
|
+
decorationSet = decorationSet?.add(view.state.doc, [deco]);
|
597
|
+
view.dispatch(view.state.tr.setMeta(plugin, { decorationSet }));
|
598
|
+
const src = await upload(image);
|
599
|
+
console.log("uploaded image", src);
|
600
|
+
const imageNode = schema.nodes.image.create({ src });
|
601
|
+
const tr = view.state.tr.replaceWith(pos, pos, imageNode);
|
602
|
+
decorationSet = decorationSet?.remove([deco]);
|
603
|
+
tr.setMeta(plugin, { decorationSet });
|
604
|
+
view.dispatch(tr);
|
507
605
|
}
|
508
|
-
const
|
509
|
-
const
|
606
|
+
const dropImagePlugin = (upload) => {
|
607
|
+
const plugin = new Plugin({
|
510
608
|
state: {
|
511
609
|
// Initialize the plugin state with an empty DecorationSet
|
512
|
-
init: () =>
|
610
|
+
init: () => DecorationSet.empty,
|
513
611
|
// Apply transactions to update the state
|
514
|
-
apply: (
|
515
|
-
const
|
516
|
-
|
612
|
+
apply: (tr, old) => {
|
613
|
+
const meta = tr.getMeta(plugin);
|
614
|
+
if (meta && meta.decorationSet) {
|
615
|
+
return meta.decorationSet;
|
616
|
+
}
|
617
|
+
return old.map(tr.mapping, tr.doc);
|
517
618
|
}
|
518
619
|
},
|
519
620
|
props: {
|
520
621
|
handleDOMEvents: {
|
521
|
-
drop: (
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
622
|
+
drop: (view, event) => {
|
623
|
+
console.log("drop event", event);
|
624
|
+
if (!event.dataTransfer?.files || event.dataTransfer?.files.length === 0) {
|
625
|
+
return false;
|
626
|
+
}
|
627
|
+
event.preventDefault();
|
628
|
+
const files = Array.from(event.dataTransfer.files);
|
629
|
+
const images = files.filter((file) => /image/i.test(file.type));
|
630
|
+
if (images.length === 0) return false;
|
631
|
+
images.forEach((image) => {
|
632
|
+
const position = view.posAtCoords({ left: event.clientX, top: event.clientY });
|
633
|
+
if (!position) return;
|
634
|
+
const reader = new FileReader();
|
635
|
+
reader.onload = async (readerEvent) => {
|
636
|
+
await onFileRead(plugin, view, readerEvent, position.pos, upload, image);
|
637
|
+
};
|
638
|
+
reader.readAsDataURL(image);
|
639
|
+
});
|
640
|
+
return true;
|
534
641
|
}
|
535
642
|
},
|
536
|
-
handlePaste(
|
537
|
-
const
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
643
|
+
handlePaste(view, event, slice) {
|
644
|
+
const items2 = Array.from(event.clipboardData?.items || []);
|
645
|
+
const pos = view.state.selection.from;
|
646
|
+
console.log("pos", pos);
|
647
|
+
let anyImageFound = false;
|
648
|
+
items2.forEach((item) => {
|
649
|
+
const image = item.getAsFile();
|
650
|
+
console.log("image", image);
|
651
|
+
if (image) {
|
652
|
+
anyImageFound = true;
|
653
|
+
const reader = new FileReader();
|
654
|
+
reader.onload = async (readerEvent) => {
|
655
|
+
await onFileRead(plugin, view, readerEvent, pos, upload, image);
|
656
|
+
};
|
657
|
+
reader.readAsDataURL(image);
|
548
658
|
}
|
549
|
-
})
|
659
|
+
});
|
660
|
+
return anyImageFound;
|
550
661
|
},
|
551
|
-
decorations(
|
552
|
-
return
|
662
|
+
decorations(state) {
|
663
|
+
return plugin.getState(state);
|
553
664
|
}
|
554
665
|
},
|
555
|
-
view(
|
666
|
+
view(editorView) {
|
556
667
|
return {
|
557
|
-
update(
|
558
|
-
const
|
559
|
-
|
668
|
+
update(view, prevState) {
|
669
|
+
const prevDecos = plugin.getState(prevState);
|
670
|
+
const newDecos = plugin.getState(view.state);
|
671
|
+
if (prevDecos !== newDecos) {
|
672
|
+
view.updateState(view.state);
|
673
|
+
}
|
560
674
|
}
|
561
675
|
};
|
562
676
|
}
|
563
677
|
});
|
564
|
-
return
|
565
|
-
}
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
678
|
+
return plugin;
|
679
|
+
};
|
680
|
+
const createImageExtension = (uploadFn) => {
|
681
|
+
return TiptapImage.extend({
|
682
|
+
addProseMirrorPlugins() {
|
683
|
+
return [dropImagePlugin(uploadFn)];
|
684
|
+
}
|
685
|
+
}).configure({
|
686
|
+
allowBase64: true,
|
687
|
+
HTMLAttributes: {
|
688
|
+
class: cls("rounded-lg border", defaultBorderMixin)
|
689
|
+
}
|
690
|
+
});
|
691
|
+
};
|
692
|
+
const CustomKeymap = Extension.create({
|
575
693
|
name: "CustomKeymap",
|
576
694
|
addCommands() {
|
577
695
|
return {
|
578
|
-
selectTextWithinNodeBoundaries: () => ({ editor
|
579
|
-
const { state
|
580
|
-
|
581
|
-
|
582
|
-
|
696
|
+
selectTextWithinNodeBoundaries: () => ({ editor, commands }) => {
|
697
|
+
const { state } = editor;
|
698
|
+
const { tr } = state;
|
699
|
+
const startNodePos = tr.selection.$from.start();
|
700
|
+
const endNodePos = tr.selection.$to.end();
|
701
|
+
return commands.setTextSelection({
|
702
|
+
from: startNodePos,
|
703
|
+
to: endNodePos
|
583
704
|
});
|
584
705
|
}
|
585
706
|
};
|
586
707
|
},
|
587
708
|
addKeyboardShortcuts() {
|
588
709
|
return {
|
589
|
-
"Mod-a": ({ editor
|
590
|
-
const { state
|
591
|
-
|
710
|
+
"Mod-a": ({ editor }) => {
|
711
|
+
const { state } = editor;
|
712
|
+
const { tr } = state;
|
713
|
+
const startSelectionPos = tr.selection.from;
|
714
|
+
const endSelectionPos = tr.selection.to;
|
715
|
+
const startNodePos = tr.selection.$from.start();
|
716
|
+
const endNodePos = tr.selection.$to.end();
|
717
|
+
const isCurrentTextSelectionNotExtendedToNodeBoundaries = startSelectionPos > startNodePos || endSelectionPos < endNodePos;
|
718
|
+
if (isCurrentTextSelectionNotExtendedToNodeBoundaries) {
|
719
|
+
editor.chain().selectTextWithinNodeBoundaries().run();
|
720
|
+
return true;
|
721
|
+
}
|
722
|
+
return false;
|
592
723
|
}
|
593
724
|
};
|
594
725
|
}
|
595
726
|
});
|
596
|
-
function
|
597
|
-
const
|
727
|
+
function absoluteRect(node) {
|
728
|
+
const data = node.getBoundingClientRect();
|
598
729
|
return {
|
599
|
-
top:
|
600
|
-
left:
|
601
|
-
width:
|
730
|
+
top: data.top,
|
731
|
+
left: data.left,
|
732
|
+
width: data.width
|
602
733
|
};
|
603
734
|
}
|
604
|
-
function
|
605
|
-
return document.elementsFromPoint(
|
606
|
-
(
|
735
|
+
function nodeDOMAtCoords(coords) {
|
736
|
+
return document.elementsFromPoint(coords.x, coords.y).find(
|
737
|
+
(elem) => elem.parentElement?.matches?.(".ProseMirror") || elem.matches(
|
607
738
|
["li", "p:not(:first-child)", "pre", "blockquote", "h1, h2, h3, h4, h5, h6"].join(", ")
|
608
739
|
)
|
609
740
|
);
|
610
741
|
}
|
611
|
-
function
|
612
|
-
const
|
613
|
-
return
|
614
|
-
left:
|
615
|
-
top:
|
742
|
+
function nodePosAtDOM(node, view, options) {
|
743
|
+
const boundingRect = node.getBoundingClientRect();
|
744
|
+
return view.posAtCoords({
|
745
|
+
left: boundingRect.left + 50 + options.dragHandleWidth,
|
746
|
+
top: boundingRect.top + 1
|
616
747
|
})?.inside;
|
617
748
|
}
|
618
|
-
function
|
619
|
-
function
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
749
|
+
function DragHandle(options) {
|
750
|
+
function handleDragStart(event, view) {
|
751
|
+
view.focus();
|
752
|
+
if (!event.dataTransfer) return;
|
753
|
+
const node = nodeDOMAtCoords({
|
754
|
+
x: event.clientX + 50 + options.dragHandleWidth,
|
755
|
+
y: event.clientY
|
624
756
|
});
|
625
|
-
if (!(
|
626
|
-
const
|
627
|
-
if (
|
628
|
-
|
629
|
-
const
|
630
|
-
|
757
|
+
if (!(node instanceof Element)) return;
|
758
|
+
const nodePos = nodePosAtDOM(node, view, options);
|
759
|
+
if (nodePos == null || nodePos < 0) return;
|
760
|
+
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)));
|
761
|
+
const slice = view.state.selection.content();
|
762
|
+
const { dom, text } = __serializeForClipboard(view, slice);
|
763
|
+
event.dataTransfer.clearData();
|
764
|
+
event.dataTransfer.setData("text/html", dom.innerHTML);
|
765
|
+
event.dataTransfer.setData("text/plain", text);
|
766
|
+
event.dataTransfer.effectAllowed = "copyMove";
|
767
|
+
event.dataTransfer.setDragImage(node, 0, 0);
|
768
|
+
view.dragging = { slice, move: event.ctrlKey };
|
631
769
|
}
|
632
|
-
function
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
770
|
+
function handleClick(event, view) {
|
771
|
+
view.focus();
|
772
|
+
view.dom.classList.remove("dragging");
|
773
|
+
const node = nodeDOMAtCoords({
|
774
|
+
x: event.clientX + 50 + options.dragHandleWidth,
|
775
|
+
y: event.clientY
|
637
776
|
});
|
638
|
-
if (!(
|
639
|
-
const
|
640
|
-
|
777
|
+
if (!(node instanceof Element)) return;
|
778
|
+
const nodePos = nodePosAtDOM(node, view, options);
|
779
|
+
if (!nodePos) return;
|
780
|
+
view.dispatch(view.state.tr.setSelection(NodeSelection.create(view.state.doc, nodePos)));
|
641
781
|
}
|
642
|
-
let
|
643
|
-
function
|
644
|
-
|
782
|
+
let dragHandleElement = null;
|
783
|
+
function hideDragHandle() {
|
784
|
+
if (dragHandleElement) {
|
785
|
+
dragHandleElement.classList.add("hide");
|
786
|
+
}
|
645
787
|
}
|
646
|
-
function
|
647
|
-
|
788
|
+
function showDragHandle() {
|
789
|
+
if (dragHandleElement) {
|
790
|
+
dragHandleElement.classList.remove("hide");
|
791
|
+
}
|
648
792
|
}
|
649
|
-
return new
|
650
|
-
view: (
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
}
|
658
|
-
|
793
|
+
return new Plugin$1({
|
794
|
+
view: (view) => {
|
795
|
+
dragHandleElement = document.createElement("div");
|
796
|
+
dragHandleElement.draggable = true;
|
797
|
+
dragHandleElement.dataset.dragHandle = "";
|
798
|
+
dragHandleElement.classList.add("drag-handle");
|
799
|
+
dragHandleElement.addEventListener("dragstart", (e) => {
|
800
|
+
handleDragStart(e, view);
|
801
|
+
});
|
802
|
+
dragHandleElement.addEventListener("click", (e) => {
|
803
|
+
handleClick(e, view);
|
804
|
+
});
|
805
|
+
hideDragHandle();
|
806
|
+
view?.dom?.parentElement?.appendChild(dragHandleElement);
|
807
|
+
return {
|
808
|
+
destroy: () => {
|
809
|
+
dragHandleElement?.remove?.();
|
810
|
+
dragHandleElement = null;
|
811
|
+
}
|
812
|
+
};
|
813
|
+
},
|
659
814
|
props: {
|
660
815
|
handleDOMEvents: {
|
661
|
-
mousemove: (
|
662
|
-
if (!
|
816
|
+
mousemove: (view, event) => {
|
817
|
+
if (!view.editable) {
|
663
818
|
return;
|
664
|
-
|
665
|
-
|
666
|
-
|
819
|
+
}
|
820
|
+
const node = nodeDOMAtCoords({
|
821
|
+
x: event.clientX + 50 + options.dragHandleWidth,
|
822
|
+
y: event.clientY
|
667
823
|
});
|
668
|
-
if (!(
|
669
|
-
|
824
|
+
if (!(node instanceof Element)) {
|
825
|
+
hideDragHandle();
|
670
826
|
return;
|
671
827
|
}
|
672
|
-
const
|
673
|
-
|
828
|
+
const compStyle = window.getComputedStyle(node);
|
829
|
+
const lineHeight = parseInt(compStyle.lineHeight, 10);
|
830
|
+
const paddingTop = parseInt(compStyle.paddingTop, 10);
|
831
|
+
const rect = absoluteRect(node);
|
832
|
+
rect.top += (lineHeight - 24) / 2;
|
833
|
+
rect.top += paddingTop;
|
834
|
+
if (node.matches("ul:not([data-type=taskList]) li, ol li")) {
|
835
|
+
rect.left -= options.dragHandleWidth;
|
836
|
+
}
|
837
|
+
rect.width = options.dragHandleWidth;
|
838
|
+
if (!dragHandleElement) return;
|
839
|
+
dragHandleElement.style.left = `${rect.left - rect.width}px`;
|
840
|
+
dragHandleElement.style.top = `${rect.top}px`;
|
841
|
+
showDragHandle();
|
674
842
|
},
|
675
843
|
keydown: () => {
|
676
|
-
|
844
|
+
hideDragHandle();
|
677
845
|
},
|
678
846
|
mousewheel: () => {
|
679
|
-
|
847
|
+
hideDragHandle();
|
680
848
|
},
|
681
849
|
// dragging class is used for CSS
|
682
|
-
dragstart: (
|
683
|
-
|
850
|
+
dragstart: (view) => {
|
851
|
+
view.dom.classList.add("dragging");
|
684
852
|
},
|
685
|
-
drop: (
|
686
|
-
|
853
|
+
drop: (view) => {
|
854
|
+
view.dom.classList.remove("dragging");
|
687
855
|
},
|
688
|
-
dragend: (
|
689
|
-
|
856
|
+
dragend: (view) => {
|
857
|
+
view.dom.classList.remove("dragging");
|
690
858
|
}
|
691
859
|
}
|
692
860
|
}
|
693
861
|
});
|
694
862
|
}
|
695
|
-
const
|
863
|
+
const DragAndDrop = Extension.create({
|
696
864
|
name: "dragAndDrop",
|
697
865
|
addProseMirrorPlugins() {
|
698
866
|
return [
|
699
|
-
|
867
|
+
DragHandle({
|
700
868
|
dragHandleWidth: 24
|
701
869
|
})
|
702
870
|
];
|
703
871
|
}
|
704
|
-
})
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
872
|
+
});
|
873
|
+
const FireCMSEditor = ({
|
874
|
+
handleImageUpload,
|
875
|
+
initialContent,
|
876
|
+
onJsonContentChange,
|
877
|
+
onHtmlContentChange,
|
878
|
+
onMarkdownContentChange
|
710
879
|
}) => {
|
711
|
-
const
|
880
|
+
const defaultEditorProps = {
|
712
881
|
handleDOMEvents: {
|
713
|
-
keydown: (
|
882
|
+
keydown: (_view, event) => {
|
883
|
+
if (["ArrowUp", "ArrowDown", "Enter"].includes(event.key)) {
|
884
|
+
const slashCommand2 = document.querySelector("#slash-command");
|
885
|
+
if (slashCommand2) {
|
886
|
+
return true;
|
887
|
+
}
|
888
|
+
}
|
889
|
+
return false;
|
890
|
+
}
|
714
891
|
}
|
715
892
|
// handlePaste: (view, event) => {
|
716
893
|
// if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) {
|
@@ -744,205 +921,235 @@ const At = B.create({
|
|
744
921
|
// }
|
745
922
|
// return false;
|
746
923
|
// }
|
747
|
-
}
|
924
|
+
};
|
925
|
+
const suggestionItems = createSuggestionItems([
|
748
926
|
{
|
749
927
|
title: "Text",
|
750
928
|
description: "Just start typing with plain text.",
|
751
929
|
searchTerms: ["p", "paragraph"],
|
752
|
-
icon: /* @__PURE__ */
|
753
|
-
command: ({ editor
|
754
|
-
|
930
|
+
icon: /* @__PURE__ */ jsx(TextFieldsIcon, { size: 18 }),
|
931
|
+
command: ({ editor, range }) => {
|
932
|
+
editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
|
755
933
|
}
|
756
934
|
},
|
757
935
|
{
|
758
936
|
title: "To-do List",
|
759
937
|
description: "Track tasks with a to-do list.",
|
760
938
|
searchTerms: ["todo", "task", "list", "check", "checkbox"],
|
761
|
-
icon: /* @__PURE__ */
|
762
|
-
command: ({ editor
|
763
|
-
|
939
|
+
icon: /* @__PURE__ */ jsx(CheckBoxIcon, { size: 18 }),
|
940
|
+
command: ({ editor, range }) => {
|
941
|
+
editor.chain().focus().deleteRange(range).toggleTaskList().run();
|
764
942
|
}
|
765
943
|
},
|
766
944
|
{
|
767
945
|
title: "Heading 1",
|
768
946
|
description: "Big section heading.",
|
769
947
|
searchTerms: ["title", "big", "large"],
|
770
|
-
icon: /* @__PURE__ */
|
771
|
-
command: ({ editor
|
772
|
-
|
948
|
+
icon: /* @__PURE__ */ jsx(LooksOneIcon, { size: 18 }),
|
949
|
+
command: ({ editor, range }) => {
|
950
|
+
editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
|
773
951
|
}
|
774
952
|
},
|
775
953
|
{
|
776
954
|
title: "Heading 2",
|
777
955
|
description: "Medium section heading.",
|
778
956
|
searchTerms: ["subtitle", "medium"],
|
779
|
-
icon: /* @__PURE__ */
|
780
|
-
command: ({ editor
|
781
|
-
|
957
|
+
icon: /* @__PURE__ */ jsx(LooksTwoIcon, { size: 18 }),
|
958
|
+
command: ({ editor, range }) => {
|
959
|
+
editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
|
782
960
|
}
|
783
961
|
},
|
784
962
|
{
|
785
963
|
title: "Heading 3",
|
786
964
|
description: "Small section heading.",
|
787
965
|
searchTerms: ["subtitle", "small"],
|
788
|
-
icon: /* @__PURE__ */
|
789
|
-
command: ({ editor
|
790
|
-
|
966
|
+
icon: /* @__PURE__ */ jsx(Looks3Icon, { size: 18 }),
|
967
|
+
command: ({ editor, range }) => {
|
968
|
+
editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
|
791
969
|
}
|
792
970
|
},
|
793
971
|
{
|
794
972
|
title: "Bullet List",
|
795
973
|
description: "Create a simple bullet list.",
|
796
974
|
searchTerms: ["unordered", "point"],
|
797
|
-
icon: /* @__PURE__ */
|
798
|
-
command: ({ editor
|
799
|
-
|
975
|
+
icon: /* @__PURE__ */ jsx(FormatListBulletedIcon, { size: 18 }),
|
976
|
+
command: ({ editor, range }) => {
|
977
|
+
editor.chain().focus().deleteRange(range).toggleBulletList().run();
|
800
978
|
}
|
801
979
|
},
|
802
980
|
{
|
803
981
|
title: "Numbered List",
|
804
982
|
description: "Create a list with numbering.",
|
805
983
|
searchTerms: ["ordered"],
|
806
|
-
icon: /* @__PURE__ */
|
807
|
-
command: ({ editor
|
808
|
-
|
984
|
+
icon: /* @__PURE__ */ jsx(FormatListNumberedIcon, { size: 18 }),
|
985
|
+
command: ({ editor, range }) => {
|
986
|
+
editor.chain().focus().deleteRange(range).toggleOrderedList().run();
|
809
987
|
}
|
810
988
|
},
|
811
989
|
{
|
812
990
|
title: "Quote",
|
813
991
|
description: "Capture a quote.",
|
814
992
|
searchTerms: ["blockquote"],
|
815
|
-
icon: /* @__PURE__ */
|
816
|
-
command: ({ editor
|
993
|
+
icon: /* @__PURE__ */ jsx(FormatQuoteIcon, { size: 18 }),
|
994
|
+
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run()
|
817
995
|
},
|
818
996
|
{
|
819
997
|
title: "Code",
|
820
998
|
description: "Capture a code snippet.",
|
821
999
|
searchTerms: ["codeblock"],
|
822
|
-
icon: /* @__PURE__ */
|
823
|
-
command: ({ editor
|
1000
|
+
icon: /* @__PURE__ */ jsx(CodeIcon, { size: 18 }),
|
1001
|
+
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run()
|
824
1002
|
},
|
825
1003
|
{
|
826
1004
|
title: "Image",
|
827
1005
|
description: "Upload an image from your computer.",
|
828
1006
|
searchTerms: ["photo", "picture", "media"],
|
829
|
-
icon: /* @__PURE__ */
|
830
|
-
command: ({ editor
|
831
|
-
|
832
|
-
const
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
1007
|
+
icon: /* @__PURE__ */ jsx(ImageIcon, { size: 18 }),
|
1008
|
+
command: ({ editor, range }) => {
|
1009
|
+
editor.chain().focus().deleteRange(range).run();
|
1010
|
+
const input = document.createElement("input");
|
1011
|
+
input.type = "file";
|
1012
|
+
input.accept = "image/*";
|
1013
|
+
input.onchange = async () => {
|
1014
|
+
if (input.files?.length) {
|
1015
|
+
const file = input.files[0];
|
1016
|
+
if (!file) return;
|
1017
|
+
editor.view.state.selection.from;
|
837
1018
|
}
|
838
|
-
}
|
1019
|
+
};
|
1020
|
+
input.click();
|
839
1021
|
}
|
840
1022
|
}
|
841
|
-
])
|
1023
|
+
]);
|
1024
|
+
const slashCommand = Command.configure({
|
842
1025
|
suggestion: {
|
843
|
-
items: () =>
|
844
|
-
render:
|
1026
|
+
items: () => suggestionItems,
|
1027
|
+
render: renderItems
|
845
1028
|
}
|
846
|
-
})
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
1029
|
+
});
|
1030
|
+
const imageExtension = useMemo(() => createImageExtension(handleImageUpload), []);
|
1031
|
+
const extensions = [
|
1032
|
+
TiptapUnderline,
|
1033
|
+
TextStyle,
|
1034
|
+
Color,
|
1035
|
+
Highlight.configure({
|
1036
|
+
multicolor: true
|
852
1037
|
}),
|
853
|
-
|
854
|
-
html:
|
855
|
-
transformCopiedText:
|
1038
|
+
Markdown.configure({
|
1039
|
+
html: false,
|
1040
|
+
transformCopiedText: true
|
856
1041
|
}),
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
1042
|
+
CustomKeymap,
|
1043
|
+
DragAndDrop,
|
1044
|
+
starterKit,
|
1045
|
+
placeholder,
|
1046
|
+
tiptapLink,
|
862
1047
|
// tiptapImage,
|
863
|
-
|
1048
|
+
imageExtension,
|
864
1049
|
// updatedImage,
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
]
|
870
|
-
|
871
|
-
const
|
872
|
-
|
1050
|
+
taskList,
|
1051
|
+
taskItem,
|
1052
|
+
horizontalRule,
|
1053
|
+
slashCommand
|
1054
|
+
];
|
1055
|
+
const [openNode, setOpenNode] = useState(false);
|
1056
|
+
const [openLink, setOpenLink] = useState(false);
|
1057
|
+
useInjectStyles("Editor", cssStyles);
|
1058
|
+
const editorRef = React.useRef(null);
|
1059
|
+
const [markdownContent, setMarkdownContent] = useState(null);
|
1060
|
+
const [jsonContent, setJsonContent] = useState(null);
|
1061
|
+
const [htmlContent, setHtmlContent] = useState(null);
|
1062
|
+
const onEditorUpdate = (editor) => {
|
1063
|
+
editorRef.current = editor;
|
1064
|
+
if (onMarkdownContentChange) {
|
1065
|
+
setMarkdownContent(editor.storage.markdown.getMarkdown());
|
1066
|
+
}
|
1067
|
+
if (onJsonContentChange) {
|
1068
|
+
setJsonContent(removeClassesFromJson(editor.getJSON()));
|
1069
|
+
}
|
1070
|
+
if (onHtmlContentChange) {
|
1071
|
+
setHtmlContent(editor.getHTML());
|
1072
|
+
}
|
873
1073
|
};
|
874
|
-
|
875
|
-
if (
|
876
|
-
const
|
877
|
-
|
1074
|
+
useDebouncedCallback(markdownContent, () => {
|
1075
|
+
if (editorRef.current) {
|
1076
|
+
const markdown = editorRef.current.storage.markdown.getMarkdown();
|
1077
|
+
onMarkdownContentChange?.(addLineBreakAfterImages(markdown));
|
878
1078
|
}
|
879
|
-
},
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
},
|
1079
|
+
}, false, 500);
|
1080
|
+
useDebouncedCallback(jsonContent, () => {
|
1081
|
+
if (jsonContent)
|
1082
|
+
onJsonContentChange?.(jsonContent);
|
1083
|
+
}, false, 500);
|
1084
|
+
useDebouncedCallback(htmlContent, () => {
|
1085
|
+
if (htmlContent)
|
1086
|
+
onHtmlContentChange?.(htmlContent);
|
1087
|
+
}, false, 500);
|
1088
|
+
if (!initialContent) return null;
|
1089
|
+
return /* @__PURE__ */ jsx("div", { className: "relative w-full p-8", children: /* @__PURE__ */ jsx(EditorRoot, { children: /* @__PURE__ */ jsx(
|
884
1090
|
"div",
|
885
1091
|
{
|
886
1092
|
className: "relative min-h-[500px] w-full bg-white dark:bg-gray-950 rounded-lg",
|
887
|
-
children: /* @__PURE__ */
|
888
|
-
|
1093
|
+
children: /* @__PURE__ */ jsxs(
|
1094
|
+
EditorProvider,
|
889
1095
|
{
|
890
|
-
content:
|
891
|
-
extensions
|
1096
|
+
content: initialContent,
|
1097
|
+
extensions,
|
892
1098
|
editorProps: {
|
893
|
-
...
|
1099
|
+
...defaultEditorProps,
|
894
1100
|
attributes: {
|
895
1101
|
class: "prose-lg prose-headings:font-title font-default focus:outline-none max-w-full p-12"
|
896
1102
|
}
|
897
1103
|
},
|
898
|
-
onUpdate: ({ editor
|
899
|
-
console.debug("Editor updated")
|
1104
|
+
onUpdate: ({ editor }) => {
|
1105
|
+
console.debug("Editor updated");
|
1106
|
+
onEditorUpdate(editor);
|
900
1107
|
},
|
901
1108
|
children: [
|
902
|
-
/* @__PURE__ */
|
903
|
-
|
1109
|
+
/* @__PURE__ */ jsxs(
|
1110
|
+
EditorCommand,
|
904
1111
|
{
|
905
|
-
className:
|
1112
|
+
className: cls("text-gray-900 dark:text-white z-50 h-auto max-h-[330px] w-72 overflow-y-auto rounded-md border bg-white dark:bg-gray-900 px-1 py-2 shadow transition-all", defaultBorderMixin),
|
906
1113
|
children: [
|
907
|
-
/* @__PURE__ */
|
908
|
-
|
909
|
-
|
1114
|
+
/* @__PURE__ */ jsx(EditorCommandEmpty, { className: "px-2 text-gray-700 dark:text-slate-300", children: "No results" }),
|
1115
|
+
suggestionItems.map((item) => /* @__PURE__ */ jsxs(
|
1116
|
+
EditorCommandItem,
|
910
1117
|
{
|
911
|
-
value:
|
912
|
-
onCommand: (
|
1118
|
+
value: item.title,
|
1119
|
+
onCommand: (val) => item?.command?.(val),
|
913
1120
|
className: "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",
|
914
1121
|
children: [
|
915
|
-
/* @__PURE__ */
|
1122
|
+
/* @__PURE__ */ jsx(
|
916
1123
|
"div",
|
917
1124
|
{
|
918
|
-
className:
|
919
|
-
children:
|
1125
|
+
className: cls("flex h-10 w-10 items-center justify-center rounded-md border bg-white dark:bg-gray-900", defaultBorderMixin),
|
1126
|
+
children: item.icon
|
920
1127
|
}
|
921
1128
|
),
|
922
|
-
/* @__PURE__ */
|
923
|
-
/* @__PURE__ */
|
924
|
-
/* @__PURE__ */
|
1129
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
1130
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: item.title }),
|
1131
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-700 dark:text-slate-300", children: item.description })
|
925
1132
|
] })
|
926
1133
|
]
|
927
1134
|
},
|
928
|
-
|
1135
|
+
item.title
|
929
1136
|
))
|
930
1137
|
]
|
931
1138
|
}
|
932
1139
|
),
|
933
|
-
/* @__PURE__ */
|
934
|
-
|
1140
|
+
/* @__PURE__ */ jsxs(
|
1141
|
+
EditorBubble,
|
935
1142
|
{
|
936
1143
|
tippyOptions: {
|
937
1144
|
placement: "top"
|
938
1145
|
},
|
939
|
-
className:
|
1146
|
+
className: cls("flex w-fit max-w-[90vw] h-10 overflow-hidden rounded border bg-white dark:bg-gray-900 shadow", defaultBorderMixin),
|
940
1147
|
children: [
|
941
|
-
/* @__PURE__ */
|
942
|
-
/* @__PURE__ */
|
943
|
-
/* @__PURE__ */
|
944
|
-
/* @__PURE__ */
|
945
|
-
/* @__PURE__ */
|
1148
|
+
/* @__PURE__ */ jsx(NodeSelector, { open: openNode, onOpenChange: setOpenNode }),
|
1149
|
+
/* @__PURE__ */ jsx(Separator, { orientation: "vertical" }),
|
1150
|
+
/* @__PURE__ */ jsx(LinkSelector, { open: openLink, onOpenChange: setOpenLink }),
|
1151
|
+
/* @__PURE__ */ jsx(Separator, { orientation: "vertical" }),
|
1152
|
+
/* @__PURE__ */ jsx(TextButtons, {})
|
946
1153
|
]
|
947
1154
|
}
|
948
1155
|
)
|
@@ -950,14 +1157,14 @@ const At = B.create({
|
|
950
1157
|
}
|
951
1158
|
)
|
952
1159
|
}
|
953
|
-
) }) })
|
1160
|
+
) }) });
|
954
1161
|
};
|
955
|
-
function
|
956
|
-
const
|
957
|
-
return
|
1162
|
+
function addLineBreakAfterImages(markdown) {
|
1163
|
+
const imageRegex = /!\[.*?\]\(.*?\)/g;
|
1164
|
+
return markdown.replace(imageRegex, (match) => `${match}
|
958
1165
|
`);
|
959
1166
|
}
|
960
|
-
const
|
1167
|
+
const cssStyles = `
|
961
1168
|
|
962
1169
|
.ProseMirror .is-editor-empty:first-child::before {
|
963
1170
|
content: attr(data-placeholder);
|
@@ -1149,6 +1356,6 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
1149
1356
|
}
|
1150
1357
|
`;
|
1151
1358
|
export {
|
1152
|
-
|
1359
|
+
FireCMSEditor
|
1153
1360
|
};
|
1154
1361
|
//# sourceMappingURL=index.es.js.map
|