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