{"version":3,"sources":["/home/runner/work/liveblocks/liveblocks/packages/liveblocks-emails/dist/index.cjs","../src/index.ts","../src/version.ts","../src/text-mention-notification.tsx","../src/lexical-editor.ts","../src/lib/utils.ts","../src/lib/batch-resolvers.ts","../src/lib/css-properties.ts","../src/tiptap-editor.ts","../src/liveblocks-text-editor.ts","../src/text-mention-content.tsx","../src/thread-notification.tsx","../src/comment-body.tsx","../src/comment-with-body.ts"],"names":["assertNever","isSerializedMentionNode","isSerializedGroupMentionNode","html","htmlSafe","MENTION_CHARACTER","jsxs","baseStyles","jsx","baseComponents"],"mappings":"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","file":"/home/runner/work/liveblocks/liveblocks/packages/liveblocks-emails/dist/index.cjs","sourcesContent":[null,"import { detectDupes } from \"@liveblocks/core\";\n\nimport { PKG_FORMAT, PKG_NAME, PKG_VERSION } from \"./version\";\n\ndetectDupes(PKG_NAME, PKG_VERSION, PKG_FORMAT);\n\nexport type { ResolveRoomInfoArgs } from \"./lib/types\";\nexport type {\n  ConvertTextEditorNodesAsHtmlStyles,\n  ConvertTextEditorNodesAsReactComponents,\n  MentionEmailAsHtmlData,\n  MentionEmailAsReactData,\n  PrepareTextMentionNotificationEmailAsHtmlOptions,\n  PrepareTextMentionNotificationEmailAsReactOptions,\n  TextEditorContainerComponentProps,\n  TextEditorMentionComponentProps,\n  TextEditorTextComponentProps,\n  TextMentionNotificationEmailDataAsHtml,\n  TextMentionNotificationEmailDataAsReact,\n} from \"./text-mention-notification\";\nexport {\n  prepareTextMentionNotificationEmailAsHtml,\n  prepareTextMentionNotificationEmailAsReact,\n} from \"./text-mention-notification\";\nexport type {\n  CommentBodyContainerComponentProps,\n  CommentBodyLinkComponentProps,\n  CommentBodyMentionComponentProps,\n  CommentBodyParagraphComponentProps,\n  CommentBodyTextComponentProps,\n  CommentEmailAsHtmlData,\n  CommentEmailAsReactData,\n  ConvertCommentBodyAsHtmlStyles,\n  ConvertCommentBodyAsReactComponents,\n  PrepareThreadNotificationEmailAsHtmlOptions,\n  PrepareThreadNotificationEmailAsReactOptions,\n  ThreadNotificationEmailDataAsHtml,\n  ThreadNotificationEmailDataAsReact,\n} from \"./thread-notification\";\nexport {\n  prepareThreadNotificationEmailAsHtml,\n  prepareThreadNotificationEmailAsReact,\n} from \"./thread-notification\";\nexport type { ResolveGroupsInfoArgs, ResolveUsersArgs } from \"@liveblocks/core\";\n","declare const __VERSION__: string;\ndeclare const TSUP_FORMAT: string;\n\nexport const PKG_NAME = \"@liveblocks/emails\";\nexport const PKG_VERSION = typeof __VERSION__ === \"string\" && __VERSION__;\nexport const PKG_FORMAT = typeof TSUP_FORMAT === \"string\" && TSUP_FORMAT;\n","import {\n  type Awaitable,\n  type BaseUserMeta,\n  type DGI,\n  type DRI,\n  type DU,\n  html,\n  htmlSafe,\n  MENTION_CHARACTER,\n  type MentionData,\n  type ResolveGroupsInfoArgs,\n  type ResolveUsersArgs,\n} from \"@liveblocks/core\";\nimport type {\n  Liveblocks,\n  TextMentionNotificationEvent,\n} from \"@liveblocks/node\";\nimport type { ComponentType, ReactNode } from \"react\";\n\nimport type { LexicalMentionNodeWithContext } from \"./lexical-editor\";\nimport {\n  findLexicalMentionNodeWithContext,\n  getMentionDataFromLexicalNode,\n  getSerializedLexicalState,\n} from \"./lexical-editor\";\nimport {\n  createBatchGroupsInfoResolver,\n  createBatchUsersResolver,\n  getResolvedForId,\n} from \"./lib/batch-resolvers\";\nimport type { CSSProperties } from \"./lib/css-properties\";\nimport { toInlineCSSString } from \"./lib/css-properties\";\nimport type { ResolveRoomInfoArgs } from \"./lib/types\";\nimport type {\n  LiveblocksTextEditorMentionNode,\n  LiveblocksTextEditorNode,\n  LiveblocksTextEditorTextNode,\n} from \"./liveblocks-text-editor\";\nimport { transformAsLiveblocksTextEditorNodes } from \"./liveblocks-text-editor\";\nimport {\n  convertTextMentionContent,\n  type ConvertTextMentionContentElements,\n} from \"./text-mention-content\";\nimport type { TiptapMentionNodeWithContext } from \"./tiptap-editor\";\nimport {\n  findTiptapMentionNodeWithContext,\n  getMentionDataFromTiptapNode,\n  getSerializedTiptapState,\n} from \"./tiptap-editor\";\n\n/** @internal hidden types */\ntype RoomTextEditor = {\n  type: \"lexical\" | \"tiptap\";\n  rootKey: string[];\n};\n\nexport type TextMentionNotificationData = (\n  | {\n      editor: \"lexical\";\n      mentionNodeWithContext: LexicalMentionNodeWithContext;\n    }\n  | {\n      editor: \"tiptap\";\n      mentionNodeWithContext: TiptapMentionNodeWithContext;\n    }\n) & {\n  createdAt: Date;\n  createdBy: string;\n  mentionData: MentionData;\n};\n\n/** @internal */\nexport const extractTextMentionNotificationData = async ({\n  client,\n  event,\n}: {\n  client: Liveblocks;\n  event: TextMentionNotificationEvent;\n}): Promise<TextMentionNotificationData | null> => {\n  const { roomId, userId, inboxNotificationId } = event.data;\n\n  const [room, inboxNotification] = await Promise.all([\n    client.getRoom(roomId),\n    client.getInboxNotification({ inboxNotificationId, userId }),\n  ]);\n\n  // Check for notification kind\n  if (inboxNotification.kind !== \"textMention\") {\n    console.warn('Inbox notification is not of kind \"textMention\"');\n    return null;\n  }\n\n  // Aligned behaviors w/ `@liveblocks/react-ui`.\n  const isUnread =\n    inboxNotification.readAt === null ||\n    inboxNotification.notifiedAt > inboxNotification.readAt;\n\n  // Notification read so do nothing\n  if (!isUnread) {\n    return null;\n  }\n\n  // Do nothing if the room as no text editor associated.\n  // We do not throw not to impact the final developer experience.\n  // @ts-expect-error - Hidden property\n  const textEditor = room.experimental_textEditor as RoomTextEditor | undefined;\n  if (!textEditor) {\n    console.warn(`Room \"${room.id}\" does not a text editor associated with it`);\n    return null;\n  }\n\n  // For now we use the `notifiedAt` inbox notification data\n  // to represent the creation date as we have currently\n  // a 1 - 1 notification <> activity\n  const mentionCreatedAt = inboxNotification.notifiedAt;\n  // In context of a text mention notification `createdBy` is a user ID\n  const mentionAuthorUserId = inboxNotification.createdBy;\n\n  const buffer = await client.getYjsDocumentAsBinaryUpdate(roomId);\n  const editorKey = textEditor.rootKey;\n  // TODO: temporarily grab the first entrance, later we will handle multiple editors\n  const key = Array.isArray(editorKey) ? editorKey[0]! : editorKey;\n\n  switch (textEditor.type) {\n    case \"lexical\": {\n      const state = getSerializedLexicalState({ buffer, key });\n      const mentionNodeWithContext = findLexicalMentionNodeWithContext({\n        root: state,\n        textMentionId: inboxNotification.mentionId,\n      });\n\n      // The mention node did not exists so we do not have to send an email.\n      if (mentionNodeWithContext === null) {\n        return null;\n      }\n\n      const mentionData = getMentionDataFromLexicalNode(\n        mentionNodeWithContext.mention\n      );\n\n      return {\n        editor: \"lexical\",\n        mentionNodeWithContext,\n        mentionData,\n        createdAt: mentionCreatedAt,\n        createdBy: mentionAuthorUserId,\n      };\n    }\n    case \"tiptap\": {\n      const state = getSerializedTiptapState({ buffer, key });\n      const mentionNodeWithContext = findTiptapMentionNodeWithContext({\n        root: state,\n        textMentionId: inboxNotification.mentionId,\n      });\n\n      // The mention node did not exists so we do not have to send an email.\n      if (mentionNodeWithContext === null) {\n        return null;\n      }\n\n      const mentionData = getMentionDataFromTiptapNode(\n        mentionNodeWithContext.mention\n      );\n\n      return {\n        editor: \"tiptap\",\n        mentionNodeWithContext,\n        mentionData,\n        createdAt: mentionCreatedAt,\n        createdBy: mentionAuthorUserId,\n      };\n    }\n  }\n};\n\nexport type MentionEmailData<\n  ContentType,\n  U extends BaseUserMeta = DU,\n> = MentionData & {\n  textMentionId: string;\n  roomId: string;\n  author: U; // Author of the mention\n  createdAt: Date;\n  content: ContentType;\n};\n\nexport type MentionEmailAsHtmlData<U extends BaseUserMeta = DU> =\n  MentionEmailData<string, U>;\n\nexport type MentionEmailAsReactData<U extends BaseUserMeta = DU> =\n  MentionEmailData<ReactNode, U>;\n\nexport type TextMentionNotificationEmailData<\n  ContentType,\n  U extends BaseUserMeta = DU,\n  M extends MentionEmailData<ContentType, U> = MentionEmailData<ContentType, U>,\n> = {\n  mention: M;\n  roomInfo: DRI;\n};\n\ntype PrepareTextMentionNotificationEmailOptions<U extends BaseUserMeta = DU> = {\n  /**\n   * A function that returns room info from room IDs.\n   */\n  resolveRoomInfo?: (args: ResolveRoomInfoArgs) => Awaitable<DRI | undefined>;\n\n  /**\n   * A function that returns user info from user IDs.\n   * You should return a list of user objects of the same size, in the same order.\n   */\n  resolveUsers?: (\n    args: ResolveUsersArgs\n  ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>;\n\n  /**\n   * A function that returns group info from group IDs.\n   * You should return a list of group info objects of the same size, in the same order.\n   */\n  resolveGroupsInfo?: (\n    args: ResolveGroupsInfoArgs\n  ) => Awaitable<(DGI | undefined)[] | undefined>;\n};\n\n/**\n * @internal\n * exported for testing purposes.\n */\nexport async function prepareTextMentionNotificationEmail<\n  ContentType,\n  U extends BaseUserMeta = DU,\n>(\n  client: Liveblocks,\n  event: TextMentionNotificationEvent,\n  options: PrepareTextMentionNotificationEmailOptions<U>,\n  elements: ConvertTextMentionContentElements<ContentType, U>,\n  callerName: string\n): Promise<TextMentionNotificationEmailData<ContentType, U> | null> {\n  const { roomId, mentionId } = event.data;\n\n  const data = await extractTextMentionNotificationData({ client, event });\n  if (data === null) {\n    return null;\n  }\n\n  const roomInfo = options.resolveRoomInfo\n    ? await options.resolveRoomInfo({ roomId: event.data.roomId })\n    : undefined;\n\n  const resolvedRoomInfo: DRI = {\n    ...roomInfo,\n    name: roomInfo?.name ?? event.data.roomId,\n  };\n\n  const batchUsersResolver = createBatchUsersResolver<U>({\n    resolveUsers: options.resolveUsers,\n    callerName,\n  });\n  const batchGroupsInfoResolver = createBatchGroupsInfoResolver({\n    resolveGroupsInfo: options.resolveGroupsInfo,\n    callerName,\n  });\n\n  const authorsIds = [data.createdBy];\n  const authorsInfoPromise = batchUsersResolver.get(authorsIds);\n\n  let textEditorNodes: LiveblocksTextEditorNode[] = [];\n\n  switch (data.editor) {\n    case \"lexical\": {\n      textEditorNodes = transformAsLiveblocksTextEditorNodes({\n        editor: \"lexical\",\n        mention: data.mentionNodeWithContext,\n      });\n      break;\n    }\n    case \"tiptap\": {\n      textEditorNodes = transformAsLiveblocksTextEditorNodes({\n        editor: \"tiptap\",\n        mention: data.mentionNodeWithContext,\n      });\n      break;\n    }\n  }\n\n  const contentPromise = convertTextMentionContent<ContentType, U>(\n    textEditorNodes,\n    {\n      resolveUsers: ({ userIds }) => batchUsersResolver.get(userIds),\n      resolveGroupsInfo: ({ groupIds }) =>\n        batchGroupsInfoResolver.get(groupIds),\n      elements,\n    }\n  );\n\n  await batchUsersResolver.resolve();\n  await batchGroupsInfoResolver.resolve();\n\n  const [authorsInfo, content] = await Promise.all([\n    authorsInfoPromise,\n    contentPromise,\n  ]);\n\n  const authorInfo = getResolvedForId(data.createdBy, authorsIds, authorsInfo);\n\n  return {\n    mention: {\n      ...data.mentionData,\n      textMentionId: mentionId,\n      roomId,\n      author: {\n        id: data.createdBy,\n        info: authorInfo ?? { name: data.createdBy },\n      } as U,\n      content,\n      createdAt: data.createdAt,\n    },\n    roomInfo: resolvedRoomInfo,\n  };\n}\n\nexport type TextEditorContainerComponentProps = {\n  /**\n   * The nodes of the text editor\n   */\n  children: ReactNode;\n};\n\nexport type TextEditorMentionComponentProps<U extends BaseUserMeta = DU> = {\n  /**\n   * The mention element.\n   */\n  element: LiveblocksTextEditorMentionNode;\n\n  /**\n   * The mention's user info, if the mention is a user mention and the `resolvedUsers` option was provided.\n   */\n  user?: U[\"info\"];\n\n  /**\n   * The mention's group info, if the mention is a group mention and the `resolvedGroupsInfo` option was provided.\n   */\n  group?: DGI;\n};\n\nexport type TextEditorTextComponentProps = {\n  /**\n   * The text element.\n   */\n  element: LiveblocksTextEditorTextNode;\n};\n\nexport type ConvertTextEditorNodesAsReactComponents<\n  U extends BaseUserMeta = DU,\n> = {\n  /**\n   *\n   * The component used to act as a container to wrap text editor nodes,\n   */\n  Container: ComponentType<TextEditorContainerComponentProps>;\n\n  /**\n   * The component used to display mentions.\n   */\n  Mention: ComponentType<TextEditorMentionComponentProps<U>>;\n\n  /**\n   * The component used to display text nodes.\n   */\n  Text: ComponentType<TextEditorTextComponentProps>;\n};\n\nconst baseComponents: ConvertTextEditorNodesAsReactComponents<BaseUserMeta> = {\n  Container: ({ children }) => <div>{children}</div>,\n  Mention: ({ element, user, group }) => (\n    <span data-mention>\n      {MENTION_CHARACTER}\n      {user?.name ?? group?.name ?? element.id}\n    </span>\n  ),\n  Text: ({ element }) => {\n    // Note: construction following the schema 👇\n    // <code><s><em><strong>{element.text}</strong></s></em></code>\n    let children: ReactNode = element.text;\n\n    if (element.bold) {\n      children = <strong>{children}</strong>;\n    }\n\n    if (element.italic) {\n      children = <em>{children}</em>;\n    }\n\n    if (element.strikethrough) {\n      children = <s>{children}</s>;\n    }\n\n    if (element.code) {\n      children = <code>{children}</code>;\n    }\n\n    return <span>{children}</span>;\n  },\n};\n\nexport type PrepareTextMentionNotificationEmailAsReactOptions<\n  U extends BaseUserMeta = DU,\n> = PrepareTextMentionNotificationEmailOptions & {\n  /**\n   * The components used to customize the resulting React nodes. Each components has\n   * priority over the base components inherited.\n   */\n  components?: Partial<ConvertTextEditorNodesAsReactComponents<U>>;\n};\n\nexport type TextMentionNotificationEmailDataAsReact<\n  U extends BaseUserMeta = DU,\n> = TextMentionNotificationEmailData<ReactNode, U, MentionEmailAsReactData<U>>;\n\n/**\n * Prepares data from a `TextMentionNotificationEvent` and convert content as React nodes.\n *\n * @param client The `Liveblocks` node client\n * @param event The `TextMentionNotificationEvent` received in the webhook handler\n * @param options The optional options to provide to resolve users, resolve room info and customize comment bodies React components.\n *\n * It returns a `TextMentionNotificationEmailDataAsReact` or `null` if there are no existing text mention.\n *\n * @example\n * import { Liveblocks} from \"@liveblocks/node\"\n * import { prepareTextMentionNotificationEmailAsReact } from \"@liveblocks/emails\"\n *\n * const liveblocks = new Liveblocks({ secret: \"sk_...\" })\n * const emailData = prepareTextMentionNotificationEmailAsReact(\n *  liveblocks,\n *  event,\n *  {\n *    resolveUsers,\n *    resolveRoomInfo,\n *    components,\n *  }\n * )\n */\nexport async function prepareTextMentionNotificationEmailAsReact(\n  client: Liveblocks,\n  event: TextMentionNotificationEvent,\n  options: PrepareTextMentionNotificationEmailAsReactOptions<BaseUserMeta> = {}\n): Promise<TextMentionNotificationEmailDataAsReact | null> {\n  const Components = { ...baseComponents, ...options.components };\n  const data = await prepareTextMentionNotificationEmail<\n    ReactNode,\n    BaseUserMeta\n  >(\n    client,\n    event,\n    {\n      resolveRoomInfo: options.resolveRoomInfo,\n      resolveUsers: options.resolveUsers,\n    },\n    {\n      container: ({ children }) => (\n        <Components.Container key=\"lb-text-editor-container\">\n          {children}\n        </Components.Container>\n      ),\n      mention: ({ node, user }, index) => (\n        <Components.Mention\n          key={`lb-text-editor-mention-${index}`}\n          element={node}\n          user={user}\n        />\n      ),\n      text: ({ node }, index) => (\n        <Components.Text key={`lb-text-editor-text-${index}`} element={node} />\n      ),\n    },\n    \"prepareTextMentionNotificationEmailAsReact\"\n  );\n\n  if (data === null) {\n    return null;\n  }\n\n  return data;\n}\n\nexport type ConvertTextEditorNodesAsHtmlStyles = {\n  /**\n   * The default inline CSS styles used to display container element.\n   */\n  container: CSSProperties;\n  /**\n   * The default inline CSS styles used to display text `<strong />` elements.\n   */\n  strong: CSSProperties;\n  /**\n   * The default inline CSS styles used to display text `<code />` elements.\n   */\n  code: CSSProperties;\n  /**\n   * The default inline CSS styles used to display mentions.\n   */\n  mention: CSSProperties;\n};\n\nexport const baseStyles: ConvertTextEditorNodesAsHtmlStyles = {\n  container: {\n    fontSize: \"14px\",\n  },\n  strong: {\n    fontWeight: 500,\n  },\n  code: {\n    fontFamily:\n      'ui-monospace, Menlo, Monaco, \"Cascadia Mono\", \"Segoe UI Mono\", \"Roboto Mono\", \"Oxygen Mono\", \"Ubuntu Mono\", \"Source Code Pro\", \"Fira Mono\", \"Droid Sans Mono\", \"Consolas\", \"Courier New\", monospace',\n    backgroundColor: \"rgba(0,0,0,0.05)\",\n    border: \"solid 1px rgba(0,0,0,0.1)\",\n    borderRadius: \"4px\",\n  },\n  mention: {\n    color: \"blue\",\n  },\n};\n\nexport type PrepareTextMentionNotificationEmailAsHtmlOptions =\n  PrepareTextMentionNotificationEmailOptions & {\n    /**\n     * The styles used to customize the html elements in the resulting html safe string.\n     * Each styles has priority over the base styles inherited.\n     */\n    styles?: Partial<ConvertTextEditorNodesAsHtmlStyles>;\n  };\n\nexport type TextMentionNotificationEmailDataAsHtml<\n  U extends BaseUserMeta = DU,\n> = TextMentionNotificationEmailData<string, U, MentionEmailAsHtmlData<U>>;\n\n/**\n * Prepares data from a `TextMentionNotificationEvent` and convert content  as an html safe string.\n *\n * @param client The `Liveblocks` node client\n * @param event The `TextMentionNotificationEvent` received in the webhook handler\n * @param options The optional options to provide to resolve users, resolve room info and customize comment bodies React components.\n *\n * It returns a `TextMentionNotificationEmailDataAsReact` or `null` if there are no existing text mention.\n *\n * @example\n * import { Liveblocks} from \"@liveblocks/node\"\n * import { prepareTextMentionNotificationEmailAsHtml } from \"@liveblocks/emails\"\n *\n * const liveblocks = new Liveblocks({ secret: \"sk_...\" })\n * const emailData = prepareTextMentionNotificationEmailAsHtml(\n *  liveblocks,\n *  event,\n *  {\n *    resolveUsers,\n *    resolveRoomInfo,\n *    styles,\n *  }\n * )\n */\nexport async function prepareTextMentionNotificationEmailAsHtml(\n  client: Liveblocks,\n  event: TextMentionNotificationEvent,\n  options: PrepareTextMentionNotificationEmailAsHtmlOptions = {}\n): Promise<TextMentionNotificationEmailDataAsHtml | null> {\n  const styles = { ...baseStyles, ...options.styles };\n  const data = await prepareTextMentionNotificationEmail<string, BaseUserMeta>(\n    client,\n    event,\n    {\n      resolveRoomInfo: options.resolveRoomInfo,\n      resolveUsers: options.resolveUsers,\n    },\n    {\n      container: ({ children }) => {\n        const content = [\n          // prettier-ignore\n          html`<div style=\"${toInlineCSSString(styles.container)}\">${htmlSafe(children.join(\"\"))}</div>`,\n        ];\n\n        return content.join(\"\\n\"); //NOTE: to represent a valid HTML string\n      },\n      mention: ({ node, user, group }) => {\n        // prettier-ignore\n        return html`<span data-mention style=\"${toInlineCSSString(styles.mention)}\">${MENTION_CHARACTER}${user?.name ? html`${user?.name}` : group?.name ? html`${group?.name}` : node.id}</span>`\n      },\n      text: ({ node }) => {\n        // Note: construction following the schema 👇\n        // <code><s><em><strong>{node.text}</strong></s></em></code>\n        let children = node.text;\n        if (!children) {\n          return html`${children}`;\n        }\n\n        if (node.bold) {\n          // prettier-ignore\n          children = html`<strong style=\"${toInlineCSSString(styles.strong)}\">${children}</strong>`;\n        }\n\n        if (node.italic) {\n          // prettier-ignore\n          children = html`<em>${children}</em>`;\n        }\n\n        if (node.strikethrough) {\n          // prettier-ignore\n          children = html`<s>${children}</s>`;\n        }\n\n        if (node.code) {\n          // prettier-ignore\n          children = html`<code style=\"${toInlineCSSString(styles.code)}\">${children}</code>`;\n        }\n\n        return html`${children}`;\n      },\n    },\n    \"prepareTextMentionNotificationEmailAsHtml\"\n  );\n\n  if (data === null) {\n    return null;\n  }\n\n  return data;\n}\n","import type { Json, JsonObject, MentionData } from \"@liveblocks/core\";\nimport { assertNever } from \"@liveblocks/core\";\nimport * as Y from \"yjs\";\n\nimport { isMentionNodeAttributeId, isString } from \"./lib/utils\";\n\nexport interface SerializedBaseLexicalNode {\n  type: string;\n  attributes: JsonObject;\n}\n\nexport interface SerializedLexicalTextNode extends SerializedBaseLexicalNode {\n  text: string;\n  group: \"text\";\n}\n\nexport interface SerializedLexicalElementNode<\n  T extends SerializedBaseLexicalNode,\n> extends SerializedBaseLexicalNode {\n  children: Array<T>;\n  group: \"element\";\n}\n\nexport interface SerializedLexicalDecoratorNode extends SerializedBaseLexicalNode {\n  group: \"decorator\";\n}\n\nexport interface SerializedLexicalMentionNode extends SerializedLexicalDecoratorNode {\n  type: \"lb-mention\";\n  attributes: {\n    __id: string;\n    __type: \"lb-mention\";\n    __userId: string;\n  };\n}\n\nexport interface SerializedLexicalGroupMentionNode extends SerializedLexicalDecoratorNode {\n  type: \"lb-group-mention\";\n  attributes: {\n    __id: string;\n    __type: \"lb-group-mention\";\n    __groupId: string;\n    __userIds: string[] | undefined;\n  };\n}\n\nexport interface SerializedLexicalLineBreakNode extends SerializedBaseLexicalNode {\n  group: \"linebreak\";\n}\n\nexport type SerializedLexicalNode =\n  | SerializedLexicalTextNode\n  | SerializedLexicalLineBreakNode\n  | SerializedLexicalDecoratorNode\n  | SerializedLexicalElementNode<SerializedLexicalNode>;\n\nexport type SerializedLexicalRootNodeChildren = Array<\n  Readonly<\n    | SerializedLexicalElementNode<Readonly<SerializedLexicalNode>>\n    | SerializedLexicalDecoratorNode\n    | SerializedLexicalLineBreakNode\n  >\n>;\n\nexport interface SerializedLexicalRootNode extends Readonly<SerializedBaseLexicalNode> {\n  readonly type: \"root\";\n  readonly children: SerializedLexicalRootNodeChildren;\n}\n\n/**\n * Create a serialized Lexical Map node.\n * Y.Map shared types are used to represent text nodes and line break nodes in Lexical.js\n */\nfunction createSerializedLexicalMapNode(\n  item: Y.Map<Json>\n): SerializedLexicalTextNode | SerializedLexicalLineBreakNode {\n  const type = item.get(\"__type\");\n  if (typeof type !== \"string\") {\n    throw new Error(\n      `Expected ${item.constructor.name} to include type attribute`\n    );\n  }\n\n  // Y.Map in Lexical stores all attributes defined in Lexical TextNode and LineBreakNode class.\n  const attributes = Object.fromEntries(item.entries());\n  if (type === \"linebreak\") {\n    return {\n      type,\n      attributes,\n      group: \"linebreak\",\n    };\n  }\n\n  return {\n    type,\n    attributes,\n    text: \"\",\n    group: \"text\",\n  };\n}\n\n/**\n * Create a serialized Lexical decorator node.\n * Y.XmlElement shared types are used to represent decorator nodes in Lexical.js\n */\nfunction createSerializedLexicalDecoratorNode(\n  item: Y.XmlElement\n): SerializedLexicalDecoratorNode {\n  const type = item.getAttribute(\"__type\");\n  if (typeof type !== \"string\") {\n    throw new Error(\n      `Expected ${item.constructor.name} to include type attribute`\n    );\n  }\n  const attributes = item.getAttributes();\n\n  return {\n    type,\n    attributes,\n    group: \"decorator\",\n  };\n}\n\n/**\n * Create a serialized Lexical element node.\n * Y.XmlText shared types are used to represent element nodes (e.g. paragraph, blockquote) in Lexical.js\n */\nfunction createSerializedLexicalElementNode(\n  item: Y.XmlText\n): SerializedLexicalElementNode<SerializedLexicalNode> {\n  // Note: disabling eslint rule as `getAttribute` returns `any` by default on `Y.XmlText` items.\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n  const type = item.getAttribute(\"__type\");\n  if (typeof type !== \"string\") {\n    throw new Error(\n      `Expected ${item.constructor.name} to include type attribute`\n    );\n  }\n  const attributes = item.getAttributes();\n\n  let start = item._start;\n  const children: SerializedLexicalNode[] = [];\n  while (start !== null) {\n    // If the item is deleted, skip it.\n    if (start.deleted) {\n      start = start.right;\n      continue;\n    }\n\n    if (start.content instanceof Y.ContentType) {\n      const content = start.content.type as Y.AbstractType<Json>;\n      if (content instanceof Y.XmlText) {\n        children.push(createSerializedLexicalElementNode(content));\n      } else if (content instanceof Y.Map) {\n        children.push(createSerializedLexicalMapNode(content as Y.Map<Json>));\n      } else if (content instanceof Y.XmlElement) {\n        children.push(\n          createSerializedLexicalDecoratorNode(content as Y.XmlElement)\n        );\n      }\n    }\n    // ContentString is used to store text content of a text node in the Y.js doc.\n    else if (start.content instanceof Y.ContentString) {\n      if (children.length > 0) {\n        const last = children[children.length - 1];\n        if (last && last.group === \"text\") {\n          last.text += start.content.str;\n        }\n      }\n    }\n\n    start = start.right;\n  }\n\n  return {\n    type,\n    attributes,\n    children,\n    group: \"element\",\n  };\n}\n\n/**\n * Create a serialized Lexical root node.\n */\nexport function createSerializedLexicalRootNode(\n  root: Y.XmlText\n): SerializedLexicalRootNode {\n  try {\n    const children: Array<\n      | SerializedLexicalElementNode<SerializedLexicalNode>\n      | SerializedLexicalDecoratorNode\n    > = [];\n    let start = root._start;\n    while (start !== null && start !== undefined) {\n      // If the item is deleted, skip it.\n      if (start.deleted) {\n        start = start.right;\n        continue;\n      }\n\n      if (start.content instanceof Y.ContentType) {\n        const content = start.content.type as Y.AbstractType<Json>;\n\n        // Immediate children of root must be XmlText (element nodes) or XmlElement (decorator nodes).\n        if (content instanceof Y.XmlText) {\n          children.push(createSerializedLexicalElementNode(content));\n        } else if (content instanceof Y.XmlElement) {\n          children.push(\n            createSerializedLexicalDecoratorNode(content as Y.XmlElement)\n          );\n        }\n      }\n\n      start = start.right;\n    }\n\n    return {\n      children,\n      type: \"root\",\n      attributes: root.getAttributes(),\n    };\n  } catch (err) {\n    console.error(err);\n    return {\n      children: [],\n      type: \"root\",\n      attributes: root.getAttributes(),\n    };\n  }\n}\n\n/**\n * Convert a document as binaries to a\n * serialized lexical state\n */\nexport function getSerializedLexicalState({\n  buffer,\n  key,\n}: {\n  buffer: ArrayBuffer;\n  key: string;\n}): SerializedLexicalRootNode {\n  const update = new Uint8Array(buffer);\n\n  // Construct a Y.js document from the binary update\n  const document = new Y.Doc();\n  Y.applyUpdate(document, update);\n\n  // Convert the Y.js document to a serializable Lexical state\n  const root = document.get(key, Y.XmlText);\n  const state = createSerializedLexicalRootNode(root);\n\n  // Destroy the Y.js document after the conversion\n  document.destroy();\n\n  return state;\n}\n\n/**\n * Linebreaks in the `Lexical` world are equivalent to\n * hard breaks (like we can have in `tiptap`).\n * They are created by using keys like `shift+enter` or `mod+enter`.\n */\nconst isSerializedLineBreakNode = (\n  node: SerializedLexicalNode\n): node is SerializedLexicalLineBreakNode => {\n  return node.group === \"linebreak\";\n};\n\nconst isSerializedElementNode = (\n  node: SerializedLexicalNode\n): node is SerializedLexicalElementNode<Readonly<SerializedLexicalNode>> => {\n  return node.group === \"element\" && node.children !== undefined;\n};\n\nconst isEmptySerializedElementNode = (node: SerializedLexicalNode): boolean => {\n  return isSerializedElementNode(node) && node.children.length === 0;\n};\n\nconst isMentionNodeType = (type: string): type is \"lb-mention\" => {\n  return type === \"lb-mention\";\n};\n\nconst isMentionNodeAttributeType = (type: unknown): type is \"lb-mention\" => {\n  return isString(type) && type === \"lb-mention\";\n};\n\nexport const isSerializedMentionNode = (\n  node: SerializedLexicalDecoratorNode\n): node is SerializedLexicalMentionNode => {\n  const attributes = node.attributes;\n\n  return (\n    isMentionNodeType(node.type) &&\n    isMentionNodeAttributeType(attributes.__type) &&\n    isMentionNodeAttributeId(attributes.__id) &&\n    isString(attributes.__userId)\n  );\n};\n\nconst isGroupMentionNodeType = (type: string): type is \"lb-group-mention\" => {\n  return type === \"lb-group-mention\";\n};\n\nconst isGroupMentionNodeAttributeType = (\n  type: unknown\n): type is \"lb-group-mention\" => {\n  return isString(type) && type === \"lb-group-mention\";\n};\n\nexport const isSerializedGroupMentionNode = (\n  node: SerializedLexicalDecoratorNode\n): node is SerializedLexicalGroupMentionNode => {\n  const attributes = node.attributes;\n\n  return (\n    isGroupMentionNodeType(node.type) &&\n    isGroupMentionNodeAttributeType(attributes.__type) &&\n    isMentionNodeAttributeId(attributes.__id) &&\n    isString(attributes.__groupId) &&\n    (attributes.__userIds === undefined || Array.isArray(attributes.__userIds))\n  );\n};\n\n/**\n * Internal type helper when flattening nodes.\n * It helps to better extract mention node with context by marking\n * start and ends of paragraph and by handling specific use cases such as\n * using twice the `enter` key which will create an empty paragraph\n * at the first `enter`:\n *\n *  \"\n *  Hey @charlie what's up?\n *  _enter_once_\n *  _enter_twice_\n *  \"\n */\ninterface FlattenedLexicalElementNodeMarker {\n  group: \"element-marker\";\n  marker: \"start\" | \"end\";\n}\n\nconst isFlattenedLexicalElementNodeMarker = (\n  node: SerializedLexicalNode | FlattenedLexicalElementNodeMarker\n): node is FlattenedLexicalElementNodeMarker => {\n  return node.group === \"element-marker\";\n};\n\n/** @internal */\ntype FlattenedSerializedLexicalNodes = Array<\n  SerializedLexicalNode | FlattenedLexicalElementNodeMarker\n>;\n\n/** @internal - export for testing only */\nexport const flattenLexicalTree = (\n  nodes: SerializedLexicalNode[]\n): FlattenedSerializedLexicalNodes => {\n  let flattenNodes: FlattenedSerializedLexicalNodes = [];\n  for (const node of nodes) {\n    if (\n      [\"text\", \"linebreak\", \"decorator\"].includes(node.group) ||\n      isEmptySerializedElementNode(node)\n    ) {\n      flattenNodes = [...flattenNodes, node];\n    } else if (node.group === \"element\") {\n      flattenNodes = [\n        ...flattenNodes,\n        {\n          group: \"element-marker\",\n          marker: \"start\",\n        },\n        ...flattenLexicalTree(node.children),\n        {\n          group: \"element-marker\",\n          marker: \"end\",\n        },\n      ];\n    }\n  }\n\n  return flattenNodes;\n};\n\n/**\n * Lexical Mention Node with context\n */\nexport type LexicalMentionNodeWithContext = {\n  before: SerializedLexicalNode[];\n  after: SerializedLexicalNode[];\n  mention: SerializedLexicalMentionNode | SerializedLexicalGroupMentionNode;\n};\n\n/**\n * Find a Lexical mention node\n * and returns it with contextual surrounding text\n */\nexport function findLexicalMentionNodeWithContext({\n  root,\n  textMentionId,\n}: {\n  root: SerializedLexicalRootNode;\n  textMentionId: string;\n}): LexicalMentionNodeWithContext | null {\n  const nodes = flattenLexicalTree(root.children);\n\n  // Find mention node\n  let mentionNodeIndex = -1;\n\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i]!;\n    if (\n      node.group === \"decorator\" &&\n      (isSerializedMentionNode(node) || isSerializedGroupMentionNode(node)) &&\n      node.attributes.__id === textMentionId\n    ) {\n      mentionNodeIndex = i;\n      break;\n    }\n  }\n\n  // No mention node found\n  if (mentionNodeIndex === -1) {\n    return null;\n  }\n\n  // Collect nodes before and after\n  const mentionNode = nodes[mentionNodeIndex] as\n    | SerializedLexicalMentionNode\n    | SerializedLexicalGroupMentionNode;\n\n  // Apply surrounding text guesses\n  // For now let's stay simple just stop at nearest line break or element\n  const beforeNodes: SerializedLexicalNode[] = [];\n  const afterNodes: SerializedLexicalNode[] = [];\n\n  // Nodes before mention node\n  for (let i = mentionNodeIndex - 1; i >= 0; i--) {\n    const node = nodes[i]!;\n\n    // Stop if nodes are markers, line breaks or empty elements\n    if (\n      isFlattenedLexicalElementNodeMarker(node) ||\n      isSerializedLineBreakNode(node)\n    ) {\n      break;\n    }\n\n    // Stop if decorator node isn't a mention\n    if (node.group === \"decorator\" && !isMentionNodeType(node.type)) {\n      break;\n    }\n\n    beforeNodes.unshift(node);\n  }\n\n  // Nodes after mention node\n  for (let i = mentionNodeIndex + 1; i < nodes.length; i++) {\n    const node = nodes[i]!;\n\n    // Stop if nodes are markers, line breaks or empty elements\n    if (\n      isFlattenedLexicalElementNodeMarker(node) ||\n      isSerializedLineBreakNode(node)\n    ) {\n      break;\n    }\n\n    // Stop if decorator node isn't a mention\n    if (node.group === \"decorator\" && !isMentionNodeType(node.type)) {\n      break;\n    }\n\n    afterNodes.push(node);\n  }\n\n  return {\n    before: beforeNodes,\n    after: afterNodes,\n    mention: mentionNode,\n  };\n}\n\nexport function getMentionDataFromLexicalNode(\n  node: SerializedLexicalMentionNode | SerializedLexicalGroupMentionNode\n): MentionData {\n  if (isSerializedMentionNode(node)) {\n    return {\n      kind: \"user\",\n      id: node.attributes.__userId,\n    };\n  } else if (isSerializedGroupMentionNode(node)) {\n    return {\n      kind: \"group\",\n      id: node.attributes.__groupId,\n      userIds: node.attributes.__userIds,\n    };\n  }\n\n  assertNever(node, \"Unknown mention kind\");\n}\n","export const isString = (value: unknown): value is string => {\n  return typeof value === \"string\";\n};\n\nexport const isMentionNodeAttributeId = (\n  value: unknown\n): value is `in_${string}` => {\n  return isString(value) && value.startsWith(\"in_\");\n};\n\nexport const exists = <T>(input: null | undefined | T): input is T => {\n  return input !== null && input !== undefined;\n};\n","import type {\n  Awaitable,\n  BaseUserMeta,\n  DGI,\n  DU,\n  ResolveGroupsInfoArgs,\n  ResolveUsersArgs,\n} from \"@liveblocks/core\";\nimport { Promise_withResolvers, warnOnce } from \"@liveblocks/core\";\n\n/**\n * Utility to get the resolved result coming from a batch resolver for a given ID.\n */\nexport function getResolvedForId<T>(\n  id: string,\n  ids: string[],\n  results: T[] | undefined\n): T | undefined {\n  const index = ids.indexOf(id);\n\n  return results?.[index];\n}\n\n/**\n * Batch calls to a resolver callback (which expects an array of IDs\n * and returns an array of results) into a single call.\n */\nexport class BatchResolver<T> {\n  private ids = new Set<string>();\n  private results = new Map<string, T | undefined>();\n  private isResolved = false;\n  private promise: Promise<void>;\n  private resolvePromise: () => void;\n  private missingCallbackWarning: string;\n  private callback?: (\n    ids: string[]\n  ) => Awaitable<(T | undefined)[] | undefined>;\n\n  constructor(\n    callback:\n      | ((ids: string[]) => Awaitable<(T | undefined)[] | undefined>)\n      | undefined,\n    missingCallbackWarning: string\n  ) {\n    this.callback = callback;\n\n    const { promise, resolve } = Promise_withResolvers<void>();\n    this.promise = promise;\n    this.resolvePromise = resolve;\n    this.missingCallbackWarning = missingCallbackWarning;\n  }\n\n  /**\n   * Add IDs to the batch and return a promise that resolves when the entire batch is resolved.\n   * It can't be called after the batch is resolved.\n   */\n  async get(ids: string[]): Promise<(T | undefined)[] | undefined> {\n    if (this.isResolved) {\n      throw new Error(\"Batch has already been resolved.\");\n    }\n\n    ids.forEach((id) => this.ids.add(id));\n\n    // Wait for the batch to be resolved\n    await this.promise;\n\n    return ids.map((id) => this.results.get(id));\n  }\n\n  #resolveBatch() {\n    this.isResolved = true;\n    this.resolvePromise();\n  }\n\n  /**\n   * Resolve all the IDs in the batch.\n   * It can only be called once.\n   */\n  async resolve(): Promise<void> {\n    if (this.isResolved) {\n      throw new Error(\"Batch has already been resolved.\");\n    }\n\n    if (!this.callback) {\n      // Warn about the missing callback and resolve the batch early\n      warnOnce(this.missingCallbackWarning);\n      this.#resolveBatch();\n\n      return;\n    }\n\n    const ids = Array.from(this.ids);\n\n    // Call the callback once with all IDs\n    try {\n      const results = this.callback ? await this.callback(ids) : undefined;\n\n      if (results !== undefined) {\n        if (!Array.isArray(results)) {\n          throw new Error(\"Callback must return an array.\");\n        } else if (ids.length !== results.length) {\n          throw new Error(\n            `Callback must return an array of the same length as the number of provided items. Expected ${ids.length}, but got ${results.length}.`\n          );\n        }\n      }\n\n      ids.forEach((id, index) => {\n        this.results.set(id, results?.[index]);\n      });\n    } catch (error) {\n      // Still mark as resolved to prevent reuse\n      this.#resolveBatch();\n\n      throw error;\n    }\n\n    this.#resolveBatch();\n  }\n}\n\nexport function createBatchUsersResolver<U extends BaseUserMeta = DU>({\n  resolveUsers,\n  callerName,\n}: {\n  resolveUsers?: (\n    args: ResolveUsersArgs\n  ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>;\n  callerName: string;\n}): BatchResolver<U[\"info\"]> {\n  return new BatchResolver<U[\"info\"]>(\n    resolveUsers ? (userIds) => resolveUsers({ userIds }) : undefined,\n    `Set \"resolveUsers\" in \"${callerName}\" to specify users info`\n  );\n}\n\nexport function createBatchGroupsInfoResolver({\n  resolveGroupsInfo,\n  callerName,\n}: {\n  resolveGroupsInfo?: (\n    args: ResolveGroupsInfoArgs\n  ) => Awaitable<(DGI | undefined)[] | undefined>;\n  callerName: string;\n}): BatchResolver<DGI> {\n  return new BatchResolver<DGI>(\n    resolveGroupsInfo\n      ? (groupIds) => resolveGroupsInfo({ groupIds })\n      : undefined,\n    `Set \"resolveGroupsInfo\" in \"${callerName}\" to specify groups info`\n  );\n}\n","import type { Properties } from \"csstype\";\n\n/**\n * CSS properties object.\n * Type alias for DX purposes.\n *\n */\nexport type CSSProperties = Properties;\n\n/**\n * Vendors\n */\nconst VENDORS_PREFIXES = new RegExp(/^(webkit|moz|ms|o)-/);\n\n/**\n * CSS properties which accept numbers but are not in units of \"px\".\n * Based on: https://github.com/facebook/react/blob/bfe91fbecf183f85fc1c4f909e12a6833a247319/packages/react-dom-bindings/src/shared/isUnitlessNumber.js\n */\nconst UNITLESS_PROPERTIES = [\n  \"animationIterationCount\",\n  \"aspectRatio\",\n  \"borderImageOutset\",\n  \"borderImageSlice\",\n  \"borderImageWidth\",\n  \"boxFlex\",\n  \"boxFlexGroup\",\n  \"boxOrdinalGroup\",\n  \"columnCount\",\n  \"columns\",\n  \"flex\",\n  \"flexGrow\",\n  \"flexPositive\",\n  \"flexShrink\",\n  \"flexNegative\",\n  \"flexOrder\",\n  \"gridArea\",\n  \"gridRow\",\n  \"gridRowEnd\",\n  \"gridRowSpan\",\n  \"gridRowStart\",\n  \"gridColumn\",\n  \"gridColumnEnd\",\n  \"gridColumnSpan\",\n  \"gridColumnStart\",\n  \"fontWeight\",\n  \"lineClamp\",\n  \"lineHeight\",\n  \"opacity\",\n  \"order\",\n  \"orphans\",\n  \"scale\",\n  \"tabSize\",\n  \"widows\",\n  \"zIndex\",\n  \"zoom\",\n  \"fillOpacity\",\n  \"floodOpacity\",\n  \"stopOpacity\",\n  \"strokeDasharray\",\n  \"strokeDashoffset\",\n  \"strokeMiterlimit\",\n  \"strokeOpacity\",\n  \"strokeWidth\",\n  \"MozAnimationIterationCount\",\n  \"MozBoxFlex\",\n  \"MozBoxFlexGroup\",\n  \"MozLineClamp\",\n  \"msAnimationIterationCount\",\n  \"msFlex\",\n  \"msZoom\",\n  \"msFlexPositive\",\n  \"msGridColumns\",\n  \"msGridRows\",\n  \"WebkitAnimationIterationCount\",\n  \"WebkitBoxFlex\",\n  \"WebKitBoxFlexGroup\",\n  \"WebkitBoxOrdinalGroup\",\n  \"WebkitColumnCount\",\n  \"WebkitColumns\",\n  \"WebkitFlex\",\n  \"WebkitFlexGrow\",\n  \"WebkitFlexPositive\",\n  \"WebkitFlexShrink\",\n  \"WebkitLineClamp\",\n];\n\n/**\n * Convert a `CSSProperties` style object into a inline CSS string.\n */\nexport function toInlineCSSString(styles: CSSProperties): string {\n  const entries = Object.entries(styles);\n  const inline = entries\n    .map(([key, value]): string | null => {\n      // Return an empty string if `value` is not acceptable\n      if (\n        value === null ||\n        typeof value === \"boolean\" ||\n        value === \"\" ||\n        typeof value === \"undefined\"\n      ) {\n        return \"\";\n      }\n\n      // Convert key from camelCase to kebab-case\n      let property = key.replace(/([A-Z])/g, \"-$1\").toLowerCase();\n\n      // Manage vendors prefixes\n      if (VENDORS_PREFIXES.test(property)) {\n        property = `-${property}`;\n      }\n\n      // Add `px` if needed for properties which aren't unitless\n      if (typeof value === \"number\" && !UNITLESS_PROPERTIES.includes(key)) {\n        return `${property}:${value}px;`;\n      }\n\n      return `${property}:${String(value).trim()};`;\n    })\n    .filter(Boolean)\n    .join(\"\");\n\n  return inline;\n}\n","import { assertNever, type MentionData } from \"@liveblocks/core\";\nimport { yXmlFragmentToProsemirrorJSON } from \"y-prosemirror\";\nimport * as Y from \"yjs\";\n\nimport { isMentionNodeAttributeId } from \"./lib/utils\";\n\nexport interface SerializedTiptapBaseNode {\n  type: string;\n  content?: Array<SerializedTiptapBaseNode>;\n}\n\nexport interface SerializedTiptapBaseMark {\n  type: string;\n  attrs: Record<string, string>;\n}\n\nexport interface SerializedTiptapBoldMark extends SerializedTiptapBaseMark {\n  type: \"bold\";\n}\n\nexport interface SerializedTiptapItalicMark extends SerializedTiptapBaseMark {\n  type: \"italic\";\n}\n\nexport interface SerializedTiptapStrikethroughMark extends SerializedTiptapBaseMark {\n  type: \"strike\";\n}\n\nexport interface SerializedTiptapCodeMark extends SerializedTiptapBaseMark {\n  type: \"code\";\n}\n\nexport interface SerializedTiptapCommentMark extends SerializedTiptapBaseMark {\n  type: \"liveblocksCommentMark\";\n  attrs: {\n    threadId: string;\n  };\n}\n\nexport type SerializedTiptapMark =\n  | SerializedTiptapBoldMark\n  | SerializedTiptapItalicMark\n  | SerializedTiptapStrikethroughMark\n  | SerializedTiptapCodeMark\n  | SerializedTiptapCommentMark;\n\nexport type SerializedTiptapMarkType = SerializedTiptapMark[\"type\"];\n\nexport interface SerializedTiptapTextNode extends SerializedTiptapBaseNode {\n  type: \"text\";\n  text: string;\n  marks?: Array<SerializedTiptapMark>;\n}\n\nexport interface SerializedTiptapMentionNode extends SerializedTiptapBaseNode {\n  type: \"liveblocksMention\";\n  attrs: {\n    id: string;\n    notificationId: string;\n  };\n}\n\nexport interface SerializedTiptapGroupMentionNode extends SerializedTiptapBaseNode {\n  type: \"liveblocksGroupMention\";\n  attrs: {\n    id: string;\n    notificationId: string;\n    userIds: string | undefined;\n  };\n}\n\nexport interface SerializedTiptapEmptyParagraphNode extends SerializedTiptapBaseNode {\n  type: \"paragraph\";\n  content?: undefined;\n}\n\n/**\n * Hard breaks are created by using keys like\n * `shift+enter` or `mod+enter`\n */\nexport interface SerializedTiptapHardBreakNode extends SerializedTiptapBaseNode {\n  type: \"hardBreak\";\n  content?: undefined;\n}\n\nexport interface SerializedTiptapParagraphNode extends SerializedTiptapBaseNode {\n  type: \"paragraph\";\n  content: Array<SerializedTiptapNode>;\n}\n\nexport type SerializedTiptapNode =\n  | SerializedTiptapParagraphNode\n  | SerializedTiptapEmptyParagraphNode\n  | SerializedTiptapHardBreakNode\n  | SerializedTiptapMentionNode\n  | SerializedTiptapGroupMentionNode\n  | SerializedTiptapTextNode;\n\nexport type SerializedTiptapRootNodeContent = Array<\n  Readonly<SerializedTiptapNode>\n>;\n\nexport interface SerializedTiptapRootNode extends Readonly<SerializedTiptapBaseNode> {\n  readonly type: \"doc\";\n  readonly content: SerializedTiptapRootNodeContent;\n}\n\n/**\n * Convert a document as binaries to\n * serialized tiptap state\n */\nexport function getSerializedTiptapState({\n  buffer,\n  key,\n}: {\n  buffer: ArrayBuffer;\n  key: string;\n}): SerializedTiptapRootNode {\n  const update = new Uint8Array(buffer);\n  // Construct a Y.js document from the binary update\n  const document = new Y.Doc();\n  Y.applyUpdate(document, update);\n\n  // Convert the Y.js document to a serializable tiptap state\n  const fragment = document.getXmlFragment(key);\n  const state = yXmlFragmentToProsemirrorJSON(fragment);\n\n  // Destroy the Y.js document after the conversion\n  document.destroy();\n\n  // Not ideal but pragmatic enough as the typing is based\n  // on real data we provide\n  return state as SerializedTiptapRootNode;\n}\n\nconst isSerializedEmptyParagraphNode = (\n  node: SerializedTiptapNode\n): node is SerializedTiptapEmptyParagraphNode => {\n  return node.type === \"paragraph\" && typeof node.content === \"undefined\";\n};\n\nconst isSerializedHardBreakNode = (\n  node: SerializedTiptapNode\n): node is SerializedTiptapHardBreakNode => {\n  return node.type === \"hardBreak\" && typeof node.content === \"undefined\";\n};\n\nconst isSerializedTextNode = (\n  node: SerializedTiptapNode\n): node is SerializedTiptapTextNode => {\n  return node.type === \"text\";\n};\n\nexport const isSerializedMentionNode = (\n  node: SerializedTiptapNode\n): node is SerializedTiptapMentionNode => {\n  return (\n    node.type === \"liveblocksMention\" &&\n    isMentionNodeAttributeId(node.attrs.notificationId)\n  );\n};\n\nexport const isSerializedGroupMentionNode = (\n  node: SerializedTiptapNode\n): node is SerializedTiptapGroupMentionNode => {\n  return (\n    node.type === \"liveblocksGroupMention\" &&\n    isMentionNodeAttributeId(node.attrs.notificationId)\n  );\n};\n\nconst isSerializedParagraphNode = (\n  node: SerializedTiptapNode\n): node is SerializedTiptapParagraphNode => {\n  return node.type === \"paragraph\" && typeof node.content !== \"undefined\";\n};\n\n/**\n * Internal type helper when flattening nodes.\n * It helps to better extract mention node with context by marking\n * start and ends of paragraph and by handling specific use cases such as\n * using twice the `enter` key which will create an empty paragraph\n * at the first `enter`:\n *\n *  \"\n *  Hey @charlie what's up?\n *  _enter_once_\n *  _enter_twice_\n *  \"\n */\ninterface FlattenedTiptapParagraphNodeMarker {\n  type: \"paragraph-marker\";\n  marker: \"start\" | \"end\";\n}\n\nconst isFlattenedTiptapParagraphNodeMarker = (\n  node: SerializedTiptapNode | FlattenedTiptapParagraphNodeMarker\n): node is FlattenedTiptapParagraphNodeMarker => {\n  return node.type === \"paragraph-marker\";\n};\n\n/** @internal */\ntype FlattenedSerializedTiptapNodes = Array<\n  SerializedTiptapNode | FlattenedTiptapParagraphNodeMarker\n>;\n\n/** @internal - export for testing only */\nexport const flattenTiptapTree = (\n  nodes: SerializedTiptapNode[]\n): FlattenedSerializedTiptapNodes => {\n  let flattenNodes: FlattenedSerializedTiptapNodes = [];\n\n  for (const node of nodes) {\n    if (\n      isSerializedEmptyParagraphNode(node) ||\n      isSerializedHardBreakNode(node) ||\n      isSerializedTextNode(node) ||\n      isSerializedMentionNode(node) ||\n      isSerializedGroupMentionNode(node)\n    ) {\n      flattenNodes = [...flattenNodes, node];\n    } else if (isSerializedParagraphNode(node)) {\n      flattenNodes = [\n        ...flattenNodes,\n        {\n          type: \"paragraph-marker\",\n          marker: \"start\",\n        },\n        ...flattenTiptapTree(node.content),\n        {\n          type: \"paragraph-marker\",\n          marker: \"end\",\n        },\n      ];\n    }\n  }\n\n  return flattenNodes;\n};\n\n/**\n * Tiptap Mention Node with context\n */\nexport type TiptapMentionNodeWithContext = {\n  before: SerializedTiptapNode[];\n  after: SerializedTiptapNode[];\n  mention: SerializedTiptapMentionNode | SerializedTiptapGroupMentionNode;\n};\n\n/**\n * Find a Tiptap mention\n * and returns it with contextual surrounding text\n */\nexport function findTiptapMentionNodeWithContext({\n  root,\n  textMentionId,\n}: {\n  root: SerializedTiptapRootNode;\n  textMentionId: string;\n}): TiptapMentionNodeWithContext | null {\n  const nodes = flattenTiptapTree(root.content);\n\n  // Find mention node\n  let mentionNodeIndex = -1;\n\n  for (let i = 0; i < nodes.length; i++) {\n    const node = nodes[i]!;\n\n    if (\n      !isFlattenedTiptapParagraphNodeMarker(node) &&\n      (isSerializedMentionNode(node) || isSerializedGroupMentionNode(node)) &&\n      node.attrs.notificationId === textMentionId\n    ) {\n      mentionNodeIndex = i;\n      break;\n    }\n  }\n\n  // No mention node found\n  if (mentionNodeIndex === -1) {\n    return null;\n  }\n\n  // Collect nodes before and after\n  const mentionNode = nodes[mentionNodeIndex] as\n    | SerializedTiptapMentionNode\n    | SerializedTiptapGroupMentionNode;\n\n  // Apply surrounding text guesses\n  // For now let's stay simple just stop at nearest line break or paragraph\n  const beforeNodes: SerializedTiptapNode[] = [];\n  const afterNodes: SerializedTiptapNode[] = [];\n\n  // Nodes before mention node\n  for (let i = mentionNodeIndex - 1; i >= 0; i--) {\n    const node = nodes[i]!;\n\n    // Stop if nodes are markers, hard breaks or empty paragraph\n    if (\n      isFlattenedTiptapParagraphNodeMarker(node) ||\n      isSerializedEmptyParagraphNode(node) ||\n      isSerializedHardBreakNode(node)\n    ) {\n      break;\n    }\n\n    beforeNodes.unshift(node);\n  }\n\n  // Nodes after mention node\n  for (let i = mentionNodeIndex + 1; i < nodes.length; i++) {\n    const node = nodes[i]!;\n\n    // Stop if nodes are markers, hard breaks or empty paragraph\n    if (\n      isFlattenedTiptapParagraphNodeMarker(node) ||\n      isSerializedEmptyParagraphNode(node) ||\n      isSerializedHardBreakNode(node)\n    ) {\n      break;\n    }\n\n    afterNodes.push(node);\n  }\n\n  return {\n    before: beforeNodes,\n    after: afterNodes,\n    mention: mentionNode,\n  };\n}\n\nfunction deserializeGroupUserIds(\n  userIds: string | undefined\n): string[] | undefined {\n  if (typeof userIds !== \"string\") {\n    return undefined;\n  }\n\n  try {\n    const parsedUserIds = JSON.parse(userIds) as string[];\n\n    if (Array.isArray(parsedUserIds)) {\n      return parsedUserIds;\n    }\n\n    return undefined;\n  } catch {\n    return undefined;\n  }\n}\n\nexport function getMentionDataFromTiptapNode(\n  node: SerializedTiptapMentionNode | SerializedTiptapGroupMentionNode\n): MentionData {\n  if (isSerializedMentionNode(node)) {\n    return {\n      kind: \"user\",\n      id: node.attrs.id,\n    };\n  } else if (isSerializedGroupMentionNode(node)) {\n    return {\n      kind: \"group\",\n      id: node.attrs.id,\n      userIds: deserializeGroupUserIds(node.attrs.userIds),\n    };\n  }\n\n  assertNever(node, \"Unknown mention kind\");\n}\n","/**\n * Liveblocks Text Editor\n *\n * Expose common types to transform nodes from different editors like `Lexical` or `TipTap`\n * and then convert them more easily as React or as html.\n */\n\nimport type {\n  Awaitable,\n  BaseUserMeta,\n  DGI,\n  Relax,\n  ResolveGroupsInfoArgs,\n  ResolveUsersArgs,\n} from \"@liveblocks/core\";\n\nimport type {\n  LexicalMentionNodeWithContext,\n  SerializedLexicalNode,\n  SerializedLexicalTextNode,\n} from \"./lexical-editor\";\nimport {\n  isSerializedGroupMentionNode as isSerializedLexicalGroupMentionNode,\n  isSerializedMentionNode as isSerializedLexicalMentionNode,\n} from \"./lexical-editor\";\nimport type {\n  SerializedTiptapMark,\n  SerializedTiptapMarkType,\n  SerializedTiptapNode,\n  SerializedTiptapTextNode,\n  TiptapMentionNodeWithContext,\n} from \"./tiptap-editor\";\nimport {\n  isSerializedGroupMentionNode as isSerializedTiptapGroupMentionNode,\n  isSerializedMentionNode as isSerializedTiptapMentionNode,\n} from \"./tiptap-editor\";\n\ntype LiveblocksTextEditorTextFormat = {\n  bold: boolean;\n  italic: boolean;\n  strikethrough: boolean;\n  code: boolean;\n};\n\nexport type LiveblocksTextEditorTextNode = {\n  type: \"text\";\n  text: string;\n} & LiveblocksTextEditorTextFormat;\n\nexport type LiveblocksTextEditorMentionNode = Relax<\n  LiveblocksTextEditorUserMentionNode | LiveblocksTextEditorGroupMentionNode\n>;\n\ntype LiveblocksTextEditorUserMentionNode = {\n  type: \"mention\";\n  kind: \"user\";\n  id: string;\n};\n\nexport type LiveblocksTextEditorGroupMentionNode = {\n  type: \"mention\";\n  kind: \"group\";\n  id: string;\n  userIds?: string[];\n};\n\n/**\n * -------------------------------------------------------------------------------------------------\n * `LiveblocksTextEditorNode` is common structure to represents text editor nodes coming from\n * like `Lexical`, `TipTap` or so.\n *\n * This (simple) structure is made to be scalable and to accommodate with other text editors we could potentially\n * want to support in the future.\n *\n * It allows to manipulate nodes more easily and converts them with ease either as React nodes or as an html safe string.\n * From a DX standpoint it provides to developers the same structure to use when using custom React components or inline css\n * to represents a text mention with its surrounding text.\n * -------------------------------------------------------------------------------------------------\n */\nexport type LiveblocksTextEditorNode =\n  | LiveblocksTextEditorTextNode\n  | LiveblocksTextEditorMentionNode;\n\nconst baseLiveblocksTextEditorTextFormat: LiveblocksTextEditorTextFormat = {\n  bold: false,\n  italic: false,\n  strikethrough: false,\n  code: false,\n};\n\n/**\n * -------------------------------------------------------------------------------------------------\n * Lexical use bitwise operators to represent text formatting:\n * → https://github.com/facebook/lexical/blob/e423c6888dbf2dbd0b5ef68f781efadda20d34f3/packages/lexical/src/LexicalConstants.ts#L39\n *\n * It allows to combine multiple flags into one single integer such as:\n * 00000001  (bold)\n * 00000010  (italic)\n * --------\n * 0000011  (bold + italic)\n *\n * For now we're copying only the bitwise flags we need to provide a consistent DX with\n * `ThreadNotificationEvent` comments:\n *  - BOLD\n *  - ITALIC\n *  - STRIKETHROUGH\n *  - CODE\n *\n * and `transformLexicalTextNodeFormatBitwiseInteger` transforms these flags\n * into a object of booleans `LiveblocksTextEditorTextFormat`:\n * ```ts\n * {\n *  bold: boolean;\n *  italic: boolean;\n *  strikethrough: boolean;\n *  code: boolean;\n * }\n * ```\n * -------------------------------------------------------------------------------------------------\n */\n\nconst IS_LEXICAL_BOLD = 1;\nconst IS_LEXICAL_ITALIC = 1 << 1;\nconst IS_LEXICAL_STRIKETHROUGH = 1 << 2;\nconst IS_LEXICAL_CODE = 1 << 4;\n\n/** @internal */\nconst transformLexicalTextNodeFormatBitwiseInteger = (\n  node: SerializedLexicalTextNode\n): LiveblocksTextEditorTextFormat => {\n  const attributes = node.attributes;\n\n  if (\"__format\" in attributes && typeof attributes.__format === \"number\") {\n    const format = attributes.__format;\n    return {\n      bold: (format & IS_LEXICAL_BOLD) !== 0,\n      italic: (format & IS_LEXICAL_ITALIC) !== 0,\n      strikethrough: (format & IS_LEXICAL_STRIKETHROUGH) !== 0,\n      code: (format & IS_LEXICAL_CODE) !== 0,\n    };\n  }\n\n  return baseLiveblocksTextEditorTextFormat;\n};\n\n/**\n * @internal\n *\n * Transform Lexical serialized nodes\n * as Liveblocks Text Editor nodes\n */\nconst transformLexicalMentionNodeWithContext = (\n  mentionNodeWithContext: LexicalMentionNodeWithContext\n): LiveblocksTextEditorNode[] => {\n  const textEditorNodes: LiveblocksTextEditorNode[] = [];\n  const { before, after, mention } = mentionNodeWithContext;\n\n  const transform = (nodes: SerializedLexicalNode[]) => {\n    for (const node of nodes) {\n      if (node.group === \"text\") {\n        const format = transformLexicalTextNodeFormatBitwiseInteger(node);\n        textEditorNodes.push({\n          type: \"text\",\n          text: node.text,\n          ...format,\n        });\n      } else if (\n        node.group === \"decorator\" &&\n        isSerializedLexicalMentionNode(node)\n      ) {\n        textEditorNodes.push({\n          type: \"mention\",\n          kind: \"user\",\n          id: node.attributes.__userId,\n        });\n      } else if (\n        node.group === \"decorator\" &&\n        isSerializedLexicalGroupMentionNode(node)\n      ) {\n        textEditorNodes.push({\n          type: \"mention\",\n          kind: \"group\",\n          id: node.attributes.__groupId,\n        });\n      }\n    }\n  };\n\n  transform(before);\n  textEditorNodes.push({\n    type: \"mention\",\n    kind: mention.type === \"lb-group-mention\" ? \"group\" : \"user\",\n    id:\n      mention.type === \"lb-group-mention\"\n        ? mention.attributes.__groupId\n        : mention.attributes.__userId,\n  });\n  transform(after);\n\n  return textEditorNodes;\n};\n\n/** @internal */\nconst hasTiptapSerializedTextNodeMark = (\n  marks: Array<SerializedTiptapMark>,\n  type: SerializedTiptapMarkType\n): boolean => marks.findIndex((mark) => mark.type === type) !== -1;\n\n/** @internal */\nconst transformTiptapTextNodeFormatMarks = (\n  node: SerializedTiptapTextNode\n): LiveblocksTextEditorTextFormat => {\n  if (!node.marks) {\n    return baseLiveblocksTextEditorTextFormat;\n  }\n\n  const marks = node.marks;\n  return {\n    bold: hasTiptapSerializedTextNodeMark(marks, \"bold\"),\n    italic: hasTiptapSerializedTextNodeMark(marks, \"italic\"),\n    strikethrough: hasTiptapSerializedTextNodeMark(marks, \"strike\"),\n    code: hasTiptapSerializedTextNodeMark(marks, \"code\"),\n  };\n};\n\n/**\n *\n * @internal\n *\n * Transform Tiptap serialized nodes\n * as Liveblocks Text Editor nodes\n */\nconst transformTiptapMentionNodeWithContext = (\n  mentionNodeWithContext: TiptapMentionNodeWithContext\n): LiveblocksTextEditorNode[] => {\n  const textEditorNodes: LiveblocksTextEditorNode[] = [];\n  const { before, after, mention } = mentionNodeWithContext;\n\n  const transform = (nodes: SerializedTiptapNode[]) => {\n    for (const node of nodes) {\n      if (node.type === \"text\") {\n        const format = transformTiptapTextNodeFormatMarks(node);\n        textEditorNodes.push({\n          type: \"text\",\n          text: node.text,\n          ...format,\n        });\n      } else if (isSerializedTiptapMentionNode(node)) {\n        textEditorNodes.push({\n          type: \"mention\",\n          kind: \"user\",\n          id: node.attrs.id,\n        });\n      } else if (isSerializedTiptapGroupMentionNode(node)) {\n        textEditorNodes.push({\n          type: \"mention\",\n          kind: \"group\",\n          id: node.attrs.id,\n        });\n      }\n    }\n  };\n\n  transform(before);\n  textEditorNodes.push({\n    type: \"mention\",\n    kind: mention.type === \"liveblocksGroupMention\" ? \"group\" : \"user\",\n    id: mention.attrs.id,\n  });\n  transform(after);\n\n  return textEditorNodes;\n};\n\ntype TransformableMentionNodeWithContext =\n  | {\n      editor: \"lexical\";\n      mention: LexicalMentionNodeWithContext;\n    }\n  | {\n      editor: \"tiptap\";\n      mention: TiptapMentionNodeWithContext;\n    };\n\n/**\n * @internal\n *\n * Transforms either Lexical or TipTap nodes into a common structure\n * of Liveblocks Text Editor nodes to ease conversion into\n * React Nodes or html safe strings\n */\nexport function transformAsLiveblocksTextEditorNodes(\n  transformableMention: TransformableMentionNodeWithContext\n): LiveblocksTextEditorNode[] {\n  switch (transformableMention.editor) {\n    case \"lexical\": {\n      return transformLexicalMentionNodeWithContext(\n        transformableMention.mention\n      );\n    }\n    case \"tiptap\": {\n      return transformTiptapMentionNodeWithContext(\n        transformableMention.mention\n      );\n    }\n  }\n}\n\n/**\n * @internal\n * Resolves mentions (users or groups) in Liveblocks Text Editor nodes.\n */\nexport const resolveMentionsInLiveblocksTextEditorNodes = async <\n  U extends BaseUserMeta,\n>(\n  nodes: LiveblocksTextEditorNode[],\n  resolveUsers?: (\n    args: ResolveUsersArgs\n  ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>,\n  resolveGroupsInfo?: (\n    args: ResolveGroupsInfoArgs\n  ) => Awaitable<(DGI | undefined)[] | undefined>\n): Promise<{\n  users: Map<string, U[\"info\"]>;\n  groups: Map<string, DGI>;\n}> => {\n  const resolvedUsers = new Map<string, U[\"info\"]>();\n  const resolvedGroupsInfo = new Map<string, DGI>();\n\n  if (!resolveUsers && !resolveGroupsInfo) {\n    return {\n      users: resolvedUsers,\n      groups: resolvedGroupsInfo,\n    };\n  }\n\n  const mentionedUserIds = new Set<string>();\n  const mentionedGroupIds = new Set<string>();\n\n  for (const node of nodes) {\n    if (node.type === \"mention\") {\n      if (node.kind === \"user\") {\n        mentionedUserIds.add(node.id);\n      } else if (node.kind === \"group\") {\n        mentionedGroupIds.add(node.id);\n      }\n    }\n  }\n\n  const userIds = Array.from(mentionedUserIds);\n  const groupIds = Array.from(mentionedGroupIds);\n\n  const [users, groups] = await Promise.all([\n    resolveUsers && userIds.length > 0 ? resolveUsers({ userIds }) : undefined,\n    resolveGroupsInfo && groupIds.length > 0\n      ? resolveGroupsInfo({ groupIds })\n      : undefined,\n  ]);\n\n  if (users) {\n    for (const [index, userId] of userIds.entries()) {\n      const user = users[index];\n      if (user) {\n        resolvedUsers.set(userId, user);\n      }\n    }\n  }\n\n  if (groups) {\n    for (const [index, groupId] of groupIds.entries()) {\n      const group = groups[index];\n      if (group) {\n        resolvedGroupsInfo.set(groupId, group);\n      }\n    }\n  }\n\n  return {\n    users: resolvedUsers,\n    groups: resolvedGroupsInfo,\n  };\n};\n","import type {\n  Awaitable,\n  BaseUserMeta,\n  DGI,\n  DU,\n  ResolveGroupsInfoArgs,\n  ResolveUsersArgs,\n} from \"@liveblocks/core\";\n\nimport {\n  type LiveblocksTextEditorMentionNode,\n  type LiveblocksTextEditorNode,\n  type LiveblocksTextEditorTextNode,\n  resolveMentionsInLiveblocksTextEditorNodes,\n} from \"./liveblocks-text-editor\";\n\nexport type TextMentionContentContainerElementArgs<T> = {\n  /**\n   * The blocks of the text mention content\n   */\n  children: T[];\n};\n\nexport type TextMentionContentMentionElementArgs<U extends BaseUserMeta = DU> =\n  {\n    /**\n     * The text mention node.\n     */\n    node: LiveblocksTextEditorMentionNode;\n\n    /**\n     * The mention's user info, if the mention is a user mention and the `resolvedUsers` option was provided.\n     */\n    user?: U[\"info\"];\n\n    /**\n     * The mention's group info, if the mention is a group mention and the `resolvedGroupsInfo` option was provided.\n     */\n    group?: DGI;\n  };\n\nexport type TextMentionContentTextElementArgs = {\n  /**\n   * The text element.\n   */\n  node: LiveblocksTextEditorTextNode;\n};\n\n/**\n * Protocol:\n * Text mention content elements to be converted to a custom format `T`\n */\nexport type ConvertTextMentionContentElements<\n  T,\n  U extends BaseUserMeta = DU,\n> = {\n  /**\n   * The container element used to display text mention content blocks\n   */\n  container: (args: TextMentionContentContainerElementArgs<T>) => T;\n  /**\n   * The mention element used to display the mention itself.\n   */\n  mention: (args: TextMentionContentMentionElementArgs<U>, index: number) => T;\n  /**\n   * The text element used to display the text surrounding the mention.\n   */\n  text: (args: TextMentionContentTextElementArgs, index: number) => T;\n};\n\nexport type ConvertTextMentionContentOptions<T, U extends BaseUserMeta = DU> = {\n  /**\n   * A function that returns user info from user IDs.\n   * You should return a list of user objects of the same size, in the same order.\n   */\n  resolveUsers?: (\n    args: ResolveUsersArgs\n  ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>;\n\n  /**\n   * A function that returns group info from group IDs.\n   * You should return a list of group info objects of the same size, in the same order.\n   */\n  resolveGroupsInfo?: (\n    args: ResolveGroupsInfoArgs\n  ) => Awaitable<(DGI | undefined)[] | undefined>;\n\n  /**\n   * The elements used to customize the resulting format `T`.\n   */\n  elements: ConvertTextMentionContentElements<T, U>;\n};\n\n/**\n * Convert a text mention content nodes to a custom format `T`.\n */\nexport async function convertTextMentionContent<T, U extends BaseUserMeta = DU>(\n  nodes: LiveblocksTextEditorNode[],\n  options: ConvertTextMentionContentOptions<T, U>\n): Promise<T> {\n  const { users: resolvedUsers, groups: resolvedGroupsInfo } =\n    await resolveMentionsInLiveblocksTextEditorNodes(\n      nodes,\n      options?.resolveUsers,\n      options?.resolveGroupsInfo\n    );\n\n  const blocks: T[] = nodes.map((node, index) => {\n    switch (node.type) {\n      case \"mention\": {\n        return options.elements.mention(\n          {\n            node,\n            user: node.kind === \"user\" ? resolvedUsers.get(node.id) : undefined,\n            group:\n              node.kind === \"group\"\n                ? resolvedGroupsInfo.get(node.id)\n                : undefined,\n          },\n          index\n        );\n      }\n      case \"text\": {\n        return options.elements.text({ node }, index);\n      }\n    }\n  });\n\n  return options.elements.container({ children: blocks });\n}\n","import type {\n  Awaitable,\n  BaseUserMeta,\n  CommentBodyLink,\n  CommentBodyMention,\n  CommentBodyText,\n  CommentData,\n  DGI,\n  DRI,\n  DU,\n  GroupData,\n  InboxNotificationData,\n  ResolveGroupsInfoArgs,\n  ResolveUsersArgs,\n} from \"@liveblocks/core\";\nimport {\n  generateUrl,\n  getMentionsFromCommentBody,\n  html,\n  htmlSafe,\n  MENTION_CHARACTER,\n} from \"@liveblocks/core\";\nimport type { Liveblocks, ThreadNotificationEvent } from \"@liveblocks/node\";\nimport type { ComponentType, ReactNode } from \"react\";\n\nimport type { ConvertCommentBodyElements } from \"./comment-body\";\nimport { convertCommentBody } from \"./comment-body\";\nimport type { CommentDataWithBody } from \"./comment-with-body\";\nimport { filterCommentsWithBody } from \"./comment-with-body\";\nimport {\n  createBatchGroupsInfoResolver,\n  createBatchUsersResolver,\n  getResolvedForId,\n} from \"./lib/batch-resolvers\";\nimport type { CSSProperties } from \"./lib/css-properties\";\nimport { toInlineCSSString } from \"./lib/css-properties\";\nimport type { ResolveRoomInfoArgs } from \"./lib/types\";\n\n/** @internal */\nexport const getUnreadComments = ({\n  comments,\n  inboxNotification,\n  notificationTriggerAt,\n  userId,\n}: {\n  comments: CommentData[];\n  inboxNotification: InboxNotificationData;\n  notificationTriggerAt: Date;\n  userId: string;\n}): CommentDataWithBody[] => {\n  // Let's get only not deleted comments with a body.\n  const commentsWithBody = filterCommentsWithBody(comments);\n  // Let's filter out comments written by the user that received the notification.\n  const notAuthoredComments = commentsWithBody.filter(\n    (c) => c.userId !== userId\n  );\n\n  const readAt = inboxNotification.readAt;\n  // This behavior is different from the `InboxNotificationThread` component\n  // because we in the front-end we want the always the last activity.\n  // In this case then we want to do a sequential reading of the activity.\n  // It allow us to determine much more precisely which comments was created between\n  // the moment the inbox notification is created and the moment the webhook event is received.\n  return notAuthoredComments.filter((c) => {\n    // If the inbox notification is read, because of the 1:1 relationship between an\n    // and inbox notification and a thread, we must not include comments created\n    // strictly after the `readAt` date. It means the inbox notification can be updated\n    // in the db after the `readAt` date.\n    if (readAt !== null) {\n      return (\n        c.createdAt > readAt &&\n        c.createdAt >= notificationTriggerAt &&\n        c.createdAt <= inboxNotification.notifiedAt\n      );\n    }\n    // Otherwise we can include all comments created between the inbox notification\n    // creation date (`triggeredAt`) and the inbox notification `notifiedAt` date.\n    return (\n      c.createdAt >= notificationTriggerAt &&\n      c.createdAt <= inboxNotification.notifiedAt\n    );\n  });\n};\n\n/** @internal */\nasync function getAllUserGroups(\n  client: Liveblocks,\n  userId: string\n): Promise<Map<string, GroupData>> {\n  const groups = new Map<string, GroupData>();\n  let cursor: string | undefined = undefined;\n\n  while (true) {\n    const { nextCursor, data } = await client.getUserGroups({\n      userId,\n      startingAfter: cursor,\n    });\n\n    for (const group of data) {\n      groups.set(group.id, group);\n    }\n\n    if (!nextCursor) {\n      break;\n    }\n\n    cursor = nextCursor;\n  }\n\n  return groups;\n}\n\n/** @internal */\nexport const getLastUnreadCommentWithMention = ({\n  comments,\n  groups,\n  mentionedUserId,\n}: {\n  comments: CommentDataWithBody[];\n  mentionedUserId: string;\n  groups: Map<string, GroupData>;\n}): CommentDataWithBody | null => {\n  if (!comments.length) {\n    return null;\n  }\n\n  for (let i = comments.length - 1; i >= 0; i--) {\n    const comment = comments[i]!;\n\n    if (comment.userId === mentionedUserId) {\n      continue;\n    }\n\n    const mentions = getMentionsFromCommentBody(comment.body);\n\n    for (const mention of mentions) {\n      // 1. The comment contains a user mention for the current user.\n      if (mention.kind === \"user\" && mention.id === mentionedUserId) {\n        return comment;\n      }\n\n      // 2. The comment contains a group mention including the current user in its `userIds` array.\n      if (\n        mention.kind === \"group\" &&\n        mention.userIds?.includes(mentionedUserId)\n      ) {\n        return comment;\n      }\n\n      // 3. The comment contains a group mention including the current user in its managed group members.\n      if (mention.kind === \"group\" && mention.userIds === undefined) {\n        // Synchronously look up the group data for this group ID.\n        const group = groups.get(mention.id);\n\n        if (group?.members.some((member) => member.id === mentionedUserId)) {\n          return comment;\n        }\n      }\n    }\n  }\n\n  return null;\n};\n\nexport type ThreadNotificationData =\n  | { type: \"unreadMention\"; comment: CommentDataWithBody }\n  | { type: \"unreadReplies\"; comments: CommentDataWithBody[] };\n\n/** @internal */\nexport const extractThreadNotificationData = async ({\n  client,\n  event,\n}: {\n  client: Liveblocks;\n  event: ThreadNotificationEvent;\n}): Promise<ThreadNotificationData | null> => {\n  const { threadId, roomId, userId, inboxNotificationId } = event.data;\n  const [thread, inboxNotification] = await Promise.all([\n    client.getThread({ roomId, threadId }),\n    client.getInboxNotification({ inboxNotificationId, userId }),\n  ]);\n\n  const notificationTriggerAt = new Date(event.data.triggeredAt);\n  const unreadComments = getUnreadComments({\n    comments: thread.comments,\n    inboxNotification,\n    userId,\n    notificationTriggerAt,\n  });\n\n  if (unreadComments.length <= 0) {\n    return null;\n  }\n\n  const userGroups = await getAllUserGroups(client, userId);\n\n  const lastUnreadCommentWithMention = getLastUnreadCommentWithMention({\n    comments: unreadComments,\n    groups: userGroups,\n    mentionedUserId: userId,\n  });\n\n  if (lastUnreadCommentWithMention !== null) {\n    return { type: \"unreadMention\", comment: lastUnreadCommentWithMention };\n  }\n\n  return {\n    type: \"unreadReplies\",\n    comments: unreadComments,\n  };\n};\n\n/**\n * @internal\n * Set the comment ID as the URL hash.\n */\nfunction generateCommentUrl({\n  roomUrl,\n  commentId,\n}: {\n  roomUrl: string | undefined;\n  commentId: string;\n}): string | undefined {\n  if (!roomUrl) {\n    return;\n  }\n\n  return generateUrl(roomUrl, undefined, commentId);\n}\n\nexport type CommentEmailData<BodyType, U extends BaseUserMeta = DU> = {\n  id: string;\n  threadId: string;\n  roomId: string;\n  createdAt: Date;\n  url?: string;\n  author: U;\n  body: BodyType;\n};\n\nexport type ThreadNotificationEmailData<\n  BodyType,\n  U extends BaseUserMeta = DU,\n  C extends CommentEmailData<BodyType, U> = CommentEmailData<BodyType, U>,\n> = (\n  | {\n      type: \"unreadReplies\";\n      comments: C[];\n    }\n  | {\n      type: \"unreadMention\";\n      comment: C;\n    }\n) & { roomInfo: DRI };\n\nexport type CommentEmailAsHtmlData<U extends BaseUserMeta = DU> =\n  CommentEmailData<string, U>;\n\nexport type CommentEmailAsReactData<U extends BaseUserMeta = DU> =\n  CommentEmailData<ReactNode, U>;\n\ntype PrepareThreadNotificationEmailOptions<U extends BaseUserMeta = DU> = {\n  /**\n   * A function that returns room info from room IDs.\n   */\n  resolveRoomInfo?: (args: ResolveRoomInfoArgs) => Awaitable<DRI | undefined>;\n\n  /**\n   * A function that returns user info from user IDs.\n   * You should return a list of user objects of the same size, in the same order.\n   */\n  resolveUsers?: (\n    args: ResolveUsersArgs\n  ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>;\n\n  /**\n   * A function that returns group info from group IDs.\n   * You should return a list of group info objects of the same size, in the same order.\n   */\n  resolveGroupsInfo?: (\n    args: ResolveGroupsInfoArgs\n  ) => Awaitable<(DGI | undefined)[] | undefined>;\n};\n\n/**\n * @internal\n * exported for testing purposes.\n */\nexport async function prepareThreadNotificationEmail<\n  BodyType,\n  U extends BaseUserMeta = DU,\n>(\n  client: Liveblocks,\n  event: ThreadNotificationEvent,\n  options: PrepareThreadNotificationEmailOptions<U>,\n  elements: ConvertCommentBodyElements<BodyType, U>,\n  callerName: string\n): Promise<ThreadNotificationEmailData<BodyType, U> | null> {\n  const data = await extractThreadNotificationData({ client, event });\n  if (data === null) {\n    return null;\n  }\n\n  const roomInfo = options.resolveRoomInfo\n    ? await options.resolveRoomInfo({ roomId: event.data.roomId })\n    : undefined;\n\n  const resolvedRoomInfo: DRI = {\n    ...roomInfo,\n    name: roomInfo?.name ?? event.data.roomId,\n  };\n\n  const batchUsersResolver = createBatchUsersResolver<U>({\n    resolveUsers: options.resolveUsers,\n    callerName,\n  });\n  const batchGroupsInfoResolver = createBatchGroupsInfoResolver({\n    resolveGroupsInfo: options.resolveGroupsInfo,\n    callerName,\n  });\n\n  switch (data.type) {\n    case \"unreadMention\": {\n      const { comment } = data;\n\n      const authorsIds = [comment.userId];\n      const authorsInfoPromise = batchUsersResolver.get(authorsIds);\n      const commentBodyPromise = convertCommentBody<BodyType, U>(comment.body, {\n        resolveUsers: ({ userIds }) => batchUsersResolver.get(userIds),\n        resolveGroupsInfo: ({ groupIds }) =>\n          batchGroupsInfoResolver.get(groupIds),\n        elements,\n      });\n\n      await batchUsersResolver.resolve();\n      await batchGroupsInfoResolver.resolve();\n\n      const [authorsInfo, commentBody] = await Promise.all([\n        authorsInfoPromise,\n        commentBodyPromise,\n      ]);\n\n      const authorInfo = getResolvedForId(\n        comment.userId,\n        authorsIds,\n        authorsInfo\n      );\n      const url = roomInfo?.url\n        ? generateCommentUrl({\n            roomUrl: roomInfo?.url,\n            commentId: comment.id,\n          })\n        : undefined;\n\n      return {\n        type: \"unreadMention\",\n        comment: {\n          id: comment.id,\n          threadId: comment.threadId,\n          roomId: comment.roomId,\n          author: {\n            id: comment.userId,\n            info: authorInfo ?? { name: comment.userId },\n          } as U,\n          createdAt: comment.createdAt,\n          url,\n          body: commentBody as BodyType,\n        },\n        roomInfo: resolvedRoomInfo,\n      };\n    }\n    case \"unreadReplies\": {\n      const { comments } = data;\n\n      const authorsIds = comments.map((c) => c.userId);\n      const authorsInfoPromise = batchUsersResolver.get(authorsIds);\n\n      const commentBodiesPromises = comments.map((c) =>\n        convertCommentBody<BodyType, U>(c.body, {\n          resolveUsers: ({ userIds }) => batchUsersResolver.get(userIds),\n          resolveGroupsInfo: ({ groupIds }) =>\n            batchGroupsInfoResolver.get(groupIds),\n          elements,\n        })\n      );\n\n      await batchUsersResolver.resolve();\n      await batchGroupsInfoResolver.resolve();\n\n      const [authorsInfo, ...commentBodies] = await Promise.all([\n        authorsInfoPromise,\n        ...commentBodiesPromises,\n      ]);\n\n      return {\n        type: \"unreadReplies\",\n        comments: comments.map((comment, index) => {\n          const authorInfo = getResolvedForId(\n            comment.userId,\n            authorsIds,\n            authorsInfo\n          );\n          const commentBody = commentBodies[index] as BodyType;\n\n          const url = generateCommentUrl({\n            roomUrl: roomInfo?.url,\n            commentId: comment.id,\n          });\n\n          return {\n            id: comment.id,\n            threadId: comment.threadId,\n            roomId: comment.roomId,\n            author: {\n              id: comment.userId,\n              info: authorInfo ?? { name: comment.userId },\n            } as U,\n            createdAt: comment.createdAt,\n            url,\n            body: commentBody,\n          };\n        }),\n        roomInfo: resolvedRoomInfo,\n      };\n    }\n  }\n}\n\n/**\n * The styles used to customize the html elements in the resulting html safe string.\n * Each styles has priority over the base styles inherited.\n */\nexport type ConvertCommentBodyAsHtmlStyles = {\n  /**\n   * The default inline CSS styles used to display paragraphs.\n   */\n  paragraph: CSSProperties;\n  /**\n   * The default inline CSS styles used to display text `<strong />` elements.\n   */\n  strong: CSSProperties;\n  /**\n   * The default inline CSS styles used to display text `<code />` elements.\n   */\n  code: CSSProperties;\n  /**\n   * The default inline CSS styles used to display links.\n   */\n  mention: CSSProperties;\n  /**\n   * The default inline CSS styles used to display mentions.\n   */\n  link: CSSProperties;\n};\n\nconst baseStyles: ConvertCommentBodyAsHtmlStyles = {\n  paragraph: {\n    fontSize: \"14px\",\n  },\n  strong: {\n    fontWeight: 500,\n  },\n  code: {\n    fontFamily:\n      'ui-monospace, Menlo, Monaco, \"Cascadia Mono\", \"Segoe UI Mono\", \"Roboto Mono\", \"Oxygen Mono\", \"Ubuntu Mono\", \"Source Code Pro\", \"Fira Mono\", \"Droid Sans Mono\", \"Consolas\", \"Courier New\", monospace',\n    backgroundColor: \"rgba(0,0,0,0.05)\",\n    border: \"solid 1px rgba(0,0,0,0.1)\",\n    borderRadius: \"4px\",\n  },\n  mention: {\n    color: \"blue\",\n  },\n  link: {\n    textDecoration: \"underline\",\n  },\n};\n\nexport type PrepareThreadNotificationEmailAsHtmlOptions<\n  U extends BaseUserMeta = DU,\n> = PrepareThreadNotificationEmailOptions<U> & {\n  /**\n   * The styles used to customize the html elements in the resulting html safe string inside a comment body.\n   * Each styles has priority over the base styles inherited.\n   */\n  styles?: Partial<ConvertCommentBodyAsHtmlStyles>;\n};\n\nexport type ThreadNotificationEmailDataAsHtml<U extends BaseUserMeta = DU> =\n  ThreadNotificationEmailData<string, U, CommentEmailAsHtmlData<U>>;\n\n/**\n * Prepares data from a `ThreadNotificationEvent` and convert comment bodies as an html safe string.\n *\n * @param client The `Liveblocks` node client\n * @param event The `ThreadNotificationEvent` received in the webhook handler\n * @param options The optional options to provide to resolve users, resolve room info\n * and customize comment bodies html elements styles with inline CSS.\n *\n * It returns a `ThreadNotificationEmailDataAsHtml` or `null` if there are no unread comments (mention or replies).\n *\n * @example\n * import { Liveblocks} from \"@liveblocks/node\"\n * import { prepareThreadNotificationEmailAsHtml } from \"@liveblocks/emails\"\n *\n * const liveblocks = new Liveblocks({ secret: \"sk_...\" })\n * const emailData = prepareThreadNotificationEmailAsHtml(\n *  liveblocks,\n *  event,\n *  {\n *    resolveUsers,\n *    resolveRoomInfo,\n *    styles,\n *  }\n * )\n *\n */\nexport async function prepareThreadNotificationEmailAsHtml(\n  client: Liveblocks,\n  event: ThreadNotificationEvent,\n  options: PrepareThreadNotificationEmailAsHtmlOptions<BaseUserMeta> = {}\n): Promise<ThreadNotificationEmailDataAsHtml | null> {\n  const styles = { ...baseStyles, ...options?.styles };\n  const data = await prepareThreadNotificationEmail<string, BaseUserMeta>(\n    client,\n    event,\n    {\n      resolveUsers: options.resolveUsers,\n      resolveGroupsInfo: options.resolveGroupsInfo,\n      resolveRoomInfo: options.resolveRoomInfo,\n    },\n    {\n      container: ({ children }) => children.join(\"\\n\"),\n      paragraph: ({ children }) => {\n        const unsafe = children.join(\"\");\n        // prettier-ignore\n        return unsafe ? html`<p style=\"${toInlineCSSString(styles.paragraph)}\">${htmlSafe(unsafe)}</p>` : unsafe;\n      },\n      text: ({ element }) => {\n        // Note: construction following the schema 👇\n        // <code><s><em><strong>{element.text}</strong></s></em></code>\n        let children = element.text;\n\n        if (!children) {\n          return html`${children}`;\n        }\n\n        if (element.bold) {\n          // prettier-ignore\n          children = html`<strong style=\"${toInlineCSSString(styles.strong)}\">${children}</strong>`;\n        }\n\n        if (element.italic) {\n          // prettier-ignore\n          children = html`<em>${children}</em>`;\n        }\n\n        if (element.strikethrough) {\n          // prettier-ignore\n          children = html`<s>${children}</s>`;\n        }\n\n        if (element.code) {\n          // prettier-ignore\n          children = html`<code style=\"${toInlineCSSString(styles.code)}\">${children}</code>`;\n        }\n\n        return html`${children}`;\n      },\n      link: ({ element, href }) => {\n        // prettier-ignore\n        return html`<a href=\"${href}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"${toInlineCSSString(styles.link)}\">${element.text ? html`${element.text}` : element.url}</a>`;\n      },\n      mention: ({ element, user, group }) => {\n        // prettier-ignore\n        return html`<span data-mention style=\"${toInlineCSSString(styles.mention)}\">${MENTION_CHARACTER}${user?.name ? html`${user?.name}` : group?.name ? html`${group?.name}` : element.id}</span>`;\n      },\n    },\n    \"prepareThreadNotificationEmailAsHtml\"\n  );\n\n  if (data === null) {\n    return null;\n  }\n\n  return data;\n}\n\nexport type CommentBodyContainerComponentProps = {\n  /**\n   * The blocks of the comment body\n   */\n  children: ReactNode;\n};\n\nexport type CommentBodyParagraphComponentProps = {\n  /**\n   * The text content of the paragraph.\n   */\n  children: ReactNode;\n};\n\nexport type CommentBodyTextComponentProps = {\n  /**\n   * The text element.\n   */\n  element: CommentBodyText;\n};\n\nexport type CommentBodyLinkComponentProps = {\n  /**\n   * The link element.\n   */\n  element: CommentBodyLink;\n\n  /**\n   * The absolute URL of the link.\n   */\n  href: string;\n};\n\nexport type CommentBodyMentionComponentProps<U extends BaseUserMeta = DU> = {\n  /**\n   * The mention element.\n   */\n  element: CommentBodyMention;\n\n  /**\n   * The mention's user info, if the mention is a user mention and the `resolvedUsers` option was provided.\n   */\n  user?: U[\"info\"];\n\n  /**\n   * The mention's group info, if the mention is a group mention and the `resolvedGroupsInfo` option was provided.\n   */\n  group?: DGI;\n};\n\nexport type ConvertCommentBodyAsReactComponents<U extends BaseUserMeta = DU> = {\n  /**\n   *\n   * The component used to act as a container to wrap comment body blocks,\n   */\n  Container: ComponentType<CommentBodyContainerComponentProps>;\n  /**\n   * The component used to display paragraphs.\n   */\n  Paragraph: ComponentType<CommentBodyParagraphComponentProps>;\n\n  /**\n   * The component used to display text elements.\n   */\n  Text: ComponentType<CommentBodyTextComponentProps>;\n\n  /**\n   * The component used to display links.\n   */\n  Link: ComponentType<CommentBodyLinkComponentProps>;\n\n  /**\n   * The component used to display mentions.\n   */\n  Mention: ComponentType<CommentBodyMentionComponentProps<U>>;\n};\n\nconst baseComponents: ConvertCommentBodyAsReactComponents<BaseUserMeta> = {\n  Container: ({ children }) => <div>{children}</div>,\n  Paragraph: ({ children }) => <p>{children}</p>,\n  Text: ({ element }) => {\n    // Note: construction following the schema 👇\n    // <code><s><em><strong>{element.text}</strong></s></em></code>\n    let children: ReactNode = element.text;\n\n    if (element.bold) {\n      children = <strong>{children}</strong>;\n    }\n\n    if (element.italic) {\n      children = <em>{children}</em>;\n    }\n\n    if (element.strikethrough) {\n      children = <s>{children}</s>;\n    }\n\n    if (element.code) {\n      children = <code>{children}</code>;\n    }\n\n    return <span>{children}</span>;\n  },\n  Link: ({ element, href }) => (\n    <a href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n      {element.text ?? element.url}\n    </a>\n  ),\n  Mention: ({ element, user, group }) => (\n    <span data-mention>\n      {MENTION_CHARACTER}\n      {user?.name ?? group?.name ?? element.id}\n    </span>\n  ),\n};\n\nexport type PrepareThreadNotificationEmailAsReactOptions<\n  U extends BaseUserMeta = DU,\n> = PrepareThreadNotificationEmailOptions<U> & {\n  /**\n   * The components used to customize the resulting React nodes inside a comment body.\n   * Each components has priority over the base components inherited internally defined.\n   */\n  components?: Partial<ConvertCommentBodyAsReactComponents<U>>;\n};\n\nexport type ThreadNotificationEmailDataAsReact<U extends BaseUserMeta = DU> =\n  ThreadNotificationEmailData<ReactNode, U, CommentEmailAsReactData<U>>;\n\n/**\n * Prepares data from a `ThreadNotificationEvent` and convert comment bodies as React nodes.\n *\n * @param client The `Liveblocks` node client\n * @param event The `ThreadNotificationEvent` received in the webhook handler\n * @param options The optional options to provide to resolve users, resolve room info and customize comment bodies React components.\n *\n * It returns a `ThreadNotificationEmailDataAsReact` or `null` if there are no unread comments (mention or replies).\n *\n * @example\n * import { Liveblocks} from \"@liveblocks/node\"\n * import { prepareThreadNotificationEmailAsReact } from \"@liveblocks/emails\"\n *\n * const liveblocks = new Liveblocks({ secret: \"sk_...\" })\n * const emailData = prepareThreadNotificationEmailAsReact(\n *  liveblocks,\n *  event,\n *  {\n *    resolveUsers,\n *    resolveRoomInfo,\n *    components,\n *  }\n * )\n *\n */\nexport async function prepareThreadNotificationEmailAsReact(\n  client: Liveblocks,\n  event: ThreadNotificationEvent,\n  options: PrepareThreadNotificationEmailAsReactOptions<BaseUserMeta> = {}\n): Promise<ThreadNotificationEmailDataAsReact | null> {\n  const Components = { ...baseComponents, ...options?.components };\n  const data = await prepareThreadNotificationEmail<ReactNode, BaseUserMeta>(\n    client,\n    event,\n    {\n      resolveUsers: options.resolveUsers,\n      resolveGroupsInfo: options.resolveGroupsInfo,\n      resolveRoomInfo: options.resolveRoomInfo,\n    },\n    {\n      container: ({ children }) => (\n        <Components.Container key=\"lb-comment-body-container\">\n          {children}\n        </Components.Container>\n      ),\n      paragraph: ({ children }, index) => (\n        <Components.Paragraph key={`lb-comment-body-paragraph-${index}`}>\n          {children}\n        </Components.Paragraph>\n      ),\n      text: ({ element }, index) => (\n        <Components.Text\n          key={`lb-comment-body-text-${index}`}\n          element={element}\n        />\n      ),\n      link: ({ element, href }, index) => (\n        <Components.Link\n          key={`lb-comment-body-link-${index}`}\n          element={element}\n          href={href}\n        />\n      ),\n      mention: ({ element, user, group }, index) =>\n        element.id ? (\n          <Components.Mention\n            key={`lb-comment-body-mention-${index}`}\n            element={element}\n            user={user}\n            group={group}\n          />\n        ) : null,\n    },\n    \"prepareThreadNotificationEmailAsReact\"\n  );\n\n  if (data === null) {\n    return null;\n  }\n\n  return data;\n}\n","import type {\n  Awaitable,\n  BaseUserMeta,\n  CommentBody,\n  CommentBodyLink,\n  CommentBodyMention,\n  CommentBodyParagraph,\n  CommentBodyText,\n  DGI,\n  DU,\n  ResolveGroupsInfoArgs,\n  ResolveUsersArgs,\n} from \"@liveblocks/core\";\nimport {\n  isCommentBodyLink,\n  isCommentBodyMention,\n  isCommentBodyText,\n  resolveMentionsInCommentBody,\n  sanitizeUrl,\n} from \"@liveblocks/core\";\n\nimport { exists } from \"./lib/utils\";\n\nexport type CommentBodyContainerElementArgs<T> = {\n  /**\n   * The blocks of the comment body\n   */\n  children: T[];\n};\n\nexport type CommentBodyParagraphElementArgs<T> = {\n  /**\n   * The paragraph element.\n   */\n  element: CommentBodyParagraph;\n  /**\n   * The text content of the paragraph.\n   */\n  children: T[];\n};\n\nexport type CommentBodyTextElementArgs = {\n  /**\n   * The text element.\n   */\n  element: CommentBodyText;\n};\n\nexport type CommentBodyLinkElementArgs = {\n  /**\n   * The link element.\n   */\n  element: CommentBodyLink;\n\n  /**\n   * The absolute URL of the link.\n   */\n  href: string;\n};\n\nexport type CommentBodyMentionElementArgs<U extends BaseUserMeta = DU> = {\n  /**\n   * The mention element.\n   */\n  element: CommentBodyMention;\n\n  /**\n   * The mention's user info, if the mention is a user mention and the `resolveUsers` option was provided.\n   */\n  user?: U[\"info\"];\n\n  /**\n   * The mention's group info, if the mention is a group mention and the `resolveGroupsInfo` option was provided.\n   */\n  group?: DGI;\n};\n\n/**\n * Protocol:\n * Comment body elements to be converted to a custom format `T`\n */\nexport type ConvertCommentBodyElements<T, U extends BaseUserMeta = DU> = {\n  /**\n   * The container element used to display comment body blocks.\n   */\n  container: (args: CommentBodyContainerElementArgs<T>) => T;\n  /**\n   * The paragraph element used to display paragraphs.\n   */\n  paragraph: (args: CommentBodyParagraphElementArgs<T>, index: number) => T;\n  /**\n   * The text element used to display text elements.\n   */\n  text: (args: CommentBodyTextElementArgs, index: number) => T;\n  /**\n   * The link element used to display links.\n   */\n  link: (args: CommentBodyLinkElementArgs, index: number) => T;\n  /**\n   * The mention element used to display mentions.\n   */\n  mention: (args: CommentBodyMentionElementArgs<U>, index: number) => T;\n};\n\nexport type ConvertCommentBodyOptions<T, U extends BaseUserMeta = DU> = {\n  /**\n   * A function that returns user info from user IDs.\n   * You should return a list of user objects of the same size, in the same order.\n   */\n  resolveUsers?: (\n    args: ResolveUsersArgs\n  ) => Awaitable<(U[\"info\"] | undefined)[] | undefined>;\n\n  /**\n   * A function that returns group info from group IDs.\n   * You should return a list of group info objects of the same size, in the same order.\n   */\n  resolveGroupsInfo?: (\n    args: ResolveGroupsInfoArgs\n  ) => Awaitable<(DGI | undefined)[] | undefined>;\n\n  /**\n   * The elements used to customize the resulting format `T`.\n   */\n  elements: ConvertCommentBodyElements<T, U>;\n};\n\n/**\n * Convert a `CommentBody` into a custom format `T`\n */\nexport async function convertCommentBody<T, U extends BaseUserMeta = DU>(\n  body: CommentBody,\n  options: ConvertCommentBodyOptions<T, U>\n): Promise<T> {\n  const { users: resolvedUsers, groups: resolvedGroupsInfo } =\n    await resolveMentionsInCommentBody(\n      body,\n      options?.resolveUsers,\n      options?.resolveGroupsInfo\n    );\n\n  const blocks: T[] = body.content\n    .map((block, index) => {\n      switch (block.type) {\n        case \"paragraph\": {\n          const children: T[] = block.children\n            .map((inline, inlineIndex) => {\n              if (isCommentBodyMention(inline)) {\n                return options.elements.mention(\n                  {\n                    element: inline,\n                    user:\n                      inline.kind === \"user\"\n                        ? resolvedUsers.get(inline.id)\n                        : undefined,\n                    group:\n                      inline.kind === \"group\"\n                        ? resolvedGroupsInfo.get(inline.id)\n                        : undefined,\n                  },\n                  inlineIndex\n                );\n              }\n\n              if (isCommentBodyLink(inline)) {\n                const href = sanitizeUrl(inline.url);\n\n                // If the URL is invalid, its text/URL are used as plain text.\n                if (href === null) {\n                  return options.elements.text(\n                    {\n                      element: { text: inline.text ?? inline.url },\n                    },\n                    inlineIndex\n                  );\n                }\n\n                return options.elements.link(\n                  {\n                    element: inline,\n                    href,\n                  },\n                  inlineIndex\n                );\n              }\n\n              if (isCommentBodyText(inline)) {\n                return options.elements.text({ element: inline }, inlineIndex);\n              }\n\n              return null;\n            })\n            .filter(exists);\n\n          return options.elements.paragraph(\n            { element: block, children },\n            index\n          );\n        }\n        default:\n          console.warn(\n            `Unsupported comment body block type: \"${JSON.stringify(block.type)}\"`\n          );\n          return null;\n      }\n    })\n    .filter(exists);\n\n  return options.elements.container({ children: blocks });\n}\n","import type { CommentBody, CommentData } from \"@liveblocks/core\";\n\nexport type CommentDataWithBody = Omit<CommentData, \"body\" | \"deletedAt\"> & {\n  body: CommentBody;\n  deletedAt?: never;\n};\n\nconst isCommentDataWithBody = (\n  comment: CommentData\n): comment is CommentDataWithBody => {\n  return comment.body !== undefined && comment.deletedAt === undefined;\n};\n\nexport function filterCommentsWithBody(\n  comments: CommentData[]\n): CommentDataWithBody[] {\n  const commentsWithBody: CommentDataWithBody[] = [];\n  for (const comment of comments) {\n    if (isCommentDataWithBody(comment)) {\n      commentsWithBody.push(comment);\n    }\n  }\n  return commentsWithBody;\n}\n"]}