@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
1 lines • 154 kB
Source Map (JSON)
{"version":3,"file":"y.cjs","names":[],"sources":["../src/y/extensions/YAttributionMarks.ts","../src/y/extensions/AttributionExtension.ts","../src/y/extensions/RelativePositionMapping.ts","../src/y/extensions/blockMatchNodes.ts","../src/y/extensions/YSync.ts","../src/y/utils.ts","../src/y/extensions/Suggestions.ts","../src/y/extensions/Versioning.ts","../src/y/extensions/YCursorPlugin.ts","../src/y/extensions/DiffVersioningExtension.ts","../src/y/extensions/index.ts","../src/y/comments/yjsHelpers.ts","../src/y/comments/YjsThreadStoreBase.ts","../src/y/comments/RESTYjsThreadStore.ts","../src/y/comments/YjsThreadStore.ts","../src/y/versioning/yhub.ts"],"sourcesContent":["import { Mark } from \"@tiptap/core\";\nimport { Mark as PMMark, MarkSpec } from \"prosemirror-model\";\nimport {\n createExtension,\n type ExtensionOptions,\n} from \"../../editor/BlockNoteExtension.js\";\nimport type { BlockNoteEditor } from \"../../editor/BlockNoteEditor.js\";\nimport { BLOCK_LEVEL_SUGGESTION_GROUP } from \"../../pm-nodes/suggestionMarks.js\";\nimport { NON_FORMATTING_MARK_GROUP } from \"../../schema/markGroups.js\";\nimport {\n fallbackColorForUserId,\n userColorPalette,\n userColorVarNames,\n} from \"../../user/index.js\";\n\n/**\n * Describes a suggestion mark to {@link GetAttributionMarkClassName}: whether it\n * wraps inline content or a whole block, and which kind of change it represents.\n * `modificationType: \"format\"` corresponds to the `y-attributed-format`\n * (modification) mark.\n */\nexport type AttributionMarkStyleInfo = {\n contentType: \"inline-content\" | \"block\";\n modificationType: \"insert\" | \"delete\" | \"format\";\n};\n\n/**\n * The class name(s) an app returns to style a suggestion mark. Either:\n * - a single string applied to *both* the mark content and its hover tooltip, or\n * - `{ content, tooltip }` to style the mark content and the tooltip\n * independently.\n */\nexport type AttributionMarkClassNames =\n | string\n | { content: string; tooltip: string };\n\n/**\n * Optional callback to override suggestion-mark styling. Given a mark's\n * {@link AttributionMarkStyleInfo}, it returns the class name(s) to apply (see\n * {@link AttributionMarkClassNames}). When a class is returned, the default\n * per-user color (the `--user-color-*` custom properties and the built-in\n * `.bn-suggestion-mark` / `.bn-suggestion-node` styling) is *not* applied to the\n * mark content, so the class fully controls its appearance — letting an app\n * color suggestions by change type (e.g. green insertions, red deletions)\n * instead of by author.\n */\nexport type GetAttributionMarkClassName = (\n info: AttributionMarkStyleInfo,\n) => AttributionMarkClassNames;\n\n/**\n * Resolve the {@link AttributionMarkClassNames} returned by a\n * {@link GetAttributionMarkClassName} callback to the class name for a single\n * target (the mark `content` or its `tooltip`).\n */\nexport const resolveAttributionMarkClassName = (\n result: AttributionMarkClassNames | undefined,\n target: \"content\" | \"tooltip\",\n): string | undefined =>\n result === undefined\n ? undefined\n : typeof result === \"string\"\n ? result\n : result[target];\n\n/**\n * Shared mark view for the attribution marks (insert / delete / modification).\n * It renders the marked content and tags the wrapper with the author(s) via\n * `data-*` attributes. The attribution tooltip shown on hover is handled\n * separately by the `AttributionExtension`, which reads those attributes\n * straight from the DOM — keeping this mark view purely presentational and the\n * tooltip state off of module scope.\n *\n * Author colors are *not* baked into the (deterministic) mark attrs. Instead the\n * wrapper sets the generic `--user-color-*` custom properties the Block.css rules\n * read, sourcing them from the author's *per-user* CSS variable via\n * `var(--user-color-<id>-*, <fallback>)`. Those per-user variables are populated\n * on the editor root by `AttributionExtension` once the user resolves, so a mark\n * shows the deterministic palette fallback immediately and recolors to the\n * author's own color purely through the CSS cascade — no decoration, no doc\n * transaction. See `userColors.ts`.\n */\nconst createAttributionMarkView =\n (\n type: \"insert\" | \"delete\" | \"modification\",\n options?: {\n editor?: BlockNoteEditor<any, any, any>;\n getAttributionMarkClassName?: GetAttributionMarkClassName;\n },\n ) =>\n ({ mark, inline }: { mark: PMMark; inline: boolean }) => {\n const editor = options?.editor;\n // `<ins>`/`<del>` are semantic elements. The modification mark has no\n // dedicated element, so it renders as a `<span>` inline or a `<div>` over a\n // block, matching its `parseDOM` rules.\n const tag =\n type === \"insert\"\n ? \"ins\"\n : type === \"delete\"\n ? \"del\"\n : inline\n ? \"span\"\n : \"div\";\n const dom = document.createElement(tag);\n\n Object.assign(dom.dataset, {\n userIds: JSON.stringify(mark.attrs[\"userIds\"]),\n inline: String(inline),\n });\n if (type === \"modification\") {\n dom.dataset[\"type\"] = \"modification\";\n dom.dataset[\"format\"] = JSON.stringify(mark.attrs[\"format\"]);\n }\n\n // Optional app-provided override: `y-attributed-format` is exposed as\n // `\"format\"`. When a class is returned it's applied to the content element\n // *instead of* the built-in `.bn-suggestion-mark` / `.bn-suggestion-node`\n // classes, and the per-user `--user-color-*` properties are omitted, so the\n // class fully controls the appearance (background, text color, etc.) with no\n // per-user color leaking through (see the type doc).\n const contentClassName = resolveAttributionMarkClassName(\n options?.getAttributionMarkClassName?.({\n contentType: inline ? \"inline-content\" : \"block\",\n modificationType: type === \"modification\" ? \"format\" : type,\n }),\n \"content\",\n );\n\n // The wrapper is always `display: contents` so it never generates a box of\n // its own — an inline `<ins>`/`<del>` around block/table content (e.g. a\n // suggestion spanning table cells) would otherwise break the normal layout.\n // Because a `display: contents` element paints nothing, the highlight is\n // applied to the inner content span (see `.bn-suggestion-mark` in Block.css)\n // using the `--user-color-*` custom properties, which cascade down from here.\n // They're sourced from the author's per-user variable (populated on the\n // editor root by `AttributionExtension`) with the deterministic palette as a\n // fallback, so a mark is colored before the user resolves and recolors via\n // the cascade afterward. When an override class owns the styling, no per-user\n // color is applied at all.\n const userIds = (mark.attrs[\"userIds\"] as string[] | null) ?? [];\n const firstId = userIds[0];\n const fallback = firstId\n ? fallbackColorForUserId(firstId)\n : userColorPalette[0];\n if (contentClassName) {\n dom.style.cssText = \"display: contents\";\n } else {\n const light = firstId\n ? `var(${userColorVarNames(firstId).light}, ${fallback.light})`\n : fallback.light;\n const dark = firstId\n ? `var(${userColorVarNames(firstId).dark}, ${fallback.dark})`\n : fallback.dark;\n dom.style.cssText =\n \"display: contents\" +\n `; --user-color-light: ${light}; --user-color-dark: ${dark}`;\n }\n\n const contentDOM = document.createElement(\"span\");\n if (inline) {\n // Inline content: the span is a real inline box that carries the highlight\n // (default path) or the app-provided override class.\n contentDOM.className =\n contentClassName ??\n (type === \"delete\"\n ? \"bn-suggestion-mark bn-suggestion-mark--delete\"\n : \"bn-suggestion-mark\");\n } else {\n // Block-level marks wrap block/table structure (e.g. <tr>/<td>/<p>). The\n // span must be `display: contents` so it doesn't inject an inline box into\n // the table layout (which triggers the browser's anonymous-table fixup and\n // breaks the table). Such a span has no box to paint a background on, so\n // the `.bn-suggestion-node` rule highlights its children (the wrapped\n // nodes) instead. An override class is applied to the same span, so it\n // should likewise target its children (e.g. `.my-class > *`).\n contentDOM.style.display = \"contents\";\n contentDOM.className =\n contentClassName ??\n (type === \"delete\"\n ? \"bn-suggestion-node bn-suggestion-node--delete\"\n : \"bn-suggestion-node\");\n if (type === \"delete\") {\n // A deleted block shows a localized \"Deleted\" badge via a `::before`\n // (see Block.css). The badge text is passed down as a CSS string token\n // in `--deleted-label` so the stylesheet stays locale-agnostic; the\n // wrapper is `display: contents` and can't paint a pseudo-element of its\n // own, so the rule renders the badge on the wrapped node instead, which\n // inherits this custom property.\n const label = editor?.dictionary.suggestion_changes.deleted;\n if (label) {\n contentDOM.style.setProperty(\n \"--deleted-label\",\n JSON.stringify(label),\n );\n }\n }\n }\n dom.appendChild(contentDOM);\n\n return {\n dom,\n contentDOM,\n };\n };\n\nexport const YAttributedInsertion = Mark.create<{\n getAttributionMarkClassName?: GetAttributionMarkClassName;\n}>({\n name: \"y-attributed-insert\",\n inclusive: false,\n excludes: \"\",\n // Two groups: `BLOCK_LEVEL_SUGGESTION_GROUP` lets the mark sit on block nodes\n // (see `suggestionMarks`), so a whole block can be marked as inserted in\n // suggestion mode; `NON_FORMATTING_MARK_GROUP` lets it annotate text inside\n // `\"plain\"` blocks (e.g. code blocks) — see `nonFormattingMarks`.\n group: `${BLOCK_LEVEL_SUGGESTION_GROUP} ${NON_FORMATTING_MARK_GROUP}`,\n addAttributes() {\n return {\n userIds: { default: null },\n };\n },\n addMarkView() {\n return createAttributionMarkView(\"insert\", {\n getAttributionMarkClassName: this.options.getAttributionMarkClassName,\n });\n },\n extendMarkSchema(extension) {\n if (extension.name !== this.name) {\n return {};\n }\n return {\n blocknoteIgnore: true,\n } satisfies MarkSpec;\n },\n});\n\nexport const YAttributedDeletion = Mark.create<{\n editor?: BlockNoteEditor<any, any, any>;\n getAttributionMarkClassName?: GetAttributionMarkClassName;\n}>({\n name: \"y-attributed-delete\",\n inclusive: false,\n excludes: \"\",\n group: `${BLOCK_LEVEL_SUGGESTION_GROUP} ${NON_FORMATTING_MARK_GROUP}`,\n addAttributes() {\n return {\n userIds: { default: null },\n };\n },\n addMarkView() {\n return createAttributionMarkView(\"delete\", {\n editor: this.options.editor,\n getAttributionMarkClassName: this.options.getAttributionMarkClassName,\n });\n },\n extendMarkSchema(extension) {\n if (extension.name !== this.name) {\n return {};\n }\n return {\n blocknoteIgnore: true,\n } satisfies MarkSpec;\n },\n});\n\nexport const YAttributedFormat = Mark.create<{\n getAttributionMarkClassName?: GetAttributionMarkClassName;\n}>({\n name: \"y-attributed-format\",\n inclusive: false,\n excludes: \"\",\n group: `${BLOCK_LEVEL_SUGGESTION_GROUP} ${NON_FORMATTING_MARK_GROUP}`,\n addAttributes() {\n return {\n userIds: { default: null },\n format: { default: null },\n };\n },\n addMarkView() {\n return createAttributionMarkView(\"modification\", {\n getAttributionMarkClassName: this.options.getAttributionMarkClassName,\n });\n },\n extendMarkSchema(extension) {\n if (extension.name !== this.name) {\n return {};\n }\n return {\n blocknoteIgnore: true,\n } satisfies MarkSpec;\n },\n});\n\n/**\n * Bundles the three `y-attributed-*` suggestion marks into a single BlockNote\n * extension, so they can be registered wherever they're actually needed (the\n * Yjs collaboration extension, or a test that exercises suggestions) instead of\n * living in the default schema. The marks opt into being allowed on block nodes\n * via their `blockLevelSuggestion` option — see `suggestionMarks`.\n */\nexport const YAttributionMarksExtension = createExtension(\n ({\n options,\n }: ExtensionOptions<\n { getAttributionMarkClassName?: GetAttributionMarkClassName } | undefined\n >) => ({\n key: \"yAttributionMarks\",\n tiptapExtensions: [\n YAttributedInsertion.configure({\n getAttributionMarkClassName: options?.getAttributionMarkClassName,\n }),\n YAttributedDeletion.configure({\n getAttributionMarkClassName: options?.getAttributionMarkClassName,\n }),\n YAttributedFormat.configure({\n getAttributionMarkClassName: options?.getAttributionMarkClassName,\n }),\n ],\n }),\n);\n","import { getChangedRanges } from \"@tiptap/core\";\nimport { Plugin, PluginKey, type Transaction } from \"prosemirror-state\";\nimport {\n createExtension,\n createStore,\n type ExtensionOptions,\n} from \"../../editor/BlockNoteExtension.js\";\nimport {\n colorsForUserIds,\n userColorVarNames,\n normalizeToUserStore,\n type UserStoreOrResolver,\n} from \"../../user/index.js\";\nimport {\n resolveAttributionMarkClassName,\n YAttributionMarksExtension,\n type GetAttributionMarkClassName,\n} from \"./YAttributionMarks.js\";\n\n/** The attribution marks, mapped to their `modificationType`. */\nconst ATTRIBUTION_MARK_TYPES = {\n \"y-attributed-insert\": \"insert\",\n \"y-attributed-delete\": \"delete\",\n \"y-attributed-format\": \"format\",\n} as const;\n\nconst ATTRIBUTION_LOAD_PLUGIN_KEY = new PluginKey(\"attributionLoadUsers\");\n\n/** Wrapper of an attribution mark; carries its author(s) in `data-user-ids`. */\nconst ATTRIBUTION_MARK_SELECTOR = \"[data-user-ids]\";\n\n/** Parse the JSON-encoded `data-user-ids` attribute; `[]` if missing/malformed. */\nconst parseUserIds = (userIdsJSON: string | undefined): string[] => {\n if (!userIdsJSON) {\n return [];\n }\n let userIds: unknown;\n try {\n userIds = JSON.parse(userIdsJSON);\n } catch {\n return [];\n }\n return Array.isArray(userIds) ? userIds.map(String) : [];\n};\n\n/**\n * The changed format keys from a modification mark's `data-format` (e.g.\n * `[\"bold\", \"italic\"]`); `[]` if missing/malformed or an empty change. This is\n * the raw change context — turning it into a localized label (e.g.\n * `\"Bold, Italic\"`) is a view concern owned by the React layer's\n * `formatChangeLabel`, so core stays i18n-agnostic here.\n */\nconst parseFormatKeys = (formatJSON: string | undefined): string[] => {\n if (!formatJSON) {\n return [];\n }\n let format: unknown;\n try {\n format = JSON.parse(formatJSON);\n } catch {\n return [];\n }\n if (typeof format !== \"object\" || format === null) {\n return [];\n }\n return Object.keys(format);\n};\n\n/**\n * The element with a real box to anchor the tooltip to. The wrapper is\n * `display: contents` (no box of its own), so use its content span child,\n * falling back further for block marks.\n */\nconst getReferenceElement = (wrapper: Element): Element => {\n const content = wrapper.firstElementChild ?? wrapper;\n const rect = content.getBoundingClientRect();\n if (rect.width || rect.height) {\n return content;\n }\n return content.firstElementChild ?? content;\n};\n\n/**\n * The box the tooltip anchors to. The wrapper is `display: contents` (no box of\n * its own), so use its content span child, falling back further for block marks.\n * Exported for the React controller's floating-ui `getBoundingClientRect`.\n */\nexport const getReferenceRect = (wrapper: Element): DOMRect =>\n getReferenceElement(wrapper).getBoundingClientRect();\n\n/**\n * The per-line client rects of the reference element, for floating-ui's\n * `inline()` middleware — it needs one rect per line to position off a\n * multi-line mark, and virtual elements don't get a default `getClientRects`.\n */\nexport const getReferenceClientRects = (wrapper: Element): DOMRectList =>\n getReferenceElement(wrapper).getClientRects();\n\n/**\n * State for the currently-hovered suggestion mark's tooltip (`undefined` when\n * none). The extension computes it; a React controller renders + positions it\n * (see `AttributionTooltipController`).\n */\nexport type AttributionTooltipState = {\n /** The wrapper element the tooltip anchors to (floating-ui reference). */\n anchor: HTMLElement;\n /** Per-user background color, resolved from the user store (default path). */\n color: string;\n /** The kind of change — `format` is the modification mark. */\n modificationType: \"insert\" | \"delete\" | \"format\";\n /** Whether the mark wraps inline content or a whole block. */\n contentType: \"inline-content\" | \"block\";\n /** Resolved usernames (falls back to raw ids), for custom renderers. */\n users: string[];\n /**\n * The changed format keys (e.g. `[\"bold\", \"italic\"]`), present only for\n * `format` marks. This is the raw change context — the view layer turns it\n * into a localized label via its `formatChangeLabel`.\n */\n format?: string[];\n /**\n * Class name from the `getAttributionMarkClassName` callback (override path).\n * When present, the tooltip applies this and skips the inline `color`.\n */\n className?: string;\n};\n\n/**\n * Resolves the attribution tooltip state for suggestion marks on hover (exposed\n * via a store for React), and loads each mark's author so its color/username\n * resolves. Marks nest, so a single delegated `mouseover` listener picks the\n * `closest` wrapper to the pointer — the innermost mark wins.\n */\nexport const AttributionExtension = createExtension(\n ({\n options,\n }: ExtensionOptions<\n | {\n /** Resolves authors to usernames. Optional; unresolved ids show raw. */\n resolveUsers?: UserStoreOrResolver;\n /** See {@link GetAttributionMarkClassName}. */\n getAttributionMarkClassName?: GetAttributionMarkClassName;\n }\n | undefined\n >) => {\n const userStore = normalizeToUserStore(options?.resolveUsers);\n const getAttributionMarkClassName = options?.getAttributionMarkClassName;\n\n const store = createStore<AttributionTooltipState | undefined>(undefined);\n\n // Load the authors of the attribution marks in `tr`'s changed ranges, so\n // their colors/usernames resolve (colors then flow to marks via `syncRootVars`).\n // `getChangedRanges` covers mark-only steps too — which suggestion mode adds\n // over existing text and `tr.changedRange()` would miss.\n const loadChangedUsers = (tr: Transaction) => {\n const ranges = getChangedRanges(tr);\n if (ranges.length === 0) {\n return;\n }\n // Most changes are local (often several steps in one small span), so scan a\n // single range spanning all of them rather than each range individually.\n let from = Infinity;\n let to = -Infinity;\n for (const { newRange } of ranges) {\n from = Math.min(from, newRange.from);\n to = Math.max(to, newRange.to);\n }\n\n const ids = new Set<string>();\n tr.doc.nodesBetween(from, to, (node) => {\n for (const mark of node.marks) {\n if (\n ATTRIBUTION_MARK_TYPES[\n mark.type.name as keyof typeof ATTRIBUTION_MARK_TYPES\n ]\n ) {\n const userIds = mark.attrs[\"userIds\"] as string[] | null;\n userIds?.forEach((id) => ids.add(id));\n }\n }\n return true;\n });\n if (ids.size > 0) {\n void userStore.loadUsers(Array.from(ids));\n }\n };\n\n return {\n key: \"attribution\",\n userStore,\n store,\n prosemirrorPlugins: [\n // Marks arrive in a transaction (suggestion mode, viewing suggestions,\n // version preview), so resolve their authors as the doc changes.\n new Plugin({\n key: ATTRIBUTION_LOAD_PLUGIN_KEY,\n state: {\n init: () => null,\n apply: (tr) => {\n if (tr.docChanged) {\n loadChangedUsers(tr);\n }\n return null;\n },\n },\n }),\n ],\n mount({ dom, root, signal }) {\n // Write each resolved author's color to the editor root as per-user CSS\n // variables (`--user-color-<id>-{light,dark}`) that the mark wrappers read\n // via `var(..., <fallback>)`, so the cascade recolors marks once a color\n // resolves. Color-less users have theirs removed so the fallback applies.\n const syncRootVars = () => {\n for (const [id, user] of userStore.store.state) {\n const { light, dark } = userColorVarNames(id);\n if (user.color && user.colorLight) {\n dom.style.setProperty(light, user.colorLight);\n dom.style.setProperty(dark, user.color);\n } else {\n dom.style.removeProperty(light);\n dom.style.removeProperty(dark);\n }\n }\n };\n\n // The wrapper currently showing a tooltip, so we don't re-emit on every\n // `mouseover` over the same mark.\n let activeAnchor: HTMLElement | undefined;\n\n // The mark's authors as usernames, falling back to the raw id when not\n // cached (`getUser` is cache-only; ids load on hover, see `onPointerOver`).\n const usersLabelArray = (userIdsJSON: string | undefined): string[] =>\n parseUserIds(userIdsJSON).map(\n (id) => userStore.getUser(id)?.username ?? id,\n );\n\n // A stable identity string for a wrapper (empty if unattributed), used to\n // (a) test whether a mark is attributed and (b) group adjacent marks with\n // the *same* attribution under one tooltip. It's an internal grouping key,\n // not the displayed text — that's composed in the view from `users` and\n // the format label — so it's built from raw `data-*` (ids + format keys)\n // and stays free of i18n/username resolution.\n const attributionIdentity = (wrapper: HTMLElement) => {\n const ids = parseUserIds(wrapper.dataset[\"userIds\"]);\n if (ids.length === 0) {\n return \"\";\n }\n const format = parseFormatKeys(wrapper.dataset[\"format\"]);\n return `${format.join(\",\")}:${ids.join(\",\")}`;\n };\n\n // Build the tooltip state from a wrapper's `data-*` attributes.\n const buildState = (anchor: HTMLElement): AttributionTooltipState => {\n const isModification = anchor.dataset[\"format\"] !== undefined;\n const modificationType: AttributionTooltipState[\"modificationType\"] =\n isModification\n ? \"format\"\n : anchor.tagName === \"INS\"\n ? \"insert\"\n : \"delete\";\n const contentType: AttributionTooltipState[\"contentType\"] =\n anchor.dataset[\"inline\"] === \"false\" ? \"block\" : \"inline-content\";\n\n return {\n anchor,\n // The tooltip is portaled outside the editor root, so it can't read\n // the cascaded per-user vars — resolve a concrete color from the store.\n color: colorsForUserIds(\n userStore,\n parseUserIds(anchor.dataset[\"userIds\"]),\n ).dark,\n modificationType,\n contentType,\n users: usersLabelArray(anchor.dataset[\"userIds\"]),\n format: isModification\n ? parseFormatKeys(anchor.dataset[\"format\"])\n : undefined,\n className: resolveAttributionMarkClassName(\n getAttributionMarkClassName?.({ contentType, modificationType }),\n \"tooltip\",\n ),\n };\n };\n\n const hideTooltip = () => {\n if (!activeAnchor) {\n return;\n }\n activeAnchor = undefined;\n store.setState(undefined);\n };\n\n // The innermost attributed mark at or above `el`, skipping unattributed\n // wrappers so an attributed ancestor still wins.\n const innermostAttributed = (\n el: Element | null,\n ): HTMLElement | undefined => {\n while (el) {\n const wrapper = el.closest<HTMLElement>(ATTRIBUTION_MARK_SELECTOR);\n if (!wrapper) {\n return undefined;\n }\n if (attributionIdentity(wrapper)) {\n return wrapper;\n }\n el = wrapper.parentElement;\n }\n return undefined;\n };\n\n const onPointerOver = (event: Event) => {\n const target = event.target instanceof Element ? event.target : null;\n const innermost = innermostAttributed(target);\n if (!innermost) {\n // Not over an attributed mark — drop the current tooltip.\n hideTooltip();\n return;\n }\n\n const identity = attributionIdentity(innermost);\n // Anchor on the outermost ancestor with the *same* attribution so one\n // tooltip covers the whole region; a differently-attributed ancestor\n // breaks the chain, and unattributed ones are climbed past.\n let anchor = innermost;\n let el: Element | null = innermost.parentElement;\n while (el) {\n const ancestor = el.closest<HTMLElement>(ATTRIBUTION_MARK_SELECTOR);\n if (!ancestor) {\n break;\n }\n const ancestorIdentity = attributionIdentity(ancestor);\n if (ancestorIdentity === identity) {\n anchor = ancestor;\n } else if (ancestorIdentity) {\n break;\n }\n el = ancestor.parentElement;\n }\n\n if (activeAnchor === anchor) {\n return;\n }\n\n activeAnchor = anchor;\n store.setState(buildState(anchor));\n\n // First hover renders raw ids (cache-only); load the authors and refresh\n // the resolved usernames once loaded, if this mark is still active.\n const ids = parseUserIds(anchor.dataset[\"userIds\"]);\n if (ids.length > 0) {\n void userStore.loadUsers(ids).then(() => {\n if (activeAnchor !== anchor) {\n return;\n }\n store.setState(buildState(anchor));\n });\n }\n };\n\n root.addEventListener(\"mouseover\", onPointerOver, { signal });\n signal.addEventListener(\"abort\", hideTooltip);\n\n // Seed from the cache, then keep the vars in sync as users resolve.\n syncRootVars();\n const unsubscribe = userStore.store.subscribe(syncRootVars);\n signal.addEventListener(\"abort\", unsubscribe);\n },\n\n // The `y-attributed-*` marks aren't in the default schema — register them\n // here so the block specs can allow them (collaboration-only).\n blockNoteExtensions: [\n YAttributionMarksExtension({\n getAttributionMarkClassName: options?.getAttributionMarkClassName,\n }),\n ],\n };\n },\n);\n","import { relativePositionStore, ySyncPluginKey } from \"@y/prosemirror\";\nimport { createExtension } from \"../../editor/BlockNoteExtension.js\";\n\nexport const RelativePositionMappingExtension = createExtension(\n ({ editor }) => {\n return {\n key: \"yPositionMapping\",\n mapPosition: (position: number, side: \"left\" | \"right\" = \"left\") => {\n const ySyncPluginState = ySyncPluginKey.getState(\n editor.prosemirrorState,\n );\n if (!ySyncPluginState?.ytype) {\n throw new Error(\"YSync plugin state not found\");\n }\n\n // 0 is a special case & always should map to itself\n if (position === 0) {\n return () => 0;\n }\n\n const posStore = relativePositionStore(\n editor.prosemirrorState.doc.resolve(\n position + (side === \"right\" ? 1 : -1),\n ),\n ySyncPluginState.ytype,\n ySyncPluginState.renderer,\n );\n\n return () => {\n const curYSyncPluginState = ySyncPluginKey.getState(\n editor.prosemirrorState,\n ) as typeof ySyncPluginState;\n const pos = posStore(\n editor.prosemirrorState.doc,\n curYSyncPluginState.ytype,\n curYSyncPluginState.renderer,\n );\n\n // This can happen if the element is garbage collected\n if (pos === null) {\n throw new Error(\"Position not found, cannot track positions\");\n }\n\n return pos + (side === \"right\" ? -1 : 1);\n };\n },\n } as const;\n },\n);\n","import { $prosemirrorDelta } from \"@y/prosemirror\";\nimport * as delta from \"lib0/delta\";\nimport * as schema from \"lib0/schema\";\n\n/**\n * Canonical name of a content delta's first block child (the child carried by an\n * insert op), or `null`. For a BlockNote `blockContainer` (content\n * `blockContent blockGroup?`) this is its block-content type (paragraph,\n * heading, image, ...).\n */\nconst firstChild = (\n d: schema.Unwrap<typeof $prosemirrorDelta>,\n): schema.Unwrap<typeof $prosemirrorDelta> | null => {\n for (const op of (d as any).children) {\n if (delta.$insertOp.check(op)) {\n for (const it of op.insert) {\n if (delta.$deltaAny.check(it)) {\n return it;\n }\n }\n }\n }\n return null;\n};\n\n/**\n * Whether a `blockContainer` delta carries a child `blockGroup` — i.e. the block\n * has nested children. A container's content is `blockContent blockGroup?`, so\n * this is what tells a leaf block apart from a parent.\n */\nconst hasBlockGroup = (d: schema.Unwrap<typeof $prosemirrorDelta>): boolean => {\n for (const op of (d as any).children) {\n if (delta.$insertOp.check(op)) {\n for (const it of op.insert) {\n if (delta.$deltaAny.check(it) && it.name === \"blockGroup\") {\n return true;\n }\n }\n }\n }\n return false;\n};\n\nfunction getTableDimensions(\n d: schema.Unwrap<typeof $prosemirrorDelta>,\n): { rows: number; cols: number } | null {\n if (d.name !== \"table\") {\n return null;\n }\n\n // Collect all rows with their cells' colspan/rowspan values.\n const rows: Array<Array<{ colspan: number; rowspan: number }>> = [];\n for (const op of (d as any).children) {\n if (delta.$insertOp.check(op)) {\n for (const tr of op.insert as Array<\n schema.Unwrap<typeof $prosemirrorDelta>\n >) {\n if (tr.name !== \"tableRow\") {\n return null;\n }\n const cells: Array<{ colspan: number; rowspan: number }> = [];\n for (const trOp of (tr as any).children) {\n if (delta.$insertOp.check(trOp)) {\n for (const td of trOp.insert as Array<\n schema.Unwrap<typeof $prosemirrorDelta>\n >) {\n if (td.name !== \"tableCell\" && td.name !== \"tableHeader\") {\n return null;\n }\n cells.push({\n colspan: Number(td.attrs.colspan) || 1,\n rowspan: Number(td.attrs.rowspan) || 1,\n });\n }\n }\n }\n rows.push(cells);\n }\n }\n }\n\n if (rows.length === 0) {\n return null;\n }\n\n // Build an occupancy grid to determine the true column count.\n // Each entry in `grid[r]` tracks which columns are already occupied\n // (by a cell from a previous row with rowspan > 1).\n const grid: boolean[][] = [];\n for (let r = 0; r < rows.length; r++) {\n if (!grid[r]) {\n grid[r] = [];\n }\n let col = 0;\n for (const cell of rows[r]) {\n // Skip columns already occupied by a rowspan from above.\n while (grid[r][col]) {\n col++;\n }\n // Mark all slots this cell occupies.\n for (let dr = 0; dr < cell.rowspan; dr++) {\n if (!grid[r + dr]) {\n grid[r + dr] = [];\n }\n for (let dc = 0; dc < cell.colspan; dc++) {\n grid[r + dr][col + dc] = true;\n }\n }\n col += cell.colspan;\n }\n }\n\n const numCols = Math.max(...grid.map((row) => row.length));\n return { rows: rows.length, cols: numCols };\n}\n\n/**\n * BlockNote's node-pairing policy for y-prosemirror's `matchNodes` option\n * (forwarded to `lib0/delta.diff`). This is the schema-specific bit that lives\n * in userland - the binding itself stays schema-agnostic.\n *\n * A `blockContainer` holds exactly one block content (`blockContent\n * blockGroup?`). Diffing a *type change* of that content as an in-place child\n * delete+insert would, under a suggestion, tombstone the old content next to the\n * new one => two block-contents in one container => schema-invalid. So we\n * declare a container's identity to be its first block-content child's type:\n * when that changes, the two containers are reported as *different*, the PM->Y\n * diff replaces the whole container, and the deleted + inserted containers sit\n * as siblings in the blockGroup (`blockGroupChild+` allows that). Each carries\n * the `y-attributed-*` node mark - which `blockContainer` already whitelists -\n * so no schema change and no storage transform are needed. A plain text edit\n * keeps the same first-child type => same identity => the diff descends and\n * merges as usual.\n *\n * @param a removed (old) node\n * @param b inserted (new) node\n * @returns whether `a` and `b` are the same node (diff in place) vs different (replace)\n */\nexport const blockMatchNodes = (\n a: schema.Unwrap<typeof $prosemirrorDelta>,\n b: schema.Unwrap<typeof $prosemirrorDelta>,\n): boolean => {\n if (a.name !== b.name) {\n return false;\n }\n\n if (a.name !== \"blockContainer\") {\n return true;\n }\n\n const childA = firstChild(a);\n const childB = firstChild(b);\n\n if (childA?.name !== childB?.name) {\n return false;\n }\n\n // A change in nesting is structural too: if one container gains or loses a\n // child `blockGroup`, diffing it in place would insert/delete the blockGroup as\n // a sibling of the block content inside a single container — schema-invalid.\n // Treat it as different so the whole container is replaced instead, same as a\n // content-type change. Keeps concurrent nesting merges (e.g. two users nesting\n // a block under the same parent) from producing a lopsided in-place result.\n if (hasBlockGroup(a) !== hasBlockGroup(b)) {\n return false;\n }\n\n if (childA?.name === \"table\" && childB?.name === \"table\") {\n const dimA = getTableDimensions(childA);\n const dimB = getTableDimensions(childB);\n if (\n dimA !== null &&\n dimB !== null &&\n dimA.rows !== dimB.rows &&\n dimA.cols !== dimB.cols\n ) {\n return false;\n }\n }\n\n return true;\n};\n","import { configureYProsemirror, syncPlugin } from \"@y/prosemirror\";\nimport {\n type ExtensionOptions,\n createExtension,\n} from \"../../editor/BlockNoteExtension.js\";\nimport { blockMatchNodes } from \"./blockMatchNodes.js\";\nimport { CollaborationOptions } from \"./index.js\";\n\n/**\n * Maps a Y attribution to BlockNote's `y-attributed-*` mark attrs.\n *\n * The mapper must be deterministic in `(format, attribution)` and emit attrs\n * that exactly match the declared mark schema in YAttributionMarks.ts. Any\n * mismatch causes the sync plugin to fire phantom reconcile dispatches in a\n * loop. See ATTRIBUTION.md in @y/prosemirror.\n *\n * Crucially the marks carry only stable identity (`userIds`, plus `format` for\n * the modification mark) — *not* user colors. Colors resolve asynchronously\n * from the {@link UserStore}, so baking them in here would make the mapper's\n * output change under a fixed `(format, attribution)` once a user loads, which\n * is exactly the non-determinism that triggers the reconcile loop. Instead the\n * `AttributionExtension` applies colors as a decoration layer that can\n * update independently of the mark representation.\n */\nexport const mapAttributionToMark = (\n format: Record<string, unknown> | null,\n attribution: {\n insert?: readonly string[];\n delete?: readonly string[];\n format?: Record<string, readonly string[]>;\n insertAt?: number;\n deleteAt?: number;\n formatAt?: number;\n },\n): Record<string, unknown> => {\n const out: Record<string, unknown> = { ...format };\n\n if (attribution.insert) {\n out[\"y-attributed-insert\"] = { userIds: attribution.insert };\n }\n\n if (attribution.delete) {\n out[\"y-attributed-delete\"] = { userIds: attribution.delete };\n }\n\n if (attribution.format) {\n const userIds = [...new Set(Object.values(attribution.format).flat())];\n out[\"y-attributed-format\"] = { userIds, format: attribution.format };\n }\n\n return out;\n};\n\nexport const YSyncExtension = createExtension(\n ({\n options,\n editor,\n }: ExtensionOptions<\n Pick<\n CollaborationOptions,\n | \"fragment\"\n | \"renderer\"\n | \"suggestionDoc\"\n | \"provider\"\n | \"getAttributionMarkClassName\"\n >\n >) => {\n return {\n key: \"ySync\",\n fragment: options.fragment,\n mount: () => {\n const configure = () => {\n editor.exec(\n configureYProsemirror({\n ytype: options.fragment,\n // purposefully not passing `renderer` here, this is only for syncing the main doc, not switching to suggestion mode\n // In the future, we may want view suggestion mode to be the default, and then we can decide how to indicate that through the options.\n // For now though, we are leaving suggestion mode as experimental and must be explicitly enabled through the SuggestionsExtension.\n }),\n );\n };\n\n if (\n options.provider &&\n \"synced\" in options.provider &&\n typeof options.provider.synced === \"boolean\"\n ) {\n if (options.provider[\"synced\"]) {\n configure();\n } else if (\n \"on\" in options.provider &&\n typeof options.provider.on === \"function\"\n ) {\n options.provider.on(\"synced\", (synced: boolean) => {\n if (synced) {\n configure();\n }\n });\n } else {\n throw new Error(\n \"YSyncExtension: provider must have a 'synced' boolean or an 'on' method to listen for 'sync'\",\n );\n }\n } else {\n configure();\n }\n },\n prosemirrorPlugins: [\n syncPlugin({\n suggestionDoc: options.suggestionDoc,\n mapAttributionToMark,\n // Node-pairing policy for the PM->Y diff: a `blockContainer` whose\n // block-content type changes is treated as a *different* node, so the\n // diff replaces the whole container (deleted + inserted siblings in\n // the blockGroup) instead of producing two block-contents in one\n // container => schema-invalid. No schema change / storage transform\n // needed; `blockContainer` already whitelists the `y-attributed-*`\n // marks. See blockMatchNodes.ts.\n customCompare: blockMatchNodes,\n }),\n ],\n runsBefore: [\"default\"],\n } as const;\n },\n);\n","import {\n deltaAttributionToFormat,\n deltaToPNode,\n deltaToPSteps,\n docToDelta,\n nodeToDelta,\n pmToFragment,\n} from \"@y/prosemirror\";\nimport * as d from \"lib0/delta\";\nimport { Node } from \"prosemirror-model\";\nimport { Transaction } from \"prosemirror-state\";\nimport {\n type Block,\n type BlockNoteEditor,\n type BlockSchema,\n type InlineContentSchema,\n type PartialBlock,\n type StyleSchema,\n blockToNode,\n docToBlocks,\n} from \"../index.js\";\nimport { blockMatchNodes } from \"./extensions/blockMatchNodes.js\";\nimport { mapAttributionToMark } from \"./extensions/YSync.js\";\n\nimport * as Y from \"@y/y\";\n\n/**\n * Find the equivalent of a Y.Type in another Y.Doc.\n *\n * For root types this looks up the matching shared key; for sub-types it\n * locates the item by its client/clock ID in the target doc's store.\n */\nexport function findTypeInOtherYdoc<T extends Y.Type<any>>(\n ytype: T,\n otherYdoc: Y.Doc,\n): T {\n const ydoc = ytype.doc;\n if (!ydoc) {\n throw new Error(\"type does not have a ydoc\");\n }\n if (ytype._item === null) {\n /**\n * If is a root type, we need to find the root key in the original ydoc\n * and use it to get the type in the other ydoc.\n */\n const rootKey = Array.from(ydoc.share.keys()).find(\n (key) => ydoc.share.get(key) === ytype,\n );\n if (rootKey == null) {\n throw new Error(\"type does not exist in other ydoc\");\n }\n return otherYdoc.get(rootKey as string, ytype.constructor as any) as T;\n } else {\n /**\n * If it is a sub type, we use the item id to find the history type.\n */\n const ytypeItem = ytype._item;\n const otherStructs = otherYdoc.store.clients.get(ytypeItem.id.client) ?? [];\n const itemIndex = Y.findIndexSS(otherStructs, ytypeItem.id.clock);\n const otherItem = otherStructs[itemIndex] as Y.Item | undefined;\n if (!otherItem) {\n throw new Error(\"type does not exist in other ydoc\");\n }\n const otherContent = otherItem.content as Y.ContentType | undefined;\n if (!otherContent) {\n throw new Error(\"type does not exist in other ydoc\");\n }\n return otherContent.type as T;\n }\n}\n\n/**\n * Turn Prosemirror JSON to BlockNote style JSON\n * @param editor BlockNote editor\n * @param json Prosemirror JSON\n * @returns BlockNote style JSON\n */\nexport function _prosemirrorJSONToBlocks<\n BSchema extends BlockSchema,\n ISchema extends InlineContentSchema,\n SSchema extends StyleSchema,\n>(editor: BlockNoteEditor<BSchema, ISchema, SSchema>, json: any) {\n // note: theoretically this should also be possible without creating prosemirror nodes,\n // but this is definitely the easiest way\n const doc = editor.pmSchema.nodeFromJSON(json);\n return docToBlocks<BSchema, ISchema, SSchema>(doc);\n}\n\n/**\n * Turn BlockNote JSON to Prosemirror node / state\n * @param editor BlockNote editor\n * @param blocks BlockNote blocks\n * @returns Prosemirror root node\n */\nexport function _blocksToProsemirrorNode<\n BSchema extends BlockSchema,\n ISchema extends InlineContentSchema,\n SSchema extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, ISchema, SSchema>,\n blocks: PartialBlock<BSchema, ISchema, SSchema>[],\n) {\n const pmNodes = blocks.map((b) => blockToNode(b, editor.pmSchema));\n\n const doc = editor.pmSchema.topNodeType.create(\n null,\n editor.pmSchema.nodes[\"blockGroup\"].create(null, pmNodes),\n );\n return doc;\n}\n\n/** YJS / BLOCKNOTE conversions */\n\n/**\n * Turn a Y.Type collaborative doc into a BlockNote document (BlockNote style JSON of all blocks)\n * @param editor BlockNote editor\n * @param fragment Y.Type\n * @returns BlockNote document (BlockNote style JSON of all blocks)\n */\nexport function yfragmentToBlocks<\n BSchema extends BlockSchema,\n ISchema extends InlineContentSchema,\n SSchema extends StyleSchema,\n>(editor: BlockNoteEditor<BSchema, ISchema, SSchema>, fragment: Y.Type) {\n const pmNode = deltaToPNode(fragment.toDeltaDeep(), editor.pmSchema, null);\n if (pmNode === null) {\n return [];\n }\n return docToBlocks<BSchema, ISchema, SSchema>(pmNode);\n}\n\n/**\n * Convert blocks to a Y.Type\n *\n * This can be used when importing existing content to Y.Doc for the first time,\n * note that this should not be used to rehydrate a Y.Doc from a database once\n * collaboration has begun as all history will be lost\n *\n * @param editor BlockNote editor\n * @param blocks the blocks to convert\n * @param fragment XML fragment name\n * @returns Y.Type\n */\nexport function blocksToYType<\n BSchema extends BlockSchema,\n ISchema extends InlineContentSchema,\n SSchema extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, ISchema, SSchema>,\n blocks: Block<BSchema, ISchema, SSchema>[],\n fragment?: Y.Type,\n) {\n if (!fragment) {\n fragment = new Y.Doc().get(\"prosemirror\");\n }\n return pmToFragment(_blocksToProsemirrorNode(editor, blocks), fragment);\n}\n\n/**\n * Turn a Y.Doc collaborative doc into a BlockNote document (BlockNote style JSON of all blocks)\n * @param editor BlockNote editor\n * @param ydoc Y.Doc\n * @param fragment XML fragment name\n * @returns BlockNote document (BlockNote style JSON of all blocks)\n */\nexport function yDocToBlocks<\n BSchema extends BlockSchema,\n ISchema extends InlineContentSchema,\n SSchema extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, ISchema, SSchema>,\n ydoc: Y.Doc,\n fragment = \"prosemirror\",\n) {\n return yfragmentToBlocks(editor, ydoc.get(fragment));\n}\n\n/**\n * This can be used when importing existing content to Y.Doc for the first time,\n * note that this should not be used to rehydrate a Y.Doc from a database once\n * collaboration has begun as all history will be lost\n *\n * @param editor BlockNote editor\n * @param blocks the blocks to convert\n * @param fragment XML fragment name\n */\nexport function blocksToYDoc<\n BSchema extends BlockSchema,\n ISchema extends InlineContentSchema,\n SSchema extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, ISchema, SSchema>,\n blocks: PartialBlock<BSchema, ISchema, SSchema>[],\n fragment = \"prosemirror\",\n) {\n const delta = docToDelta(_blocksToProsemirrorNode(editor, blocks));\n const doc = new Y.Doc();\n doc.get(fragment).applyDelta(delta);\n return doc;\n}\n\n/**\n * Diff two ProseMirror documents into a delta, using BlockNote's node-pairing\n * policy ({@link blockMatchNodes}) so a block's content-type change is reported\n * as a replace rather than a schema-invalid in-place edit.\n */\nexport function docDiffToDelta(previousDoc: Node, newDoc: Node) {\n const initialDelta = nodeToDelta(previousDoc);\n const finalDelta = nodeToDelta(newDoc);\n return d.diff(initialDelta.done(), finalDelta.done(), {\n compare: blockMatchNodes,\n });\n}\n\n/**\n * Build a ProseMirror transaction that turns `tr.doc` into the content of a\n * Y.Type `fragment`, applying the `renderer`'s authorship as\n * `y-attributed-*` marks. Used to render a (read-only) diff of a snapshot / a\n * version comparison into the editor.\n */\nexport function getProseMirrorTrFromYFragment({\n tr,\n fragment,\n renderer,\n}: {\n tr: Transaction;\n fragment: Y.Type;\n renderer?: Y.AbstractRenderer | null;\n}): Transaction {\n const ycontent = deltaAttributionToFormat(\n fragment.toDeltaDeep({ renderer }),\n mapAttributionToMark,\n );\n // @todo it is preferred to apply the minimal diff - at least for debugging purposes. the\n // document replacal is more reliable though\n\n const pcontent = nodeToDelta(tr.doc, undefined, true);\n const diff = d.diff(pcontent.done(), ycontent.done(), {\n compare: blockMatchNodes,\n });\n return deltaToPSteps(tr, diff, undefined, undefined);\n}\n","import { getMarkRange, posToDOMRect } from \"@tiptap/core\";\n\nimport {\n createExtension,\n ExtensionOptions,\n} from \"../../editor/BlockNoteExtension.js\";\nimport {\n acceptChanges,\n rejectAllChanges,\n rejectChanges,\n configureYProsemirror,\n acceptAllChanges,\n} from \"@y/prosemirror\";\nimport { CollaborationOptions } from \"./index.js\";\nimport { findTypeInOtherYdoc } from \"../utils.js\";\nimport { normalizeToUserStore } from \"../../user/index.js\";\n\nexport const SuggestionsExtension = createExtension(\n ({ editor, options }: ExtensionOptions<CollaborationOptions>) => {\n const suggestionDoc = options.suggestionDoc;\n if (!suggestionDoc) {\n throw new Error(\"Suggestion doc not found\");\n }\n // Shared with the rest of collaboration; the collaboration extension passes\n // an already-built store here.\n const userStore = normalizeToUserStore(options.resolveUsers);\n\n function getSuggestionElementAtPos(pos: number) {\n let currentNode = editor.prosemirrorView.nodeDOM(pos);\n while (currentNode && currentNode.parentElement) {\n if (currentNode.nodeName === \"INS\" || currentNode.nodeName === \"DEL\") {\n return currentNode as HTMLElement;\n }\n currentNode = currentNode.parentElement;\n }\n return null;\n }\n\n function getMarkAtPos(pos: number, markType: string) {\n return editor.transact((tr) => {\n const resolvedPos = tr.doc.resolve(pos);\n const mark = resolvedPos\n .marks()\n .find((mark) => mark.type.name === markType);\n\n if (!mark) {\n return;\n }\n\n const markRange = getMarkRange(resolvedPos, mark.type);\n if (!markRange) {\n return;\n }\n\n return {\n range: markRange,\n mark,\n get text() {\n return tr.doc.textBetween(markRange.from, markRange.to);\n },\n get position() {\n // to minimize re-renders, we convert to JSON, which is the same shape anyway\n return posToDOMRect(\n editor.prosemirrorView,\n markRange.from,\n markRange.to,\n ).toJSON() as DOMRect;\n },\n };\n });\n }\n\n function getSuggestionAtSelection() {\n return editor.transact((tr) => {\n const selection = tr.selection;\n if (!selection.empty) {\n return undefined;\n }\n return (\n getMarkAtPos(selection.anchor, \"insertion\") ||\n getMarkAtPos(selection.anchor, \"deletion\") ||\n getMarkAtPos(selection.anchor, \"modification\")\n );\n });\n }\n\n return {\n key: \"suggestions\",\n userStore,\n runsBefore: [\"ySync\"],\n viewSuggestions: () => {\n if (options.renderer) {\n options.renderer.suggestionMode = false;\n }\n editor.exec(\n configureYProsemirror({\n ytype: findTypeInOtherYdoc(options.fragment, suggestionDoc),\n renderer: options.renderer,\n }),\n );\n },\n enableSuggestions: () => {\n if (options.renderer) {\n options.renderer.suggestionMode = true;\n }\n editor.exec(\n configureYProsemirror({\n ytype: findTypeInOtherYdoc(options.fragment, suggestionDoc),\n renderer: options.renderer,\n }),\n );\n },\n disableSuggestions: () => {\n editor.exec(\n configureYProsemirror({\n ytype: options.fragment,\n renderer: null,\n }),\n );\n },\n applyAllSuggestions: () => {\n return editor.exec(acceptAllChanges());\n },\n applySuggestion: (start: number, end?: number) => {\n return editor.exec(acceptChanges(start, end));\n },\n revertSuggestion: (start: number, end?: number) => {\n return editor.exec(rejectChanges(start, end));\n },\n revertAllSuggestions: () => {\n return editor.exec(rejectAllChanges());\n },\n\n getSuggestionElementAtPos,\n getMarkAtPos,\n getSuggestionAtSelection,\n getSuggestionAtCoords: (coords: { left: number; top: number }) => {\n return editor.transact(() => {\n const posAtCoords = editor.prosemirrorView.posAtCoords(coords);\n if (posAtCoords === null || posAtCoords?.inside === -1) {\n return undefined;\n }\n\n return (\n getMarkAtPos(posAtCoords.pos, \"y-attributed-insert\") ||\n getMarkAtPos(posAtCoords.pos, \"y-attributed-delete\") ||\n getMarkAtPos(posAtCoords.pos, \"y-attributed-format\")\n );\n });\n },\n checkUnresolvedSuggestions: () => {\n let hasUnresolvedSuggestions = false;\n\n editor.prosemirrorState.doc.descendants((node) => {\n if (hasUnresolvedSuggestions) {\n return false;\n }\n\n hasUnresolvedSuggestions =\n node.marks.findIndex(\n (mark) =>\n mark.type.name === \"y-attributed-insert\" ||\n mark.type.name === \"y-attributed-delete\" ||\n mark.type.name === \"y-attributed-format\",\n ) !== -1;\n\n return true;\n });\n\n return hasUnresolvedSuggestions;\n },\n } as const;\n },\n);\n","import { configureYProsemirror, pauseSync } from \"@y/prosemirror\";\nimport * as Y from \"@y/y\";\n\nimport type { BlockNoteEditor } from \"../../editor/BlockNoteEditor.js\";\nimport type { PreviewController } from \"../../extensions/Versioning/index.js\";\nimport {\n findTypeInOtherYdoc,\n getProseMirrorTrFromYFragment,\n} from \"../utils.js\";\n\n/**\n * Empties the document before a {@link configureYProsemirror} refill so\n * ProseMirror rebuilds every node view instead of reusing a stale-positioned one\n * (BlockNote node views resolve their block eagerly via `getPos()` and throw on\n * a moved node). Sync is paused first so the clear never reaches the Y.Doc.\n *\n * TODO: remove once `configureYProsemirror` applies a minimal diff.\n */\nfunction clearDocumentForConfigure(editor: BlockNoteEditor<any, any, any>) {\n // Pause sync (ytype -> null) so the deletion below stays local.\n editor.exec(pauseSync);\n editor.removeBlocks(editor.document);\n}\n\n/**\n * Creates a Yjs-specific adapter that provides the {@link PreviewController}\n * and `getCurrentDocument` callback required by the base\n * {@link VersioningExtension}.\n *\n * This is wired automatically by the {@link CollaborationExtension} when\n * `versioningEndpoints` is provided. You only need to call this directly if\n * you're using the `VersioningExtension` outside of the collaboration wrapper.\n */\nexport function createYjsVersioningAdapter(\n editor: BlockNoteEditor<any, any, any>,\n fragment: Y.Type,\n): {\n preview: PreviewController<Uint8Array, Y.ContentMap>;\n getCurrentDocument: () => Y.Type;\n serializeCurrentContent: () => Uint8Array;\n} {\n return {\n getCurrentDocument: () => fragment,\n // Serialise the live document as a V2 update — the same format that\n // `getContent` returns (via `convertUpdateFormatV1ToV2`) and that\n // `enterPreview` consumes (`applyUpdateV2`). Used to render a read-only\n // diff of the live document against a snapshot.\n serializeCurrentContent: () => Y.encodeStateAsUpdateV2(fragment.doc!),\n preview: {\n enterPreview: (\n snapshotContent: Uint8Array,\n compareToContent?: Uint8Array,\n attributions?: Y.ContentMap,\n ) => {\n let prevSnapshot: { fragment: Y.Type } | undefined;\n if (compareToContent) {\n const compareToDoc = new Y.Doc({ isSuggestionDoc: true });\n Y.applyUpdateV2(compareToDoc, compareToContent);\n prevSnapshot = {\n fragment: findTypeInOtherYdoc(fragment, compareToDoc),\n };\n }\n\n const doc = new Y.Doc();\n Y.applyUpdateV2(doc, snapshotContent);\n // Empty the document before reconfiguring so ProseMirror rebuilds node\n // views from scratch instead of reusing stale-positioned ones. See\n // clearDocumentForConfigure.\n clearDocumentForConfigure(editor);\n\n editor.exec((state, dispatch) => {\n const tr = getProseMirrorTrFromYFragment({\n tr: state.tr,\n fragment: findTypeInOtherYdoc(fragment, doc),\n // Pass the optional content map as `attrs` so the diff renderer\n // knows who/when authored each change. Without it, the renderer\n // only produces \"what changed\" (empty userIds, null timestamps) and\n // downstream mark tooltips show \"unknown / unknown time\".\n renderer: prevSnapshot\n ? Y.createDiffRenderer(\n prevSnapshot.fragment.doc!,\n doc,\n attributions ? { attrs: attributions } : undefined,\n )\n : undefined,\n });\n if (dispatch) {\n dispatch(tr);\n }\n return true;\n });\n },\n exitPreview: () => {\n // Empty the document before reconfiguring so ProseMirror rebuilds node\n // views from scratch instead of reusing stale-positioned ones. See\n // clearDocumentForConfigure.\n clearDocumentForConfigure(editor);\n editor.exec(configureYProsemirror({ ytype: fragment }));\n },\n applyRestore: (_snapshotContent: Uint8Array) => {\n // For Yjs-backed versioning, restoration happens on the server (e.g.\n // YHub's `/rollback` endpoint) which publishes a reverting update to\n // the document's room. That update propagates back to this client over\n // the live sync connection and updates `fragment` automatically, so\n // there is nothing to apply locally — we only need to leave preview\n // mode. `exitPreview` is already called by the base extension before\n // this runs, so this is a no-op.\n //\n // Note: this assumes `endpoints.restore` performs the server-side\n // restore. The default in-memory adapter has no server, which is why\n // this is specific to the Yjs collaboration setup.\n },\n },\n };\n}\n","import { defaultSelectionBuilder, yCursorPlugin } from \"@y/prosemirror\";\nimport {\n createExtension,\n ExtensionOptions,\n} from \"../../editor/BlockNoteExtension.js\";\nimport { CollaborationOptions } from \"./index.js\";\n\nexport type CollaborationUser = {\n id?: string;\n name: string;\n color: string;\n [key: string]: unknown;\n};\n\n/**\n * Determine whether the foreground color should be white or black based on a provided background color\n * Inspired by: https://stackoverflow.com/a/3943023\n */\nfunction isDarkColor(bgColor: string): boolean {\n const color = bgColor.charAt(0) === \"#\" ? bgColor.substring(1, 7) : bgColor;\n const r = parseInt(color.substring(0, 2), 16); // hexToR\n const g = parseInt(color.substring(2, 4), 16); // hexToG\n const b = parseInt(color.substring(4, 6), 16); // hexToB\n const uicolors = [r / 255, g / 255, b / 255];\n const c = uicolors.map((col) => {\n if (col <= 0.03928) {\n return col / 12.92;\n }\n return Math.pow((col + 0.055) / 1.055, 2.4);\n });\n const L = 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];\n return L <= 0.179;\n}\n\nfunction defaultCursorRender(user: CollaborationUser) {\n const cursorElement = document.createElement(\"span\");\n\n cursorElement.classList.add(\"bn-collaboration-cursor__base\");\n\n const caretElement = document.createElement(\"span\");\n caretElement.setAttribute(\"contentedEditable\", \"false\");\n caretElement.classList.add(\"bn-collaboration-cursor__caret\");\n caretElement.setAttribute(\n \"style\",\n `background-color: ${user.color}; color: ${\n isDarkColor(user.color) ? \"white\" : \"black\"\n }`,\n );\n\n const labelElement = document.createElement(\"span\");\n\n labelElement.classList.add(\"bn-collaboration-cursor__label\");\n labelElement.setAttribute(\n \"style\",\n `background-color: ${user.color}; color: ${\n isDarkColor(user.color) ? \"white\" : \"black\"\n }`,\n );\n labelElement.insertBefore(document.createTextNode(user.name), null);\n\n caretElement.insertBefore(labelElement, null);\n\n cursorElement.insertBefore(document.createTextNode(\"\\u2060\"), null); // Non-breaking space\n cursorElement.insertBefore(caretElement, null);\n cursorElement.insertBefore(document.createTextNode(\"\\u2060\"), null); // Non-breaking space\n\n return cursorElement;\n}\n\nexport const YCursorExtension = createExtension(\n ({ options }: ExtensionOptions<CollaborationOptions>) => {\n const recentlyUpdatedCursors = new Map();\n const awareness =\n options.provider &&\n \"awareness\" in options.provider &&\n typeof options.provider.awareness === \"object\"\n ? options.provider.awareness\n : undefined;\n if (awareness) {\n if (\n \"setLocalStateField\" in awareness &&\n typeof awareness.setLocalStateField === \"function\"\n ) {\n awareness.setLocalStateField(\"user\", options.user);\n }\n if (\"on\" in awareness && typeof awareness.on === \"function\") {\n if (options.showCursorLabels !== \"always\") {\n awareness.on(\n \"change\",\n ({\n updated,\n }: {\n added: Array<number>;\n updated: Array<number>;\n removed: Array<number>;\n }) => {\n for (const clientID of updated) {\n const cursor = recentlyUpdatedCursors.get(clientID);\n\n if (cursor) {\n setTimeout(() => {\n cursor.element.setAttribute(\"data-active\", \"\");\n }, 10);\n\n if (cursor.hideTimeout) {\n clearTimeout(cursor.hideTimeout);\n }\n\n recentlyUpdatedCursors.set(clientID, {\n element: cursor.element,\n hideTimeout: setTimeout(() => {\n cursor.element.removeAttribute(\"data-active\");\n }, 2000),\n });\n }\n }\n },\n );\n }\n }\n }\n\n return {\n key: \"yCursor\",\n prosemirrorPlugins: [\n awareness\n ? yCursorPlugin(awareness, {\n selectionBuilder: defaultSelectionBuilder,\n cursorBuilder(user, clientID) {\n let cursorData = recentlyUpdatedCursors.get(clientID);\n\n if (!cursorData) {\n const cursorElement = (\n options.renderCursor ?? defaultCursorRender\n )(user as CollaborationUser);\n\n if (options.showCursorLabels !== \"always\") {\n cursorElement.addEventListener(\"mouseenter\", () => {\n const cursor = recentlyUpdatedCursors.get(clientID)!;\n cursor.element.setAttribute(\"data-active\", \"\");\n\n if (cursor.hideTimeout) {\n clearTimeout(cursor.hideTimeout);\n recentlyUpdatedCursors.set(clientID, {\n element: cursor.element,\n hideTimeout: undefined,\n });\n }\n });\n\n cursorElement.addEventListener(\"mouseleave\", () => {\n const cursor = recentlyUpdatedCursors.get(clientID)!;\n\n recentlyUpdatedCursors.set(clientID, {\n element: cursor.element,\n hideTimeout: setTimeout(() => {\n cursor.element.removeAttribute(\"data-active\");\n }, 2000),\n });\n });\n }\n\n cursorData = {\n element: cursorElement,\n hideTimeout: undefined,\n };\n\n recentlyUpdatedCursors.set(clientID, cursorData);\n }\n\n return cursorData.element;\n },\n })\n : undefined,\n ].filter((a) => a !== undefined),\n dependsOn: [\"ySync\"],\n updateUser(user: CollaborationUser) {\n awareness?.setLocalStateField(\"user\", user);\n },\n getUser(): CollaborationUser | undefined {\n const state = awareness?.getLocalState();\n if (!state) {\n return undefined;\n }\n return state[\"user\"];\n },\n } as const;\n },\n);\n","import { docToDelta } from \"@y/prosemirror\";\nimport * as Y from \"@y/y\";\n\nimport type { Block } from \"../../blocks/defaultBlocks.js\";\nimport type { BlockNoteEditor } from \"../../editor/BlockNoteEditor.js\";\nimport { createExtension } from \"../../editor/BlockNoteExtension.js\";\nimport type { User } from \"../../user/index.js\";\nimport {\n _blocksToProsemirrorNode,\n docDiffToDelta,\n findTypeInOtherYdoc,\n getProseMirrorTrFromYFragment,\n} from \"../utils.js\";\nimport { AttributionExtension } from \"./AttributionExtension.js\";\nimport type { GetAttributionMarkClassName } from \"./YAttributionMarks.js\";\n\n/**\n * A version diff has a single \"author\" — the version that introduced the changes\n * — not a real user, so all `y-attributed-*` marks carry one synthetic id. That\n * id is derived from the version's label (see {@link diffAuthorId}) so it's stable\n * per version: the user store caches resolved users by id, so a per-label id keeps\n * each version's tooltip showing its own name instead of a stale cached one.\n */\nconst DIFF_AUTHOR_ID_PREFIX = \"version:\";\n\n/** The synthetic author id for a given version label. */\nconst diffAuthorId = (label: string) => DIFF_AUTHOR_ID_PREFIX + label;\n\n/** Fallback label used when a diff is rendered without a version name. */\nconst DEFAULT_DIFF_LABEL = \"This version\";\n\n/** Color used for the version diff marks. */\nconst DIFF_AUTHOR_COLOR = \"#4363d8\";\n\nexport type DiffVersioningExtensionOptions = {\n /**\n * The color used for the diff's attribution marks. Defaults to a blue.\n */\n color?: string;\n /**\n * See {@link GetAttributionMarkClassName}. Forwarded to the underlying\n * {@link AttributionExtension} to override mark styling by change type.\n */\n getAttributionMarkClassName?: GetAttributionMarkClassName;\n};\n\n/**\n * Records the author of each transaction on `doc` into a mutable\n * {@link Y.Attributions}, so the resulting attribution marks carry a non-empty\n * `userIds` (and therefore resolve to a color/name). The listener must be\n * attached *before* the attributed transaction runs. Mirrors the store used by\n * the suggestion gallery example (`createAttributionStore`).\n */\nfunction attributeTransactionsTo(doc: Y.Doc, userId: string): Y.Attributions {\n const attrs = new Y.Attributions();\n doc.on(\"beforeObserverCalls\", (tr) => {\n if (!tr.insertSet.isEmpty()) {\n Y.insertIntoIdMap(\n attrs.inserts,\n Y.createIdMapFromIdSet(tr.insertSet, [\n Y.createContentAttribute(\"insert\", userId),\n ]),\n );\n }\n if (!tr.deleteSet.isEmpty()) {\n Y.insertIntoIdMap(\n attrs.deletes,\n Y.createIdMapFromIdSet(tr.deleteSet, [\n Y.createContentAttribute(\"delete\", userId),\n ]),\n );\n }\n });\n return attrs;\n}\n\n/**\n * An opt-in extension that renders a read-only diff between two BlockNote\n * documents (`Block[]`) directly in the editor, marking insertions/deletions\n * with the `y-attributed-*` suggestion marks — the same visual result the Yjs\n * collaboration adapter produces, but driven from two plain block arrays with\n * no server and no live Yjs sync.\n *\n * It composes {@link AttributionExtension} (which registers the attribution\n * marks and drives their colors + hover tooltips from a user store), and adds\n * the {@link renderDiff} / {@link clearDiff} capability.\n *\n * Registering this extension is what makes non-collaborative versioning\n * (`inMemoryVersioning`) capable of showing diffs: the in-memory preview\n * controller looks this up by key (`\"diffVersioning\"`) and delegates to\n * {@link renderDiff}, falling back to a static document swap when it's absent.\n *\n * `renderDiff` is also a standalone, directly-callable API — you can register\n * just this extension and call\n * `editor.getExtension(DiffVersioningExtension).renderDiff(target, baseline)`\n * to render a known diff (e.g. in tests).\n *\n * @example\n * ```ts\n * const editor = BlockNoteEditor.create({\n * extensions: [DiffVersioningExtension()],\n * });\n * editor.getExtension(DiffVersioningExtension)!.renderDiff(target, baseline);\n * ```\n */\nexport const DiffVersioningExtension = createExtension(\n ({\n options,\n editor,\n }: {\n options: DiffVersioningExtensionOptions | undefined;\n editor: BlockNoteEditor<any, any, any>;\n }) => {\n const color = options?.color ?? DIFF_AUTHOR_COLOR;\n\n // Resolve a synthetic author id back to its version label. The id encodes\n // the label (`version:<label>`), so this is a pure decode — no shared mutable\n // state, and the user store caches each version's \"user\" separately (so\n // switching between version comparisons never shows a stale name).\n const resolveUsers = async (ids: string[]): Promise<User[]> =>\n ids\n .filter((id) => id.startsWith(DIFF_AUTHOR_ID_PREFIX))\n .map((id) => ({\n id,\n username: id.slice(DIFF_AUTHOR_ID_PREFIX.length),\n avatarUrl: \"\",\n color,\n }));\n\n /**\n * Render a read-only diff of `baselineBlocks` → `snapshotBlocks` into the\n * editor. The changes are attributed to the version that introduced them:\n * pass `versionLabel` to label the diff marks (shown in their hover tooltip,\n * e.g. \"Edited by: {versionLabel}\"). Uses the \"two-doc fork\" recipe so the\n * two Y.Docs share history — a hard requirement for\n * `createDiffRenderer`, which diffs by Yjs client/clock ids.\n */\n const renderDiff = (\n snapshotBlocks: Block<any, any, any>[],\n baselineBlocks: Block<any, any, any>[],\n versionLabel: string = DEFAULT_DIFF_LABEL,\n ) => {\n const authorId = diffAuthorId(versionLabel);\n\n if (!editor.pmSchema.marks[\"y-attributed-insert\"]) {\n throw new Error(\n \"DiffVersioningExtension: the y-attributed-* marks are missing from \" +\n \"the schema. This should not happen — the extension registers them \" +\n \"via AttributionExtension.\",\n );\n }\n\n const baselineNode = _blocksToProsemirrorNode(editor, baselineBlocks);\n const snapshotNode = _blocksToProsemirrorNode(editor, snapshotBlocks);\n\n // gc must stay off so the attribution manager can read the full struct\n // store (including deleted items) when diffing.\n const prevDoc = new Y.Doc({ gc: false });\n const prevType = prevDoc.get(\"prosemirror\");\n prevDoc.transact(() => {\n prevType.applyDelta(docToDelta(baselineNode) as any);\n });\n\n // Fork prevDoc into nextDoc so they share client/clock ids, then apply the\n // baseline → snapshot delta as a new transaction. New content gets ids\n // that prevDoc lacks (→ inserts); items retained-away become deletes.\n const nextDoc = new Y.Doc({ gc: false });\n Y.applyUpdateV2(nextDoc, Y.encodeStateAsUpdateV2(prevDoc));\n const nextType = findTypeInOtherYdoc(prevType, nextDoc);\n\n // Attach the author store BEFORE applying the delta so the diff\n // transaction's inserts/deletes are attributed.\n const attrs = attributeTransactionsTo(nextDoc, authorId);\n\n const delta = docDiffToDelta(baselineNode, snapshotNode);\n nextDoc.transact(() => {\n nextType.applyDelta(delta as any);\n }, authorId);\n\n const renderer = Y.createDiffRenderer(prevDoc, nextDoc, { attrs });\n\n // Clear the live doc first so ProseMirror rebuilds node views from\n // scratch (BlockNote node views resolve their block eagerly via getPos()\n // and throw on a moved node). The diff then inserts the attributed content\n // against an empty doc.\n editor.replaceBlocks(editor.document, []);\n\n editor.exec((state, dispatch) => {\n const tr = getProseMirrorTrFromYFragment({\n tr: state.tr,\n fragment: nextType,\n renderer,\n });\n if (dispatch) {\n dispatch(tr);\n }\n return true;\n });\n\n prevDoc.destroy();\n nextDoc.destroy();\n };\n\n /**\n * Leave diff view: clear the (mark-carrying) document and restore the given\n * blocks. Clears first so stale node views for block-level marks are torn\n * down instead of reused.\n */\n const clearDiff = (restore: Block<any, any, any>[]) => {\n editor.replaceBlocks(editor.document, []);\n editor.replaceBlocks(editor.document, restore);\n };\n\n return {\n key: \"diffVersioning\",\n // Compose AttributionExtension: registers the y-attributed-* marks and\n // drives their colors + hover-tooltip names from the (static) user store.\n blockNoteExtensions: [\n AttributionExtension({\n resolveUsers,\n getAttributionMarkClassName: options?.getAttributionMarkClassName,\n }),\n ],\n renderDiff,\n clearDiff,\n };\n },\n);\n","import type { Awareness } from \"@y/protocols/awareness\";\nimport type * as Y from \"@y/y\";\nimport { BlockNoteEditorOptions } from \"../../editor/BlockNoteEditor.js\";\nimport {\n createExtension,\n ExtensionOptions,\n} from \"../../editor/BlockNoteExtension.js\";\nimport {\n VersioningEndpoints,\n VersioningEndpointsFactory,\n VersioningExtension,\n} from \"../../extensions/Versioning/index.js\";\nimport { normalizeToUserStore, UserStoreOrResolver } from \"../../user/index.js\";\nimport { AttributionExtension } from \"./AttributionExtension.js\";\nimport { RelativePositionMappingExtension } from \"./RelativePositionMapping.js\";\nimport { SuggestionsExtension } from \"./Suggestions.js\";\nimport { createYjsVersioningAdapter } from \"./Versioning.js\";\nimport { CollaborationUser, YCursorExtension } from \"./YCursorPlugin.js\";\nimport type { GetAttributionMarkClassName } from \"./YAttributionMarks.js\";\nimport { YSyncExtension } from \"./YSync.js\";\n\nexport type CollaborationOptions = {\n /**\n * The Yjs Type that's used for collaboration.\n */\n fragment: Y.Type;\n /**\n * The user info for the current user that's shown to other collaborators.\n */\n user: CollaborationUser;\n /**\n * Resolve user information (usernames, colors) for suggestions and versions,\n * used to drive suggestion-author tooltips, suggestion colors and\n * version-history author labels.\n *\n * Either a resolver function (called with the ids of users that are not yet\n * cached, returning their information — a user store is built from it\n * internally) or a pre-built user store (see `createUserStore`). Pass the same\n * store you give the comments extension so a single de-duped user cache is\n * shared across comments, suggestions and versioning.\n */\n resolveUsers?: UserStoreOrResolver;\n /**\n * A Yjs provider (used for awareness / cursor information)\n */\n provider?: { awareness?: Awareness };\n /**\n * Optional function to customize how cursors of users are rendered\n */\n renderCursor?: (user: CollaborationUser) => HTMLElement;\n /**\n * Optional flag to set when the user label should be shown with the default\n * collaboration cursor. Setting to \"always\" will always show the label,\n * while \"activity\" will only show the label when the user moves the cursor\n * or types. Defaults to \"activity\".\n */\n showCursorLabels?: \"always\" | \"activity\";\n /**\n * The renderer for the collaboration.\n */\n renderer?: Y.DiffRenderer;\n /**\n * The suggestion doc for the collaboration. If using suggestion mode\n */\n suggestionDoc?: Y.Doc;\n\n /**\n * Optional callback to override suggestion-mark styling by change type instead\n * of by author. Given a mark's content/modification type, return a class name\n * applied to the mark and its hover tooltip; the per-user color is then\n * dropped for that mark. See {@link GetAttributionMarkClassName}.\n */\n getAttributionMarkClassName?: GetAttributionMarkClassName;\n\n /**\n * The endpoints for the versioning functionality.\n */\n versioningEndpoints?:\n | VersioningEndpoints<Y.Type, Uint8Array>\n | VersioningEndpointsFactory<Y.Type, Uint8Array>;\n};\n\nexport const CollaborationExtension = createExtension(\n ({ editor, options }: ExtensionOptions<CollaborationOptions>) => {\n // Build a single user store here (from a resolver callback or a store the\n // consumer passed in) and hand that same store down to every sub-extension\n // that needs it — suggestions, suggestion-mark tooltips/colors, versioning —\n // so they share one de-duped cache. Passing the resolved store (rather than\n // the raw resolver) is what guarantees the sharing: each child re-normalizes\n // it to itself instead of building its own.\n const userStore = normalizeToUserStore(options.resolveUsers);\n const optionsWithUserStore = { ...options, resolveUsers: userStore };\n return {\n key: \"collaboration\",\n userStore,\n blockNoteExtensions: [\n options.suggestionDoc\n ? SuggestionsExtension(optionsWithUserStore)\n : null,\n RelativePositionMappingExtension(),\n YSyncExtension(optionsWithUserStore),\n YCursorExtension(options),\n options.versioningEndpoints\n ? VersioningExtension({\n ...createYjsVersioningAdapter(editor, options.fragment),\n endpoints: options.versioningEndpoints,\n resolveUsers: userStore,\n })\n : null,\n AttributionExtension({\n resolveUsers: userStore,\n getAttributionMarkClassName: options.getAttributionMarkClassName,\n }),\n ].filter((a) => a !== null),\n } as const;\n },\n);\n\nexport function withCollaboration<\n Options extends Partial<BlockNoteEditorOptions<any, any, any>>,\n>(\n options: Options & {\n /**\n * Options for configuring the collaboration functionality.\n */\n collaboration: CollaborationOptions;\n },\n): Options {\n if (options.initialContent) {\n // eslint-disable-next-line no-console\n console.warn(\n \"When using Collaboration, initialContent might cause conflicts, because changes should come from the collaboration provider\",\n );\n }\n return {\n ...options,\n extensions: [\n ...(options.extensions ?? []),\n CollaborationExtension(options.collaboration),\n ],\n // We disable the default prosemirror history plugin, since it's not compatible with yjs\n disableExtensions: [\"history\", ...(options.disableExtensions ?? [])],\n // We don't want the default initial content, since it will generate a random id for the initial block on each client,\n // leading to conflicts when syncing happens afterwards.\n initialContent: [{ type: \"paragraph\", id: \"initialBlockId\" }],\n };\n}\n\nexport * from \"./AttributionExtension.js\";\nexport * from \"./DiffVersioningExtension.js\";\nexport * from \"./RelativePositionMapping.js\";\nexport * from \"./Suggestions.js\";\nexport * from \"./Versioning.js\";\nexport * from \"./YAttributionMarks.js\";\nexport * from \"./YCursorPlugin.js\";\nexport * from \"./YSync.js\";\n","import * as Y from \"@y/y\";\nimport type {\n CommentData,\n CommentReactionData,\n ThreadData,\n} from \"../../comments/types.js\";\n\nexport function commentToYType(comment: CommentData) {\n const yType = new Y.Type();\n yType.setAttr(\"id\", comment.id);\n yType.setAttr(\"userId\", comment.userId);\n yType.setAttr(\"createdAt\", comment.createdAt.getTime());\n yType.setAttr(\"updatedAt\", comment.updatedAt.getTime());\n if (comment.deletedAt) {\n yType.setAttr(\"deletedAt\", comment.deletedAt.getTime());\n yType.setAttr(\"body\", undefined);\n } else {\n yType.setAttr(\"body\", comment.body);\n }\n if (comment.reactions.length > 0) {\n throw new Error(\"Reactions should be empty in commentToYType\");\n }\n\n /**\n * Reactions are stored in a map keyed by {userId-emoji},\n * this makes it easy to add / remove reactions and in a way that works local-first.\n * The cost is that \"reading\" the reactions is a bit more complex (see yTypeToReactions).\n */\n yType.setAttr(\"reactionsByUser\", new Y.Type());\n yType.setAttr(\"metadata\", comment.metadata);\n\n return yType;\n}\n\nexport function threadToYType(thread: ThreadData) {\n const yType = new Y.Type();\n yType.setAttr(\"id\", thread.id);\n yType.setAttr(\"createdAt\", thread.createdAt.getTime());\n yType.setAttr(\"updatedAt\", thread.updatedAt.getTime());\n const commentsType = new Y.Type();\n\n commentsType.push(thread.comments.map((comment) => commentToYType(comment)));\n\n yType.setAttr(\"comments\", commentsType);\n yType.setAttr(\"resolved\", thread.resolved);\n yType.setAttr(\"resolvedUpdatedAt\", thread.resolvedUpdatedAt?.getTime());\n yType.setAttr(\"resolvedBy\", thread.resolvedBy);\n yType.setAttr(\"metadata\", thread.metadata);\n return yType;\n}\n\ntype SingleUserCommentReactionData = {\n emoji: string;\n createdAt: Date;\n userId: string;\n};\n\nexport function yTypeToReaction(yType: Y.Type): SingleUserCommentReactionData {\n return {\n emoji: yType.getAttr(\"emoji\"),\n createdAt: new Date(yType.getAttr(\"createdAt\")),\n userId: yType.getAttr(\"userId\"),\n };\n}\n\nfunction yTypeToReactions(yType: Y.Type): CommentReactionData[] {\n const flatReactions = [...yType.attrValues()].map((reaction: Y.Type) =>\n yTypeToReaction(reaction),\n );\n // combine reactions by the same emoji\n return flatReactions.reduce(\n (acc: CommentReactionData[], reaction: SingleUserCommentReactionData) => {\n const existingReaction = acc.find((r) => r.emoji === reaction.emoji);\n if (existingReaction) {\n existingReaction.userIds.push(reaction.userId);\n existingReaction.createdAt = new Date(\n Math.min(\n existingReaction.createdAt.getTime(),\n reaction.createdAt.getTime(),\n ),\n );\n } else {\n acc.push({\n emoji: reaction.emoji,\n createdAt: reaction.createdAt,\n userIds: [reaction.userId],\n });\n }\n return acc;\n },\n [] as CommentReactionData[],\n );\n}\n\nexport function yTypeToComment(yType: Y.Type): CommentData {\n return {\n type: \"comment\",\n id: yType.getAttr(\"id\"),\n userId: yType.getAttr(\"userId\"),\n createdAt: new Date(yType.getAttr(\"createdAt\")),\n updatedAt: new Date(yType.getAttr(\"updatedAt\")),\n deletedAt: yType.getAttr(\"deletedAt\")\n ? new Date(yType.getAttr(\"deletedAt\"))\n : undefined,\n reactions: yTypeToReactions(yType.getAttr(\"reactionsByUser\")),\n metadata: yType.getAttr(\"metadata\"),\n body: yType.getAttr(\"body\"),\n };\n}\n\nexport function yTypeToThread(yType: Y.Type): ThreadData {\n return {\n type: \"thread\",\n id: yType.getAttr(\"id\"),\n createdAt: new Date(yType.getAttr(\"createdAt\")),\n updatedAt: new Date(yType.getAttr(\"updatedAt\")),\n comments: ((yType.getAttr(\"comments\") as Y.Type)?.toArray() || []).map(\n (comment) => yTypeToComment(comment as Y.Type),\n ),\n resolved: yType.getAttr(\"resolved\"),\n resolvedUpdatedAt: new Date(yType.getAttr(\"resolvedUpdatedAt\")),\n resolvedBy: yType.getAttr(\"resolvedBy\"),\n metadata: yType.getAttr(\"metadata\"),\n };\n}\n","import * as Y from \"@y/y\";\nimport type { ThreadData } from \"../../comments/types.js\";\nimport { ThreadStore } from \"../../comments/threadstore/ThreadStore.js\";\nimport type { ThreadStoreAuth } from \"../../comments/threadstore/ThreadStoreAuth.js\";\nimport { yTypeToThread } from \"./yjsHelpers.js\";\n\n/**\n * This is an abstract class that only implements the READ methods required by the ThreadStore interface.\n * The data is read from a @y/y Type used as a map (via attributes).\n */\nexport abstract class YjsThreadStoreBase extends ThreadStore {\n constructor(\n protected readonly threadsYType: Y.Type,\n auth: ThreadStoreAuth,\n ) {\n super(auth);\n }\n\n // TODO: async / reactive interface?\n public getThread(threadId: string) {\n const yThread = this.threadsYType.getAttr(threadId);\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n const thread = yTypeToThread(yThread);\n return thread;\n }\n\n public getThreads(): Map<string, ThreadData> {\n const threadMap = new Map<string, ThreadData>();\n this.threadsYType.forEachAttr((yThread: any, id: string | number) => {\n if (yThread instanceof Y.Type) {\n threadMap.set(String(id), yTypeToThread(yThread));\n }\n });\n return threadMap;\n }\n\n public subscribe(cb: (threads: Map<string, ThreadData>) => void) {\n const observer = () => {\n cb(this.getThreads());\n };\n\n this.threadsYType.observeDeep(observer);\n\n return () => {\n this.threadsYType.unobserveDeep(observer);\n };\n }\n}\n","import * as Y from \"@y/y\";\nimport type { CommentBody } from \"../../comments/types.js\";\nimport type { ThreadStoreAuth } from \"../../comments/threadstore/ThreadStoreAuth.js\";\nimport { YjsThreadStoreBase } from \"./YjsThreadStoreBase.js\";\n\n/**\n * This is a REST-based implementation of the YjsThreadStoreBase for @y/y (v14).\n * It Reads data directly from the underlying document (same as YjsThreadStore),\n * but for Writes, it sends data to a REST API that should:\n * - check the user has the correct permissions to make the desired changes\n * - apply the updates to the underlying Yjs document\n *\n * (see https://github.com/TypeCellOS/BlockNote-demo-nextjs-hocuspocus)\n *\n * The reason we still use the Yjs document as underlying storage is that it makes it easy to\n * sync updates in real-time to other collaborators.\n * (but technically, you could also implement a different storage altogether\n * and not store the thread related data in the Yjs document)\n */\nexport class RESTYjsThreadStore extends YjsThreadStoreBase {\n constructor(\n private readonly BASE_URL: string,\n private readonly headers: Record<string, string>,\n threadsYType: Y.Type,\n auth: ThreadStoreAuth,\n ) {\n super(threadsYType, auth);\n }\n\n private doRequest = async (path: string, method: string, body?: any) => {\n const response = await fetch(`${this.BASE_URL}${path}`, {\n method,\n body: JSON.stringify(body),\n headers: {\n \"Content-Type\": \"application/json\",\n ...this.headers,\n },\n });\n\n if (!response.ok) {\n throw new Error(`Failed to ${method} ${path}: ${response.statusText}`);\n }\n\n return response.json();\n };\n\n public addThreadToDocument = async (options: {\n threadId: string;\n selection: {\n head: number;\n anchor: number;\n };\n }) => {\n const { threadId, ...rest } = options;\n return this.doRequest(`/${threadId}/addToDocument`, \"POST\", rest);\n };\n\n public createThread = async (options: {\n initialComment: {\n body: CommentBody;\n metadata?: any;\n };\n metadata?: any;\n }) => {\n return this.doRequest(\"\", \"POST\", options);\n };\n\n public addComment = (options: {\n comment: {\n body: CommentBody;\n metadata?: any;\n };\n threadId: string;\n }) => {\n const { threadId, ...rest } = options;\n return this.doRequest(`/${threadId}/comments`, \"POST\", rest);\n };\n\n public updateComment = (options: {\n comment: {\n body: CommentBody;\n metadata?: any;\n };\n threadId: string;\n commentId: string;\n }) => {\n const { threadId, commentId, ...rest } = options;\n return this.doRequest(`/${threadId}/comments/${commentId}`, \"PUT\", rest);\n };\n\n public deleteComment = (options: {\n threadId: string;\n commentId: string;\n softDelete?: boolean;\n }) => {\n const { threadId, commentId, ...rest } = options;\n return this.doRequest(\n `/${threadId}/comments/${commentId}?soft=${!!rest.softDelete}`,\n \"DELETE\",\n );\n };\n\n public deleteThread = (options: { threadId: string }) => {\n return this.doRequest(`/${options.threadId}`, \"DELETE\");\n };\n\n public resolveThread = (options: { threadId: string }) => {\n return this.doRequest(`/${options.threadId}/resolve`, \"POST\");\n };\n\n public unresolveThread = (options: { threadId: string }) => {\n return this.doRequest(`/${options.threadId}/unresolve`, \"POST\");\n };\n\n public addReaction = (options: {\n threadId: string;\n commentId: string;\n emoji: string;\n }) => {\n const { threadId, commentId, ...rest } = options;\n return this.doRequest(\n `/${threadId}/comments/${commentId}/reactions`,\n \"POST\",\n rest,\n );\n };\n\n public deleteReaction = (options: {\n threadId: string;\n commentId: string;\n emoji: string;\n }) => {\n return this.doRequest(\n `/${options.threadId}/comments/${options.commentId}/reactions/${options.emoji}`,\n \"DELETE\",\n );\n };\n}\n","import { uuidv4 } from \"lib0/random\";\nimport * as Y from \"@y/y\";\nimport type {\n CommentBody,\n CommentData,\n ThreadData,\n} from \"../../comments/types.js\";\nimport type { ThreadStoreAuth } from \"../../comments/threadstore/ThreadStoreAuth.js\";\nimport { YjsThreadStoreBase } from \"./YjsThreadStoreBase.js\";\nimport {\n commentToYType,\n threadToYType,\n yTypeToComment,\n yTypeToThread,\n} from \"./yjsHelpers.js\";\n\n/**\n * This is a @y/y (v14)-based implementation of the ThreadStore interface.\n *\n * It reads and writes thread / comments information directly to the underlying Yjs Document.\n *\n * @important While this is the easiest to add to your app, there are two challenges:\n * - The user needs to be able to write to the Yjs document to store the information.\n * So a user without write access to the Yjs document cannot leave any comments.\n * - Even with write access, the operations are not secure. Unless your Yjs server\n * guards against malicious operations, it's technically possible for one user to make changes to another user's comments, etc.\n * (even though these options are not visible in the UI, a malicious user can make unauthorized changes to the underlying Yjs document)\n */\nexport class YjsThreadStore extends YjsThreadStoreBase {\n constructor(\n private readonly userId: string,\n threadsYType: Y.Type,\n auth: ThreadStoreAuth,\n ) {\n super(threadsYType, auth);\n }\n\n private transact = <T, R>(\n fn: (options: T) => R,\n ): ((options: T) => Promise<R>) => {\n return async (options: T) => {\n return this.threadsYType.doc!.transact(() => {\n return fn(options);\n });\n };\n };\n\n public createThread = this.transact(\n (options: {\n initialComment: {\n body: CommentBody;\n metadata?: any;\n };\n metadata?: any;\n }) => {\n if (!this.auth.canCreateThread()) {\n throw new Error(\"Not authorized\");\n }\n\n const date = new Date();\n\n const comment: CommentData = {\n type: \"comment\",\n id: uuidv4(),\n userId: this.userId,\n createdAt: date,\n updatedAt: date,\n reactions: [],\n metadata: options.initialComment.metadata,\n body: options.initialComment.body,\n };\n\n const thread: ThreadData = {\n type: \"thread\",\n id: uuidv4(),\n createdAt: date,\n updatedAt: date,\n comments: [comment],\n resolved: false,\n metadata: options.metadata,\n };\n\n this.threadsYType.setAttr(thread.id, threadToYType(thread));\n\n return thread;\n },\n );\n\n // YjsThreadStore does not support addThreadToDocument\n public addThreadToDocument = undefined;\n\n public addComment = this.transact(\n (options: {\n comment: {\n body: CommentBody;\n metadata?: any;\n };\n threadId: string;\n }) => {\n const yThread = this.threadsYType.getAttr(options.threadId) as\n | Y.Type\n | undefined;\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n if (!this.auth.canAddComment(yTypeToThread(yThread))) {\n throw new Error(\"Not authorized\");\n }\n\n const date = new Date();\n const comment: CommentData = {\n type: \"comment\",\n id: uuidv4(),\n userId: this.userId,\n createdAt: date,\n updatedAt: date,\n deletedAt: undefined,\n reactions: [],\n metadata: options.comment.metadata,\n body: options.comment.body,\n };\n\n (yThread.getAttr(\"comments\") as Y.Type).push([commentToYType(comment)]);\n\n yThread.setAttr(\"updatedAt\", new Date().getTime());\n return comment;\n },\n );\n\n public updateComment = this.transact(\n (options: {\n comment: {\n body: CommentBody;\n metadata?: any;\n };\n threadId: string;\n commentId: string;\n }) => {\n const yThread = this.threadsYType.getAttr(options.threadId) as\n | Y.Type\n | undefined;\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n const commentsType = yThread.getAttr(\"comments\") as Y.Type;\n const yCommentIndex = yTypeFindIndex(\n commentsType,\n (comment) => (comment as Y.Type).getAttr(\"id\") === options.commentId,\n );\n\n if (yCommentIndex === -1) {\n throw new Error(\"Comment not found\");\n }\n\n const yComment = commentsType.get(yCommentIndex) as Y.Type;\n\n if (!this.auth.canUpdateComment(yTypeToComment(yComment))) {\n throw new Error(\"Not authorized\");\n }\n\n yComment.setAttr(\"body\", options.comment.body);\n yComment.setAttr(\"updatedAt\", new Date().getTime());\n yComment.setAttr(\"metadata\", options.comment.metadata);\n },\n );\n\n public deleteComment = this.transact(\n (options: {\n threadId: string;\n commentId: string;\n softDelete?: boolean;\n }) => {\n const yThread = this.threadsYType.getAttr(options.threadId) as\n | Y.Type\n | undefined;\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n const commentsType = yThread.getAttr(\"comments\") as Y.Type;\n const yCommentIndex = yTypeFindIndex(\n commentsType,\n (comment) => (comment as Y.Type).getAttr(\"id\") === options.commentId,\n );\n\n if (yCommentIndex === -1) {\n throw new Error(\"Comment not found\");\n }\n\n const yComment = commentsType.get(yCommentIndex) as Y.Type;\n\n if (!this.auth.canDeleteComment(yTypeToComment(yComment))) {\n throw new Error(\"Not authorized\");\n }\n\n if (yComment.getAttr(\"deletedAt\")) {\n throw new Error(\"Comment already deleted\");\n }\n\n if (options.softDelete) {\n yComment.setAttr(\"deletedAt\", new Date().getTime());\n yComment.setAttr(\"body\", undefined);\n } else {\n commentsType.delete(yCommentIndex);\n }\n\n if (\n commentsType\n .toArray()\n .every((comment) => (comment as Y.Type).getAttr(\"deletedAt\"))\n ) {\n // all comments deleted\n if (options.softDelete) {\n yThread.setAttr(\"deletedAt\", new Date().getTime());\n } else {\n this.threadsYType.deleteAttr(options.threadId);\n }\n }\n\n yThread.setAttr(\"updatedAt\", new Date().getTime());\n },\n );\n\n public deleteThread = this.transact((options: { threadId: string }) => {\n if (\n !this.auth.canDeleteThread(\n yTypeToThread(this.threadsYType.getAttr(options.threadId) as Y.Type),\n )\n ) {\n throw new Error(\"Not authorized\");\n }\n\n this.threadsYType.deleteAttr(options.threadId);\n });\n\n public resolveThread = this.transact((options: { threadId: string }) => {\n const yThread = this.threadsYType.getAttr(options.threadId) as\n | Y.Type\n | undefined;\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n if (!this.auth.canResolveThread(yTypeToThread(yThread))) {\n throw new Error(\"Not authorized\");\n }\n\n yThread.setAttr(\"resolved\", true);\n yThread.setAttr(\"resolvedUpdatedAt\", new Date().getTime());\n yThread.setAttr(\"resolvedBy\", this.userId);\n });\n\n public unresolveThread = this.transact((options: { threadId: string }) => {\n const yThread = this.threadsYType.getAttr(options.threadId) as\n | Y.Type\n | undefined;\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n if (!this.auth.canUnresolveThread(yTypeToThread(yThread))) {\n throw new Error(\"Not authorized\");\n }\n\n yThread.setAttr(\"resolved\", false);\n yThread.setAttr(\"resolvedUpdatedAt\", new Date().getTime());\n });\n\n public addReaction = this.transact(\n (options: { threadId: string; commentId: string; emoji: string }) => {\n const yThread = this.threadsYType.getAttr(options.threadId) as\n | Y.Type\n | undefined;\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n const commentsType = yThread.getAttr(\"comments\") as Y.Type;\n const yCommentIndex = yTypeFindIndex(\n commentsType,\n (comment) => (comment as Y.Type).getAttr(\"id\") === options.commentId,\n );\n\n if (yCommentIndex === -1) {\n throw new Error(\"Comment not found\");\n }\n\n const yComment = commentsType.get(yCommentIndex) as Y.Type;\n\n if (!this.auth.canAddReaction(yTypeToComment(yComment), options.emoji)) {\n throw new Error(\"Not authorized\");\n }\n\n const date = new Date();\n\n const key = `${this.userId}-${options.emoji}`;\n\n const reactionsByUser = yComment.getAttr(\"reactionsByUser\") as Y.Type;\n\n if (reactionsByUser.hasAttr(key)) {\n // already exists\n return;\n } else {\n const reaction = new Y.Type();\n reaction.setAttr(\"emoji\", options.emoji);\n reaction.setAttr(\"createdAt\", date.getTime());\n reaction.setAttr(\"userId\", this.userId);\n reactionsByUser.setAttr(key, reaction);\n }\n },\n );\n\n public deleteReaction = this.transact(\n (options: { threadId: string; commentId: string; emoji: string }) => {\n const yThread = this.threadsYType.getAttr(options.threadId) as\n | Y.Type\n | undefined;\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n const commentsType = yThread.getAttr(\"comments\") as Y.Type;\n const yCommentIndex = yTypeFindIndex(\n commentsType,\n (comment) => (comment as Y.Type).getAttr(\"id\") === options.commentId,\n );\n\n if (yCommentIndex === -1) {\n throw new Error(\"Comment not found\");\n }\n\n const yComment = commentsType.get(yCommentIndex) as Y.Type;\n\n if (\n !this.auth.canDeleteReaction(yTypeToComment(yComment), options.emoji)\n ) {\n throw new Error(\"Not authorized\");\n }\n\n const key = `${this.userId}-${options.emoji}`;\n\n const reactionsByUser = yComment.getAttr(\"reactionsByUser\") as Y.Type;\n\n reactionsByUser.deleteAttr(key);\n },\n );\n}\n\nfunction yTypeFindIndex(yType: Y.Type, predicate: (item: any) => boolean) {\n for (let i = 0; i < yType.length; i++) {\n if (predicate(yType.get(i))) {\n return i;\n }\n }\n return -1;\n}\n","import * as Y from \"@y/y\";\nimport { decodeAny, encodeAny } from \"lib0/buffer\";\n\nimport {\n CURRENT_VERSION_ID,\n sortSnapshotsNewestFirst,\n VersioningEndpointsFactory,\n type VersioningEndpoints,\n type VersionSnapshot,\n} from \"../../extensions/Versioning/index.js\";\nimport { uint32 } from \"lib0/random\";\nimport { YCursorExtension } from \"../extensions/YCursorPlugin.js\";\nimport { YSyncExtension } from \"../extensions/YSync.js\";\n\n/**\n * Name of the root {@link Y.Type} map on the live collaboration doc that stores\n * a mutable `versionId -> name` mapping. Because YHub attributions are\n * immutable, version names that need to be editable (renamed) live here on the\n * Y.Doc instead of (or in addition to) the immutable `name` attribution.\n */\nconst VERSION_NAMES_MAP = \"__bn_version_names\";\n\n/**\n * Options for creating a YHub versioning endpoints instance.\n */\nexport interface YHubVersioningOptions {\n /**\n * Base URL of the YHub API (e.g. `\"https://yhub.example.com\"`).\n * Must **not** include a trailing slash.\n */\n baseUrl: string;\n\n /** YHub organisation identifier. */\n org: string;\n\n /** Document identifier within the organisation. */\n docId: string;\n\n /**\n * Optional headers to include in every request (e.g. authentication tokens).\n */\n headers?: Record<string, string>;\n\n /**\n * Maximum number of activity entries to fetch when listing versions.\n * @default 50\n */\n activityLimit?: number;\n\n /**\n * When set, forwarded as the `group` query param to the YHub activity API,\n * controlling whether adjacent edits are grouped into single entries.\n */\n group?: boolean;\n\n /**\n * Maximum gap (in ms) between edits for them to be grouped together.\n * Forwarded as the `groupMaxGap` query param.\n * @default 10000\n */\n groupMaxGap?: number;\n\n /**\n * Maximum total duration (in ms) a single group of edits may span.\n * When set, forwarded as the `groupMaxDuration` query param.\n */\n groupMaxDuration?: number;\n\n // TODO mergeUsers is not in standard yhub, but it exists in our fork.\n /**\n * When `true`, adjacent edits are grouped together even when made by\n * *different* users (their ids accumulate in the grouped entry's `by`).\n * When `false` (the default), only same-user adjacent edits are merged.\n * Forwarded as the `mergeUsers` query param.\n * @default false\n */\n mergeUsers?: boolean;\n}\n\n/**\n * Shape of a single activity entry returned by the YHub\n * `GET /activity/{org}/{docId}` endpoint (after `decodeAny`).\n */\ninterface YHubActivityEntry {\n /** Start of the change window (unix-ms timestamp). */\n from: number;\n /** End of the change window (unix-ms timestamp). */\n to: number;\n /** Comma separated list of user-ids that matches the attribution */\n by?: string;\n /** Custom attribution key-value pairs (when `customAttributions=true`). */\n customAttributions?: Array<{ k: string; v: string }>;\n}\n\n/**\n * Shape returned by the YHub `GET /changeset/{org}/{docId}` endpoint (after\n * `decodeAny`).\n */\ninterface YHubChangeset {\n /** Full Y.Doc state **before** the changeset window. */\n prevDoc?: Uint8Array;\n /** Full Y.Doc state **after** the changeset window. */\n nextDoc?: Uint8Array;\n /**\n * Encoded {@link Y.ContentMap} describing who authored each change in the\n * window and when. Present when the changeset is requested with\n * `attributions=true`.\n */\n attributions?: Uint8Array;\n}\n\n/**\n * Whether an activity entry is a version marker (created with a `type:version`\n * custom attribution) as opposed to a plain edit.\n */\nfunction isVersionEntry(entry: YHubActivityEntry): boolean {\n return (\n entry.customAttributions?.some(\n (a) => a.k === \"type\" && a.v === \"version\",\n ) ?? false\n );\n}\n\n/**\n * Convert a YHub activity entry into a {@link VersionSnapshot}.\n *\n * Version markers (entries with a `type:version` custom attribution) map to\n * named snapshots: the `id` attribution becomes the snapshot identifier and the\n * `name` attribution its name. Any other (plain edit) entry maps to a\n * history-only snapshot with a synthetic `history-<to>-<index>` id and no name.\n * In both cases the entry's `by` user-ids are passed through raw on\n * {@link VersionSnapshot.by} — resolving them to user info is the view layer's\n * job.\n *\n * The history id embeds the entry's `index` within the activity response\n * because YHub can emit multiple activity entries sharing the same `to`\n * timestamp (e.g. distinct same-`insertAt` patches that grouping did not merge),\n * and `to` alone would then produce colliding `history-<to>` ids — duplicate\n * React keys in the sidebar. The `index` disambiguates them. The changeset\n * lookups (`getContent`/`getAttributions`/`restore`) key off\n * {@link VersionSnapshot.createdAt} (= `entry.to`), never the id, so embedding\n * the index in the id is safe.\n */\nfunction activityToSnapshot(\n entry: YHubActivityEntry,\n index: number,\n): VersionSnapshot | undefined {\n const by = entry.by\n ?.split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n const byField = by && by.length > 0 ? by : undefined;\n\n if (isVersionEntry(entry)) {\n const id = entry.customAttributions?.find((a) => a.k === \"id\")?.v;\n if (id === undefined) {\n return undefined;\n }\n const attributionName = entry.customAttributions?.find(\n (a) => a.k === \"name\",\n )?.v;\n return {\n id,\n name: attributionName,\n createdAt: entry.to,\n updatedAt: entry.to,\n by: byField,\n };\n }\n\n return {\n id: `history-${entry.to}-${index}`,\n createdAt: entry.to,\n updatedAt: entry.to,\n by: byField,\n };\n}\n\nasync function yhubFetch(\n url: string,\n headers: Record<string, string>,\n init?: RequestInit,\n): Promise<ArrayBuffer> {\n const res = await fetch(url, {\n ...init,\n headers: {\n ...headers,\n ...(init?.headers instanceof Headers\n ? Object.fromEntries(init.headers.entries())\n : Array.isArray(init?.headers)\n ? Object.fromEntries(init.headers)\n : init?.headers),\n },\n });\n if (!res.ok) {\n throw new Error(\n `YHub request failed: ${res.status} ${res.statusText} (${url})`,\n );\n }\n return res.arrayBuffer();\n}\n\n/**\n * Create a {@link VersioningEndpoints} implementation backed by the\n * [YHub](https://github.com/yjs/yhub) HTTP API.\n *\n * Versions are created by PATCHing the document with custom attributions\n * (`type:version` + an optional `name`). The `list` endpoint returns the full\n * activity timeline, mapping `type:version` markers to named versions and every\n * other entry to a history-only snapshot, so the sidebar can show both the\n * named versions and the complete edit history.\n *\n * A version's id lives in immutable YHub attributions (`type:version` + `id`),\n * so it is fixed at creation time. Version *names*, however, are stored in a\n * mutable `__bn_version_names` map on the live collaboration doc (see\n * {@link VERSION_NAMES_MAP}), so `rename` is supported and simply updates that\n * store.\n *\n * @example\n * ```ts\n * import { withCollaboration } from \"@blocknote/core/y\";\n * import { createYHubVersioningEndpoints } from \"@blocknote/core/y\";\n *\n * const editor = BlockNoteEditor.create(\n * withCollaboration({\n * collaboration: {\n * fragment,\n * user: { name: \"Alice\", color: \"#ff0\" },\n * provider,\n * versioningEndpoints: createYHubVersioningEndpoints({\n * baseUrl: \"https://yhub.example.com\",\n * org: \"my-org\",\n * docId: \"my-doc\",\n * }),\n * },\n * }),\n * );\n * ```\n */\nexport function createYHubVersioningEndpoints(\n options: YHubVersioningOptions,\n): VersioningEndpointsFactory<Y.Type, Uint8Array, Y.ContentMap> {\n const {\n baseUrl,\n org,\n docId,\n headers = {},\n activityLimit = 50,\n group,\n } = options;\n\n const activityUrl = `${baseUrl}/activity/${org}/${docId}`;\n const changesetUrl = `${baseUrl}/changeset/${org}/${docId}`;\n const rollbackUrl = `${baseUrl}/rollback/${org}/${docId}`;\n\n return (editor) => {\n /**\n * The mutable per-id version-name store on the live collaboration doc.\n *\n * Returns the root {@link VERSION_NAMES_MAP} map-typed {@link Y.Type}, which\n * uses `setAttr`/`getAttr` for keyed access (this Yjs fork has a single\n * unified `Y.Type` rather than a distinct `Y.Map`). `undefined` until the\n * live doc has been captured from a `create` call.\n */\n const getVersionNamesMap = (): Y.Type | undefined => {\n const fragment =\n editor.getExtension<typeof YSyncExtension>(\"ySync\")?.fragment.doc;\n // `fragment` is undefined until the live doc has been captured (e.g. no\n // ySync extension attached yet); return undefined rather than throwing so\n // callers can gracefully fall back to the immutable name attribution.\n return fragment?.get(VERSION_NAMES_MAP);\n };\n\n /**\n * Build the synthetic \"current version\" snapshot, or `undefined` when the\n * live document matches the latest saved version (no edits since).\n *\n * Both lookups are made here, independently of the grouped `list()` request:\n *\n * - the newest activity entry of *any* kind (ungrouped, so its `to` is the\n * true last-edit time), and\n * - the newest **version marker** (via the `withCustomAttributions`\n * server-side filter).\n *\n * Deriving the marker time from `list()`'s grouped entries would be wrong:\n * with grouping (especially `mergeUsers`) the newest marker's group absorbs\n * the later unsaved edit, so the group's `to` equals the edit's `to` and the\n * comparison below can never fire. Fetching the marker unmerged avoids that.\n */\n const getCurrentVersionEntry = async (): Promise<\n VersionSnapshot | undefined\n > => {\n const latestParams = new URLSearchParams({\n order: \"desc\",\n limit: \"1\",\n customAttributions: \"true\",\n });\n const latestVersionParams = new URLSearchParams({\n order: \"desc\",\n limit: \"1\",\n customAttributions: \"true\",\n // Server-side filter to `type:version` markers only, so this ignores the\n // plain edits that would otherwise be the newest entries.\n withCustomAttributions: \"type:version\",\n });\n\n const [latestBuf, latestVersionBuf] = await Promise.all([\n yhubFetch(`${activityUrl}?${latestParams}`, headers),\n yhubFetch(`${activityUrl}?${latestVersionParams}`, headers),\n ]);\n const latestEdit = (\n decodeAny(new Uint8Array(latestBuf)) as YHubActivityEntry[]\n )[0];\n const latestVersion = (\n decodeAny(new Uint8Array(latestVersionBuf)) as YHubActivityEntry[]\n )[0];\n\n if (!latestEdit || latestEdit.to <= (latestVersion?.to ?? 0)) {\n return undefined;\n }\n\n // Build the synthetic entry directly rather than via `activityToSnapshot`,\n // whose `id` comes from a string-typed wire attribution — the current\n // entry's id is the `CURRENT_VERSION_ID` symbol, not a real version id.\n const by =\n latestEdit.by\n ?.split(\",\")\n .map((t) => t.trim())\n .filter(Boolean) ?? [];\n return {\n id: CURRENT_VERSION_ID,\n createdAt: latestEdit.to,\n updatedAt: latestEdit.to,\n by: by.length > 0 ? by : undefined,\n };\n };\n\n /**\n * PATCH the current document state to YHub, optionally with custom\n * attributions. Used both for creating named version markers and for\n * backing up the document before a restore.\n */\n const patchDoc = async (\n fragment: Y.Type,\n customAttributions: Array<{ k: string; v: any }>,\n by?: string,\n ) => {\n const doc = fragment.doc;\n if (!doc) {\n throw new Error(\n \"Cannot patch document: the Y.Type is not attached to a Y.Doc.\",\n );\n }\n\n // YHub only records custom attributions when they attach to NEW content\n // that survives its server-side diff. An update-less PATCH is rejected\n // (400 — \"at least one of update or awareness must be present\"), and even\n // if it weren't, there'd be no content for the attributions to ride on, so\n // no activity entry is created. YHub has no metadata-only marker path.\n //\n // So we introduce a tiny piece of novel content for the marker to attach\n // to: a single insert into a dedicated `__bn_version_markers` fragment that\n // the editor never renders. A fresh Y.Doc guarantees a clientID/content the\n // server has never seen, so the diff is non-empty and the attributions land\n // on it. The reconstructed document at this version's timestamp still\n // contains the full editor content — this marker only ever lives in the\n // throwaway fragment.\n const markerDoc = new Y.Doc();\n markerDoc.get(\"__bn_version_markers\", \"XmlFragment\").insert(0, [\"v\"]);\n const update = Y.encodeStateAsUpdate(markerDoc);\n\n const body: Record<string, unknown> = { update, customAttributions };\n\n await yhubFetch(\n `${baseUrl}/ydoc/${org}/${docId}${by ? `?userid=${by}` : \"\"}`,\n headers,\n {\n method: \"PATCH\",\n body: encodeAny(body) as BufferSource,\n },\n );\n };\n\n /**\n * Create a named version marker for the current document state by PATCHing\n * it with `type:version` custom attributions.\n */\n const create: VersioningEndpoints<\n Y.Type,\n Uint8Array,\n Y.ContentMap\n >[\"create\"] = async (fragment, options) => {\n const id = String(uint32());\n const now = Date.now();\n\n if (options?.name) {\n getVersionNamesMap()?.setAttr(id, options.name);\n }\n\n const customAttributions: Array<{ k: string; v: string }> = [\n { k: \"type\", v: \"version\" },\n { k: \"id\", v: id },\n ];\n if (options?.name) {\n customAttributions.push({ k: \"name\", v: options.name });\n }\n\n const user = editor\n .getExtension<typeof YCursorExtension>(\"yCursor\")\n ?.getUser();\n await patchDoc(fragment, customAttributions, user?.id);\n\n return {\n id,\n name: options?.name,\n createdAt: now,\n updatedAt: now,\n by: user?.id,\n };\n };\n\n /**\n * Reconstruct the full document state as it was at a given `to` timestamp.\n *\n * The changeset endpoint builds `nextDoc` purely from the `to` timestamp\n * range — it ignores `withCustomAttributions` for doc reconstruction (that\n * filter only scopes the attribution overlay). So historical document state\n * can only be retrieved by timestamp, never by the version's `id`.\n */\n const getContentAt = async (to: number): Promise<Uint8Array> => {\n const params = new URLSearchParams({\n ydoc: \"true\",\n to: String(to),\n });\n\n const buf = await yhubFetch(`${changesetUrl}?${params}`, headers);\n const changeset = decodeAny(new Uint8Array(buf)) as YHubChangeset;\n\n if (!changeset.nextDoc) {\n throw new Error(`YHub returned no document state at timestamp ${to}.`);\n }\n\n return Y.convertUpdateFormatV1ToV2(changeset.nextDoc);\n };\n\n /**\n * Fetch the full document content for a saved version snapshot.\n *\n * The snapshot's `createdAt` is the activity entry's `to` timestamp (see\n * {@link activityToSnapshot}), which is exactly what the changeset API needs.\n */\n const getContent: VersioningEndpoints<\n Y.Type,\n Uint8Array,\n Y.ContentMap\n >[\"getContent\"] = async (snapshot) => {\n return getContentAt(snapshot.createdAt);\n };\n\n /**\n * Fetch the authorship attributions for the changes between two snapshots\n * (or from the start of the document when `compareTo` is omitted).\n *\n * Snapshots carry their `to` timestamp directly in `createdAt`, so no\n * activity lookup is needed to resolve the changeset window.\n */\n const getAttributions: VersioningEndpoints<\n Y.Type,\n Uint8Array,\n Y.ContentMap\n >[\"getAttributions\"] = async (snapshot, compareTo) => {\n const to = snapshot.createdAt;\n const from = compareTo !== undefined ? compareTo.createdAt : 0;\n\n const params = new URLSearchParams({\n from: String(from),\n to: String(to),\n attributions: \"true\",\n });\n\n const buf = await yhubFetch(`${changesetUrl}?${params}`, headers);\n const changeset = decodeAny(new Uint8Array(buf)) as YHubChangeset;\n\n if (!changeset.attributions) {\n throw new Error(\n `YHub returned no attributions for snapshot ${String(snapshot.id)}.`,\n );\n }\n\n return Y.decodeContentMap(changeset.attributions);\n };\n\n /**\n * Restore the document to a saved version: fetch the target version's\n * content and roll back everything after it.\n *\n * The snapshot's `createdAt` is the activity entry's `to` timestamp.\n */\n const restore: VersioningEndpoints<\n Y.Type,\n Uint8Array,\n Y.ContentMap\n >[\"restore\"] = async (_fragment, snapshot) => {\n const to = snapshot.createdAt;\n const snapshotContent = await getContentAt(to);\n\n await yhubFetch(`${rollbackUrl}?from=${to}`, headers, {\n method: \"POST\",\n body: encodeAny({ from: to }) as BufferSource,\n });\n\n return snapshotContent;\n };\n\n /**\n * Rename a saved version by updating its entry in the mutable\n * {@link VERSION_NAMES_MAP} store on the live collaboration doc.\n *\n * The version's `id` remains fixed in its immutable YHub attributions —\n * only the editable name in the map is changed. Passing an empty or\n * `undefined` name clears the entry (falling back to the immutable `name`\n * attribution captured at creation time).\n */\n const rename: VersioningEndpoints<\n Y.Type,\n Uint8Array,\n Y.ContentMap\n >[\"rename\"] = async (snapshot, name) => {\n if (typeof snapshot.id !== \"string\") {\n // CURRENT_VERSION_ID (symbol) is not renameable.\n return;\n }\n const map = getVersionNamesMap();\n if (!map) {\n throw new Error(\n \"Cannot rename version: no live collaboration document is available.\",\n );\n }\n if (name === undefined || name === \"\") {\n map.deleteAttr(snapshot.id);\n } else {\n map.setAttr(snapshot.id, name);\n }\n };\n\n /**\n * List the full version timeline (newest first), plus a synthetic\n * \"current version\" entry when the live document has unsaved edits.\n *\n * Returns the entire activity timeline: `type:version` markers are mapped\n * to named snapshots and every other entry to a history-only snapshot (see\n * {@link activityToSnapshot}), so the sidebar can offer both a \"named\n * versions\" and a full \"history\" view. Author user-ids are passed through\n * raw on {@link VersionSnapshot.by} — the view layer resolves them to user\n * info via the versioning extension's user store.\n */\n const list: VersioningEndpoints<\n Y.Type,\n Uint8Array,\n Y.ContentMap\n >[\"list\"] = async () => {\n // Read the grouping knobs fresh from `options` so a caller mutating the\n // object it passed in reconfigures grouping on the next refresh (see the\n // note where these are deliberately left out of the destructure above).\n const groupMaxGap = options.groupMaxGap ?? 10000;\n const groupMaxDuration = options.groupMaxDuration;\n const mergeUsers = options.mergeUsers;\n\n const params = new URLSearchParams({\n order: \"desc\",\n limit: String(activityLimit),\n customAttributions: \"true\",\n });\n // Always send a concrete `groupMaxGap`. Sending\n // `String(undefined)` here would make the server `parseInt(\"undefined\")`\n // to NaN, silently disabling grouping — which surfaces every same-`to`\n // attribution as its own history entry and produces duplicate React keys.\n params.set(\"groupMaxGap\", String(groupMaxGap));\n if (group !== undefined) {\n params.set(\"group\", String(group));\n }\n if (groupMaxDuration !== undefined) {\n params.set(\"groupMaxDuration\", String(groupMaxDuration));\n }\n if (mergeUsers !== undefined) {\n params.set(\"mergeUsers\", String(mergeUsers));\n }\n\n const buf = await yhubFetch(`${activityUrl}?${params}`, headers);\n const entries = decodeAny(new Uint8Array(buf)) as YHubActivityEntry[];\n\n const snapshots = sortSnapshotsNewestFirst(\n entries\n .map((entry, i) => activityToSnapshot(entry, i))\n .filter((s): s is VersionSnapshot => s !== undefined)\n // Prefer the mutable per-id name from the live doc's\n // `__bn_version_names` store over the immutable `name` attribution,\n // so renames (which only mutate that store) are reflected here.\n .map((snapshot) => {\n const attributionName = snapshot.name;\n const mappedName =\n (typeof snapshot.id === \"string\"\n ? (getVersionNamesMap()?.getAttr(snapshot.id) as\n | string\n | undefined)\n : undefined) ?? attributionName;\n return { ...snapshot, name: mappedName };\n }),\n );\n\n // Surface a \"current version\" entry when the live document has edits\n // beyond the most recent saved version marker. `getCurrentVersionEntry`\n // makes its own unmerged lookups (see there), so it is unaffected by the\n // grouping/mergeUsers params used for the list above.\n //\n // This only re-evaluates when `list()` runs (sidebar open / refresh),\n // which matches how YHub versions load today.\n const currentEntry = await getCurrentVersionEntry();\n return currentEntry ? [currentEntry, ...snapshots] : snapshots;\n };\n\n return {\n list,\n create,\n getContent,\n getAttributions,\n restore,\n rename,\n };\n };\n}\n"],"mappings":"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"}