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
JavaScript
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
};