reactjs-tiptap-editor
Version:
A modern WYSIWYG rich text editor based on tiptap and shadcn ui for React
430 lines (429 loc) • 13.7 kB
JavaScript
import { h as B, m as q, k as V } from "./clsx-DaPvp9ji.js";
import { H as k, w as C, J as X, u as Y, d as D, A as Z, K as J, j as U, y as Q } from "./index-RcSPeQHn.js";
import { jsx as u, jsxs as z, Fragment as K } from "react/jsx-runtime";
import { useState as G, useMemo as P, useCallback as M, useEffect as $, useRef as ee } from "react";
import { I as j, i as W, j as te } from "./index-C07N8gA1.js";
import "./theme.js";
import { P as ne, a as re, b as ie } from "./popover-CtinPbiy.js";
const O = {
TOP_LEFT: "tl",
TOP_RIGHT: "tr",
BOTTOM_LEFT: "bl",
BOTTOM_RIGHT: "br"
};
function ae(e) {
var p, H;
const [t, r] = G({
width: j,
height: j
}), [c, i] = G({
width: 0,
height: 0
}), [n] = G([
O.TOP_LEFT,
O.TOP_RIGHT,
O.BOTTOM_LEFT,
O.BOTTOM_RIGHT
]), [a, s] = G(!1), [l, v] = G({
x: 0,
y: 0,
w: 0,
h: 0,
dir: ""
}), { align: b } = (p = e == null ? void 0 : e.node) == null ? void 0 : p.attrs, g = P(() => {
var w;
const { src: o, alt: m, width: f, height: x } = (w = e == null ? void 0 : e.node) == null ? void 0 : w.attrs, A = k(f) ? `${f}px` : f, d = k(x) ? `${x}px` : x;
return {
src: o || void 0,
alt: m || void 0,
style: {
width: A || void 0,
height: d || void 0
}
};
}, [(H = e == null ? void 0 : e.node) == null ? void 0 : H.attrs]), y = P(() => {
const {
style: { width: o }
} = g;
return { width: o === "100%" ? o : void 0 };
}, [g]);
function h(o) {
i({
width: o.target.width,
height: o.target.height
});
}
function _() {
const { editor: o, getPos: m } = e;
o.commands.setNodeSelection(m());
}
const I = M(
C(() => {
const { editor: o } = e, { width: m } = getComputedStyle(o.view.dom);
r((f) => ({
...f,
width: Number.parseInt(m, 10)
}));
}, W),
[e == null ? void 0 : e.editor]
);
function F(o, m) {
o.preventDefault(), o.stopPropagation();
const f = c.width, x = c.height, A = f / x;
let d = Number(e.node.attrs.width), w = Number(e.node.attrs.height);
const T = t.width;
d && !w ? (d = d > T ? T : d, w = Math.round(d / A)) : w && !d ? (d = Math.round(w * A), d = d > T ? T : d) : !d && !w ? (d = f > T ? T : f, w = Math.round(d / A)) : d = d > T ? T : d, s(!0), v({
x: o.clientX,
y: o.clientY,
w: d,
h: w,
dir: m
});
}
const N = M(
C((o) => {
if (o.preventDefault(), o.stopPropagation(), !a)
return;
const { x: m, w: f, dir: x } = l, A = (o.clientX - m) * (/l/.test(x) ? -1 : 1), d = X(f + A, te, t.width);
e.updateAttributes({
width: d,
height: null
});
}, W),
[a, l, t, e.updateAttributes]
), E = M(
(o) => {
o.preventDefault(), o.stopPropagation(), a && (v({
x: 0,
y: 0,
w: 0,
h: 0,
dir: ""
}), s(!1), _());
},
[a, _]
), R = M(() => {
document == null || document.addEventListener("mousemove", N, !0), document == null || document.addEventListener("mouseup", E, !0);
}, [N, E]), S = M(() => {
document == null || document.removeEventListener("mousemove", N, !0), document == null || document.removeEventListener("mouseup", E, !0);
}, [N, E]);
$(() => (a ? R() : S(), () => {
S();
}), [a, R, S]);
const L = P(() => new ResizeObserver(() => I()), [I]);
return $(() => (L.observe(e.editor.view.dom), () => {
L.disconnect();
}), [e.editor.view.dom, L]), /* @__PURE__ */ u(
B,
{
className: "image-view",
style: { ...y, width: "100%", textAlign: b },
children: /* @__PURE__ */ z(
"div",
{
"data-drag-handle": !0,
draggable: "true",
style: y,
className: `image-view__body ${e != null && e.selected ? "image-view__body--focused" : ""} ${a ? "image-view__body--resizing" : ""}`,
children: [
/* @__PURE__ */ u(
"img",
{
alt: g.alt,
className: "image-view__body__image block",
height: "auto",
onClick: _,
onLoad: h,
src: g.src,
style: g.style
}
),
(e == null ? void 0 : e.editor.view.editable) && ((e == null ? void 0 : e.selected) || a) && /* @__PURE__ */ u("div", { className: "image-resizer", children: n == null ? void 0 : n.map((o) => /* @__PURE__ */ u(
"span",
{
className: `image-resizer__handler image-resizer__handler--${o}`,
onMouseDown: (m) => F(m, o)
},
`image-dir-${o}`
)) })
]
}
)
}
);
}
async function se(e) {
var c;
const r = await (await fetch(`https://api.giphy.com/v1/gifs/trending?q=&limit=15&api_key=${e}`)).json();
return (c = r == null ? void 0 : r.data) == null ? void 0 : c.map((i) => {
var n, a, s;
return {
id: i == null ? void 0 : i.id,
src: (n = i == null ? void 0 : i.images.original) == null ? void 0 : n.url,
width: +((a = i == null ? void 0 : i.images.original) == null ? void 0 : a.width),
height: +((s = i == null ? void 0 : i.images.original) == null ? void 0 : s.width)
};
});
}
async function oe(e, t) {
var i;
const c = await (await fetch(`https://api.giphy.com/v1/gifs/search?q=${e}&limit=15&api_key=${t}`)).json();
return (i = c == null ? void 0 : c.data) == null ? void 0 : i.map((n) => {
var a, s, l;
return {
id: n == null ? void 0 : n.id,
src: (a = n == null ? void 0 : n.images.original) == null ? void 0 : a.url,
width: +((s = n == null ? void 0 : n.images.original) == null ? void 0 : s.width),
height: +((l = n == null ? void 0 : n.images.original) == null ? void 0 : l.width)
};
});
}
async function ce(e) {
var n, a;
const t = await fetch(`https://tenor.googleapis.com/v2/trending_terms?key=${e}&limit=10`), r = await (t == null ? void 0 : t.json()), i = await (await fetch(`https://tenor.googleapis.com/v2/search?key=${e}&q=${(n = r == null ? void 0 : r.results) == null ? void 0 : n[0]}&limit=15`)).json();
return (a = i == null ? void 0 : i.results) == null ? void 0 : a.map((s) => {
var l, v, b, g, y, h, _, I;
return {
id: s == null ? void 0 : s.id,
src: (v = (l = s == null ? void 0 : s.media_formats) == null ? void 0 : l.gif) == null ? void 0 : v.url,
width: (y = (g = (b = s == null ? void 0 : s.media_formats) == null ? void 0 : b.gif) == null ? void 0 : g.dims) == null ? void 0 : y[0],
height: (I = (_ = (h = s == null ? void 0 : s.media_formats) == null ? void 0 : h.gif) == null ? void 0 : _.dims) == null ? void 0 : I[1]
};
});
}
async function de(e, t) {
var i;
const c = await (await fetch(`https://tenor.googleapis.com/v2/search?key=${t}&q=${e}&limit=15`)).json();
return (i = c == null ? void 0 : c.results) == null ? void 0 : i.map((n) => {
var a, s, l, v, b, g, y, h;
return {
id: n == null ? void 0 : n.id,
src: (s = (a = n == null ? void 0 : n.media_formats) == null ? void 0 : a.gif) == null ? void 0 : s.url,
width: (b = (v = (l = n == null ? void 0 : n.media_formats) == null ? void 0 : l.gif) == null ? void 0 : v.dims) == null ? void 0 : b[0],
height: (h = (y = (g = n == null ? void 0 : n.media_formats) == null ? void 0 : g.gif) == null ? void 0 : y.dims) == null ? void 0 : h[1]
};
});
}
function le(e, t) {
return {
searchTrending: async () => t ? e === "giphy" ? se(t) : e === "tenor" ? ce(t) : [] : [],
searchWord: async (i) => t ? e === "giphy" ? oe(i, t) : e === "tenor" ? de(i, t) : [] : []
};
}
function he({ selectImage: e, apiKey: t, provider: r, children: c }) {
const [i, n] = G(!1), [a, s] = G([]), { editorDisabled: l } = D(), v = ee(null), { searchTrending: b, searchWord: g } = le(r, t);
$(() => {
(async () => {
const h = await b();
s(h);
})();
}, []);
const y = M(
J(async (h) => {
if (!h.target.value) {
const I = await b();
s(I);
return;
}
const _ = await g(h.target.value);
s(_);
}, 350),
// Adjust the debounce delay as needed
[]
);
return /* @__PURE__ */ z(
ne,
{
modal: !0,
onOpenChange: n,
open: i,
children: [
/* @__PURE__ */ u(
re,
{
asChild: !0,
disabled: l,
children: c
}
),
/* @__PURE__ */ u(
ie,
{
align: "start",
className: "richtext-size-full richtext-p-2",
hideWhenDetached: !0,
side: "bottom",
children: t ? /* @__PURE__ */ z(K, { children: [
/* @__PURE__ */ u("div", { className: "richtext-mb-[10px] richtext-w-full", children: /* @__PURE__ */ u(
U,
{
onChange: y,
placeholder: "Search GIF",
ref: v,
type: "text"
}
) }),
/* @__PURE__ */ u("div", { className: "richtext-max-h-[280px] !richtext-max-w-[400px] richtext-overflow-y-auto", children: /* @__PURE__ */ u("div", { className: "richtext-grid richtext-grid-cols-2 richtext-gap-1 ", children: a != null && a.length ? a == null ? void 0 : a.map((h) => /* @__PURE__ */ u(
"img",
{
alt: "",
className: "richtext-cursor-pointer richtext-object-contain richtext-text-center",
src: h.src,
onClick: () => {
e(h.src), n(!1);
}
},
h.id
)) : /* @__PURE__ */ u("p", { children: "No GIFs found" }) }) })
] }) : /* @__PURE__ */ u("div", { children: /* @__PURE__ */ u("p", { children: "Missing Giphy API Key" }) })
}
)
]
}
);
}
function _e() {
const e = Y(ue.name), {
action: t,
icon: r,
tooltip: c,
apiKey: i,
provider: n
} = (e == null ? void 0 : e.componentProps) ?? {}, { editorDisabled: a } = D();
return /* @__PURE__ */ u(
he,
{
apiKey: i,
provider: n,
selectImage: (l) => {
a || t && t(l);
},
children: /* @__PURE__ */ u(
Z,
{
disabled: a,
icon: r,
tooltip: c
}
)
}
);
}
const ue = /* @__PURE__ */ Q.extend({
name: "imageGif",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-expect-error
addOptions() {
var e;
return {
...(e = this.parent) == null ? void 0 : e.call(this),
inline: !1,
content: "",
marks: "",
group: "block",
API_KEY: "",
provider: "giphy",
draggable: !1,
selectable: !0,
atom: !0,
button: ({ editor: t, extension: r, t: c }) => {
var a, s;
const i = ((a = r == null ? void 0 : r.options) == null ? void 0 : a.provider) || "", n = ((s = r == null ? void 0 : r.options) == null ? void 0 : s.API_KEY) || "";
return {
componentProps: {
action: (l) => {
t.chain().focus().setImageGif({ src: l }).run();
},
isActive: () => !1,
disabled: !1,
icon: "GifIcon",
tooltip: c("editor.imageGif.tooltip"),
apiKey: n,
provider: i
}
};
}
};
},
addAttributes() {
var e;
return {
...(e = this.parent) == null ? void 0 : e.call(this),
width: {
default: null,
parseHTML: (t) => {
const r = t.style.width || t.getAttribute("width") || "10";
return r === void 0 ? null : Number.parseInt(`${r}`, 10);
},
renderHTML: (t) => ({
width: t.width
})
},
align: {
default: "center",
parseHTML: (t) => t.getAttribute("align"),
renderHTML: (t) => ({
align: t.align
})
}
};
},
addNodeView() {
return V(ae);
},
addCommands() {
var e;
return {
...(e = this.parent) == null ? void 0 : e.call(this),
setImageGif: (t) => ({ commands: r }) => r.insertContent({
type: this.name,
attrs: t
}),
updateImageGif: (t) => ({ commands: r }) => r.updateAttributes(this.name, t),
setAlignImageGif: (t) => ({ commands: r }) => r.updateAttributes(this.name, { align: t })
};
},
renderHTML({ HTMLAttributes: e }) {
const { align: t } = e;
return [
"div",
// Parent element
{
style: t ? `text-align: ${t};` : "",
class: "imageGIf"
},
[
"img",
q(
// Always render the `height="auto"`
{
height: "auto"
},
this.options.HTMLAttributes,
e
)
]
];
},
parseHTML() {
return [
{
tag: "div[class=imageGIf]",
getAttrs: (e) => {
const t = e.querySelector("img"), r = t == null ? void 0 : t.getAttribute("width");
return {
src: t == null ? void 0 : t.getAttribute("src"),
alt: t == null ? void 0 : t.getAttribute("alt"),
title: t == null ? void 0 : t.getAttribute("title"),
width: r ? Number.parseInt(r, 10) : null,
align: (t == null ? void 0 : t.getAttribute("align")) || e.style.textAlign || null
};
}
}
];
}
});
export {
ue as ImageGif,
_e as RichTextImageGif
};