UNPKG

reactjs-tiptap-editor

Version:

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

371 lines (370 loc) 13.4 kB
import { E as Q } from "./clsx-DaPvp9ji.js"; import { Plugin as X, PluginKey as Y } from "@tiptap/pm/state"; import { DecorationSet as $, Decoration as Z } from "@tiptap/pm/view"; import { jsx as u, Fragment as _, jsxs as T } from "react/jsx-runtime"; import { useState as b, useEffect as k } from "react"; import { h as ee, u as te, e as se, A as re, L as B, j as V, B as N, I as q, C as ne } from "./index-RcSPeQHn.js"; import "./theme.js"; import { u as ce } from "./index-C07N8gA1.js"; import { P as ae, a as le, b as oe } from "./popover-CtinPbiy.js"; function Ce() { const { t } = ce(), e = ee(), s = te(xe.name), { icon: n = void 0, tooltip: i = void 0, shortcutKeys: l = void 0, tooltipOptions: c = {}, action: h = void 0, isActive: m = void 0 } = (s == null ? void 0 : s.componentProps) ?? {}, { disabled: o } = se(m), [f, d] = b(!1), [x, A] = b(""), [S, w] = b(""), [I, M] = b(!1), [W, j] = b(""), y = () => { var r, a, g, p; j(`${((a = (r = e == null ? void 0 : e.storage) == null ? void 0 : r.searchAndReplace) == null ? void 0 : a.resultIndex) + 1}/${(p = (g = e == null ? void 0 : e.storage) == null ? void 0 : g.searchAndReplace) == null ? void 0 : p.results.length}`); }; k(() => { e && y(); }, [e]); const H = () => { o || h && h(); }, L = (r = !1) => { var a, g, p, R, v, F, P, O; e && (r && ((g = (a = e == null ? void 0 : e.commands) == null ? void 0 : a.resetIndex) == null || g.call(a)), (R = (p = e == null ? void 0 : e.commands) == null ? void 0 : p.setSearchTerm) == null || R.call(p, x), (F = (v = e == null ? void 0 : e.commands) == null ? void 0 : v.setReplaceTerm) == null || F.call(v, S), (O = (P = e == null ? void 0 : e.commands) == null ? void 0 : P.setCaseSensitive) == null || O.call(P, I), y()); }, E = () => { var R, v; if (!e) return; const { results: r, resultIndex: a } = e.storage.searchAndReplace, g = r[a]; if (!g) return; (v = (R = e == null ? void 0 : e.commands) == null ? void 0 : R.setTextSelection) == null || v.call(R, g); const { node: p } = e.view.domAtPos( e.state.selection.anchor ); p instanceof HTMLElement && p.scrollIntoView({ behavior: "smooth", block: "center" }), y(); }; k(() => { x.trim() || D(), x.trim() && L(!0); }, [x]), k(() => { S.trim() && L(); }, [S]), k(() => { L(!0); }, [I]); const U = () => { var r, a; (a = (r = e == null ? void 0 : e.commands) == null ? void 0 : r.replace) == null || a.call(r), E(); }, z = () => { var r, a; (a = (r = e == null ? void 0 : e.commands) == null ? void 0 : r.nextSearchResult) == null || a.call(r), E(); }, G = () => { var r, a; (a = (r = e == null ? void 0 : e.commands) == null ? void 0 : r.previousSearchResult) == null || a.call(r), E(); }, D = () => { var r, a; A(""), w(""), (a = (r = e == null ? void 0 : e.commands) == null ? void 0 : r.resetIndex) == null || a.call(r), y(); }, J = () => { var r, a; (a = (r = e == null ? void 0 : e.commands) == null ? void 0 : r.replaceAll) == null || a.call(r), j("0/0"); }; return s ? /* @__PURE__ */ T( ae, { onOpenChange: d, open: f, children: [ /* @__PURE__ */ u( le, { asChild: !0, disabled: o, children: /* @__PURE__ */ u( re, { action: H, disabled: o, icon: n, shortcutKeys: l, tooltip: i, tooltipOptions: c } ) } ), /* @__PURE__ */ T( oe, { align: "start", className: "richtext-w-full", hideWhenDetached: !0, side: "bottom", children: [ /* @__PURE__ */ T("div", { className: "richtext-mb-[6px] richtext-flex richtext-items-center richtext-justify-between", children: [ /* @__PURE__ */ u(B, { children: t("editor.search.dialog.text") }), /* @__PURE__ */ u("span", { className: "richtext-font-semibold", children: W }) ] }), /* @__PURE__ */ T("div", { className: "richtext-mb-[10px] richtext-flex richtext-w-full richtext-max-w-sm richtext-items-center richtext-gap-1.5", children: [ /* @__PURE__ */ u( V, { autoFocus: !0, className: "richtext-w-full", onChange: (r) => A(r.target.value), placeholder: "Text", required: !0, type: "text", value: x } ), /* @__PURE__ */ u( N, { className: "richtext-flex-1", onClick: G, children: /* @__PURE__ */ u(q, { name: "ChevronUp" }) } ), /* @__PURE__ */ u( N, { className: "richtext-flex-1", onClick: z, children: /* @__PURE__ */ u(q, { name: "ChevronDown" }) } ), /* @__PURE__ */ u( N, { className: "richtext-flex-1", onClick: D, children: "Clear" } ) ] }), /* @__PURE__ */ u(B, { className: "richtext-mb-[6px]", children: t("editor.replace.dialog.text") }), /* @__PURE__ */ u("div", { className: "richtext-mb-[5px] richtext-flex richtext-w-full richtext-max-w-sm richtext-items-center richtext-gap-1.5", children: /* @__PURE__ */ u("div", { className: "richtext-relative richtext-w-full richtext-max-w-sm richtext-items-center", children: /* @__PURE__ */ u( V, { className: "richtext-w-80", onChange: (r) => w(r.target.value), placeholder: "Text", required: !0, type: "text", value: S } ) }) }), /* @__PURE__ */ T("div", { className: "richtext-my-[10px] richtext-flex richtext-items-center richtext-gap-1", children: [ /* @__PURE__ */ u( ne, { checked: I, onCheckedChange: (r) => { M(r), e.commands.setCaseSensitive(r); } } ), /* @__PURE__ */ u(B, { children: t("editor.replace.caseSensitive") }) ] }), /* @__PURE__ */ T("div", { className: "richtext-flex richtext-items-center richtext-gap-[10px]", children: [ /* @__PURE__ */ u( N, { className: "richtext-flex-1", onClick: U, children: t("editor.replace.dialog.text") } ), /* @__PURE__ */ u( N, { className: "richtext-flex-1", onClick: J, children: t("editor.replaceAll.dialog.text") } ) ] }) ] } ) ] } ) : /* @__PURE__ */ u(_, {}); } const C = (t, e) => e(t.tr); function ie(t, e, s) { return RegExp( e ? t.replace(/[$()*+.?[\\\]^{|}]/g, String.raw`\$&`) : t, s ? "gu" : "gui" ); } function ue(t, e, s, n) { const i = [], l = []; let c = [], h = 0; if (!e) return { decorationsToReturn: $.empty, results: [] }; t == null || t.descendants((m, o) => { m.isText ? c[h] ? c[h] = { text: c[h].text + m.text, pos: c[h].pos } : c[h] = { text: `${m.text}`, pos: o } : h += 1; }), c = c.filter(Boolean); for (const m of c) { const { text: o, pos: f } = m, d = Array.from(o.matchAll(e)).filter( ([x]) => x.trim() ); for (const x of d) { if (x[0] === "") break; x.index !== void 0 && l.push({ from: f + x.index, to: f + x.index + x[0].length }); } } for (const [m, o] of l.entries()) { const f = m === n ? `${s} ${s}-current` : s, d = Z.inline(o.from, o.to, { class: f }); i.push(d); } return { decorationsToReturn: $.create(t, i), results: l }; } function K(t, e, { state: s, dispatch: n }) { if (!e[0]) return; const { from: l, to: c } = e[0]; n && n(s.tr.insertText(t, l, c)); } function he(t, e, s, n) { const i = e + 1; if (!n[i]) return null; const { from: l, to: c } = n[e], h = c - l - t.length + s, { from: m, to: o } = n[i]; return n[i] = { to: o - h, from: m - h }, [h, n]; } function me(t, e, { tr: s, dispatch: n }) { let i = 0, l = e.slice(); if (l.length !== 0) { for (let c = 0; c < l.length; c += 1) { const { from: h, to: m } = l[c]; s.insertText(t, h, m); const o = he( t, c, i, l ); o && (i = o[0], l = o[1]); } n(s); } } const xe = Q.create({ name: "searchAndReplace", addOptions() { var t; return { ...(t = this.parent) == null ? void 0 : t.call(this), searchTerm: "", replaceTerm: "", results: [], searchResultClass: "search-result", searchResultCurrentClass: "search-result-current", caseSensitive: !1, disableRegex: !0, onChange: () => { }, button: ({ editor: e, t: s }) => ({ // component: RichTextSearchAndReplace, componentProps: { action: () => { }, icon: "SearchAndReplace", tooltip: s("editor.searchAndReplace.tooltip"), isActive: () => !0, editor: e } }) }; }, addStorage() { return { searchTerm: "", replaceTerm: "", results: [], lastSearchTerm: "", caseSensitive: !1, lastCaseSensitive: !1, resultIndex: 0, lastResultIndex: 0 }; }, addCommands() { return { setSearchTerm: (t) => ({ editor: e, state: s, dispatch: n }) => (e.storage.searchAndReplace.searchTerm = t, C(s, n), !1), setReplaceTerm: (t) => ({ editor: e, state: s, dispatch: n }) => (e.storage.searchAndReplace.replaceTerm = t, C(s, n), !1), setCaseSensitive: (t) => ({ editor: e, state: s, dispatch: n }) => (e.storage.searchAndReplace.caseSensitive = t, C(s, n), !1), resetIndex: () => ({ editor: t, state: e, dispatch: s }) => (t.storage.searchAndReplace.resultIndex = 0, C(e, s), !1), nextSearchResult: () => ({ editor: t }) => { const { results: e, resultIndex: s } = t.storage.searchAndReplace, n = s + 1; return e[n] ? t.storage.searchAndReplace.resultIndex = n : t.storage.searchAndReplace.resultIndex = 0, !1; }, previousSearchResult: () => ({ editor: t }) => { const { results: e, resultIndex: s } = t.storage.searchAndReplace, n = s - 1; return e[n] ? t.storage.searchAndReplace.resultIndex = n : t.storage.searchAndReplace.resultIndex = e.length - 1, !1; }, replace: () => ({ editor: t, state: e, dispatch: s }) => { const { replaceTerm: n, results: i, resultIndex: l } = t.storage.searchAndReplace, c = i[l]; return c ? (K(n, [c], { state: e, dispatch: s }), t.storage.searchAndReplace.results.splice(l, 1)) : (K(n, i, { state: e, dispatch: s }), t.storage.searchAndReplace.results.shift()), C(e, s), !1; }, replaceAll: () => ({ editor: t, tr: e, state: s, dispatch: n }) => { const { replaceTerm: i, results: l } = t.storage.searchAndReplace; return me(i, l, { tr: e, dispatch: n }), t.storage.searchAndReplace.resultIndex = 0, t.storage.searchAndReplace.results = [], C(s, n), !1; } }; }, addProseMirrorPlugins() { const t = this.editor, { searchResultClass: e, disableRegex: s } = this.options, n = (c) => t.storage.searchAndReplace.lastSearchTerm = c, i = (c) => t.storage.searchAndReplace.lastCaseSensitive = c, l = (c) => t.storage.searchAndReplace.lastResultIndex = c; return [ new X({ key: new Y(`richtextCustomPlugin${this.name}`), state: { init: () => $.empty, apply({ doc: c, docChanged: h }, m) { const { searchTerm: o, lastSearchTerm: f, caseSensitive: d, lastCaseSensitive: x, resultIndex: A, lastResultIndex: S } = t.storage.searchAndReplace; if (!h && f === o && x === d && S === A) return m; if (n(o), i(d), l(A), !o) return t.storage.searchAndReplace.results = [], $.empty; const { decorationsToReturn: w, results: I } = ue( c, ie(o, s, d), e, A ); return t.storage.searchAndReplace.results = I, w; } }, props: { decorations(c) { return this.getState(c); } } }) ]; } }); export { Ce as RichTextSearchAndReplace, xe as SearchAndReplace };