@liveblocks/emails 3.18.3-test2 → 3.18.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"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"]}
|
|
1
|
+
{"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"]}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liveblocks/emails",
|
|
3
|
-
"version": "3.18.3
|
|
3
|
+
"version": "3.18.3",
|
|
4
4
|
"description": "A set of functions and utilities to make sending emails based on Liveblocks notification events easy. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Liveblocks Inc.",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@liveblocks/core": "3.18.3
|
|
29
|
-
"@liveblocks/node": "3.18.3
|
|
28
|
+
"@liveblocks/core": "3.18.3",
|
|
29
|
+
"@liveblocks/node": "3.18.3"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"react": "^18 || ^19 || ^19.0.0-rc",
|