UNPKG

velaris-richtext-editor

Version:

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

340 lines (339 loc) 12.1 kB
import { E as _, P as M, a as $, b as N, D as P } from "./index-Drmyqk9P.js"; import q from "scroll-into-view-if-needed"; import { jsxs as f, jsx as o } from "react/jsx-runtime"; import { useState as g, useEffect as E } from "react"; import { l as k, E as d, P as B, m as j, A as F, k as A, n as O, L as I, o as y, B as T, S as W, p as K, q as R } from "./RichTextEditor-BbtoCmQU.js"; import { u as U } from "./index-D9llIWB5.js"; function z({ editor: e, ...t }) { const { t: s } = U(), [c, r] = g(-1), [n, i] = g([]), [a, u] = g(""), [l, h] = g(""), [x, v] = g(!1), [b, V] = g(!1); return E(() => { x || (u(""), h(""), r(-1), i([]), e.commands.setSearchTerm(""), e.commands.setReplaceTerm("")); }, [e, x]), E(() => { x && e && e.commands && e.commands.setSearchTerm && e.commands.setSearchTerm(a); }, [x, a, e]), E(() => { x && e && e.commands && e.commands.setReplaceTerm && e.commands.setReplaceTerm(l); }, [x, l, e]), E(() => { if (!e) return; const m = e.extensionManager.extensions.find((S) => S.name === Y.name); if (!m) return; const w = () => { if (!x) return; const S = m ? m.storage.currentIndex : -1, L = m ? m.storage.results : []; r((C) => C !== S ? S : C), i((C) => K(C, L) ? C : L); }; return k(d.SEARCH_REPLCE, w), () => { m && k(d.SEARCH_REPLCE, w); }; }, [x, e]), /* @__PURE__ */ f( B, { onOpenChange: v, open: x, children: [ /* @__PURE__ */ o( j, { asChild: !0, disabled: t == null ? void 0 : t.disabled, children: /* @__PURE__ */ o( F, { disabled: t == null ? void 0 : t.disabled, isActive: t == null ? void 0 : t.isActive, tooltip: t == null ? void 0 : t.tooltip, children: /* @__PURE__ */ o(A, { name: t == null ? void 0 : t.icon }) } ) } ), /* @__PURE__ */ f( O, { align: "start", className: "richtext-w-full", hideWhenDetached: !0, side: "bottom", children: [ /* @__PURE__ */ f("div", { className: "richtext-mb-[6px] richtext-flex richtext-items-center richtext-justify-between", children: [ /* @__PURE__ */ o(I, { children: s("editor.search.dialog.text") }), /* @__PURE__ */ o("span", { className: "richtext-font-semibold", children: n.length > 0 ? `${c + 1}/${n.length}` : "0/0" }) ] }), /* @__PURE__ */ f("div", { className: "richtext-mb-[10px] richtext-flex richtext-w-full richtext-max-w-sm richtext-items-center richtext-gap-1.5", children: [ /* @__PURE__ */ o( y, { autoFocus: !0, className: "richtext-w-full", onChange: (m) => u(m.target.value), placeholder: "Text", required: !0, type: "text", value: a } ), /* @__PURE__ */ o( T, { className: "richtext-flex-1", disabled: n.length === 0, onClick: e.commands.goToPrevSearchResult, children: /* @__PURE__ */ o(A, { name: "ChevronUp" }) } ), /* @__PURE__ */ o( T, { className: "richtext-flex-1", disabled: n.length === 0, onClick: e.commands.goToNextSearchResult, children: /* @__PURE__ */ o(A, { name: "ChevronDown" }) } ) ] }), /* @__PURE__ */ o(I, { className: "richtext-mb-[6px]", children: s("editor.replace.dialog.text") }), /* @__PURE__ */ o("div", { className: "richtext-mb-[5px] richtext-flex richtext-w-full richtext-max-w-sm richtext-items-center richtext-gap-1.5", children: /* @__PURE__ */ o("div", { className: "richtext-relative richtext-w-full richtext-max-w-sm richtext-items-center", children: /* @__PURE__ */ o( y, { className: "richtext-w-80", onChange: (m) => h(m.target.value), placeholder: "Text", required: !0, type: "text", value: l } ) }) }), /* @__PURE__ */ f("div", { className: "richtext-mb-[10px] richtext-flex richtext-items-center richtext-space-x-2", children: [ /* @__PURE__ */ o( W, { checked: b, onCheckedChange: (m) => { V(m), e.commands.setCaseSensitive(m); } } ), /* @__PURE__ */ o(I, { children: s("editor.replace.caseSensitive") }) ] }), /* @__PURE__ */ f("div", { className: "richtext-flex richtext-items-center richtext-gap-[10px]", children: [ /* @__PURE__ */ o( T, { className: "richtext-flex-1", disabled: n.length === 0, onClick: e.commands.replace, children: s("editor.replace.dialog.text") } ), /* @__PURE__ */ o( T, { className: "richtext-flex-1", disabled: n.length === 0, onClick: e.commands.replaceAll, children: s("editor.replaceAll.dialog.text") } ) ] }) ] } ) ] } ); } const p = (e, t) => t(e.tr); function G(e, t, s) { return RegExp(t ? e.replace(/[$()*+./?[\\\]^{|}-]/g, String.raw`\$&`) : e, s ? "gu" : "gui"); } function J(e, t, s) { const c = []; let r = []; const n = []; let i = 0; if (!t) return { decorationsToReturn: [], results: [] }; e == null || e.descendants((a, u) => { a.isText ? r[i] ? r[i] = { text: r[i].text + a.text, pos: r[i].pos } : r[i] = { text: `${a.text}`, pos: u } : i += 1; }), r = r.filter(Boolean); for (const { text: a, pos: u } of r) { const l = [...a.matchAll(t)]; for (const h of l) { if (h[0] === "") break; h.index !== void 0 && n.push({ from: u + h.index, to: u + h.index + h[0].length }); } } for (const a of n) c.push(N.inline(a.from, a.to, { class: s })); return { decorationsToReturn: c, results: n }; } function D(e, t, { state: s, dispatch: c }) { if (!t[0]) return; const { from: n, to: i } = t[0]; c && c(s.tr.insertText(e, n, i)); } function Q(e, t, s, c) { const r = t + 1; if (!c[r]) return null; const { from: n, to: i } = c[t], a = i - n - e.length + s, { from: u, to: l } = c[r]; return c[r] = { to: l - a, from: u - a }, [a, c]; } function X(e, t, { tr: s, dispatch: c }) { let r = 0, n = t.slice(); if (n.length === 0) return !1; for (let i = 0; i < n.length; i += 1) { const { from: a, to: u } = n[i]; s.insertText(e, a, u); const l = Q(e, i, r, n); l && (r = l[0], n = l[1]); } return c(s), !0; } function H({ view: e, tr: t, searchResults: s, searchResultCurrentClass: c, gotoIndex: r }) { const n = s[r]; if (n) { const i = t.setMeta("directDecoration", { fromPos: n.from, toPos: n.to, attrs: { class: c } }); return e == null || e.dispatch(i), setTimeout(() => { const a = window.document.querySelector(`.${c}`); a && q(a, { behavior: "smooth", scrollMode: "if-needed" }); }, 0), !0; } return !1; } const Y = /* @__PURE__ */ _.create({ name: "search", addOptions() { var e; return { ...(e = this.parent) == null ? void 0 : e.call(this), searchTerm: "", replaceTerm: "", results: [], currentIndex: 0, searchResultClass: "search-result", searchResultCurrentClass: "search-result-current", caseSensitive: !1, disableRegex: !1, onChange: () => { }, button: ({ editor: t, t: s }) => ({ component: z, componentProps: { action: () => { }, icon: "SearchAndReplace", tooltip: s("editor.searchAndReplace.tooltip"), isActive: () => !1, disabled: !1, editor: t } }) }; }, addStorage() { return { results: [], currentIndex: -1 }; }, addCommands() { return { setSearchTerm: (e) => ({ state: t, dispatch: s }) => (this.options.searchTerm = e, this.storage.results = [], this.storage.currentIndex = 0, R(d.SEARCH_REPLCE), p(t, s), !1), setReplaceTerm: (e) => ({ state: t, dispatch: s }) => (this.options.replaceTerm = e, p(t, s), !1), setCaseSensitive: (e) => ({ state: t, dispatch: s }) => (this.options.caseSensitive = e, p(t, s), !1), replace: () => ({ state: e, dispatch: t }) => { const { replaceTerm: s } = this.options, { currentIndex: c, results: r } = this.storage, n = r[c]; return n ? (D(s, [n], { state: e, dispatch: t }), this.storage.results.splice(c, 1)) : (D(s, r, { state: e, dispatch: t }), this.storage.results.shift()), R(d.SEARCH_REPLCE), p(e, t), !1; }, replaceAll: () => ({ state: e, tr: t, dispatch: s }) => { const { replaceTerm: c } = this.options, { results: r } = this.storage; return X(c, r, { tr: t, dispatch: s }), this.storage.currentIndex = -1, this.storage.results = [], R(d.SEARCH_REPLCE), p(e, s), !1; }, goToPrevSearchResult: () => ({ view: e, tr: t }) => { const { searchResultCurrentClass: s } = this.options, { currentIndex: c, results: r } = this.storage, n = (c + r.length - 1) % r.length; return this.storage.currentIndex = n, R(d.SEARCH_REPLCE), H({ view: e, tr: t, searchResults: r, searchResultCurrentClass: s, gotoIndex: n }); }, goToNextSearchResult: () => ({ view: e, tr: t }) => { const { searchResultCurrentClass: s } = this.options, { currentIndex: c, results: r } = this.storage, n = (c + 1) % r.length; return this.storage.currentIndex = n, this.options.onChange && this.options.onChange(), R(d.SEARCH_REPLCE), H({ view: e, tr: t, searchResults: r, searchResultCurrentClass: s, gotoIndex: n }); } }; }, addProseMirrorPlugins() { const e = this; return [ new M({ key: new $("search"), state: { init() { return P.empty; }, apply(t) { const { doc: s, docChanged: c } = t, { searchTerm: r, searchResultClass: n, searchResultCurrentClass: i, disableRegex: a, caseSensitive: u } = e.options; if (c || r) { const { decorationsToReturn: l, results: h } = J( s, G(r, a, u), n ); if (e.storage.results = h, e.storage.currentIndex > h.length - 1 && (e.storage.currentIndex = 0), R(d.SEARCH_REPLCE), t.getMeta("directDecoration")) { const { fromPos: x, toPos: v, attrs: b } = t.getMeta("directDecoration"); l.push(N.inline(x, v, b)); } else h.length > 0 && (l[0] = N.inline(h[0].from, h[0].to, { class: i })); return P.create(s, l); } return P.empty; } }, props: { decorations(t) { return this.getState(t); } } }) ]; } }); export { Y as SearchAndReplace };