@firecms/editor 3.0.0-canary.9 → 3.0.0-canary.90
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 +114 -21
- package/dist/components/editor-command-item.d.ts +4 -5
- package/dist/components/editor-command.d.ts +8 -9
- package/dist/components/editor.d.ts +21 -43
- package/dist/extensions/_image-resizer.d.ts +0 -1
- package/dist/index.es.js +727 -529
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1149 -3
- package/dist/index.umd.js.map +1 -1
- package/dist/utils/utils.d.ts +1 -1
- package/package.json +29 -29
package/dist/index.es.js
CHANGED
@@ -1,342 +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
|
-
|
233
|
-
const r = q.filter((a) => a.isActive(t)).pop() ?? {
|
288
|
+
const { editor } = useCurrentEditor();
|
289
|
+
if (!editor) return null;
|
290
|
+
const activeItem = items.filter((item) => item.isActive(editor)).pop() ?? {
|
234
291
|
name: "Multiple"
|
235
292
|
};
|
236
|
-
return /* @__PURE__ */
|
237
|
-
|
293
|
+
return /* @__PURE__ */ jsx(
|
294
|
+
Popover,
|
238
295
|
{
|
239
296
|
sideOffset: 5,
|
240
297
|
align: "start",
|
241
298
|
className: "w-48 p-1",
|
242
|
-
trigger: /* @__PURE__ */
|
243
|
-
|
299
|
+
trigger: /* @__PURE__ */ jsxs(
|
300
|
+
Button,
|
244
301
|
{
|
245
302
|
variant: "text",
|
246
303
|
className: "gap-2 rounded-none",
|
247
304
|
color: "text",
|
248
305
|
children: [
|
249
|
-
/* @__PURE__ */
|
250
|
-
/* @__PURE__ */
|
306
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-sm", children: activeItem.name }),
|
307
|
+
/* @__PURE__ */ jsx(ExpandMoreIcon, { size: "small" })
|
251
308
|
]
|
252
309
|
}
|
253
310
|
),
|
254
|
-
modal:
|
255
|
-
open
|
256
|
-
onOpenChange
|
257
|
-
children:
|
258
|
-
|
311
|
+
modal: true,
|
312
|
+
open,
|
313
|
+
onOpenChange,
|
314
|
+
children: items.map((item, index) => /* @__PURE__ */ jsxs(
|
315
|
+
EditorBubbleItem,
|
259
316
|
{
|
260
|
-
onSelect: (
|
261
|
-
|
317
|
+
onSelect: (editor2) => {
|
318
|
+
item.command(editor2);
|
319
|
+
onOpenChange(false);
|
262
320
|
},
|
263
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",
|
264
322
|
children: [
|
265
|
-
/* @__PURE__ */
|
266
|
-
/* @__PURE__ */
|
267
|
-
/* @__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 })
|
268
326
|
] }),
|
269
|
-
|
327
|
+
activeItem.name === item.name && /* @__PURE__ */ jsx(CheckIcon, { size: "smallest" })
|
270
328
|
]
|
271
329
|
},
|
272
|
-
|
330
|
+
index
|
273
331
|
))
|
274
332
|
}
|
275
333
|
);
|
276
334
|
};
|
277
|
-
function
|
335
|
+
function isValidUrl(url) {
|
278
336
|
try {
|
279
|
-
|
280
|
-
|
281
|
-
|
337
|
+
new URL(url);
|
338
|
+
return true;
|
339
|
+
} catch (e) {
|
340
|
+
return false;
|
282
341
|
}
|
283
342
|
}
|
284
|
-
function
|
285
|
-
if (
|
286
|
-
return e;
|
343
|
+
function getUrlFromString(str) {
|
344
|
+
if (isValidUrl(str)) return str;
|
287
345
|
try {
|
288
|
-
|
289
|
-
|
346
|
+
if (str.includes(".") && !str.includes(" ")) {
|
347
|
+
return new URL(`https://${str}`).toString();
|
348
|
+
}
|
349
|
+
return null;
|
350
|
+
} catch (e) {
|
290
351
|
return null;
|
291
352
|
}
|
292
353
|
}
|
293
|
-
const
|
294
|
-
open
|
295
|
-
onOpenChange
|
354
|
+
const LinkSelector = ({
|
355
|
+
open,
|
356
|
+
onOpenChange
|
296
357
|
}) => {
|
297
|
-
const
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
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,
|
302
366
|
{
|
303
|
-
modal:
|
304
|
-
open
|
305
|
-
onOpenChange
|
306
|
-
trigger: /* @__PURE__ */
|
307
|
-
|
367
|
+
modal: true,
|
368
|
+
open,
|
369
|
+
onOpenChange,
|
370
|
+
trigger: /* @__PURE__ */ jsx(
|
371
|
+
Button,
|
308
372
|
{
|
309
373
|
variant: "text",
|
310
374
|
className: "gap-2 rounded-none",
|
311
375
|
color: "text",
|
312
|
-
children: /* @__PURE__ */
|
313
|
-
"text-blue-500":
|
376
|
+
children: /* @__PURE__ */ jsx("p", { className: cls("underline decoration-stone-400 underline-offset-4", {
|
377
|
+
"text-blue-500": editor.isActive("link")
|
314
378
|
}), children: "Link" })
|
315
379
|
}
|
316
380
|
),
|
317
|
-
children: /* @__PURE__ */
|
381
|
+
children: /* @__PURE__ */ jsxs(
|
318
382
|
"form",
|
319
383
|
{
|
320
|
-
onSubmit: (
|
321
|
-
const
|
322
|
-
|
323
|
-
const
|
324
|
-
|
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();
|
325
390
|
},
|
326
391
|
className: "flex p-1",
|
327
392
|
children: [
|
328
|
-
/* @__PURE__ */
|
393
|
+
/* @__PURE__ */ jsx(
|
329
394
|
"input",
|
330
395
|
{
|
331
|
-
ref:
|
332
|
-
autoFocus:
|
396
|
+
ref: inputRef,
|
397
|
+
autoFocus: open,
|
333
398
|
placeholder: "Paste a link",
|
334
|
-
defaultValue:
|
399
|
+
defaultValue: editor.getAttributes("link").href || "",
|
335
400
|
className: "text-gray-900 dark:text-white flex-grow bg-transparent p-1 text-sm outline-none"
|
336
401
|
}
|
337
402
|
),
|
338
|
-
|
339
|
-
|
403
|
+
editor.getAttributes("link").href ? /* @__PURE__ */ jsx(
|
404
|
+
Button,
|
340
405
|
{
|
341
406
|
size: "small",
|
342
407
|
variant: "text",
|
@@ -344,381 +409,485 @@ const ut = ({
|
|
344
409
|
color: "text",
|
345
410
|
className: "flex items-center",
|
346
411
|
onClick: () => {
|
347
|
-
|
412
|
+
editor.chain().focus().unsetLink().run();
|
348
413
|
},
|
349
|
-
children: /* @__PURE__ */
|
414
|
+
children: /* @__PURE__ */ jsx(DeleteIcon, { size: "small" })
|
350
415
|
}
|
351
|
-
) : /* @__PURE__ */
|
352
|
-
|
416
|
+
) : /* @__PURE__ */ jsx(
|
417
|
+
Button,
|
353
418
|
{
|
354
419
|
size: "small",
|
355
420
|
variant: "text",
|
356
|
-
children: /* @__PURE__ */
|
421
|
+
children: /* @__PURE__ */ jsx(CheckIcon, { size: "small" })
|
357
422
|
}
|
358
423
|
)
|
359
424
|
]
|
360
425
|
}
|
361
426
|
)
|
362
427
|
}
|
363
|
-
)
|
364
|
-
}
|
365
|
-
|
366
|
-
|
428
|
+
);
|
429
|
+
};
|
430
|
+
const TextButtons = () => {
|
431
|
+
const { editor } = useCurrentEditor();
|
432
|
+
if (!editor) return null;
|
433
|
+
const items2 = [
|
367
434
|
{
|
368
435
|
name: "bold",
|
369
|
-
isActive: (
|
370
|
-
command: (
|
371
|
-
icon:
|
436
|
+
isActive: (editor2) => editor2?.isActive("bold") ?? false,
|
437
|
+
command: (editor2) => editor2?.chain().focus().toggleBold().run(),
|
438
|
+
icon: FormatBoldIcon
|
372
439
|
},
|
373
440
|
{
|
374
441
|
name: "italic",
|
375
|
-
isActive: (
|
376
|
-
command: (
|
377
|
-
icon:
|
442
|
+
isActive: (editor2) => editor2?.isActive("italic") ?? false,
|
443
|
+
command: (editor2) => editor2?.chain().focus().toggleItalic().run(),
|
444
|
+
icon: FormatItalicIcon
|
378
445
|
},
|
379
446
|
{
|
380
447
|
name: "underline",
|
381
|
-
isActive: (
|
382
|
-
command: (
|
383
|
-
icon:
|
448
|
+
isActive: (editor2) => editor2?.isActive("underline") ?? false,
|
449
|
+
command: (editor2) => editor2?.chain().focus().toggleUnderline().run(),
|
450
|
+
icon: FormatUnderlinedIcon
|
384
451
|
},
|
385
452
|
{
|
386
453
|
name: "strike",
|
387
|
-
isActive: (
|
388
|
-
command: (
|
389
|
-
icon:
|
454
|
+
isActive: (editor2) => editor2?.isActive("strike") ?? false,
|
455
|
+
command: (editor2) => editor2?.chain().focus().toggleStrike().run(),
|
456
|
+
icon: FormatStrikethroughIcon
|
390
457
|
},
|
391
458
|
{
|
392
459
|
name: "code",
|
393
|
-
isActive: (
|
394
|
-
command: (
|
395
|
-
icon:
|
460
|
+
isActive: (editor2) => editor2?.isActive("code") ?? false,
|
461
|
+
command: (editor2) => editor2?.chain().focus().toggleCode().run(),
|
462
|
+
icon: CodeIcon
|
396
463
|
}
|
397
|
-
]
|
398
|
-
|
464
|
+
];
|
465
|
+
return /* @__PURE__ */ jsx("div", { className: "flex", children: items2.map((item, index) => /* @__PURE__ */ jsx(
|
466
|
+
EditorBubbleItem,
|
399
467
|
{
|
400
|
-
onSelect: (
|
401
|
-
|
468
|
+
onSelect: (editor2) => {
|
469
|
+
item.command(editor2);
|
402
470
|
},
|
403
|
-
children: /* @__PURE__ */
|
404
|
-
|
471
|
+
children: /* @__PURE__ */ jsx(
|
472
|
+
Button,
|
405
473
|
{
|
406
474
|
size: "small",
|
407
475
|
color: "text",
|
408
476
|
className: "gap-2 rounded-none h-full",
|
409
477
|
variant: "text",
|
410
|
-
children: /* @__PURE__ */
|
411
|
-
|
478
|
+
children: /* @__PURE__ */ jsx(
|
479
|
+
item.icon,
|
412
480
|
{
|
413
|
-
className:
|
414
|
-
"text-inherit": !
|
415
|
-
"text-blue-500":
|
481
|
+
className: cls({
|
482
|
+
"text-inherit": !item.isActive(editor),
|
483
|
+
"text-blue-500": item.isActive(editor)
|
416
484
|
})
|
417
485
|
}
|
418
486
|
)
|
419
487
|
}
|
420
488
|
)
|
421
489
|
},
|
422
|
-
|
423
|
-
)) })
|
490
|
+
index
|
491
|
+
)) });
|
424
492
|
};
|
425
|
-
function
|
426
|
-
const
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
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]
|
434
509
|
);
|
435
510
|
}
|
436
|
-
function
|
437
|
-
|
438
|
-
|
439
|
-
})
|
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;
|
440
523
|
}
|
441
|
-
const
|
524
|
+
const placeholder = PlaceholderExtension;
|
525
|
+
const tiptapLink = TiptapLink.configure({
|
442
526
|
HTMLAttributes: {
|
443
|
-
class:
|
527
|
+
class: cls(
|
444
528
|
"text-gray-600 dark:text-slate-300 underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer"
|
445
529
|
)
|
446
530
|
}
|
447
|
-
})
|
531
|
+
});
|
532
|
+
const taskList = TaskList.configure({
|
448
533
|
HTMLAttributes: {
|
449
|
-
class:
|
534
|
+
class: cls("not-prose")
|
450
535
|
}
|
451
|
-
})
|
536
|
+
});
|
537
|
+
const taskItem = TaskItem.configure({
|
452
538
|
HTMLAttributes: {
|
453
|
-
class:
|
539
|
+
class: cls("flex items-start my-4")
|
454
540
|
},
|
455
|
-
nested:
|
456
|
-
})
|
541
|
+
nested: true
|
542
|
+
});
|
543
|
+
const horizontalRule = Horizontal.configure({
|
457
544
|
HTMLAttributes: {
|
458
|
-
class:
|
545
|
+
class: cls("mt-4 mb-6 border-t", defaultBorderMixin)
|
459
546
|
}
|
460
|
-
})
|
547
|
+
});
|
548
|
+
const starterKit = StarterKit.configure({
|
461
549
|
bulletList: {
|
462
550
|
HTMLAttributes: {
|
463
|
-
class:
|
551
|
+
class: cls("list-disc list-outside leading-3 -mt-2")
|
464
552
|
}
|
465
553
|
},
|
466
554
|
orderedList: {
|
467
555
|
HTMLAttributes: {
|
468
|
-
class:
|
556
|
+
class: cls("list-decimal list-outside leading-3 -mt-2")
|
469
557
|
}
|
470
558
|
},
|
471
559
|
listItem: {
|
472
560
|
HTMLAttributes: {
|
473
|
-
class:
|
561
|
+
class: cls("leading-normal -mb-2")
|
474
562
|
}
|
475
563
|
},
|
476
564
|
blockquote: {
|
477
565
|
HTMLAttributes: {
|
478
|
-
class:
|
566
|
+
class: cls("border-l-4 border-primary")
|
479
567
|
}
|
480
568
|
},
|
481
569
|
codeBlock: {
|
482
570
|
HTMLAttributes: {
|
483
|
-
class:
|
571
|
+
class: cls("rounded bg-blue-50 dark:bg-gray-700 border p-5 font-mono font-medium", defaultBorderMixin)
|
484
572
|
}
|
485
573
|
},
|
486
574
|
code: {
|
487
575
|
HTMLAttributes: {
|
488
|
-
class:
|
576
|
+
class: cls("rounded-md bg-slate-50 dark:bg-gray-700 px-1.5 py-1 font-mono font-medium"),
|
489
577
|
spellcheck: "false"
|
490
578
|
}
|
491
579
|
},
|
492
|
-
horizontalRule:
|
580
|
+
horizontalRule: false,
|
493
581
|
dropcursor: {
|
494
582
|
color: "#DBEAFE",
|
495
583
|
width: 4
|
496
584
|
},
|
497
|
-
gapcursor:
|
585
|
+
gapcursor: false
|
498
586
|
});
|
499
|
-
async function
|
500
|
-
const { schema
|
501
|
-
let
|
502
|
-
const
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
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);
|
510
605
|
}
|
511
|
-
const
|
512
|
-
const
|
606
|
+
const dropImagePlugin = (upload) => {
|
607
|
+
const plugin = new Plugin({
|
513
608
|
state: {
|
514
609
|
// Initialize the plugin state with an empty DecorationSet
|
515
|
-
init: () =>
|
610
|
+
init: () => DecorationSet.empty,
|
516
611
|
// Apply transactions to update the state
|
517
|
-
apply: (
|
518
|
-
const
|
519
|
-
|
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);
|
520
618
|
}
|
521
619
|
},
|
522
620
|
props: {
|
523
621
|
handleDOMEvents: {
|
524
|
-
drop: (
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
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;
|
538
641
|
}
|
539
642
|
},
|
540
|
-
handlePaste(
|
541
|
-
const
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
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);
|
552
658
|
}
|
553
|
-
})
|
659
|
+
});
|
660
|
+
return anyImageFound;
|
554
661
|
},
|
555
|
-
decorations(
|
556
|
-
return
|
662
|
+
decorations(state) {
|
663
|
+
return plugin.getState(state);
|
557
664
|
}
|
558
665
|
},
|
559
|
-
view(
|
666
|
+
view(editorView) {
|
560
667
|
return {
|
561
|
-
update(
|
562
|
-
const
|
563
|
-
|
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
|
+
}
|
564
674
|
}
|
565
675
|
};
|
566
676
|
}
|
567
677
|
});
|
568
|
-
return
|
569
|
-
}
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
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({
|
579
693
|
name: "CustomKeymap",
|
580
694
|
addCommands() {
|
581
695
|
return {
|
582
|
-
selectTextWithinNodeBoundaries: () => ({ editor
|
583
|
-
const { state
|
584
|
-
|
585
|
-
|
586
|
-
|
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
|
587
704
|
});
|
588
705
|
}
|
589
706
|
};
|
590
707
|
},
|
591
708
|
addKeyboardShortcuts() {
|
592
709
|
return {
|
593
|
-
"Mod-a": ({ editor
|
594
|
-
const { state
|
595
|
-
|
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;
|
596
723
|
}
|
597
724
|
};
|
598
725
|
}
|
599
726
|
});
|
600
|
-
function
|
601
|
-
const
|
727
|
+
function absoluteRect(node) {
|
728
|
+
const data = node.getBoundingClientRect();
|
602
729
|
return {
|
603
|
-
top:
|
604
|
-
left:
|
605
|
-
width:
|
730
|
+
top: data.top,
|
731
|
+
left: data.left,
|
732
|
+
width: data.width
|
606
733
|
};
|
607
734
|
}
|
608
|
-
function
|
609
|
-
return document.elementsFromPoint(
|
610
|
-
(
|
735
|
+
function nodeDOMAtCoords(coords) {
|
736
|
+
return document.elementsFromPoint(coords.x, coords.y).find(
|
737
|
+
(elem) => elem.parentElement?.matches?.(".ProseMirror") || elem.matches(
|
611
738
|
["li", "p:not(:first-child)", "pre", "blockquote", "h1, h2, h3, h4, h5, h6"].join(", ")
|
612
739
|
)
|
613
740
|
);
|
614
741
|
}
|
615
|
-
function
|
616
|
-
const
|
617
|
-
return
|
618
|
-
left:
|
619
|
-
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
|
620
747
|
})?.inside;
|
621
748
|
}
|
622
|
-
function
|
623
|
-
function
|
624
|
-
|
625
|
-
|
626
|
-
const
|
627
|
-
x:
|
628
|
-
y:
|
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
|
629
756
|
});
|
630
|
-
if (!(
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
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 };
|
638
769
|
}
|
639
|
-
function
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
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
|
644
776
|
});
|
645
|
-
if (!(
|
646
|
-
|
647
|
-
|
648
|
-
|
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)));
|
649
781
|
}
|
650
|
-
let
|
651
|
-
function
|
652
|
-
|
782
|
+
let dragHandleElement = null;
|
783
|
+
function hideDragHandle() {
|
784
|
+
if (dragHandleElement) {
|
785
|
+
dragHandleElement.classList.add("hide");
|
786
|
+
}
|
653
787
|
}
|
654
|
-
function
|
655
|
-
|
788
|
+
function showDragHandle() {
|
789
|
+
if (dragHandleElement) {
|
790
|
+
dragHandleElement.classList.remove("hide");
|
791
|
+
}
|
656
792
|
}
|
657
|
-
return new
|
658
|
-
view: (
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
}
|
666
|
-
|
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
|
+
},
|
667
814
|
props: {
|
668
815
|
handleDOMEvents: {
|
669
|
-
mousemove: (
|
670
|
-
if (!
|
816
|
+
mousemove: (view, event) => {
|
817
|
+
if (!view.editable) {
|
671
818
|
return;
|
672
|
-
|
673
|
-
|
674
|
-
|
819
|
+
}
|
820
|
+
const node = nodeDOMAtCoords({
|
821
|
+
x: event.clientX + 50 + options.dragHandleWidth,
|
822
|
+
y: event.clientY
|
675
823
|
});
|
676
|
-
if (!(
|
677
|
-
|
824
|
+
if (!(node instanceof Element)) {
|
825
|
+
hideDragHandle();
|
678
826
|
return;
|
679
827
|
}
|
680
|
-
const
|
681
|
-
|
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();
|
682
842
|
},
|
683
843
|
keydown: () => {
|
684
|
-
|
844
|
+
hideDragHandle();
|
685
845
|
},
|
686
846
|
mousewheel: () => {
|
687
|
-
|
847
|
+
hideDragHandle();
|
688
848
|
},
|
689
849
|
// dragging class is used for CSS
|
690
|
-
dragstart: (
|
691
|
-
|
850
|
+
dragstart: (view) => {
|
851
|
+
view.dom.classList.add("dragging");
|
692
852
|
},
|
693
|
-
drop: (
|
694
|
-
|
853
|
+
drop: (view) => {
|
854
|
+
view.dom.classList.remove("dragging");
|
695
855
|
},
|
696
|
-
dragend: (
|
697
|
-
|
856
|
+
dragend: (view) => {
|
857
|
+
view.dom.classList.remove("dragging");
|
698
858
|
}
|
699
859
|
}
|
700
860
|
}
|
701
861
|
});
|
702
862
|
}
|
703
|
-
const
|
863
|
+
const DragAndDrop = Extension.create({
|
704
864
|
name: "dragAndDrop",
|
705
865
|
addProseMirrorPlugins() {
|
706
866
|
return [
|
707
|
-
|
867
|
+
DragHandle({
|
708
868
|
dragHandleWidth: 24
|
709
869
|
})
|
710
870
|
];
|
711
871
|
}
|
712
|
-
})
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
872
|
+
});
|
873
|
+
const FireCMSEditor = ({
|
874
|
+
handleImageUpload,
|
875
|
+
initialContent,
|
876
|
+
onJsonContentChange,
|
877
|
+
onHtmlContentChange,
|
878
|
+
onMarkdownContentChange
|
718
879
|
}) => {
|
719
|
-
const
|
880
|
+
const defaultEditorProps = {
|
720
881
|
handleDOMEvents: {
|
721
|
-
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
|
+
}
|
722
891
|
}
|
723
892
|
// handlePaste: (view, event) => {
|
724
893
|
// if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) {
|
@@ -752,206 +921,235 @@ const At = B.create({
|
|
752
921
|
// }
|
753
922
|
// return false;
|
754
923
|
// }
|
755
|
-
}
|
924
|
+
};
|
925
|
+
const suggestionItems = createSuggestionItems([
|
756
926
|
{
|
757
927
|
title: "Text",
|
758
928
|
description: "Just start typing with plain text.",
|
759
929
|
searchTerms: ["p", "paragraph"],
|
760
|
-
icon: /* @__PURE__ */
|
761
|
-
command: ({ editor
|
762
|
-
|
930
|
+
icon: /* @__PURE__ */ jsx(TextFieldsIcon, { size: 18 }),
|
931
|
+
command: ({ editor, range }) => {
|
932
|
+
editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
|
763
933
|
}
|
764
934
|
},
|
765
935
|
{
|
766
936
|
title: "To-do List",
|
767
937
|
description: "Track tasks with a to-do list.",
|
768
938
|
searchTerms: ["todo", "task", "list", "check", "checkbox"],
|
769
|
-
icon: /* @__PURE__ */
|
770
|
-
command: ({ editor
|
771
|
-
|
939
|
+
icon: /* @__PURE__ */ jsx(CheckBoxIcon, { size: 18 }),
|
940
|
+
command: ({ editor, range }) => {
|
941
|
+
editor.chain().focus().deleteRange(range).toggleTaskList().run();
|
772
942
|
}
|
773
943
|
},
|
774
944
|
{
|
775
945
|
title: "Heading 1",
|
776
946
|
description: "Big section heading.",
|
777
947
|
searchTerms: ["title", "big", "large"],
|
778
|
-
icon: /* @__PURE__ */
|
779
|
-
command: ({ editor
|
780
|
-
|
948
|
+
icon: /* @__PURE__ */ jsx(LooksOneIcon, { size: 18 }),
|
949
|
+
command: ({ editor, range }) => {
|
950
|
+
editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
|
781
951
|
}
|
782
952
|
},
|
783
953
|
{
|
784
954
|
title: "Heading 2",
|
785
955
|
description: "Medium section heading.",
|
786
956
|
searchTerms: ["subtitle", "medium"],
|
787
|
-
icon: /* @__PURE__ */
|
788
|
-
command: ({ editor
|
789
|
-
|
957
|
+
icon: /* @__PURE__ */ jsx(LooksTwoIcon, { size: 18 }),
|
958
|
+
command: ({ editor, range }) => {
|
959
|
+
editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
|
790
960
|
}
|
791
961
|
},
|
792
962
|
{
|
793
963
|
title: "Heading 3",
|
794
964
|
description: "Small section heading.",
|
795
965
|
searchTerms: ["subtitle", "small"],
|
796
|
-
icon: /* @__PURE__ */
|
797
|
-
command: ({ editor
|
798
|
-
|
966
|
+
icon: /* @__PURE__ */ jsx(Looks3Icon, { size: 18 }),
|
967
|
+
command: ({ editor, range }) => {
|
968
|
+
editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
|
799
969
|
}
|
800
970
|
},
|
801
971
|
{
|
802
972
|
title: "Bullet List",
|
803
973
|
description: "Create a simple bullet list.",
|
804
974
|
searchTerms: ["unordered", "point"],
|
805
|
-
icon: /* @__PURE__ */
|
806
|
-
command: ({ editor
|
807
|
-
|
975
|
+
icon: /* @__PURE__ */ jsx(FormatListBulletedIcon, { size: 18 }),
|
976
|
+
command: ({ editor, range }) => {
|
977
|
+
editor.chain().focus().deleteRange(range).toggleBulletList().run();
|
808
978
|
}
|
809
979
|
},
|
810
980
|
{
|
811
981
|
title: "Numbered List",
|
812
982
|
description: "Create a list with numbering.",
|
813
983
|
searchTerms: ["ordered"],
|
814
|
-
icon: /* @__PURE__ */
|
815
|
-
command: ({ editor
|
816
|
-
|
984
|
+
icon: /* @__PURE__ */ jsx(FormatListNumberedIcon, { size: 18 }),
|
985
|
+
command: ({ editor, range }) => {
|
986
|
+
editor.chain().focus().deleteRange(range).toggleOrderedList().run();
|
817
987
|
}
|
818
988
|
},
|
819
989
|
{
|
820
990
|
title: "Quote",
|
821
991
|
description: "Capture a quote.",
|
822
992
|
searchTerms: ["blockquote"],
|
823
|
-
icon: /* @__PURE__ */
|
824
|
-
command: ({ editor
|
993
|
+
icon: /* @__PURE__ */ jsx(FormatQuoteIcon, { size: 18 }),
|
994
|
+
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run()
|
825
995
|
},
|
826
996
|
{
|
827
997
|
title: "Code",
|
828
998
|
description: "Capture a code snippet.",
|
829
999
|
searchTerms: ["codeblock"],
|
830
|
-
icon: /* @__PURE__ */
|
831
|
-
command: ({ editor
|
1000
|
+
icon: /* @__PURE__ */ jsx(CodeIcon, { size: 18 }),
|
1001
|
+
command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run()
|
832
1002
|
},
|
833
1003
|
{
|
834
1004
|
title: "Image",
|
835
1005
|
description: "Upload an image from your computer.",
|
836
1006
|
searchTerms: ["photo", "picture", "media"],
|
837
|
-
icon: /* @__PURE__ */
|
838
|
-
command: ({ editor
|
839
|
-
|
840
|
-
const
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
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;
|
846
1018
|
}
|
847
|
-
}
|
1019
|
+
};
|
1020
|
+
input.click();
|
848
1021
|
}
|
849
1022
|
}
|
850
|
-
])
|
1023
|
+
]);
|
1024
|
+
const slashCommand = Command.configure({
|
851
1025
|
suggestion: {
|
852
|
-
items: () =>
|
853
|
-
render:
|
1026
|
+
items: () => suggestionItems,
|
1027
|
+
render: renderItems
|
854
1028
|
}
|
855
|
-
})
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
1029
|
+
});
|
1030
|
+
const imageExtension = useMemo(() => createImageExtension(handleImageUpload), []);
|
1031
|
+
const extensions = [
|
1032
|
+
TiptapUnderline,
|
1033
|
+
TextStyle,
|
1034
|
+
Color,
|
1035
|
+
Highlight.configure({
|
1036
|
+
multicolor: true
|
861
1037
|
}),
|
862
|
-
|
863
|
-
html:
|
864
|
-
transformCopiedText:
|
1038
|
+
Markdown.configure({
|
1039
|
+
html: false,
|
1040
|
+
transformCopiedText: true
|
865
1041
|
}),
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
1042
|
+
CustomKeymap,
|
1043
|
+
DragAndDrop,
|
1044
|
+
starterKit,
|
1045
|
+
placeholder,
|
1046
|
+
tiptapLink,
|
871
1047
|
// tiptapImage,
|
872
|
-
|
1048
|
+
imageExtension,
|
873
1049
|
// updatedImage,
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
]
|
879
|
-
|
880
|
-
const
|
881
|
-
|
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
|
+
}
|
882
1073
|
};
|
883
|
-
|
884
|
-
if (
|
885
|
-
const
|
886
|
-
|
1074
|
+
useDebouncedCallback(markdownContent, () => {
|
1075
|
+
if (editorRef.current) {
|
1076
|
+
const markdown = editorRef.current.storage.markdown.getMarkdown();
|
1077
|
+
onMarkdownContentChange?.(addLineBreakAfterImages(markdown));
|
887
1078
|
}
|
888
|
-
},
|
889
|
-
|
890
|
-
|
891
|
-
|
892
|
-
},
|
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(
|
893
1090
|
"div",
|
894
1091
|
{
|
895
1092
|
className: "relative min-h-[500px] w-full bg-white dark:bg-gray-950 rounded-lg",
|
896
|
-
children: /* @__PURE__ */
|
897
|
-
|
1093
|
+
children: /* @__PURE__ */ jsxs(
|
1094
|
+
EditorProvider,
|
898
1095
|
{
|
899
|
-
content:
|
900
|
-
extensions
|
1096
|
+
content: initialContent,
|
1097
|
+
extensions,
|
901
1098
|
editorProps: {
|
902
|
-
...
|
1099
|
+
...defaultEditorProps,
|
903
1100
|
attributes: {
|
904
1101
|
class: "prose-lg prose-headings:font-title font-default focus:outline-none max-w-full p-12"
|
905
1102
|
}
|
906
1103
|
},
|
907
|
-
onUpdate: ({ editor
|
908
|
-
console.
|
1104
|
+
onUpdate: ({ editor }) => {
|
1105
|
+
console.debug("Editor updated");
|
1106
|
+
onEditorUpdate(editor);
|
909
1107
|
},
|
910
1108
|
children: [
|
911
|
-
/* @__PURE__ */
|
912
|
-
|
1109
|
+
/* @__PURE__ */ jsxs(
|
1110
|
+
EditorCommand,
|
913
1111
|
{
|
914
|
-
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),
|
915
1113
|
children: [
|
916
|
-
/* @__PURE__ */
|
917
|
-
|
918
|
-
|
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,
|
919
1117
|
{
|
920
|
-
value:
|
921
|
-
onCommand: (
|
1118
|
+
value: item.title,
|
1119
|
+
onCommand: (val) => item?.command?.(val),
|
922
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",
|
923
1121
|
children: [
|
924
|
-
/* @__PURE__ */
|
1122
|
+
/* @__PURE__ */ jsx(
|
925
1123
|
"div",
|
926
1124
|
{
|
927
|
-
className:
|
928
|
-
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
|
929
1127
|
}
|
930
1128
|
),
|
931
|
-
/* @__PURE__ */
|
932
|
-
/* @__PURE__ */
|
933
|
-
/* @__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 })
|
934
1132
|
] })
|
935
1133
|
]
|
936
1134
|
},
|
937
|
-
|
1135
|
+
item.title
|
938
1136
|
))
|
939
1137
|
]
|
940
1138
|
}
|
941
1139
|
),
|
942
|
-
/* @__PURE__ */
|
943
|
-
|
1140
|
+
/* @__PURE__ */ jsxs(
|
1141
|
+
EditorBubble,
|
944
1142
|
{
|
945
1143
|
tippyOptions: {
|
946
1144
|
placement: "top"
|
947
1145
|
},
|
948
|
-
className:
|
1146
|
+
className: cls("flex w-fit max-w-[90vw] h-10 overflow-hidden rounded border bg-white dark:bg-gray-900 shadow", defaultBorderMixin),
|
949
1147
|
children: [
|
950
|
-
/* @__PURE__ */
|
951
|
-
/* @__PURE__ */
|
952
|
-
/* @__PURE__ */
|
953
|
-
/* @__PURE__ */
|
954
|
-
/* @__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, {})
|
955
1153
|
]
|
956
1154
|
}
|
957
1155
|
)
|
@@ -959,14 +1157,14 @@ const At = B.create({
|
|
959
1157
|
}
|
960
1158
|
)
|
961
1159
|
}
|
962
|
-
) }) })
|
1160
|
+
) }) });
|
963
1161
|
};
|
964
|
-
function
|
965
|
-
const
|
966
|
-
return
|
1162
|
+
function addLineBreakAfterImages(markdown) {
|
1163
|
+
const imageRegex = /!\[.*?\]\(.*?\)/g;
|
1164
|
+
return markdown.replace(imageRegex, (match) => `${match}
|
967
1165
|
`);
|
968
1166
|
}
|
969
|
-
const
|
1167
|
+
const cssStyles = `
|
970
1168
|
|
971
1169
|
.ProseMirror .is-editor-empty:first-child::before {
|
972
1170
|
content: attr(data-placeholder);
|
@@ -1158,6 +1356,6 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
|
|
1158
1356
|
}
|
1159
1357
|
`;
|
1160
1358
|
export {
|
1161
|
-
|
1359
|
+
FireCMSEditor
|
1162
1360
|
};
|
1163
1361
|
//# sourceMappingURL=index.es.js.map
|