UNPKG

@blocknote/core

Version:

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

1,451 lines 72.8 kB
var Ee = Object.defineProperty; var Ie = (n, e, t) => e in n ? Ee(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t; var b = (n, e, t) => Ie(n, typeof e != "symbol" ? e + "" : e, t); import { Plugin as T, PluginKey as P, NodeSelection as ae, TextSelection as le, Selection as L } from "prosemirror-state"; import { combineTransactionSteps as Be, getMarkRange as Te, posToDOMRect as Pe, findChildren as X } from "@tiptap/core"; import Oe from "fast-deep-equal"; import { i as j, t as De, U as Ae, n as Me, g as N, a as R, c as ce, m as Le, e as de, f as Ne, h as Re, j as Ve, k as He, l as Fe, o as q, p as W } from "./blockToNode-BNoNIXU7.js"; import { ai as ue, a2 as G, aj as he, $ as $e, a1 as J } from "./defaultBlocks-DvCGYzqu.js"; import { c as k, a as H } from "./BlockNoteExtension-C2X7LW-V.js"; import { yCursorPlugin as Ue, defaultSelectionBuilder as _e, ySyncPlugin as ze, redoCommand as Ke, undoCommand as Ye, yUndoPlugin as Xe, yUndoPluginKey as Q } from "y-prosemirror"; import * as I from "yjs"; import { PluginKey as me, Plugin as pe, TextSelection as je } from "@tiptap/pm/state"; import { dropCursor as qe } from "prosemirror-dropcursor"; import { redo as We, undo as Ge, history as Je } from "@tiptap/pm/history"; import { Decoration as A, DecorationSet as V } from "prosemirror-view"; import { v4 as Qe } from "uuid"; import { DOMParser as Ze, Slice as et } from "@tiptap/pm/model"; import { DOMSerializer as fe, Fragment as ge, Slice as tt } from "prosemirror-model"; import ot from "rehype-parse"; import nt from "rehype-remark"; import rt from "remark-gfm"; import st from "remark-stringify"; import { unified as it } from "unified"; import { fromDom as at } from "hast-util-from-dom"; import { visit as lt } from "unist-util-visit"; import { splitCell as ct, mergeCells as dt, deleteRow as ut, deleteColumn as ht, addRowBefore as mt, addRowAfter as pt, addColumnBefore as ft, addColumnAfter as gt, CellSelection as wt } from "prosemirror-tables"; function we(n) { const e = Array.from(n.classList).filter( (t) => !t.startsWith("bn-") ) || []; e.length > 0 ? n.className = e.join(" ") : n.removeAttribute("class"); } function ye(n, e, t, o) { var a; let r; if (e) if (typeof e == "string") r = j([e], n.pmSchema); else if (Array.isArray(e)) r = j(e, n.pmSchema); else if (e.type === "tableContent") r = De(e, n.pmSchema); else throw new Ae(e.type); else throw new Error("blockContent is required"); const i = ((o == null ? void 0 : o.document) ?? document).createDocumentFragment(); for (const c of r) if (c.type.name !== "text" && n.schema.inlineContentSchema[c.type.name]) { const l = n.schema.inlineContentSpecs[c.type.name].implementation; if (l) { const m = Me( c, n.schema.inlineContentSchema, n.schema.styleSchema ), h = l.toExternalHTML ? l.toExternalHTML( m, n ) : l.render.call( { renderType: "dom", props: void 0 }, m, () => { }, n ); if (h) { if (i.appendChild(h.dom), h.contentDOM) { const f = t.serializeFragment( c.content, o ); h.contentDOM.dataset.editable = "", h.contentDOM.appendChild(f); } continue; } } } else if (c.type.name === "text") { let l = document.createTextNode( c.textContent ); for (const m of c.marks.toReversed()) if (m.type.name in n.schema.styleSpecs) { const h = (n.schema.styleSpecs[m.type.name].implementation.toExternalHTML ?? n.schema.styleSpecs[m.type.name].implementation.render)(m.attrs.stringValue, n); h.contentDOM.appendChild(l), l = h.dom; } else { const h = m.type.spec.toDOM(m, !0), f = fe.renderSpec(document, h); f.contentDOM.appendChild(l), l = f.dom; } i.appendChild(l); } else { const l = t.serializeFragment( ge.from([c]), o ); i.appendChild(l); } return i.childNodes.length === 1 && ((a = i.firstChild) == null ? void 0 : a.nodeType) === 1 && we(i.firstChild), i; } function yt(n, e, t, o, r, s, i) { var p, w, y, E, O, _, z, K, Y; const a = (i == null ? void 0 : i.document) ?? document, c = e.pmSchema.nodes.blockContainer, l = t.props || {}; for (const [v, S] of Object.entries( e.schema.blockSchema[t.type].propSchema )) !(v in l) && S.default !== void 0 && (l[v] = S.default); const m = (w = (p = c.spec) == null ? void 0 : p.toDOM) == null ? void 0 : w.call( p, c.create({ id: t.id, ...l }) ), h = Array.from(m.dom.attributes), f = e.blockImplementations[t.type].implementation, u = ((y = f.toExternalHTML) == null ? void 0 : y.call( {}, { ...t, props: l }, e )) || f.render.call( {}, { ...t, props: l }, e ), g = a.createDocumentFragment(); if (u.dom.classList.contains("bn-block-content")) { const v = [ ...h, ...Array.from(u.dom.attributes) ].filter( (S) => S.name.startsWith("data") && S.name !== "data-content-type" && S.name !== "data-file-block" && S.name !== "data-node-view-wrapper" && S.name !== "data-node-type" && S.name !== "data-id" && S.name !== "data-editable" ); for (const S of v) u.dom.firstChild.setAttribute(S.name, S.value); we(u.dom.firstChild), g.append(...Array.from(u.dom.childNodes)); } else g.append(u.dom); if (u.contentDOM && t.content) { const v = ye( e, t.content, // TODO o, i ); u.contentDOM.appendChild(v); } let d; if (r.has(t.type) ? d = "OL" : s.has(t.type) && (d = "UL"), d) { if (((E = n.lastChild) == null ? void 0 : E.nodeName) !== d) { const v = a.createElement(d); d === "OL" && "start" in l && l.start && (l == null ? void 0 : l.start) !== 1 && v.setAttribute("start", l.start + ""), n.append(v); } n.lastChild.appendChild(g); } else n.append(g); if (t.children && t.children.length > 0) { const v = a.createDocumentFragment(); if (be( v, e, t.children, o, r, s, i ), ((O = n.lastChild) == null ? void 0 : O.nodeName) === "UL" || ((_ = n.lastChild) == null ? void 0 : _.nodeName) === "OL") for (; ((z = v.firstChild) == null ? void 0 : z.nodeName) === "UL" || ((K = v.firstChild) == null ? void 0 : K.nodeName) === "OL"; ) n.lastChild.lastChild.appendChild(v.firstChild); e.pmSchema.nodes[t.type].isInGroup("blockContent") ? n.append(v) : (Y = u.contentDOM) == null || Y.append(v); } } const be = (n, e, t, o, r, s, i) => { for (const a of t) yt( n, e, a, o, r, s, i ); }, bt = (n, e, t, o, r, s) => { const a = ((s == null ? void 0 : s.document) ?? document).createDocumentFragment(); return be( a, n, e, t, o, r, s ), a; }, ve = (n, e) => { const t = fe.fromSchema(n); return { exportBlocks: (o, r) => { const s = bt( e, o, t, /* @__PURE__ */ new Set(["numberedListItem"]), /* @__PURE__ */ new Set(["bulletListItem", "checkListItem", "toggleListItem"]), r ), i = document.createElement("div"); return i.append(s), i.innerHTML; }, exportInlineContent: (o, r) => { const s = ye( e, o, t, r ), i = document.createElement("div"); return i.append(s.cloneNode(!0)), i.innerHTML; } }; }; function vt(n, e) { if (e === 0) return; const t = n.resolve(e); for (let o = t.depth; o > 0; o--) { const r = t.node(o); if (ue(r)) return r.attrs.id; } } function kt(n) { return n.getMeta("paste") ? { type: "paste" } : n.getMeta("uiEvent") === "drop" ? { type: "drop" } : n.getMeta("history$") ? { type: n.getMeta("history$").redo ? "redo" : "undo" } : n.getMeta("y-sync$") ? n.getMeta("y-sync$").isUndoRedoOperation ? { type: "undo-redo" } : { type: "yjs-remote" } : { type: "local" }; } function Z(n) { const e = "__root__", t = {}, o = {}, r = N(n); return n.descendants((s, i) => { if (!ue(s)) return !0; const a = vt(n, i), c = a ?? e; o[c] || (o[c] = []); const l = R(s, r); return t[s.attrs.id] = { block: l, parentId: a }, o[c].push(s.attrs.id), !0; }), { byId: t, childrenByParent: o }; } function Ct(n, e) { const t = /* @__PURE__ */ new Set(); if (!n || !e) return t; const o = new Set(n), r = e.filter((d) => o.has(d)), s = n.filter( (d) => r.includes(d) ); if (s.length <= 1 || r.length <= 1) return t; const i = {}; for (let d = 0; d < s.length; d++) i[s[d]] = d; const a = r.map((d) => i[d]), c = a.length, l = [], m = [], h = new Array(c).fill(-1), f = (d, p) => { let w = 0, y = d.length; for (; w < y; ) { const E = w + y >>> 1; d[E] < p ? w = E + 1 : y = E; } return w; }; for (let d = 0; d < c; d++) { const p = a[d], w = f(l, p); w > 0 && (h[d] = m[w - 1]), w === l.length ? (l.push(p), m.push(d)) : (l[w] = p, m[w] = d); } const u = /* @__PURE__ */ new Set(); let g = m[m.length - 1] ?? -1; for (; g !== -1; ) u.add(g), g = h[g]; for (let d = 0; d < r.length; d++) u.has(d) || t.add(r[d]); return t; } function St(n, e = []) { const t = kt(n), o = Be(n.before, [ n, ...e ]), r = Z( o.before ), s = Z( o.doc ), i = [], a = /* @__PURE__ */ new Set(); Object.keys(s.byId).filter((u) => !(u in r.byId)).forEach((u) => { i.push({ type: "insert", block: s.byId[u].block, source: t, prevBlock: void 0 }), a.add(u); }), Object.keys(r.byId).filter((u) => !(u in s.byId)).forEach((u) => { i.push({ type: "delete", block: r.byId[u].block, source: t, prevBlock: void 0 }), a.add(u); }), Object.keys(s.byId).filter((u) => u in r.byId).forEach((u) => { var w, y; const g = r.byId[u], d = s.byId[u]; g.parentId !== d.parentId ? (i.push({ type: "move", block: d.block, prevBlock: g.block, source: t, prevParent: g.parentId ? (w = r.byId[g.parentId]) == null ? void 0 : w.block : void 0, currentParent: d.parentId ? (y = s.byId[d.parentId]) == null ? void 0 : y.block : void 0 }), a.add(u)) : Oe( { ...g.block, children: void 0 }, { ...d.block, children: void 0 } ) || (i.push({ type: "update", block: d.block, prevBlock: g.block, source: t }), a.add(u)); }); const c = r.childrenByParent, l = s.childrenByParent, m = "__root__", h = /* @__PURE__ */ new Set([ ...Object.keys(c), ...Object.keys(l) ]), f = /* @__PURE__ */ new Set(); return h.forEach((u) => { const g = Ct( c[u], l[u] ); g.size !== 0 && g.forEach((d) => { var E, O; const p = r.byId[d], w = s.byId[d]; !p || !w || p.parentId !== w.parentId || a.has(d) || (p.parentId ?? m) !== u || f.has(d) || (f.add(d), i.push({ type: "move", block: w.block, prevBlock: p.block, source: t, prevParent: p.parentId ? (E = r.byId[p.parentId]) == null ? void 0 : E.block : void 0, currentParent: w.parentId ? (O = s.byId[w.parentId]) == null ? void 0 : O.block : void 0 }), a.add(d)); }); }), i; } const po = k(() => { const n = []; return { key: "blockChange", prosemirrorPlugins: [ new T({ key: new P("blockChange"), filterTransaction: (e) => { let t; return n.reduce((o, r) => o === !1 ? o : r({ getChanges() { return t || (t = St(e), t); }, tr: e }) !== !1, !0); } }) ], /** * Subscribe to the block change events. */ subscribe(e) { return n.push(e), () => { n.splice( n.indexOf(e), 1 ); }; } }; }); function ee(n) { const e = n.charAt(0) === "#" ? n.substring(1, 7) : n, t = parseInt(e.substring(0, 2), 16), o = parseInt(e.substring(2, 4), 16), r = parseInt(e.substring(4, 6), 16), i = [t / 255, o / 255, r / 255].map((c) => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)); return 0.2126 * i[0] + 0.7152 * i[1] + 0.0722 * i[2] <= 0.179; } function xt(n) { const e = document.createElement("span"); e.classList.add("bn-collaboration-cursor__base"); const t = document.createElement("span"); t.setAttribute("contentedEditable", "false"), t.classList.add("bn-collaboration-cursor__caret"), t.setAttribute( "style", `background-color: ${n.color}; color: ${ee(n.color) ? "white" : "black"}` ); const o = document.createElement("span"); return o.classList.add("bn-collaboration-cursor__label"), o.setAttribute( "style", `background-color: ${n.color}; color: ${ee(n.color) ? "white" : "black"}` ), o.insertBefore(document.createTextNode(n.name), null), t.insertBefore(o, null), e.insertBefore(document.createTextNode("⁠"), null), e.insertBefore(t, null), e.insertBefore(document.createTextNode("⁠"), null), e; } const te = k( ({ options: n }) => { const e = /* @__PURE__ */ new Map(), t = n.provider && "awareness" in n.provider && typeof n.provider.awareness == "object" ? n.provider.awareness : void 0; return t && ("setLocalStateField" in t && typeof t.setLocalStateField == "function" && t.setLocalStateField("user", n.user), "on" in t && typeof t.on == "function" && n.showCursorLabels !== "always" && t.on( "change", ({ updated: o }) => { for (const r of o) { const s = e.get(r); s && (s.element.setAttribute("data-active", ""), s.hideTimeout && clearTimeout(s.hideTimeout), e.set(r, { element: s.element, hideTimeout: setTimeout(() => { s.element.removeAttribute("data-active"); }, 2e3) })); } } )), { key: "yCursor", prosemirrorPlugins: [ t ? Ue(t, { selectionBuilder: _e, cursorBuilder(o, r) { let s = e.get(r); if (!s) { const i = (n.renderCursor ?? xt)(o); n.showCursorLabels !== "always" && (i.addEventListener("mouseenter", () => { const a = e.get(r); a.element.setAttribute("data-active", ""), a.hideTimeout && (clearTimeout(a.hideTimeout), e.set(r, { element: a.element, hideTimeout: void 0 })); }), i.addEventListener("mouseleave", () => { const a = e.get(r); e.set(r, { element: a.element, hideTimeout: setTimeout(() => { a.element.removeAttribute("data-active"); }, 2e3) }); })), s = { element: i, hideTimeout: void 0 }, e.set(r, s); } return s.element; } }) : void 0 ].filter(Boolean), dependsOn: ["ySync"], updateUser(o) { t == null || t.setLocalStateField("user", o); } }; } ), F = k( ({ options: n }) => ({ key: "ySync", prosemirrorPlugins: [ze(n.fragment)], runsBefore: ["default"] }) ), $ = k(() => ({ key: "yUndo", prosemirrorPlugins: [Xe()], dependsOn: ["yCursor", "ySync"], undoCommand: Ye, redoCommand: Ke })); function Et(n, e) { const t = n.doc; if (n._item === null) { const o = Array.from(t.share.keys()).find( (r) => t.share.get(r) === n ); if (o == null) throw new Error("type does not exist in other ydoc"); return e.get(o, n.constructor); } else { const o = n._item, r = e.store.clients.get(o.id.client) ?? [], s = I.findIndexSS(r, o.id.clock); return r[s].content.type; } } const fo = k( ({ editor: n, options: e }) => { let t; const o = H({ isForked: !1 }); return { key: "yForkDoc", store: o, /** * Fork the Y.js document from syncing to the remote, * allowing modifications to the document without affecting the remote. * These changes can later be rolled back or applied to the remote. */ fork() { if (t) return; const r = e.fragment; if (!r) throw new Error("No fragment to fork from"); const s = new I.Doc(); I.applyUpdate(s, I.encodeStateAsUpdate(r.doc)); const i = Et(r, s); t = { undoStack: Q.getState(n.prosemirrorState).undoManager.undoStack, originalFragment: r, forkedFragment: i }, n.unregisterExtension([ $, te, F ]); const a = { ...e, fragment: i }; n.registerExtension([ F(a), // No need to register the cursor plugin again, it's a local fork $() ]), o.setState({ isForked: !0 }); }, /** * Resume syncing the Y.js document to the remote * If `keepChanges` is true, any changes that have been made to the forked document will be applied to the original document. * Otherwise, the original document will be restored and the changes will be discarded. */ merge({ keepChanges: r }) { if (!t) return; n.unregisterExtension(["ySync", "yCursor", "yUndo"]); const { originalFragment: s, forkedFragment: i, undoStack: a } = t; if (n.registerExtension([ F(e), te(e), $() ]), Q.getState( n.prosemirrorState ).undoManager.undoStack = a, r) { const c = I.encodeStateAsUpdate( i.doc, I.encodeStateVector(s.doc) ); I.applyUpdate(s.doc, c, n); } t = void 0, o.setState({ isForked: !1 }); } }; } ), ke = (n, e) => { e(n), n.forEach((t) => { t instanceof I.XmlElement && ke(t, e); }); }, It = (n, e) => { const t = /* @__PURE__ */ new Map(); return n.forEach((o) => { o instanceof I.XmlElement && ke(o, (r) => { if (r.nodeName === "blockContainer" && r.hasAttribute("id")) { const s = r.getAttribute("textColor"), i = r.getAttribute("backgroundColor"), a = { textColor: s === G.textColor.default ? void 0 : s, backgroundColor: i === G.backgroundColor.default ? void 0 : i }; (a.textColor || a.backgroundColor) && t.set(r.getAttribute("id"), a); } }); }), t.size === 0 ? !1 : (e.doc.descendants((o, r) => { if (o.type.name === "blockContainer" && t.has(o.attrs.id)) { const s = e.doc.nodeAt(r + 1); if (!s) throw new Error("No element found"); e.setNodeMarkup(r + 1, void 0, { // preserve existing attributes ...s.attrs, // add the textColor and backgroundColor attributes ...t.get(o.attrs.id) }); } }), !0); }, Bt = [It], go = k( ({ options: n }) => { let e = !1; const t = new me("schemaMigration"); return { key: "schemaMigration", prosemirrorPlugins: [ new pe({ key: t, appendTransaction: (o, r, s) => { if (e || // If any of the transactions are not due to a yjs sync, we don't need to run the migration !o.some((a) => a.getMeta("y-sync$")) || // If none of the transactions result in a document change, we don't need to run the migration o.every((a) => !a.docChanged) || // If the fragment is still empty, we can't run the migration (since it has not yet been applied to the Y.Doc) !n.fragment.firstChild) return; const i = s.tr; for (const a of Bt) a(n.fragment, i); if (e = !0, !!i.docChanged) return i; } }) ] }; } ), wo = k( ({ editor: n, options: e }) => ({ key: "dropCursor", prosemirrorPlugins: [ (e.dropCursor ?? qe)({ width: 5, color: "#ddeeff", editor: n }) ] }) ), yo = k(({ editor: n }) => { const e = H(!1), t = () => n.transact((o) => { var s; if (o.selection.empty || o.selection instanceof ae && (o.selection.node.type.spec.content === "inline*" || ((s = o.selection.node.firstChild) == null ? void 0 : s.type.spec.content) === "inline*") || o.selection instanceof le && o.doc.textBetween(o.selection.from, o.selection.to).length === 0) return !1; let r = !1; return o.selection.content().content.descendants((i) => (i.type.spec.code && (r = !0), !r)), !r; }); return { key: "formattingToolbar", store: e, mount({ dom: o, signal: r }) { let s = !1; const i = n.onChange(() => { s || e.setState(t()); }), a = n.onSelectionChange(() => { s || e.setState(t()); }); o.addEventListener( "pointerdown", () => { s = !0, e.setState(!1); }, { signal: r } ), n.prosemirrorView.root.addEventListener( "pointerup", () => { s = !1, n.isFocused() && e.setState(t()); }, { signal: r, capture: !0 } ), o.addEventListener( "pointercancel", () => { s = !1; }, { signal: r, capture: !0 } ), r.addEventListener("abort", () => { i(), a(); }); } }; }), bo = k(() => ({ key: "history", prosemirrorPlugins: [Je()], undoCommand: Ge, redoCommand: We })), vo = k(({ editor: n }) => { function e(r) { let s = n.prosemirrorView.nodeDOM(r); for (; s && s.parentElement; ) { if (s.nodeName === "A") return s; s = s.parentElement; } return null; } function t(r, s) { return n.transact((i) => { const a = i.doc.resolve(r), c = a.marks().find((m) => m.type.name === s); if (!c) return; const l = Te(a, c.type); if (l) return { range: l, mark: c, get text() { return i.doc.textBetween(l.from, l.to); }, get position() { return Pe( n.prosemirrorView, l.from, l.to ).toJSON(); } }; }); } function o() { return n.transact((r) => { const s = r.selection; if (s.empty) return t(s.anchor, "link"); }); } return { key: "linkToolbar", getLinkAtSelection: o, getLinkElementAtPos: e, getMarkAtPos: t, getLinkAtElement(r) { return n.transact(() => { const s = n.prosemirrorView.posAtDOM(r, 0) + 1; return t(s, "link"); }); }, editLink(r, s, i = n.transact((a) => a.selection.anchor)) { n.transact((a) => { const c = N(a), { range: l } = t(i + 1, "link") || { range: { from: a.selection.from, to: a.selection.to } }; l && (a.insertText(s, l.from, l.to), a.addMark( l.from, l.from + s.length, c.mark("link", { href: r }) )); }), n.prosemirrorView.focus(); }, deleteLink(r = n.transact((s) => s.selection.anchor)) { n.transact((s) => { const i = N(s), { range: a } = t(r + 1, "link") || { range: { from: s.selection.from, to: s.selection.to } }; a && s.removeMark(a.from, a.to, i.marks.link).setMeta( "preventAutolink", !0 ); }), n.prosemirrorView.focus(); } }; }), ko = [ "http", "https", "ftp", "ftps", "mailto", "tel", "callto", "sms", "cid", "xmpp" ], Co = "https", Tt = new P("node-selection-keyboard"), So = k( () => ({ key: "nodeSelectionKeyboard", prosemirrorPlugins: [ new T({ key: Tt, props: { handleKeyDown: (n, e) => { if ("node" in n.state.selection) { if (e.ctrlKey || e.metaKey) return !1; if (e.key.length === 1) return e.preventDefault(), !0; if (e.key === "Enter" && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) { const t = n.state.tr; return n.dispatch( t.insert( n.state.tr.selection.$to.after(), n.state.schema.nodes.paragraph.createChecked() ).setSelection( new le( t.doc.resolve( n.state.tr.selection.$to.after() + 1 ) ) ) ), !0; } } return !1; } } }) ] }) ), Pt = new P("blocknote-placeholder"), xo = k( ({ editor: n, options: e }) => { const t = e.placeholders; return { key: "placeholder", prosemirrorPlugins: [ new T({ key: Pt, view: (o) => { const r = `placeholder-selector-${Qe()}`; o.dom.classList.add(r); const s = document.createElement("style"), i = n._tiptapEditor.options.injectNonce; i && s.setAttribute("nonce", i), o.root instanceof window.ShadowRoot ? o.root.append(s) : o.root.head.appendChild(s); const a = s.sheet, c = (l = "") => `.${r} .bn-block-content${l} .bn-inline-content:has(> .ProseMirror-trailingBreak:only-child):before`; try { const { default: l, emptyDocument: m, ...h } = t || {}; for (const [g, d] of Object.entries(h)) { const p = `[data-content-type="${g}"]`; a.insertRule( `${c(p)} { content: ${JSON.stringify( d )}; }` ); } const f = "[data-is-only-empty-block]", u = "[data-is-empty-and-focused]"; a.insertRule( `${c(f)} { content: ${JSON.stringify( m )}; }` ), a.insertRule( `${c(u)} { content: ${JSON.stringify( l )}; }` ); } catch (l) { console.warn( "Failed to insert placeholder CSS rule - this is likely due to the browser not supporting certain CSS pseudo-element selectors (:has, :only-child:, or :before)", l ); } return { destroy: () => { o.root instanceof window.ShadowRoot ? o.root.removeChild(s) : o.root.head.removeChild(s); } }; }, props: { decorations: (o) => { const { doc: r, selection: s } = o; if (!n.isEditable || !s.empty || s.$from.parent.type.spec.code) return; const i = []; o.doc.content.size === 6 && i.push( A.node(2, 4, { "data-is-only-empty-block": "true" }) ); const a = s.$anchor, c = a.parent; if (c.content.size === 0) { const l = a.before(); i.push( A.node(l, l + c.nodeSize, { "data-is-empty-and-focused": "true" }) ); } return V.create(r, i); } } }) ] }; } ), oe = new P("previous-blocks"), Ot = { // Numbered List Items index: "index", // Headings level: "level", // All Blocks type: "type", depth: "depth", "depth-change": "depth-change" }, Eo = k(() => { let n; return { key: "previousBlockType", prosemirrorPlugins: [ new T({ key: oe, view(e) { return { update: async (t, o) => { var r; ((r = this.key) == null ? void 0 : r.getState(t.state).updatedBlocks.size) > 0 && (n = setTimeout(() => { t.dispatch( t.state.tr.setMeta(oe, { clearUpdate: !0 }) ); }, 0)); }, destroy: () => { n && clearTimeout(n); } }; }, state: { init() { return { // Block attributes, by block ID, from just before the previous transaction. prevTransactionOldBlockAttrs: {}, // Block attributes, by block ID, from just before the current transaction. currentTransactionOldBlockAttrs: {}, // Set of IDs of blocks whose attributes changed from the current transaction. updatedBlocks: /* @__PURE__ */ new Set() }; }, apply(e, t, o, r) { if (t.currentTransactionOldBlockAttrs = {}, t.updatedBlocks.clear(), !e.docChanged || o.doc.eq(r.doc)) return t; const s = {}, i = X( o.doc, (l) => l.attrs.id ), a = new Map( i.map((l) => [l.node.attrs.id, l]) ), c = X( r.doc, (l) => l.attrs.id ); for (const l of c) { const m = a.get(l.node.attrs.id), h = m == null ? void 0 : m.node.firstChild, f = l.node.firstChild; if (m && h && f) { const u = { index: f.attrs.index, level: f.attrs.level, type: f.type.name, depth: r.doc.resolve(l.pos).depth }, g = { index: h.attrs.index, level: h.attrs.level, type: h.type.name, depth: o.doc.resolve(m.pos).depth }; s[l.node.attrs.id] = g, t.currentTransactionOldBlockAttrs[l.node.attrs.id] = g, JSON.stringify(g) !== JSON.stringify(u) && (g["depth-change"] = g.depth - u.depth, t.updatedBlocks.add(l.node.attrs.id)); } } return t.prevTransactionOldBlockAttrs = s, t; } }, props: { decorations(e) { const t = this.getState(e); if (t.updatedBlocks.size === 0) return; const o = []; return e.doc.descendants((r, s) => { if (!r.attrs.id || !t.updatedBlocks.has(r.attrs.id)) return; const i = t.currentTransactionOldBlockAttrs[r.attrs.id], a = {}; for (const [l, m] of Object.entries(i)) a["data-prev-" + Ot[l]] = m || "none"; const c = A.node(s, s + r.nodeSize, { ...a }); o.push(c); }), V.create(e.doc, o); } } }) ] }; }); function Ce(n, e) { var t, o; for (; n && n.parentElement && n.parentElement !== e.dom && ((t = n.getAttribute) == null ? void 0 : t.call(n, "data-node-type")) !== "blockContainer"; ) n = n.parentElement; if (((o = n.getAttribute) == null ? void 0 : o.call(n, "data-node-type")) === "blockContainer") return { node: n, id: n.getAttribute("data-id") }; } function Dt() { const n = (e) => { let t = e.children.length; for (let o = 0; o < t; o++) { const r = e.children[o]; if (r.type === "element" && (n(r), r.tagName === "u")) if (r.children.length > 0) { e.children.splice(o, 1, ...r.children); const s = r.children.length - 1; t += s, o += s; } else e.children.splice(o, 1), t--, o--; } }; return n; } function At() { const n = (e) => { var t; if (e.children && "length" in e.children && e.children.length) for (let o = e.children.length - 1; o >= 0; o--) { const r = e.children[o], s = o + 1 < e.children.length ? e.children[o + 1] : void 0; r.type === "element" && r.tagName === "input" && ((t = r.properties) == null ? void 0 : t.type) === "checkbox" && (s == null ? void 0 : s.type) === "element" && s.tagName === "p" ? (s.tagName = "span", s.children.splice( 0, 0, at(document.createTextNode(" ")) )) : n(r); } }; return n; } function Mt() { return (n) => { lt(n, "element", (e, t, o) => { var r, s, i, a; if (o && e.tagName === "video") { const c = ((r = e.properties) == null ? void 0 : r.src) || ((s = e.properties) == null ? void 0 : s["data-url"]) || "", l = ((i = e.properties) == null ? void 0 : i.title) || ((a = e.properties) == null ? void 0 : a["data-name"]) || ""; o.children[t] = { type: "text", value: `![${l}](${c})` }; } }); }; } function Se(n) { return it().use(ot, { fragment: !0 }).use(Mt).use(Dt).use(At).use(nt).use(rt).use(st, { handlers: { text: (t) => t.value } }).processSync(n).value; } function Io(n, e, t, o) { const s = ve(e, t).exportBlocks(n, o); return Se(s); } function Lt(n) { const e = []; return n.descendants((t) => { var r, s; const o = N(t); return t.type.name === "blockContainer" && ((r = t.firstChild) == null ? void 0 : r.type.name) === "blockGroup" ? !0 : t.type.name === "columnList" && t.childCount === 1 ? ((s = t.firstChild) == null || s.forEach((i) => { e.push(R(i, o)); }), !1) : t.type.isInGroup("bnBlock") ? (e.push(R(t, o)), !1) : !0; }), e; } class B extends L { constructor(t, o) { super(t, o); b(this, "nodes"); const r = t.node(); this.nodes = [], t.doc.nodesBetween(t.pos, o.pos, (s, i, a) => { if (a !== null && a.eq(r)) return this.nodes.push(s), !1; }); } static create(t, o, r = o) { return new B(t.resolve(o), t.resolve(r)); } content() { return new tt(ge.from(this.nodes), 0, 0); } eq(t) { if (!(t instanceof B) || this.nodes.length !== t.nodes.length || this.from !== t.from || this.to !== t.to) return !1; for (let o = 0; o < this.nodes.length; o++) if (!this.nodes[o].eq(t.nodes[o])) return !1; return !0; } map(t, o) { const r = o.mapResult(this.from), s = o.mapResult(this.to); return s.deleted ? L.near(t.resolve(r.pos)) : r.deleted ? L.near(t.resolve(s.pos)) : new B( t.resolve(r.pos), t.resolve(s.pos) ); } toJSON() { return { type: "multiple-node", anchor: this.anchor, head: this.head }; } } L.jsonID("multiple-node", B); let x; function Nt(n, e) { let t, o; const r = e.resolve(n.from).node().type.spec.group === "blockContent", s = e.resolve(n.to).node().type.spec.group === "blockContent", i = Math.min(n.$anchor.depth, n.$head.depth); if (r && s) { const a = n.$from.start(i - 1), c = n.$to.end(i - 1); t = e.resolve(a - 1).pos, o = e.resolve(c + 1).pos; } else t = n.from, o = n.to; return { from: t, to: o }; } function ne(n, e, t = e) { e === t && (t += n.state.doc.resolve(e + 1).node().nodeSize); const o = n.domAtPos(e).node.cloneNode(!0), r = n.domAtPos(e).node, s = (h, f) => Array.prototype.indexOf.call(h.children, f), i = s( r, // Expects from position to be just before the first selected block. n.domAtPos(e + 1).node.parentElement ), a = s( r, // Expects to position to be just after the last selected block. n.domAtPos(t - 1).node.parentElement ); for (let h = r.childElementCount - 1; h >= 0; h--) (h > a || h < i) && o.removeChild(o.children[h]); xe(n.root), x = o; const c = x.getElementsByTagName("iframe"); for (let h = 0; h < c.length; h++) { const f = c[h], u = f.parentElement; u && u.removeChild(f); } const m = n.dom.className.split(" ").filter( (h) => h !== "ProseMirror" && h !== "bn-root" && h !== "bn-editor" ).join(" "); x.className = x.className + " bn-drag-preview " + m, n.root instanceof ShadowRoot ? n.root.appendChild(x) : n.root.body.appendChild(x); } function xe(n) { x !== void 0 && (n instanceof ShadowRoot ? n.removeChild(x) : n.body.removeChild(x), x = void 0); } function Rt(n, e, t) { if (!n.dataTransfer || t.headless) return; const o = t.prosemirrorView, r = he(e.id, o.state.doc); if (!r) throw new Error(`Block with ID ${e.id} not found`); const s = r.posBeforeNode; if (s != null) { const i = o.state.selection, a = o.state.doc, { from: c, to: l } = Nt(i, a), m = c <= s && s < l, h = i.$anchor.node() !== i.$head.node() || i instanceof B; m && h ? (o.dispatch( o.state.tr.setSelection(B.create(a, c, l)) ), ne(o, c, l)) : (o.dispatch( o.state.tr.setSelection(ae.create(o.state.doc, s)) ), ne(o, s)); const f = o.state.selection.content(), u = t.pmSchema, g = o.serializeForClipboard(f).dom.innerHTML, d = ve(u, t), p = Lt(f.content), w = d.exportBlocks(p, {}), y = Se(w); n.dataTransfer.clearData(), n.dataTransfer.setData("blocknote/html", g), n.dataTransfer.setData("text/html", w), n.dataTransfer.setData("text/plain", y), n.dataTransfer.effectAllowed = "move", n.dataTransfer.setDragImage(x, 0, 0); } } const re = 250; function U(n, e, t = !0) { const o = n.root.elementsFromPoint(e.left, e.top); for (const r of o) if (n.dom.contains(r)) return t && r.closest("[data-node-type=columnList]") ? U( n, { // TODO can we do better than this? left: e.left + 50, // bit hacky, but if we're inside a column, offset x position to right to account for the width of sidemenu itself top: e.top }, !1 ) : Ce(r, n); } function Vt(n, e) { if (!e.dom.firstChild) return; const t = e.dom.firstChild.getBoundingClientRect(), o = { // Clamps the x position to the editor's bounding box. left: Math.min( Math.max(t.left + 10, n.x), t.right - 10 ), top: n.y }, r = U(e, o); if (!r) return; const s = r.node.getBoundingClientRect(); return U( e, { left: s.right - 10, top: n.y }, !1 ); } class Ht { constructor(e, t, o) { b(this, "state"); b(this, "emitUpdate"); b(this, "mousePos"); b(this, "hoveredBlock"); b(this, "menuFrozen", !1); b(this, "isDragOrigin", !1); b(this, "updateState", (e) => { this.state = e, this.emitUpdate(this.state); }); b(this, "updateStateFromMousePos", () => { var o, r, s, i, a; if (this.menuFrozen || !this.mousePos) return; const e = this.findClosestEditorElement({ clientX: this.mousePos.x, clientY: this.mousePos.y }); if ((e == null ? void 0 : e.element) !== this.pmView.dom || e.distance > re) { (o = this.state) != null && o.show && (this.state.show = !1, this.updateState(this.state)); return; } const t = Vt(this.mousePos, this.pmView); if (!t || !this.editor.isEditable) { (r = this.state) != null && r.show && (this.state.show = !1, this.updateState(this.state)); return; } if (!((s = this.state) != null && s.show && ((i = this.hoveredBlock) != null && i.hasAttribute("data-id")) && ((a = this.hoveredBlock) == null ? void 0 : a.getAttribute("data-id")) === t.id) && (this.hoveredBlock = t.node, this.editor.isEditable)) { const c = t.node.getBoundingClientRect(), l = t.node.closest("[data-node-type=column]"); this.state = { show: !0, referencePos: new DOMRect( l ? ( // We take the first child as column elements have some default // padding. This is a little weird since this child element will // be the first block, but since it's always non-nested and we // only take the x coordinate, it's ok. l.firstElementChild.getBoundingClientRect().x ) : this.pmView.dom.firstChild.getBoundingClientRect().x, c.y, c.width, c.height ), block: this.editor.getBlock( this.hoveredBlock.getAttribute("data-id") ) }, this.updateState(this.state); } }); /** * If a block is being dragged, ProseMirror usually gets the context of what's * being dragged from `view.dragging`, which is automatically set when a * `dragstart` event fires in the editor. However, if the user tries to drag * and drop blocks between multiple editors, only the one in which the drag * began has that context, so we need to set it on the others manually. This * ensures that PM always drops the blocks in between other blocks, and not * inside them. * * After the `dragstart` event fires on the drag handle, it sets * `blocknote/html` data on the clipboard. This handler fires right after, * parsing the `blocknote/html` data into nodes and setting them on * `view.dragging`. * * Note: Setting `view.dragging` on `dragover` would be better as the user * could then drag between editors in different windows, but you can only * access `dataTransfer` contents on `dragstart` and `drop` events. */ b(this, "onDragStart", (e) => { var i; const t = (i = e.dataTransfer) == null ? void 0 : i.getData("blocknote/html"); if (!t || this.pmView.dragging) return; const o = document.createElement("div"); o.innerHTML = t; const s = Ze.fromSchema(this.pmView.state.schema).parse(o, { topNode: this.pmView.state.schema.nodes.blockGroup.create() }); this.pmView.dragging = { slice: new et(s.content, 0, 0), move: !0 }; }); /** * Finds the closest editor visually to the given coordinates */ b(this, "findClosestEditorElement", (e) => { const t = Array.from(this.pmView.root.querySelectorAll(".bn-editor")); if (t.length === 0) return null; let o = t[0], r = Number.MAX_VALUE; return t.forEach((s) => { const i = s.querySelector(".bn-block-group").getBoundingClientRect(), a = e.clientX < i.left ? i.left - e.clientX : e.clientX > i.right ? e.clientX - i.right : 0, c = e.clientY < i.top ? i.top - e.clientY : e.clientY > i.bottom ? e.clientY - i.bottom : 0, l = Math.sqrt( Math.pow(a, 2) + Math.pow(c, 2) ); l < r && (r = l, o = s); }), { element: o, distance: r }; }); /** * This dragover event handler listens at the document level, * and is trying to handle dragover events for all editors. * * It specifically is trying to handle the following cases: * - If the dragover event is within the bounds of any editor, then it does nothing * - If the dragover event is outside the bounds of any editor, but close enough (within DISTANCE_TO_CONSIDER_EDITOR_BOUNDS) to the closest editor, * then it dispatches a synthetic dragover event to the closest editor (which will trigger the drop-cursor to be shown on that editor) * - If the dragover event is outside the bounds of the current editor, then it will dispatch a synthetic dragleave event to the current editor * (which will trigger the drop-cursor to be removed from the current editor) * * The synthetic event is a necessary evil because we do not control prosemirror-dropcursor to be able to show the drop-cursor within the range we want */ b(this, "onDragOver", (e) => { if (e.synthetic) return; const t = this.getDragEventContext(e); if (!t || !t.isDropPoint) { this.closeDropCursor(); return; } t.isDropPoint && !t.isDropWithinEditorBounds && this.dispatchSyntheticEvent(e); }); /** * Closes the drop-cursor for the current editor */ b(this, "closeDropCursor", () => { const e = new Event("dragleave", { bubbles: !1 }); e.synthetic = !0, this.pmView.dom.dispatchEvent(e); }); /** * It is surprisingly difficult to determine the information we need to know about a drag event * * This function is trying to determine the following: * - Whether the current editor instance is the drop point * - Whether the current editor instance is the drag origin * - Whether the drop event is within the bounds of the current editor instance */ b(this, "getDragEventContext", (e) => { var c; const t = !((c = e.dataTransfer) != null && c.types.includes("blocknote/html")) && !!this.pmView.dragging, o = !!this.isDragOrigin, r = t || o, s = this.findClosestEditorElement(e); if (!s || s.distance > re) return; const i = s.element === this.pmView.dom, a = i && s.distance === 0; if (!(!i && !r)) return { isDropPoint: i, isDropWithinEditorBounds: a, isDragOrigin: r }; }); /** * The drop event handler listens at the document level, * and handles drop events for all editors. * * It specifically handles the following cases: * - If we are both the drag origin and drop point: * - Let normal drop handling take over * - If we are the drop point but not the drag origin: * - Collapse selection to prevent PM from deleting unrelated content * - If drop event is outside our editor bounds, dispatch synthetic drop event to our editor * - If we are the drag origin but not the drop point: * - Delete the dragged content from our editor after a delay */ b(this, "onDrop", (e) => { if (e.synthetic) return; const t = this.getDragEventContext(e); if (!t) { this.closeDropCursor(); return; } const { isDropPoint: o, isDropWithinEditorBounds: r, isDragOrigin: s } = t; if (!r && o && this.dispatchSyntheticEvent(e), o) { if (this.pmView.dragging) return; this.pmView.dispatch( this.pmView.state.tr.setSelection( je.create( this.pmView.state.tr.doc, this.pmView.state.tr.selection.anchor ) ) ); return; } else if (s) { setTimeout( () => this.pmView.dispatch(this.pmView.state.tr.deleteSelection()), 0 ); return; } }); b(this, "onDragEnd", (e) => { e.synthetic || (this.pmView.dragging = null); }); b(this, "onKeyDown", (e) => { var t; (t = this.state) != null && t.show && this.editor.isFocused() && (this.state.show = !1, this.emitUpdate(this.state)); }); b(this, "onMouseMove", (e) => { var s; if (this.menuFrozen) return; this.mousePos = { x: e.clientX, y: e.clientY }; const t = this.pmView.dom.getBoundingClientRect(), o = this.mousePos.x > t.left && this.mousePos.x < t.right && this.mousePos.y > t.top && this.mousePos.y < t.bottom, r = this.pmView.dom.parentElement; if ( // Cursor is within the editor area o && // An element is hovered e && e.target && // Element is outside the editor !(r === e.target || r.contains(e.target)) ) { (s = this.state) != null && s.show && (this.state.show = !1, this.emitUpdate(this.state)); return; } this.updateStateFromMousePos(); }); this.editor = e, this.pmView = t, this.emitUpdate = () => { if (!this.state) throw new Error("Attempting to update uninitialized side menu"); o(this.state); }, this.pmView.root.addEventListener( "dragstart", this.onDragStart ), this.pmView.root.addEventListener( "dragover", this.onDragOver ), this.pmView.root.addEventListener( "drop", this.onDrop, !0 ), this.pmView.root.addEventListener( "dragend", this.onDragEnd, !0 ), this.pmView.root.addEventListener( "mousemove", this.onMouseMove, !0 ), this.pmView.root.addEventListener( "keydown", this.onKeyDown, !0 ); } dispatchSyntheticEvent(e) { const t = new Event(e.type, e), o = this.pmView.dom.firstChild.getBoundingClientRect(); t.clientX = e.clientX, t.clientY = e.clientY, t.clientX = Math.min( Math.max(e.clientX, o.left), o.left + o.width ), t.clientY = Math.min( Math.max(e.clientY, o.top), o.top + o.height ), t.dataTransfer = e.dataTransfer, t.preventDefault = () => e.preventDefault(), t.synthetic = !0, this.pmView.dom.dispatchEvent(t); } // Needed in cases where the editor state updates without the mouse cursor // moving, as some state updates can require a side menu update. For example, // adding a button to the side menu which removes the block can cause the // block below to jump up into the place of the removed block when clicked, // allowing the user to click the button again without moving the cursor. This // would otherwise not update the side menu, and so clicking the button again // would attempt to remove the same block again, causing an error. update(e, t) { var r; !t.doc.eq(this.pmView.state.doc) && ((r = this.state) != null && r.show) && this.updateStateFromMousePos(); } destroy() { var e; (e = this.state) != null && e.show && (this.state.show = !1, this.emitUpdate(this.state)), this.pmView.root.removeEventListener( "mousemove", this.onMouseMove, !0 ), this.pmView.root.removeEventListener( "dragstart", this.onDragStart ), this.pmView.root.removeEventListener( "dragover", this.onDragOver ), this.pmView.root.removeEventListener( "drop", this.onDrop, !0 ), this.pmView.root.removeEventListener( "dragend", this.onDragEnd, !0 ), this.pmView.root.removeEventListener( "keydown"