UNPKG

@blocknote/react

Version:

A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.

420 lines (419 loc) 13.4 kB
import { t as e } from "./rolldown-runtime-CAFD8bLK.js"; import { f as t, g as n, i as r, l as i, m as a, n as o, p as s, r as c, s as l, t as u } from "./defaultCommentEditorSchema-3DnX6Knw.js"; import d, { memo as f, useCallback as p, useEffect as m, useMemo as h, useRef as g, useState as _, useSyncExternalStore as v } from "react"; import { CommentsExtension as y } from "@blocknote/core/comments"; import { flip as b, offset as x, shift as S } from "@floating-ui/react"; import { mergeCSSClasses as C } from "@blocknote/core"; import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime"; import { RiArrowGoBackFill as D, RiCheckFill as O, RiDeleteBinFill as k, RiEditFill as A, RiEmotionLine as j, RiMoreFill as M } from "react-icons/ri"; //#region src/components/Comments/EmojiMartPicker.tsx var N; async function P() { return N || (N = (async () => { let [e, t] = await Promise.all([import("emoji-mart"), import("@emoji-mart/data")]), n = "default" in e ? e.default : e, r = "default" in t ? t.default : t; return await n.init({ data: r }), { emojiMart: n, emojiData: r }; })(), N); } function F(e) { let t = g(null), n = g(null); return n.current && n.current.update(e), m(() => ((async () => { let { emojiMart: r } = await P(); n.current = new r.Picker({ ...e, ref: t }); })(), () => { n.current = null; }), []), d.createElement("div", { ref: t }); } //#endregion //#region src/components/Comments/EmojiPicker.tsx var I = (e) => { let [t, r] = _(!1), a = i(), o = n(), s = o.editor?.portalElement; if (!s) throw Error("Portal root not found"); return /* @__PURE__ */ E(a.Generic.Popover.Root, { open: t, portalRoot: s, children: [/* @__PURE__ */ T(a.Generic.Popover.Trigger, { children: /* @__PURE__ */ T("div", { onClick: (n) => { n.preventDefault(), n.stopPropagation(), r(!t), e.onOpenChange?.(!t); }, style: { display: "flex", justifyContent: "center", alignItems: "center" }, children: e.children }) }), /* @__PURE__ */ T(a.Generic.Popover.Content, { className: "bn-emoji-picker-popover", variant: "panel-popover", children: /* @__PURE__ */ T(F, { perLine: 7, onClickOutside: () => { r(!1), e.onOpenChange?.(!1); }, onEmojiSelect: (t) => { e.onEmojiSelect(t), r(!1), e.onOpenChange?.(!1); }, theme: o?.colorSchemePreference }) })] }); }; //#endregion //#region src/components/Comments/useUsers.ts function L(e) { return R([e]).get(e); } function R(e) { let n = t(y).userStore, r = p(() => { let t = /* @__PURE__ */ new Map(); for (let r of e) { let e = n.getUser(r); e && t.set(r, e); } return t; }, [n, e]), i = h(() => ({ current: r() }), [r]); return v(p((t) => { let a = n.subscribe((e) => { i.current = r(), t(); }); return n.loadUsers(e), a; }, [ n, r, e, i ]), () => i.current); } //#endregion //#region src/components/Comments/ReactionBadge.tsx var z = (e) => { let n = i(), r = l(), a = t(y), o = e.comment.reactions.find((t) => t.emoji === e.emoji); if (!o) throw Error("Trying to render reaction badge for non-existing reaction"); let [s, c] = _([]), u = R(s); return /* @__PURE__ */ T(n.Generic.Badge.Root, { className: C("bn-badge", "bn-comment-reaction"), text: o.userIds.length.toString(), icon: o.emoji, isSelected: a.threadStore.auth.canDeleteReaction(e.comment, o.emoji), onClick: () => e.onReactionSelect(o.emoji), onMouseEnter: () => c(o.userIds), mainTooltip: r.comments.reactions.reacted_by, secondaryTooltip: `${Array.from(u.values()).map((e) => e.username).join("\n")}` }, o.emoji); }, B = f(({ isEmpty: e, comment: t, isEditing: n, threadStore: r, onReactionSelect: i, onEditSubmit: a, onEditCancel: o, onEmojiPickerOpenChange: s, Components: c, dict: l }) => { let u = r.auth.canAddReaction(t); return /* @__PURE__ */ E(w, { children: [t.reactions.length > 0 && !n && /* @__PURE__ */ E(c.Generic.Badge.Group, { className: C("bn-badge-group", "bn-comment-reactions"), children: [t.reactions.map((e) => /* @__PURE__ */ T(z, { comment: t, emoji: e.emoji, onReactionSelect: i }, e.emoji)), u && /* @__PURE__ */ T(I, { onEmojiSelect: (e) => i(e.native), onOpenChange: s, children: /* @__PURE__ */ T(c.Generic.Badge.Root, { className: C("bn-badge", "bn-comment-add-reaction"), text: "+", icon: /* @__PURE__ */ T(j, { size: 16 }), mainTooltip: l.comments.actions.add_reaction }) })] }), n && /* @__PURE__ */ E(c.Generic.Toolbar.Root, { variant: "action-toolbar", className: C("bn-action-toolbar", "bn-comment-actions"), children: [/* @__PURE__ */ T(c.Generic.Toolbar.Button, { mainTooltip: l.comments.save_button_text, variant: "compact", onClick: a, isDisabled: e, children: l.comments.save_button_text }), /* @__PURE__ */ T(c.Generic.Toolbar.Button, { className: "bn-button", mainTooltip: l.comments.cancel_button_text, variant: "compact", onClick: o, children: l.comments.cancel_button_text })] })] }); }), V = ({ comment: e, thread: n, showResolveButton: r }) => { let a = t(y), s = l(), d = c({ initialContent: e.body, trailingBlock: !1, dictionary: { ...s, placeholders: { emptyDocument: s.placeholders.edit_comment } }, schema: a.commentEditorSchema || u }), f = i(), [m, h] = _(!1), [g, v] = _(!1), b = a.threadStore, x = p(() => { h(!0); }, []), S = p(() => { d.replaceBlocks(d.document, e.body), h(!1); }, [d, e.body]), w = p(async (t) => { await b.updateComment({ commentId: e.id, comment: { body: d.document }, threadId: n.id }), h(!1); }, [ e, n.id, d, b ]), N = p(async () => { await b.deleteComment({ commentId: e.id, threadId: n.id }); }, [ e, n.id, b ]), P = p(async (t) => { b.auth.canAddReaction(e, t) ? await b.addReaction({ threadId: n.id, commentId: e.id, emoji: t }) : b.auth.canDeleteReaction(e, t) && await b.deleteReaction({ threadId: n.id, commentId: e.id, emoji: t }); }, [ b, e, n.id ]), F = p(async () => { await b.resolveThread({ threadId: n.id }); }, [n.id, b]), R = p(async () => { await b.unresolveThread({ threadId: n.id }); }, [n.id, b]), z = L(e.userId); if (!e.body) return null; let V, H = b.auth.canAddReaction(e), U = b.auth.canDeleteComment(e), W = b.auth.canUpdateComment(e), G = r && (n.resolved ? b.auth.canUnresolveThread(n) : b.auth.canResolveThread(n)); m || (V = /* @__PURE__ */ E(f.Generic.Toolbar.Root, { className: C("bn-action-toolbar", "bn-comment-actions"), variant: "action-toolbar", children: [ H && /* @__PURE__ */ T(I, { onEmojiSelect: (e) => P(e.native), onOpenChange: v, children: /* @__PURE__ */ T(f.Generic.Toolbar.Button, { mainTooltip: s.comments.actions.add_reaction, variant: "compact", children: /* @__PURE__ */ T(j, { size: 16 }) }, "add-reaction") }), G && (n.resolved ? /* @__PURE__ */ T(f.Generic.Toolbar.Button, { mainTooltip: s.comments.actions.reopen, variant: "compact", onClick: R, children: /* @__PURE__ */ T(D, { size: 16 }) }, "reopen") : /* @__PURE__ */ T(f.Generic.Toolbar.Button, { mainTooltip: s.comments.actions.resolve, variant: "compact", onClick: F, children: /* @__PURE__ */ T(O, { size: 16 }) }, "resolve")), (U || W) && /* @__PURE__ */ E(f.Generic.Menu.Root, { position: "bottom-start", children: [/* @__PURE__ */ T(f.Generic.Menu.Trigger, { children: /* @__PURE__ */ T(f.Generic.Toolbar.Button, { mainTooltip: s.comments.actions.more_actions, variant: "compact", children: /* @__PURE__ */ T(M, { size: 16 }) }, "more-actions") }), /* @__PURE__ */ E(f.Generic.Menu.Dropdown, { className: "bn-menu-dropdown", children: [W && /* @__PURE__ */ T(f.Generic.Menu.Item, { icon: /* @__PURE__ */ T(A, {}), onClick: x, children: s.comments.actions.edit_comment }, "edit-comment"), U && /* @__PURE__ */ T(f.Generic.Menu.Item, { icon: /* @__PURE__ */ T(k, {}), onClick: N, children: s.comments.actions.delete_comment }, "delete-comment")] })] }) ] })); let K = e.createdAt.toLocaleDateString(void 0, { month: "short", day: "numeric" }); if (!e.body) throw Error("soft deletes are not yet supported"); return /* @__PURE__ */ T(f.Comments.Comment, { authorInfo: z ?? "loading", timeString: K, edited: e.updatedAt.getTime() !== e.createdAt.getTime(), showActions: "hover", actions: V, className: "bn-thread-comment", emojiPickerOpen: g, children: /* @__PURE__ */ T(o, { autoFocus: m, editor: d, editable: m, actions: e.reactions.length > 0 || m ? ({ isFocused: t, isEmpty: n }) => /* @__PURE__ */ T(B, { isFocused: t, isEmpty: n, comment: e, isEditing: m, threadStore: b, onReactionSelect: P, onEditSubmit: w, onEditCancel: S, onEmojiPickerOpenChange: v, Components: f, dict: s }) : void 0 }) }); }, H = ({ thread: e, maxCommentsBeforeCollapse: t }) => { let n = i(), r = l(), a = R(e.resolvedBy ? [e.resolvedBy] : []), o = e.comments.map((t, n) => /* @__PURE__ */ T(V, { thread: e, comment: t, showResolveButton: n === 0 }, t.id + JSON.stringify(t.body || "{}"))); if (e.resolved && e.resolvedUpdatedAt && e.resolvedBy) { if (!a.get(e.resolvedBy)) throw Error(`User ${e.resolvedBy} resolved thread ${e.id}, but their data could not be found.`); let t = e.comments.findLastIndex((t) => e.resolvedUpdatedAt.getTime() > t.createdAt.getTime()) + 1; o.splice(t, 0, /* @__PURE__ */ T(n.Comments.Comment, { className: "bn-thread-comment", authorInfo: e.resolvedBy && a.get(e.resolvedBy) || "loading", timeString: e.resolvedUpdatedAt.toLocaleDateString(void 0, { month: "short", day: "numeric" }), edited: !1, showActions: !1, children: /* @__PURE__ */ T("div", { className: "bn-resolved-text", children: r.comments.sidebar.marked_as_resolved }) }, "resolved-comment")); } return t && o.length > t && o.splice(1, o.length - 2, /* @__PURE__ */ T(n.Comments.ExpandSectionsPrompt, { className: "bn-thread-expand-prompt", children: r.comments.sidebar.more_replies(e.comments.length - 2) }, "expand-prompt")), o; }, U = f(({ isEmpty: e, onNewCommentSave: t, Components: n, dict: r }) => e ? null : /* @__PURE__ */ T(n.Generic.Toolbar.Root, { variant: "action-toolbar", className: C("bn-action-toolbar", "bn-comment-actions"), children: /* @__PURE__ */ T(n.Generic.Toolbar.Button, { mainTooltip: r.comments.save_button_text, variant: "compact", isDisabled: e, onClick: t, children: r.comments.save_button_text }) })), W = ({ thread: e, selected: n, orphaned: r, referenceText: a, maxCommentsBeforeCollapse: s, onFocus: d, onBlur: f, tabIndex: m }) => { let h = i(), g = l(), _ = t(y), v = c({ trailingBlock: !1, dictionary: { ...g, placeholders: { emptyDocument: g.placeholders.comment_reply } }, schema: _.commentEditorSchema || u }), b = p(async () => { await _.threadStore.addComment({ comment: { body: v.document }, threadId: e.id }), v.removeBlocks(v.document); }, [ _, v, e.id ]); return /* @__PURE__ */ E(h.Comments.Card, { className: C("bn-thread", r && "bn-thread-orphaned"), headerText: a, onFocus: d, onBlur: f, selected: n, tabIndex: m, children: [/* @__PURE__ */ T(h.Comments.CardSection, { className: "bn-thread-comments", children: /* @__PURE__ */ T(H, { thread: e, maxCommentsBeforeCollapse: n ? void 0 : s || 5 }) }), n && /* @__PURE__ */ T(h.Comments.CardSection, { className: "bn-thread-composer", children: /* @__PURE__ */ T(o, { autoFocus: !1, editable: !0, editor: v, actions: ({ isFocused: e, isEmpty: t }) => /* @__PURE__ */ T(U, { isFocused: e, isEmpty: t, onNewCommentSave: b, Components: h, dict: g }) }) })] }); }; //#endregion //#region src/components/Comments/useThreads.ts function G() { let e = t(y).threadStore, n = g(void 0); return n.current ||= e.getThreads(), v(p((t) => e.subscribe((e) => { n.current = e, t(); }), [e]), () => n.current); } //#endregion //#region src/components/Comments/FloatingThreadController.tsx var K = /* @__PURE__ */ e({ default: () => q }); function q(e) { let n = a(), i = t(y), o = s(y, { editor: n, selector: (e) => e.selectedThreadId ? { id: e.selectedThreadId, position: e.threadPositions.get(e.selectedThreadId) } : void 0 }), c = G(), l = h(() => o ? c.get(o.id) : void 0, [o, c]), u = h(() => ({ ...e.floatingUIOptions, useFloatingOptions: { open: !!o, onOpenChange: (e, t, r) => { r === "escape-key" && n.focus(), e || i.selectThread(void 0); }, placement: "bottom", middleware: [ x(10), S(), b() ], ...e.floatingUIOptions?.useFloatingOptions }, focusManagerProps: { disabled: !0, ...e.floatingUIOptions?.focusManagerProps }, elementProps: { style: { zIndex: 30 }, ...e.floatingUIOptions?.elementProps } }), [ i, n, e.floatingUIOptions, o ]), d = e.floatingThread || W; return /* @__PURE__ */ T(r, { position: o?.position, portalElement: e.portalElement, ...u, children: l && /* @__PURE__ */ T(d, { thread: l, selected: !0 }) }); } //#endregion export { H as a, R as c, W as i, K as n, V as o, G as r, L as s, q as t }; //# sourceMappingURL=FloatingThreadController-BZkNOp17.js.map