UNPKG

@blocknote/core

Version:

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

547 lines (546 loc) 19.6 kB
import { On as e, jt as t, vn as n, vt as r } from "./blocks-CiuUGa1E.js"; import { n as i, t as a } from "./BlockNoteExtension-B_IEJHS8.js"; import { t as o } from "./ThreadStore-et9wB5Xt.js"; import { Node as s } from "prosemirror-model"; import { uuidv4 as c } from "lib0/random"; import { Plugin as l, PluginKey as u } from "@tiptap/pm/state"; import { absolutePositionToRelativePosition as d, defaultSelectionBuilder as f, prosemirrorToYDoc as p, prosemirrorToYXmlFragment as m, redoCommand as h, relativePositionToAbsolutePosition as g, undoCommand as _, yCursorPlugin as v, ySyncPlugin as y, ySyncPluginKey as b, yUndoPlugin as ee, yUndoPluginKey as x, yXmlFragmentToProseMirrorRootNode as S } from "y-prosemirror"; import * as C from "yjs"; //#region src/yjs/utils.ts function w(e, t) { let n = e.doc; if (!n) throw Error("type does not have a ydoc"); if (e._item === null) { let r = Array.from(n.share.keys()).find((t) => n.share.get(t) === e); if (r == null) throw Error("type does not exist in other ydoc"); return t.get(r, e.constructor); } else { let n = e._item, r = t.store.clients.get(n.id.client) ?? [], i = r[C.findIndexSS(r, n.id.clock)]; if (!i) throw Error("type does not exist in other ydoc"); let a = i.content; if (!a) throw Error("type does not exist in other ydoc"); return a.type; } } function T(e, t) { return n(e.pmSchema.nodeFromJSON(t)); } function E(e, n) { let r = n.map((n) => t(n, e.pmSchema)); return e.pmSchema.topNodeType.create(null, e.pmSchema.nodes.blockGroup.create(null, r)); } function D(e, t) { return n(S(t, e.pmSchema)); } function O(e, t, n) { return m(E(e, t), n); } function k(e, t, n = "prosemirror") { return D(e, t.getXmlFragment(n)); } function A(e, t, n = "prosemirror") { return p(E(e, t), n); } //#endregion //#region src/yjs/extensions/FixUpSchema.ts var j = a(({ editor: t }) => (t.on("create", () => { let n = t.pmSchema, r, i = n.nodes.doc.createAndFill; n.nodes.doc.createAndFill = ((...e) => { if (r) return r; let t = i.apply(n.nodes.doc, e), a = JSON.parse(JSON.stringify(t.toJSON())); return a.content[0].content[0].attrs.id = "initialBlockId", r = s.fromJSON(n, a), r; }); let a = n.node; n.node = function(t, n, r, i) { let o = typeof t == "string" ? this.nodes[t] : t; return o && e(this, o) && Array.isArray(r) && (r = r.map((e) => e?.isText && e.marks.length ? e.mark(o.allowedMarks(e.marks)) : e)), a.call(this, t, n, r, i); }; }), { key: "fixUpSchema" })); //#endregion //#region src/yjs/extensions/YCursorPlugin.ts function M(e) { let t = e.charAt(0) === "#" ? e.substring(1, 7) : e, n = parseInt(t.substring(0, 2), 16), r = parseInt(t.substring(2, 4), 16), i = parseInt(t.substring(4, 6), 16), a = [ n / 255, r / 255, i / 255 ].map((e) => e <= .03928 ? e / 12.92 : ((e + .055) / 1.055) ** 2.4); return .2126 * a[0] + .7152 * a[1] + .0722 * a[2] <= .179; } function N(e) { let t = document.createElement("span"); t.classList.add("bn-collaboration-cursor__base"); let n = document.createElement("span"); n.setAttribute("contentedEditable", "false"), n.classList.add("bn-collaboration-cursor__caret"), n.setAttribute("style", `background-color: ${e.color}; color: ${M(e.color) ? "white" : "black"}`); let r = document.createElement("span"); return r.classList.add("bn-collaboration-cursor__label"), r.setAttribute("style", `background-color: ${e.color}; color: ${M(e.color) ? "white" : "black"}`), r.insertBefore(document.createTextNode(e.name), null), n.insertBefore(r, null), t.insertBefore(document.createTextNode("⁠"), null), t.insertBefore(n, null), t.insertBefore(document.createTextNode("⁠"), null), t; } var P = a(({ options: e }) => { let t = /* @__PURE__ */ new Map(), n = e.provider && "awareness" in e.provider && typeof e.provider.awareness == "object" ? e.provider.awareness : void 0; n && "setLocalStateField" in n && typeof n.setLocalStateField == "function" && n.setLocalStateField("user", e.user); let r = ({ updated: e }) => { for (let n of e) { let e = t.get(n); e && (setTimeout(() => { e.element.setAttribute("data-active", ""); }, 10), e.hideTimeout && clearTimeout(e.hideTimeout), t.set(n, { element: e.element, hideTimeout: setTimeout(() => { e.element.removeAttribute("data-active"); }, 2e3) })); } }; return { key: "yCursor", mount() { if (n && e.showCursorLabels !== "always" && "on" in n && typeof n.on == "function") return n.on("change", r), () => { "off" in n && typeof n.off == "function" && n.off("change", r); }; }, prosemirrorPlugins: [n ? v(n, { selectionBuilder: f, cursorBuilder(n, r) { let i = t.get(r); if (!i) { let a = (e.renderCursor ?? N)(n); e.showCursorLabels !== "always" && (a.addEventListener("mouseenter", () => { let e = t.get(r); e.element.setAttribute("data-active", ""), e.hideTimeout && (clearTimeout(e.hideTimeout), t.set(r, { element: e.element, hideTimeout: void 0 })); }), a.addEventListener("mouseleave", () => { let e = t.get(r); t.set(r, { element: e.element, hideTimeout: setTimeout(() => { e.element.removeAttribute("data-active"); }, 2e3) }); })), i = { element: a, hideTimeout: void 0 }, t.set(r, i); } return i.element; } }) : void 0].filter(Boolean), dependsOn: ["ySync"], updateUser(e) { n?.setLocalStateField("user", e); }, getUser() { let e = n?.getLocalState(); if (e) return e.user; } }; }), F = a(({ options: e }) => ({ key: "ySync", prosemirrorPlugins: [y(e.fragment)], runsBefore: ["default"] })), I = a(() => ({ key: "yUndo", prosemirrorPlugins: [ee()], dependsOn: ["yCursor", "ySync"], undoCommand: _, redoCommand: h })), L = a(({ editor: e, options: t }) => { let n, r = i({ isForked: !1 }); return { key: "yForkDoc", store: r, fork({ initialUpdate: i } = {}) { if (n) return; let a = t.fragment; if (!a) throw Error("No fragment to fork from"); let o = new C.Doc(); C.applyUpdate(o, i ?? C.encodeStateAsUpdate(a.doc)); let s = w(a, o); n = { undoStack: x.getState(e.prosemirrorState).undoManager.undoStack, originalFragment: a, forkedFragment: s }; let c = { ...t, fragment: s }; e.replaceExtension([ "ySync", "yCursor", "yUndo" ], [F(c), I()]), r.setState({ isForked: !0 }); }, merge({ keepChanges: i }) { if (!n) return; let { originalFragment: a, forkedFragment: o, undoStack: s } = n; if (e.replaceExtension([ "ySync", "yCursor", "yUndo" ], [ F(t), P(t), I() ]), x.getState(e.prosemirrorState).undoManager.undoStack = s, i) { let t = C.encodeStateAsUpdate(o.doc, C.encodeStateVector(a.doc)); C.applyUpdate(a.doc, t, e); } n = void 0, r.setState({ isForked: !1 }); } }; }), R = a(({ editor: e }) => ({ key: "yPositionMapping", mapPosition: (t, n = "left") => { let r = b.getState(e.prosemirrorState); if (!r) throw Error("YSync plugin state not found"); if (t === 0) return () => 0; if (r.binding.type.length === 0) { let r = e.getExtension("positionMapping"); if (!r) throw Error("positionMapping extension is not available; cannot map position before sync"); return r.mapPosition(t, n); } let i = d(t + (n === "right" ? 1 : -1), r.binding.type, r.binding.mapping); return () => { let t = b.getState(e.prosemirrorState), r = g(t.doc, t.binding.type, i, t.binding.mapping); if (r === null) throw Error("Position not found, cannot track positions"); return r + (n === "right" ? -1 : 1); }; } })), z = (e, t) => { t(e), e.forEach((e) => { e instanceof C.XmlElement && z(e, t); }); }, B = [(e, t) => { let n = /* @__PURE__ */ new Map(); return e.forEach((e) => { e instanceof C.XmlElement && z(e, (e) => { if (e.nodeName === "blockContainer" && e.hasAttribute("id")) { let t = e.getAttribute("textColor"), i = e.getAttribute("backgroundColor"), a = { textColor: t === r.textColor.default ? void 0 : t, backgroundColor: i === r.backgroundColor.default ? void 0 : i }; (a.textColor || a.backgroundColor) && n.set(e.getAttribute("id"), a); } }); }), n.size === 0 ? !1 : (t.doc.descendants((e, r) => { if (e.type.name === "blockContainer" && n.has(e.attrs.id)) { let i = t.doc.nodeAt(r + 1); if (!i) throw Error("No element found"); t.setNodeMarkup(r + 1, void 0, { ...i.attrs, ...n.get(e.attrs.id) }); } }), !0); }], V = a(({ options: e }) => { let t = !1; return { key: "schemaMigration", prosemirrorPlugins: [new l({ key: new u("schemaMigration"), appendTransaction: (n, r, i) => { if (t || !n.some((e) => e.getMeta("y-sync$")) || n.every((e) => !e.docChanged) || !e.fragment.firstChild) return; let a = i.tr; for (let t of B) t(e.fragment, a); if (t = !0, a.docChanged) return a; } })] }; }); //#endregion //#region src/yjs/extensions/Versioning.ts function H(e, t) { let { fragment: n } = t; function r() { let t = e.getExtension(L); if (!t) throw Error("ForkYDocExtension is required for the Yjs versioning adapter. Make sure it is registered before the VersioningExtension."); return t; } return { getCurrentDocument: () => n, serializeCurrentContent: () => C.encodeStateAsUpdateV2(n.doc), preview: { supportsComparison: !1, enterPreview(e, t) { let n = r(); n.store.state.isForked && n.merge({ keepChanges: !1 }), n.fork({ initialUpdate: e }); }, exitPreview() { let e = r(); e.store.state.isForked && e.merge({ keepChanges: !1 }); }, applyRestore(e) { throw Error("Restore is not yet implemented for Yjs v13 versioning adapter."); } } }; } //#endregion //#region src/yjs/extensions/index.ts var U = a(({ options: e }) => ({ key: "collaboration", blockNoteExtensions: [ j(), L(e), R(), V(e), P(e), F(e), I() ] })); function W(e) { return { ...e, extensions: [...e.extensions ?? [], U(e.collaboration)], disableExtensions: ["history", ...e.disableExtensions ?? []], initialContent: [{ type: "paragraph", id: "initialBlockId" }] }; } //#endregion //#region src/yjs/comments/yjsHelpers.ts function G(e) { let t = new C.Map(); if (t.set("id", e.id), t.set("userId", e.userId), t.set("createdAt", e.createdAt.getTime()), t.set("updatedAt", e.updatedAt.getTime()), e.deletedAt ? (t.set("deletedAt", e.deletedAt.getTime()), t.set("body", void 0)) : t.set("body", e.body), e.reactions.length > 0) throw Error("Reactions should be empty in commentToYMap"); return t.set("reactionsByUser", new C.Map()), t.set("metadata", e.metadata), t; } function K(e) { let t = new C.Map(); t.set("id", e.id), t.set("createdAt", e.createdAt.getTime()), t.set("updatedAt", e.updatedAt.getTime()); let n = new C.Array(); return n.push(e.comments.map((e) => G(e))), t.set("comments", n), t.set("resolved", e.resolved), t.set("resolvedUpdatedAt", e.resolvedUpdatedAt?.getTime()), t.set("resolvedBy", e.resolvedBy), t.set("metadata", e.metadata), t; } function q(e) { return { emoji: e.get("emoji"), createdAt: new Date(e.get("createdAt")), userId: e.get("userId") }; } function J(e) { return [...e.values()].map((e) => q(e)).reduce((e, t) => { let n = e.find((e) => e.emoji === t.emoji); return n ? (n.userIds.push(t.userId), n.createdAt = new Date(Math.min(n.createdAt.getTime(), t.createdAt.getTime()))) : e.push({ emoji: t.emoji, createdAt: t.createdAt, userIds: [t.userId] }), e; }, []); } function Y(e) { return { type: "comment", id: e.get("id"), userId: e.get("userId"), createdAt: new Date(e.get("createdAt")), updatedAt: new Date(e.get("updatedAt")), deletedAt: e.get("deletedAt") ? new Date(e.get("deletedAt")) : void 0, reactions: J(e.get("reactionsByUser")), metadata: e.get("metadata"), body: e.get("body") }; } function X(e) { return { type: "thread", id: e.get("id"), createdAt: new Date(e.get("createdAt")), updatedAt: new Date(e.get("updatedAt")), comments: (e.get("comments") || []).map((e) => Y(e)), resolved: e.get("resolved"), resolvedUpdatedAt: new Date(e.get("resolvedUpdatedAt")), resolvedBy: e.get("resolvedBy"), metadata: e.get("metadata") }; } //#endregion //#region src/yjs/comments/YjsThreadStoreBase.ts var Z = class extends o { threadsYMap; constructor(e, t) { super(t), this.threadsYMap = e; } getThread(e) { let t = this.threadsYMap.get(e); if (!t) throw Error("Thread not found"); return X(t); } getThreads() { let e = /* @__PURE__ */ new Map(); return this.threadsYMap.forEach((t, n) => { t instanceof C.Map && e.set(n, X(t)); }), e; } subscribe(e) { let t = () => { e(this.getThreads()); }; return this.threadsYMap.observeDeep(t), () => { this.threadsYMap.unobserveDeep(t); }; } }, Q = class extends Z { BASE_URL; headers; constructor(e, t, n, r) { super(n, r), this.BASE_URL = e, this.headers = t; } doRequest = async (e, t, n) => { let r = await fetch(`${this.BASE_URL}${e}`, { method: t, body: JSON.stringify(n), headers: { "Content-Type": "application/json", ...this.headers } }); if (!r.ok) throw Error(`Failed to ${t} ${e}: ${r.statusText}`); return r.json(); }; addThreadToDocument = async (e) => { let { threadId: t, selection: n } = e, r = b.getState(e.editor.prosemirrorState), i = r ? { head: d(n.head, r.type, r.mapping), anchor: d(n.anchor, r.type, r.mapping) } : void 0; return this.doRequest(`/${t}/addToDocument`, "POST", { selection: { prosemirror: n, yjs: i } }); }; createThread = async (e) => this.doRequest("", "POST", e); addComment = (e) => { let { threadId: t, ...n } = e; return this.doRequest(`/${t}/comments`, "POST", n); }; updateComment = (e) => { let { threadId: t, commentId: n, ...r } = e; return this.doRequest(`/${t}/comments/${n}`, "PUT", r); }; deleteComment = (e) => { let { threadId: t, commentId: n, ...r } = e; return this.doRequest(`/${t}/comments/${n}?soft=${!!r.softDelete}`, "DELETE"); }; deleteThread = (e) => this.doRequest(`/${e.threadId}`, "DELETE"); resolveThread = (e) => this.doRequest(`/${e.threadId}/resolve`, "POST"); unresolveThread = (e) => this.doRequest(`/${e.threadId}/unresolve`, "POST"); addReaction = (e) => { let { threadId: t, commentId: n, ...r } = e; return this.doRequest(`/${t}/comments/${n}/reactions`, "POST", r); }; deleteReaction = (e) => this.doRequest(`/${e.threadId}/comments/${e.commentId}/reactions/${e.emoji}`, "DELETE"); }, te = class extends Z { userId; constructor(e, t, n) { super(t, n), this.userId = e; } transact = (e) => async (t) => this.threadsYMap.doc.transact(() => e(t)); createThread = this.transact((e) => { if (!this.auth.canCreateThread()) throw Error("Not authorized"); let t = /* @__PURE__ */ new Date(), n = { type: "comment", id: c(), userId: this.userId, createdAt: t, updatedAt: t, reactions: [], metadata: e.initialComment.metadata, body: e.initialComment.body }, r = { type: "thread", id: c(), createdAt: t, updatedAt: t, comments: [n], resolved: !1, metadata: e.metadata }; return this.threadsYMap.set(r.id, K(r)), r; }); addThreadToDocument = void 0; addComment = this.transact((e) => { let t = this.threadsYMap.get(e.threadId); if (!t) throw Error("Thread not found"); if (!this.auth.canAddComment(X(t))) throw Error("Not authorized"); let n = /* @__PURE__ */ new Date(), r = { type: "comment", id: c(), userId: this.userId, createdAt: n, updatedAt: n, deletedAt: void 0, reactions: [], metadata: e.comment.metadata, body: e.comment.body }; return t.get("comments").push([G(r)]), t.set("updatedAt", (/* @__PURE__ */ new Date()).getTime()), r; }); updateComment = this.transact((e) => { let t = this.threadsYMap.get(e.threadId); if (!t) throw Error("Thread not found"); let n = $(t.get("comments"), (t) => t.get("id") === e.commentId); if (n === -1) throw Error("Comment not found"); let r = t.get("comments").get(n); if (!this.auth.canUpdateComment(Y(r))) throw Error("Not authorized"); r.set("body", e.comment.body), r.set("updatedAt", (/* @__PURE__ */ new Date()).getTime()), r.set("metadata", e.comment.metadata); }); deleteComment = this.transact((e) => { let t = this.threadsYMap.get(e.threadId); if (!t) throw Error("Thread not found"); let n = $(t.get("comments"), (t) => t.get("id") === e.commentId); if (n === -1) throw Error("Comment not found"); let r = t.get("comments").get(n); if (!this.auth.canDeleteComment(Y(r))) throw Error("Not authorized"); if (r.get("deletedAt")) throw Error("Comment already deleted"); e.softDelete ? (r.set("deletedAt", (/* @__PURE__ */ new Date()).getTime()), r.set("body", void 0)) : t.get("comments").delete(n), t.get("comments").toArray().every((e) => e.get("deletedAt")) && (e.softDelete ? t.set("deletedAt", (/* @__PURE__ */ new Date()).getTime()) : this.threadsYMap.delete(e.threadId)), t.set("updatedAt", (/* @__PURE__ */ new Date()).getTime()); }); deleteThread = this.transact((e) => { if (!this.auth.canDeleteThread(X(this.threadsYMap.get(e.threadId)))) throw Error("Not authorized"); this.threadsYMap.delete(e.threadId); }); resolveThread = this.transact((e) => { let t = this.threadsYMap.get(e.threadId); if (!t) throw Error("Thread not found"); if (!this.auth.canResolveThread(X(t))) throw Error("Not authorized"); t.set("resolved", !0), t.set("resolvedUpdatedAt", (/* @__PURE__ */ new Date()).getTime()), t.set("resolvedBy", this.userId); }); unresolveThread = this.transact((e) => { let t = this.threadsYMap.get(e.threadId); if (!t) throw Error("Thread not found"); if (!this.auth.canUnresolveThread(X(t))) throw Error("Not authorized"); t.set("resolved", !1), t.set("resolvedUpdatedAt", (/* @__PURE__ */ new Date()).getTime()); }); addReaction = this.transact((e) => { let t = this.threadsYMap.get(e.threadId); if (!t) throw Error("Thread not found"); let n = $(t.get("comments"), (t) => t.get("id") === e.commentId); if (n === -1) throw Error("Comment not found"); let r = t.get("comments").get(n); if (!this.auth.canAddReaction(Y(r), e.emoji)) throw Error("Not authorized"); let i = /* @__PURE__ */ new Date(), a = `${this.userId}-${e.emoji}`, o = r.get("reactionsByUser"); if (!o.has(a)) { let t = new C.Map(); t.set("emoji", e.emoji), t.set("createdAt", i.getTime()), t.set("userId", this.userId), o.set(a, t); } }); deleteReaction = this.transact((e) => { let t = this.threadsYMap.get(e.threadId); if (!t) throw Error("Thread not found"); let n = $(t.get("comments"), (t) => t.get("id") === e.commentId); if (n === -1) throw Error("Comment not found"); let r = t.get("comments").get(n); if (!this.auth.canDeleteReaction(Y(r), e.emoji)) throw Error("Not authorized"); let i = `${this.userId}-${e.emoji}`; r.get("reactionsByUser").delete(i); }); }; function $(e, t) { for (let n = 0; n < e.length; n++) if (t(e.get(n))) return n; return -1; } //#endregion export { U as CollaborationExtension, L as ForkYDocExtension, Q as RESTYjsThreadStore, R as RelativePositionMappingExtension, V as SchemaMigration, P as YCursorExtension, F as YSyncExtension, I as YUndoExtension, te as YjsThreadStore, Z as YjsThreadStoreBase, E as _blocksToProsemirrorNode, T as _prosemirrorJSONToBlocks, A as blocksToYDoc, O as blocksToYXmlFragment, H as createYjsVersioningAdapter, w as findTypeInOtherYdoc, W as withCollaboration, k as yDocToBlocks, D as yXmlFragmentToBlocks }; //# sourceMappingURL=yjs.js.map