UNPKG

reactjs-tiptap-editor

Version:

A modern WYSIWYG rich text editor based on tiptap and shadcn ui for React

262 lines (261 loc) 8.82 kB
import { N as T, B as y, m as h, j as p, E as M } from "./clsx-DaPvp9ji.js"; import { Node as L } from "@tiptap/pm/model"; import { S } from "./index-Qcl3BG94.js"; import { PluginKey as H } from "@tiptap/pm/state"; import { jsx as g, jsxs as A } from "react/jsx-runtime"; import { forwardRef as N, useRef as C, useState as $, useEffect as f, useImperativeHandle as k } from "react"; import E from "scroll-into-view-if-needed"; import { r as x } from "./renderNodeView-BEkECnnY.js"; function _({ editor: e, overrideSuggestionOptions: t, extensionName: n, char: r = "@" }) { const o = new H(); return { editor: e, char: r, pluginKey: o, command: ({ editor: s, range: a, props: l }) => { var u, i, c; const d = s.view.state.selection.$to.nodeAfter; ((u = d == null ? void 0 : d.text) == null ? void 0 : u.startsWith(" ")) && (a.to += 1), s.chain().focus().insertContentAt(a, [ { type: n, attrs: { ...l, mentionSuggestionChar: r } }, { type: "text", text: " " } ]).run(), (c = (i = s.view.dom.ownerDocument.defaultView) == null ? void 0 : i.getSelection()) == null || c.collapseToEnd(); }, allow: ({ state: s, range: a }) => { const l = s.doc.resolve(a.from), u = s.schema.nodes[n]; return !!l.parent.type.contentMatch.matchType(u); }, ...t }; } function w(e) { return (e.options.suggestions.length ? e.options.suggestions : [e.options.suggestion]).map( (t) => _({ // @ts-ignore `editor` can be `undefined` when converting the document to HTML with the HTML utility editor: e.editor, overrideSuggestionOptions: t, extensionName: e.name, char: t.char }) ); } function b(e, t) { const n = w(e), r = n.find((o) => o.char === t); return r || (n.length ? n[0] : null); } var I = T.create({ name: "mention", priority: 101, addOptions() { return { HTMLAttributes: {}, renderText({ node: e, suggestion: t }) { var n, r; return `${(n = t == null ? void 0 : t.char) != null ? n : "@"}${(r = e.attrs.label) != null ? r : e.attrs.id}`; }, deleteTriggerWithBackspace: !1, renderHTML({ options: e, node: t, suggestion: n }) { var r, o; return [ "span", h(this.HTMLAttributes, e.HTMLAttributes), `${(r = n == null ? void 0 : n.char) != null ? r : "@"}${(o = t.attrs.label) != null ? o : t.attrs.id}` ]; }, suggestions: [], suggestion: {} }; }, group: "inline", inline: !0, selectable: !1, atom: !0, addAttributes() { return { id: { default: null, parseHTML: (e) => e.getAttribute("data-id"), renderHTML: (e) => e.id ? { "data-id": e.id } : {} }, label: { default: null, parseHTML: (e) => e.getAttribute("data-label"), renderHTML: (e) => e.label ? { "data-label": e.label } : {} }, // When there are multiple types of mentions, this attribute helps distinguish them mentionSuggestionChar: { default: "@", parseHTML: (e) => e.getAttribute("data-mention-suggestion-char"), renderHTML: (e) => ({ "data-mention-suggestion-char": e.mentionSuggestionChar }) } }; }, parseHTML() { return [ { tag: `span[data-type="${this.name}"]` } ]; }, renderHTML({ node: e, HTMLAttributes: t }) { const n = b(this, e.attrs.mentionSuggestionChar); if (this.options.renderLabel !== void 0) return console.warn("renderLabel is deprecated use renderText and renderHTML instead"), [ "span", h({ "data-type": this.name }, this.options.HTMLAttributes, t), this.options.renderLabel({ options: this.options, node: e, suggestion: n }) ]; const r = { ...this.options }; r.HTMLAttributes = h( { "data-type": this.name }, this.options.HTMLAttributes, t ); const o = this.options.renderHTML({ options: r, node: e, suggestion: n }); return typeof o == "string" ? ["span", h({ "data-type": this.name }, this.options.HTMLAttributes, t), o] : o; }, ...y({ nodeName: "mention", name: "@", selfClosing: !0, allowedAttributes: ["id", "label", { name: "mentionSuggestionChar", skipIfDefault: "@" }], parseAttributes: (e) => { const t = {}, n = /(\w+)=(?:"([^"]*)"|'([^']*)')/g; let r = n.exec(e); for (; r !== null; ) { const [, o, s, a] = r, l = s ?? a; t[o === "char" ? "mentionSuggestionChar" : o] = l, r = n.exec(e); } return t; }, serializeAttributes: (e) => Object.entries(e).filter(([, t]) => t != null).map(([t, n]) => `${t === "mentionSuggestionChar" ? "char" : t}="${n}"`).join(" ") }), renderText({ node: e }) { const t = { options: this.options, node: e, suggestion: b(this, e.attrs.mentionSuggestionChar) }; return this.options.renderLabel !== void 0 ? (console.warn("renderLabel is deprecated use renderText and renderHTML instead"), this.options.renderLabel(t)) : this.options.renderText(t); }, addKeyboardShortcuts() { return { Backspace: () => this.editor.commands.command(({ tr: e, state: t }) => { let n = !1; const { selection: r } = t, { empty: o, anchor: s } = r; if (!o) return !1; let a = new L(), l = 0; return t.doc.nodesBetween(s - 1, s, (u, i) => { if (u.type.name === this.name) return n = !0, a = u, l = i, !1; }), n && e.insertText( this.options.deleteTriggerWithBackspace ? "" : a.attrs.mentionSuggestionChar, l, l + a.nodeSize ), n; }) }; }, addProseMirrorPlugins() { return w(this).map(S); } }), K = I; const v = N((e, t) => { const n = C(null), [r, o] = $(0), s = (i) => { const c = e.items[i]; c && e.command(c); }, a = () => { o((r + e.items.length - 1) % e.items.length); }, l = () => { o((r + 1) % e.items.length); }, u = () => { s(r); }; return f(() => o(0), [e.items]), f(() => { if (Number.isNaN(r + 1)) return; const i = n.current.querySelector(`span:nth-of-type(${r + 1})`); i && E(i, { behavior: "smooth", scrollMode: "if-needed" }); }, [r]), k(t, () => ({ onKeyDown: ({ event: i }) => i.key === "ArrowUp" ? (a(), !0) : i.key === "ArrowDown" ? (l(), !0) : i.key === "Enter" ? (u(), !0) : !1 })), /* @__PURE__ */ g( "div", { className: " !richtext-max-h-[320px] !richtext-w-[160px] richtext-overflow-y-auto richtext-overflow-x-hidden richtext-rounded-md !richtext-border !richtext-border-solid !richtext-border-border richtext-bg-popover richtext-p-1 richtext-text-popover-foreground richtext-shadow-md richtext-outline-none", "data-richtext-portal": !0, ref: n, children: /* @__PURE__ */ g("div", { children: e.items.length > 0 ? e.items.map((i, c) => { var d; return /* @__PURE__ */ A( "span", { className: p("richtext-flex richtext-w-full richtext-items-center richtext-gap-3 richtext-rounded-sm !richtext-border-none !richtext-bg-transparent richtext-px-2 richtext-py-1.5 richtext-text-left richtext-text-sm richtext-text-foreground !richtext-outline-none richtext-transition-colors hover:!richtext-bg-accent ", { "bg-item-active": c === r }), onClick: (m) => { m.preventDefault(), s(c); }, children: [ ((d = i == null ? void 0 : i.avatar) == null ? void 0 : d.src) && /* @__PURE__ */ g( "img", { alt: i.label, className: "richtext-size-5 richtext-rounded-full", src: i.avatar.src } ), i == null ? void 0 : i.label ] }, `mention-item-${i.id}` ); }) : /* @__PURE__ */ g("div", { className: p("itemUserEmpty, richtext-text-foreground"), children: "Empty" }) }) } ); }), Q = /* @__PURE__ */ M.create({ name: "richTextMentionWrapper", addExtensions() { var t, n, r, o; const e = { ...this.options }; return (t = this.options) != null && t.suggestion && (e.suggestion = { render: x(v), ...this.options.suggestion }), (r = (n = this.options) == null ? void 0 : n.suggestions) != null && r.length && (e.suggestions = (o = this.options.suggestions) == null ? void 0 : o.map((s) => ({ render: x(v), ...s }))), [K.configure({ HTMLAttributes: { class: "mention" }, ...e })]; } }); export { Q as Mention };