{"version":3,"file":"comments.cjs","names":[],"sources":["../src/comments/mark.ts","../src/comments/userstore/UserStore.ts","../src/comments/extension.ts","../src/comments/threadstore/ThreadStoreAuth.ts","../src/comments/threadstore/DefaultThreadStoreAuth.ts","../src/comments/threadstore/ThreadStore.ts","../src/comments/threadstore/TipTapThreadStore.ts","../src/comments/threadstore/yjs/yjsHelpers.ts","../src/comments/threadstore/yjs/YjsThreadStoreBase.ts","../src/comments/threadstore/yjs/RESTYjsThreadStore.ts","../src/comments/threadstore/yjs/YjsThreadStore.ts"],"sourcesContent":["import { Mark, mergeAttributes } from \"@tiptap/core\";\n\nexport const CommentMark = Mark.create({\n  name: \"comment\",\n  excludes: \"\",\n  inclusive: false,\n  keepOnSplit: true,\n\n  addAttributes() {\n    // Return an object with attribute configuration\n    return {\n      // orphans are marks that currently don't have an active thread. It could be\n      // that users have resolved the thread. Resolved threads by default are not shown in the document,\n      // but we need to keep the mark (positioning) data so we can still \"revive\" it when the thread is unresolved\n      // or we enter a \"comments\" view that includes resolved threads.\n      orphan: {\n        parseHTML: (element) => !!element.getAttribute(\"data-orphan\"),\n        renderHTML: (attributes) => {\n          return (attributes as { orphan: boolean }).orphan\n            ? {\n                \"data-orphan\": \"true\",\n              }\n            : {};\n        },\n        default: false,\n      },\n      threadId: {\n        parseHTML: (element) => element.getAttribute(\"data-bn-thread-id\"),\n        renderHTML: (attributes) => {\n          return {\n            \"data-bn-thread-id\": (attributes as { threadId: string }).threadId,\n          };\n        },\n        default: \"\",\n      },\n    };\n  },\n\n  renderHTML({ HTMLAttributes }: { HTMLAttributes: Record<string, any> }) {\n    return [\n      \"span\",\n      mergeAttributes(HTMLAttributes, {\n        class: \"bn-thread-mark\",\n      }),\n    ];\n  },\n\n  parseHTML() {\n    return [{ tag: \"span.bn-thread-mark\" }];\n  },\n\n  extendMarkSchema(extension) {\n    if (extension.name === \"comment\") {\n      return {\n        blocknoteIgnore: true,\n      };\n    }\n    return {};\n  },\n});\n","import type { User } from \"../types.js\";\nimport { EventEmitter } from \"../../util/EventEmitter.js\";\n\n/**\n * The `UserStore` is used to retrieve and cache information about users.\n *\n * It does this by calling `resolveUsers` (which is user-defined in the Editor Options)\n * for users that are not yet cached.\n */\nexport class UserStore<U extends User> extends EventEmitter<any> {\n  private userCache: Map<string, U> = new Map();\n\n  // avoid duplicate loads\n  private loadingUsers = new Set<string>();\n\n  public constructor(\n    private readonly resolveUsers: (userIds: string[]) => Promise<U[]>,\n  ) {\n    super();\n  }\n\n  /**\n   * Load information about users based on an array of user ids.\n   */\n  public async loadUsers(userIds: string[]) {\n    const missingUsers = userIds.filter(\n      (id) => !this.userCache.has(id) && !this.loadingUsers.has(id),\n    );\n\n    if (missingUsers.length === 0) {\n      return;\n    }\n\n    for (const id of missingUsers) {\n      this.loadingUsers.add(id);\n    }\n\n    try {\n      const users = await this.resolveUsers(missingUsers);\n      for (const user of users) {\n        this.userCache.set(user.id, user);\n      }\n      this.emit(\"update\", this.userCache);\n    } finally {\n      for (const id of missingUsers) {\n        // delete the users from the loading set\n        // on a next call to `loadUsers` we will either\n        // return the cached user or retry loading the user if the request failed failed\n        this.loadingUsers.delete(id);\n      }\n    }\n  }\n\n  /**\n   * Retrieve information about a user based on their id, if cached.\n   *\n   * The user will have to be loaded via `loadUsers` first\n   */\n  public getUser(userId: string): U | undefined {\n    return this.userCache.get(userId);\n  }\n\n  /**\n   * Subscribe to changes in the user store.\n   *\n   * @param cb - The callback to call when the user store changes.\n   * @returns A function to unsubscribe from the user store.\n   */\n  public subscribe(cb: (users: Map<string, U>) => void): () => void {\n    return this.on(\"update\", cb);\n  }\n}\n","import { Node } from \"prosemirror-model\";\nimport { Plugin, PluginKey } from \"prosemirror-state\";\nimport { Decoration, DecorationSet } from \"prosemirror-view\";\nimport { getRelativeSelection, ySyncPluginKey } from \"y-prosemirror\";\nimport {\n  createExtension,\n  createStore,\n  ExtensionOptions,\n} from \"../editor/BlockNoteExtension.js\";\nimport { ShowSelectionExtension } from \"../extensions/ShowSelection/ShowSelection.js\";\nimport { CustomBlockNoteSchema } from \"../schema/schema.js\";\nimport { CommentMark } from \"./mark.js\";\nimport type { ThreadStore } from \"./threadstore/ThreadStore.js\";\nimport type { CommentBody, ThreadData } from \"./types.js\";\nimport { User } from \"./types.js\";\nimport { UserStore } from \"./userstore/UserStore.js\";\n\nconst PLUGIN_KEY = new PluginKey(\"blocknote-comments\");\n\ntype CommentsPluginState = {\n  /**\n   * Decorations to be rendered, specifically to indicate the selected thread\n   */\n  decorations: DecorationSet;\n};\n\n/**\n * Calculate the thread positions from the current document state\n */\nfunction getUpdatedThreadPositions(doc: Node, markType: string) {\n  const threadPositions = new Map<string, { from: number; to: number }>();\n\n  // find all thread marks and store their position + create decoration for selected thread\n  doc.descendants((node, pos) => {\n    node.marks.forEach((mark) => {\n      if (mark.type.name === markType) {\n        const thisThreadId = (mark.attrs as { threadId: string | undefined })\n          .threadId;\n        if (!thisThreadId) {\n          return;\n        }\n        const from = pos;\n        const to = from + node.nodeSize;\n\n        // FloatingThreads component uses \"to\" as the position, so always store the largest \"to\" found\n        // AnchoredThreads component uses \"from\" as the position, so always store the smallest \"from\" found\n        const currentPosition = threadPositions.get(thisThreadId) ?? {\n          from: Infinity,\n          to: 0,\n        };\n        threadPositions.set(thisThreadId, {\n          from: Math.min(from, currentPosition.from),\n          to: Math.max(to, currentPosition.to),\n        });\n      }\n    });\n  });\n  return threadPositions;\n}\n\nexport const CommentsExtension = createExtension(\n  ({\n    editor,\n    options: { schema: commentEditorSchema, threadStore, resolveUsers },\n  }: ExtensionOptions<{\n    /**\n     * The thread store implementation to use for storing and retrieving comment threads\n     */\n    threadStore: ThreadStore;\n    /**\n     * Resolve user information for comments.\n     *\n     * See [Comments](https://www.blocknotejs.org/docs/features/collaboration/comments) for more info.\n     */\n    resolveUsers: (userIds: string[]) => Promise<User[]>;\n    /**\n     * A schema to use for the comment editor (which allows you to customize the blocks and styles that are available in the comment editor)\n     */\n    schema?: CustomBlockNoteSchema<any, any, any>;\n  }>) => {\n    if (!resolveUsers) {\n      throw new Error(\n        \"resolveUsers is required to be defined when using comments\",\n      );\n    }\n    if (!threadStore) {\n      throw new Error(\n        \"threadStore is required to be defined when using comments\",\n      );\n    }\n    const markType = CommentMark.name;\n\n    const userStore = new UserStore<User>(resolveUsers);\n    const store = createStore(\n      {\n        pendingComment: false,\n        selectedThreadId: undefined as string | undefined,\n        threadPositions: new Map<string, { from: number; to: number }>(),\n      },\n      {\n        onUpdate() {\n          // If the selected thread id changed, we need to update the decorations\n          if (\n            store.state.selectedThreadId !== store.prevState.selectedThreadId\n          ) {\n            // So, we issue a transaction to update the decorations\n            editor.transact((tr) => tr.setMeta(PLUGIN_KEY, true));\n          }\n        },\n      },\n    );\n\n    const updateMarksFromThreads = (threads: Map<string, ThreadData>) => {\n      editor.transact((tr) => {\n        tr.doc.descendants((node, pos) => {\n          node.marks.forEach((mark) => {\n            if (mark.type.name === markType) {\n              const markTypeInstance = mark.type;\n              const markThreadId = mark.attrs.threadId as string;\n              const thread = threads.get(markThreadId);\n              const isOrphan = !!(\n                !thread ||\n                thread.resolved ||\n                thread.deletedAt\n              );\n\n              if (isOrphan !== mark.attrs.orphan) {\n                const trimmedFrom = Math.max(pos, 0);\n                const trimmedTo = Math.min(\n                  pos + node.nodeSize,\n                  tr.doc.content.size - 1,\n                  tr.doc.content.size - 1,\n                );\n                tr.removeMark(trimmedFrom, trimmedTo, mark);\n                tr.addMark(\n                  trimmedFrom,\n                  trimmedTo,\n                  markTypeInstance.create({\n                    ...mark.attrs,\n                    orphan: isOrphan,\n                  }),\n                );\n\n                if (isOrphan && store.state.selectedThreadId === markThreadId) {\n                  // unselect\n                  store.setState((prev) => ({\n                    ...prev,\n                    selectedThreadId: undefined,\n                  }));\n                }\n              }\n            }\n          });\n        });\n      });\n    };\n\n    return {\n      key: \"comments\",\n      store,\n      runsBefore: [\"link\"],\n      tiptapExtensions: [CommentMark],\n      prosemirrorPlugins: [\n        new Plugin<CommentsPluginState>({\n          key: PLUGIN_KEY,\n          state: {\n            init() {\n              return {\n                decorations: DecorationSet.empty,\n              };\n            },\n            apply(tr, state) {\n              const action = tr.getMeta(PLUGIN_KEY);\n\n              if (!tr.docChanged && !action) {\n                return state;\n              }\n\n              // only update threadPositions if the doc changed\n              const newThreadPositions = tr.docChanged\n                ? getUpdatedThreadPositions(tr.doc, markType)\n                : store.state.threadPositions;\n\n              if (\n                newThreadPositions.size > 0 ||\n                store.state.threadPositions.size > 0\n              ) {\n                // small optimization; don't emit event if threadPositions before / after were both empty\n                store.setState((prev) => ({\n                  ...prev,\n                  threadPositions: newThreadPositions,\n                }));\n              }\n\n              // update decorations if doc or selected thread changed\n              const decorations = [] as any[];\n\n              if (store.state.selectedThreadId) {\n                const selectedThreadPosition = newThreadPositions.get(\n                  store.state.selectedThreadId,\n                );\n\n                if (selectedThreadPosition) {\n                  decorations.push(\n                    Decoration.inline(\n                      selectedThreadPosition.from,\n                      selectedThreadPosition.to,\n                      {\n                        class: \"bn-thread-mark-selected\",\n                      },\n                    ),\n                  );\n                }\n              }\n\n              return {\n                decorations: DecorationSet.create(tr.doc, decorations),\n              };\n            },\n          },\n          props: {\n            decorations(state) {\n              return (\n                PLUGIN_KEY.getState(state)?.decorations ?? DecorationSet.empty\n              );\n            },\n            handleClick: (view, pos, event) => {\n              if (event.button !== 0) {\n                return false;\n              }\n\n              const node = view.state.doc.nodeAt(pos);\n\n              if (!node) {\n                // unselect\n                store.setState((prev) => ({\n                  ...prev,\n                  selectedThreadId: undefined,\n                }));\n                return false;\n              }\n\n              const commentMark = node.marks.find(\n                (mark) =>\n                  mark.type.name === markType && mark.attrs.orphan !== true,\n              );\n\n              if (!commentMark) {\n                // Clicked outside any comment thread. Deselect if needed but\n                // don't consume the event so other handlers (e.g. link\n                // navigation) can process it.\n                if (store.state.selectedThreadId !== undefined) {\n                  store.setState((prev) => ({\n                    ...prev,\n                    selectedThreadId: undefined,\n                  }));\n                }\n                return false;\n              }\n\n              const threadId = commentMark.attrs.threadId as string;\n\n              // If the clicked thread is already selected, do nothing and let\n              // other handlers process the event (e.g. navigating a link).\n              if (threadId === store.state.selectedThreadId) {\n                return false;\n              }\n\n              store.setState((prev) => ({\n                ...prev,\n                selectedThreadId: threadId,\n              }));\n\n              return true;\n            },\n          },\n        }),\n      ],\n      threadStore: threadStore,\n      mount() {\n        const unsubscribe = threadStore.subscribe(updateMarksFromThreads);\n        updateMarksFromThreads(threadStore.getThreads());\n\n        const unsubscribeOnSelectionChange = editor.onSelectionChange(() => {\n          if (store.state.pendingComment) {\n            store.setState((prev) => ({\n              ...prev,\n              pendingComment: false,\n            }));\n          }\n        });\n\n        return () => {\n          unsubscribe();\n          unsubscribeOnSelectionChange();\n        };\n      },\n      selectThread(threadId: string | undefined, scrollToThread = true) {\n        if (store.state.selectedThreadId === threadId) {\n          return;\n        }\n        store.setState((prev) => ({\n          ...prev,\n          pendingComment: false,\n          selectedThreadId: threadId,\n        }));\n\n        if (threadId && scrollToThread) {\n          const selectedThreadPosition =\n            store.state.threadPositions.get(threadId);\n          if (!selectedThreadPosition) {\n            return;\n          }\n          (\n            editor.prosemirrorView?.domAtPos(selectedThreadPosition.from)\n              .node as Element | undefined\n          )?.scrollIntoView({\n            behavior: \"smooth\",\n            block: \"center\",\n          });\n        }\n      },\n      startPendingComment() {\n        store.setState((prev) => ({\n          ...prev,\n          selectedThreadId: undefined,\n          pendingComment: true,\n        }));\n        // Use `editor.domElement` as `editor.focus()` doesn't do anything if\n        // the editor is non-editable. Editor needs to be focused as\n        // `showSelection` will otherwise trigger a selection update which\n        // triggers `stopPendingComment`.\n        editor.domElement?.focus();\n        editor\n          .getExtension(ShowSelectionExtension)\n          ?.showSelection(true, \"comments\");\n      },\n      stopPendingComment() {\n        store.setState((prev) => ({\n          ...prev,\n          selectedThreadId: undefined,\n          pendingComment: false,\n        }));\n        editor\n          .getExtension(ShowSelectionExtension)\n          ?.showSelection(false, \"comments\");\n      },\n      async createThread(options: {\n        initialComment: { body: CommentBody; metadata?: any };\n        metadata?: any;\n      }) {\n        const thread = await threadStore.createThread(options);\n        if (threadStore.addThreadToDocument) {\n          const view = editor.prosemirrorView!;\n          const pmSelection = view.state.selection;\n          const ystate = ySyncPluginKey.getState(view.state);\n          const selection = {\n            prosemirror: {\n              head: pmSelection.head,\n              anchor: pmSelection.anchor,\n            },\n            yjs: ystate\n              ? getRelativeSelection(ystate.binding, view.state)\n              : undefined,\n          };\n          await threadStore.addThreadToDocument({\n            threadId: thread.id,\n            selection,\n          });\n        } else {\n          (editor as any)._tiptapEditor.commands.setMark(markType, {\n            orphan: false,\n            threadId: thread.id,\n          });\n        }\n      },\n      userStore,\n      commentEditorSchema,\n    } as const;\n  },\n);\n","import { CommentData, ThreadData } from \"../types.js\";\n\nexport abstract class ThreadStoreAuth {\n  abstract canCreateThread(): boolean;\n  abstract canAddComment(thread: ThreadData): boolean;\n  abstract canUpdateComment(comment: CommentData): boolean;\n  abstract canDeleteComment(comment: CommentData): boolean;\n  abstract canDeleteThread(thread: ThreadData): boolean;\n  abstract canResolveThread(thread: ThreadData): boolean;\n  abstract canUnresolveThread(thread: ThreadData): boolean;\n  abstract canAddReaction(comment: CommentData, emoji?: string): boolean;\n  abstract canDeleteReaction(comment: CommentData, emoji?: string): boolean;\n}\n","import { CommentData, ThreadData } from \"../types.js\";\nimport { ThreadStoreAuth } from \"./ThreadStoreAuth.js\";\n\n/*\n * The DefaultThreadStoreAuth class defines the authorization rules for interacting with comments.\n * We take a role (\"comment\" or \"editor\") and implement the rules.\n *\n * This class is then used in the UI to show / hide specific interactions.\n *\n * Rules:\n * - View-only users should not be able to see any comments\n * - Comment-only users and editors can:\n * - - create new comments / replies / reactions\n * - - edit / delete their own comments / reactions\n * - - resolve / unresolve threads\n * - Editors can also delete any comment or thread\n */\nexport class DefaultThreadStoreAuth extends ThreadStoreAuth {\n  constructor(\n    private readonly userId: string,\n    private readonly role: \"comment\" | \"editor\",\n  ) {\n    super();\n  }\n\n  /**\n   * Auth: should be possible by anyone with comment access\n   */\n  canCreateThread(): boolean {\n    return true;\n  }\n\n  /**\n   * Auth: should be possible by anyone with comment access\n   */\n  canAddComment(_thread: ThreadData): boolean {\n    return true;\n  }\n\n  /**\n   * Auth: should only be possible by the comment author\n   */\n  canUpdateComment(comment: CommentData): boolean {\n    return comment.userId === this.userId;\n  }\n\n  /**\n   * Auth: should be possible by the comment author OR an editor of the document\n   */\n  canDeleteComment(comment: CommentData): boolean {\n    return comment.userId === this.userId || this.role === \"editor\";\n  }\n\n  /**\n   * Auth: should only be possible by an editor of the document\n   */\n  canDeleteThread(_thread: ThreadData): boolean {\n    return this.role === \"editor\";\n  }\n\n  /**\n   * Auth: should be possible by anyone with comment access\n   */\n  canResolveThread(_thread: ThreadData): boolean {\n    return true;\n  }\n\n  /**\n   * Auth: should be possible by anyone with comment access\n   */\n  canUnresolveThread(_thread: ThreadData): boolean {\n    return true;\n  }\n\n  /**\n   * Auth: should be possible by anyone with comment access\n   *\n   * Note: will also check if the user has already reacted with the same emoji. TBD: is that a nice design or should this responsibility be outside of auth?\n   */\n  canAddReaction(comment: CommentData, emoji?: string): boolean {\n    if (!emoji) {\n      return true;\n    }\n\n    return !comment.reactions.some(\n      (reaction) =>\n        reaction.emoji === emoji && reaction.userIds.includes(this.userId),\n    );\n  }\n\n  /**\n   * Auth: should be possible by anyone with comment access\n   *\n   * Note: will also check if the user has already reacted with the same emoji. TBD: is that a nice design or should this responsibility be outside of auth?\n   */\n  canDeleteReaction(comment: CommentData, emoji?: string): boolean {\n    if (!emoji) {\n      return true;\n    }\n\n    return comment.reactions.some(\n      (reaction) =>\n        reaction.emoji === emoji && reaction.userIds.includes(this.userId),\n    );\n  }\n}\n","import { CommentBody, CommentData, ThreadData } from \"../types.js\";\nimport { ThreadStoreAuth } from \"./ThreadStoreAuth.js\";\n\n/**\n * ThreadStore is an abstract class that defines the interface\n * to read / add / update / delete threads and comments.\n */\nexport abstract class ThreadStore {\n  public readonly auth: ThreadStoreAuth;\n\n  constructor(auth: ThreadStoreAuth) {\n    this.auth = auth;\n  }\n\n  /**\n   * A \"thread\" in the ThreadStore only contains information about the content\n   * of the thread / comments. It does not contain information about the position.\n   *\n   * This function can be implemented to store the thread in the document (by creating a mark)\n   * If not implemented, default behavior will apply (creating the mark via TipTap)\n   * See CommentsPlugin.ts for more details.\n   */\n  abstract addThreadToDocument?(options: {\n    threadId: string;\n    selection: {\n      prosemirror: {\n        head: number;\n        anchor: number;\n      };\n      yjs?: {\n        head: any;\n        anchor: any;\n      };\n    };\n  }): Promise<void>;\n\n  /**\n   * Creates a new thread with an initial comment.\n   */\n  abstract createThread(options: {\n    initialComment: {\n      body: CommentBody;\n      metadata?: any;\n    };\n    metadata?: any;\n  }): Promise<ThreadData>;\n\n  /**\n   * Adds a comment to a thread.\n   */\n  abstract addComment(options: {\n    comment: {\n      body: CommentBody;\n      metadata?: any;\n    };\n    threadId: string;\n  }): Promise<CommentData>;\n\n  /**\n   * Updates a comment in a thread.\n   */\n  abstract updateComment(options: {\n    comment: {\n      body: CommentBody;\n      metadata?: any;\n    };\n    threadId: string;\n    commentId: string;\n  }): Promise<void>;\n\n  /**\n   * Deletes a comment from a thread.\n   */\n  abstract deleteComment(options: {\n    threadId: string;\n    commentId: string;\n  }): Promise<void>;\n\n  /**\n   * Deletes a thread.\n   */\n  abstract deleteThread(options: { threadId: string }): Promise<void>;\n\n  /**\n   * Marks a thread as resolved.\n   */\n  abstract resolveThread(options: { threadId: string }): Promise<void>;\n\n  /**\n   * Marks a thread as unresolved.\n   */\n  abstract unresolveThread(options: { threadId: string }): Promise<void>;\n\n  /**\n   * Adds a reaction to a comment.\n   *\n   * Auth: should be possible by anyone with comment access\n   */\n  abstract addReaction(options: {\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }): Promise<void>;\n\n  /**\n   * Deletes a reaction from a comment.\n   *\n   * Auth: should be possible by the reaction author\n   */\n  abstract deleteReaction(options: {\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }): Promise<void>;\n\n  /**\n   * Retrieve data for a specific thread.\n   */\n  abstract getThread(threadId: string): ThreadData;\n\n  /**\n   * Retrieve all threads.\n   */\n  abstract getThreads(): Map<string, ThreadData>;\n\n  /**\n   * Subscribe to changes in the thread store.\n   *\n   * @returns a function to unsubscribe from the thread store\n   */\n  abstract subscribe(\n    cb: (threads: Map<string, ThreadData>) => void,\n  ): () => void;\n}\n","import {\n  CommentBody,\n  CommentData,\n  CommentReactionData,\n  ThreadData,\n} from \"../types.js\";\nimport { ThreadStore } from \"./ThreadStore.js\";\nimport { ThreadStoreAuth } from \"./ThreadStoreAuth.js\";\nimport type {\n  TCollabComment,\n  TCollabThread,\n  TiptapCollabProvider,\n} from \"./tiptap/types.js\";\n\ntype ReactionAsTiptapData = {\n  emoji: string;\n  createdAt: number;\n  userId: string;\n};\n\n/**\n * The `TiptapThreadStore` integrates with Tiptap's collaboration provider for comment management.\n * You can pass a `TiptapCollabProvider` to the constructor which takes care of storing the comments.\n *\n * Under the hood, this actually works similarly to the `YjsThreadStore` implementation. (comments are stored in the Yjs document)\n */\nexport class TiptapThreadStore extends ThreadStore {\n  constructor(\n    private readonly userId: string,\n    private readonly provider: TiptapCollabProvider,\n    auth: ThreadStoreAuth, // TODO: use?\n  ) {\n    super(auth);\n  }\n\n  /**\n   * Creates a new thread with an initial comment.\n   */\n  public async createThread(options: {\n    initialComment: {\n      body: CommentBody;\n      metadata?: any;\n    };\n    metadata?: any;\n  }): Promise<ThreadData> {\n    let thread = this.provider.createThread({\n      data: options.metadata,\n    });\n\n    thread = this.provider.addComment(thread.id, {\n      content: options.initialComment.body,\n      data: {\n        metadata: options.initialComment.metadata,\n        userId: this.userId,\n      },\n    });\n\n    return this.tiptapThreadToThreadData(thread);\n  }\n\n  // TipTapThreadStore does not support addThreadToDocument\n  public addThreadToDocument = undefined;\n\n  /**\n   * Adds a comment to a thread.\n   */\n  public async addComment(options: {\n    comment: {\n      body: CommentBody;\n      metadata?: any;\n    };\n    threadId: string;\n  }): Promise<CommentBody> {\n    const thread = this.provider.addComment(options.threadId, {\n      content: options.comment.body,\n      data: {\n        metadata: options.comment.metadata,\n        userId: this.userId,\n      },\n    });\n\n    return this.tiptapCommentToCommentData(\n      thread.comments[thread.comments.length - 1],\n    );\n  }\n\n  /**\n   * Updates a comment in a thread.\n   */\n  public async updateComment(options: {\n    comment: {\n      body: CommentBody;\n      metadata?: any;\n    };\n    threadId: string;\n    commentId: string;\n  }) {\n    const comment = this.provider.getThreadComment(\n      options.threadId,\n      options.commentId,\n      true,\n    );\n\n    if (!comment) {\n      throw new Error(\"Comment not found\");\n    }\n\n    this.provider.updateComment(options.threadId, options.commentId, {\n      content: options.comment.body,\n      data: {\n        ...comment.data,\n        metadata: options.comment.metadata,\n      },\n    });\n  }\n\n  private tiptapCommentToCommentData(comment: TCollabComment): CommentData {\n    const reactions: CommentReactionData[] = [];\n\n    for (const reaction of (comment.data?.reactions ||\n      []) as ReactionAsTiptapData[]) {\n      const existingReaction = reactions.find(\n        (r) => r.emoji === reaction.emoji,\n      );\n      if (existingReaction) {\n        existingReaction.userIds.push(reaction.userId);\n        existingReaction.createdAt = new Date(\n          Math.min(existingReaction.createdAt.getTime(), reaction.createdAt),\n        );\n      } else {\n        reactions.push({\n          emoji: reaction.emoji,\n          createdAt: new Date(reaction.createdAt),\n          userIds: [reaction.userId],\n        });\n      }\n    }\n\n    return {\n      type: \"comment\",\n      id: comment.id,\n      body: comment.content,\n      metadata: comment.data?.metadata,\n      userId: comment.data?.userId,\n      createdAt: new Date(comment.createdAt),\n      updatedAt: new Date(comment.updatedAt),\n      reactions,\n    };\n  }\n\n  private tiptapThreadToThreadData(thread: TCollabThread): ThreadData {\n    return {\n      type: \"thread\",\n      id: thread.id,\n      comments: thread.comments.map((comment) =>\n        this.tiptapCommentToCommentData(comment),\n      ),\n      resolved: !!thread.resolvedAt,\n      metadata: thread.data?.metadata,\n      createdAt: new Date(thread.createdAt),\n      updatedAt: new Date(thread.updatedAt),\n    };\n  }\n\n  /**\n   * Deletes a comment from a thread.\n   */\n  public async deleteComment(options: { threadId: string; commentId: string }) {\n    this.provider.deleteComment(options.threadId, options.commentId);\n  }\n\n  /**\n   * Deletes a thread.\n   */\n  public async deleteThread(options: { threadId: string }) {\n    this.provider.deleteThread(options.threadId);\n  }\n\n  /**\n   * Marks a thread as resolved.\n   */\n  public async resolveThread(options: { threadId: string }) {\n    this.provider.updateThread(options.threadId, {\n      resolvedAt: new Date().toISOString(),\n    });\n  }\n\n  /**\n   * Marks a thread as unresolved.\n   */\n  public async unresolveThread(options: { threadId: string }) {\n    this.provider.updateThread(options.threadId, {\n      resolvedAt: null,\n    });\n  }\n\n  /**\n   * Adds a reaction to a comment.\n   *\n   * Auth: should be possible by anyone with comment access\n   */\n  public async addReaction(options: {\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }) {\n    const comment = this.provider.getThreadComment(\n      options.threadId,\n      options.commentId,\n      true,\n    );\n\n    if (!comment) {\n      throw new Error(\"Comment not found\");\n    }\n\n    this.provider.updateComment(options.threadId, options.commentId, {\n      data: {\n        ...comment.data,\n        reactions: [\n          ...((comment.data?.reactions || []) as ReactionAsTiptapData[]),\n          {\n            emoji: options.emoji,\n            createdAt: Date.now(),\n            userId: this.userId,\n          },\n        ],\n      },\n    });\n  }\n\n  /**\n   * Deletes a reaction from a comment.\n   *\n   * Auth: should be possible by the reaction author\n   */\n  public async deleteReaction(options: {\n    threadId: string;\n    commentId: string;\n    emoji: string;\n  }) {\n    const comment = this.provider.getThreadComment(\n      options.threadId,\n      options.commentId,\n      true,\n    );\n\n    if (!comment) {\n      throw new Error(\"Comment not found\");\n    }\n\n    this.provider.updateComment(options.threadId, options.commentId, {\n      data: {\n        ...comment.data,\n        reactions: (\n          (comment.data?.reactions || []) as ReactionAsTiptapData[]\n        ).filter(\n          (reaction) =>\n            reaction.emoji !== options.emoji && reaction.userId !== this.userId,\n        ),\n      },\n    });\n  }\n\n  public getThread(threadId: string): ThreadData {\n    const thread = this.provider.getThread(threadId);\n\n    if (!thread) {\n      throw new Error(\"Thread not found\");\n    }\n\n    return this.tiptapThreadToThreadData(thread);\n  }\n\n  public getThreads(): Map<string, ThreadData> {\n    return new Map(\n      this.provider\n        .getThreads()\n        .map((thread) => [thread.id, this.tiptapThreadToThreadData(thread)]),\n    );\n  }\n\n  public subscribe(cb: (threads: Map<string, ThreadData>) => void): () => void {\n    const newCb = () => {\n      cb(this.getThreads());\n    };\n    this.provider.watchThreads(newCb);\n    return () => {\n      this.provider.unwatchThreads(newCb);\n    };\n  }\n}\n","import * as Y from \"yjs\";\nimport { CommentData, CommentReactionData, ThreadData } from \"../../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 { ThreadData } from \"../../types.js\";\nimport { ThreadStore } from \"../ThreadStore.js\";\nimport { ThreadStoreAuth } from \"../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 { CommentBody } from \"../../types.js\";\nimport { ThreadStoreAuth } from \"../ThreadStoreAuth.js\";\nimport { YjsThreadStoreBase } from \"./YjsThreadStoreBase.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      prosemirror: {\n        head: number;\n        anchor: number;\n      };\n      yjs: {\n        head: any;\n        anchor: any;\n      };\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 \"yjs\";\nimport { CommentBody, CommentData, ThreadData } from \"../../types.js\";\nimport { ThreadStoreAuth } from \"../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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"}