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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -1,340 +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) return null;
232
- 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() ?? {
233
136
  name: "Multiple"
234
137
  };
235
- return /* @__PURE__ */ s(
236
- Q,
138
+ return /* @__PURE__ */ jsx(
139
+ Popover,
237
140
  {
238
141
  sideOffset: 5,
239
142
  align: "start",
143
+ portalContainer,
240
144
  className: "w-48 p-1",
241
- trigger: /* @__PURE__ */ b(
242
- w,
145
+ trigger: /* @__PURE__ */ jsxs(
146
+ Button,
243
147
  {
244
148
  variant: "text",
245
149
  className: "gap-2 rounded-none",
246
150
  color: "text",
247
151
  children: [
248
- /* @__PURE__ */ s("span", { className: "whitespace-nowrap text-sm", children: r.name }),
249
- /* @__PURE__ */ s(Ue, { size: "small" })
152
+ /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-sm", children: activeItem.name }),
153
+ /* @__PURE__ */ jsx(ExpandMoreIcon, { size: "small" })
250
154
  ]
251
155
  }
252
156
  ),
253
- modal: !0,
254
- open: e,
255
- onOpenChange: o,
256
- children: q.map((a, c) => /* @__PURE__ */ b(
257
- F,
157
+ modal: true,
158
+ open,
159
+ onOpenChange,
160
+ children: items.map((item, index) => /* @__PURE__ */ jsxs(
161
+ EditorBubbleItem,
258
162
  {
259
- onSelect: (n) => {
260
- a.command(n), o(!1);
163
+ onSelect: (editor2) => {
164
+ item.command(editor2);
165
+ onOpenChange(false);
261
166
  },
262
167
  className: "flex cursor-pointer items-center justify-between rounded px-2 py-1 text-sm hover:bg-blue-50 hover:dark:bg-gray-700 text-gray-900 dark:text-white",
263
168
  children: [
264
- /* @__PURE__ */ b("div", { className: "flex items-center space-x-2", children: [
265
- /* @__PURE__ */ s(a.icon, { size: "smallest" }),
266
- /* @__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 })
267
172
  ] }),
268
- r.name === a.name && /* @__PURE__ */ s(V, { size: "smallest" })
173
+ activeItem.name === item.name && /* @__PURE__ */ jsx(CheckIcon, { size: "smallest" })
269
174
  ]
270
175
  },
271
- c
176
+ index
272
177
  ))
273
178
  }
274
179
  );
275
180
  };
276
- function lt(e) {
181
+ function isValidUrl(url) {
277
182
  try {
278
- return new URL(e), !0;
279
- } catch {
280
- return !1;
183
+ new URL(url);
184
+ return true;
185
+ } catch (e) {
186
+ return false;
281
187
  }
282
188
  }
283
- function dt(e) {
284
- if (lt(e)) return e;
189
+ function getUrlFromString(str) {
190
+ if (isValidUrl(str)) return str;
285
191
  try {
286
- return e.includes(".") && !e.includes(" ") ? new URL(`https://${e}`).toString() : null;
287
- } catch {
192
+ if (str.includes(".") && !str.includes(" ")) {
193
+ return new URL(`https://${str}`).toString();
194
+ }
195
+ return null;
196
+ } catch (e) {
288
197
  return null;
289
198
  }
290
199
  }
291
- const ut = ({
292
- open: e,
293
- onOpenChange: o
200
+ const LinkSelector = ({
201
+ open,
202
+ onOpenChange
294
203
  }) => {
295
- const t = D(null), { editor: r } = y();
296
- return C(() => {
297
- t.current && t.current?.focus();
298
- }), r ? /* @__PURE__ */ s(
299
- 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,
300
212
  {
301
- modal: !0,
302
- open: e,
303
- onOpenChange: o,
304
- trigger: /* @__PURE__ */ s(
305
- w,
213
+ modal: true,
214
+ open,
215
+ onOpenChange,
216
+ trigger: /* @__PURE__ */ jsx(
217
+ Button,
306
218
  {
307
219
  variant: "text",
308
220
  className: "gap-2 rounded-none",
309
221
  color: "text",
310
- children: /* @__PURE__ */ s("p", { className: g("underline decoration-stone-400 underline-offset-4", {
311
- "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")
312
224
  }), children: "Link" })
313
225
  }
314
226
  ),
315
- children: /* @__PURE__ */ b(
227
+ children: /* @__PURE__ */ jsxs(
316
228
  "form",
317
229
  {
318
- onSubmit: (a) => {
319
- const c = a.currentTarget;
320
- a.preventDefault();
321
- const n = c[0], i = dt(n.value);
322
- 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();
323
236
  },
324
237
  className: "flex p-1",
325
238
  children: [
326
- /* @__PURE__ */ s(
239
+ /* @__PURE__ */ jsx(
327
240
  "input",
328
241
  {
329
- ref: t,
330
- autoFocus: e,
242
+ ref: inputRef,
243
+ autoFocus: open,
331
244
  placeholder: "Paste a link",
332
- defaultValue: r.getAttributes("link").href || "",
333
- className: "text-gray-900 dark:text-white flex-grow bg-transparent p-1 text-sm outline-none"
245
+ defaultValue: editor.getAttributes("link").href || "",
246
+ className: cls("text-gray-900 dark:text-white flex-grow bg-transparent p-1 text-sm outline-none", focusedDisabled)
334
247
  }
335
248
  ),
336
- r.getAttributes("link").href ? /* @__PURE__ */ s(
337
- w,
249
+ editor.getAttributes("link").href ? /* @__PURE__ */ jsx(
250
+ Button,
338
251
  {
339
252
  size: "small",
340
253
  variant: "text",
@@ -342,623 +255,1374 @@ const ut = ({
342
255
  color: "text",
343
256
  className: "flex items-center",
344
257
  onClick: () => {
345
- r.chain().focus().unsetLink().run();
258
+ editor.chain().focus().unsetLink().run();
346
259
  },
347
- children: /* @__PURE__ */ s(Ke, { size: "small" })
260
+ children: /* @__PURE__ */ jsx(DeleteIcon, { size: "small" })
348
261
  }
349
- ) : /* @__PURE__ */ s(
350
- w,
262
+ ) : /* @__PURE__ */ jsx(
263
+ Button,
351
264
  {
352
265
  size: "small",
353
266
  variant: "text",
354
- children: /* @__PURE__ */ s(V, { size: "small" })
267
+ children: /* @__PURE__ */ jsx(CheckIcon, { size: "small" })
355
268
  }
356
269
  )
357
270
  ]
358
271
  }
359
272
  )
360
273
  }
361
- ) : null;
362
- }, mt = () => {
363
- const { editor: e } = y();
364
- 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 = [
365
280
  {
366
281
  name: "bold",
367
- isActive: (t) => t?.isActive("bold") ?? !1,
368
- command: (t) => t?.chain().focus().toggleBold().run(),
369
- icon: $e
282
+ isActive: (editor2) => editor2?.isActive("bold") ?? false,
283
+ command: (editor2) => editor2?.chain().focus().toggleBold().run(),
284
+ icon: FormatBoldIcon
370
285
  },
371
286
  {
372
287
  name: "italic",
373
- isActive: (t) => t?.isActive("italic") ?? !1,
374
- command: (t) => t?.chain().focus().toggleItalic().run(),
375
- icon: qe
288
+ isActive: (editor2) => editor2?.isActive("italic") ?? false,
289
+ command: (editor2) => editor2?.chain().focus().toggleItalic().run(),
290
+ icon: FormatItalicIcon
376
291
  },
377
292
  {
378
293
  name: "underline",
379
- isActive: (t) => t?.isActive("underline") ?? !1,
380
- command: (t) => t?.chain().focus().toggleUnderline().run(),
381
- icon: We
294
+ isActive: (editor2) => editor2?.isActive("underline") ?? false,
295
+ command: (editor2) => editor2?.chain().focus().toggleUnderline().run(),
296
+ icon: FormatUnderlinedIcon
382
297
  },
383
298
  {
384
299
  name: "strike",
385
- isActive: (t) => t?.isActive("strike") ?? !1,
386
- command: (t) => t?.chain().focus().toggleStrike().run(),
387
- icon: Oe
300
+ isActive: (editor2) => editor2?.isActive("strike") ?? false,
301
+ command: (editor2) => editor2?.chain().focus().toggleStrike().run(),
302
+ icon: FormatStrikethroughIcon
388
303
  },
389
304
  {
390
305
  name: "code",
391
- isActive: (t) => t?.isActive("code") ?? !1,
392
- command: (t) => t?.chain().focus().toggleCode().run(),
393
- icon: z
306
+ isActive: (editor2) => editor2?.isActive("code") ?? false,
307
+ command: (editor2) => editor2?.chain().focus().toggleCode().run(),
308
+ icon: CodeIcon
394
309
  }
395
- ].map((t, r) => /* @__PURE__ */ s(
396
- F,
310
+ ];
311
+ return /* @__PURE__ */ jsx("div", { className: "flex", children: items2.map((item, index) => /* @__PURE__ */ jsx(
312
+ EditorBubbleItem,
397
313
  {
398
- onSelect: (a) => {
399
- t.command(a);
314
+ onSelect: (editor2) => {
315
+ item.command(editor2);
400
316
  },
401
- children: /* @__PURE__ */ s(
402
- w,
317
+ children: /* @__PURE__ */ jsx(
318
+ Button,
403
319
  {
404
320
  size: "small",
405
321
  color: "text",
406
322
  className: "gap-2 rounded-none h-full",
407
323
  variant: "text",
408
- children: /* @__PURE__ */ s(
409
- t.icon,
324
+ children: /* @__PURE__ */ jsx(
325
+ item.icon,
410
326
  {
411
- className: g({
412
- "text-inherit": !t.isActive(e),
413
- "text-blue-500": t.isActive(e)
327
+ className: cls({
328
+ "text-inherit": !item.isActive(editor),
329
+ "text-blue-500": item.isActive(editor)
414
330
  })
415
331
  }
416
332
  )
417
333
  }
418
334
  )
419
335
  },
420
- r
421
- )) }) : null;
336
+ index
337
+ )) });
422
338
  };
423
- function R(e, o, t, r = 300) {
424
- const a = L.useRef(!1), c = () => {
425
- o(), a.current = !1;
426
- }, n = L.useRef(void 0);
427
- L.useEffect(
428
- () => (a.current = !0, clearTimeout(n.current), n.current = setTimeout(c, r), () => {
429
- }),
430
- [t, e]
431
- );
432
- }
433
- function H(e) {
434
- 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) => {
435
- e[o] = H(e[o]);
436
- })), e);
339
+ function removeClassesFromJson(jsonObj) {
340
+ if (Array.isArray(jsonObj)) {
341
+ return jsonObj.map((item) => removeClassesFromJson(item));
342
+ } else if (typeof jsonObj === "object" && jsonObj !== null) {
343
+ if (jsonObj.attrs && typeof jsonObj.attrs === "object" && "class" in jsonObj.attrs) {
344
+ delete jsonObj.attrs.class;
345
+ }
346
+ Object.keys(jsonObj).forEach((key) => {
347
+ jsonObj[key] = removeClassesFromJson(jsonObj[key]);
348
+ });
349
+ }
350
+ return jsonObj;
437
351
  }
438
- const 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({
439
489
  HTMLAttributes: {
440
- class: g(
490
+ class: cls(
441
491
  "text-gray-600 dark:text-slate-300 underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer"
442
492
  )
443
493
  }
444
- }), ft = De.configure({
494
+ });
495
+ const taskList = TaskList.configure({
445
496
  HTMLAttributes: {
446
- class: g("not-prose")
497
+ class: cls("not-prose")
447
498
  }
448
- }), ht = He.configure({
499
+ });
500
+ const taskItem = TaskItem.configure({
449
501
  HTMLAttributes: {
450
- class: g("flex items-start my-4")
502
+ class: cls("flex items-start my-4")
451
503
  },
452
- nested: !0
453
- }), bt = it.configure({
504
+ nested: true
505
+ });
506
+ const markdownExtension = Markdown.configure({
507
+ html: true
508
+ });
509
+ const horizontalRule = Horizontal.configure({
454
510
  HTMLAttributes: {
455
- class: g("mt-4 mb-6 border-t", x)
511
+ class: cls("mt-4 mb-6 border-t", defaultBorderMixin)
456
512
  }
457
- }), kt = Ne.configure({
513
+ });
514
+ const starterKit = StarterKit.configure({
458
515
  bulletList: {
459
516
  HTMLAttributes: {
460
- class: g("list-disc list-outside leading-3 -mt-2")
517
+ class: cls("list-disc list-outside leading-3 -mt-2")
461
518
  }
462
519
  },
463
520
  orderedList: {
464
521
  HTMLAttributes: {
465
- class: g("list-decimal list-outside leading-3 -mt-2")
522
+ class: cls("list-decimal list-outside leading-3 -mt-2")
466
523
  }
467
524
  },
468
525
  listItem: {
469
526
  HTMLAttributes: {
470
- class: g("leading-normal -mb-2")
527
+ class: cls("leading-normal -mb-2")
471
528
  }
472
529
  },
473
530
  blockquote: {
474
531
  HTMLAttributes: {
475
- class: g("border-l-4 border-primary")
532
+ class: cls("border-l-4 border-primary")
476
533
  }
477
534
  },
478
535
  codeBlock: {
479
536
  HTMLAttributes: {
480
- 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)
481
538
  }
482
539
  },
483
540
  code: {
484
541
  HTMLAttributes: {
485
- 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"),
486
543
  spellcheck: "false"
487
544
  }
488
545
  },
489
- horizontalRule: !1,
546
+ document: false,
547
+ horizontalRule: false,
490
548
  dropcursor: {
491
549
  color: "#DBEAFE",
492
550
  width: 4
493
551
  },
494
- gapcursor: !1
552
+ gapcursor: false
495
553
  });
496
- async function W(e, o, t, r, a, c) {
497
- const { schema: n } = o.state;
498
- let i = e.getState(o.state);
499
- const d = document.createElement("div"), u = document.createElement("img");
500
- u.setAttribute("class", "opacity-40 rounded-lg border border-stone-200"), u.src = t.target?.result, d.appendChild(u);
501
- const p = Xe.widget(r, d);
502
- i = i?.add(o.state.doc, [p]), o.dispatch(o.state.tr.setMeta(e, { decorationSet: i }));
503
- const h = await a(c);
504
- console.log("uploaded image", h);
505
- const f = n.nodes.image.create({ src: h }), A = o.state.tr.replaceWith(r, r, f);
506
- 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);
507
577
  }
508
- const xt = (e) => {
509
- const o = new Qe({
578
+ const ImagePluginKey = new PluginKey$1("imagePlugin");
579
+ const createDropImagePlugin = (upload) => {
580
+ const plugin = new Plugin$1({
581
+ key: ImagePluginKey,
510
582
  state: {
511
583
  // Initialize the plugin state with an empty DecorationSet
512
- init: () => Ve.empty,
584
+ init: () => DecorationSet$1.empty,
513
585
  // Apply transactions to update the state
514
- apply: (t, r) => {
515
- const a = t.getMeta(o);
516
- 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);
517
592
  }
518
593
  },
519
594
  props: {
520
595
  handleDOMEvents: {
521
- drop: (t, r) => {
522
- if (console.log("drop event", r), !r.dataTransfer?.files || r.dataTransfer?.files.length === 0)
523
- return !1;
524
- r.preventDefault();
525
- const c = Array.from(r.dataTransfer.files).filter((n) => /image/i.test(n.type));
526
- return c.length === 0 ? !1 : (c.forEach((n) => {
527
- const i = t.posAtCoords({ left: r.clientX, top: r.clientY });
528
- if (!i) return;
529
- const d = new FileReader();
530
- d.onload = async (u) => {
531
- await W(o, t, u, i.pos, e, n);
532
- }, d.readAsDataURL(n);
533
- }), !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;
534
620
  }
535
621
  },
536
- handlePaste(t, r, a) {
537
- const c = Array.from(r.clipboardData?.items || []), n = t.state.selection.from;
538
- console.log("pos", n);
539
- let i = !1;
540
- return c.forEach((d) => {
541
- const u = d.getAsFile();
542
- if (console.log("image", u), u) {
543
- i = !0;
544
- const p = new FileReader();
545
- p.onload = async (h) => {
546
- await W(o, t, h, n, e, u);
547
- }, 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);
548
635
  }
549
- }), i;
636
+ });
637
+ return anyImageFound;
550
638
  },
551
- decorations(t) {
552
- return o.getState(t);
639
+ decorations(state) {
640
+ return plugin.getState(state);
553
641
  }
554
642
  },
555
- view(t) {
643
+ view(editorView) {
556
644
  return {
557
- update(r, a) {
558
- const c = o.getState(a), n = o.getState(r.state);
559
- 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
+ }
560
651
  }
561
652
  };
562
653
  }
563
654
  });
564
- return o;
565
- }, yt = (e) => Ie.extend({
566
- addProseMirrorPlugins() {
567
- return [xt(e)];
568
- }
569
- }).configure({
570
- allowBase64: !0,
571
- HTMLAttributes: {
572
- class: g("rounded-lg border", x)
573
- }
574
- }), 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({
575
670
  name: "CustomKeymap",
576
671
  addCommands() {
577
672
  return {
578
- selectTextWithinNodeBoundaries: () => ({ editor: e, commands: o }) => {
579
- const { state: t } = e, { tr: r } = t, a = r.selection.$from.start(), c = r.selection.$to.end();
580
- return o.setTextSelection({
581
- from: a,
582
- 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
583
681
  });
584
682
  }
585
683
  };
586
684
  },
587
685
  addKeyboardShortcuts() {
588
686
  return {
589
- "Mod-a": ({ editor: e }) => {
590
- 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();
591
- 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;
592
700
  }
593
701
  };
594
702
  }
595
703
  });
596
- function Ct(e) {
597
- 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();
598
711
  return {
599
- top: o.top,
600
- left: o.left,
601
- width: o.width
712
+ top: data.top - (ancestorRect?.top ?? 0),
713
+ left: data.left - (ancestorRect?.left ?? 0),
714
+ width: data.width
602
715
  };
603
716
  }
604
- function I(e) {
605
- return document.elementsFromPoint(e.x, e.y).find(
606
- (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(
607
720
  ["li", "p:not(:first-child)", "pre", "blockquote", "h1, h2, h3, h4, h5, h6"].join(", ")
608
721
  )
609
722
  );
610
723
  }
611
- function O(e, o, t) {
612
- const r = e.getBoundingClientRect();
613
- return o.posAtCoords({
614
- left: r.left + 50 + t.dragHandleWidth,
615
- 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
616
729
  })?.inside;
617
730
  }
618
- function wt(e) {
619
- function o(n, i) {
620
- if (i.focus(), !n.dataTransfer) return;
621
- const d = I({
622
- x: n.clientX + 50 + e.dragHandleWidth,
623
- 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
624
738
  });
625
- if (!(d instanceof Element)) return;
626
- const u = O(d, i, e);
627
- if (u == null || u < 0) return;
628
- i.dispatch(i.state.tr.setSelection($.create(i.state.doc, u)));
629
- const p = i.state.selection.content(), { dom: h, text: f } = Ye(i, p);
630
- 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
+ };
631
757
  }
632
- function t(n, i) {
633
- i.focus(), i.dom.classList.remove("dragging");
634
- const d = I({
635
- x: n.clientX + 50 + e.dragHandleWidth,
636
- 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
637
764
  });
638
- if (!(d instanceof Element)) return;
639
- const u = O(d, i, e);
640
- 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)));
641
769
  }
642
- let r = null;
643
- function a() {
644
- r && r.classList.add("hide");
770
+ let dragHandleElement = null;
771
+ function hideDragHandle() {
772
+ if (dragHandleElement) {
773
+ dragHandleElement.classList.add("hide");
774
+ }
645
775
  }
646
- function c() {
647
- r && r.classList.remove("hide");
776
+ function showDragHandle() {
777
+ if (dragHandleElement) {
778
+ dragHandleElement.classList.remove("hide");
779
+ }
648
780
  }
649
- return new Je({
650
- view: (n) => (r = document.createElement("div"), r.draggable = !0, r.dataset.dragHandle = "", r.classList.add("drag-handle"), r.addEventListener("dragstart", (i) => {
651
- o(i, n);
652
- }), r.addEventListener("click", (i) => {
653
- t(i, n);
654
- }), a(), n?.dom?.parentElement?.appendChild(r), {
655
- destroy: () => {
656
- r?.remove?.(), r = null;
657
- }
658
- }),
781
+ return new Plugin$1({
782
+ view: (view) => {
783
+ dragHandleElement = document.createElement("div");
784
+ dragHandleElement.draggable = true;
785
+ dragHandleElement.dataset.dragHandle = "";
786
+ dragHandleElement.classList.add("drag-handle");
787
+ dragHandleElement.addEventListener("dragstart", (e) => {
788
+ handleDragStart(e, view);
789
+ });
790
+ dragHandleElement.addEventListener("click", (e) => {
791
+ handleClick(e, view);
792
+ });
793
+ hideDragHandle();
794
+ view?.dom?.parentElement?.appendChild(dragHandleElement);
795
+ return {
796
+ destroy: () => {
797
+ }
798
+ };
799
+ },
659
800
  props: {
660
801
  handleDOMEvents: {
661
- mousemove: (n, i) => {
662
- if (!n.editable)
802
+ mousemove: (view, event) => {
803
+ if (!view.editable) {
663
804
  return;
664
- const d = I({
665
- x: i.clientX + 50 + e.dragHandleWidth,
666
- y: i.clientY
805
+ }
806
+ const node = nodeDOMAtCoords({
807
+ x: event.clientX + 50 + options.dragHandleWidth,
808
+ y: event.clientY
667
809
  });
668
- if (!(d instanceof Element)) {
669
- a();
810
+ if (!(node instanceof Element)) {
811
+ hideDragHandle();
670
812
  return;
671
813
  }
672
- const u = window.getComputedStyle(d), p = parseInt(u.lineHeight, 10), h = parseInt(u.paddingTop, 10), f = Ct(d);
673
- 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();
674
828
  },
675
829
  keydown: () => {
676
- a();
830
+ hideDragHandle();
677
831
  },
678
832
  mousewheel: () => {
679
- a();
833
+ hideDragHandle();
680
834
  },
681
835
  // dragging class is used for CSS
682
- dragstart: (n) => {
683
- n.dom.classList.add("dragging");
836
+ dragstart: (view) => {
837
+ view.dom.classList.add("dragging");
684
838
  },
685
- drop: (n) => {
686
- n.dom.classList.remove("dragging");
839
+ drop: (view) => {
840
+ view.dom.classList.remove("dragging");
687
841
  },
688
- dragend: (n) => {
689
- n.dom.classList.remove("dragging");
842
+ dragend: (view) => {
843
+ view.dom.classList.remove("dragging");
690
844
  }
691
845
  }
692
846
  }
693
847
  });
694
848
  }
695
- const At = B.create({
849
+ const DragAndDrop = Extension.create({
696
850
  name: "dragAndDrop",
697
851
  addProseMirrorPlugins() {
698
852
  return [
699
- wt({
853
+ DragHandle({
700
854
  dragHandleWidth: 24
701
855
  })
702
856
  ];
703
857
  }
704
- }), eo = ({
705
- handleImageUpload: e,
706
- initialContent: o,
707
- onJsonContentChange: t,
708
- onHtmlContentChange: r,
709
- onMarkdownContentChange: a
710
- }) => {
711
- const c = {
712
- handleDOMEvents: {
713
- keydown: (l, m) => !!(["ArrowUp", "ArrowDown", "Enter"].includes(m.key) && document.querySelector("#slash-command"))
714
- }
715
- // handlePaste: (view, event) => {
716
- // if (event.clipboardData && event.clipboardData.files && event.clipboardData.files[0]) {
717
- // event.preventDefault();
718
- // const file = event.clipboardData.files[0];
719
- // const pos = view.state.selection.from;
720
- //
721
- // // startImageUpload({ file, view, pos, handleImageUpload });
722
- // return true;
723
- // }
724
- // return false;
725
- // },
726
- // handleDrop: (view, event, _slice, moved) => {
727
- // console.log("handleDrop", { event, moved });
728
- // if (!moved && event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
729
- // console.log("handleDrop!!!", { event, moved });
730
- // event.preventDefault();
731
- // const file = event.dataTransfer.files[0];
732
- // const coordinates = view.posAtCoords({
733
- // left: event.clientX,
734
- // top: event.clientY
735
- // });
736
- // // here we deduct 1 from the pos or else the image will create an extra node
737
- // startImageUpload({
738
- // file,
739
- // view,
740
- // pos: coordinates?.pos || 0 - 1,
741
- // handleImageUpload,
742
- // });
743
- // return true;
744
- // }
745
- // return false;
746
- // }
747
- }, n = at([
748
- {
749
- title: "Text",
750
- description: "Just start typing with plain text.",
751
- searchTerms: ["p", "paragraph"],
752
- icon: /* @__PURE__ */ s(X, { size: 18 }),
753
- command: ({ editor: l, range: m }) => {
754
- 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
+ }
755
916
  }
756
- },
757
- {
758
- title: "To-do List",
759
- description: "Track tasks with a to-do list.",
760
- searchTerms: ["todo", "task", "list", "check", "checkbox"],
761
- icon: /* @__PURE__ */ s(j, { size: 18 }),
762
- command: ({ editor: l, range: m }) => {
763
- 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
+ }
764
948
  }
765
- },
766
- {
767
- title: "Heading 1",
768
- description: "Big section heading.",
769
- searchTerms: ["title", "big", "large"],
770
- icon: /* @__PURE__ */ s(Y, { size: 18 }),
771
- command: ({ editor: l, range: m }) => {
772
- l.chain().focus().deleteRange(m).setNode("heading", { level: 1 }).run();
949
+ };
950
+ },
951
+ parseHTML() {
952
+ return [
953
+ {
954
+ tag: `span[data-type="${this.name}"]`
773
955
  }
774
- },
775
- {
776
- title: "Heading 2",
777
- description: "Medium section heading.",
778
- searchTerms: ["subtitle", "medium"],
779
- icon: /* @__PURE__ */ s(J, { size: 18 }),
780
- command: ({ editor: l, range: m }) => {
781
- 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();
782
1107
  }
783
- },
784
- {
785
- title: "Heading 3",
786
- description: "Small section heading.",
787
- searchTerms: ["subtitle", "small"],
788
- icon: /* @__PURE__ */ s(G, { size: 18 }),
789
- command: ({ editor: l, range: m }) => {
790
- 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;
791
1139
  }
792
- },
793
- {
794
- title: "Bullet List",
795
- description: "Create a simple bullet list.",
796
- searchTerms: ["unordered", "point"],
797
- icon: /* @__PURE__ */ s(ee, { size: 18 }),
798
- command: ({ editor: l, range: m }) => {
799
- l.chain().focus().deleteRange(m).toggleBulletList().run();
1140
+ if (event.key === "ArrowDown") {
1141
+ downHandler();
1142
+ return true;
800
1143
  }
801
- },
802
- {
803
- title: "Numbered List",
804
- description: "Create a list with numbering.",
805
- searchTerms: ["ordered"],
806
- icon: /* @__PURE__ */ s(te, { size: 18 }),
807
- command: ({ editor: l, range: m }) => {
808
- l.chain().focus().deleteRange(m).toggleOrderedList().run();
1144
+ if (event.key === "Enter") {
1145
+ enterHandler();
1146
+ return true;
809
1147
  }
810
- },
811
- {
812
- title: "Quote",
813
- description: "Capture a quote.",
814
- searchTerms: ["blockquote"],
815
- icon: /* @__PURE__ */ s(oe, { size: 18 }),
816
- command: ({ editor: l, range: m }) => l.chain().focus().deleteRange(m).toggleNode("paragraph", "paragraph").toggleBlockquote().run()
817
- },
818
- {
819
- title: "Code",
820
- description: "Capture a code snippet.",
821
- searchTerms: ["codeblock"],
822
- icon: /* @__PURE__ */ s(z, { size: 18 }),
823
- command: ({ editor: l, range: m }) => l.chain().focus().deleteRange(m).toggleCodeBlock().run()
824
- },
1148
+ return false;
1149
+ }
1150
+ }));
1151
+ const itemRefs = useRef([]);
1152
+ useEffect(() => {
1153
+ if (itemRefs.current[selectedIndex]) {
1154
+ itemRefs.current[selectedIndex].scrollIntoView({
1155
+ block: "nearest"
1156
+ });
1157
+ }
1158
+ }, [selectedIndex]);
1159
+ return /* @__PURE__ */ jsx(
1160
+ "div",
825
1161
  {
826
- title: "Image",
827
- description: "Upload an image from your computer.",
828
- searchTerms: ["photo", "picture", "media"],
829
- icon: /* @__PURE__ */ s(_e, { size: 18 }),
830
- command: ({ editor: l, range: m }) => {
831
- l.chain().focus().deleteRange(m).run();
832
- const k = document.createElement("input");
833
- k.type = "file", k.accept = "image/*", k.onchange = async () => {
834
- if (k.files?.length) {
835
- if (!k.files[0]) return;
836
- 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
+ }
837
1442
  }
838
- }, 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();
839
1526
  }
840
1527
  }
841
- ]), i = rt.configure({
842
- suggestion: {
843
- items: () => n,
844
- 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));
845
1534
  }
846
- }), d = Z(() => yt(e), []), u = [
847
- me,
848
- ge,
849
- pe,
850
- he.configure({
851
- multicolor: !0
852
- }),
853
- fe.configure({
854
- html: !1,
855
- transformCopiedText: !0
856
- }),
857
- vt,
858
- At,
859
- kt,
860
- gt,
861
- pt,
862
- // tiptapImage,
863
- d,
864
- // updatedImage,
865
- ft,
866
- ht,
867
- bt,
868
- i
869
- ], [p, h] = v(!1), [f, A] = v(!1);
870
- Ze("Editor", Tt);
871
- const E = L.useRef(null), [ie, ce] = v(null), [M, le] = v(null), [N, de] = v(null), ue = (l) => {
872
- E.current = l, a && ce(l.storage.markdown.getMarkdown()), t && le(H(l.getJSON())), r && de(l.getHTML());
873
- };
874
- return R(ie, () => {
875
- if (E.current) {
876
- const l = E.current.storage.markdown.getMarkdown();
877
- a?.(Lt(l));
1535
+ if (onJsonContentChange) {
1536
+ const jsonContent = removeClassesFromJson(editor.getJSON());
1537
+ onJsonContentChange(jsonContent);
1538
+ }
1539
+ if (onHtmlContentChange) {
1540
+ onHtmlContentChange?.(editor.getHTML());
878
1541
  }
879
- }, !1, 500), R(M, () => {
880
- M && t?.(M);
881
- }, !1, 500), R(N, () => {
882
- N && r?.(N);
883
- }, !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(
884
1577
  "div",
885
1578
  {
886
- className: "relative min-h-[500px] w-full bg-white dark:bg-gray-950 rounded-lg",
887
- children: /* @__PURE__ */ b(
888
- ye,
1579
+ ref,
1580
+ className: "relative min-h-[300px] w-full",
1581
+ children: /* @__PURE__ */ jsx(
1582
+ EditorProvider,
889
1583
  {
890
- content: o,
891
- extensions: u,
1584
+ content: content ?? "",
1585
+ extensions,
892
1586
  editorProps: {
893
- ...c,
894
1587
  attributes: {
895
- 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")
896
1589
  }
897
1590
  },
898
- onUpdate: ({ editor: l }) => {
899
- console.debug("Editor updated"), ue(l);
1591
+ onCreate: ({ editor }) => {
1592
+ editorRef.current = editor;
900
1593
  },
901
- children: [
902
- /* @__PURE__ */ b(
903
- tt,
904
- {
905
- 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),
906
- children: [
907
- /* @__PURE__ */ s(ot, { className: "px-2 text-gray-700 dark:text-slate-300", children: "No results" }),
908
- n.map((l) => /* @__PURE__ */ b(
909
- se,
910
- {
911
- value: l.title,
912
- onCommand: (m) => l?.command?.(m),
913
- className: "flex w-full items-center space-x-2 rounded-md px-2 py-1 text-left text-sm hover:bg-blue-50 hover:dark:bg-gray-700 aria-selected:bg-blue-50 aria-selected:dark:bg-gray-700",
914
- children: [
915
- /* @__PURE__ */ s(
916
- "div",
917
- {
918
- className: g("flex h-10 w-10 items-center justify-center rounded-md border bg-white dark:bg-gray-900", x),
919
- children: l.icon
920
- }
921
- ),
922
- /* @__PURE__ */ b("div", { children: [
923
- /* @__PURE__ */ s("p", { className: "font-medium", children: l.title }),
924
- /* @__PURE__ */ s("p", { className: "text-xs text-gray-700 dark:text-slate-300", children: l.description })
925
- ] })
926
- ]
927
- },
928
- l.title
929
- ))
930
- ]
931
- }
932
- ),
933
- /* @__PURE__ */ b(
934
- je,
935
- {
936
- tippyOptions: {
937
- placement: "top"
938
- },
939
- className: g("flex w-fit max-w-[90vw] h-10 overflow-hidden rounded border bg-white dark:bg-gray-900 shadow", x),
940
- children: [
941
- /* @__PURE__ */ s(ct, { open: p, onOpenChange: h }),
942
- /* @__PURE__ */ s(K, { orientation: "vertical" }),
943
- /* @__PURE__ */ s(ut, { open: f, onOpenChange: A }),
944
- /* @__PURE__ */ s(K, { orientation: "vertical" }),
945
- /* @__PURE__ */ s(mt, {})
946
- ]
947
- }
948
- )
949
- ]
1594
+ onUpdate: ({ editor }) => {
1595
+ onEditorUpdate(editor);
1596
+ },
1597
+ children: /* @__PURE__ */ jsxs(
1598
+ EditorBubble,
1599
+ {
1600
+ tippyOptions: {
1601
+ placement: "top"
1602
+ },
1603
+ className: cls("flex w-fit max-w-[90vw] h-10 overflow-hidden rounded border bg-white dark:bg-gray-900 shadow", defaultBorderMixin),
1604
+ children: [
1605
+ /* @__PURE__ */ jsx(NodeSelector, { portalContainer: ref.current, open: openNode, onOpenChange: setOpenNode }),
1606
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical" }),
1607
+ /* @__PURE__ */ jsx(LinkSelector, { open: openLink, onOpenChange: setOpenLink }),
1608
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical" }),
1609
+ /* @__PURE__ */ jsx(TextButtons, {})
1610
+ ]
1611
+ }
1612
+ )
950
1613
  }
951
1614
  )
952
1615
  }
953
- ) }) }) : null;
1616
+ );
954
1617
  };
955
- function Lt(e) {
956
- const o = /!\[.*?\]\(.*?\)/g;
957
- return e.replace(o, (t) => `${t}
958
- `);
1618
+ function addLineBreakAfterImages(markdown) {
1619
+ const imageRegex = /!\[.*?\]\(.*?\)/g;
1620
+ return markdown.replace(imageRegex, (match) => `${match}`);
1621
+ }
1622
+ const cssStyles = `
1623
+ .ProseMirror {
1624
+ box-shadow: none !important;
959
1625
  }
960
- const Tt = `
961
-
962
1626
  .ProseMirror .is-editor-empty:first-child::before {
963
1627
  content: attr(data-placeholder);
964
1628
  float: left;
@@ -981,6 +1645,7 @@ const Tt = `
981
1645
  }
982
1646
 
983
1647
  .is-empty {
1648
+ cursor: text;
984
1649
  color: rgb(100 116 139); //500
985
1650
  }
986
1651
 
@@ -998,6 +1663,7 @@ const Tt = `
998
1663
  &.ProseMirror-selectednode {
999
1664
  outline: 3px solid #5abbf7;
1000
1665
  filter: brightness(90%);
1666
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000) !important;
1001
1667
  }
1002
1668
  }
1003
1669
 
@@ -1025,7 +1691,7 @@ ul[data-type="taskList"] li > label {
1025
1691
  }
1026
1692
 
1027
1693
  &:active {
1028
- background-color: rgb(71 85 105);;
1694
+ background-color: rgb(71 85 105);
1029
1695
  }
1030
1696
  }
1031
1697
  }
@@ -1092,7 +1758,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
1092
1758
  }
1093
1759
 
1094
1760
  .ProseMirror:not(.dragging) .ProseMirror-selectednode {
1095
- outline: none !important;
1761
+ // outline: none !important;
1096
1762
  background-color: rgb(219 234 254); // blue 100
1097
1763
  transition: background-color 0.2s;
1098
1764
  box-shadow: none;
@@ -1103,7 +1769,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
1103
1769
  }
1104
1770
 
1105
1771
  .drag-handle {
1106
- position: fixed;
1772
+ position: absolute;
1107
1773
  opacity: 1;
1108
1774
  transition: opacity ease-in 0.2s;
1109
1775
  border-radius: 0.25rem;
@@ -1114,7 +1780,7 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
1114
1780
  background-position: center;
1115
1781
  width: 1.2rem;
1116
1782
  height: 1.5rem;
1117
- z-index: 50;
1783
+ z-index: 100;
1118
1784
  cursor: grab;
1119
1785
 
1120
1786
  &:hover {
@@ -1149,6 +1815,6 @@ ul[data-type="taskList"] li[data-checked="true"] > div > p {
1149
1815
  }
1150
1816
  `;
1151
1817
  export {
1152
- eo as FireCMSEditor
1818
+ FireCMSEditor
1153
1819
  };
1154
1820
  //# sourceMappingURL=index.es.js.map