@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
559 lines (558 loc) • 19.8 kB
JavaScript
import { Ct as e, It as t, Pn as n, wn as r } from "./blocks-CzQLehlc.js";
import { n as i, t as a } from "./BlockNoteExtension-CbvDA0F7.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);
}
{
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) {
let n = e.pmSchema.nodeFromJSON(t);
return r(n);
}
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) {
let n = S(t, e.pmSchema);
return r(n);
}
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: e }) => (e.on("create", () => {
let t = e.pmSchema, r, i = t.nodes.doc.createAndFill;
t.nodes.doc.createAndFill = ((...e) => {
if (r) return r;
let n = i.apply(t.nodes.doc, e), a = JSON.parse(JSON.stringify(n.toJSON()));
return a.content[0].content[0].attrs.id = "initialBlockId", r = s.fromJSON(t, a), r;
});
let a = t.node;
t.node = function(e, t, r, i) {
let o = typeof e == "string" ? this.nodes[e] : e;
return o && n(this, o) && Array.isArray(r) && (r = r.map((e) => e?.isText && e.marks.length ? e.mark(o.allowedMarks(e.marks)) : e)), a.call(this, e, t, 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
}));
//#endregion
//#region src/yjs/extensions/ForkYDoc.ts
function L(e, t) {
e.transact((e) => e.setMeta(b, {
type: t,
doc: t.doc
}));
}
var R = 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()]), L(e, s), 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()
]), L(e, a), 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 });
}
};
}), z = 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 = t.binding.type, a = g(r.doc, r, i, t.binding.mapping);
if (a === null) throw Error("Position not found, cannot track positions");
return a + (n === "right" ? -1 : 1);
};
}
})), B = (e, t) => {
t(e), e.forEach((e) => {
e instanceof C.XmlElement && B(e, t);
});
}, V = [(t, n) => {
let r = /* @__PURE__ */ new Map();
return t.forEach((t) => {
t instanceof C.XmlElement && B(t, (t) => {
if (t.nodeName === "blockContainer" && t.hasAttribute("id")) {
let n = t.getAttribute("textColor"), i = t.getAttribute("backgroundColor"), a = {
textColor: n === e.textColor.default ? void 0 : n,
backgroundColor: i === e.backgroundColor.default ? void 0 : i
};
(a.textColor || a.backgroundColor) && r.set(t.getAttribute("id"), a);
}
});
}), r.size !== 0 && (n.doc.descendants((e, t) => {
if (e.type.name === "blockContainer" && r.has(e.attrs.id)) {
let i = n.doc.nodeAt(t + 1);
if (!i) throw Error("No element found");
n.setNodeMarkup(t + 1, void 0, {
...i.attrs,
...r.get(e.attrs.id)
});
}
}), !0);
}], H = a(({ options: e }) => {
let t = !1, n = new u("schemaMigration");
return {
key: "schemaMigration",
prosemirrorPlugins: [new l({
key: n,
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 V) t(e.fragment, a);
if (t = !0, a.docChanged) return a;
}
})]
};
});
//#endregion
//#region src/yjs/extensions/Versioning.ts
function U(e, t) {
let { fragment: n } = t;
function r() {
let t = e.getExtension(R);
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 W = a(({ options: e }) => ({
key: "collaboration",
blockNoteExtensions: [
j(),
R(e),
z(),
H(e),
P(e),
F(e),
I()
]
}));
function G(e) {
return {
...e,
extensions: [...e.extensions ?? [], W(e.collaboration)],
disableExtensions: ["history", ...e.disableExtensions ?? []],
initialContent: [{
type: "paragraph",
id: "initialBlockId"
}]
};
}
//#endregion
//#region src/yjs/comments/yjsHelpers.ts
function K(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 q(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) => K(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 J(e) {
return {
emoji: e.get("emoji"),
createdAt: new Date(e.get("createdAt")),
userId: e.get("userId")
};
}
function Y(e) {
return [...e.values()].map((e) => J(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 X(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: Y(e.get("reactionsByUser")),
metadata: e.get("metadata"),
body: e.get("body")
};
}
function Z(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) => X(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 Q = 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 Z(t);
}
getThreads() {
let e = /* @__PURE__ */ new Map();
return this.threadsYMap.forEach((t, n) => {
t instanceof C.Map && e.set(n, Z(t));
}), e;
}
subscribe(e) {
let t = () => {
e(this.getThreads());
};
return this.threadsYMap.observeDeep(t), () => {
this.threadsYMap.unobserveDeep(t);
};
}
}, te = class extends Q {
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)?.binding, 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");
}, ne = class extends Q {
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, q(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(Z(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([K(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(X(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(X(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(Z(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(Z(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(Z(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(X(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(X(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 { W as CollaborationExtension, R as ForkYDocExtension, te as RESTYjsThreadStore, z as RelativePositionMappingExtension, H as SchemaMigration, P as YCursorExtension, F as YSyncExtension, I as YUndoExtension, ne as YjsThreadStore, Q as YjsThreadStoreBase, E as _blocksToProsemirrorNode, T as _prosemirrorJSONToBlocks, A as blocksToYDoc, O as blocksToYXmlFragment, U as createYjsVersioningAdapter, w as findTypeInOtherYdoc, G as withCollaboration, k as yDocToBlocks, D as yXmlFragmentToBlocks };
//# sourceMappingURL=yjs.js.map