@firecms/editor 3.0.0-beta.7 → 3.0.0-beta.9

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