UNPKG

@blocknote/core

Version:

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

1,589 lines (1,588 loc) 137 kB
var Fe = Object.defineProperty; var Ve = (n, e, t) => e in n ? Fe(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t; var k = (n, e, t) => Ve(n, typeof e != "symbol" ? e + "" : e, t); import { Slice as F, Fragment as w, DOMSerializer as me, DOMParser as Ue, Node as ze } from "prosemirror-model"; import { ReplaceStep as Re, ReplaceAroundStep as W } from "prosemirror-transform"; import { n as q, i as V, g as E, b as Z, a as C, t as Ge, U, q as D, r as z, d as je, s as We, u as qe, v, w as B, x as ke, y as Ke, z as be, A as K } from "./blockToNode-BNoNIXU7.js"; import { B as ms, G as ks, C as bs, D as gs, N as Bs, E as ys, O as Cs, F as Ss, H as xs, I as Es, L as Ps, J as Ts, M as Ms, K as ws } from "./blockToNode-BNoNIXU7.js"; import { ak as G, al as Je, am as Ye, an as Qe, aj as P, a as Xe, ao as Ze, ap as et, a5 as tt, a8 as J, aq as ot, ar as nt, a6 as st, as as ee, a9 as ge, at as rt } from "./defaultBlocks-Caw1U1oV.js"; import { aw as Is, av as As, E as Ns, F as _s, r as Ls, N as Ds, a4 as Os, ad as $s, aG as Hs, ax as Fs, b as Vs, d as Us, e as zs, a0 as Rs, aM as Gs, au as js, c as Ws, f as qs, ag as Ks, ah as Js, aB as Ys, x as Qs, y as Xs, A as Zs, z as er, g as tr, h as or, T as nr, j as sr, k as rr, l as ir, n as ar, o as cr, q as lr, s as dr, w as ur, aC as pr, aH as fr, B as hr, C as mr, H as kr, I as br, J as gr, K as Br, aE as yr, aI as Cr, M as Sr, D as xr, G as Er, S as Pr, O as Tr, Q as Mr, W as wr, U as vr, _ as Ir, Z as Ar, a2 as Nr, Y as _r, X as Lr, R as Dr, $ as Or, m as $r, aN as Hr, aK as Fr, az as Vr, af as Ur, i as zr, ay as Rr, aD as Gr, ae as jr, a7 as Wr, t as qr, u as Kr, v as Jr, aJ as Yr, ai as Qr, aL as Xr, a1 as Zr, V as ei, p as ti, a3 as oi, aF as ni, L as si, aO as ri, P as ii, aA as ai } from "./defaultBlocks-Caw1U1oV.js"; import { j as it, k as Be, l as at, m as ct, n as lt, c as Y, F as dt, Y as ut, a as pt, b as ft, S as ht, B as ye, D as mt, L as kt, N as bt, P as gt, g as Bt, i as yt, H as Ct, h as St, e as xt, V as Et, d as Pt } from "./TrailingNode-F9hX_UlQ.js"; import { s as Tt, B as Mt } from "./BlockNoteSchema-dmbNkHA-.js"; import { C as li, b as di, c as ui, a as pi, g as fi, u as hi, w as mi } from "./BlockNoteSchema-dmbNkHA-.js"; import { Node as O, Extension as T, mergeAttributes as wt, Mark as te, extensions as N, isNodeSelection as vt, posToDOMRect as It, selectionToInsertionEnd as At, findParentNodeClosestToPos as Nt, getSchema as _t, createDocument as Lt, Editor as Dt } from "@tiptap/core"; import { E as Ce } from "./EventEmitter-CjSwpTbz.js"; import { Fragment as R, Slice as j } from "@tiptap/pm/model"; import { e as Ot } from "./en-njEqD7AG.js"; import { inputRules as $t, InputRule as Ht } from "@handlewithcare/prosemirror-inputrules"; import { keymap as Ft } from "@tiptap/pm/keymap"; import { c as Vt, o as Ut } from "./BlockNoteExtension-C2X7LW-V.js"; import { a as bi } from "./BlockNoteExtension-C2X7LW-V.js"; import { Gapcursor as zt } from "@tiptap/extensions/gap-cursor"; import { Link as Rt } from "@tiptap/extension-link"; import { Text as Gt } from "@tiptap/extension-text"; import { NodeSelection as $, TextSelection as S, Plugin as oe } from "prosemirror-state"; import { CellSelection as ne, TableMap as re } from "prosemirror-tables"; import { S as jt } from "./ShowSelection-B0ch3unP.js"; import Wt from "remark-gfm"; import qt from "remark-parse"; import Kt, { defaultHandlers as ie } from "remark-rehype"; import Jt from "rehype-stringify"; import { unified as Yt } from "unified"; import { TextSelection as Qt } from "@tiptap/pm/state"; function Xt(n, e) { const t = [ { tag: `[data-inline-content-type="${n.type}"]`, contentElement: (o) => { const s = o; return s.matches("[data-editable]") ? s : s.querySelector("[data-editable]") || s; } } ]; return e && t.push({ tag: "*", getAttrs(o) { if (typeof o == "string") return !1; const s = e == null ? void 0 : e(o); return s === void 0 ? !1 : s; } }), t; } function ls(n, e) { var o; const t = O.create({ name: n.type, inline: !0, group: "inline", draggable: (o = e.meta) == null ? void 0 : o.draggable, selectable: n.content === "styled", atom: n.content === "none", content: n.content === "styled" ? "inline*" : "", addAttributes() { return Ye(n.propSchema); }, addKeyboardShortcuts() { return Je(n); }, parseHTML() { return Xt( n, e.parse ); }, renderHTML({ node: s }) { const r = this.options.editor, i = e.render.call( { renderType: "dom", props: void 0 }, q( s, r.schema.inlineContentSchema, r.schema.styleSchema ), // TODO: fix cast () => { }, r ); return G( i, n.type, s.attrs, n.propSchema ); }, addNodeView() { return (s) => { const { node: r, getPos: i } = s, l = this.options.editor, a = e.render.call( { renderType: "nodeView", props: s }, q( r, l.schema.inlineContentSchema, l.schema.styleSchema ), // TODO: fix cast (c) => { const d = V([c], l.pmSchema), u = i(); u && l.transact( (p) => p.replaceWith(u, u + r.nodeSize, d) ); }, l ); return G( a, n.type, r.attrs, n.propSchema ); }; } }); return Qe( t, n.propSchema, { ...e, toExternalHTML: e.toExternalHTML, render(s, r, i) { const l = e.render( s, r, i ); return G( l, n.type, s.props, n.propSchema ); } } ); } function Zt(n, e, t, o = "before") { const s = typeof t == "string" ? t : t.id, r = E(n), i = e.map( (d) => Z(d, r) ), l = P(s, n.doc); if (!l) throw new Error(`Block with ID ${s} not found`); let a = l.posBeforeNode; return o === "after" && (a += l.node.nodeSize), n.step( new Re(a, a, new F(w.from(i), 0, 0)) ), i.map( (d) => C(d, r) ); } function Q(n) { if (!n || n.type.name !== "column") throw new Error("Invalid columnPos: does not point to column node."); const e = n.firstChild; if (!e) throw new Error("Invalid column: does not have child node."); const t = e.firstChild; if (!t) throw new Error("Invalid blockContainer: does not have child node."); return n.childCount === 1 && e.childCount === 1 && t.type.name === "paragraph" && t.content.content.length === 0; } function eo(n, e) { const t = n.doc.resolve(e), o = t.nodeAfter; if (!o || o.type.name !== "columnList") throw new Error( "Invalid columnListPos: does not point to columnList node." ); for (let s = o.childCount - 1; s >= 0; s--) { const r = n.doc.resolve(t.pos + 1).posAtIndex(s), l = n.doc.resolve(r).nodeAfter; if (!l || l.type.name !== "column") throw new Error("Invalid columnPos: does not point to column node."); Q(l) && n.delete(r, r + l.nodeSize); } } function H(n, e) { eo(n, e); const o = n.doc.resolve(e).nodeAfter; if (!o || o.type.name !== "columnList") throw new Error( "Invalid columnListPos: does not point to columnList node." ); if (o.childCount > 2) return; if (o.childCount < 2) throw new Error("Invalid columnList: contains fewer than two children."); const s = e + 1, i = n.doc.resolve(s).nodeAfter, l = e + o.nodeSize - 1, c = n.doc.resolve(l).nodeBefore; if (!i || !c) throw new Error("Invalid columnList: does not contain children."); const d = Q(i), u = Q(c); if (d && u) { n.delete(e, e + o.nodeSize); return; } if (d) { n.step( new W( // Replaces `columnList`. e, e + o.nodeSize, // Replaces with content of last `column`. l - c.nodeSize + 1, l - 1, // Doesn't append anything. F.empty, 0, !1 ) ); return; } if (u) { n.step( new W( // Replaces `columnList`. e, e + o.nodeSize, // Replaces with content of first `column`. s + 1, s + i.nodeSize - 1, // Doesn't append anything. F.empty, 0, !1 ) ); return; } } function ae(n, e, t) { const o = E(n), s = t.map( (u) => Z(u, o) ), r = new Set( e.map( (u) => typeof u == "string" ? u : u.id ) ), i = [], l = /* @__PURE__ */ new Set(), a = typeof e[0] == "string" ? e[0] : e[0].id; let c = 0; if (n.doc.descendants((u, p) => { if (r.size === 0) return !1; if (!u.type.isInGroup("bnBlock") || !r.has(u.attrs.id)) return !0; if (i.push(C(u, o)), r.delete(u.attrs.id), t.length > 0 && u.attrs.id === a) { const b = n.doc.nodeSize; n.insert(p, s); const g = n.doc.nodeSize; c += b - g; } const h = n.doc.nodeSize, f = n.doc.resolve(p - c); f.node().type.name === "column" ? l.add(f.before(-1)) : f.node().type.name === "columnList" && l.add(f.before()), f.node().type.name === "blockGroup" && f.node(f.depth - 1).type.name !== "doc" && f.node().childCount === 1 ? n.delete(f.before(), f.after()) : n.delete(p - c, p - c + u.nodeSize); const m = n.doc.nodeSize; return c += h - m, !1; }), r.size > 0) { const u = [...r].join(` `); throw Error( "Blocks with the following IDs could not be found in the editor: " + u ); } return l.forEach((u) => H(n, u)), { insertedBlocks: s.map( (u) => C(u, o) ), removedBlocks: i }; } function to(n, e, t, o, s) { let r; if (e) if (typeof e == "string") r = V([e], n.pmSchema, o); else if (Array.isArray(e)) r = V(e, n.pmSchema, o); else if (e.type === "tableContent") r = Ge(e, n.pmSchema); else throw new U(e.type); else throw new Error("blockContent is required"); const l = ((s == null ? void 0 : s.document) ?? document).createDocumentFragment(); for (const a of r) if (a.type.name !== "text" && n.schema.inlineContentSchema[a.type.name]) { const c = n.schema.inlineContentSpecs[a.type.name].implementation; if (c) { const d = q( a, n.schema.inlineContentSchema, n.schema.styleSchema ), u = c.render.call( { renderType: "dom", props: void 0 }, d, () => { }, n ); if (u) { if (l.appendChild(u.dom), u.contentDOM) { const p = t.serializeFragment( a.content, s ); u.contentDOM.dataset.editable = "", u.contentDOM.appendChild(p); } continue; } } } else if (a.type.name === "text") { let c = document.createTextNode( a.textContent ); for (const d of a.marks.toReversed()) if (d.type.name in n.schema.styleSpecs) { const u = n.schema.styleSpecs[d.type.name].implementation.render(d.attrs.stringValue, n); u.contentDOM.appendChild(c), c = u.dom; } else { const u = d.type.spec.toDOM(d, !0), p = me.renderSpec(document, u); p.contentDOM.appendChild(c), c = p.dom; } l.appendChild(c); } else { const c = t.serializeFragment( w.from([a]), s ); l.appendChild(c); } return l; } function oo(n, e, t, o) { var u, p, h, f, m; const s = n.pmSchema.nodes.blockContainer, r = e.props || {}; for (const [b, g] of Object.entries( n.schema.blockSchema[e.type].propSchema )) !(b in r) && g.default !== void 0 && (r[b] = g.default); const i = e.children || [], a = n.blockImplementations[e.type].implementation.render.call( { renderType: "dom", props: void 0 }, { ...e, props: r, children: i }, n ); if (a.contentDOM && e.content) { const b = to( n, e.content, // TODO t, e.type, o ); a.contentDOM.appendChild(b); } if (n.pmSchema.nodes[e.type].isInGroup("bnBlock")) { if (e.children && e.children.length > 0) { const b = Se( n, e.children, t, o ); (u = a.contentDOM) == null || u.append(b); } return a.dom; } const d = (h = (p = s.spec) == null ? void 0 : p.toDOM) == null ? void 0 : h.call( p, s.create({ id: e.id, ...r }) ); return (f = d.contentDOM) == null || f.appendChild(a.dom), e.children && e.children.length > 0 && ((m = d.contentDOM) == null || m.appendChild( xe(n, e.children, t, o) )), d.dom; } function Se(n, e, t, o) { const r = ((o == null ? void 0 : o.document) ?? document).createDocumentFragment(); for (const i of e) { const l = oo(n, i, t, o); r.appendChild(l); } return r; } const xe = (n, e, t, o) => { var l; const s = n.pmSchema.nodes.blockGroup, r = s.spec.toDOM(s.create({})), i = Se(n, e, t, o); return (l = r.contentDOM) == null || l.appendChild(i), r.dom; }, no = (n) => (n.querySelectorAll( '[data-content-type="numberedListItem"]' ).forEach((t) => { var s, r; const o = (r = (s = t.closest(".bn-block-outer")) == null ? void 0 : s.previousElementSibling) == null ? void 0 : r.querySelector( '[data-content-type="numberedListItem"]' ); if (!o) t.setAttribute( "data-index", t.getAttribute("data-start") || "1" ); else { const i = o.getAttribute("data-index"); t.setAttribute( "data-index", (parseInt(i || "0") + 1).toString() ); } }), n), so = (n) => (n.querySelectorAll( '[data-content-type="checkListItem"] input' ).forEach((t) => { t.disabled = !0; }), n), ro = (n) => (n.querySelectorAll( '.bn-toggle-wrapper[data-show-children="false"]' ).forEach((t) => { t.setAttribute("data-show-children", "true"); }), n), io = (n) => (n.querySelectorAll('[data-content-type="table"] table').forEach((t) => { t.setAttribute( "style", `--default-cell-min-width: ${Xe}px;` ), t.setAttribute("data-show-children", "true"); }), n), ao = (n) => (n.querySelectorAll('[data-content-type="table"] table').forEach((t) => { var r; const o = document.createElement("div"); o.className = "tableWrapper"; const s = document.createElement("div"); s.className = "tableWrapper-inner", o.appendChild(s), (r = t.parentElement) == null || r.appendChild(o), o.appendChild(t); }), n), co = (n) => (n.querySelectorAll( ".bn-inline-content:empty" ).forEach((t) => { const o = document.createElement("span"); o.className = "ProseMirror-trailingBreak", o.setAttribute("style", "display: inline-block;"), t.appendChild(o); }), n), lo = (n, e) => { const t = me.fromSchema(n), o = [ no, so, ro, io, ao, co ]; return { serializeBlocks: (s, r) => { let i = xe( e, s, t, r ); for (const l of o) i = l(i); return i.outerHTML; } }; }; function uo(n) { return n.transact((e) => { const t = D(e.doc, e.selection.anchor); if (e.selection instanceof ne) return { type: "cell", anchorBlockId: t.node.attrs.id, anchorCellOffset: e.selection.$anchorCell.pos - t.posBeforeNode, headCellOffset: e.selection.$headCell.pos - t.posBeforeNode }; if (e.selection instanceof $) return { type: "node", anchorBlockId: t.node.attrs.id }; { const o = D(e.doc, e.selection.head); return { type: "text", anchorBlockId: t.node.attrs.id, headBlockId: o.node.attrs.id, anchorOffset: e.selection.anchor - t.posBeforeNode, headOffset: e.selection.head - o.posBeforeNode }; } }); } function po(n, e) { var s, r; const t = (s = P(e.anchorBlockId, n.doc)) == null ? void 0 : s.posBeforeNode; if (t === void 0) throw new Error( `Could not find block with ID ${e.anchorBlockId} to update selection` ); let o; if (e.type === "cell") o = ne.create( n.doc, t + e.anchorCellOffset, t + e.headCellOffset ); else if (e.type === "node") o = $.create(n.doc, t + 1); else { const i = (r = P(e.headBlockId, n.doc)) == null ? void 0 : r.posBeforeNode; if (i === void 0) throw new Error( `Could not find block with ID ${e.headBlockId} to update selection` ); o = S.create( n.doc, t + e.anchorOffset, i + e.headOffset ); } n.setSelection(o); } function X(n) { return n.map((e) => e.type === "columnList" ? e.children.map((t) => X(t.children)).flat() : { ...e, children: X(e.children) }).flat(); } function Ee(n, e, t) { n.transact((o) => { var i; const s = ((i = n.getSelection()) == null ? void 0 : i.blocks) || [ n.getTextCursorPosition().block ], r = uo(n); n.removeBlocks(s), n.insertBlocks(X(s), e, t), po(o, r); }); } function Pe(n) { return !n || n.type !== "columnList"; } function Te(n, e, t) { let o, s; if (e ? e.children.length > 0 ? (o = e.children[e.children.length - 1], s = "after") : (o = e, s = "before") : t && (o = t, s = "before"), !o || !s) return; const r = n.getParentBlock(o); return Pe(r) ? { referenceBlock: o, placement: s } : Te( n, s === "after" ? o : n.getPrevBlock(o), r ); } function Me(n, e, t) { let o, s; if (e ? e.children.length > 0 ? (o = e.children[0], s = "before") : (o = e, s = "after") : t && (o = t, s = "after"), !o || !s) return; const r = n.getParentBlock(o); return Pe(r) ? { referenceBlock: o, placement: s } : Me( n, s === "before" ? o : n.getNextBlock(o), r ); } function fo(n) { n.transact(() => { const e = n.getSelection(), t = (e == null ? void 0 : e.blocks[0]) || n.getTextCursorPosition().block, o = Te( n, n.getPrevBlock(t), n.getParentBlock(t) ); o && Ee( n, o.referenceBlock, o.placement ); }); } function ho(n) { n.transact(() => { const e = n.getSelection(), t = (e == null ? void 0 : e.blocks[(e == null ? void 0 : e.blocks.length) - 1]) || n.getTextCursorPosition().block, o = Me( n, n.getNextBlock(t), n.getParentBlock(t) ); o && Ee( n, o.referenceBlock, o.placement ); }); } function mo(n, e, t) { const { $from: o, $to: s } = n.selection, r = o.blockRange( s, (f) => f.childCount > 0 && (f.type.name === "blockGroup" || f.type.name === "column") // change necessary to not look at first item child type ); if (!r) return !1; const i = r.startIndex; if (i === 0) return !1; const a = r.parent.child(i - 1); if (a.type !== e) return !1; const c = a.lastChild && a.lastChild.type === t, d = w.from(c ? e.create() : null), u = new F( w.from( e.create(null, w.from(t.create(null, d))) // change necessary to create "groupType" instead of parent.type ), c ? 3 : 1, 0 ), p = r.start, h = r.end; return n.step( new W( p - (c ? 3 : 1), h, p, h, u, 1, !0 ) ).scrollIntoView(), !0; } function we(n) { return n.transact((e) => mo( e, n.pmSchema.nodes.blockContainer, n.pmSchema.nodes.blockGroup )); } function ko(n) { n._tiptapEditor.commands.liftListItem("blockContainer"); } function bo(n) { return n.transact((e) => { const { bnBlock: t } = z(e); return e.doc.resolve(t.beforePos).nodeBefore !== null; }); } function go(n) { return n.transact((e) => { const { bnBlock: t } = z(e); return e.doc.resolve(t.beforePos).depth > 1; }); } function Bo(n, e) { const t = typeof e == "string" ? e : e.id, o = E(n), s = P(t, n); if (s) return C(s.node, o); } function yo(n, e) { const t = typeof e == "string" ? e : e.id, o = P(t, n), s = E(n); if (!o) return; const i = n.resolve(o.posBeforeNode).nodeBefore; if (i) return C(i, s); } function Co(n, e) { const t = typeof e == "string" ? e : e.id, o = P(t, n), s = E(n); if (!o) return; const i = n.resolve( o.posBeforeNode + o.node.nodeSize ).nodeAfter; if (i) return C(i, s); } function So(n, e) { const t = typeof e == "string" ? e : e.id, o = E(n), s = P(t, n); if (!s) return; const r = n.resolve(s.posBeforeNode), i = r.node(), l = r.node(-1), a = l.type.name !== "doc" ? i.type.name === "blockGroup" ? l : i : void 0; if (a) return C(a, o); } class xo { constructor(e) { this.editor = e; } /** * Gets a snapshot of all top-level (non-nested) blocks in the editor. * @returns A snapshot of all top-level (non-nested) blocks in the editor. */ get document() { return this.editor.transact((e) => je(e.doc, this.editor.pmSchema)); } /** * Gets a snapshot of an existing block from the editor. * @param blockIdentifier The identifier of an existing block that should be * retrieved. * @returns The block that matches the identifier, or `undefined` if no * matching block was found. */ getBlock(e) { return this.editor.transact((t) => Bo(t.doc, e)); } /** * Gets a snapshot of the previous sibling of an existing block from the * editor. * @param blockIdentifier The identifier of an existing block for which the * previous sibling should be retrieved. * @returns The previous sibling of the block that matches the identifier. * `undefined` if no matching block was found, or it's the first child/block * in the document. */ getPrevBlock(e) { return this.editor.transact((t) => yo(t.doc, e)); } /** * Gets a snapshot of the next sibling of an existing block from the editor. * @param blockIdentifier The identifier of an existing block for which the * next sibling should be retrieved. * @returns The next sibling of the block that matches the identifier. * `undefined` if no matching block was found, or it's the last child/block in * the document. */ getNextBlock(e) { return this.editor.transact((t) => Co(t.doc, e)); } /** * Gets a snapshot of the parent of an existing block from the editor. * @param blockIdentifier The identifier of an existing block for which the * parent should be retrieved. * @returns The parent of the block that matches the identifier. `undefined` * if no matching block was found, or the block isn't nested. */ getParentBlock(e) { return this.editor.transact( (t) => So(t.doc, e) ); } /** * Traverses all blocks in the editor depth-first, and executes a callback for each. * @param callback The callback to execute for each block. Returning `false` stops the traversal. * @param reverse Whether the blocks should be traversed in reverse order. */ forEachBlock(e, t = !1) { const o = this.document.slice(); t && o.reverse(); function s(r) { for (const i of r) { if (e(i) === !1) return !1; const l = t ? i.children.slice().reverse() : i.children; if (!s(l)) return !1; } return !0; } s(o); } /** * Inserts new blocks into the editor. If a block's `id` is undefined, BlockNote generates one automatically. Throws an * error if the reference block could not be found. * @param blocksToInsert An array of partial blocks that should be inserted. * @param referenceBlock An identifier for an existing block, at which the new blocks should be inserted. * @param placement Whether the blocks should be inserted just before, just after, or nested inside the * `referenceBlock`. */ insertBlocks(e, t, o = "before") { return this.editor.transact( (s) => Zt(s, e, t, o) ); } /** * Updates an existing block in the editor. Since updatedBlock is a PartialBlock object, some fields might not be * defined. These undefined fields are kept as-is from the existing block. Throws an error if the block to update could * not be found. * @param blockToUpdate The block that should be updated. * @param update A partial block which defines how the existing block should be changed. */ updateBlock(e, t) { return this.editor.transact((o) => Ze(o, e, t)); } /** * Removes existing blocks from the editor. Throws an error if any of the blocks could not be found. * @param blocksToRemove An array of identifiers for existing blocks that should be removed. */ removeBlocks(e) { return this.editor.transact( (t) => ae(t, e, []).removedBlocks ); } /** * Replaces existing blocks in the editor with new blocks. If the blocks that should be removed are not adjacent or * are at different nesting levels, `blocksToInsert` will be inserted at the position of the first block in * `blocksToRemove`. Throws an error if any of the blocks to remove could not be found. * @param blocksToRemove An array of blocks that should be replaced. * @param blocksToInsert An array of partial blocks to replace the old ones with. */ replaceBlocks(e, t) { return this.editor.transact( (o) => ae(o, e, t) ); } /** * Checks if the block containing the text cursor can be nested. */ canNestBlock() { return bo(this.editor); } /** * Nests the block containing the text cursor into the block above it. */ nestBlock() { we(this.editor); } /** * Checks if the block containing the text cursor is nested. */ canUnnestBlock() { return go(this.editor); } /** * Lifts the block containing the text cursor out of its parent. */ unnestBlock() { ko(this.editor); } /** * Moves the selected blocks up. If the previous block has children, moves * them to the end of its children. If there is no previous block, but the * current blocks share a common parent, moves them out of & before it. */ moveBlocksUp() { return fo(this.editor); } /** * Moves the selected blocks down. If the next block has children, moves * them to the start of its children. If there is no next block, but the * current blocks share a common parent, moves them out of & after it. */ moveBlocksDown() { return ho(this.editor); } } class Eo extends Ce { constructor(e) { super(), this.editor = e, e.on("create", () => { e._tiptapEditor.on( "update", ({ transaction: t, appendedTransactions: o }) => { this.emit("onChange", { editor: e, transaction: t, appendedTransactions: o }); } ), e._tiptapEditor.on("selectionUpdate", ({ transaction: t }) => { this.emit("onSelectionChange", { editor: e, transaction: t }); }), e._tiptapEditor.on("mount", () => { this.emit("onMount", { editor: e }); }), e._tiptapEditor.on("unmount", () => { this.emit("onUnmount", { editor: e }); }); }); } /** * Register a callback that will be called when the editor changes. */ onChange(e, t = !0) { const o = ({ transaction: s, appendedTransactions: r }) => { !t && ce(s) || e(this.editor, { getChanges() { return it( s, r ); } }); }; return this.on("onChange", o), () => { this.off("onChange", o); }; } /** * Register a callback that will be called when the selection changes. */ onSelectionChange(e, t = !1) { const o = (s) => { !t && ce(s.transaction) || e(this.editor); }; return this.on("onSelectionChange", o), () => { this.off("onSelectionChange", o); }; } /** * Register a callback that will be called when the editor is mounted. */ onMount(e) { return this.on("onMount", e), () => { this.off("onMount", e); }; } /** * Register a callback that will be called when the editor is unmounted. */ onUnmount(e) { return this.on("onUnmount", e), () => { this.off("onUnmount", e); }; } } function ce(n) { return !!n.getMeta("y-sync$"); } function Po(n) { return Array.prototype.indexOf.call(n.parentElement.childNodes, n); } function To(n) { return n.nodeType === 3 && !/\S/.test(n.nodeValue || ""); } function Mo(n) { n.querySelectorAll("li > ul, li > ol").forEach((e) => { const t = Po(e), o = e.parentElement, s = Array.from(o.childNodes).slice( t + 1 ); e.remove(), s.forEach((r) => { r.remove(); }), o.insertAdjacentElement("afterend", e), s.reverse().forEach((r) => { if (To(r)) return; const i = document.createElement("li"); i.append(r), e.insertAdjacentElement("afterend", i); }), o.childNodes.length === 0 && o.remove(); }); } function wo(n) { n.querySelectorAll("li + ul, li + ol").forEach((e) => { var r, i; const t = e.previousElementSibling, o = document.createElement("div"); t.insertAdjacentElement("afterend", o), o.append(t); const s = document.createElement("div"); for (s.setAttribute("data-node-type", "blockGroup"), o.append(s); ((r = o.nextElementSibling) == null ? void 0 : r.nodeName) === "UL" || ((i = o.nextElementSibling) == null ? void 0 : i.nodeName) === "OL"; ) s.append(o.nextElementSibling); }); } let le = null; function vo() { return le || (le = document.implementation.createHTMLDocument("title")); } function Io(n) { if (typeof n == "string") { const e = vo().createElement("div"); e.innerHTML = n, n = e; } return Mo(n), wo(n), n; } function ve(n, e) { const t = Io(n), s = Ue.fromSchema(e).parse(t, { topNode: e.nodes.blockGroup.create() }), r = []; for (let i = 0; i < s.childCount; i++) r.push(C(s.child(i), e)); return r; } function Ao(n, e) { const t = e.value ? e.value : "", o = {}; e.lang && (o["data-language"] = e.lang); let s = { type: "element", tagName: "code", properties: o, children: [{ type: "text", value: t }] }; return e.meta && (s.data = { meta: e.meta }), n.patch(e, s), s = n.applyData(e, s), s = { type: "element", tagName: "pre", properties: {}, children: [s] }, n.patch(e, s), s; } function No(n, e) { var r; const t = String((e == null ? void 0 : e.url) || ""), o = e != null && e.title ? String(e.title) : void 0; let s = { type: "element", tagName: "video", properties: { src: t, "data-name": o, "data-url": t, controls: !0 }, children: [] }; return (r = n.patch) == null || r.call(n, e, s), s = n.applyData ? n.applyData(e, s) : s, s; } function Ie(n) { return Yt().use(qt).use(Wt).use(Kt, { handlers: { ...ie, image: (t, o) => { const s = String((o == null ? void 0 : o.url) || ""); return et(s) ? No(t, o) : ie.image(t, o); }, code: Ao, blockquote: (t, o) => { const s = { type: "element", tagName: "blockquote", properties: {}, // The only difference from the original is that we don't wrap the children with line endings children: t.wrap(t.all(o), !1) }; return t.patch(o, s), t.applyData(o, s); } } }).use(Jt).processSync(n).value; } function _o(n, e) { const t = Ie(n); return ve(t, e); } class Lo { constructor(e) { this.editor = e; } /** * Exports blocks into a simplified HTML string. To better conform to HTML standards, children of blocks which aren't list * items are un-nested in the output HTML. * * @param blocks An array of blocks that should be serialized into HTML. * @returns The blocks, serialized as an HTML string. */ blocksToHTMLLossy(e = this.editor.document) { return Be( this.editor.pmSchema, this.editor ).exportBlocks(e, {}); } /** * Serializes blocks into an HTML string in the format that would normally be rendered by the editor. * * Use this method if you want to server-side render HTML (for example, a blog post that has been edited in BlockNote) * and serve it to users without loading the editor on the client (i.e.: displaying the blog post) * * @param blocks An array of blocks that should be serialized into HTML. * @returns The blocks, serialized as an HTML string. */ blocksToFullHTML(e = this.editor.document) { return lo( this.editor.pmSchema, this.editor ).serializeBlocks(e, {}); } /** * Parses blocks from an HTML string. Tries to create `Block` objects out of any HTML block-level elements, and * `InlineNode` objects from any HTML inline elements, though not all element types are recognized. If BlockNote * doesn't recognize an HTML element's tag, it will parse it as a paragraph or plain text. * @param html The HTML string to parse blocks from. * @returns The blocks parsed from the HTML string. */ tryParseHTMLToBlocks(e) { return ve(e, this.editor.pmSchema); } /** * Serializes blocks into a Markdown string. The output is simplified as Markdown does not support all features of * BlockNote - children of blocks which aren't list items are un-nested and certain styles are removed. * @param blocks An array of blocks that should be serialized into Markdown. * @returns The blocks, serialized as a Markdown string. */ blocksToMarkdownLossy(e = this.editor.document) { return at(e, this.editor.pmSchema, this.editor, {}); } /** * Creates a list of blocks from a Markdown string. Tries to create `Block` and `InlineNode` objects based on * Markdown syntax, though not all symbols are recognized. If BlockNote doesn't recognize a symbol, it will parse it * as text. * @param markdown The Markdown string to parse blocks from. * @returns The blocks parsed from the Markdown string. */ tryParseMarkdownToBlocks(e) { return _o(e, this.editor.pmSchema); } /** * Paste HTML into the editor. Defaults to converting HTML to BlockNote HTML. * @param html The HTML to paste. * @param raw Whether to paste the HTML as is, or to convert it to BlockNote HTML. */ pasteHTML(e, t = !1) { var s; let o = e; if (!t) { const r = this.tryParseHTMLToBlocks(e); o = this.blocksToFullHTML(r); } o && ((s = this.editor.prosemirrorView) == null || s.pasteHTML(o)); } /** * Paste text into the editor. Defaults to interpreting text as markdown. * @param text The text to paste. */ pasteText(e) { var t; return (t = this.editor.prosemirrorView) == null ? void 0 : t.pasteText(e); } /** * Paste markdown into the editor. * @param markdown The markdown to paste. */ pasteMarkdown(e) { const t = Ie(e); return this.pasteHTML(t); } } const se = [ "vscode-editor-data", "blocknote/html", "text/markdown", "text/html", "text/plain", "Files" ]; function Do(n, e) { if (!n.startsWith(".") || !e.startsWith(".")) throw new Error("The strings provided are not valid file extensions."); return n === e; } function Oo(n, e) { const t = n.split("/"), o = e.split("/"); if (t.length !== 2) throw new Error(`The string ${n} is not a valid MIME type.`); if (o.length !== 2) throw new Error(`The string ${e} is not a valid MIME type.`); return t[1] === "*" || o[1] === "*" ? t[0] === o[0] : (t[0] === "*" || o[0] === "*" || t[0] === o[0]) && t[1] === o[1]; } function de(n, e, t, o = "after") { let s; return Array.isArray(e.content) && e.content.length === 0 ? s = n.updateBlock(e, t).id : s = n.insertBlocks( [t], e, o )[0].id, s; } async function Ae(n, e) { var r; if (!e.uploadFile) { console.warn( "Attempted ot insert file, but uploadFile is not set in the BlockNote editor options" ); return; } const t = "dataTransfer" in n ? n.dataTransfer : n.clipboardData; if (t === null) return; let o = null; for (const i of se) if (t.types.includes(i)) { o = i; break; } if (o !== "Files") return; const s = t.items; if (s) { n.preventDefault(); for (let i = 0; i < s.length; i++) { let l = "file"; for (const c of Object.values(e.schema.blockSpecs)) for (const d of ((r = c.implementation.meta) == null ? void 0 : r.fileBlockAccept) || []) { const u = d.startsWith("."), p = s[i].getAsFile(); if (p && (!u && p.type && Oo(s[i].type, d) || u && Do( "." + p.name.split(".").pop(), d ))) { l = c.config.type; break; } } const a = s[i].getAsFile(); if (a) { const c = { type: l, props: { name: a.name } }; let d; if (n.type === "paste") { const h = e.getTextCursorPosition().block; d = de(e, h, c); } else if (n.type === "drop") { const h = { left: n.clientX, top: n.clientY }, f = e.prosemirrorView.posAtCoords(h); if (!f) return; d = e.transact((m) => { var y; const b = D(m.doc, f.pos), g = (y = e.domElement) == null ? void 0 : y.querySelector( `[data-id="${b.node.attrs.id}"]` ), x = g == null ? void 0 : g.getBoundingClientRect(); return de( e, e.getBlock(b.node.attrs.id), c, x && (x.top + x.bottom) / 2 > h.top ? "before" : "after" ); }); } else return; const u = await e.uploadFile(a, d), p = typeof u == "string" ? { props: { url: u } } : { ...u }; e.updateBlock(d, p); } } } } const $o = (n) => T.create({ name: "dropFile", addProseMirrorPlugins() { return [ new oe({ props: { handleDOMEvents: { drop(e, t) { if (!n.isEditable) return; let o = null; for (const s of se) if (t.dataTransfer.types.includes(s)) { o = s; break; } return o === null ? !0 : o === "Files" ? (Ae(t, n), !0) : !1; } } } }) ]; } }), Ho = /(^|\n) {0,3}#{1,6} {1,8}[^\n]{1,64}\r?\n\r?\n\s{0,32}\S/, Fo = /(_|__|\*|\*\*|~~|==|\+\+)(?!\s)(?:[^\s](?:.{0,62}[^\s])?|\S)(?=\1)/, Vo = /\[[^\]]{1,128}\]\(https?:\/\/\S{1,999}\)/, Uo = /(?:\s|^)`(?!\s)(?:[^\s`](?:[^`]{0,46}[^\s`])?|[^\s`])`([^\w]|$)/, zo = /(?:^|\n)\s{0,5}-\s{1}[^\n]+\n\s{0,15}-\s/, Ro = /(?:^|\n)\s{0,5}\d+\.\s{1}[^\n]+\n\s{0,15}\d+\.\s/, Go = /\n{2} {0,3}-{2,48}\n{2}/, jo = /(?:\n|^)(```|~~~|\$\$)(?!`|~)[^\s]{0,64} {0,64}[^\n]{0,64}\n[\s\S]{0,9999}?\s*\1 {0,64}(?:\n+|$)/, Wo = /(?:\n|^)(?!\s)\w[^\n]{0,64}\r?\n(-|=)\1{0,64}\n\n\s{0,64}(\w|$)/, qo = /(?:^|(\r?\n\r?\n))( {0,3}>[^\n]{1,333}\n){1,999}($|(\r?\n))/, Ko = /^\s*\|(.+\|)+\s*$/m, Jo = /^\s*\|(\s*[-:]+[-:]\s*\|)+\s*$/m, Yo = /^\s*\|(.+\|)+\s*$/m, Qo = (n) => Ho.test(n) || Fo.test(n) || Vo.test(n) || Uo.test(n) || zo.test(n) || Ro.test(n) || Go.test(n) || jo.test(n) || Wo.test(n) || qo.test(n) || Ko.test(n) || Jo.test(n) || Yo.test(n); async function Xo(n, e) { const { schema: t } = e.state; if (!n.clipboardData) return !1; const o = n.clipboardData.getData("text/plain"); if (!o) return !1; if (!t.nodes.codeBlock) return e.pasteText(o), !0; const s = n.clipboardData.getData("vscode-editor-data"), r = s ? JSON.parse(s) : void 0, i = r == null ? void 0 : r.mode; return i ? (e.pasteHTML( `<pre><code class="language-${i}">${o.replace( /\r\n?/g, ` ` )}</code></pre>` ), !0) : !1; } function Zo({ event: n, editor: e, prioritizeMarkdownOverHTML: t, plainTextAsMarkdown: o }) { var l; if (e.transact( (a) => a.selection.$from.parent.type.spec.code && a.selection.$to.parent.type.spec.code )) { const a = (l = n.clipboardData) == null ? void 0 : l.getData("text/plain"); if (a) return e.pasteText(a), !0; } let r; for (const a of se) if (n.clipboardData.types.includes(a)) { r = a; break; } if (!r) return !0; if (r === "vscode-editor-data") return Xo(n, e.prosemirrorView), !0; if (r === "Files") return Ae(n, e), !0; const i = n.clipboardData.getData(r); if (r === "blocknote/html") return e.pasteHTML(i, !0), !0; if (r === "text/markdown") return e.pasteMarkdown(i), !0; if (t) { const a = n.clipboardData.getData("text/plain"); if (Qo(a)) return e.pasteMarkdown(a), !0; } return r === "text/html" ? (e.pasteHTML(i), !0) : o ? (e.pasteMarkdown(i), !0) : (e.pasteText(i), !0); } const en = (n, e) => T.create({ name: "pasteFromClipboard", addProseMirrorPlugins() { return [ new oe({ props: { handleDOMEvents: { paste(t, o) { if (o.preventDefault(), !!n.isEditable) return e({ event: o, editor: n, defaultPasteHandler: ({ prioritizeMarkdownOverHTML: s = !0, plainTextAsMarkdown: r = !0 } = {}) => Zo({ event: o, editor: n, prioritizeMarkdownOverHTML: s, plainTextAsMarkdown: r }) }); } } } }) ]; } }); function tn(n, e, t) { var l; let o = !1; const s = n.state.selection instanceof ne; if (!s) { const a = n.state.doc.slice( n.state.selection.from, n.state.selection.to, !1 ).content, c = []; for (let d = 0; d < a.childCount; d++) c.push(a.child(d)); o = c.find( (d) => d.type.isInGroup("bnBlock") || d.type.name === "blockGroup" || d.type.spec.group === "blockContent" ) === void 0, o && (e = a); } let r; const i = Be( n.state.schema, t ); if (s) { ((l = e.firstChild) == null ? void 0 : l.type.name) === "table" && (e = e.firstChild.content); const a = We( e, t.schema.inlineContentSchema, t.schema.styleSchema ); r = `<table>${i.exportInlineContent( a, {} )}</table>`; } else if (o) { const a = qe( e, t.schema.inlineContentSchema, t.schema.styleSchema ); r = i.exportInlineContent(a, {}); } else { const a = lt(e); r = i.exportBlocks(a, {}); } return r; } function Ne(n, e) { "node" in n.state.selection && n.state.selection.node.type.spec.group === "blockContent" && e.transact( (i) => i.setSelection( new $(i.doc.resolve(n.state.selection.from - 1)) ) ); const t = n.serializeForClipboard( n.state.selection.content() ).dom.innerHTML, o = n.state.selection.content().content, s = tn( n, o, e ), r = ct(s); return { clipboardHTML: t, externalHTML: s, markdown: r }; } const ue = () => { const n = window.getSelection(); if (!n || n.isCollapsed) return !0; let e = n.focusNode; for (; e; ) { if (e instanceof HTMLElement && e.getAttribute("contenteditable") === "false") return !0; e = e.parentElement; } return !1; }, pe = (n, e, t) => { t.preventDefault(), t.clipboardData.clearData(); const { clipboardHTML: o, externalHTML: s, markdown: r } = Ne( e, n ); t.clipboardData.setData("blocknote/html", o), t.clipboardData.setData("text/html", s), t.clipboardData.setData("text/plain", r); }, on = (n) => T.create({ name: "copyToClipboard", addProseMirrorPlugins() { return [ new oe({ props: { handleDOMEvents: { copy(e, t) { return ue() || pe(n, e, t), !0; }, cut(e, t) { return ue() || (pe(n, e, t), e.editable && e.dispatch(e.state.tr.deleteSelection())), !0; }, // This is for the use-case in which only a block without content // is selected, e.g. an image block, and dragged (not using the // drag handle). dragstart(e, t) { if (!("node" in e.state.selection) || e.state.selection.node.type.spec.group !== "blockContent") return; n.transact( (i) => i.setSelection( new $( i.doc.resolve(e.state.selection.from - 1) ) ) ), t.preventDefault(), t.dataTransfer.clearData(); const { clipboardHTML: o, externalHTML: s, markdown: r } = Ne(e, n); return t.dataTransfer.setData("blocknote/html", o), t.dataTransfer.setData("text/html", s), t.dataTransfer.setData("text/plain", r), !0; } } } }) ]; } }), nn = T.create({ name: "blockBackgroundColor", addGlobalAttributes() { return [ { types: ["tableCell", "tableHeader"], attributes: { backgroundColor: tt() } } ]; } }), sn = O.create({ name: "hardBreak", inline: !0, group: "inline", selectable: !1, linebreakReplacement: !0, priority: 10, parseHTML() { return [{ tag: "br" }]; }, renderHTML({ HTMLAttributes: n }) { return ["br", wt(this.options.HTMLAttributes, n)]; }, renderText() { return ` `; } }), _e = (n, e) => { var l; const t = n.resolve(e), o = t.depth - 1, s = t.before(o), r = n.resolve(s).nodeAfter; return r ? (l = r.type.spec.group) != null && l.includes("bnBlock") ? v( n.resolve(s) ) : _e(n, s) : void 0; }, L = (n, e) => { const t = n.resolve(e), o = t.index(); if (o === 0) return; const s = t.posAtIndex(o - 1); return v( n.resolve(s) ); }, _ = (n, e) => { const t = n.resolve(e), o = t.index(); if (o === t.node().childCount - 1) return; const s = t.posAtIndex(o + 1); return v( n.resolve(s) ); }, Le = (n, e) => { for (; e.childContainer; ) { const t = e.childContainer.node, o = n.resolve(e.childContainer.beforePos + 1).posAtIndex(t.childCount - 1); e = v(n.resolve(o)); } return e; }, rn = (n, e) => n.isBlockContainer && n.blockContent.node.type.spec.content === "inline*" && n.blockContent.node.childCount > 0 && e.isBlockContainer && e.blockContent.node.type.spec.content === "inline*", an = (n, e, t, o) => { if (!o.isBlockContainer) throw new Error( `Attempted to merge block at position ${o.bnBlock.beforePos} into previous block at position ${t.bnBlock.beforePos}, but next block is not a block container` ); if (o.childContainer) { const s = n.doc.resolve( o.childContainer.beforePos + 1 ), r = n.doc.resolve( o.childContainer.afterPos - 1 ), i = s.blockRange(r); if (e) { const l = n.doc.resolve(o.bnBlock.beforePos); n.tr.lift(i, l.depth); } } if (e) { if (!t.isBlockContainer) throw new Error( `Attempted to merge block at position ${o.bnBlock.beforePos} into previous block at position ${t.bnBlock.beforePos}, but previous block is not a block container` ); e( n.tr.delete( t.blockContent.afterPos - 1, o.blockContent.beforePos + 1 ) ); } return !0; }, fe = (n) => ({ state: e, dispatch: t }) => { const o = e.doc.resolve(n), s = v(o), r = L( e.doc, s.bnBlock.beforePos ); if (!r) return !1; const i = Le( e.doc, r ); return rn(i, s) ? an(e, t, i, s) : !1; }, cn = T.create({ priority: 50, // TODO: The shortcuts need a refactor. Do we want to use a command priority // design as there is now, or clump the logic into a single function? addKeyboardShortcuts() { const n = () => this.editor.commands.first(({ chain: o, commands: s }) => [ // Deletes the selection if it's not empty. () => s.deleteSelection(), // Undoes an input rule if one was triggered in the last editor state change. () => s.undoInputRule(), // Reverts block content type to a paragraph if the selection is at the start of the block. () => s.command(({ state: r }) => { const i = B(r); if (!i.isBlockContainer) return !1; const l = r.selection.from === i.blockContent.beforePos + 1, a = i.blockContent.node.type.name === "paragraph"; return l && !a ? s.command( nt(i.bnBlock.beforePos, { type: "paragraph", props: {} }) ) : !1; }), // Removes a level of nesting if the block is indented if the selection is at the start of the block. () => s.command(({ state: r }) => { const i = B(r); if (!i.isBlockContainer) return !1; const { blockContent: l } = i; return r.selection.from === l.beforePos + 1 ? s.liftListItem("blockContainer") : !1; }), // Merges block with the previous one if it isn't indented, and the selection is at the start of the // block. The target block for merging must contain inline content. () => s.command(({ state: r }) => { const i = B(r); if (!i.isBlockContainer) return !1; const { bnBlock: l, blockContent: a } = i, c = L( r.doc, i.bnBlock.beforePos ); if (!c || !c.isBlockContainer || c.blockContent.node.type.spec.content !== "inline*") return !1; const d = r.selection.from === a.beforePos + 1, u = r.selection.empty, p = l.beforePos; return d && u ? o().command(fe(p)).scrollIntoView().run() : !1; }), // If the previous block is a columnList, moves the current block to // the end of the last column in it. () => s.command(({ state: r, tr: i, dispatch: l }) => { const a = B(r); if (!a.isBlockContainer) return !1; const c = L( r.doc, a.bnBlock.beforePos ); if (!c || c.isBlockContainer)