{"version":3,"file":"comments.cjs","names":[],"sources":["../src/comments/mark.ts","../src/comments/extension.ts","../src/comments/threadstore/ThreadStoreAuth.ts","../src/comments/threadstore/DefaultThreadStoreAuth.ts","../src/comments/threadstore/TipTapThreadStore.ts"],"sourcesContent":["import { Mark, mergeAttributes } from \"@tiptap/core\";\n\nimport { NON_FORMATTING_MARK_GROUP } from \"../schema/markGroups.js\";\n\nexport const CommentMark = Mark.create({\n  name: \"comment\",\n  excludes: \"\",\n  inclusive: false,\n  keepOnSplit: true,\n  // Allowed on \"plain\" blocks (e.g. code blocks) via this group.\n  group: NON_FORMATTING_MARK_GROUP,\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 { Node } from \"prosemirror-model\";\nimport { Plugin, PluginKey } from \"prosemirror-state\";\nimport { Decoration, DecorationSet } from \"prosemirror-view\";\nimport {\n  createExtension,\n  createStore,\n  ExtensionOptions,\n} from \"../editor/BlockNoteExtension.js\";\nimport { ShowSelectionExtension } from \"../extensions/ShowSelection/ShowSelection.js\";\nimport { normalizeToUserStore, UserStoreOrResolver } from \"../user/index.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\";\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: {\n      schema: commentEditorSchema,\n      threadStore,\n      resolveUsers,\n      confirmBeforeDiscard = true,\n    },\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 (names, avatars) for comment authors.\n     *\n     * Either a resolver function (called with the ids of users that are not yet\n     * cached, returning their information) or a pre-built user store (see\n     * `createUserStore`). Pass the same store to the collaboration options so a\n     * single de-duped user cache is shared across comments and collaboration.\n     *\n     * See [Comments](https://www.blocknotejs.org/docs/features/collaboration/comments) for more info.\n     */\n    resolveUsers: UserStoreOrResolver;\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     * Whether to ask the user for confirmation before discarding unsaved text\n     * in a comment composer (a new comment, a reply, or an in-progress edit)\n     * when it's dismissed (e.g. by clicking outside or pressing Escape).\n     *\n     * @default true\n     */\n    confirmBeforeDiscard?: boolean;\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    // Resolve users through this store, exposed on the extension instance so the\n    // comments UI can read from it directly. Accepts a resolver callback or a\n    // shared store (see the option docs above).\n    const userStore = normalizeToUserStore(resolveUsers);\n    const markType = CommentMark.name;\n\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(state, prevState) {\n          // If the selected thread id changed, we need to update the decorations\n          if (state.selectedThreadId !== prevState.selectedThreadId) {\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      userStore,\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          await threadStore.addThreadToDocument({\n            threadId: thread.id,\n            selection: editor.transact((tr) => tr.selection),\n            editor,\n          });\n        } else {\n          (editor as any)._tiptapEditor.commands.setMark(markType, {\n            orphan: false,\n            threadId: thread.id,\n          });\n        }\n      },\n      commentEditorSchema,\n      confirmBeforeDiscard,\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 {\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"],"mappings":"iSAIA,IAAa,EAAc,EAAA,KAAK,OAAO,CACrC,KAAM,UACN,SAAU,GACV,UAAW,GACX,YAAa,GAEb,MAAO,EAAA,EAEP,eAAgB,CAEd,MAAO,CAKL,OAAQ,CACN,UAAY,GAAY,CAAC,CAAC,EAAQ,aAAa,aAAa,EAC5D,WAAa,GACH,EAAmC,OACvC,CACE,cAAe,MACjB,EACA,CAAC,EAEP,QAAS,EACX,EACA,SAAU,CACR,UAAY,GAAY,EAAQ,aAAa,mBAAmB,EAChE,WAAa,IACJ,CACL,oBAAsB,EAAoC,QAC5D,GAEF,QAAS,EACX,CACF,CACF,EAEA,WAAW,CAAE,kBAA2D,CACtE,MAAO,CACL,QAAA,EACA,EAAA,gBAAA,CAAgB,EAAgB,CAC9B,MAAO,gBACT,CAAC,CACH,CACF,EAEA,WAAY,CACV,MAAO,CAAC,CAAE,IAAK,qBAAsB,CAAC,CACxC,EAEA,iBAAiB,EAAW,CAM1B,OALI,EAAU,OAAS,UACd,CACL,gBAAiB,EACnB,EAEK,CAAC,CACV,CACF,CAAC,EChDK,EAAa,IAAI,EAAA,UAAU,oBAAoB,EAYrD,SAAS,EAA0B,EAAW,EAAkB,CAC9D,IAAM,EAAkB,IAAI,IA2B5B,OAxBA,EAAI,aAAa,EAAM,IAAQ,CAC7B,EAAK,MAAM,QAAS,GAAS,CAC3B,GAAI,EAAK,KAAK,OAAS,EAAU,CAC/B,IAAM,EAAgB,EAAK,MACxB,SACH,GAAI,CAAC,EACH,OAEF,IAAM,EAAO,EACP,EAAK,EAAO,EAAK,SAIjB,EAAkB,EAAgB,IAAI,CAAY,GAAK,CAC3D,KAAM,IACN,GAAI,CACN,EACA,EAAgB,IAAI,EAAc,CAChC,KAAM,KAAK,IAAI,EAAM,EAAgB,IAAI,EACzC,GAAI,KAAK,IAAI,EAAI,EAAgB,EAAE,CACrC,CAAC,CACH,CACF,CAAC,CACH,CAAC,EACM,CACT,CAEA,IAAa,EAAoB,EAAA,GAC9B,CACC,SACA,QAAS,CACP,OAAQ,EACR,cACA,eACA,uBAAuB,OA8BpB,CACL,GAAI,CAAC,EACH,MAAU,MACR,4DACF,EAEF,GAAI,CAAC,EACH,MAAU,MACR,2DACF,EAKF,IAAM,EAAY,EAAA,EAAqB,CAAY,EAC7C,EAAW,EAAY,KAEvB,EAAQ,EAAA,EACZ,CACE,eAAgB,GAChB,iBAAkB,IAAA,GAClB,gBAAiB,IAAI,GACvB,EACA,CACE,SAAS,EAAO,EAAW,CAErB,EAAM,mBAAqB,EAAU,kBAEvC,EAAO,SAAU,GAAO,EAAG,QAAQ,EAAY,EAAI,CAAC,CAExD,CACF,CACF,EAEM,EAA0B,GAAqC,CACnE,EAAO,SAAU,GAAO,CACtB,EAAG,IAAI,aAAa,EAAM,IAAQ,CAChC,EAAK,MAAM,QAAS,GAAS,CAC3B,GAAI,EAAK,KAAK,OAAS,EAAU,CAC/B,IAAM,EAAmB,EAAK,KACxB,EAAe,EAAK,MAAM,SAC1B,EAAS,EAAQ,IAAI,CAAY,EACjC,EAAW,CAAC,EAChB,CAAC,GACD,EAAO,UACP,EAAO,WAGT,GAAI,IAAa,EAAK,MAAM,OAAQ,CAClC,IAAM,EAAc,KAAK,IAAI,EAAK,CAAC,EAC7B,EAAY,KAAK,IACrB,EAAM,EAAK,SACX,EAAG,IAAI,QAAQ,KAAO,EACtB,EAAG,IAAI,QAAQ,KAAO,CACxB,EACA,EAAG,WAAW,EAAa,EAAW,CAAI,EAC1C,EAAG,QACD,EACA,EACA,EAAiB,OAAO,CACtB,GAAG,EAAK,MACR,OAAQ,CACV,CAAC,CACH,EAEI,GAAY,EAAM,MAAM,mBAAqB,GAE/C,EAAM,SAAU,IAAU,CACxB,GAAG,EACH,iBAAkB,IAAA,EACpB,EAAE,CAEN,CACF,CACF,CAAC,CACH,CAAC,CACH,CAAC,CACH,EAEA,MAAO,CACL,IAAK,WACL,QACA,YACA,WAAY,CAAC,MAAM,EACnB,iBAAkB,CAAC,CAAW,EAC9B,mBAAoB,CAClB,IAAI,EAAA,OAA4B,CAC9B,IAAK,EACL,MAAO,CACL,MAAO,CACL,MAAO,CACL,YAAa,EAAA,cAAc,KAC7B,CACF,EACA,MAAM,EAAI,EAAO,CACf,IAAM,EAAS,EAAG,QAAQ,CAAU,EAEpC,GAAI,CAAC,EAAG,YAAc,CAAC,EACrB,OAAO,EAIT,IAAM,EAAqB,EAAG,WAC1B,EAA0B,EAAG,IAAK,CAAQ,EAC1C,EAAM,MAAM,iBAGd,EAAmB,KAAO,GAC1B,EAAM,MAAM,gBAAgB,KAAO,IAGnC,EAAM,SAAU,IAAU,CACxB,GAAG,EACH,gBAAiB,CACnB,EAAE,EAIJ,IAAM,EAAc,CAAC,EAErB,GAAI,EAAM,MAAM,iBAAkB,CAChC,IAAM,EAAyB,EAAmB,IAChD,EAAM,MAAM,gBACd,EAEI,GACF,EAAY,KACV,EAAA,WAAW,OACT,EAAuB,KACvB,EAAuB,GACvB,CACE,MAAO,yBACT,CACF,CACF,CAEJ,CAEA,MAAO,CACL,YAAa,EAAA,cAAc,OAAO,EAAG,IAAK,CAAW,CACvD,CACF,CACF,EACA,MAAO,CACL,YAAY,EAAO,CACjB,OACE,EAAW,SAAS,CAAK,CAAC,EAAE,aAAe,EAAA,cAAc,KAE7D,EACA,aAAc,EAAM,EAAK,IAAU,CACjC,GAAI,EAAM,SAAW,EACnB,MAAO,GAGT,IAAM,EAAO,EAAK,MAAM,IAAI,OAAO,CAAG,EAEtC,GAAI,CAAC,EAMH,OAJA,EAAM,SAAU,IAAU,CACxB,GAAG,EACH,iBAAkB,IAAA,EACpB,EAAE,EACK,GAGT,IAAM,EAAc,EAAK,MAAM,KAC5B,GACC,EAAK,KAAK,OAAS,GAAY,EAAK,MAAM,SAAW,EACzD,EAEA,GAAI,CAAC,EAUH,OANI,EAAM,MAAM,mBAAqB,IAAA,IACnC,EAAM,SAAU,IAAU,CACxB,GAAG,EACH,iBAAkB,IAAA,EACpB,EAAE,EAEG,GAGT,IAAM,EAAW,EAAY,MAAM,SAanC,OATI,IAAa,EAAM,MAAM,mBAI7B,EAAM,SAAU,IAAU,CACxB,GAAG,EACH,iBAAkB,CACpB,EAAE,EAEK,GACT,CACF,CACF,CAAC,CACH,EACa,cACb,OAAQ,CACN,IAAM,EAAc,EAAY,UAAU,CAAsB,EAChE,EAAuB,EAAY,WAAW,CAAC,EAE/C,IAAM,EAA+B,EAAO,sBAAwB,CAC9D,EAAM,MAAM,gBACd,EAAM,SAAU,IAAU,CACxB,GAAG,EACH,eAAgB,EAClB,EAAE,CAEN,CAAC,EAED,UAAa,CACX,EAAY,EACZ,EAA6B,CAC/B,CACF,EACA,aAAa,EAA8B,EAAiB,GAAM,CAC5D,KAAM,MAAM,mBAAqB,IAGrC,EAAM,SAAU,IAAU,CACxB,GAAG,EACH,eAAgB,GAChB,iBAAkB,CACpB,EAAE,EAEE,GAAY,GAAgB,CAC9B,IAAM,EACJ,EAAM,MAAM,gBAAgB,IAAI,CAAQ,EAC1C,GAAI,CAAC,EACH,QAGA,EAAO,iBAAiB,SAAS,EAAuB,IAAI,CAAC,CAC1D,KAAA,EACF,eAAe,CAChB,SAAU,SACV,MAAO,QACT,CAAC,CACH,CACF,EACA,qBAAsB,CACpB,EAAM,SAAU,IAAU,CACxB,GAAG,EACH,iBAAkB,IAAA,GAClB,eAAgB,EAClB,EAAE,EAKF,EAAO,YAAY,MAAM,EACzB,EACG,aAAa,EAAA,CAAsB,CAAC,EACnC,cAAc,GAAM,UAAU,CACpC,EACA,oBAAqB,CACnB,EAAM,SAAU,IAAU,CACxB,GAAG,EACH,iBAAkB,IAAA,GAClB,eAAgB,EAClB,EAAE,EACF,EACG,aAAa,EAAA,CAAsB,CAAC,EACnC,cAAc,GAAO,UAAU,CACrC,EACA,MAAM,aAAa,EAGhB,CACD,IAAM,EAAS,MAAM,EAAY,aAAa,CAAO,EACjD,EAAY,oBACd,MAAM,EAAY,oBAAoB,CACpC,SAAU,EAAO,GACjB,UAAW,EAAO,SAAU,GAAO,EAAG,SAAS,EAC/C,QACF,CAAC,EAED,EAAgB,cAAc,SAAS,QAAQ,EAAU,CACvD,OAAQ,GACR,SAAU,EAAO,EACnB,CAAC,CAEL,EACA,sBACA,sBACF,CACF,CACF,ECjYsB,EAAtB,KAAsC,CAUtC,ECKa,EAAb,cAA4C,CAAgB,CAEvC,OACA,KAFnB,YACE,EACA,EACA,CACA,MAAM,EAHW,KAAA,OAAA,EACA,KAAA,KAAA,CAGnB,CAKA,iBAA2B,CACzB,MAAO,EACT,CAKA,cAAc,EAA8B,CAC1C,MAAO,EACT,CAKA,iBAAiB,EAA+B,CAC9C,OAAO,EAAQ,SAAW,KAAK,MACjC,CAKA,iBAAiB,EAA+B,CAC9C,OAAO,EAAQ,SAAW,KAAK,QAAU,KAAK,OAAS,QACzD,CAKA,gBAAgB,EAA8B,CAC5C,OAAO,KAAK,OAAS,QACvB,CAKA,iBAAiB,EAA8B,CAC7C,MAAO,EACT,CAKA,mBAAmB,EAA8B,CAC/C,MAAO,EACT,CAOA,eAAe,EAAsB,EAAyB,CAK5D,MAJA,CAAK,GAIE,CAAC,EAAQ,UAAU,KACvB,GACC,EAAS,QAAU,GAAS,EAAS,QAAQ,SAAS,KAAK,MAAM,CACrE,CACF,CAOA,kBAAkB,EAAsB,EAAyB,CAK/D,MAJA,CAAK,GAIE,EAAQ,UAAU,KACtB,GACC,EAAS,QAAU,GAAS,EAAS,QAAQ,SAAS,KAAK,MAAM,CACrE,CACF,CACF,EC/Ea,EAAb,cAAuC,EAAA,CAAY,CAE9B,OACA,SAFnB,YACE,EACA,EACA,EACA,CACA,MAAM,CAAI,EAJO,KAAA,OAAA,EACA,KAAA,SAAA,CAInB,CAKA,MAAa,aAAa,EAMF,CACtB,IAAI,EAAS,KAAK,SAAS,aAAa,CACtC,KAAM,EAAQ,QAChB,CAAC,EAUD,MARA,GAAS,KAAK,SAAS,WAAW,EAAO,GAAI,CAC3C,QAAS,EAAQ,eAAe,KAChC,KAAM,CACJ,SAAU,EAAQ,eAAe,SACjC,OAAQ,KAAK,MACf,CACF,CAAC,EAEM,KAAK,yBAAyB,CAAM,CAC7C,CAGA,oBAA6B,IAAA,GAK7B,MAAa,WAAW,EAMC,CACvB,IAAM,EAAS,KAAK,SAAS,WAAW,EAAQ,SAAU,CACxD,QAAS,EAAQ,QAAQ,KACzB,KAAM,CACJ,SAAU,EAAQ,QAAQ,SAC1B,OAAQ,KAAK,MACf,CACF,CAAC,EAED,OAAO,KAAK,2BACV,EAAO,SAAS,EAAO,SAAS,OAAS,EAC3C,CACF,CAKA,MAAa,cAAc,EAOxB,CACD,IAAM,EAAU,KAAK,SAAS,iBAC5B,EAAQ,SACR,EAAQ,UACR,EACF,EAEA,GAAI,CAAC,EACH,MAAU,MAAM,mBAAmB,EAGrC,KAAK,SAAS,cAAc,EAAQ,SAAU,EAAQ,UAAW,CAC/D,QAAS,EAAQ,QAAQ,KACzB,KAAM,CACJ,GAAG,EAAQ,KACX,SAAU,EAAQ,QAAQ,QAC5B,CACF,CAAC,CACH,CAEA,2BAAmC,EAAsC,CACvE,IAAM,EAAmC,CAAC,EAE1C,IAAK,IAAM,KAAa,EAAQ,MAAM,WACpC,CAAC,EAA8B,CAC/B,IAAM,EAAmB,EAAU,KAChC,GAAM,EAAE,QAAU,EAAS,KAC9B,EACI,GACF,EAAiB,QAAQ,KAAK,EAAS,MAAM,EAC7C,EAAiB,UAAY,IAAI,KAC/B,KAAK,IAAI,EAAiB,UAAU,QAAQ,EAAG,EAAS,SAAS,CACnE,GAEA,EAAU,KAAK,CACb,MAAO,EAAS,MAChB,UAAW,IAAI,KAAK,EAAS,SAAS,EACtC,QAAS,CAAC,EAAS,MAAM,CAC3B,CAAC,CAEL,CAEA,MAAO,CACL,KAAM,UACN,GAAI,EAAQ,GACZ,KAAM,EAAQ,QACd,SAAU,EAAQ,MAAM,SACxB,OAAQ,EAAQ,MAAM,OACtB,UAAW,IAAI,KAAK,EAAQ,SAAS,EACrC,UAAW,IAAI,KAAK,EAAQ,SAAS,EACrC,WACF,CACF,CAEA,yBAAiC,EAAmC,CAClE,MAAO,CACL,KAAM,SACN,GAAI,EAAO,GACX,SAAU,EAAO,SAAS,IAAK,GAC7B,KAAK,2BAA2B,CAAO,CACzC,EACA,SAAU,CAAC,CAAC,EAAO,WACnB,SAAU,EAAO,MAAM,SACvB,UAAW,IAAI,KAAK,EAAO,SAAS,EACpC,UAAW,IAAI,KAAK,EAAO,SAAS,CACtC,CACF,CAKA,MAAa,cAAc,EAAkD,CAC3E,KAAK,SAAS,cAAc,EAAQ,SAAU,EAAQ,SAAS,CACjE,CAKA,MAAa,aAAa,EAA+B,CACvD,KAAK,SAAS,aAAa,EAAQ,QAAQ,CAC7C,CAKA,MAAa,cAAc,EAA+B,CACxD,KAAK,SAAS,aAAa,EAAQ,SAAU,CAC3C,WAAY,IAAI,KAAK,CAAA,CAAE,YAAY,CACrC,CAAC,CACH,CAKA,MAAa,gBAAgB,EAA+B,CAC1D,KAAK,SAAS,aAAa,EAAQ,SAAU,CAC3C,WAAY,IACd,CAAC,CACH,CAOA,MAAa,YAAY,EAItB,CACD,IAAM,EAAU,KAAK,SAAS,iBAC5B,EAAQ,SACR,EAAQ,UACR,EACF,EAEA,GAAI,CAAC,EACH,MAAU,MAAM,mBAAmB,EAGrC,KAAK,SAAS,cAAc,EAAQ,SAAU,EAAQ,UAAW,CAC/D,KAAM,CACJ,GAAG,EAAQ,KACX,UAAW,CACT,GAAK,EAAQ,MAAM,WAAa,CAAC,EACjC,CACE,MAAO,EAAQ,MACf,UAAW,KAAK,IAAI,EACpB,OAAQ,KAAK,MACf,CACF,CACF,CACF,CAAC,CACH,CAOA,MAAa,eAAe,EAIzB,CACD,IAAM,EAAU,KAAK,SAAS,iBAC5B,EAAQ,SACR,EAAQ,UACR,EACF,EAEA,GAAI,CAAC,EACH,MAAU,MAAM,mBAAmB,EAGrC,KAAK,SAAS,cAAc,EAAQ,SAAU,EAAQ,UAAW,CAC/D,KAAM,CACJ,GAAG,EAAQ,KACX,WACG,EAAQ,MAAM,WAAa,CAAC,EAAA,CAC7B,OACC,GACC,EAAS,QAAU,EAAQ,OAAS,EAAS,SAAW,KAAK,MACjE,CACF,CACF,CAAC,CACH,CAEA,UAAiB,EAA8B,CAC7C,IAAM,EAAS,KAAK,SAAS,UAAU,CAAQ,EAE/C,GAAI,CAAC,EACH,MAAU,MAAM,kBAAkB,EAGpC,OAAO,KAAK,yBAAyB,CAAM,CAC7C,CAEA,YAA6C,CAC3C,OAAO,IAAI,IACT,KAAK,SACF,WAAW,CAAC,CACZ,IAAK,GAAW,CAAC,EAAO,GAAI,KAAK,yBAAyB,CAAM,CAAC,CAAC,CACvE,CACF,CAEA,UAAiB,EAA4D,CAC3E,IAAM,MAAc,CAClB,EAAG,KAAK,WAAW,CAAC,CACtB,EAEA,OADA,KAAK,SAAS,aAAa,CAAK,MACnB,CACX,KAAK,SAAS,eAAe,CAAK,CACpC,CACF,CACF"}