UNPKG

reactjs-tiptap-editor

Version:

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

591 lines (590 loc) 19.3 kB
import { useRef as O, useState as S, useEffect as B } from "react"; import { a8 as z, a9 as N } from "./RichTextEditor-iSPxjLdO.js"; import { aa as ve } from "./RichTextEditor-iSPxjLdO.js"; import { B as ke } from "./dom-dataset-2RXYq9wp.js"; import { E as f, P as p, a as y, z as L, D as C, b, B as R, F as k, S as P, T as M, g as T, C as w, G as D, H as V, N as x, m as A, I as H } from "./index-CXIIg9Sq.js"; import { ListItem as F } from "./ListItem.js"; import { T as I } from "./index-BkoojfPK.js"; import { Document as K } from "./Document.js"; import { Selection as G } from "./Selection.js"; import { TextBubble as W } from "./TextBubble.js"; import { TrailingNode as J } from "./TrailingNode.js"; import { m } from "./index-D-DR0FPY.js"; function we() { var l; const t = O({ editor: null }), [e, o] = S(!1), [s, r] = S(null); return B(() => { var i; (i = t.current) != null && i.editor && (o(!0), r(t.current.editor)); }, [t, (l = t.current) == null ? void 0 : l.editor]), { isReady: e, editor: s, editorRef: t }; } const X = f.create({ name: "characterCount", addOptions() { return { limit: null, mode: "textSize", textCounter: (t) => t.length, wordCounter: (t) => t.split(" ").filter((e) => e !== "").length }; }, addStorage() { return { characters: () => 0, words: () => 0 }; }, onBeforeCreate() { this.storage.characters = (t) => { const e = (t == null ? void 0 : t.node) || this.editor.state.doc; if (((t == null ? void 0 : t.mode) || this.options.mode) === "textSize") { const s = e.textBetween(0, e.content.size, void 0, " "); return this.options.textCounter(s); } return e.nodeSize; }, this.storage.words = (t) => { const e = (t == null ? void 0 : t.node) || this.editor.state.doc, o = e.textBetween(0, e.content.size, " ", " "); return this.options.wordCounter(o); }; }, addProseMirrorPlugins() { let t = !1; return [ new p({ key: new y("characterCount"), appendTransaction: (e, o, s) => { if (t) return; const r = this.options.limit; if (r == null || r === 0) { t = !0; return; } const l = this.storage.characters({ node: s.doc }); if (l > r) { const i = l - r, n = 0, a = i; console.warn(`[CharacterCount] Initial content exceeded limit of ${r} characters. Content was automatically trimmed.`); const c = s.tr.deleteRange(n, a); return t = !0, c; } t = !0; }, filterTransaction: (e, o) => { const s = this.options.limit; if (!e.docChanged || s === 0 || s === null || s === void 0) return !0; const r = this.storage.characters({ node: o.doc }), l = this.storage.characters({ node: e.doc }); if (l <= s || r > s && l > s && l <= r) return !0; if (r > s && l > s && l > r || !e.getMeta("paste")) return !1; const n = e.selection.$head.pos, a = l - s, c = n - a, u = n; return e.deleteRange(c, u), !(this.storage.characters({ node: e.doc }) > s); } }) ]; } }); function Y(t = {}) { return new p({ view(e) { return new j(e, t); } }); } class j { constructor(e, o) { var s; this.editorView = e, this.cursorPos = null, this.element = null, this.timeout = -1, this.width = (s = o.width) !== null && s !== void 0 ? s : 1, this.color = o.color === !1 ? void 0 : o.color || "black", this.class = o.class, this.handlers = ["dragover", "dragend", "drop", "dragleave"].map((r) => { let l = (i) => { this[r](i); }; return e.dom.addEventListener(r, l), { name: r, handler: l }; }); } destroy() { this.handlers.forEach(({ name: e, handler: o }) => this.editorView.dom.removeEventListener(e, o)); } update(e, o) { this.cursorPos != null && o.doc != e.state.doc && (this.cursorPos > e.state.doc.content.size ? this.setCursor(null) : this.updateOverlay()); } setCursor(e) { e != this.cursorPos && (this.cursorPos = e, e == null ? (this.element.parentNode.removeChild(this.element), this.element = null) : this.updateOverlay()); } updateOverlay() { let e = this.editorView.state.doc.resolve(this.cursorPos), o = !e.parent.inlineContent, s; if (o) { let n = e.nodeBefore, a = e.nodeAfter; if (n || a) { let c = this.editorView.nodeDOM(this.cursorPos - (n ? n.nodeSize : 0)); if (c) { let u = c.getBoundingClientRect(), h = n ? u.bottom : u.top; n && a && (h = (h + this.editorView.nodeDOM(this.cursorPos).getBoundingClientRect().top) / 2), s = { left: u.left, right: u.right, top: h - this.width / 2, bottom: h + this.width / 2 }; } } } if (!s) { let n = this.editorView.coordsAtPos(this.cursorPos); s = { left: n.left - this.width / 2, right: n.left + this.width / 2, top: n.top, bottom: n.bottom }; } let r = this.editorView.dom.offsetParent; this.element || (this.element = r.appendChild(document.createElement("div")), this.class && (this.element.className = this.class), this.element.style.cssText = "position: absolute; z-index: 50; pointer-events: none;", this.color && (this.element.style.backgroundColor = this.color)), this.element.classList.toggle("prosemirror-dropcursor-block", o), this.element.classList.toggle("prosemirror-dropcursor-inline", !o); let l, i; if (!r || r == document.body && getComputedStyle(r).position == "static") l = -pageXOffset, i = -pageYOffset; else { let n = r.getBoundingClientRect(); l = n.left - r.scrollLeft, i = n.top - r.scrollTop; } this.element.style.left = s.left - l + "px", this.element.style.top = s.top - i + "px", this.element.style.width = s.right - s.left + "px", this.element.style.height = s.bottom - s.top + "px"; } scheduleRemoval(e) { clearTimeout(this.timeout), this.timeout = setTimeout(() => this.setCursor(null), e); } dragover(e) { if (!this.editorView.editable) return; let o = this.editorView.posAtCoords({ left: e.clientX, top: e.clientY }), s = o && o.inside >= 0 && this.editorView.state.doc.nodeAt(o.inside), r = s && s.type.spec.disableDropCursor, l = typeof r == "function" ? r(this.editorView, o, e) : r; if (o && !l) { let i = o.pos; if (this.editorView.dragging && this.editorView.dragging.slice) { let n = L(this.editorView.state.doc, i, this.editorView.dragging.slice); n != null && (i = n); } this.setCursor(i), this.scheduleRemoval(5e3); } } dragend() { this.scheduleRemoval(20); } drop() { this.scheduleRemoval(20); } dragleave(e) { (e.target == this.editorView.dom || !this.editorView.dom.contains(e.relatedTarget)) && this.setCursor(null); } } const q = f.create({ name: "dropCursor", addOptions() { return { color: "currentColor", width: 1, class: void 0 }; }, addProseMirrorPlugins() { return [ Y(this.options) ]; } }), U = f.create({ name: "focus", addOptions() { return { className: "has-focus", mode: "all" }; }, addProseMirrorPlugins() { return [ new p({ key: new y("focus"), props: { decorations: ({ doc: t, selection: e }) => { const { isEditable: o, isFocused: s } = this.editor, { anchor: r } = e, l = []; if (!o || !s) return C.create(t, []); let i = 0; this.options.mode === "deepest" && t.descendants((a, c) => { if (a.isText) return; if (!(r >= c && r <= c + a.nodeSize - 1)) return !1; i += 1; }); let n = 0; return t.descendants((a, c) => { if (a.isText || !(r >= c && r <= c + a.nodeSize - 1)) return !1; if (n += 1, this.options.mode === "deepest" && i - n > 0 || this.options.mode === "shallowest" && n > 1) return this.options.mode === "deepest"; l.push(b.node(c, c + a.nodeSize, { class: this.options.className })); }), C.create(t, l); } } }) ]; } }); class d extends w { /** Create a gap cursor. */ constructor(e) { super(e, e); } map(e, o) { let s = e.resolve(o.map(this.head)); return d.valid(s) ? new d(s) : w.near(s); } content() { return P.empty; } eq(e) { return e instanceof d && e.head == this.head; } toJSON() { return { type: "gapcursor", pos: this.head }; } /** @internal */ static fromJSON(e, o) { if (typeof o.pos != "number") throw new RangeError("Invalid input for GapCursor.fromJSON"); return new d(e.resolve(o.pos)); } /** @internal */ getBookmark() { return new v(this.anchor); } /** @internal */ static valid(e) { let o = e.parent; if (o.isTextblock || !Q(e) || !Z(e)) return !1; let s = o.type.spec.allowGapCursor; if (s != null) return s; let r = o.contentMatchAt(e.index()).defaultType; return r && r.isTextblock; } /** @internal */ static findGapCursorFrom(e, o, s = !1) { e: for (; ; ) { if (!s && d.valid(e)) return e; let r = e.pos, l = null; for (let i = e.depth; ; i--) { let n = e.node(i); if (o > 0 ? e.indexAfter(i) < n.childCount : e.index(i) > 0) { l = n.child(o > 0 ? e.indexAfter(i) : e.index(i) - 1); break; } else if (i == 0) return null; r += o; let a = e.doc.resolve(r); if (d.valid(a)) return a; } for (; ; ) { let i = o > 0 ? l.firstChild : l.lastChild; if (!i) { if (l.isAtom && !l.isText && !T.isSelectable(l)) { e = e.doc.resolve(r + l.nodeSize * o), s = !1; continue e; } break; } l = i, r += o; let n = e.doc.resolve(r); if (d.valid(n)) return n; } return null; } } } d.prototype.visible = !1; d.findFrom = d.findGapCursorFrom; w.jsonID("gapcursor", d); class v { constructor(e) { this.pos = e; } map(e) { return new v(e.map(this.pos)); } resolve(e) { let o = e.resolve(this.pos); return d.valid(o) ? new d(o) : w.near(o); } } function Q(t) { for (let e = t.depth; e >= 0; e--) { let o = t.index(e), s = t.node(e); if (o == 0) { if (s.type.spec.isolating) return !0; continue; } for (let r = s.child(o - 1); ; r = r.lastChild) { if (r.childCount == 0 && !r.inlineContent || r.isAtom || r.type.spec.isolating) return !0; if (r.inlineContent) return !1; } } return !0; } function Z(t) { for (let e = t.depth; e >= 0; e--) { let o = t.indexAfter(e), s = t.node(e); if (o == s.childCount) { if (s.type.spec.isolating) return !0; continue; } for (let r = s.child(o); ; r = r.firstChild) { if (r.childCount == 0 && !r.inlineContent || r.isAtom || r.type.spec.isolating) return !0; if (r.inlineContent) return !1; } } return !0; } function $() { return new p({ props: { decorations: re, createSelectionBetween(t, e, o) { return e.pos == o.pos && d.valid(o) ? new d(o) : null; }, handleClick: ee, handleKeyDown: _, handleDOMEvents: { beforeinput: te } } }); } const _ = R({ ArrowLeft: g("horiz", -1), ArrowRight: g("horiz", 1), ArrowUp: g("vert", -1), ArrowDown: g("vert", 1) }); function g(t, e) { const o = t == "vert" ? e > 0 ? "down" : "up" : e > 0 ? "right" : "left"; return function(s, r, l) { let i = s.selection, n = e > 0 ? i.$to : i.$from, a = i.empty; if (i instanceof M) { if (!l.endOfTextblock(o) || n.depth == 0) return !1; a = !1, n = s.doc.resolve(e > 0 ? n.after() : n.before()); } let c = d.findGapCursorFrom(n, e, a); return c ? (r && r(s.tr.setSelection(new d(c))), !0) : !1; }; } function ee(t, e, o) { if (!t || !t.editable) return !1; let s = t.state.doc.resolve(e); if (!d.valid(s)) return !1; let r = t.posAtCoords({ left: o.clientX, top: o.clientY }); return r && r.inside > -1 && T.isSelectable(t.state.doc.nodeAt(r.inside)) ? !1 : (t.dispatch(t.state.tr.setSelection(new d(s))), !0); } function te(t, e) { if (e.inputType != "insertCompositionText" || !(t.state.selection instanceof d)) return !1; let { $from: o } = t.state.selection, s = o.parent.contentMatchAt(o.index()).findWrapping(t.state.schema.nodes.text); if (!s) return !1; let r = k.empty; for (let i = s.length - 1; i >= 0; i--) r = k.from(s[i].createAndFill(null, r)); let l = t.state.tr.replace(o.pos, o.pos, new P(r, 0, 0)); return l.setSelection(M.near(l.doc.resolve(o.pos + 1))), t.dispatch(l), !1; } function re(t) { if (!(t.selection instanceof d)) return null; let e = document.createElement("div"); return e.className = "ProseMirror-gapcursor", C.create(t.doc, [b.widget(t.selection.head, e, { key: "gapcursor" })]); } const oe = f.create({ name: "gapCursor", addProseMirrorPlugins() { return [ $() ]; }, extendNodeSchema(t) { var e; const o = { name: t.name, options: t.options, storage: t.storage }; return { allowGapCursor: (e = D(V(t, "allowGapCursor", o))) !== null && e !== void 0 ? e : null }; } }), se = x.create({ name: "hardBreak", addOptions() { return { keepMarks: !0, HTMLAttributes: {} }; }, inline: !0, group: "inline", selectable: !1, linebreakReplacement: !0, parseHTML() { return [ { tag: "br" } ]; }, renderHTML({ HTMLAttributes: t }) { return ["br", A(this.options.HTMLAttributes, t)]; }, renderText() { return ` `; }, addCommands() { return { setHardBreak: () => ({ commands: t, chain: e, state: o, editor: s }) => t.first([ () => t.exitCode(), () => t.command(() => { const { selection: r, storedMarks: l } = o; if (r.$from.parent.type.spec.isolating) return !1; const { keepMarks: i } = this.options, { splittableMarks: n } = s.extensionManager, a = l || r.$to.parentOffset && r.$from.marks(); return e().insertContent({ type: this.name }).command(({ tr: c, dispatch: u }) => { if (u && a && i) { const h = a.filter((E) => n.includes(E.type.name)); c.ensureMarks(h); } return !0; }).run(); }) ]) }; }, addKeyboardShortcuts() { return { "Mod-Enter": () => this.editor.commands.setHardBreak(), "Shift-Enter": () => this.editor.commands.setHardBreak() }; } }), ie = x.create({ name: "paragraph", priority: 1e3, addOptions() { return { HTMLAttributes: {} }; }, group: "block", content: "inline*", parseHTML() { return [ { tag: "p" } ]; }, renderHTML({ HTMLAttributes: t }) { return ["p", A(this.options.HTMLAttributes, t), 0]; }, addCommands() { return { setParagraph: () => ({ commands: t }) => t.setNode(this.name) }; }, addKeyboardShortcuts() { return { "Mod-Alt-0": () => this.editor.commands.setParagraph() }; } }), ne = f.create({ name: "placeholder", addOptions() { return { emptyEditorClass: "is-editor-empty", emptyNodeClass: "is-empty", placeholder: "Write something …", showOnlyWhenEditable: !0, showOnlyCurrent: !0, includeChildren: !1 }; }, addProseMirrorPlugins() { return [ new p({ key: new y("placeholder"), props: { decorations: ({ doc: t, selection: e }) => { const o = this.editor.isEditable || !this.options.showOnlyWhenEditable, { anchor: s } = e, r = []; if (!o) return null; const l = this.editor.isEmpty; return t.descendants((i, n) => { const a = s >= n && s <= n + i.nodeSize, c = !i.isLeaf && H(i); if ((a || !this.options.showOnlyCurrent) && c) { const u = [this.options.emptyNodeClass]; l && u.push(this.options.emptyEditorClass); const h = b.node(n, n + i.nodeSize, { class: u.join(" "), "data-placeholder": typeof this.options.placeholder == "function" ? this.options.placeholder({ editor: this.editor, node: i, pos: n, hasAnchor: a }) : this.options.placeholder }); r.push(h); } return this.options.includeChildren; }), C.create(t, r); } } }) ]; } }), le = x.create({ name: "text", group: "inline" }), ye = /* @__PURE__ */ f.create({ name: "base-kit", addExtensions() { const t = []; return this.options.document !== !1 && t.push(K.configure()), this.options.placeholder !== !1 && t.push( ne.configure({ placeholder: ({ node: e, pos: o, editor: s }) => { var r, l, i, n, a; return ((r = e == null ? void 0 : e.type) == null ? void 0 : r.name) === "columns" || ((l = e == null ? void 0 : e.content) == null ? void 0 : l.size) !== 0 ? "" : ((i = e == null ? void 0 : e.type) == null ? void 0 : i.name) === "heading" ? `${m.t(`editor.heading.h${e.attrs.level}.tooltip`)}` : ((n = e == null ? void 0 : e.type) == null ? void 0 : n.name) === "codeBlock" || ((a = e == null ? void 0 : e.type) == null ? void 0 : a.name) === "table" ? "" : s.extensionManager.extensions.some((c) => c.name === "slashCommand") ? m.t("editor.slash") : o === 0 ? m.t("editor.content") : m.t("editor.content"); }, ...this.options.placeholder }) ), this.options.focus !== !1 && t.push( U.configure({ className: "focus", ...this.options.focus }) ), this.options.text !== !1 && t.push(le.configure()), this.options.textBubble !== !1 && t.push(W.configure()), this.options.gapcursor !== !1 && t.push(oe.configure()), this.options.dropcursor !== !1 && t.push( q.configure({ ...this.options.dropcursor, width: 2, class: "ProseMirror-dropcursor border-black" }) ), this.options.characterCount !== !1 && t.push(X.configure(this.options.characterCount)), this.options.paragraph !== !1 && t.push(ie.configure(this.options.paragraph)), this.options.hardBreak !== !1 && t.push(se.configure(this.options.hardBreak)), this.options.listItem !== !1 && t.push(F.configure(this.options.listItem)), this.options.textStyle !== !1 && t.push(I.configure(this.options.textStyle)), this.options.trailingNode !== !1 && t.push(J.configure(this.options.trailingNode)), this.options.selection !== !1 && t.push(G), this.options.multiColumn !== !1 && t.push(z, N), t; } }); export { ye as BaseKit, ke as BubbleMenu, ve as default, we as useEditorState };