UNPKG

@blocknote/core

Version:

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

350 lines (349 loc) 9.75 kB
import { i as e, n as t, t as n } from "./BlockNoteExtension-B_IEJHS8.js"; import { n as r, r as i } from "./UserStore-QbbPFj0z.js"; import { t as a } from "./ThreadStore-et9wB5Xt.js"; import { Mark as o, mergeAttributes as s } from "@tiptap/core"; import { Plugin as c, PluginKey as l } from "prosemirror-state"; import { Decoration as u, DecorationSet as d } from "prosemirror-view"; //#region src/comments/mark.ts var f = o.create({ name: "comment", excludes: "", inclusive: !1, keepOnSplit: !0, group: e, addAttributes() { return { orphan: { parseHTML: (e) => !!e.getAttribute("data-orphan"), renderHTML: (e) => e.orphan ? { "data-orphan": "true" } : {}, default: !1 }, threadId: { parseHTML: (e) => e.getAttribute("data-bn-thread-id"), renderHTML: (e) => ({ "data-bn-thread-id": e.threadId }), default: "" } }; }, renderHTML({ HTMLAttributes: e }) { return ["span", s(e, { class: "bn-thread-mark" })]; }, parseHTML() { return [{ tag: "span.bn-thread-mark" }]; }, extendMarkSchema(e) { return e.name === "comment" ? { blocknoteIgnore: !0 } : {}; } }), p = new l("blocknote-comments"); function m(e, t) { let n = /* @__PURE__ */ new Map(); return e.descendants((e, r) => { e.marks.forEach((i) => { if (i.type.name === t) { let t = i.attrs.threadId; if (!t) return; let a = r, o = a + e.nodeSize, s = n.get(t) ?? { from: Infinity, to: 0 }; n.set(t, { from: Math.min(a, s.from), to: Math.max(o, s.to) }); } }); }), n; } var h = n(({ editor: e, options: { schema: n, threadStore: a, resolveUsers: o, confirmBeforeDiscard: s = !0 } }) => { if (!o) throw Error("resolveUsers is required to be defined when using comments"); if (!a) throw Error("threadStore is required to be defined when using comments"); let l = r(o), h = f.name, g = t({ pendingComment: !1, selectedThreadId: void 0, threadPositions: /* @__PURE__ */ new Map() }, { onUpdate() { g.state.selectedThreadId !== g.prevState.selectedThreadId && e.transact((e) => e.setMeta(p, !0)); } }), _ = (t) => { e.transact((e) => { e.doc.descendants((n, r) => { n.marks.forEach((i) => { if (i.type.name === h) { let a = i.type, o = i.attrs.threadId, s = t.get(o), c = !!(!s || s.resolved || s.deletedAt); if (c !== i.attrs.orphan) { let t = Math.max(r, 0), s = Math.min(r + n.nodeSize, e.doc.content.size - 1, e.doc.content.size - 1); e.removeMark(t, s, i), e.addMark(t, s, a.create({ ...i.attrs, orphan: c })), c && g.state.selectedThreadId === o && g.setState((e) => ({ ...e, selectedThreadId: void 0 })); } } }); }); }); }; return { key: "comments", store: g, userStore: l, runsBefore: ["link"], tiptapExtensions: [f], prosemirrorPlugins: [new c({ key: p, state: { init() { return { decorations: d.empty }; }, apply(e, t) { let n = e.getMeta(p); if (!e.docChanged && !n) return t; let r = e.docChanged ? m(e.doc, h) : g.state.threadPositions; (r.size > 0 || g.state.threadPositions.size > 0) && g.setState((e) => ({ ...e, threadPositions: r })); let i = []; if (g.state.selectedThreadId) { let e = r.get(g.state.selectedThreadId); e && i.push(u.inline(e.from, e.to, { class: "bn-thread-mark-selected" })); } return { decorations: d.create(e.doc, i) }; } }, props: { decorations(e) { return p.getState(e)?.decorations ?? d.empty; }, handleClick: (e, t, n) => { if (n.button !== 0) return !1; let r = e.state.doc.nodeAt(t); if (!r) return g.setState((e) => ({ ...e, selectedThreadId: void 0 })), !1; let i = r.marks.find((e) => e.type.name === h && e.attrs.orphan !== !0); if (!i) return g.state.selectedThreadId !== void 0 && g.setState((e) => ({ ...e, selectedThreadId: void 0 })), !1; let a = i.attrs.threadId; return a === g.state.selectedThreadId ? !1 : (g.setState((e) => ({ ...e, selectedThreadId: a })), !0); } } })], threadStore: a, mount() { let t = a.subscribe(_); _(a.getThreads()); let n = e.onSelectionChange(() => { g.state.pendingComment && g.setState((e) => ({ ...e, pendingComment: !1 })); }); return () => { t(), n(); }; }, selectThread(t, n = !0) { if (g.state.selectedThreadId !== t && (g.setState((e) => ({ ...e, pendingComment: !1, selectedThreadId: t })), t && n)) { let n = g.state.threadPositions.get(t); if (!n) return; (e.prosemirrorView?.domAtPos(n.from).node)?.scrollIntoView({ behavior: "smooth", block: "center" }); } }, startPendingComment() { g.setState((e) => ({ ...e, selectedThreadId: void 0, pendingComment: !0 })), e.domElement?.focus(), e.getExtension(i)?.showSelection(!0, "comments"); }, stopPendingComment() { g.setState((e) => ({ ...e, selectedThreadId: void 0, pendingComment: !1 })), e.getExtension(i)?.showSelection(!1, "comments"); }, async createThread(t) { let n = await a.createThread(t); a.addThreadToDocument ? await a.addThreadToDocument({ threadId: n.id, selection: e.transact((e) => e.selection), editor: e }) : e._tiptapEditor.commands.setMark(h, { orphan: !1, threadId: n.id }); }, commentEditorSchema: n, confirmBeforeDiscard: s }; }), g = class {}, _ = class extends g { userId; role; constructor(e, t) { super(), this.userId = e, this.role = t; } canCreateThread() { return !0; } canAddComment(e) { return !0; } canUpdateComment(e) { return e.userId === this.userId; } canDeleteComment(e) { return e.userId === this.userId || this.role === "editor"; } canDeleteThread(e) { return this.role === "editor"; } canResolveThread(e) { return !0; } canUnresolveThread(e) { return !0; } canAddReaction(e, t) { return t ? !e.reactions.some((e) => e.emoji === t && e.userIds.includes(this.userId)) : !0; } canDeleteReaction(e, t) { return t ? e.reactions.some((e) => e.emoji === t && e.userIds.includes(this.userId)) : !0; } }, v = class extends a { userId; provider; constructor(e, t, n) { super(n), this.userId = e, this.provider = t; } async createThread(e) { let t = this.provider.createThread({ data: e.metadata }); return t = this.provider.addComment(t.id, { content: e.initialComment.body, data: { metadata: e.initialComment.metadata, userId: this.userId } }), this.tiptapThreadToThreadData(t); } addThreadToDocument = void 0; async addComment(e) { let t = this.provider.addComment(e.threadId, { content: e.comment.body, data: { metadata: e.comment.metadata, userId: this.userId } }); return this.tiptapCommentToCommentData(t.comments[t.comments.length - 1]); } async updateComment(e) { let t = this.provider.getThreadComment(e.threadId, e.commentId, !0); if (!t) throw Error("Comment not found"); this.provider.updateComment(e.threadId, e.commentId, { content: e.comment.body, data: { ...t.data, metadata: e.comment.metadata } }); } tiptapCommentToCommentData(e) { let t = []; for (let n of e.data?.reactions || []) { let e = t.find((e) => e.emoji === n.emoji); e ? (e.userIds.push(n.userId), e.createdAt = new Date(Math.min(e.createdAt.getTime(), n.createdAt))) : t.push({ emoji: n.emoji, createdAt: new Date(n.createdAt), userIds: [n.userId] }); } return { type: "comment", id: e.id, body: e.content, metadata: e.data?.metadata, userId: e.data?.userId, createdAt: new Date(e.createdAt), updatedAt: new Date(e.updatedAt), reactions: t }; } tiptapThreadToThreadData(e) { return { type: "thread", id: e.id, comments: e.comments.map((e) => this.tiptapCommentToCommentData(e)), resolved: !!e.resolvedAt, metadata: e.data?.metadata, createdAt: new Date(e.createdAt), updatedAt: new Date(e.updatedAt) }; } async deleteComment(e) { this.provider.deleteComment(e.threadId, e.commentId); } async deleteThread(e) { this.provider.deleteThread(e.threadId); } async resolveThread(e) { this.provider.updateThread(e.threadId, { resolvedAt: (/* @__PURE__ */ new Date()).toISOString() }); } async unresolveThread(e) { this.provider.updateThread(e.threadId, { resolvedAt: null }); } async addReaction(e) { let t = this.provider.getThreadComment(e.threadId, e.commentId, !0); if (!t) throw Error("Comment not found"); this.provider.updateComment(e.threadId, e.commentId, { data: { ...t.data, reactions: [...t.data?.reactions || [], { emoji: e.emoji, createdAt: Date.now(), userId: this.userId }] } }); } async deleteReaction(e) { let t = this.provider.getThreadComment(e.threadId, e.commentId, !0); if (!t) throw Error("Comment not found"); this.provider.updateComment(e.threadId, e.commentId, { data: { ...t.data, reactions: (t.data?.reactions || []).filter((t) => t.emoji !== e.emoji && t.userId !== this.userId) } }); } getThread(e) { let t = this.provider.getThread(e); if (!t) throw Error("Thread not found"); return this.tiptapThreadToThreadData(t); } getThreads() { return new Map(this.provider.getThreads().map((e) => [e.id, this.tiptapThreadToThreadData(e)])); } subscribe(e) { let t = () => { e(this.getThreads()); }; return this.provider.watchThreads(t), () => { this.provider.unwatchThreads(t); }; } }; //#endregion export { f as CommentMark, h as CommentsExtension, _ as DefaultThreadStoreAuth, a as ThreadStore, g as ThreadStoreAuth, v as TiptapThreadStore }; //# sourceMappingURL=comments.js.map