@blocknote/core
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
1 lines • 76.7 kB
Source Map (JSON)
{"version":3,"file":"yjs.cjs","names":[],"sources":["../src/yjs/utils.ts","../src/yjs/extensions/FixUpSchema.ts","../src/yjs/extensions/YCursorPlugin.ts","../src/yjs/extensions/YSync.ts","../src/yjs/extensions/YUndo.ts","../src/yjs/extensions/ForkYDoc.ts","../src/yjs/extensions/RelativePositionMapping.ts","../src/yjs/extensions/schemaMigration/migrationRules/moveColorAttributes.ts","../src/yjs/extensions/schemaMigration/migrationRules/index.ts","../src/yjs/extensions/schemaMigration/SchemaMigration.ts","../src/yjs/extensions/Versioning.ts","../src/yjs/extensions/index.ts","../src/yjs/comments/yjsHelpers.ts","../src/yjs/comments/YjsThreadStoreBase.ts","../src/yjs/comments/RESTYjsThreadStore.ts","../src/yjs/comments/YjsThreadStore.ts"],"sourcesContent":["import {\n prosemirrorToYDoc,\n prosemirrorToYXmlFragment,\n yXmlFragmentToProseMirrorRootNode,\n} from \"y-prosemirror\";\nimport * as Y from \"yjs\";\n\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\";\n\n/**\n * Find a Y.AbstractType in another Y.Doc that corresponds to the same\n * logical type in the original doc.\n */\nexport function findTypeInOtherYdoc<T extends Y.AbstractType<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 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, ytype.constructor as new () => T) as T;\n } else {\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.XmlFragment collaborative doc into a BlockNote document (BlockNote style JSON of all blocks)\n * @param editor BlockNote editor\n * @param xmlFragment Y.XmlFragment\n * @returns BlockNote document (BlockNote style JSON of all blocks)\n */\nexport function yXmlFragmentToBlocks<\n BSchema extends BlockSchema,\n ISchema extends InlineContentSchema,\n SSchema extends StyleSchema,\n>(\n editor: BlockNoteEditor<BSchema, ISchema, SSchema>,\n xmlFragment: Y.XmlFragment,\n) {\n const pmNode = yXmlFragmentToProseMirrorRootNode(\n xmlFragment,\n editor.pmSchema,\n );\n return docToBlocks<BSchema, ISchema, SSchema>(pmNode);\n}\n\n/**\n * Convert blocks to a Y.XmlFragment\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 xmlFragment XML fragment name\n * @returns Y.XmlFragment\n */\nexport function blocksToYXmlFragment<\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 xmlFragment?: Y.XmlFragment,\n) {\n return prosemirrorToYXmlFragment(\n _blocksToProsemirrorNode(editor, blocks),\n xmlFragment,\n );\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 xmlFragment 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 xmlFragment = \"prosemirror\",\n) {\n return yXmlFragmentToBlocks(editor, ydoc.getXmlFragment(xmlFragment));\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 xmlFragment 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 xmlFragment = \"prosemirror\",\n) {\n return prosemirrorToYDoc(\n _blocksToProsemirrorNode(editor, blocks),\n xmlFragment,\n );\n}\n","import { Attrs, Fragment, Mark, Node, NodeType } from \"prosemirror-model\";\n\nimport { isPlainContentNodeType } from \"../../api/pmUtil.js\";\nimport { createExtension } from \"../../editor/BlockNoteExtension.js\";\n\n// Collaboration-only patches to the live ProseMirror schema. Both are applied\n// once on `create`, before y-prosemirror binds and reconstructs the document.\nexport const FixUpSchemaExtension = createExtension(({ editor }) => {\n editor.on(\"create\", () => {\n const schema = editor.pmSchema;\n\n // 1. Preserve the initial block id.\n // When y-prosemirror creates an empty document, the `blockContainer` node is created with an `id` of `null`.\n // This causes the unique id extension to generate a new id for the initial block, which is not what we want\n // Since it will be randomly generated & cause there to be more updates to the ydoc\n // This is a hack to make it so that anytime `schema.doc.createAndFill` is called, the initial block id is already set to \"initialBlockId\"\n let cache: Node | undefined = undefined;\n // eslint-disable-next-line @typescript-eslint/unbound-method -- intentionally saving reference for monkey-patching\n const oldCreateAndFill = schema.nodes.doc.createAndFill;\n schema.nodes.doc.createAndFill = ((...args: any) => {\n if (cache) {\n return cache;\n }\n const ret = oldCreateAndFill.apply(schema.nodes.doc, args)!;\n\n // create a copy that we can mutate (otherwise, assigning attrs is not safe and corrupts the pm state)\n const jsonNode = JSON.parse(JSON.stringify(ret.toJSON()));\n jsonNode.content[0].content[0].attrs.id = \"initialBlockId\";\n\n cache = Node.fromJSON(schema, jsonNode);\n return cache;\n }) as unknown as typeof schema.nodes.doc.createAndFill;\n\n // 2. Drop marks a plain block no longer allows, instead of deleting the block.\n // When a block's content type changes across BlockNote versions — e.g. a\n // `codeBlock` that was `\"inline\"` (formatting marks like bold allowed) and is\n // now `\"plain\"` (only the `\"annotation\"` mark group: comments + suggestions) —\n // old Yjs documents carry text with marks the new node type disallows.\n //\n // y-prosemirror rebuilds each block by calling `schema.node(nodeName, attrs,\n // children)`, where `children` are text nodes that may still carry those\n // disallowed marks. `createChecked` then throws, and y-prosemirror's error\n // handler DELETES the whole block from the Yjs doc — a deletion that propagates\n // to every peer (data loss).\n //\n // Rather than mutating the Yjs doc to strip those marks, we leave them in place\n // (marks are just format attributes; they don't affect position counting, so\n // editing text is unaffected) and simply never materialize the disallowed ones\n // into ProseMirror. We do that by wrapping `schema.node` to filter each text\n // child's marks through the parent node type's `allowedMarks` before the node\n // is built. The wrapper is scoped to plain-content blocks (the only place this\n // migration applies); all other node types pass through untouched.\n //\n // NOTE: this covers marks that EXIST in the schema but aren't allowed on the\n // node. A mark whose type is entirely absent from the schema still hits\n // y-prosemirror's earlier `schema.mark` throw (and delete) path — that's a\n // different migration scenario and is out of scope here.\n // eslint-disable-next-line @typescript-eslint/unbound-method -- intentionally saving reference for monkey-patching\n const originalNode = schema.node;\n schema.node = function (\n this: typeof schema,\n type: string | NodeType,\n attrs?: Attrs | null,\n content?: Fragment | Node | readonly Node[],\n marks?: readonly Mark[],\n ) {\n const nodeType = typeof type === \"string\" ? this.nodes[type] : type;\n // y-prosemirror always passes `content` as a plain array of nodes; only\n // touch text children that carry marks.\n if (\n nodeType &&\n isPlainContentNodeType(this, nodeType) &&\n Array.isArray(content)\n ) {\n content = (content as readonly Node[]).map((child) =>\n child?.isText && child.marks.length\n ? child.mark(nodeType.allowedMarks(child.marks))\n : child,\n );\n }\n return originalNode.call(this, type, attrs, content, marks);\n } as typeof schema.node;\n });\n\n return {\n key: \"fixUpSchema\",\n } as const;\n});\n","import { defaultSelectionBuilder, yCursorPlugin } from \"y-prosemirror\";\nimport {\n createExtension,\n ExtensionOptions,\n} from \"../../editor/BlockNoteExtension.js\";\nimport type { 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 }\n\n const handleAwarenessChange = ({\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 return {\n key: \"yCursor\",\n mount() {\n if (\n awareness &&\n options.showCursorLabels !== \"always\" &&\n \"on\" in awareness &&\n typeof awareness.on === \"function\"\n ) {\n awareness.on(\"change\", handleAwarenessChange);\n\n return () => {\n if (\"off\" in awareness && typeof awareness.off === \"function\") {\n awareness.off(\"change\", handleAwarenessChange);\n }\n };\n }\n\n return undefined;\n },\n prosemirrorPlugins: [\n awareness\n ? yCursorPlugin(awareness, {\n selectionBuilder: defaultSelectionBuilder,\n cursorBuilder(user: CollaborationUser, clientID: number) {\n let cursorData = recentlyUpdatedCursors.get(clientID);\n\n if (!cursorData) {\n const cursorElement = (\n options.renderCursor ?? defaultCursorRender\n )(user);\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(Boolean),\n dependsOn: [\"ySync\"],\n updateUser(user: { name: string; color: string; [key: string]: string }) {\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 { ySyncPlugin } from \"y-prosemirror\";\nimport {\n ExtensionOptions,\n createExtension,\n} from \"../../editor/BlockNoteExtension.js\";\nimport type { CollaborationOptions } from \"./index.js\";\n\nexport const YSyncExtension = createExtension(\n ({ options }: ExtensionOptions<Pick<CollaborationOptions, \"fragment\">>) => {\n return {\n key: \"ySync\",\n prosemirrorPlugins: [ySyncPlugin(options.fragment)],\n runsBefore: [\"default\"],\n } as const;\n },\n);\n","import { redoCommand, undoCommand, yUndoPlugin } from \"y-prosemirror\";\nimport { createExtension } from \"../../editor/BlockNoteExtension.js\";\n\nexport const YUndoExtension = createExtension(() => {\n return {\n key: \"yUndo\",\n prosemirrorPlugins: [yUndoPlugin()],\n dependsOn: [\"yCursor\", \"ySync\"],\n undoCommand: undoCommand,\n redoCommand: redoCommand,\n } as const;\n});\n","import { ySyncPluginKey, yUndoPluginKey } from \"y-prosemirror\";\nimport * as Y from \"yjs\";\nimport type { BlockNoteEditor } from \"../../editor/BlockNoteEditor.js\";\nimport {\n createExtension,\n createStore,\n ExtensionOptions,\n} from \"../../editor/BlockNoteExtension.js\";\nimport type { CollaborationOptions } from \"./index.js\";\nimport { YCursorExtension } from \"./YCursorPlugin.js\";\nimport { YSyncExtension } from \"./YSync.js\";\nimport { YUndoExtension } from \"./YUndo.js\";\nimport { findTypeInOtherYdoc } from \"../utils.js\";\n\n/**\n * Point the `ySync` plugin state at `fragment`.\n *\n * Swapping the `ySync` plugin reconfigures the ProseMirror state, and\n * ProseMirror carries over the state of plugins that share a key instead of\n * re-initializing them. So the new plugin's `binding` (which is set from its\n * view, via a transaction) ends up on the new fragment, while `type` and `doc`\n * still point at the fragment the editor was bound to before. Anything reading\n * those (e.g. `RelativePositionMappingExtension`) would then mix up the two\n * Y.Docs, so we set them explicitly here.\n */\nfunction bindYSyncPluginStateTo(\n editor: BlockNoteEditor<any, any, any>,\n fragment: Y.XmlFragment,\n) {\n editor.transact((tr) =>\n tr.setMeta(ySyncPluginKey, { type: fragment, doc: fragment.doc }),\n );\n}\n\nexport const ForkYDocExtension = createExtension(\n ({ editor, options }: ExtensionOptions<CollaborationOptions>) => {\n let forkedState:\n | {\n originalFragment: Y.XmlFragment;\n undoStack: Y.UndoManager[\"undoStack\"];\n forkedFragment: Y.XmlFragment;\n }\n | undefined = undefined;\n\n const store = createStore({ isForked: false });\n\n return {\n key: \"yForkDoc\",\n store,\n /**\n * Fork the Y.js document from syncing to the remote,\n * allowing modifications to the document without affecting the remote.\n * These changes can later be rolled back or applied to the remote.\n */\n fork({\n /**\n * The initial update to apply to the forked document.\n * If not provided, the current document state is used.\n */\n initialUpdate,\n }: {\n initialUpdate?: Uint8Array;\n } = {}) {\n if (forkedState) {\n return;\n }\n\n const originalFragment = options.fragment;\n\n if (!originalFragment) {\n throw new Error(\"No fragment to fork from\");\n }\n\n const doc = new Y.Doc();\n // Copy the original document (or apply the provided update) to a new Yjs document\n Y.applyUpdate(\n doc,\n initialUpdate ?? Y.encodeStateAsUpdate(originalFragment.doc!),\n );\n\n // Find the forked fragment in the new Yjs document\n const forkedFragment = findTypeInOtherYdoc(originalFragment, doc);\n\n forkedState = {\n undoStack: yUndoPluginKey.getState(editor.prosemirrorState)!\n .undoManager.undoStack,\n originalFragment,\n forkedFragment,\n };\n\n const newOptions = {\n ...options,\n fragment: forkedFragment,\n };\n\n // Atomically swap the yjs plugins to avoid re-entrant dispatch issues\n // where y-prosemirror's view hooks can dispatch a transaction between\n // separate unregister/register calls, re-introducing stale plugins.\n editor.replaceExtension(\n [\"ySync\", \"yCursor\", \"yUndo\"],\n [\n YSyncExtension(newOptions),\n // No need to register the cursor plugin again, it's a local fork\n YUndoExtension(),\n ],\n );\n\n bindYSyncPluginStateTo(editor, forkedFragment);\n\n // Tell the store that the editor is now forked\n store.setState({ isForked: true });\n },\n\n /**\n * Resume syncing the Y.js document to the remote\n * If `keepChanges` is true, any changes that have been made to the forked document will be applied to the original document.\n * Otherwise, the original document will be restored and the changes will be discarded.\n */\n merge({ keepChanges }: { keepChanges: boolean }) {\n if (!forkedState) {\n return;\n }\n\n const { originalFragment, forkedFragment, undoStack } = forkedState;\n\n // Atomically swap the forked plugins back to the original ones\n editor.replaceExtension(\n [\"ySync\", \"yCursor\", \"yUndo\"],\n [\n YSyncExtension(options),\n YCursorExtension(options),\n YUndoExtension(),\n ],\n );\n\n bindYSyncPluginStateTo(editor, originalFragment);\n\n // Reset the undo stack to the original undo stack\n yUndoPluginKey.getState(\n editor.prosemirrorState,\n )!.undoManager.undoStack = undoStack;\n\n if (keepChanges) {\n // Apply any changes that have been made to the fork, onto the original doc\n const update = Y.encodeStateAsUpdate(\n forkedFragment.doc!,\n Y.encodeStateVector(originalFragment.doc!),\n );\n // Applying this change will add to the undo stack, allowing it to be undone normally\n Y.applyUpdate(originalFragment.doc!, update, editor);\n }\n // Reset the forked state\n forkedState = undefined;\n // Tell the store that the editor is no longer forked\n store.setState({ isForked: false });\n },\n } as const;\n },\n);\n","import {\n absolutePositionToRelativePosition,\n relativePositionToAbsolutePosition,\n ySyncPluginKey,\n} from \"y-prosemirror\";\nimport { createExtension } from \"../../editor/BlockNoteExtension.js\";\nimport type { PositionMappingExtension } from \"../../extensions/index.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) {\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 // If the document is empty, it has not been synced yet\n if (ySyncPluginState.binding.type.length === 0) {\n // so, we just fallback to the prosemirror position mapping extension\n // If a remote transaction or sync happens in this case. The position map will be invalidated,\n // and the positions will be moved to the end of the document\n // This is acceptable, because the document had not been synced so there are no positions to map properly into\n const fallback =\n editor.getExtension<typeof PositionMappingExtension>(\n \"positionMapping\",\n );\n if (!fallback) {\n throw new Error(\n \"positionMapping extension is not available; cannot map position before sync\",\n );\n }\n return fallback.mapPosition(position, side);\n }\n\n const relativePosition = absolutePositionToRelativePosition(\n position + (side === \"right\" ? 1 : -1),\n ySyncPluginState.binding.type,\n ySyncPluginState.binding.mapping,\n );\n\n return () => {\n const curYSyncPluginState = ySyncPluginKey.getState(\n editor.prosemirrorState,\n ) as typeof ySyncPluginState;\n // Resolve against the doc that owns the currently bound type, and not\n // against `curYSyncPluginState.doc`. Those can point at different\n // Y.Docs (e.g. right after forking the doc, see `ForkYDocExtension`),\n // in which case the resolved type wouldn't be part of the bound\n // fragment and the position would be reported as \"not found\".\n const boundType = curYSyncPluginState.binding.type;\n const pos = relativePositionToAbsolutePosition(\n boundType.doc,\n boundType,\n relativePosition,\n curYSyncPluginState.binding.mapping,\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 * as Y from \"yjs\";\n\nimport { MigrationRule } from \"./migrationRule.js\";\nimport { defaultProps } from \"../../../../blocks/defaultProps.js\";\n\n// Helper function to recursively traverse a `Y.XMLElement` and its descendant\n// elements.\nconst traverseElement = (\n rootElement: Y.XmlElement,\n cb: (element: Y.XmlElement) => void,\n) => {\n cb(rootElement);\n rootElement.forEach((element) => {\n if (element instanceof Y.XmlElement) {\n traverseElement(element, cb);\n }\n });\n};\n\n// Moves `textColor` and `backgroundColor` attributes from `blockContainer`\n// nodes to their child `blockContent` nodes. This is due to a schema change\n// introduced in PR #TODO.\nexport const moveColorAttributes: MigrationRule = (fragment, tr) => {\n // Stores necessary info for all `blockContainer` nodes which still have\n // `textColor` or `backgroundColor` attributes that need to be moved.\n const targetBlockContainers: Map<\n string,\n {\n textColor: string | undefined;\n backgroundColor: string | undefined;\n }\n > = new Map();\n // Finds all elements which still have `textColor` or `backgroundColor`\n // attributes in the current Yjs fragment.\n fragment.forEach((element) => {\n if (element instanceof Y.XmlElement) {\n traverseElement(element, (element) => {\n if (\n element.nodeName === \"blockContainer\" &&\n element.hasAttribute(\"id\")\n ) {\n const textColor = element.getAttribute(\"textColor\");\n const backgroundColor = element.getAttribute(\"backgroundColor\");\n\n const colors = {\n textColor:\n textColor === defaultProps.textColor.default\n ? undefined\n : textColor,\n backgroundColor:\n backgroundColor === defaultProps.backgroundColor.default\n ? undefined\n : backgroundColor,\n };\n\n if (colors.textColor || colors.backgroundColor) {\n targetBlockContainers.set(element.getAttribute(\"id\")!, colors);\n }\n }\n });\n }\n });\n\n if (targetBlockContainers.size === 0) {\n return false;\n }\n\n // Appends transactions to add the `textColor` and `backgroundColor`\n // attributes found on each `blockContainer` node to move them to the child\n // `blockContent` node.\n tr.doc.descendants((node, pos) => {\n if (\n node.type.name === \"blockContainer\" &&\n targetBlockContainers.has(node.attrs.id)\n ) {\n const el = tr.doc.nodeAt(pos + 1);\n if (!el) {\n throw new Error(\"No element found\");\n }\n\n tr.setNodeMarkup(pos + 1, undefined, {\n // preserve existing attributes\n ...el.attrs,\n // add the textColor and backgroundColor attributes\n ...targetBlockContainers.get(node.attrs.id),\n });\n }\n });\n\n return true;\n};\n","import { MigrationRule } from \"./migrationRule.js\";\nimport { moveColorAttributes } from \"./moveColorAttributes.js\";\n\nexport default [moveColorAttributes] as MigrationRule[];\n","import { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport * as Y from \"yjs\";\n\nimport {\n createExtension,\n ExtensionOptions,\n} from \"../../../editor/BlockNoteExtension.js\";\nimport migrationRules from \"./migrationRules/index.js\";\n\n// This plugin allows us to update collaboration YDocs whenever BlockNote's\n// underlying ProseMirror schema changes. The plugin reads the current Yjs\n// fragment and dispatches additional transactions to the ProseMirror state, in\n// case things are found in the fragment that don't adhere to the editor schema\n// and need to be fixed. These fixes are defined as `MigrationRule`s within the\n// `migrationRules` directory.\nexport const SchemaMigration = createExtension(\n ({ options }: ExtensionOptions<{ fragment: Y.XmlFragment }>) => {\n let migrationDone = false;\n const pluginKey = new PluginKey(\"schemaMigration\");\n\n return {\n key: \"schemaMigration\",\n prosemirrorPlugins: [\n new Plugin({\n key: pluginKey,\n appendTransaction: (transactions, _oldState, newState) => {\n if (migrationDone) {\n return undefined;\n }\n\n if (\n // If any of the transactions are not due to a yjs sync, we don't need to run the migration\n !transactions.some((tr) => tr.getMeta(\"y-sync$\")) ||\n // If none of the transactions result in a document change, we don't need to run the migration\n transactions.every((tr) => !tr.docChanged) ||\n // If the fragment is still empty, we can't run the migration (since it has not yet been applied to the Y.Doc)\n !options.fragment.firstChild\n ) {\n return undefined;\n }\n\n const tr = newState.tr;\n for (const migrationRule of migrationRules) {\n migrationRule(options.fragment, tr);\n }\n\n migrationDone = true;\n\n if (!tr.docChanged) {\n return undefined;\n }\n\n return tr;\n },\n }),\n ],\n } as const;\n },\n);\n","import * as Y from \"yjs\";\n\nimport type { BlockNoteEditor } from \"../../editor/BlockNoteEditor.js\";\nimport type { PreviewController } from \"../../extensions/Versioning/index.js\";\nimport type { CollaborationOptions } from \"./index.js\";\nimport { ForkYDocExtension } from \"./ForkYDoc.js\";\n\n/**\n * Creates a Yjs v13 adapter that provides the {@link PreviewController}\n * and `getCurrentDocument` callback required by the base\n * {@link VersioningExtension}.\n *\n * Delegates to the {@link ForkYDocExtension} for entering/exiting preview:\n * - **enterPreview**: calls `fork({ initialUpdate: snapshotContent })` to\n * switch the editor to a temporary doc built from the snapshot.\n * - **exitPreview**: calls `merge({ keepChanges: false })` to discard the\n * preview and restore the live document.\n * - **applyRestore**: calls `merge({ keepChanges: true })` to apply the\n * snapshot content back to the live document.\n */\nexport function createYjsVersioningAdapter(\n /** The BlockNote editor instance (must have ForkYDocExtension). */\n editor: BlockNoteEditor<any, any, any>,\n /** The full collaboration options (used for `fragment` access). */\n options: CollaborationOptions,\n): {\n preview: PreviewController<Uint8Array>;\n getCurrentDocument: () => Y.XmlFragment;\n serializeCurrentContent: () => Uint8Array;\n} {\n const { fragment } = options;\n\n function getForkYDoc() {\n const ext = editor.getExtension(ForkYDocExtension);\n if (!ext) {\n throw new Error(\n \"ForkYDocExtension is required for the Yjs versioning adapter. \" +\n \"Make sure it is registered before the VersioningExtension.\",\n );\n }\n return ext;\n }\n\n return {\n getCurrentDocument: () => fragment,\n serializeCurrentContent: () => Y.encodeStateAsUpdateV2(fragment.doc!),\n preview: {\n // Yjs v13 can only fork the document to a single snapshot; it has no way\n // to diff two versions, so comparison is unsupported.\n supportsComparison: false,\n enterPreview(\n snapshotContent: Uint8Array,\n _compareToContent?: Uint8Array,\n ) {\n const forkYDoc = getForkYDoc();\n\n // If already in a preview (forked state), exit first.\n if (forkYDoc.store.state.isForked) {\n forkYDoc.merge({ keepChanges: false });\n }\n\n forkYDoc.fork({ initialUpdate: snapshotContent });\n },\n\n exitPreview() {\n const forkYDoc = getForkYDoc();\n if (forkYDoc.store.state.isForked) {\n forkYDoc.merge({ keepChanges: false });\n }\n },\n\n applyRestore(_snapshotContent: Uint8Array) {\n // Restoring to an older Yjs state cannot be done by merging a fork\n // because the original doc already contains all CRDT state vectors\n // from the snapshot. Restore must be handled at the endpoint/server\n // level (e.g., the server creates a new Y.Doc and syncs it).\n throw new Error(\n \"Restore is not yet implemented for Yjs v13 versioning adapter.\",\n );\n },\n },\n };\n}\n","import type { Awareness } from \"y-protocols/awareness\";\nimport type * as Y from \"yjs\";\nimport type { BlockNoteEditorOptions } from \"../../editor/BlockNoteEditor\";\nimport {\n createExtension,\n type ExtensionOptions,\n} from \"../../editor/BlockNoteExtension.js\";\nimport { FixUpSchemaExtension } from \"./FixUpSchema.js\";\nimport { ForkYDocExtension } from \"./ForkYDoc.js\";\nimport { RelativePositionMappingExtension } from \"./RelativePositionMapping.js\";\nimport { SchemaMigration } from \"./schemaMigration/SchemaMigration.js\";\nimport { CollaborationUser, YCursorExtension } from \"./YCursorPlugin.js\";\nimport { YSyncExtension } from \"./YSync.js\";\nimport { YUndoExtension } from \"./YUndo.js\";\n\nexport type CollaborationOptions = {\n /**\n * The Yjs XML fragment that's used for collaboration.\n */\n fragment: Y.XmlFragment;\n /**\n * The user info for the current user that's shown to other collaborators.\n */\n user: CollaborationUser;\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\nexport const CollaborationExtension = createExtension(\n ({ options }: ExtensionOptions<CollaborationOptions>) => {\n return {\n key: \"collaboration\",\n blockNoteExtensions: [\n FixUpSchemaExtension(),\n ForkYDocExtension(options),\n RelativePositionMappingExtension(),\n SchemaMigration(options),\n YCursorExtension(options),\n YSyncExtension(options),\n YUndoExtension(),\n ],\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 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 \"./ForkYDoc.js\";\nexport * from \"./RelativePositionMapping.js\";\nexport * from \"./schemaMigration/SchemaMigration.js\";\nexport * from \"./Versioning.js\";\nexport * from \"./YCursorPlugin.js\";\nexport * from \"./YSync.js\";\nexport * from \"./YUndo.js\";\n","import * as Y from \"yjs\";\nimport type {\n CommentData,\n CommentReactionData,\n ThreadData,\n} from \"../../comments/types.js\";\n\nexport function commentToYMap(comment: CommentData) {\n const yMap = new Y.Map<any>();\n yMap.set(\"id\", comment.id);\n yMap.set(\"userId\", comment.userId);\n yMap.set(\"createdAt\", comment.createdAt.getTime());\n yMap.set(\"updatedAt\", comment.updatedAt.getTime());\n if (comment.deletedAt) {\n yMap.set(\"deletedAt\", comment.deletedAt.getTime());\n yMap.set(\"body\", undefined);\n } else {\n yMap.set(\"body\", comment.body);\n }\n if (comment.reactions.length > 0) {\n throw new Error(\"Reactions should be empty in commentToYMap\");\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 yMapToReactions).\n */\n yMap.set(\"reactionsByUser\", new Y.Map());\n yMap.set(\"metadata\", comment.metadata);\n\n return yMap;\n}\n\nexport function threadToYMap(thread: ThreadData) {\n const yMap = new Y.Map();\n yMap.set(\"id\", thread.id);\n yMap.set(\"createdAt\", thread.createdAt.getTime());\n yMap.set(\"updatedAt\", thread.updatedAt.getTime());\n const commentsArray = new Y.Array<Y.Map<any>>();\n\n commentsArray.push(thread.comments.map((comment) => commentToYMap(comment)));\n\n yMap.set(\"comments\", commentsArray);\n yMap.set(\"resolved\", thread.resolved);\n yMap.set(\"resolvedUpdatedAt\", thread.resolvedUpdatedAt?.getTime());\n yMap.set(\"resolvedBy\", thread.resolvedBy);\n yMap.set(\"metadata\", thread.metadata);\n return yMap;\n}\n\ntype SingleUserCommentReactionData = {\n emoji: string;\n createdAt: Date;\n userId: string;\n};\n\nexport function yMapToReaction(\n yMap: Y.Map<any>,\n): SingleUserCommentReactionData {\n return {\n emoji: yMap.get(\"emoji\"),\n createdAt: new Date(yMap.get(\"createdAt\")),\n userId: yMap.get(\"userId\"),\n };\n}\n\nfunction yMapToReactions(yMap: Y.Map<any>): CommentReactionData[] {\n const flatReactions = [...yMap.values()].map((reaction: Y.Map<any>) =>\n yMapToReaction(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 yMapToComment(yMap: Y.Map<any>): CommentData {\n return {\n type: \"comment\",\n id: yMap.get(\"id\"),\n userId: yMap.get(\"userId\"),\n createdAt: new Date(yMap.get(\"createdAt\")),\n updatedAt: new Date(yMap.get(\"updatedAt\")),\n deletedAt: yMap.get(\"deletedAt\")\n ? new Date(yMap.get(\"deletedAt\"))\n : undefined,\n reactions: yMapToReactions(yMap.get(\"reactionsByUser\")),\n metadata: yMap.get(\"metadata\"),\n body: yMap.get(\"body\"),\n };\n}\n\nexport function yMapToThread(yMap: Y.Map<any>): ThreadData {\n return {\n type: \"thread\",\n id: yMap.get(\"id\"),\n createdAt: new Date(yMap.get(\"createdAt\")),\n updatedAt: new Date(yMap.get(\"updatedAt\")),\n comments: ((yMap.get(\"comments\") as Y.Array<Y.Map<any>>) || []).map(\n (comment) => yMapToComment(comment),\n ),\n resolved: yMap.get(\"resolved\"),\n resolvedUpdatedAt: new Date(yMap.get(\"resolvedUpdatedAt\")),\n resolvedBy: yMap.get(\"resolvedBy\"),\n metadata: yMap.get(\"metadata\"),\n };\n}\n","import * as Y from \"yjs\";\nimport type { ThreadData } from \"../../comments/types.js\";\nimport { ThreadStore } from \"../../comments/threadstore/ThreadStore.js\";\nimport type { ThreadStoreAuth } from \"../../comments/threadstore/ThreadStoreAuth.js\";\nimport { yMapToThread } 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 Yjs Map.\n */\nexport abstract class YjsThreadStoreBase extends ThreadStore {\n constructor(\n protected readonly threadsYMap: Y.Map<any>,\n auth: ThreadStoreAuth,\n ) {\n super(auth);\n }\n\n // TODO: async / reactive interface?\n public getThread(threadId: string) {\n const yThread = this.threadsYMap.get(threadId);\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n const thread = yMapToThread(yThread);\n return thread;\n }\n\n public getThreads(): Map<string, ThreadData> {\n const threadMap = new Map<string, ThreadData>();\n this.threadsYMap.forEach((yThread, id) => {\n if (yThread instanceof Y.Map) {\n threadMap.set(id, yMapToThread(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.threadsYMap.observeDeep(observer);\n\n return () => {\n this.threadsYMap.unobserveDeep(observer);\n };\n }\n}\n","import * as Y from \"yjs\";\nimport {\n absolutePositionToRelativePosition,\n ySyncPluginKey,\n} from \"y-prosemirror\";\nimport type { CommentBody } from \"../../comments/types.js\";\nimport type { ThreadStoreAuth } from \"../../comments/threadstore/ThreadStoreAuth.js\";\nimport { YjsThreadStoreBase } from \"./YjsThreadStoreBase.js\";\nimport { BlockNoteEditor } from \"../../editor/BlockNoteEditor.js\";\n\n/**\n * This is a REST-based implementation of the YjsThreadStoreBase.\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 threadsYMap: Y.Map<any>,\n auth: ThreadStoreAuth,\n ) {\n super(threadsYMap, 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 editor: BlockNoteEditor<any, any, any>;\n }) => {\n const { threadId, selection } = options;\n\n // Note: the positions have to be resolved against the *binding's* type and\n // mapping. The plugin state has a `type` of its own, but no `mapping`, and\n // its `type` can go stale (e.g. while the doc is forked, see\n // `ForkYDocExtension`).\n const binding = ySyncPluginKey.getState(\n options.editor.prosemirrorState,\n )?.binding;\n const yjsSelection = binding\n ? {\n head: absolutePositionToRelativePosition(\n selection.head,\n binding.type,\n binding.mapping,\n ),\n anchor: absolutePositionToRelativePosition(\n selection.anchor,\n binding.type,\n binding.mapping,\n ),\n }\n : undefined;\n\n return this.doRequest(`/${threadId}/addToDocument`, \"POST\", {\n selection: {\n prosemirror: selection,\n yjs: yjsSelection,\n },\n });\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 * as Y from \"yjs\";\nimport { uuidv4 } from \"lib0/random\";\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 commentToYMap,\n threadToYMap,\n yMapToComment,\n yMapToThread,\n} from \"./yjsHelpers.js\";\n\n/**\n * This is a Yjs-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 threadsYMap: Y.Map<any>,\n auth: ThreadStoreAuth,\n ) {\n super(threadsYMap, 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.threadsYMap.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.threadsYMap.set(thread.id, threadToYMap(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.threadsYMap.get(options.threadId);\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n if (!this.auth.canAddComment(yMapToThread(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.get(\"comments\") as Y.Array<Y.Map<any>>).push([\n commentToYMap(comment),\n ]);\n\n yThread.set(\"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.threadsYMap.get(options.threadId);\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n const yCommentIndex = yArrayFindIndex(\n yThread.get(\"comments\"),\n (comment) => comment.get(\"id\") === options.commentId,\n );\n\n if (yCommentIndex === -1) {\n throw new Error(\"Comment not found\");\n }\n\n const yComment = yThread.get(\"comments\").get(yCommentIndex);\n\n if (!this.auth.canUpdateComment(yMapToComment(yComment))) {\n throw new Error(\"Not authorized\");\n }\n\n yComment.set(\"body\", options.comment.body);\n yComment.set(\"updatedAt\", new Date().getTime());\n yComment.set(\"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.threadsYMap.get(options.threadId);\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n const yCommentIndex = yArrayFindIndex(\n yThread.get(\"comments\"),\n (comment) => comment.get(\"id\") === options.commentId,\n );\n\n if (yCommentIndex === -1) {\n throw new Error(\"Comment not found\");\n }\n\n const yComment = yThread.get(\"comments\").get(yCommentIndex);\n\n if (!this.auth.canDeleteComment(yMapToComment(yComment))) {\n throw new Error(\"Not authorized\");\n }\n\n if (yComment.get(\"deletedAt\")) {\n throw new Error(\"Comment already deleted\");\n }\n\n if (options.softDelete) {\n yComment.set(\"deletedAt\", new Date().getTime());\n yComment.set(\"body\", undefined);\n } else {\n yThread.get(\"comments\").delete(yCommentIndex);\n }\n\n if (\n (yThread.get(\"comments\") as Y.Array<any>)\n .toArray()\n .every((comment) => comment.get(\"deletedAt\"))\n ) {\n // all comments deleted\n if (options.softDelete) {\n yThread.set(\"deletedAt\", new Date().getTime());\n } else {\n this.threadsYMap.delete(options.threadId);\n }\n }\n\n yThread.set(\"updatedAt\", new Date().getTime());\n },\n );\n\n public deleteThread = this.transact((options: { threadId: string }) => {\n if (\n !this.auth.canDeleteThread(\n yMapToThread(this.threadsYMap.get(options.threadId)),\n )\n ) {\n throw new Error(\"Not authorized\");\n }\n\n this.threadsYMap.delete(options.threadId);\n });\n\n public resolveThread = this.transact((options: { threadId: string }) => {\n const yThread = this.threadsYMap.get(options.threadId);\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n if (!this.auth.canResolveThread(yMapToThread(yThread))) {\n throw new Error(\"Not authorized\");\n }\n\n yThread.set(\"resolved\", true);\n yThread.set(\"resolvedUpdatedAt\", new Date().getTime());\n yThread.set(\"resolvedBy\", this.userId);\n });\n\n public unresolveThread = this.transact((options: { threadId: string }) => {\n const yThread = this.threadsYMap.get(options.threadId);\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n if (!this.auth.canUnresolveThread(yMapToThread(yThread))) {\n throw new Error(\"Not authorized\");\n }\n\n yThread.set(\"resolved\", false);\n yThread.set(\"resolvedUpdatedAt\", new Date().getTime());\n });\n\n public addReaction = this.transact(\n (options: { threadId: string; commentId: string; emoji: string }) => {\n const yThread = this.threadsYMap.get(options.threadId);\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n const yCommentIndex = yArrayFindIndex(\n yThread.get(\"comments\"),\n (comment) => comment.get(\"id\") === options.commentId,\n );\n\n if (yCommentIndex === -1) {\n throw new Error(\"Comment not found\");\n }\n\n const yComment = yThread.get(\"comments\").get(yCommentIndex);\n\n if (!this.auth.canAddReaction(yMapToComment(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.get(\"reactionsByUser\");\n\n if (reactionsByUser.has(key)) {\n // already exists\n return;\n } else {\n const reaction = new Y.Map();\n reaction.set(\"emoji\", options.emoji);\n reaction.set(\"createdAt\", date.getTime());\n reaction.set(\"userId\", this.userId);\n reactionsByUser.set(key, reaction);\n }\n },\n );\n\n public deleteReaction = this.transact(\n (options: { threadId: string; commentId: string; emoji: string }) => {\n const yThread = this.threadsYMap.get(options.threadId);\n if (!yThread) {\n throw new Error(\"Thread not found\");\n }\n\n const yCommentIndex = yArrayFindIndex(\n yThread.get(\"comments\"),\n (comment) => comment.get(\"id\") === options.commentId,\n );\n\n if (yCommentIndex === -1) {\n throw new Error(\"Comment not found\");\n }\n\n const yComment = yThread.get(\"comments\").get(yCommentIndex);\n\n if (\n !this.auth.canDeleteReaction(yMapToComment(yComment), options.emoji)\n ) {\n throw new Error(\"Not authorized\");\n }\n\n const key = `${this.userId}-${options.emoji}`;\n\n const reactionsByUser = yComment.get(\"reactionsByUser\");\n\n reactionsByUser.delete(key);\n },\n );\n}\n\nfunction yArrayFindIndex(\n yArray: Y.Array<any>,\n predicate: (item: any) => boolean,\n) {\n for (let i = 0; i < yArray.length; i++) {\n if (predicate(yArray.get(i))) {\n return i;\n }\n }\n return -1;\n}\n"],"mappings":"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"}