UNPKG

vue-simple-tiptap-editor

Version:

A lightweight Vue 3 wrapper around **Tiptap**, designed for simplicity and flexibility. ---

1,177 lines (1,176 loc) 45.9 kB
import { computed as W, shallowRef as H, onBeforeUnmount as J, withDirectives as Q, createElementBlock as L, openBlock as M, normalizeClass as B, createElementVNode as w, createVNode as Y, Fragment as Z, renderList as R, unref as x, vShow as ee } from "vue"; import { useEditor as te, EditorContent as ne } from "@tiptap/vue-3"; import _ from "axios"; import { createLowlight as se, common as re } from "lowlight"; import { Node as m, wrappingInputRule as v, mergeAttributes as f, renderNestedMarkdownContent as V, Extension as P, parseIndentedBlocks as j, isNodeActive as y, isAtStartOfNode as ie, isAtEndOfNode as oe, getNodeType as D, getNodeAtPosition as ae } from "@tiptap/core"; import { Color as le, TextStyle as N } from "@tiptap/extension-text-style"; import ce from "@tiptap/starter-kit"; import ue from "@tiptap/extension-code-block-lowlight"; import de from "@tiptap/extension-file-handler"; import he from "@tiptap/extension-text-align"; import pe from "@tiptap/extension-image"; import ge from "@tiptap/extension-heading"; import fe from "@tiptap/extension-link"; function me(e, t, n, s) { return W(() => [ { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>', title: "clear", action: () => { t() && (e.value.chain().focus().unsetAllMarks().run(), e.value.chain().focus().clearNodes().run()); } }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bold-icon lucide-bold"><path d="M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8"/></svg>', title: "Bold", action: () => t() && e.value.chain().focus().toggleBold().run(), active: () => t() && e.value.isActive("bold") }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-italic-icon lucide-italic"><line x1="19" x2="10" y1="4" y2="4"/><line x1="14" x2="5" y1="20" y2="20"/><line x1="15" x2="9" y1="4" y2="20"/></svg>', title: "Italic", action: () => t() && e.value.chain().focus().toggleItalic().run(), active: () => t() && e.value.isActive("italic") }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-strikethrough-icon lucide-strikethrough"><path d="M16 4H9a3 3 0 0 0-2.83 4"/><path d="M14 12a4 4 0 0 1 0 8H6"/><line x1="4" x2="20" y1="12" y2="12"/></svg>', title: "Strike", action: () => t() && e.value.chain().focus().toggleStrike().run(), active: () => t() && e.value.isActive("strike") }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-code-xml-icon lucide-code-xml"><path d="m18 16 4-4-4-4"/><path d="m6 8-4 4 4 4"/><path d="m14.5 4-5 16"/></svg>', title: "Code", action: () => t() && e.value.chain().focus().toggleCode().run(), active: () => t() && e.value.isActive("code") }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pilcrow-icon lucide-pilcrow"><path d="M13 4v16"/><path d="M17 4v16"/><path d="M19 4H9.5a4.5 4.5 0 0 0 0 9H13"/></svg>', title: "Code Block", action: () => t() && e.value.chain().focus().setParagraph().run(), active: () => t() && e.value.isActive("paragraph") }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-heading-icon lucide-heading"><path d="M6 12h12"/><path d="M6 20V4"/><path d="M18 20V4"/></svg>', title: "Heading 1", action: () => t() && e.value.chain().focus().toggleHeading({ level: 1 }).run(), active: () => t() && e.value.isActive("heading", { level: 1 }) }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-heading2-icon lucide-heading-2"><path d="M4 12h8"/><path d="M4 18V6"/><path d="M12 18V6"/><path d="M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1"/></svg>', title: "Heading 2", action: () => t() && e.value.chain().focus().toggleHeading({ level: 2 }).run(), active: () => t() && e.value.isActive("heading", { level: 2 }) }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-heading3-icon lucide-heading-3"><path d="M4 12h8"/><path d="M4 18V6"/><path d="M12 18V6"/><path d="M17.5 10.5c1.7-1 3.5 0 3.5 1.5a2 2 0 0 1-2 2"/><path d="M17 17.5c2 1.5 4 .3 4-1.5a2 2 0 0 0-2-2"/></svg>', title: "Heading 3", action: () => t() && e.value.chain().focus().toggleHeading({ level: 3 }).run(), active: () => t() && e.value.isActive("heading", { level: 3 }) }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-heading4-icon lucide-heading-4"><path d="M12 18V6"/><path d="M17 10v3a1 1 0 0 0 1 1h3"/><path d="M21 10v8"/><path d="M4 12h8"/><path d="M4 18V6"/></svg>', title: "Heading 4", action: () => t() && e.value.chain().focus().toggleHeading({ level: 4 }).run(), active: () => t() && e.value.isActive("heading", { level: 4 }) }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-heading5-icon lucide-heading-5"><path d="M4 12h8"/><path d="M4 18V6"/><path d="M12 18V6"/><path d="M17 13v-3h4"/><path d="M17 17.7c.4.2.8.3 1.3.3 1.5 0 2.7-1.1 2.7-2.5S19.8 13 18.3 13H17"/></svg>', title: "Heading 5", action: () => t() && e.value.chain().focus().toggleHeading({ level: 5 }).run(), active: () => t() && e.value.isActive("heading", { level: 5 }) }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-heading6-icon lucide-heading-6"><path d="M4 12h8"/><path d="M4 18V6"/><path d="M12 18V6"/><circle cx="19" cy="16" r="2"/><path d="M20 10c-2 2-3 3.5-3 6"/></svg>', title: "Heading 6", action: () => t() && e.value.chain().focus().toggleHeading({ level: 6 }).run(), active: () => t() && e.value.isActive("heading", { level: 6 }) }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-ordered-icon lucide-list-ordered"><path d="M11 5h10"/><path d="M11 12h10"/><path d="M11 19h10"/><path d="M4 4h1v5"/><path d="M4 9h2"/><path d="M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02"/></svg>', title: "Ordered List", action: () => t() && e.value.chain().focus().toggleOrderedList().run(), active: () => t() && e.value.isActive("orderedList") }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-icon lucide-list"><path d="M3 5h.01"/><path d="M3 12h.01"/><path d="M3 19h.01"/><path d="M8 5h13"/><path d="M8 12h13"/><path d="M8 19h13"/></svg>', title: "Bullet List", action: () => t() && e.value.chain().focus().toggleBulletList().run(), active: () => t() && e.value.isActive("bulletList") }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-code-corner-icon lucide-file-code-corner"><path d="M4 12.15V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-3.35"/><path d="M14 2v5a1 1 0 0 0 1 1h5"/><path d="m5 16-3 3 3 3"/><path d="m9 22 3-3-3-3"/></svg>', title: "Code Block", action: () => t() && e.value.chain().focus().toggleCodeBlock().run(), active: () => t() && e.value.isActive("codeBlock") }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-message-square-quote-icon lucide-message-square-quote"><path d="M14 14a2 2 0 0 0 2-2V8h-2"/><path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"/><path d="M8 14a2 2 0 0 0 2-2V8H8"/></svg>', title: "Block Quote", action: () => t() && e.value.chain().focus().toggleBlockquote().run(), active: () => t() && e.value.isActive("blockquote") }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-undo-icon lucide-undo"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/></svg>', title: "Undo", action: () => t() && e.value.chain().focus().undo().run(), disabled: () => t() && !e.value.can().chain().focus().undo().run() }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-redo-icon lucide-redo"><path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7"/></svg>', title: "Redo", action: () => t() && e.value.chain().focus().redo().run(), disabled: () => t() && !e.value.can().chain().focus().redo().run() }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-image-icon lucide-image"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>', title: "Add Image", action: () => t() && n() }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link-icon lucide-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>', title: "Link", action: () => t() && s(), active: () => t() && e.value.isActive("link") }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-unlink-icon lucide-unlink"><path d="m18.84 12.25 1.72-1.71h-.02a5.004 5.004 0 0 0-.12-7.07 5.006 5.006 0 0 0-6.95 0l-1.72 1.71"/><path d="m5.17 11.75-1.71 1.71a5.004 5.004 0 0 0 .12 7.07 5.006 5.006 0 0 0 6.95 0l1.71-1.71"/><line x1="8" x2="8" y1="2" y2="5"/><line x1="2" x2="5" y1="8" y2="8"/><line x1="16" x2="16" y1="19" y2="22"/><line x1="19" x2="22" y1="16" y2="16"/></svg>', title: "Unset Link", action: () => t() && e.value.chain().focus().unsetLink().run(), disabled: () => t() && !e.value.isActive("link") }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-align-start-icon lucide-text-align-start"><path d="M21 5H3"/><path d="M15 12H3"/><path d="M17 19H3"/></svg>', title: "Align Left", action: () => t() && e.value.chain().focus().setTextAlign("left").run(), active: () => t() && e.value.isActive({ textAlign: "left" }) }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-align-center-icon lucide-text-align-center"><path d="M21 5H3"/><path d="M17 12H7"/><path d="M19 19H5"/></svg>', title: "Align Center", action: () => t() && e.value.chain().focus().setTextAlign("center").run(), active: () => t() && e.value.isActive({ textAlign: "center" }) }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-align-end-icon lucide-text-align-end"><path d="M21 5H3"/><path d="M21 12H9"/><path d="M21 19H7"/></svg>', title: "Align Right", action: () => t() && e.value.chain().focus().setTextAlign("right").run(), active: () => t() && e.value.isActive({ textAlign: "right" }) }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-align-justify-icon lucide-text-align-justify"><path d="M3 5h18"/><path d="M3 12h18"/><path d="M3 19h18"/></svg>', title: "Justify", action: () => t() && e.value.chain().focus().setTextAlign("justify").run(), active: () => t() && e.value.isActive({ textAlign: "justify" }) }, { label: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list-x-icon lucide-list-x"><path d="M16 5H3"/><path d="M11 12H3"/><path d="M16 19H3"/><path d="m15.5 9.5 5 5"/><path d="m20.5 9.5-5 5"/></svg>', title: "Unset Alignment", action: () => t() && e.value.chain().focus().unsetTextAlign().run() } ]); } function ve(e, t, n) { const s = H(/* @__PURE__ */ new Set()), r = H(/* @__PURE__ */ new Map()); async function o(i) { if (!t.uploadUrl) throw new Error("uploadUrl prop is not set"); const d = new FormData(); d.append("file", i), n("upload-start", i); try { const a = (await _.post(t.uploadUrl, d, { headers: { ...t.headers, "Content-Type": "multipart/form-data" } })).data, l = a.url || a.path || a.data?.url; if (!l) throw new Error("Upload response did not include url"); return n("upload-success", l, i), l; } catch (u) { throw n("upload-error", u, i), u; } } function c() { const i = document.createElement("input"); i.type = "file", i.accept = "image/*", i.multiple = !0, i.onchange = async () => { const d = Array.from(i.files); for (const u of d) { if (t.uploadOnInsert) if (!t.uploadUrl) console.warn("uploadOnInsert is true but uploadUrl is not provided"); else { const h = URL.createObjectURL(u); e.value.chain().focus().insertContentAt(e.value.state.selection.anchor, { type: "image", attrs: { src: h, "data-uploading": "true" } }).run(); const p = o(u).then((g) => { e.value.chain().focus().updateAttributes("image", { src: g, "data-uploading": null }).run(), s.value.add(g), r.value.delete(h); }).catch((g) => { console.error("upload failed", g), r.value.delete(h); }); r.value.set(h, p); continue; } const a = e.value.state.selection.anchor; e.value.chain().focus().insertContentAt(a, "Loading...").run(); const l = URL.createObjectURL(u); e.value.chain().focus().insertContentAt(e.value.state.selection.anchor, { type: "image", attrs: { src: l } }).run(), e.value.chain().focus().deleteRange({ from: a, to: a + 10 }).run(); } }, i.click(); } return { uploadFile: o, addImage: c, uploadedImages: s, pendingUploads: r }; } var ke = Object.defineProperty, we = (e, t) => { for (var n in t) ke(e, n, { get: t[n], enumerable: !0 }); }, xe = "listItem", E = "textStyle", S = /^\s*([-+*])\s$/, be = m.create({ name: "bulletList", addOptions() { return { itemTypeName: "listItem", HTMLAttributes: {}, keepMarks: !1, keepAttributes: !1 }; }, group: "block list", content() { return `${this.options.itemTypeName}+`; }, parseHTML() { return [{ tag: "ul" }]; }, renderHTML({ HTMLAttributes: e }) { return ["ul", f(this.options.HTMLAttributes, e), 0]; }, markdownTokenName: "list", parseMarkdown: (e, t) => e.type !== "list" || e.ordered ? [] : { type: "bulletList", content: e.items ? t.parseChildren(e.items) : [] }, renderMarkdown: (e, t) => e.content ? t.renderChildren(e.content, ` `) : "", markdownOptions: { indentsContent: !0 }, addCommands() { return { toggleBulletList: () => ({ commands: e, chain: t }) => this.options.keepAttributes ? t().toggleList(this.name, this.options.itemTypeName, this.options.keepMarks).updateAttributes(xe, this.editor.getAttributes(E)).run() : e.toggleList(this.name, this.options.itemTypeName, this.options.keepMarks) }; }, addKeyboardShortcuts() { return { "Mod-Shift-8": () => this.editor.commands.toggleBulletList() }; }, addInputRules() { let e = v({ find: S, type: this.type }); return (this.options.keepMarks || this.options.keepAttributes) && (e = v({ find: S, type: this.type, keepMarks: this.options.keepMarks, keepAttributes: this.options.keepAttributes, getAttributes: () => this.editor.getAttributes(E), editor: this.editor })), [e]; } }), A = m.create({ name: "listItem", addOptions() { return { HTMLAttributes: {}, bulletListTypeName: "bulletList", orderedListTypeName: "orderedList" }; }, content: "paragraph block*", defining: !0, parseHTML() { return [ { tag: "li" } ]; }, renderHTML({ HTMLAttributes: e }) { return ["li", f(this.options.HTMLAttributes, e), 0]; }, markdownTokenName: "list_item", parseMarkdown: (e, t) => { if (e.type !== "list_item") return []; let n = []; if (e.tokens && e.tokens.length > 0) if (e.tokens.some((r) => r.type === "paragraph")) n = t.parseChildren(e.tokens); else { const r = e.tokens[0]; if (r && r.type === "text" && r.tokens && r.tokens.length > 0) { if (n = [ { type: "paragraph", content: t.parseInline(r.tokens) } ], e.tokens.length > 1) { const c = e.tokens.slice(1), i = t.parseChildren(c); n.push(...i); } } else n = t.parseChildren(e.tokens); } return n.length === 0 && (n = [ { type: "paragraph", content: [] } ]), { type: "listItem", content: n }; }, renderMarkdown: (e, t, n) => V( e, t, (s) => s.parentType === "bulletList" ? "- " : s.parentType === "orderedList" ? `${s.index + 1}. ` : "- ", n ), addKeyboardShortcuts() { return { Enter: () => this.editor.commands.splitListItem(this.name), Tab: () => this.editor.commands.sinkListItem(this.name), "Shift-Tab": () => this.editor.commands.liftListItem(this.name) }; } }), Le = {}; we(Le, { findListItemPos: () => k, getNextListDepth: () => T, handleBackspace: () => C, handleDelete: () => I, hasListBefore: () => F, hasListItemAfter: () => Me, hasListItemBefore: () => K, listItemHasSubList: () => q, nextListIsDeeper: () => z, nextListIsHigher: () => X }); var k = (e, t) => { const { $from: n } = t.selection, s = D(e, t.schema); let r = null, o = n.depth, c = n.pos, i = null; for (; o > 0 && i === null; ) r = n.node(o), r.type === s ? i = o : (o -= 1, c -= 1); return i === null ? null : { $pos: t.doc.resolve(c), depth: i }; }, T = (e, t) => { const n = k(e, t); if (!n) return !1; const [, s] = ae(t, e, n.$pos.pos + 4); return s; }, F = (e, t, n) => { const { $anchor: s } = e.selection, r = Math.max(0, s.pos - 2), o = e.doc.resolve(r).node(); return !(!o || !n.includes(o.type.name)); }, K = (e, t) => { var n; const { $anchor: s } = t.selection, r = t.doc.resolve(s.pos - 2); return !(r.index() === 0 || ((n = r.nodeBefore) == null ? void 0 : n.type.name) !== e); }, q = (e, t, n) => { if (!n) return !1; const s = D(e, t.schema); let r = !1; return n.descendants((o) => { o.type === s && (r = !0); }), r; }, C = (e, t, n) => { if (e.commands.undoInputRule()) return !0; if (e.state.selection.from !== e.state.selection.to) return !1; if (!y(e.state, t) && F(e.state, t, n)) { const { $anchor: i } = e.state.selection, d = e.state.doc.resolve(i.before() - 1), u = []; d.node().descendants((h, p) => { h.type.name === t && u.push({ node: h, pos: p }); }); const a = u.at(-1); if (!a) return !1; const l = e.state.doc.resolve(d.start() + a.pos + 1); return e.chain().cut({ from: i.start() - 1, to: i.end() + 1 }, l.end()).joinForward().run(); } if (!y(e.state, t) || !ie(e.state)) return !1; const s = k(t, e.state); if (!s) return !1; const o = e.state.doc.resolve(s.$pos.pos - 2).node(s.depth), c = q(t, e.state, o); return K(t, e.state) && !c ? e.commands.joinItemBackward() : e.chain().liftListItem(t).run(); }, z = (e, t) => { const n = T(e, t), s = k(e, t); return !s || !n ? !1 : n > s.depth; }, X = (e, t) => { const n = T(e, t), s = k(e, t); return !s || !n ? !1 : n < s.depth; }, I = (e, t) => { if (!y(e.state, t) || !oe(e.state, t)) return !1; const { selection: n } = e.state, { $from: s, $to: r } = n; return !n.empty && s.sameParent(r) ? !1 : z(t, e.state) ? e.chain().focus(e.state.selection.from + 4).lift(t).joinBackward().run() : X(t, e.state) ? e.chain().joinForward().joinBackward().run() : e.commands.joinItemForward(); }, Me = (e, t) => { var n; const { $anchor: s } = t.selection, r = t.doc.resolve(s.pos - s.parentOffset - 2); return !(r.index() === r.parent.childCount - 1 || ((n = r.nodeAfter) == null ? void 0 : n.type.name) !== e); }, ye = P.create({ name: "listKeymap", addOptions() { return { listTypes: [ { itemName: "listItem", wrapperNames: ["bulletList", "orderedList"] }, { itemName: "taskItem", wrapperNames: ["taskList"] } ] }; }, addKeyboardShortcuts() { return { Delete: ({ editor: e }) => { let t = !1; return this.options.listTypes.forEach(({ itemName: n }) => { e.state.schema.nodes[n] !== void 0 && I(e, n) && (t = !0); }), t; }, "Mod-Delete": ({ editor: e }) => { let t = !1; return this.options.listTypes.forEach(({ itemName: n }) => { e.state.schema.nodes[n] !== void 0 && I(e, n) && (t = !0); }), t; }, Backspace: ({ editor: e }) => { let t = !1; return this.options.listTypes.forEach(({ itemName: n, wrapperNames: s }) => { e.state.schema.nodes[n] !== void 0 && C(e, n, s) && (t = !0); }), t; }, "Mod-Backspace": ({ editor: e }) => { let t = !1; return this.options.listTypes.forEach(({ itemName: n, wrapperNames: s }) => { e.state.schema.nodes[n] !== void 0 && C(e, n, s) && (t = !0); }), t; } }; } }), O = /^(\s*)(\d+)\.\s+(.*)$/, Ae = /^\s/; function Ce(e) { const t = []; let n = 0, s = 0; for (; n < e.length; ) { const r = e[n], o = r.match(O); if (!o) break; const [, c, i, d] = o, u = c.length; let a = d, l = n + 1; const h = [r]; for (; l < e.length; ) { const p = e[l]; if (p.match(O)) break; if (p.trim() === "") h.push(p), a += ` `, l += 1; else if (p.match(Ae)) h.push(p), a += ` ${p.slice(u + 2)}`, l += 1; else break; } t.push({ indent: u, number: parseInt(i, 10), content: a.trim(), raw: h.join(` `) }), s = l, n = l; } return [t, s]; } function G(e, t, n) { var s; const r = []; let o = 0; for (; o < e.length; ) { const c = e[o]; if (c.indent === t) { const i = c.content.split(` `), d = ((s = i[0]) == null ? void 0 : s.trim()) || "", u = []; d && u.push({ type: "paragraph", raw: d, tokens: n.inlineTokens(d) }); const a = i.slice(1).join(` `).trim(); if (a) { const p = n.blockTokens(a); u.push(...p); } let l = o + 1; const h = []; for (; l < e.length && e[l].indent > t; ) h.push(e[l]), l += 1; if (h.length > 0) { const p = Math.min(...h.map((b) => b.indent)), g = G(h, p, n); u.push({ type: "list", ordered: !0, start: h[0].number, items: g, raw: h.map((b) => b.raw).join(` `) }); } r.push({ type: "list_item", raw: c.raw, tokens: u }), o = l; } else o += 1; } return r; } function Ie(e, t) { return e.map((n) => { if (n.type !== "list_item") return t.parseChildren([n])[0]; const s = []; return n.tokens && n.tokens.length > 0 && n.tokens.forEach((r) => { if (r.type === "paragraph" || r.type === "list" || r.type === "blockquote" || r.type === "code") s.push(...t.parseChildren([r])); else if (r.type === "text" && r.tokens) { const o = t.parseChildren([r]); s.push({ type: "paragraph", content: o }); } else { const o = t.parseChildren([r]); o.length > 0 && s.push(...o); } }), { type: "listItem", content: s }; }); } var Te = "listItem", U = "textStyle", $ = /^(\d+)\.\s$/, He = m.create({ name: "orderedList", addOptions() { return { itemTypeName: "listItem", HTMLAttributes: {}, keepMarks: !1, keepAttributes: !1 }; }, group: "block list", content() { return `${this.options.itemTypeName}+`; }, addAttributes() { return { start: { default: 1, parseHTML: (e) => e.hasAttribute("start") ? parseInt(e.getAttribute("start") || "", 10) : 1 }, type: { default: null, parseHTML: (e) => e.getAttribute("type") } }; }, parseHTML() { return [ { tag: "ol" } ]; }, renderHTML({ HTMLAttributes: e }) { const { start: t, ...n } = e; return t === 1 ? ["ol", f(this.options.HTMLAttributes, n), 0] : ["ol", f(this.options.HTMLAttributes, e), 0]; }, markdownTokenName: "list", parseMarkdown: (e, t) => { if (e.type !== "list" || !e.ordered) return []; const n = e.start || 1, s = e.items ? Ie(e.items, t) : []; return n !== 1 ? { type: "orderedList", attrs: { start: n }, content: s } : { type: "orderedList", content: s }; }, renderMarkdown: (e, t) => e.content ? t.renderChildren(e.content, ` `) : "", markdownTokenizer: { name: "orderedList", level: "block", start: (e) => { const t = e.match(/^(\s*)(\d+)\.\s+/), n = t?.index; return n !== void 0 ? n : -1; }, tokenize: (e, t, n) => { var s; const r = e.split(` `), [o, c] = Ce(r); if (o.length === 0) return; const i = G(o, 0, n); return i.length === 0 ? void 0 : { type: "list", ordered: !0, start: ((s = o[0]) == null ? void 0 : s.number) || 1, items: i, raw: r.slice(0, c).join(` `) }; } }, markdownOptions: { indentsContent: !0 }, addCommands() { return { toggleOrderedList: () => ({ commands: e, chain: t }) => this.options.keepAttributes ? t().toggleList(this.name, this.options.itemTypeName, this.options.keepMarks).updateAttributes(Te, this.editor.getAttributes(U)).run() : e.toggleList(this.name, this.options.itemTypeName, this.options.keepMarks) }; }, addKeyboardShortcuts() { return { "Mod-Shift-7": () => this.editor.commands.toggleOrderedList() }; }, addInputRules() { let e = v({ find: $, type: this.type, getAttributes: (t) => ({ start: +t[1] }), joinPredicate: (t, n) => n.childCount + n.attrs.start === +t[1] }); return (this.options.keepMarks || this.options.keepAttributes) && (e = v({ find: $, type: this.type, keepMarks: this.options.keepMarks, keepAttributes: this.options.keepAttributes, getAttributes: (t) => ({ start: +t[1], ...this.editor.getAttributes(U) }), joinPredicate: (t, n) => n.childCount + n.attrs.start === +t[1], editor: this.editor })), [e]; } }), Be = /^\s*(\[([( |x])?\])\s$/, je = m.create({ name: "taskItem", addOptions() { return { nested: !1, HTMLAttributes: {}, taskListTypeName: "taskList", a11y: void 0 }; }, content() { return this.options.nested ? "paragraph block*" : "paragraph+"; }, defining: !0, addAttributes() { return { checked: { default: !1, keepOnSplit: !1, parseHTML: (e) => { const t = e.getAttribute("data-checked"); return t === "" || t === "true"; }, renderHTML: (e) => ({ "data-checked": e.checked }) } }; }, parseHTML() { return [ { tag: `li[data-type="${this.name}"]`, priority: 51 } ]; }, renderHTML({ node: e, HTMLAttributes: t }) { return [ "li", f(this.options.HTMLAttributes, t, { "data-type": this.name }), [ "label", [ "input", { type: "checkbox", checked: e.attrs.checked ? "checked" : null } ], ["span"] ], ["div", 0] ]; }, parseMarkdown: (e, t) => { const n = []; if (e.tokens && e.tokens.length > 0 ? n.push(t.createNode("paragraph", {}, t.parseInline(e.tokens))) : e.text ? n.push(t.createNode("paragraph", {}, [t.createNode("text", { text: e.text })])) : n.push(t.createNode("paragraph", {}, [])), e.nestedTokens && e.nestedTokens.length > 0) { const s = t.parseChildren(e.nestedTokens); n.push(...s); } return t.createNode("taskItem", { checked: e.checked || !1 }, n); }, renderMarkdown: (e, t) => { var n; const r = `- [${(n = e.attrs) != null && n.checked ? "x" : " "}] `; return V(e, t, r); }, addKeyboardShortcuts() { const e = { Enter: () => this.editor.commands.splitListItem(this.name), "Shift-Tab": () => this.editor.commands.liftListItem(this.name) }; return this.options.nested ? { ...e, Tab: () => this.editor.commands.sinkListItem(this.name) } : e; }, addNodeView() { return ({ node: e, HTMLAttributes: t, getPos: n, editor: s }) => { const r = document.createElement("li"), o = document.createElement("label"), c = document.createElement("span"), i = document.createElement("input"), d = document.createElement("div"), u = (a) => { var l, h; i.ariaLabel = ((h = (l = this.options.a11y) == null ? void 0 : l.checkboxLabel) == null ? void 0 : h.call(l, a, i.checked)) || `Task item checkbox for ${a.textContent || "empty task item"}`; }; return u(e), o.contentEditable = "false", i.type = "checkbox", i.addEventListener("mousedown", (a) => a.preventDefault()), i.addEventListener("change", (a) => { if (!s.isEditable && !this.options.onReadOnlyChecked) { i.checked = !i.checked; return; } const { checked: l } = a.target; s.isEditable && typeof n == "function" && s.chain().focus(void 0, { scrollIntoView: !1 }).command(({ tr: h }) => { const p = n(); if (typeof p != "number") return !1; const g = h.doc.nodeAt(p); return h.setNodeMarkup(p, void 0, { ...g?.attrs, checked: l }), !0; }).run(), !s.isEditable && this.options.onReadOnlyChecked && (this.options.onReadOnlyChecked(e, l) || (i.checked = !i.checked)); }), Object.entries(this.options.HTMLAttributes).forEach(([a, l]) => { r.setAttribute(a, l); }), r.dataset.checked = e.attrs.checked, i.checked = e.attrs.checked, o.append(i, c), r.append(o, d), Object.entries(t).forEach(([a, l]) => { r.setAttribute(a, l); }), { dom: r, contentDOM: d, update: (a) => a.type !== this.type ? !1 : (r.dataset.checked = a.attrs.checked, i.checked = a.attrs.checked, u(a), !0) }; }; }, addInputRules() { return [ v({ find: Be, type: this.type, getAttributes: (e) => ({ checked: e[e.length - 1] === "x" }) }) ]; } }), Ne = m.create({ name: "taskList", addOptions() { return { itemTypeName: "taskItem", HTMLAttributes: {} }; }, group: "block list", content() { return `${this.options.itemTypeName}+`; }, parseHTML() { return [ { tag: `ul[data-type="${this.name}"]`, priority: 51 } ]; }, renderHTML({ HTMLAttributes: e }) { return ["ul", f(this.options.HTMLAttributes, e, { "data-type": this.name }), 0]; }, parseMarkdown: (e, t) => t.createNode("taskList", {}, t.parseChildren(e.items || [])), renderMarkdown: (e, t) => e.content ? t.renderChildren(e.content, ` `) : "", markdownTokenizer: { name: "taskList", level: "block", start(e) { var t; const n = (t = e.match(/^\s*[-+*]\s+\[([ xX])\]\s+/)) == null ? void 0 : t.index; return n !== void 0 ? n : -1; }, tokenize(e, t, n) { const s = (o) => { const c = j( o, { itemPattern: /^(\s*)([-+*])\s+\[([ xX])\]\s+(.*)$/, extractItemData: (i) => ({ indentLevel: i[1].length, mainContent: i[4], checked: i[3].toLowerCase() === "x" }), createToken: (i, d) => ({ type: "taskItem", raw: "", mainContent: i.mainContent, indentLevel: i.indentLevel, checked: i.checked, text: i.mainContent, tokens: n.inlineTokens(i.mainContent), nestedTokens: d }), // Allow recursive nesting customNestedParser: s }, n ); return c ? [ { type: "taskList", raw: c.raw, items: c.items } ] : n.blockTokens(o); }, r = j( e, { itemPattern: /^(\s*)([-+*])\s+\[([ xX])\]\s+(.*)$/, extractItemData: (o) => ({ indentLevel: o[1].length, mainContent: o[4], checked: o[3].toLowerCase() === "x" }), createToken: (o, c) => ({ type: "taskItem", raw: "", mainContent: o.mainContent, indentLevel: o.indentLevel, checked: o.checked, text: o.mainContent, tokens: n.inlineTokens(o.mainContent), nestedTokens: c }), // Use the recursive parser for nested content customNestedParser: s }, n ); if (r) return { type: "taskList", raw: r.raw, items: r.items }; } }, markdownOptions: { indentsContent: !0 }, addCommands() { return { toggleTaskList: () => ({ commands: e }) => e.toggleList(this.name, this.options.itemTypeName) }; }, addKeyboardShortcuts() { return { "Mod-Shift-9": () => this.editor.commands.toggleTaskList() }; } }); P.create({ name: "listKit", addExtensions() { const e = []; return this.options.bulletList !== !1 && e.push(be.configure(this.options.bulletList)), this.options.listItem !== !1 && e.push(A.configure(this.options.listItem)), this.options.listKeymap !== !1 && e.push(ye.configure(this.options.listKeymap)), this.options.orderedList !== !1 && e.push(He.configure(this.options.orderedList)), this.options.taskItem !== !1 && e.push(je.configure(this.options.taskItem)), this.options.taskList !== !1 && e.push(Ne.configure(this.options.taskList)), e; } }); const Ee = m.create({ name: "iframe", group: "block", atom: !0, addOptions() { return { allowFullscreen: !0, HTMLAttributes: { class: "iframe-wrapper" } }; }, addAttributes() { return { src: { default: null }, frameborder: { default: 0 }, allow: { default: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" }, allowfullscreen: { default: this.options.allowFullscreen } }; }, parseHTML() { return [ { tag: "iframe" } ]; }, renderHTML({ HTMLAttributes: e }) { return ["div", { class: "iframe-wrapper" }, ["iframe", f(e)]]; }, addCommands() { return { setIframe: (e) => ({ commands: t }) => t.insertContent({ type: this.name, attrs: e }) }; }, addPasteRules() { return [ { find: /<iframe.*?src="(.*?)".*?>.*?<\/iframe>/gi, handler: ({ match: e, chain: t, range: n }) => { const s = e[1]; s && t().focus().deleteRange(n).setIframe({ src: s }).run(); } } ]; } }); function Se(e) { return e.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-"); } function Oe() { return ge.extend({ renderHTML({ node: e, HTMLAttributes: t }) { const n = e.textContent, s = Se(n); return [ `h${e.attrs.level}`, { ...t, id: s }, 0 ]; } }); } function Ue(e, t) { const n = se(re), s = Oe(); async function r(c) { if (!e.uploadUrl) throw new Error("uploadUrl prop is not set"); const i = new FormData(); i.append("file", c), t("upload-start", c); try { const u = (await _.post(e.uploadUrl, i, { headers: { ...e.headers, "Content-Type": "multipart/form-data" } })).data, a = u.url || u.path || u.data?.url; if (!a) throw new Error("Upload response did not include url"); return t("upload-success", a, c), a; } catch (d) { throw t("upload-error", d, c), d; } } return te({ editorProps: { attributes: { class: "tiptap prose prose-sm sm:prose-base lg:prose-lg xl:prose-xl max-w-none p-4 m-0 focus:outline-none" }, uploadOnInsert: { type: Boolean, default: !1 }, uploadUrl: { type: String, default: null }, deleteUrl: { type: String, default: null }, headers: { type: Object, default: () => ({}) }, extractImages: { type: Function, default: null } }, extensions: [ le.configure({ types: [N.name, A.name] }), N.configure({ types: [A.name] }), ce.configure({ codeBlock: !1, link: !1, heading: !1 }), fe.configure({ HTMLAttributes: { rel: "noopener" } }), he.configure({ types: ["heading", "paragraph"] }), ue.configure({ lowlight: n, enableTabIndentation: !0 }), s, pe, de.configure({ allowedMimeTypes: ["image/png", "image/jpeg", "image/gif", "image/webp"], onDrop: (c, i, d) => { i.forEach((u) => { const a = URL.createObjectURL(u); e.uploadOnInsert && e.uploadUrl ? (c.chain().focus().insertContentAt(d, { type: "image", attrs: { src: a, "data-uploading": "true" } }).run(), r(u).then((l) => { c.chain().focus().updateAttributes("image", { src: l, "data-uploading": null }).run(); }).catch((l) => { console.error("upload failed", l), pendingUploads.value.delete(a); })) : c.chain().focus().insertContentAt(d, { type: "image", attrs: { src: a } }).run(); }); }, onPaste: (c, i) => { i.forEach((d) => { const u = URL.createObjectURL(d); e.uploadOnInsert && e.uploadUrl ? (c.chain().focus().insertContentAt(pos, { type: "image", attrs: { src: u, "data-uploading": "true" } }).run(), r(d).then((a) => { c.chain().focus().updateAttributes("image", { src: a, "data-uploading": null }).run(); }).catch((a) => { console.error("upload failed", a), pendingUploads.value.delete(u); })) : c.chain().focus().insertContentAt(pos, { type: "image", attrs: { src: u } }).run(); }); } }), Ee ], content: e.modelValue, onUpdate: ({ editor: c }) => { c.getHTML() !== e.modelValue && t("update:modelValue", c.getHTML()); } }); } function $e(e) { function t() { const { empty: s } = e.value.state.selection, r = e.value.getAttributes("link").href, o = window.prompt("URL", r); if (s) { e.value.chain().focus().insertContent(o).run(); const c = e.value.state.selection.from - o.length, i = e.value.state.selection.from; e.value.chain().setTextSelection({ from: c, to: i }).setLink({ href: o }).setTextSelection(i).run(); } else { if (o === null) return; if (o === "") { e.value.chain().focus().extendMarkRange("link").unsetLink().run(); return; } e.value.chain().focus().extendMarkRange("link").setLink({ href: o }).run(); return; } } function n() { console.log("test"); } return { setLink: t, test: n }; } const _e = { class: "sticky top-0 overflow-hidden w-full rounded-t-2xl mx-auto z-10 bg-gray-100!" }, Ve = { class: "flex md:flex-wrap flex-nowrap overflow-x-auto gap-2 p-2 scrollbar-hide masked-overflow" }, Pe = ["title", "aria-label", "onClick", "disabled", "innerHTML"], De = { __name: "Editor", props: { modelValue: { type: String, required: !0 }, classes: { type: String, default: "w-full sm:w-11/12 md:w-4/5 lg:w-3/4 xl:w-2/3 mx-auto" }, uploadOnInsert: { type: Boolean, default: !1 }, uploadUrl: { type: String, default: null }, headers: { type: Object, default: () => ({}) }, extractImages: { type: Function, default: null } }, emits: [ "update:modelValue", "upload-start", "upload-success", "upload-error", "image-deleted" ], setup(e, { emit: t }) { const n = e, s = t, r = Ue(n, s), o = () => !!(r && r.value), { addImage: c } = ve(r, n, s), { setLink: i } = $e(r), d = me(r, o, c, i); return J(() => { r.value?.destroy(); }), (u, a) => Q((M(), L("div", { class: B(`bg-white! rounded-2xl mt-4 border border-gray-300 ${n.classes}`) }, [ w("div", _e, [ w("div", Ve, [ (M(!0), L(Z, null, R(x(d), (l) => (M(), L("button", { type: "button", title: l.title, key: l.label, "aria-label": l.label, class: B(["px-1.5 sm:px-3 py-1 text-xs text-gray-900 sm:text-sm border border-gray-800 dark:border-neutral-600 rounded-sm whitespace-nowrap transition delay-100 duration-100 ease-in-out hover:-translate-y-1 hover:scale-102", { "is-active": l.active ? l.active() : !1 }]), onClick: l.action, disabled: l.disabled ? l.disabled() : !1, innerHTML: l.label }, null, 10, Pe))), 128)) ]), a[0] || (a[0] = w("div", { class: "pointer-events-none absolute left-0 top-0 h-full w-8 fade-left" }, null, -1)), a[1] || (a[1] = w("div", { class: "pointer-events-none absolute right-0 top-0 h-full w-8 fade-right" }, null, -1)) ]), Y(x(ne), { class: "w-full", editor: x(r) }, null, 8, ["editor"]) ], 2)), [ [ee, x(r)] ]); } }, tt = { install: (e, t) => { e.component("Editor", De); } }; export { tt as default };