@liveblocks/react-ui 3.4.0-alpha1 → 3.4.1

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.
Files changed (32) hide show
  1. package/dist/components/AiChat.cjs +1 -4
  2. package/dist/components/AiChat.cjs.map +1 -1
  3. package/dist/components/AiChat.js +1 -4
  4. package/dist/components/AiChat.js.map +1 -1
  5. package/dist/components/Comment.cjs +5 -2
  6. package/dist/components/Comment.cjs.map +1 -1
  7. package/dist/components/Comment.js +5 -2
  8. package/dist/components/Comment.js.map +1 -1
  9. package/dist/components/Composer.cjs +5 -2
  10. package/dist/components/Composer.cjs.map +1 -1
  11. package/dist/components/Composer.js +5 -2
  12. package/dist/components/Composer.js.map +1 -1
  13. package/dist/components/internal/AiChatAssistantMessage.cjs +8 -16
  14. package/dist/components/internal/AiChatAssistantMessage.cjs.map +1 -1
  15. package/dist/components/internal/AiChatAssistantMessage.js +8 -16
  16. package/dist/components/internal/AiChatAssistantMessage.js.map +1 -1
  17. package/dist/primitives/AiMessage/index.cjs +2 -3
  18. package/dist/primitives/AiMessage/index.cjs.map +1 -1
  19. package/dist/primitives/AiMessage/index.js +2 -3
  20. package/dist/primitives/AiMessage/index.js.map +1 -1
  21. package/dist/primitives/AiMessage/tool-invocation.cjs +3 -4
  22. package/dist/primitives/AiMessage/tool-invocation.cjs.map +1 -1
  23. package/dist/primitives/AiMessage/tool-invocation.js +3 -4
  24. package/dist/primitives/AiMessage/tool-invocation.js.map +1 -1
  25. package/dist/version.cjs +1 -1
  26. package/dist/version.cjs.map +1 -1
  27. package/dist/version.js +1 -1
  28. package/dist/version.js.map +1 -1
  29. package/package.json +4 -4
  30. package/src/styles/index.css +4 -0
  31. package/styles.css +1 -1
  32. package/styles.css.map +1 -1
@@ -24,7 +24,6 @@ const defaultComponents = {
24
24
  const AiChatMessages = react.forwardRef(
25
25
  ({
26
26
  messages,
27
- copilotId,
28
27
  overrides,
29
28
  components,
30
29
  lastSentMessageId,
@@ -162,8 +161,7 @@ const AiChatMessages = react.forwardRef(
162
161
  return /* @__PURE__ */ jsxRuntime.jsx(AiChatAssistantMessage.AiChatAssistantMessage, {
163
162
  message,
164
163
  overrides,
165
- components,
166
- copilotId
164
+ components
167
165
  }, message.id);
168
166
  } else {
169
167
  return null;
@@ -250,7 +248,6 @@ const AiChat = react.forwardRef(
250
248
  children: [
251
249
  /* @__PURE__ */ jsxRuntime.jsx(AiChatMessages, {
252
250
  ref: messagesRef,
253
- copilotId,
254
251
  messages,
255
252
  overrides: overrides$1,
256
253
  components,
@@ -1 +1 @@
1
- {"version":3,"file":"AiChat.cjs","sources":["../../src/components/AiChat.tsx"],"sourcesContent":["import type {\n AiKnowledgeSource,\n AiOpaqueToolDefinition,\n CopilotId,\n MessageId,\n} from \"@liveblocks/core\";\nimport { RegisterAiTool, useAiChatMessages } from \"@liveblocks/react\";\nimport { useLatest } from \"@liveblocks/react/_private\";\nimport {\n type ComponentProps,\n type ComponentType,\n forwardRef,\n type MutableRefObject,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { SpinnerIcon } from \"../icons/Spinner\";\nimport {\n type AiChatMessageOverrides,\n type AiChatOverrides,\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport type { MarkdownComponents } from \"../primitives/Markdown\";\nimport { cn } from \"../utils/cn\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { AiChatAssistantMessage } from \"./internal/AiChatAssistantMessage\";\nimport { AiChatUserMessage } from \"./internal/AiChatUserMessage\";\nimport { AiComposer, type AiComposerProps } from \"./internal/AiComposer\";\n\n/**\n * The minimum number of pixels from the bottom of the scrollable area\n * before showing the scroll to bottom indicator.\n */\nconst MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 60;\n\nexport type AiChatComponentsEmptyProps = {\n /**\n * The chat ID provided to the `AiChat` component.\n */\n chatId: string;\n\n /**\n * The copilot ID provided to the `AiChat` component.\n */\n copilotId?: string;\n};\n\nexport type AiChatComponentsLoadingProps = Record<string, never>;\n\nexport type AiChatComponents = {\n /**\n * The component used to render the empty state of the chat.\n */\n Empty: ComponentType<AiChatComponentsEmptyProps>;\n\n /**\n * The component used to render the loading state of the chat.\n */\n Loading: ComponentType<AiChatComponentsLoadingProps>;\n\n /**\n * The components used to render Markdown content.\n */\n markdown?: Partial<MarkdownComponents>;\n};\n\nexport interface AiChatProps extends ComponentProps<\"div\"> {\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * Whether to focus the chat composer on mount.\n */\n autoFocus?: boolean;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: string;\n\n /**\n * The contextual knowledge to include in the chat. May be used by the\n * assistant when generating responses. In addition to the knowledge passed\n * in via this prop, the AiChat instance will also have access to any\n * globally registered knowledge via <RegisterAiKnowledge />.\n */\n knowledge?: AiKnowledgeSource[];\n\n /**\n * Tool definitions to make available within this chat. May be used by the assistant when generating responses.\n */\n tools?: Record<string, AiOpaqueToolDefinition>;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: AiComposerProps[\"onComposerSubmit\"];\n\n /**\n * The layout of the chat and its composer.\n */\n layout?: \"inset\" | \"compact\";\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides &\n AiComposerOverrides &\n AiChatMessageOverrides &\n AiChatOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & AiChatComponents>;\n}\n\ninterface AiChatMessagesProps extends ComponentProps<\"div\"> {\n messages: NonNullable<ReturnType<typeof useAiChatMessages>[\"messages\"]>;\n copilotId: AiChatProps[\"copilotId\"];\n overrides: AiChatProps[\"overrides\"];\n components: AiChatProps[\"components\"];\n lastSentMessageId: MessageId | null;\n scrollToBottom: MutableRefObject<\n (behavior: \"instant\" | \"smooth\", includeTrailingSpace?: boolean) => void\n >;\n onScrollAtBottomChange: MutableRefObject<\n (isScrollAtBottom: boolean | null) => void\n >;\n containerRef: MutableRefObject<HTMLDivElement | null>;\n footerRef: MutableRefObject<HTMLDivElement | null>;\n messagesRef: MutableRefObject<HTMLDivElement | null>;\n bottomTrailingMarkerRef: MutableRefObject<HTMLDivElement | null>;\n trailingSpacerRef: MutableRefObject<HTMLDivElement | null>;\n}\n\nconst defaultComponents: AiChatComponents = {\n Empty: () => null,\n Loading: () => (\n <div className=\"lb-loading lb-ai-chat-loading\">\n <SpinnerIcon />\n </div>\n ),\n};\n\nconst AiChatMessages = forwardRef<HTMLDivElement, AiChatMessagesProps>(\n (\n {\n messages,\n copilotId,\n overrides,\n components,\n lastSentMessageId,\n scrollToBottom,\n onScrollAtBottomChange,\n containerRef,\n footerRef,\n messagesRef,\n bottomTrailingMarkerRef,\n trailingSpacerRef,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const hasLastSentMessage = lastSentMessageId !== null;\n\n /**\n * Every time the container, footer, or messages list change size,\n * we calculate the trailing space that would allow the penultimate\n * message to be at the top of the viewport, and apply it.\n *\n * ┌─────────────────────────────────────────┐▲ A = The `scroll-margin-top`\n * │ ┌─────────────────────────┐ │▼▲ value of the penultimate message\n * │ │ The penultimate message │ │ │\n * │ └─────────────────────────┘ │ │ B = The height from the top of\n * │ │ │ the penultimate message to the\n * │ ┌─────────────────────────┐ │ │ bottom of the messages list,\n * │ │ The last message │ │ │ including the messages' heights,\n * │ └─────────────────────────┘ │ │ and any padding, gap, etc\n * │ │ │\n * ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤▲▼\n * │ ││ The trailing space needed to\n * │ = container height - (A + B + C) ││ allow the penultimate message\n * │ ││ to be at the top of the viewport\n * ├ ┬─────────────────────────────────────┬ ┤▼▲\n * │ │ │ │ │\n * │ │ │ │ │ C = The footer's height,\n * │ │ │ │ │ including any padding\n * │ └─────────────────────────────────────┘ │ │\n * └─────────────────────────────────────────┘ ▼\n */\n useEffect(\n () => {\n if (!hasLastSentMessage) {\n return;\n }\n\n const container = containerRef.current;\n const footer = footerRef.current;\n const messages = messagesRef.current;\n\n if (!container || !footer || !messages) {\n return;\n }\n\n const trailingSpacer = trailingSpacerRef.current;\n const bottomTrailingMarker = bottomTrailingMarkerRef.current;\n\n let containerHeight: number | null = null;\n let footerHeight: number | null = null;\n let messagesHeight: number | null = null;\n\n const resetTrailingSpace = () => {\n trailingSpacer?.style.removeProperty(\"height\");\n bottomTrailingMarker?.style.removeProperty(\"top\");\n };\n\n const resizeObserver = new ResizeObserver((entries) => {\n if (!trailingSpacer || !bottomTrailingMarker) {\n return;\n }\n\n const lastMessage = messages.lastElementChild;\n const penultimateMessage = lastMessage?.previousElementSibling;\n\n // If there's no last pair of messages, there's no need for any trailing space.\n if (!lastMessage || !penultimateMessage) {\n resetTrailingSpace();\n return;\n }\n\n // If the container's height is based on its content, the container isn't scrollable and there's no need for any trailing space.\n if (container.scrollHeight === container.clientHeight) {\n resetTrailingSpace();\n return;\n }\n\n let updatedContainerHeight: number | null = containerHeight;\n let updatedFooterHeight: number | null = footerHeight;\n let updatedMessagesHeight: number | null = messagesHeight;\n\n for (const entry of entries) {\n const entryHeight =\n entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height;\n\n if (entry.target === container) {\n updatedContainerHeight = entryHeight ?? null;\n } else if (entry.target === footer) {\n updatedFooterHeight = entryHeight ?? null;\n } else if (entry.target === messages) {\n updatedMessagesHeight = entryHeight ?? null;\n }\n }\n\n // If we don't have all the heights, we can't compute the trailing space.\n if (\n updatedContainerHeight === null ||\n updatedFooterHeight === null ||\n updatedMessagesHeight === null\n ) {\n resetTrailingSpace();\n return;\n }\n\n // If none of the heights have changed, we don't need to do anything.\n if (\n updatedContainerHeight === containerHeight &&\n updatedFooterHeight === footerHeight &&\n updatedMessagesHeight === messagesHeight\n ) {\n return;\n }\n\n // Now that we have compared them, we can update the heights.\n containerHeight = updatedContainerHeight;\n footerHeight = updatedFooterHeight;\n messagesHeight = updatedMessagesHeight;\n\n // A\n const penultimateMessageScrollMarginTop = Number.parseFloat(\n getComputedStyle(penultimateMessage as HTMLElement).scrollMarginTop\n );\n\n // B\n const messagesRect = messages.getBoundingClientRect();\n const penultimateMessageRect =\n penultimateMessage.getBoundingClientRect();\n const heightFromPenultimateMessageTopToMessagesListBottom =\n messagesRect.bottom - penultimateMessageRect.top;\n\n // A + B + C\n const differenceHeight =\n penultimateMessageScrollMarginTop +\n heightFromPenultimateMessageTopToMessagesListBottom +\n (footerHeight ?? 0);\n\n // = container height - (A + B + C)\n const trailingSpace = Math.max(containerHeight - differenceHeight, 0);\n\n // Update the trailing space.\n trailingSpacer.style.height = `${trailingSpace}px`;\n\n // Offset what \"the bottom\" is to the \"scroll at the bottom\" detection logic,\n // so that it doesn't include the trailing space.\n bottomTrailingMarker.style.top = `${-trailingSpace}px`;\n });\n\n resizeObserver.observe(container);\n resizeObserver.observe(footer);\n resizeObserver.observe(messages);\n\n return () => {\n resizeObserver.disconnect();\n resetTrailingSpace();\n };\n },\n // This effect only uses stable refs.\n [hasLastSentMessage] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Update the \"scroll at bottom\" state when needed.\n */\n useIntersectionCallback(\n bottomTrailingMarkerRef,\n (isIntersecting) => {\n onScrollAtBottomChange.current(isIntersecting);\n },\n { root: containerRef, rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR }\n );\n\n /**\n * Instantly scroll to the bottom for the initial state.\n */\n useEffect(\n () => {\n scrollToBottom.current(\"instant\");\n },\n // `scrollToBottom` is a stable ref containing the callback.\n [] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Scroll to new messages when sending them.\n */\n useEffect(\n () => {\n if (lastSentMessageId) {\n scrollToBottom.current(\"smooth\", true);\n }\n },\n // `scrollToBottom` is a stable ref containing the callback.\n [lastSentMessageId] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Reset the \"scroll at bottom\" state when the component unmounts.\n */\n useEffect(\n () => {\n const onScrollAtBottomChangeCallback = onScrollAtBottomChange.current;\n\n return () => {\n onScrollAtBottomChangeCallback(null);\n };\n },\n // `onScrollAtBottomChange` is a stable ref containing the callback.\n [] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n return (\n <div\n className={cn(\"lb-ai-chat-messages\", className)}\n ref={forwardedRef}\n {...props}\n >\n {messages.map((message) => {\n if (message.role === \"user\") {\n return (\n <AiChatUserMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n />\n );\n } else if (message.role === \"assistant\") {\n return (\n <AiChatAssistantMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n copilotId={copilotId}\n />\n );\n } else {\n return null;\n }\n })}\n </div>\n );\n }\n);\n\nexport const AiChat = forwardRef<HTMLDivElement, AiChatProps>(\n (\n {\n chatId,\n copilotId,\n autoFocus,\n overrides,\n knowledge: localKnowledge,\n tools = {},\n onComposerSubmit,\n layout = \"inset\",\n components,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const { messages, isLoading, error } = useAiChatMessages(chatId);\n const [lastSentMessageId, setLastSentMessageId] =\n useState<MessageId | null>(null);\n\n const $ = useOverrides(overrides);\n const Empty = components?.Empty ?? defaultComponents.Empty;\n const Loading = components?.Loading ?? defaultComponents.Loading;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const messagesRef = useRef<HTMLDivElement | null>(null);\n const footerRef = useRef<HTMLDivElement | null>(null);\n const bottomMarkerRef = useRef<HTMLDivElement | null>(null);\n const bottomTrailingMarkerRef = useRef<HTMLDivElement | null>(null);\n const trailingSpacerRef = useRef<HTMLDivElement | null>(null);\n\n const [isScrollAtBottom, setScrollAtBottom] = useState<boolean | null>(\n null\n );\n // `useState`'s setter is stable but this is for clarity in the places it's used.\n const onScrollAtBottomChange = useLatest(setScrollAtBottom);\n const isScrollIndicatorVisible =\n messages && isScrollAtBottom !== null ? !isScrollAtBottom : false;\n\n useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(\n forwardedRef,\n () => containerRef.current,\n []\n );\n\n const scrollToBottom = useLatest(\n (behavior: \"instant\" | \"smooth\", includeTrailingSpace = false) => {\n if (includeTrailingSpace) {\n // Scroll to the bottom marker to include the trailing space,\n // and wait for a frame in case the trailing space hasn't\n // been updated yet. (e.g. when sending a new message)\n requestAnimationFrame(() => {\n bottomMarkerRef.current?.scrollIntoView({\n behavior,\n block: \"end\",\n });\n });\n } else {\n // Scroll to the trailing space marker to only scroll to the\n // bottom of the messages, without including the trailing space.\n bottomTrailingMarkerRef.current?.scrollIntoView({\n behavior,\n block: \"end\",\n });\n }\n }\n );\n\n return (\n <div\n ref={containerRef}\n {...props}\n className={cn(\n \"lb-root lb-ai-chat\",\n `lb-ai-chat:layout-${layout}`,\n className\n )}\n >\n {Object.entries(tools).map(([name, tool]) => (\n <RegisterAiTool key={name} chatId={chatId} name={name} tool={tool} />\n ))}\n\n <div className=\"lb-ai-chat-content\">\n {isLoading ? (\n <Loading />\n ) : error !== undefined ? (\n <div className=\"lb-error lb-ai-chat-error\">\n {$.AI_CHAT_MESSAGES_ERROR(error)}\n </div>\n ) : messages.length === 0 ? (\n <Empty chatId={chatId} copilotId={copilotId} />\n ) : (\n <>\n <AiChatMessages\n ref={messagesRef}\n copilotId={copilotId}\n messages={messages}\n overrides={overrides}\n components={components}\n lastSentMessageId={lastSentMessageId}\n scrollToBottom={scrollToBottom}\n onScrollAtBottomChange={onScrollAtBottomChange}\n containerRef={containerRef}\n footerRef={footerRef}\n messagesRef={messagesRef}\n bottomTrailingMarkerRef={bottomTrailingMarkerRef}\n trailingSpacerRef={trailingSpacerRef}\n />\n\n {/**\n * This trailing spacer is used to extend the scrollable area beyond its actual\n * content, to allow messages to appear at the top of the viewport for example.\n */}\n <div\n ref={trailingSpacerRef}\n data-trailing-spacer=\"\"\n style={{\n pointerEvents: \"none\",\n }}\n aria-hidden\n />\n </>\n )}\n </div>\n\n <div className=\"lb-ai-chat-footer\" ref={footerRef}>\n <div className=\"lb-ai-chat-footer-actions\">\n <div\n className=\"lb-root lb-elevation lb-elevation-moderate lb-ai-chat-scroll-indicator\"\n data-visible={isScrollIndicatorVisible ? \"\" : undefined}\n >\n <button\n className=\"lb-ai-chat-scroll-indicator-button\"\n tabIndex={isScrollIndicatorVisible ? 0 : -1}\n aria-hidden={!isScrollIndicatorVisible}\n onClick={() => scrollToBottom.current(\"smooth\")}\n >\n <span className=\"lb-icon-container\">\n <ArrowDownIcon />\n </span>\n </button>\n </div>\n </div>\n <AiComposer\n key={chatId}\n chatId={chatId}\n copilotId={copilotId as CopilotId}\n overrides={overrides}\n autoFocus={autoFocus}\n knowledge={localKnowledge}\n onComposerSubmit={onComposerSubmit}\n onComposerSubmitted={({ id }) => setLastSentMessageId(id)}\n className={cn(\n \"lb-ai-chat-composer\",\n layout === \"inset\"\n ? \"lb-elevation lb-elevation-moderate\"\n : undefined\n )}\n />\n </div>\n\n {/**\n * This invisible marker is a trick which allows us to use IntersectionObserver to detect when the\n * scrollable area is fully scrolled to the bottom instead of manually tracking the scroll position\n * and having to deal with resizes, etc.\n *\n * It's positioned at the bottom of the scrollable area and reliably only becomes \"visible\" to the\n * IntersectionObserver when the scrollable area is scrolled to the bottom.\n */}\n {messages && messages.length > 0 ? (\n <div\n ref={bottomMarkerRef}\n style={{ position: \"sticky\", height: 0 }}\n aria-hidden\n data-bottom-marker=\"\"\n >\n {/**\n * This inner marker is absolutely offset by the same distance as the trailing space so its\n * visibility means the scrollable area is at the bottom of the messages, not the full bottom.\n */}\n <div\n ref={bottomTrailingMarkerRef}\n style={{\n position: \"absolute\",\n height: 0,\n }}\n data-bottom-trailing-marker=\"\"\n />\n </div>\n ) : null}\n </div>\n );\n }\n);\n"],"names":["jsx","SpinnerIcon","forwardRef","useEffect","messages","useIntersectionCallback","cn","AiChatUserMessage","AiChatAssistantMessage","overrides","useAiChatMessages","useState","useOverrides","useRef","useLatest","useImperativeHandle","jsxs","RegisterAiTool","Fragment","ArrowDownIcon","AiComposer"],"mappings":"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"}
1
+ {"version":3,"file":"AiChat.cjs","sources":["../../src/components/AiChat.tsx"],"sourcesContent":["import type {\n AiKnowledgeSource,\n AiOpaqueToolDefinition,\n CopilotId,\n MessageId,\n} from \"@liveblocks/core\";\nimport { RegisterAiTool, useAiChatMessages } from \"@liveblocks/react\";\nimport { useLatest } from \"@liveblocks/react/_private\";\nimport {\n type ComponentProps,\n type ComponentType,\n forwardRef,\n type MutableRefObject,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { SpinnerIcon } from \"../icons/Spinner\";\nimport {\n type AiChatMessageOverrides,\n type AiChatOverrides,\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport type { MarkdownComponents } from \"../primitives/Markdown\";\nimport { cn } from \"../utils/cn\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { AiChatAssistantMessage } from \"./internal/AiChatAssistantMessage\";\nimport { AiChatUserMessage } from \"./internal/AiChatUserMessage\";\nimport { AiComposer, type AiComposerProps } from \"./internal/AiComposer\";\n\n/**\n * The minimum number of pixels from the bottom of the scrollable area\n * before showing the scroll to bottom indicator.\n */\nconst MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 60;\n\nexport type AiChatComponentsEmptyProps = {\n /**\n * The chat ID provided to the `AiChat` component.\n */\n chatId: string;\n\n /**\n * The copilot ID provided to the `AiChat` component.\n */\n copilotId?: string;\n};\n\nexport type AiChatComponentsLoadingProps = Record<string, never>;\n\nexport type AiChatComponents = {\n /**\n * The component used to render the empty state of the chat.\n */\n Empty: ComponentType<AiChatComponentsEmptyProps>;\n\n /**\n * The component used to render the loading state of the chat.\n */\n Loading: ComponentType<AiChatComponentsLoadingProps>;\n\n /**\n * The components used to render Markdown content.\n */\n markdown?: Partial<MarkdownComponents>;\n};\n\nexport interface AiChatProps extends ComponentProps<\"div\"> {\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * Whether to focus the chat composer on mount.\n */\n autoFocus?: boolean;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: string;\n\n /**\n * The contextual knowledge to include in the chat. May be used by the\n * assistant when generating responses. In addition to the knowledge passed\n * in via this prop, the AiChat instance will also have access to any\n * globally registered knowledge via <RegisterAiKnowledge />.\n */\n knowledge?: AiKnowledgeSource[];\n\n /**\n * Tool definitions to make available within this chat. May be used by the assistant when generating responses.\n */\n tools?: Record<string, AiOpaqueToolDefinition>;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: AiComposerProps[\"onComposerSubmit\"];\n\n /**\n * The layout of the chat and its composer.\n */\n layout?: \"inset\" | \"compact\";\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides &\n AiComposerOverrides &\n AiChatMessageOverrides &\n AiChatOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & AiChatComponents>;\n}\n\ninterface AiChatMessagesProps extends ComponentProps<\"div\"> {\n messages: NonNullable<ReturnType<typeof useAiChatMessages>[\"messages\"]>;\n overrides: AiChatProps[\"overrides\"];\n components: AiChatProps[\"components\"];\n lastSentMessageId: MessageId | null;\n scrollToBottom: MutableRefObject<\n (behavior: \"instant\" | \"smooth\", includeTrailingSpace?: boolean) => void\n >;\n onScrollAtBottomChange: MutableRefObject<\n (isScrollAtBottom: boolean | null) => void\n >;\n containerRef: MutableRefObject<HTMLDivElement | null>;\n footerRef: MutableRefObject<HTMLDivElement | null>;\n messagesRef: MutableRefObject<HTMLDivElement | null>;\n bottomTrailingMarkerRef: MutableRefObject<HTMLDivElement | null>;\n trailingSpacerRef: MutableRefObject<HTMLDivElement | null>;\n}\n\nconst defaultComponents: AiChatComponents = {\n Empty: () => null,\n Loading: () => (\n <div className=\"lb-loading lb-ai-chat-loading\">\n <SpinnerIcon />\n </div>\n ),\n};\n\nconst AiChatMessages = forwardRef<HTMLDivElement, AiChatMessagesProps>(\n (\n {\n messages,\n overrides,\n components,\n lastSentMessageId,\n scrollToBottom,\n onScrollAtBottomChange,\n containerRef,\n footerRef,\n messagesRef,\n bottomTrailingMarkerRef,\n trailingSpacerRef,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const hasLastSentMessage = lastSentMessageId !== null;\n\n /**\n * Every time the container, footer, or messages list change size,\n * we calculate the trailing space that would allow the penultimate\n * message to be at the top of the viewport, and apply it.\n *\n * ┌─────────────────────────────────────────┐▲ A = The `scroll-margin-top`\n * │ ┌─────────────────────────┐ │▼▲ value of the penultimate message\n * │ │ The penultimate message │ │ │\n * │ └─────────────────────────┘ │ │ B = The height from the top of\n * │ │ │ the penultimate message to the\n * │ ┌─────────────────────────┐ │ │ bottom of the messages list,\n * │ │ The last message │ │ │ including the messages' heights,\n * │ └─────────────────────────┘ │ │ and any padding, gap, etc\n * │ │ │\n * ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤▲▼\n * │ ││ The trailing space needed to\n * │ = container height - (A + B + C) ││ allow the penultimate message\n * │ ││ to be at the top of the viewport\n * ├ ┬─────────────────────────────────────┬ ┤▼▲\n * │ │ │ │ │\n * │ │ │ │ │ C = The footer's height,\n * │ │ │ │ │ including any padding\n * │ └─────────────────────────────────────┘ │ │\n * └─────────────────────────────────────────┘ ▼\n */\n useEffect(\n () => {\n if (!hasLastSentMessage) {\n return;\n }\n\n const container = containerRef.current;\n const footer = footerRef.current;\n const messages = messagesRef.current;\n\n if (!container || !footer || !messages) {\n return;\n }\n\n const trailingSpacer = trailingSpacerRef.current;\n const bottomTrailingMarker = bottomTrailingMarkerRef.current;\n\n let containerHeight: number | null = null;\n let footerHeight: number | null = null;\n let messagesHeight: number | null = null;\n\n const resetTrailingSpace = () => {\n trailingSpacer?.style.removeProperty(\"height\");\n bottomTrailingMarker?.style.removeProperty(\"top\");\n };\n\n const resizeObserver = new ResizeObserver((entries) => {\n if (!trailingSpacer || !bottomTrailingMarker) {\n return;\n }\n\n const lastMessage = messages.lastElementChild;\n const penultimateMessage = lastMessage?.previousElementSibling;\n\n // If there's no last pair of messages, there's no need for any trailing space.\n if (!lastMessage || !penultimateMessage) {\n resetTrailingSpace();\n return;\n }\n\n // If the container's height is based on its content, the container isn't scrollable and there's no need for any trailing space.\n if (container.scrollHeight === container.clientHeight) {\n resetTrailingSpace();\n return;\n }\n\n let updatedContainerHeight: number | null = containerHeight;\n let updatedFooterHeight: number | null = footerHeight;\n let updatedMessagesHeight: number | null = messagesHeight;\n\n for (const entry of entries) {\n const entryHeight =\n entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height;\n\n if (entry.target === container) {\n updatedContainerHeight = entryHeight ?? null;\n } else if (entry.target === footer) {\n updatedFooterHeight = entryHeight ?? null;\n } else if (entry.target === messages) {\n updatedMessagesHeight = entryHeight ?? null;\n }\n }\n\n // If we don't have all the heights, we can't compute the trailing space.\n if (\n updatedContainerHeight === null ||\n updatedFooterHeight === null ||\n updatedMessagesHeight === null\n ) {\n resetTrailingSpace();\n return;\n }\n\n // If none of the heights have changed, we don't need to do anything.\n if (\n updatedContainerHeight === containerHeight &&\n updatedFooterHeight === footerHeight &&\n updatedMessagesHeight === messagesHeight\n ) {\n return;\n }\n\n // Now that we have compared them, we can update the heights.\n containerHeight = updatedContainerHeight;\n footerHeight = updatedFooterHeight;\n messagesHeight = updatedMessagesHeight;\n\n // A\n const penultimateMessageScrollMarginTop = Number.parseFloat(\n getComputedStyle(penultimateMessage as HTMLElement).scrollMarginTop\n );\n\n // B\n const messagesRect = messages.getBoundingClientRect();\n const penultimateMessageRect =\n penultimateMessage.getBoundingClientRect();\n const heightFromPenultimateMessageTopToMessagesListBottom =\n messagesRect.bottom - penultimateMessageRect.top;\n\n // A + B + C\n const differenceHeight =\n penultimateMessageScrollMarginTop +\n heightFromPenultimateMessageTopToMessagesListBottom +\n (footerHeight ?? 0);\n\n // = container height - (A + B + C)\n const trailingSpace = Math.max(containerHeight - differenceHeight, 0);\n\n // Update the trailing space.\n trailingSpacer.style.height = `${trailingSpace}px`;\n\n // Offset what \"the bottom\" is to the \"scroll at the bottom\" detection logic,\n // so that it doesn't include the trailing space.\n bottomTrailingMarker.style.top = `${-trailingSpace}px`;\n });\n\n resizeObserver.observe(container);\n resizeObserver.observe(footer);\n resizeObserver.observe(messages);\n\n return () => {\n resizeObserver.disconnect();\n resetTrailingSpace();\n };\n },\n // This effect only uses stable refs.\n [hasLastSentMessage] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Update the \"scroll at bottom\" state when needed.\n */\n useIntersectionCallback(\n bottomTrailingMarkerRef,\n (isIntersecting) => {\n onScrollAtBottomChange.current(isIntersecting);\n },\n { root: containerRef, rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR }\n );\n\n /**\n * Instantly scroll to the bottom for the initial state.\n */\n useEffect(\n () => {\n scrollToBottom.current(\"instant\");\n },\n // `scrollToBottom` is a stable ref containing the callback.\n [] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Scroll to new messages when sending them.\n */\n useEffect(\n () => {\n if (lastSentMessageId) {\n scrollToBottom.current(\"smooth\", true);\n }\n },\n // `scrollToBottom` is a stable ref containing the callback.\n [lastSentMessageId] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Reset the \"scroll at bottom\" state when the component unmounts.\n */\n useEffect(\n () => {\n const onScrollAtBottomChangeCallback = onScrollAtBottomChange.current;\n\n return () => {\n onScrollAtBottomChangeCallback(null);\n };\n },\n // `onScrollAtBottomChange` is a stable ref containing the callback.\n [] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n return (\n <div\n className={cn(\"lb-ai-chat-messages\", className)}\n ref={forwardedRef}\n {...props}\n >\n {messages.map((message) => {\n if (message.role === \"user\") {\n return (\n <AiChatUserMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n />\n );\n } else if (message.role === \"assistant\") {\n return (\n <AiChatAssistantMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n />\n );\n } else {\n return null;\n }\n })}\n </div>\n );\n }\n);\n\nexport const AiChat = forwardRef<HTMLDivElement, AiChatProps>(\n (\n {\n chatId,\n copilotId,\n autoFocus,\n overrides,\n knowledge: localKnowledge,\n tools = {},\n onComposerSubmit,\n layout = \"inset\",\n components,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const { messages, isLoading, error } = useAiChatMessages(chatId);\n const [lastSentMessageId, setLastSentMessageId] =\n useState<MessageId | null>(null);\n\n const $ = useOverrides(overrides);\n const Empty = components?.Empty ?? defaultComponents.Empty;\n const Loading = components?.Loading ?? defaultComponents.Loading;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const messagesRef = useRef<HTMLDivElement | null>(null);\n const footerRef = useRef<HTMLDivElement | null>(null);\n const bottomMarkerRef = useRef<HTMLDivElement | null>(null);\n const bottomTrailingMarkerRef = useRef<HTMLDivElement | null>(null);\n const trailingSpacerRef = useRef<HTMLDivElement | null>(null);\n\n const [isScrollAtBottom, setScrollAtBottom] = useState<boolean | null>(\n null\n );\n // `useState`'s setter is stable but this is for clarity in the places it's used.\n const onScrollAtBottomChange = useLatest(setScrollAtBottom);\n const isScrollIndicatorVisible =\n messages && isScrollAtBottom !== null ? !isScrollAtBottom : false;\n\n useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(\n forwardedRef,\n () => containerRef.current,\n []\n );\n\n const scrollToBottom = useLatest(\n (behavior: \"instant\" | \"smooth\", includeTrailingSpace = false) => {\n if (includeTrailingSpace) {\n // Scroll to the bottom marker to include the trailing space,\n // and wait for a frame in case the trailing space hasn't\n // been updated yet. (e.g. when sending a new message)\n requestAnimationFrame(() => {\n bottomMarkerRef.current?.scrollIntoView({\n behavior,\n block: \"end\",\n });\n });\n } else {\n // Scroll to the trailing space marker to only scroll to the\n // bottom of the messages, without including the trailing space.\n bottomTrailingMarkerRef.current?.scrollIntoView({\n behavior,\n block: \"end\",\n });\n }\n }\n );\n\n return (\n <div\n ref={containerRef}\n {...props}\n className={cn(\n \"lb-root lb-ai-chat\",\n `lb-ai-chat:layout-${layout}`,\n className\n )}\n >\n {Object.entries(tools).map(([name, tool]) => (\n <RegisterAiTool key={name} chatId={chatId} name={name} tool={tool} />\n ))}\n\n <div className=\"lb-ai-chat-content\">\n {isLoading ? (\n <Loading />\n ) : error !== undefined ? (\n <div className=\"lb-error lb-ai-chat-error\">\n {$.AI_CHAT_MESSAGES_ERROR(error)}\n </div>\n ) : messages.length === 0 ? (\n <Empty chatId={chatId} copilotId={copilotId} />\n ) : (\n <>\n <AiChatMessages\n ref={messagesRef}\n messages={messages}\n overrides={overrides}\n components={components}\n lastSentMessageId={lastSentMessageId}\n scrollToBottom={scrollToBottom}\n onScrollAtBottomChange={onScrollAtBottomChange}\n containerRef={containerRef}\n footerRef={footerRef}\n messagesRef={messagesRef}\n bottomTrailingMarkerRef={bottomTrailingMarkerRef}\n trailingSpacerRef={trailingSpacerRef}\n />\n\n {/**\n * This trailing spacer is used to extend the scrollable area beyond its actual\n * content, to allow messages to appear at the top of the viewport for example.\n */}\n <div\n ref={trailingSpacerRef}\n data-trailing-spacer=\"\"\n style={{\n pointerEvents: \"none\",\n }}\n aria-hidden\n />\n </>\n )}\n </div>\n\n <div className=\"lb-ai-chat-footer\" ref={footerRef}>\n <div className=\"lb-ai-chat-footer-actions\">\n <div\n className=\"lb-root lb-elevation lb-elevation-moderate lb-ai-chat-scroll-indicator\"\n data-visible={isScrollIndicatorVisible ? \"\" : undefined}\n >\n <button\n className=\"lb-ai-chat-scroll-indicator-button\"\n tabIndex={isScrollIndicatorVisible ? 0 : -1}\n aria-hidden={!isScrollIndicatorVisible}\n onClick={() => scrollToBottom.current(\"smooth\")}\n >\n <span className=\"lb-icon-container\">\n <ArrowDownIcon />\n </span>\n </button>\n </div>\n </div>\n <AiComposer\n key={chatId}\n chatId={chatId}\n copilotId={copilotId as CopilotId}\n overrides={overrides}\n autoFocus={autoFocus}\n knowledge={localKnowledge}\n onComposerSubmit={onComposerSubmit}\n onComposerSubmitted={({ id }) => setLastSentMessageId(id)}\n className={cn(\n \"lb-ai-chat-composer\",\n layout === \"inset\"\n ? \"lb-elevation lb-elevation-moderate\"\n : undefined\n )}\n />\n </div>\n\n {/**\n * This invisible marker is a trick which allows us to use IntersectionObserver to detect when the\n * scrollable area is fully scrolled to the bottom instead of manually tracking the scroll position\n * and having to deal with resizes, etc.\n *\n * It's positioned at the bottom of the scrollable area and reliably only becomes \"visible\" to the\n * IntersectionObserver when the scrollable area is scrolled to the bottom.\n */}\n {messages && messages.length > 0 ? (\n <div\n ref={bottomMarkerRef}\n style={{ position: \"sticky\", height: 0 }}\n aria-hidden\n data-bottom-marker=\"\"\n >\n {/**\n * This inner marker is absolutely offset by the same distance as the trailing space so its\n * visibility means the scrollable area is at the bottom of the messages, not the full bottom.\n */}\n <div\n ref={bottomTrailingMarkerRef}\n style={{\n position: \"absolute\",\n height: 0,\n }}\n data-bottom-trailing-marker=\"\"\n />\n </div>\n ) : null}\n </div>\n );\n }\n);\n"],"names":["jsx","SpinnerIcon","forwardRef","useEffect","messages","useIntersectionCallback","cn","AiChatUserMessage","AiChatAssistantMessage","overrides","useAiChatMessages","useState","useOverrides","useRef","useLatest","useImperativeHandle","jsxs","RegisterAiTool","Fragment","ArrowDownIcon","AiComposer"],"mappings":"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"}
@@ -22,7 +22,6 @@ const defaultComponents = {
22
22
  const AiChatMessages = forwardRef(
23
23
  ({
24
24
  messages,
25
- copilotId,
26
25
  overrides,
27
26
  components,
28
27
  lastSentMessageId,
@@ -160,8 +159,7 @@ const AiChatMessages = forwardRef(
160
159
  return /* @__PURE__ */ jsx(AiChatAssistantMessage, {
161
160
  message,
162
161
  overrides,
163
- components,
164
- copilotId
162
+ components
165
163
  }, message.id);
166
164
  } else {
167
165
  return null;
@@ -248,7 +246,6 @@ const AiChat = forwardRef(
248
246
  children: [
249
247
  /* @__PURE__ */ jsx(AiChatMessages, {
250
248
  ref: messagesRef,
251
- copilotId,
252
249
  messages,
253
250
  overrides,
254
251
  components,
@@ -1 +1 @@
1
- {"version":3,"file":"AiChat.js","sources":["../../src/components/AiChat.tsx"],"sourcesContent":["import type {\n AiKnowledgeSource,\n AiOpaqueToolDefinition,\n CopilotId,\n MessageId,\n} from \"@liveblocks/core\";\nimport { RegisterAiTool, useAiChatMessages } from \"@liveblocks/react\";\nimport { useLatest } from \"@liveblocks/react/_private\";\nimport {\n type ComponentProps,\n type ComponentType,\n forwardRef,\n type MutableRefObject,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { SpinnerIcon } from \"../icons/Spinner\";\nimport {\n type AiChatMessageOverrides,\n type AiChatOverrides,\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport type { MarkdownComponents } from \"../primitives/Markdown\";\nimport { cn } from \"../utils/cn\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { AiChatAssistantMessage } from \"./internal/AiChatAssistantMessage\";\nimport { AiChatUserMessage } from \"./internal/AiChatUserMessage\";\nimport { AiComposer, type AiComposerProps } from \"./internal/AiComposer\";\n\n/**\n * The minimum number of pixels from the bottom of the scrollable area\n * before showing the scroll to bottom indicator.\n */\nconst MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 60;\n\nexport type AiChatComponentsEmptyProps = {\n /**\n * The chat ID provided to the `AiChat` component.\n */\n chatId: string;\n\n /**\n * The copilot ID provided to the `AiChat` component.\n */\n copilotId?: string;\n};\n\nexport type AiChatComponentsLoadingProps = Record<string, never>;\n\nexport type AiChatComponents = {\n /**\n * The component used to render the empty state of the chat.\n */\n Empty: ComponentType<AiChatComponentsEmptyProps>;\n\n /**\n * The component used to render the loading state of the chat.\n */\n Loading: ComponentType<AiChatComponentsLoadingProps>;\n\n /**\n * The components used to render Markdown content.\n */\n markdown?: Partial<MarkdownComponents>;\n};\n\nexport interface AiChatProps extends ComponentProps<\"div\"> {\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * Whether to focus the chat composer on mount.\n */\n autoFocus?: boolean;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: string;\n\n /**\n * The contextual knowledge to include in the chat. May be used by the\n * assistant when generating responses. In addition to the knowledge passed\n * in via this prop, the AiChat instance will also have access to any\n * globally registered knowledge via <RegisterAiKnowledge />.\n */\n knowledge?: AiKnowledgeSource[];\n\n /**\n * Tool definitions to make available within this chat. May be used by the assistant when generating responses.\n */\n tools?: Record<string, AiOpaqueToolDefinition>;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: AiComposerProps[\"onComposerSubmit\"];\n\n /**\n * The layout of the chat and its composer.\n */\n layout?: \"inset\" | \"compact\";\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides &\n AiComposerOverrides &\n AiChatMessageOverrides &\n AiChatOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & AiChatComponents>;\n}\n\ninterface AiChatMessagesProps extends ComponentProps<\"div\"> {\n messages: NonNullable<ReturnType<typeof useAiChatMessages>[\"messages\"]>;\n copilotId: AiChatProps[\"copilotId\"];\n overrides: AiChatProps[\"overrides\"];\n components: AiChatProps[\"components\"];\n lastSentMessageId: MessageId | null;\n scrollToBottom: MutableRefObject<\n (behavior: \"instant\" | \"smooth\", includeTrailingSpace?: boolean) => void\n >;\n onScrollAtBottomChange: MutableRefObject<\n (isScrollAtBottom: boolean | null) => void\n >;\n containerRef: MutableRefObject<HTMLDivElement | null>;\n footerRef: MutableRefObject<HTMLDivElement | null>;\n messagesRef: MutableRefObject<HTMLDivElement | null>;\n bottomTrailingMarkerRef: MutableRefObject<HTMLDivElement | null>;\n trailingSpacerRef: MutableRefObject<HTMLDivElement | null>;\n}\n\nconst defaultComponents: AiChatComponents = {\n Empty: () => null,\n Loading: () => (\n <div className=\"lb-loading lb-ai-chat-loading\">\n <SpinnerIcon />\n </div>\n ),\n};\n\nconst AiChatMessages = forwardRef<HTMLDivElement, AiChatMessagesProps>(\n (\n {\n messages,\n copilotId,\n overrides,\n components,\n lastSentMessageId,\n scrollToBottom,\n onScrollAtBottomChange,\n containerRef,\n footerRef,\n messagesRef,\n bottomTrailingMarkerRef,\n trailingSpacerRef,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const hasLastSentMessage = lastSentMessageId !== null;\n\n /**\n * Every time the container, footer, or messages list change size,\n * we calculate the trailing space that would allow the penultimate\n * message to be at the top of the viewport, and apply it.\n *\n * ┌─────────────────────────────────────────┐▲ A = The `scroll-margin-top`\n * │ ┌─────────────────────────┐ │▼▲ value of the penultimate message\n * │ │ The penultimate message │ │ │\n * │ └─────────────────────────┘ │ │ B = The height from the top of\n * │ │ │ the penultimate message to the\n * │ ┌─────────────────────────┐ │ │ bottom of the messages list,\n * │ │ The last message │ │ │ including the messages' heights,\n * │ └─────────────────────────┘ │ │ and any padding, gap, etc\n * │ │ │\n * ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤▲▼\n * │ ││ The trailing space needed to\n * │ = container height - (A + B + C) ││ allow the penultimate message\n * │ ││ to be at the top of the viewport\n * ├ ┬─────────────────────────────────────┬ ┤▼▲\n * │ │ │ │ │\n * │ │ │ │ │ C = The footer's height,\n * │ │ │ │ │ including any padding\n * │ └─────────────────────────────────────┘ │ │\n * └─────────────────────────────────────────┘ ▼\n */\n useEffect(\n () => {\n if (!hasLastSentMessage) {\n return;\n }\n\n const container = containerRef.current;\n const footer = footerRef.current;\n const messages = messagesRef.current;\n\n if (!container || !footer || !messages) {\n return;\n }\n\n const trailingSpacer = trailingSpacerRef.current;\n const bottomTrailingMarker = bottomTrailingMarkerRef.current;\n\n let containerHeight: number | null = null;\n let footerHeight: number | null = null;\n let messagesHeight: number | null = null;\n\n const resetTrailingSpace = () => {\n trailingSpacer?.style.removeProperty(\"height\");\n bottomTrailingMarker?.style.removeProperty(\"top\");\n };\n\n const resizeObserver = new ResizeObserver((entries) => {\n if (!trailingSpacer || !bottomTrailingMarker) {\n return;\n }\n\n const lastMessage = messages.lastElementChild;\n const penultimateMessage = lastMessage?.previousElementSibling;\n\n // If there's no last pair of messages, there's no need for any trailing space.\n if (!lastMessage || !penultimateMessage) {\n resetTrailingSpace();\n return;\n }\n\n // If the container's height is based on its content, the container isn't scrollable and there's no need for any trailing space.\n if (container.scrollHeight === container.clientHeight) {\n resetTrailingSpace();\n return;\n }\n\n let updatedContainerHeight: number | null = containerHeight;\n let updatedFooterHeight: number | null = footerHeight;\n let updatedMessagesHeight: number | null = messagesHeight;\n\n for (const entry of entries) {\n const entryHeight =\n entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height;\n\n if (entry.target === container) {\n updatedContainerHeight = entryHeight ?? null;\n } else if (entry.target === footer) {\n updatedFooterHeight = entryHeight ?? null;\n } else if (entry.target === messages) {\n updatedMessagesHeight = entryHeight ?? null;\n }\n }\n\n // If we don't have all the heights, we can't compute the trailing space.\n if (\n updatedContainerHeight === null ||\n updatedFooterHeight === null ||\n updatedMessagesHeight === null\n ) {\n resetTrailingSpace();\n return;\n }\n\n // If none of the heights have changed, we don't need to do anything.\n if (\n updatedContainerHeight === containerHeight &&\n updatedFooterHeight === footerHeight &&\n updatedMessagesHeight === messagesHeight\n ) {\n return;\n }\n\n // Now that we have compared them, we can update the heights.\n containerHeight = updatedContainerHeight;\n footerHeight = updatedFooterHeight;\n messagesHeight = updatedMessagesHeight;\n\n // A\n const penultimateMessageScrollMarginTop = Number.parseFloat(\n getComputedStyle(penultimateMessage as HTMLElement).scrollMarginTop\n );\n\n // B\n const messagesRect = messages.getBoundingClientRect();\n const penultimateMessageRect =\n penultimateMessage.getBoundingClientRect();\n const heightFromPenultimateMessageTopToMessagesListBottom =\n messagesRect.bottom - penultimateMessageRect.top;\n\n // A + B + C\n const differenceHeight =\n penultimateMessageScrollMarginTop +\n heightFromPenultimateMessageTopToMessagesListBottom +\n (footerHeight ?? 0);\n\n // = container height - (A + B + C)\n const trailingSpace = Math.max(containerHeight - differenceHeight, 0);\n\n // Update the trailing space.\n trailingSpacer.style.height = `${trailingSpace}px`;\n\n // Offset what \"the bottom\" is to the \"scroll at the bottom\" detection logic,\n // so that it doesn't include the trailing space.\n bottomTrailingMarker.style.top = `${-trailingSpace}px`;\n });\n\n resizeObserver.observe(container);\n resizeObserver.observe(footer);\n resizeObserver.observe(messages);\n\n return () => {\n resizeObserver.disconnect();\n resetTrailingSpace();\n };\n },\n // This effect only uses stable refs.\n [hasLastSentMessage] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Update the \"scroll at bottom\" state when needed.\n */\n useIntersectionCallback(\n bottomTrailingMarkerRef,\n (isIntersecting) => {\n onScrollAtBottomChange.current(isIntersecting);\n },\n { root: containerRef, rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR }\n );\n\n /**\n * Instantly scroll to the bottom for the initial state.\n */\n useEffect(\n () => {\n scrollToBottom.current(\"instant\");\n },\n // `scrollToBottom` is a stable ref containing the callback.\n [] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Scroll to new messages when sending them.\n */\n useEffect(\n () => {\n if (lastSentMessageId) {\n scrollToBottom.current(\"smooth\", true);\n }\n },\n // `scrollToBottom` is a stable ref containing the callback.\n [lastSentMessageId] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Reset the \"scroll at bottom\" state when the component unmounts.\n */\n useEffect(\n () => {\n const onScrollAtBottomChangeCallback = onScrollAtBottomChange.current;\n\n return () => {\n onScrollAtBottomChangeCallback(null);\n };\n },\n // `onScrollAtBottomChange` is a stable ref containing the callback.\n [] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n return (\n <div\n className={cn(\"lb-ai-chat-messages\", className)}\n ref={forwardedRef}\n {...props}\n >\n {messages.map((message) => {\n if (message.role === \"user\") {\n return (\n <AiChatUserMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n />\n );\n } else if (message.role === \"assistant\") {\n return (\n <AiChatAssistantMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n copilotId={copilotId}\n />\n );\n } else {\n return null;\n }\n })}\n </div>\n );\n }\n);\n\nexport const AiChat = forwardRef<HTMLDivElement, AiChatProps>(\n (\n {\n chatId,\n copilotId,\n autoFocus,\n overrides,\n knowledge: localKnowledge,\n tools = {},\n onComposerSubmit,\n layout = \"inset\",\n components,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const { messages, isLoading, error } = useAiChatMessages(chatId);\n const [lastSentMessageId, setLastSentMessageId] =\n useState<MessageId | null>(null);\n\n const $ = useOverrides(overrides);\n const Empty = components?.Empty ?? defaultComponents.Empty;\n const Loading = components?.Loading ?? defaultComponents.Loading;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const messagesRef = useRef<HTMLDivElement | null>(null);\n const footerRef = useRef<HTMLDivElement | null>(null);\n const bottomMarkerRef = useRef<HTMLDivElement | null>(null);\n const bottomTrailingMarkerRef = useRef<HTMLDivElement | null>(null);\n const trailingSpacerRef = useRef<HTMLDivElement | null>(null);\n\n const [isScrollAtBottom, setScrollAtBottom] = useState<boolean | null>(\n null\n );\n // `useState`'s setter is stable but this is for clarity in the places it's used.\n const onScrollAtBottomChange = useLatest(setScrollAtBottom);\n const isScrollIndicatorVisible =\n messages && isScrollAtBottom !== null ? !isScrollAtBottom : false;\n\n useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(\n forwardedRef,\n () => containerRef.current,\n []\n );\n\n const scrollToBottom = useLatest(\n (behavior: \"instant\" | \"smooth\", includeTrailingSpace = false) => {\n if (includeTrailingSpace) {\n // Scroll to the bottom marker to include the trailing space,\n // and wait for a frame in case the trailing space hasn't\n // been updated yet. (e.g. when sending a new message)\n requestAnimationFrame(() => {\n bottomMarkerRef.current?.scrollIntoView({\n behavior,\n block: \"end\",\n });\n });\n } else {\n // Scroll to the trailing space marker to only scroll to the\n // bottom of the messages, without including the trailing space.\n bottomTrailingMarkerRef.current?.scrollIntoView({\n behavior,\n block: \"end\",\n });\n }\n }\n );\n\n return (\n <div\n ref={containerRef}\n {...props}\n className={cn(\n \"lb-root lb-ai-chat\",\n `lb-ai-chat:layout-${layout}`,\n className\n )}\n >\n {Object.entries(tools).map(([name, tool]) => (\n <RegisterAiTool key={name} chatId={chatId} name={name} tool={tool} />\n ))}\n\n <div className=\"lb-ai-chat-content\">\n {isLoading ? (\n <Loading />\n ) : error !== undefined ? (\n <div className=\"lb-error lb-ai-chat-error\">\n {$.AI_CHAT_MESSAGES_ERROR(error)}\n </div>\n ) : messages.length === 0 ? (\n <Empty chatId={chatId} copilotId={copilotId} />\n ) : (\n <>\n <AiChatMessages\n ref={messagesRef}\n copilotId={copilotId}\n messages={messages}\n overrides={overrides}\n components={components}\n lastSentMessageId={lastSentMessageId}\n scrollToBottom={scrollToBottom}\n onScrollAtBottomChange={onScrollAtBottomChange}\n containerRef={containerRef}\n footerRef={footerRef}\n messagesRef={messagesRef}\n bottomTrailingMarkerRef={bottomTrailingMarkerRef}\n trailingSpacerRef={trailingSpacerRef}\n />\n\n {/**\n * This trailing spacer is used to extend the scrollable area beyond its actual\n * content, to allow messages to appear at the top of the viewport for example.\n */}\n <div\n ref={trailingSpacerRef}\n data-trailing-spacer=\"\"\n style={{\n pointerEvents: \"none\",\n }}\n aria-hidden\n />\n </>\n )}\n </div>\n\n <div className=\"lb-ai-chat-footer\" ref={footerRef}>\n <div className=\"lb-ai-chat-footer-actions\">\n <div\n className=\"lb-root lb-elevation lb-elevation-moderate lb-ai-chat-scroll-indicator\"\n data-visible={isScrollIndicatorVisible ? \"\" : undefined}\n >\n <button\n className=\"lb-ai-chat-scroll-indicator-button\"\n tabIndex={isScrollIndicatorVisible ? 0 : -1}\n aria-hidden={!isScrollIndicatorVisible}\n onClick={() => scrollToBottom.current(\"smooth\")}\n >\n <span className=\"lb-icon-container\">\n <ArrowDownIcon />\n </span>\n </button>\n </div>\n </div>\n <AiComposer\n key={chatId}\n chatId={chatId}\n copilotId={copilotId as CopilotId}\n overrides={overrides}\n autoFocus={autoFocus}\n knowledge={localKnowledge}\n onComposerSubmit={onComposerSubmit}\n onComposerSubmitted={({ id }) => setLastSentMessageId(id)}\n className={cn(\n \"lb-ai-chat-composer\",\n layout === \"inset\"\n ? \"lb-elevation lb-elevation-moderate\"\n : undefined\n )}\n />\n </div>\n\n {/**\n * This invisible marker is a trick which allows us to use IntersectionObserver to detect when the\n * scrollable area is fully scrolled to the bottom instead of manually tracking the scroll position\n * and having to deal with resizes, etc.\n *\n * It's positioned at the bottom of the scrollable area and reliably only becomes \"visible\" to the\n * IntersectionObserver when the scrollable area is scrolled to the bottom.\n */}\n {messages && messages.length > 0 ? (\n <div\n ref={bottomMarkerRef}\n style={{ position: \"sticky\", height: 0 }}\n aria-hidden\n data-bottom-marker=\"\"\n >\n {/**\n * This inner marker is absolutely offset by the same distance as the trailing space so its\n * visibility means the scrollable area is at the bottom of the messages, not the full bottom.\n */}\n <div\n ref={bottomTrailingMarkerRef}\n style={{\n position: \"absolute\",\n height: 0,\n }}\n data-bottom-trailing-marker=\"\"\n />\n </div>\n ) : null}\n </div>\n );\n }\n);\n"],"names":["messages"],"mappings":"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"}
1
+ {"version":3,"file":"AiChat.js","sources":["../../src/components/AiChat.tsx"],"sourcesContent":["import type {\n AiKnowledgeSource,\n AiOpaqueToolDefinition,\n CopilotId,\n MessageId,\n} from \"@liveblocks/core\";\nimport { RegisterAiTool, useAiChatMessages } from \"@liveblocks/react\";\nimport { useLatest } from \"@liveblocks/react/_private\";\nimport {\n type ComponentProps,\n type ComponentType,\n forwardRef,\n type MutableRefObject,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { GlobalComponents } from \"../components\";\nimport { ArrowDownIcon } from \"../icons/ArrowDown\";\nimport { SpinnerIcon } from \"../icons/Spinner\";\nimport {\n type AiChatMessageOverrides,\n type AiChatOverrides,\n type AiComposerOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport type { MarkdownComponents } from \"../primitives/Markdown\";\nimport { cn } from \"../utils/cn\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { AiChatAssistantMessage } from \"./internal/AiChatAssistantMessage\";\nimport { AiChatUserMessage } from \"./internal/AiChatUserMessage\";\nimport { AiComposer, type AiComposerProps } from \"./internal/AiComposer\";\n\n/**\n * The minimum number of pixels from the bottom of the scrollable area\n * before showing the scroll to bottom indicator.\n */\nconst MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR = 60;\n\nexport type AiChatComponentsEmptyProps = {\n /**\n * The chat ID provided to the `AiChat` component.\n */\n chatId: string;\n\n /**\n * The copilot ID provided to the `AiChat` component.\n */\n copilotId?: string;\n};\n\nexport type AiChatComponentsLoadingProps = Record<string, never>;\n\nexport type AiChatComponents = {\n /**\n * The component used to render the empty state of the chat.\n */\n Empty: ComponentType<AiChatComponentsEmptyProps>;\n\n /**\n * The component used to render the loading state of the chat.\n */\n Loading: ComponentType<AiChatComponentsLoadingProps>;\n\n /**\n * The components used to render Markdown content.\n */\n markdown?: Partial<MarkdownComponents>;\n};\n\nexport interface AiChatProps extends ComponentProps<\"div\"> {\n /**\n * The ID of the chat the composer belongs to.\n */\n chatId: string;\n\n /**\n * Whether to focus the chat composer on mount.\n */\n autoFocus?: boolean;\n\n /**\n * The ID of the copilot to use to send the message.\n */\n copilotId?: string;\n\n /**\n * The contextual knowledge to include in the chat. May be used by the\n * assistant when generating responses. In addition to the knowledge passed\n * in via this prop, the AiChat instance will also have access to any\n * globally registered knowledge via <RegisterAiKnowledge />.\n */\n knowledge?: AiKnowledgeSource[];\n\n /**\n * Tool definitions to make available within this chat. May be used by the assistant when generating responses.\n */\n tools?: Record<string, AiOpaqueToolDefinition>;\n\n /**\n * The event handler called when the composer is submitted.\n */\n onComposerSubmit?: AiComposerProps[\"onComposerSubmit\"];\n\n /**\n * The layout of the chat and its composer.\n */\n layout?: \"inset\" | \"compact\";\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<\n GlobalOverrides &\n AiComposerOverrides &\n AiChatMessageOverrides &\n AiChatOverrides\n >;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents & AiChatComponents>;\n}\n\ninterface AiChatMessagesProps extends ComponentProps<\"div\"> {\n messages: NonNullable<ReturnType<typeof useAiChatMessages>[\"messages\"]>;\n overrides: AiChatProps[\"overrides\"];\n components: AiChatProps[\"components\"];\n lastSentMessageId: MessageId | null;\n scrollToBottom: MutableRefObject<\n (behavior: \"instant\" | \"smooth\", includeTrailingSpace?: boolean) => void\n >;\n onScrollAtBottomChange: MutableRefObject<\n (isScrollAtBottom: boolean | null) => void\n >;\n containerRef: MutableRefObject<HTMLDivElement | null>;\n footerRef: MutableRefObject<HTMLDivElement | null>;\n messagesRef: MutableRefObject<HTMLDivElement | null>;\n bottomTrailingMarkerRef: MutableRefObject<HTMLDivElement | null>;\n trailingSpacerRef: MutableRefObject<HTMLDivElement | null>;\n}\n\nconst defaultComponents: AiChatComponents = {\n Empty: () => null,\n Loading: () => (\n <div className=\"lb-loading lb-ai-chat-loading\">\n <SpinnerIcon />\n </div>\n ),\n};\n\nconst AiChatMessages = forwardRef<HTMLDivElement, AiChatMessagesProps>(\n (\n {\n messages,\n overrides,\n components,\n lastSentMessageId,\n scrollToBottom,\n onScrollAtBottomChange,\n containerRef,\n footerRef,\n messagesRef,\n bottomTrailingMarkerRef,\n trailingSpacerRef,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const hasLastSentMessage = lastSentMessageId !== null;\n\n /**\n * Every time the container, footer, or messages list change size,\n * we calculate the trailing space that would allow the penultimate\n * message to be at the top of the viewport, and apply it.\n *\n * ┌─────────────────────────────────────────┐▲ A = The `scroll-margin-top`\n * │ ┌─────────────────────────┐ │▼▲ value of the penultimate message\n * │ │ The penultimate message │ │ │\n * │ └─────────────────────────┘ │ │ B = The height from the top of\n * │ │ │ the penultimate message to the\n * │ ┌─────────────────────────┐ │ │ bottom of the messages list,\n * │ │ The last message │ │ │ including the messages' heights,\n * │ └─────────────────────────┘ │ │ and any padding, gap, etc\n * │ │ │\n * ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤▲▼\n * │ ││ The trailing space needed to\n * │ = container height - (A + B + C) ││ allow the penultimate message\n * │ ││ to be at the top of the viewport\n * ├ ┬─────────────────────────────────────┬ ┤▼▲\n * │ │ │ │ │\n * │ │ │ │ │ C = The footer's height,\n * │ │ │ │ │ including any padding\n * │ └─────────────────────────────────────┘ │ │\n * └─────────────────────────────────────────┘ ▼\n */\n useEffect(\n () => {\n if (!hasLastSentMessage) {\n return;\n }\n\n const container = containerRef.current;\n const footer = footerRef.current;\n const messages = messagesRef.current;\n\n if (!container || !footer || !messages) {\n return;\n }\n\n const trailingSpacer = trailingSpacerRef.current;\n const bottomTrailingMarker = bottomTrailingMarkerRef.current;\n\n let containerHeight: number | null = null;\n let footerHeight: number | null = null;\n let messagesHeight: number | null = null;\n\n const resetTrailingSpace = () => {\n trailingSpacer?.style.removeProperty(\"height\");\n bottomTrailingMarker?.style.removeProperty(\"top\");\n };\n\n const resizeObserver = new ResizeObserver((entries) => {\n if (!trailingSpacer || !bottomTrailingMarker) {\n return;\n }\n\n const lastMessage = messages.lastElementChild;\n const penultimateMessage = lastMessage?.previousElementSibling;\n\n // If there's no last pair of messages, there's no need for any trailing space.\n if (!lastMessage || !penultimateMessage) {\n resetTrailingSpace();\n return;\n }\n\n // If the container's height is based on its content, the container isn't scrollable and there's no need for any trailing space.\n if (container.scrollHeight === container.clientHeight) {\n resetTrailingSpace();\n return;\n }\n\n let updatedContainerHeight: number | null = containerHeight;\n let updatedFooterHeight: number | null = footerHeight;\n let updatedMessagesHeight: number | null = messagesHeight;\n\n for (const entry of entries) {\n const entryHeight =\n entry.borderBoxSize?.[0]?.blockSize ?? entry.contentRect.height;\n\n if (entry.target === container) {\n updatedContainerHeight = entryHeight ?? null;\n } else if (entry.target === footer) {\n updatedFooterHeight = entryHeight ?? null;\n } else if (entry.target === messages) {\n updatedMessagesHeight = entryHeight ?? null;\n }\n }\n\n // If we don't have all the heights, we can't compute the trailing space.\n if (\n updatedContainerHeight === null ||\n updatedFooterHeight === null ||\n updatedMessagesHeight === null\n ) {\n resetTrailingSpace();\n return;\n }\n\n // If none of the heights have changed, we don't need to do anything.\n if (\n updatedContainerHeight === containerHeight &&\n updatedFooterHeight === footerHeight &&\n updatedMessagesHeight === messagesHeight\n ) {\n return;\n }\n\n // Now that we have compared them, we can update the heights.\n containerHeight = updatedContainerHeight;\n footerHeight = updatedFooterHeight;\n messagesHeight = updatedMessagesHeight;\n\n // A\n const penultimateMessageScrollMarginTop = Number.parseFloat(\n getComputedStyle(penultimateMessage as HTMLElement).scrollMarginTop\n );\n\n // B\n const messagesRect = messages.getBoundingClientRect();\n const penultimateMessageRect =\n penultimateMessage.getBoundingClientRect();\n const heightFromPenultimateMessageTopToMessagesListBottom =\n messagesRect.bottom - penultimateMessageRect.top;\n\n // A + B + C\n const differenceHeight =\n penultimateMessageScrollMarginTop +\n heightFromPenultimateMessageTopToMessagesListBottom +\n (footerHeight ?? 0);\n\n // = container height - (A + B + C)\n const trailingSpace = Math.max(containerHeight - differenceHeight, 0);\n\n // Update the trailing space.\n trailingSpacer.style.height = `${trailingSpace}px`;\n\n // Offset what \"the bottom\" is to the \"scroll at the bottom\" detection logic,\n // so that it doesn't include the trailing space.\n bottomTrailingMarker.style.top = `${-trailingSpace}px`;\n });\n\n resizeObserver.observe(container);\n resizeObserver.observe(footer);\n resizeObserver.observe(messages);\n\n return () => {\n resizeObserver.disconnect();\n resetTrailingSpace();\n };\n },\n // This effect only uses stable refs.\n [hasLastSentMessage] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Update the \"scroll at bottom\" state when needed.\n */\n useIntersectionCallback(\n bottomTrailingMarkerRef,\n (isIntersecting) => {\n onScrollAtBottomChange.current(isIntersecting);\n },\n { root: containerRef, rootMargin: MIN_DISTANCE_BOTTOM_SCROLL_INDICATOR }\n );\n\n /**\n * Instantly scroll to the bottom for the initial state.\n */\n useEffect(\n () => {\n scrollToBottom.current(\"instant\");\n },\n // `scrollToBottom` is a stable ref containing the callback.\n [] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Scroll to new messages when sending them.\n */\n useEffect(\n () => {\n if (lastSentMessageId) {\n scrollToBottom.current(\"smooth\", true);\n }\n },\n // `scrollToBottom` is a stable ref containing the callback.\n [lastSentMessageId] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n /**\n * Reset the \"scroll at bottom\" state when the component unmounts.\n */\n useEffect(\n () => {\n const onScrollAtBottomChangeCallback = onScrollAtBottomChange.current;\n\n return () => {\n onScrollAtBottomChangeCallback(null);\n };\n },\n // `onScrollAtBottomChange` is a stable ref containing the callback.\n [] // eslint-disable-line react-hooks/exhaustive-deps\n );\n\n return (\n <div\n className={cn(\"lb-ai-chat-messages\", className)}\n ref={forwardedRef}\n {...props}\n >\n {messages.map((message) => {\n if (message.role === \"user\") {\n return (\n <AiChatUserMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n />\n );\n } else if (message.role === \"assistant\") {\n return (\n <AiChatAssistantMessage\n key={message.id}\n message={message}\n overrides={overrides}\n components={components}\n />\n );\n } else {\n return null;\n }\n })}\n </div>\n );\n }\n);\n\nexport const AiChat = forwardRef<HTMLDivElement, AiChatProps>(\n (\n {\n chatId,\n copilotId,\n autoFocus,\n overrides,\n knowledge: localKnowledge,\n tools = {},\n onComposerSubmit,\n layout = \"inset\",\n components,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const { messages, isLoading, error } = useAiChatMessages(chatId);\n const [lastSentMessageId, setLastSentMessageId] =\n useState<MessageId | null>(null);\n\n const $ = useOverrides(overrides);\n const Empty = components?.Empty ?? defaultComponents.Empty;\n const Loading = components?.Loading ?? defaultComponents.Loading;\n\n const containerRef = useRef<HTMLDivElement | null>(null);\n const messagesRef = useRef<HTMLDivElement | null>(null);\n const footerRef = useRef<HTMLDivElement | null>(null);\n const bottomMarkerRef = useRef<HTMLDivElement | null>(null);\n const bottomTrailingMarkerRef = useRef<HTMLDivElement | null>(null);\n const trailingSpacerRef = useRef<HTMLDivElement | null>(null);\n\n const [isScrollAtBottom, setScrollAtBottom] = useState<boolean | null>(\n null\n );\n // `useState`'s setter is stable but this is for clarity in the places it's used.\n const onScrollAtBottomChange = useLatest(setScrollAtBottom);\n const isScrollIndicatorVisible =\n messages && isScrollAtBottom !== null ? !isScrollAtBottom : false;\n\n useImperativeHandle<HTMLDivElement | null, HTMLDivElement | null>(\n forwardedRef,\n () => containerRef.current,\n []\n );\n\n const scrollToBottom = useLatest(\n (behavior: \"instant\" | \"smooth\", includeTrailingSpace = false) => {\n if (includeTrailingSpace) {\n // Scroll to the bottom marker to include the trailing space,\n // and wait for a frame in case the trailing space hasn't\n // been updated yet. (e.g. when sending a new message)\n requestAnimationFrame(() => {\n bottomMarkerRef.current?.scrollIntoView({\n behavior,\n block: \"end\",\n });\n });\n } else {\n // Scroll to the trailing space marker to only scroll to the\n // bottom of the messages, without including the trailing space.\n bottomTrailingMarkerRef.current?.scrollIntoView({\n behavior,\n block: \"end\",\n });\n }\n }\n );\n\n return (\n <div\n ref={containerRef}\n {...props}\n className={cn(\n \"lb-root lb-ai-chat\",\n `lb-ai-chat:layout-${layout}`,\n className\n )}\n >\n {Object.entries(tools).map(([name, tool]) => (\n <RegisterAiTool key={name} chatId={chatId} name={name} tool={tool} />\n ))}\n\n <div className=\"lb-ai-chat-content\">\n {isLoading ? (\n <Loading />\n ) : error !== undefined ? (\n <div className=\"lb-error lb-ai-chat-error\">\n {$.AI_CHAT_MESSAGES_ERROR(error)}\n </div>\n ) : messages.length === 0 ? (\n <Empty chatId={chatId} copilotId={copilotId} />\n ) : (\n <>\n <AiChatMessages\n ref={messagesRef}\n messages={messages}\n overrides={overrides}\n components={components}\n lastSentMessageId={lastSentMessageId}\n scrollToBottom={scrollToBottom}\n onScrollAtBottomChange={onScrollAtBottomChange}\n containerRef={containerRef}\n footerRef={footerRef}\n messagesRef={messagesRef}\n bottomTrailingMarkerRef={bottomTrailingMarkerRef}\n trailingSpacerRef={trailingSpacerRef}\n />\n\n {/**\n * This trailing spacer is used to extend the scrollable area beyond its actual\n * content, to allow messages to appear at the top of the viewport for example.\n */}\n <div\n ref={trailingSpacerRef}\n data-trailing-spacer=\"\"\n style={{\n pointerEvents: \"none\",\n }}\n aria-hidden\n />\n </>\n )}\n </div>\n\n <div className=\"lb-ai-chat-footer\" ref={footerRef}>\n <div className=\"lb-ai-chat-footer-actions\">\n <div\n className=\"lb-root lb-elevation lb-elevation-moderate lb-ai-chat-scroll-indicator\"\n data-visible={isScrollIndicatorVisible ? \"\" : undefined}\n >\n <button\n className=\"lb-ai-chat-scroll-indicator-button\"\n tabIndex={isScrollIndicatorVisible ? 0 : -1}\n aria-hidden={!isScrollIndicatorVisible}\n onClick={() => scrollToBottom.current(\"smooth\")}\n >\n <span className=\"lb-icon-container\">\n <ArrowDownIcon />\n </span>\n </button>\n </div>\n </div>\n <AiComposer\n key={chatId}\n chatId={chatId}\n copilotId={copilotId as CopilotId}\n overrides={overrides}\n autoFocus={autoFocus}\n knowledge={localKnowledge}\n onComposerSubmit={onComposerSubmit}\n onComposerSubmitted={({ id }) => setLastSentMessageId(id)}\n className={cn(\n \"lb-ai-chat-composer\",\n layout === \"inset\"\n ? \"lb-elevation lb-elevation-moderate\"\n : undefined\n )}\n />\n </div>\n\n {/**\n * This invisible marker is a trick which allows us to use IntersectionObserver to detect when the\n * scrollable area is fully scrolled to the bottom instead of manually tracking the scroll position\n * and having to deal with resizes, etc.\n *\n * It's positioned at the bottom of the scrollable area and reliably only becomes \"visible\" to the\n * IntersectionObserver when the scrollable area is scrolled to the bottom.\n */}\n {messages && messages.length > 0 ? (\n <div\n ref={bottomMarkerRef}\n style={{ position: \"sticky\", height: 0 }}\n aria-hidden\n data-bottom-marker=\"\"\n >\n {/**\n * This inner marker is absolutely offset by the same distance as the trailing space so its\n * visibility means the scrollable area is at the bottom of the messages, not the full bottom.\n */}\n <div\n ref={bottomTrailingMarkerRef}\n style={{\n position: \"absolute\",\n height: 0,\n }}\n data-bottom-trailing-marker=\"\"\n />\n </div>\n ) : null}\n </div>\n );\n }\n);\n"],"names":["messages"],"mappings":"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"}
@@ -68,11 +68,14 @@ function CommentMention({
68
68
  switch (mention.kind) {
69
69
  case "user":
70
70
  return /* @__PURE__ */ jsxRuntime.jsxs(index.Mention, {
71
- className: cn.cn("lb-comment-mention", className),
71
+ className: cn.cn("lb-mention lb-comment-mention", className),
72
72
  "data-self": mention.id === currentId ? "" : void 0,
73
73
  ...props,
74
74
  children: [
75
- constants.MENTION_CHARACTER,
75
+ /* @__PURE__ */ jsxRuntime.jsx("span", {
76
+ className: "lb-mention-symbol",
77
+ children: constants.MENTION_CHARACTER
78
+ }),
76
79
  /* @__PURE__ */ jsxRuntime.jsx(User.User, {
77
80
  userId: mention.id
78
81
  })
@@ -1 +1 @@
1
- {"version":3,"file":"Comment.cjs","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n assertNever,\n type CommentAttachment,\n type CommentData,\n type CommentReaction as CommentReactionData,\n type MentionData,\n Permission,\n} from \"@liveblocks/core\";\nimport {\n useAddRoomCommentReaction,\n useDeleteRoomComment,\n useEditRoomComment,\n useMarkRoomThreadAsRead,\n useRemoveRoomCommentReaction,\n useRoomAttachmentUrl,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n ReactNode,\n RefObject,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n ComponentsProvider,\n type GlobalComponents,\n useComponents,\n} from \"../components\";\nimport { MENTION_CHARACTER } from \"../constants\";\nimport { CheckIcon } from \"../icons/Check\";\nimport { CrossIcon } from \"../icons/Cross\";\nimport { DeleteIcon } from \"../icons/Delete\";\nimport { EditIcon } from \"../icons/Edit\";\nimport { EllipsisIcon } from \"../icons/Ellipsis\";\nimport { EmojiPlusIcon } from \"../icons/EmojiPlus\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ComposerSubmitComment } from \"../primitives\";\nimport * as CommentPrimitive from \"../primitives/Comment\";\nimport type {\n CommentBodyLinkProps,\n CommentBodyMentionProps,\n CommentLinkProps,\n CommentMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport type { CommentAttachmentArgs } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { download } from \"../utils/download\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport {\n FileAttachment,\n MediaAttachment,\n separateMediaAttachments,\n} from \"./internal/Attachment\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button, CustomButton } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { List } from \"./internal/List\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\nconst REACTIONS_TRUNCATE = 5;\n\nexport interface CommentProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The comment to display.\n */\n comment: CommentData;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: boolean | \"hover\";\n\n /**\n * Whether to show the comment if it was deleted. If set to `false`, it will render deleted comments as `null`.\n */\n showDeleted?: boolean;\n\n /**\n * Whether to show reactions.\n */\n showReactions?: boolean;\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * Whether to show the composer's formatting controls when editing the comment.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Whether to indent the comment's content.\n */\n indentContent?: boolean;\n\n /**\n * The event handler called when the comment is edited.\n */\n onCommentEdit?: (comment: CommentData) => void;\n\n /**\n * The event handler called when the comment is deleted.\n */\n onCommentDelete?: (comment: CommentData) => void;\n\n /**\n * The event handler called when clicking on the author.\n */\n onAuthorClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: (\n mention: MentionData,\n event: MouseEvent<HTMLElement>\n ) => void;\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: (\n args: CommentAttachmentArgs,\n event: MouseEvent<HTMLElement>\n ) => void;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n\n /**\n * @internal\n */\n autoMarkReadThreadId?: string;\n\n /**\n * @internal\n */\n additionalActions?: ReactNode;\n\n /**\n * @internal\n */\n additionalDropdownItemsBefore?: ReactNode;\n\n /**\n * @internal\n */\n additionalDropdownItemsAfter?: ReactNode;\n\n /**\n * @internal\n */\n additionalActionsClassName?: string;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\ninterface CommentAttachmentProps extends ComponentProps<typeof FileAttachment> {\n attachment: CommentAttachment;\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n}\n\nexport function CommentMention({\n mention,\n className,\n ...props\n}: CommentBodyMentionProps & CommentMentionProps) {\n const currentId = useCurrentUserId();\n\n switch (mention.kind) {\n case \"user\":\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-comment-mention\", className)}\n data-self={mention.id === currentId ? \"\" : undefined}\n {...props}\n >\n {MENTION_CHARACTER}\n <User userId={mention.id} />\n </CommentPrimitive.Mention>\n );\n\n default:\n return assertNever(mention.kind, \"Unhandled mention kind\");\n }\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n const { Anchor } = useComponents();\n\n return (\n <CommentPrimitive.Link\n className={cn(\"lb-comment-link\", className)}\n href={href}\n {...props}\n asChild\n >\n <Anchor {...props}>{children}</Anchor>\n </CommentPrimitive.Link>\n );\n}\n\nexport function CommentNonInteractiveLink({\n href: _href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <span className={cn(\"lb-comment-link\", className)} {...props}>\n {children}\n </span>\n );\n}\n\nconst CommentReactionButton = forwardRef<\n HTMLButtonElement,\n CommentReactionButtonProps\n>(({ reaction, overrides, className, ...props }, forwardedRef) => {\n const $ = useOverrides(overrides);\n return (\n <CustomButton\n className={cn(\"lb-comment-reaction\", className)}\n variant=\"outline\"\n aria-label={$.COMMENT_REACTION_DESCRIPTION(\n reaction.emoji,\n reaction.users.length\n )}\n {...props}\n ref={forwardedRef}\n >\n <Emoji className=\"lb-comment-reaction-emoji\" emoji={reaction.emoji} />\n <span className=\"lb-comment-reaction-count\">{reaction.users.length}</span>\n </CustomButton>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\nfunction openAttachment({ attachment, url }: CommentAttachmentArgs) {\n // Open the attachment in a new tab if the attachment is a PDF,\n // an image, a video, or audio. Otherwise, download it.\n if (\n attachment.mimeType === \"application/pdf\" ||\n attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\") ||\n attachment.mimeType.startsWith(\"audio/\")\n ) {\n window.open(url, \"_blank\");\n } else {\n download(url, attachment.name);\n }\n}\n\nfunction CommentMediaAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <MediaAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nfunction CommentFileAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nexport function CommentNonInteractiveFileAttachment({\n className,\n ...props\n}: CommentAttachmentProps) {\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n allowMediaPreview={false}\n {...props}\n />\n );\n}\n\n// A void component (which doesn't render anything) responsible for marking a thread\n// as read when the comment it's used in becomes visible.\n// Moving this logic into a separate component allows us to use the visibility\n// and focus hooks \"conditionally\" by conditionally rendering this component.\nfunction AutoMarkReadThreadIdHandler({\n threadId,\n roomId,\n commentRef,\n}: {\n threadId: string;\n roomId: string;\n commentRef: RefObject<HTMLElement>;\n}) {\n const markThreadAsRead = useMarkRoomThreadAsRead(roomId);\n const isWindowFocused = useWindowFocus();\n\n useIntersectionCallback(\n commentRef,\n (isIntersecting) => {\n if (isIntersecting) {\n markThreadAsRead(threadId);\n }\n },\n {\n // The underlying IntersectionObserver is only enabled when the window is focused\n enabled: isWindowFocused,\n }\n );\n\n return null;\n}\n\n/**\n * Displays a single comment.\n *\n * @example\n * <>\n * {thread.comments.map((comment) => (\n * <Comment key={comment.id} comment={comment} />\n * ))}\n * </>\n */\nexport const Comment = forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n showAttachments = true,\n showComposerFormattingControls = true,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onCommentEdit,\n onCommentDelete,\n overrides,\n components,\n className,\n additionalActions,\n additionalActionsClassName,\n additionalDropdownItemsBefore,\n additionalDropdownItemsAfter,\n autoMarkReadThreadId,\n ...props\n },\n forwardedRef\n ) => {\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteRoomComment(comment.roomId);\n const editComment = useEditRoomComment(comment.roomId);\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n const { mediaAttachments, fileAttachments } = useMemo(() => {\n return separateMediaAttachments(comment.attachments);\n }, [comment.attachments]);\n\n const permissions = useRoomPermissions(comment.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleEdit = useCallback(() => {\n setEditing(true);\n }, []);\n\n const handleEditCancel = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n event.stopPropagation();\n setEditing(false);\n },\n []\n );\n\n const handleEditSubmit = useCallback(\n (\n { body, attachments }: ComposerSubmitComment,\n event: FormEvent<HTMLFormElement>\n ) => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentEdit?.(comment);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n event.preventDefault();\n\n setEditing(false);\n editComment({\n commentId: comment.id,\n threadId: comment.threadId,\n body,\n attachments,\n });\n },\n [comment, editComment, onCommentEdit]\n );\n\n const handleDelete = useCallback(() => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentDelete?.(comment);\n\n deleteComment({\n commentId: comment.id,\n threadId: comment.threadId,\n });\n }, [comment, deleteComment, onCommentDelete]);\n\n const handleAuthorClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n onAuthorClick?.(comment.userId, event);\n },\n [comment.userId, onAuthorClick]\n );\n\n const handleReactionSelect = useCallback(\n (emoji: string) => {\n const reactionIndex = comment.reactions.findIndex(\n (reaction) => reaction.emoji === emoji\n );\n\n if (\n reactionIndex >= 0 &&\n currentUserId &&\n comment.reactions[reactionIndex]?.users.some(\n (user) => user.id === currentUserId\n )\n ) {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n } else {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n }\n },\n [\n addReaction,\n comment.id,\n comment.reactions,\n comment.threadId,\n removeReaction,\n currentUserId,\n ]\n );\n\n useEffect(() => {\n const isWindowDefined = typeof window !== \"undefined\";\n if (!isWindowDefined) return;\n\n const hash = window.location.hash;\n const commentId = hash.slice(1);\n\n if (commentId === comment.id) {\n setTarget(true);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!showDeleted && !comment.body) {\n return null;\n }\n\n return (\n <TooltipProvider>\n <ComponentsProvider components={components}>\n {autoMarkReadThreadId && (\n <AutoMarkReadThreadIdHandler\n commentRef={ref}\n threadId={autoMarkReadThreadId}\n roomId={comment.roomId}\n />\n )}\n <div\n id={comment.id}\n className={cn(\n \"lb-root lb-comment\",\n indentContent && \"lb-comment:indent-content\",\n showActions === \"hover\" && \"lb-comment:show-actions-hover\",\n (isMoreActionOpen || isReactionActionOpen) &&\n \"lb-comment:action-open\",\n className\n )}\n data-deleted={!comment.body ? \"\" : undefined}\n data-editing={isEditing ? \"\" : undefined}\n // In some cases, `:target` doesn't work as expected so we also define it manually.\n data-target={isTarget ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-details-labels\">\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-date\">\n <Timestamp\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-date lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n </span>\n </div>\n {showActions && !isEditing && (\n <div\n className={cn(\n \"lb-comment-actions\",\n additionalActionsClassName\n )}\n >\n {additionalActions ?? null}\n {showReactions && canComment ? (\n <EmojiPicker\n onEmojiSelect={handleReactionSelect}\n onOpenChange={setReactionActionOpen}\n >\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n {comment.userId === currentUserId ||\n additionalDropdownItemsBefore ||\n additionalDropdownItemsAfter ? (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={\n <>\n {additionalDropdownItemsBefore}\n {comment.userId === currentUserId && (\n <>\n <DropdownItem\n onSelect={handleEdit}\n onClick={stopPropagation}\n icon={<EditIcon />}\n >\n {$.COMMENT_EDIT}\n </DropdownItem>\n <DropdownItem\n onSelect={handleDelete}\n onClick={stopPropagation}\n icon={<DeleteIcon />}\n >\n {$.COMMENT_DELETE}\n </DropdownItem>\n </>\n )}\n {additionalDropdownItemsAfter}\n </>\n }\n >\n <Tooltip content={$.COMMENT_MORE}>\n <DropdownTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n disabled={!comment.body}\n onClick={stopPropagation}\n aria-label={$.COMMENT_MORE}\n icon={<EllipsisIcon />}\n />\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n ) : null}\n </div>\n )}\n </div>\n <div className=\"lb-comment-content\">\n {isEditing ? (\n <Composer\n className=\"lb-comment-composer\"\n onComposerSubmit={handleEditSubmit}\n defaultValue={comment.body}\n defaultAttachments={comment.attachments}\n autoFocus\n showAttribution={false}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n actions={\n <>\n <Tooltip\n content={$.COMMENT_EDIT_COMPOSER_CANCEL}\n aria-label={$.COMMENT_EDIT_COMPOSER_CANCEL}\n >\n <Button\n className=\"lb-composer-action\"\n onClick={handleEditCancel}\n icon={<CrossIcon />}\n />\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut=\"Enter\"\n >\n <ComposerPrimitive.Submit asChild>\n <Button\n variant=\"primary\"\n className=\"lb-composer-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_EDIT_COMPOSER_SAVE}\n icon={<CheckIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n roomId={comment.roomId}\n />\n ) : comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: ({ mention }) => (\n <CommentMention\n mention={mention}\n onClick={(event) => onMentionClick?.(mention, event)}\n />\n ),\n Link: CommentLink,\n }}\n />\n {showAttachments &&\n (mediaAttachments.length > 0 ||\n fileAttachments.length > 0) ? (\n <div className=\"lb-comment-attachments\">\n {mediaAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {mediaAttachments.map((attachment) => (\n <CommentMediaAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n {fileAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {fileAttachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n disabled={!canComment}\n />\n ))}\n {canComment ? (\n <EmojiPicker onEmojiSelect={handleReactionSelect}>\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-reaction lb-comment-reaction-add\"\n variant=\"outline\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n </div>\n )}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n )}\n </div>\n </div>\n </ComponentsProvider>\n </TooltipProvider>\n );\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyFA;AA4HO;AAAwB;AAC7B;AACA;AAEF;AACE;AAEA;AAAsB;AAElB;AACG;AAC8C;AACF;AACvC;AAEH;AAAA;AACA;AAAqB;AAAI;AAAA;AAC5B;AAIF;AAAyD;AAE/D;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AAEA;AACG;AAC2C;AAC1C;AACI;AACG;AAEN;AAAW;AAAQ;AAAS;AAGnC;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AACG;AAA+C;AAAO;AACpD;AAGP;AAEA;AAIE;AACA;AACG;AAC+C;AACtC;AACM;AACH;AACM;AACjB;AACI;AACC;AAEL;AAAC;AAAgB;AAA4C;AAAO;AACnE;AAAe;AAA4C;AAAO;AAAA;AAGzE;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAElB;AACI;AACA;AAEI;AAAkC;AAAe;AACnD;AACkB;AACT;AACA;AACZ;AACS;AACM;AACjB;AACF;AAEU;AAGd;AACE;AAAsB;AAGxB;AAA4B;AAExB;AACE;AAAY;AACQ;AACC;AACH;AACjB;AAED;AAAe;AACK;AACC;AACH;AACjB;AACH;AACF;AAC0E;AAG5E;AACG;AACU;AACA;AACC;AAET;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEJ;AAC4B;AAC3B;AACA;AACI;AACN;AACF;AAGN;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACG;AACc;AACc;AAC3B;AACA;AACI;AACC;AAGX;AAEA;AAGE;AAME;AAAyB;AAEzB;AAA6B;AAEjC;AAEA;AAAgC;AAC9B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACG;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAGN;AAEA;AAA+B;AAC7B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACG;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAGN;AAEO;AAA6C;AAClD;AAEF;AACE;AACG;AACiD;AAC7B;AACf;AAGV;AAMA;AAAqC;AACnC;AACA;AAEF;AAKE;AACA;AAEA;AAAA;AACE;AAEE;AACE;AAAyB;AAC3B;AACF;AACA;AAEW;AACX;AAGF;AACF;AAYO;AAAgB;AAEnB;AACE;AACgB;AAChB;AACc;AACE;AACE;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAmD;AAGrD;AACA;AAMA;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAMrB;AAEA;AACE;AAAA;AAGF;AACA;AAEA;AACA;AAAY;AACS;AACD;AAClB;AACA;AACD;AACH;AACoC;AAGtC;AAEE;AAEA;AAAc;AACO;AACD;AACnB;AAGH;AAA0B;AAEtB;AAAqC;AACvC;AAC8B;AAGhC;AAA6B;AAEzB;AAAwC;AACL;AAGnC;AAG0C;AAChB;AAGxB;AAAe;AACK;AACC;AACnB;AACD;AAED;AAAY;AACQ;AACC;AACnB;AACD;AACH;AACF;AACA;AACE;AACQ;AACA;AACA;AACR;AACA;AACF;AAGF;AACE;AACA;AAAsB;AAEtB;AACA;AAEA;AACE;AAAc;AAChB;AAGF;AACE;AAAO;AAGT;AACG;AACE;AAAmB;AACjB;AACE;AACa;AACF;AACM;AAClB;AAED;AACa;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACtB;AACH;AACC;AAEL;AAAC;AAAc;AACb;AAAC;AAAc;AACb;AAAC;AACW;AACM;AACP;AACX;AACC;AAAe;AACd;AAAC;AACW;AACM;AACP;AACX;AACC;AAAe;AACd;AAAC;AACW;AACI;AACJ;AACZ;AAEE;AACG;AAAA;AACA;AAAe;AACX;AACL;AAAA;AACF;AAAA;AAEJ;AAAA;AACF;AAAA;AACF;AAEG;AACY;AACT;AACA;AACF;AAEC;AAAqB;AAEnB;AACgB;AACD;AAEb;AAAmB;AACjB;AAA0B;AACxB;AACW;AACD;AACK;AACO;AACvB;AACF;AACF;AAEA;AAID;AACO;AACQ;AACR;AAEJ;AACG;AAAA;AAEC;AACE;AAAC;AACW;AACD;AACO;AAEb;AACL;AACC;AACW;AACD;AACS;AAEf;AACL;AAAA;AACF;AAED;AAAA;AACH;AAGD;AAAmB;AACjB;AAAuB;AACrB;AACW;AACS;AACV;AACK;AACM;AACtB;AACF;AACF;AAEA;AAAA;AACN;AAAA;AAEJ;AACC;AAAc;AAEV;AACW;AACQ;AACI;AACM;AACnB;AACQ;AACjB;AACwB;AAEtB;AACE;AAAC;AACY;AACG;AAEb;AACW;AACD;AACQ;AACnB;AACF;AACC;AACY;AACF;AAER;AAAgC;AAC9B;AACS;AACE;AACD;AACK;AACG;AACnB;AACF;AACF;AAAA;AACF;AAES;AACe;AAC1B;AACgB;AAGlB;AACE;AAAC;AACW;AACI;AACF;AAEP;AACC;AACmD;AACrD;AAEI;AACR;AACF;AAIG;AAAc;AACZ;AACE;AAAc;AAEV;AAEC;AACA;AACA;AACgB;AAEnB;AAED;AAED;AAAc;AAEV;AAEC;AACA;AACA;AACgB;AAEnB;AAED;AAAA;AAEJ;AAED;AAAc;AACZ;AACE;AAEC;AACA;AACA;AACW;AAEd;AAEE;AAA2B;AACzB;AAAmB;AACjB;AAA0B;AACxB;AACW;AACF;AACC;AACK;AACO;AACvB;AACF;AACF;AAEA;AAAA;AACN;AAAA;AAIH;AAAc;AACZ;AAAY;AAAwB;AAAgB;AACvD;AAEJ;AAAA;AACF;AAAA;AACF;AACF;AAGN;;;;;;;;"}
1
+ {"version":3,"file":"Comment.cjs","sources":["../../src/components/Comment.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n assertNever,\n type CommentAttachment,\n type CommentData,\n type CommentReaction as CommentReactionData,\n type MentionData,\n Permission,\n} from \"@liveblocks/core\";\nimport {\n useAddRoomCommentReaction,\n useDeleteRoomComment,\n useEditRoomComment,\n useMarkRoomThreadAsRead,\n useRemoveRoomCommentReaction,\n useRoomAttachmentUrl,\n useRoomPermissions,\n} from \"@liveblocks/react/_private\";\nimport * as TogglePrimitive from \"@radix-ui/react-toggle\";\nimport type {\n ComponentProps,\n ComponentPropsWithoutRef,\n FormEvent,\n MouseEvent,\n ReactNode,\n RefObject,\n SyntheticEvent,\n} from \"react\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport {\n ComponentsProvider,\n type GlobalComponents,\n useComponents,\n} from \"../components\";\nimport { MENTION_CHARACTER } from \"../constants\";\nimport { CheckIcon } from \"../icons/Check\";\nimport { CrossIcon } from \"../icons/Cross\";\nimport { DeleteIcon } from \"../icons/Delete\";\nimport { EditIcon } from \"../icons/Edit\";\nimport { EllipsisIcon } from \"../icons/Ellipsis\";\nimport { EmojiPlusIcon } from \"../icons/EmojiPlus\";\nimport type {\n CommentOverrides,\n ComposerOverrides,\n GlobalOverrides,\n} from \"../overrides\";\nimport { useOverrides } from \"../overrides\";\nimport type { ComposerSubmitComment } from \"../primitives\";\nimport * as CommentPrimitive from \"../primitives/Comment\";\nimport type {\n CommentBodyLinkProps,\n CommentBodyMentionProps,\n CommentLinkProps,\n CommentMentionProps,\n} from \"../primitives/Comment/types\";\nimport * as ComposerPrimitive from \"../primitives/Composer\";\nimport { Timestamp } from \"../primitives/Timestamp\";\nimport { useCurrentUserId } from \"../shared\";\nimport type { CommentAttachmentArgs } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport { download } from \"../utils/download\";\nimport { useRefs } from \"../utils/use-refs\";\nimport { useIntersectionCallback } from \"../utils/use-visible\";\nimport { useWindowFocus } from \"../utils/use-window-focus\";\nimport type { ComposerProps } from \"./Composer\";\nimport { Composer } from \"./Composer\";\nimport {\n FileAttachment,\n MediaAttachment,\n separateMediaAttachments,\n} from \"./internal/Attachment\";\nimport { Avatar } from \"./internal/Avatar\";\nimport { Button, CustomButton } from \"./internal/Button\";\nimport { Dropdown, DropdownItem, DropdownTrigger } from \"./internal/Dropdown\";\nimport { Emoji } from \"./internal/Emoji\";\nimport { EmojiPicker, EmojiPickerTrigger } from \"./internal/EmojiPicker\";\nimport { List } from \"./internal/List\";\nimport { ShortcutTooltip, Tooltip, TooltipProvider } from \"./internal/Tooltip\";\nimport { User } from \"./internal/User\";\n\nconst REACTIONS_TRUNCATE = 5;\n\nexport interface CommentProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The comment to display.\n */\n comment: CommentData;\n\n /**\n * How to show or hide the actions.\n */\n showActions?: boolean | \"hover\";\n\n /**\n * Whether to show the comment if it was deleted. If set to `false`, it will render deleted comments as `null`.\n */\n showDeleted?: boolean;\n\n /**\n * Whether to show reactions.\n */\n showReactions?: boolean;\n\n /**\n * Whether to show attachments.\n */\n showAttachments?: boolean;\n\n /**\n * Whether to show the composer's formatting controls when editing the comment.\n */\n showComposerFormattingControls?: ComposerProps[\"showFormattingControls\"];\n\n /**\n * Whether to indent the comment's content.\n */\n indentContent?: boolean;\n\n /**\n * The event handler called when the comment is edited.\n */\n onCommentEdit?: (comment: CommentData) => void;\n\n /**\n * The event handler called when the comment is deleted.\n */\n onCommentDelete?: (comment: CommentData) => void;\n\n /**\n * The event handler called when clicking on the author.\n */\n onAuthorClick?: (userId: string, event: MouseEvent<HTMLElement>) => void;\n\n /**\n * The event handler called when clicking on a mention.\n */\n onMentionClick?: (\n mention: MentionData,\n event: MouseEvent<HTMLElement>\n ) => void;\n\n /**\n * The event handler called when clicking on a comment's attachment.\n */\n onAttachmentClick?: (\n args: CommentAttachmentArgs,\n event: MouseEvent<HTMLElement>\n ) => void;\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & CommentOverrides & ComposerOverrides>;\n\n /**\n * Override the component's components.\n */\n components?: Partial<GlobalComponents>;\n\n /**\n * @internal\n */\n autoMarkReadThreadId?: string;\n\n /**\n * @internal\n */\n additionalActions?: ReactNode;\n\n /**\n * @internal\n */\n additionalDropdownItemsBefore?: ReactNode;\n\n /**\n * @internal\n */\n additionalDropdownItemsAfter?: ReactNode;\n\n /**\n * @internal\n */\n additionalActionsClassName?: string;\n}\n\ninterface CommentReactionButtonProps\n extends ComponentPropsWithoutRef<typeof Button> {\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ninterface CommentReactionProps extends ComponentPropsWithoutRef<\"button\"> {\n comment: CommentData;\n reaction: CommentReactionData;\n overrides?: Partial<GlobalOverrides & CommentOverrides>;\n}\n\ntype CommentNonInteractiveReactionProps = Omit<CommentReactionProps, \"comment\">;\n\ninterface CommentAttachmentProps extends ComponentProps<typeof FileAttachment> {\n attachment: CommentAttachment;\n onAttachmentClick?: CommentProps[\"onAttachmentClick\"];\n}\n\nexport function CommentMention({\n mention,\n className,\n ...props\n}: CommentBodyMentionProps & CommentMentionProps) {\n const currentId = useCurrentUserId();\n\n switch (mention.kind) {\n case \"user\":\n return (\n <CommentPrimitive.Mention\n className={cn(\"lb-mention lb-comment-mention\", className)}\n data-self={mention.id === currentId ? \"\" : undefined}\n {...props}\n >\n <span className=\"lb-mention-symbol\">{MENTION_CHARACTER}</span>\n <User userId={mention.id} />\n </CommentPrimitive.Mention>\n );\n\n default:\n return assertNever(mention.kind, \"Unhandled mention kind\");\n }\n}\n\nexport function CommentLink({\n href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n const { Anchor } = useComponents();\n\n return (\n <CommentPrimitive.Link\n className={cn(\"lb-comment-link\", className)}\n href={href}\n {...props}\n asChild\n >\n <Anchor {...props}>{children}</Anchor>\n </CommentPrimitive.Link>\n );\n}\n\nexport function CommentNonInteractiveLink({\n href: _href,\n children,\n className,\n ...props\n}: CommentBodyLinkProps & CommentLinkProps) {\n return (\n <span className={cn(\"lb-comment-link\", className)} {...props}>\n {children}\n </span>\n );\n}\n\nconst CommentReactionButton = forwardRef<\n HTMLButtonElement,\n CommentReactionButtonProps\n>(({ reaction, overrides, className, ...props }, forwardedRef) => {\n const $ = useOverrides(overrides);\n return (\n <CustomButton\n className={cn(\"lb-comment-reaction\", className)}\n variant=\"outline\"\n aria-label={$.COMMENT_REACTION_DESCRIPTION(\n reaction.emoji,\n reaction.users.length\n )}\n {...props}\n ref={forwardedRef}\n >\n <Emoji className=\"lb-comment-reaction-emoji\" emoji={reaction.emoji} />\n <span className=\"lb-comment-reaction-count\">{reaction.users.length}</span>\n </CustomButton>\n );\n});\n\nexport const CommentReaction = forwardRef<\n HTMLButtonElement,\n CommentReactionProps\n>(({ comment, reaction, overrides, disabled, ...props }, forwardedRef) => {\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n const $ = useOverrides(overrides);\n const tooltipContent = useMemo(\n () => (\n <span>\n {$.COMMENT_REACTION_LIST(\n <List\n values={reaction.users.map((users) => (\n <User key={users.id} userId={users.id} replaceSelf />\n ))}\n formatRemaining={$.LIST_REMAINING_USERS}\n truncate={REACTIONS_TRUNCATE}\n locale={$.locale}\n />,\n reaction.emoji,\n reaction.users.length\n )}\n </span>\n ),\n [$, reaction]\n );\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handlePressedChange = useCallback(\n (isPressed: boolean) => {\n if (isPressed) {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n } else {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji: reaction.emoji,\n });\n }\n },\n [addReaction, comment.threadId, comment.id, reaction.emoji, removeReaction]\n );\n\n return (\n <Tooltip\n content={tooltipContent}\n multiline\n className=\"lb-comment-reaction-tooltip\"\n >\n <TogglePrimitive.Root\n asChild\n pressed={isActive}\n onPressedChange={handlePressedChange}\n onClick={stopPropagation}\n disabled={disabled}\n ref={forwardedRef}\n >\n <CommentReactionButton\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n />\n </TogglePrimitive.Root>\n </Tooltip>\n );\n});\n\nexport const CommentNonInteractiveReaction = forwardRef<\n HTMLButtonElement,\n CommentNonInteractiveReactionProps\n>(({ reaction, overrides, ...props }, forwardedRef) => {\n const currentId = useCurrentUserId();\n const isActive = useMemo(() => {\n return reaction.users.some((users) => users.id === currentId);\n }, [currentId, reaction]);\n\n return (\n <CommentReactionButton\n disableable={false}\n data-self={isActive ? \"\" : undefined}\n reaction={reaction}\n overrides={overrides}\n {...props}\n ref={forwardedRef}\n />\n );\n});\n\nfunction openAttachment({ attachment, url }: CommentAttachmentArgs) {\n // Open the attachment in a new tab if the attachment is a PDF,\n // an image, a video, or audio. Otherwise, download it.\n if (\n attachment.mimeType === \"application/pdf\" ||\n attachment.mimeType.startsWith(\"image/\") ||\n attachment.mimeType.startsWith(\"video/\") ||\n attachment.mimeType.startsWith(\"audio/\")\n ) {\n window.open(url, \"_blank\");\n } else {\n download(url, attachment.name);\n }\n}\n\nfunction CommentMediaAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <MediaAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nfunction CommentFileAttachment({\n attachment,\n onAttachmentClick,\n roomId,\n className,\n overrides,\n ...props\n}: CommentAttachmentProps & {\n roomId: string;\n}) {\n const { url } = useRoomAttachmentUrl(attachment.id, roomId);\n\n const handleClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n if (!url) {\n return;\n }\n\n const args: CommentAttachmentArgs = { attachment, url };\n\n onAttachmentClick?.(args, event);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n openAttachment(args);\n },\n [attachment, onAttachmentClick, url]\n );\n\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n {...props}\n attachment={attachment}\n overrides={overrides}\n onClick={url ? handleClick : undefined}\n roomId={roomId}\n />\n );\n}\n\nexport function CommentNonInteractiveFileAttachment({\n className,\n ...props\n}: CommentAttachmentProps) {\n return (\n <FileAttachment\n className={cn(\"lb-comment-attachment\", className)}\n allowMediaPreview={false}\n {...props}\n />\n );\n}\n\n// A void component (which doesn't render anything) responsible for marking a thread\n// as read when the comment it's used in becomes visible.\n// Moving this logic into a separate component allows us to use the visibility\n// and focus hooks \"conditionally\" by conditionally rendering this component.\nfunction AutoMarkReadThreadIdHandler({\n threadId,\n roomId,\n commentRef,\n}: {\n threadId: string;\n roomId: string;\n commentRef: RefObject<HTMLElement>;\n}) {\n const markThreadAsRead = useMarkRoomThreadAsRead(roomId);\n const isWindowFocused = useWindowFocus();\n\n useIntersectionCallback(\n commentRef,\n (isIntersecting) => {\n if (isIntersecting) {\n markThreadAsRead(threadId);\n }\n },\n {\n // The underlying IntersectionObserver is only enabled when the window is focused\n enabled: isWindowFocused,\n }\n );\n\n return null;\n}\n\n/**\n * Displays a single comment.\n *\n * @example\n * <>\n * {thread.comments.map((comment) => (\n * <Comment key={comment.id} comment={comment} />\n * ))}\n * </>\n */\nexport const Comment = forwardRef<HTMLDivElement, CommentProps>(\n (\n {\n comment,\n indentContent = true,\n showDeleted,\n showActions = \"hover\",\n showReactions = true,\n showAttachments = true,\n showComposerFormattingControls = true,\n onAuthorClick,\n onMentionClick,\n onAttachmentClick,\n onCommentEdit,\n onCommentDelete,\n overrides,\n components,\n className,\n additionalActions,\n additionalActionsClassName,\n additionalDropdownItemsBefore,\n additionalDropdownItemsAfter,\n autoMarkReadThreadId,\n ...props\n },\n forwardedRef\n ) => {\n const ref = useRef<HTMLDivElement>(null);\n const mergedRefs = useRefs(forwardedRef, ref);\n const currentUserId = useCurrentUserId();\n const deleteComment = useDeleteRoomComment(comment.roomId);\n const editComment = useEditRoomComment(comment.roomId);\n const addReaction = useAddRoomCommentReaction(comment.roomId);\n const removeReaction = useRemoveRoomCommentReaction(comment.roomId);\n const $ = useOverrides(overrides);\n const [isEditing, setEditing] = useState(false);\n const [isTarget, setTarget] = useState(false);\n const [isMoreActionOpen, setMoreActionOpen] = useState(false);\n const [isReactionActionOpen, setReactionActionOpen] = useState(false);\n const { mediaAttachments, fileAttachments } = useMemo(() => {\n return separateMediaAttachments(comment.attachments);\n }, [comment.attachments]);\n\n const permissions = useRoomPermissions(comment.roomId);\n const canComment =\n permissions.size > 0\n ? permissions.has(Permission.CommentsWrite) ||\n permissions.has(Permission.Write)\n : true;\n\n const stopPropagation = useCallback((event: SyntheticEvent) => {\n event.stopPropagation();\n }, []);\n\n const handleEdit = useCallback(() => {\n setEditing(true);\n }, []);\n\n const handleEditCancel = useCallback(\n (event: MouseEvent<HTMLButtonElement>) => {\n event.stopPropagation();\n setEditing(false);\n },\n []\n );\n\n const handleEditSubmit = useCallback(\n (\n { body, attachments }: ComposerSubmitComment,\n event: FormEvent<HTMLFormElement>\n ) => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentEdit?.(comment);\n\n if (event.isDefaultPrevented()) {\n return;\n }\n\n event.stopPropagation();\n event.preventDefault();\n\n setEditing(false);\n editComment({\n commentId: comment.id,\n threadId: comment.threadId,\n body,\n attachments,\n });\n },\n [comment, editComment, onCommentEdit]\n );\n\n const handleDelete = useCallback(() => {\n // TODO: Add a way to preventDefault from within this callback, to override the default behavior (e.g. showing a confirmation dialog)\n onCommentDelete?.(comment);\n\n deleteComment({\n commentId: comment.id,\n threadId: comment.threadId,\n });\n }, [comment, deleteComment, onCommentDelete]);\n\n const handleAuthorClick = useCallback(\n (event: MouseEvent<HTMLElement>) => {\n onAuthorClick?.(comment.userId, event);\n },\n [comment.userId, onAuthorClick]\n );\n\n const handleReactionSelect = useCallback(\n (emoji: string) => {\n const reactionIndex = comment.reactions.findIndex(\n (reaction) => reaction.emoji === emoji\n );\n\n if (\n reactionIndex >= 0 &&\n currentUserId &&\n comment.reactions[reactionIndex]?.users.some(\n (user) => user.id === currentUserId\n )\n ) {\n removeReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n } else {\n addReaction({\n threadId: comment.threadId,\n commentId: comment.id,\n emoji,\n });\n }\n },\n [\n addReaction,\n comment.id,\n comment.reactions,\n comment.threadId,\n removeReaction,\n currentUserId,\n ]\n );\n\n useEffect(() => {\n const isWindowDefined = typeof window !== \"undefined\";\n if (!isWindowDefined) return;\n\n const hash = window.location.hash;\n const commentId = hash.slice(1);\n\n if (commentId === comment.id) {\n setTarget(true);\n }\n }, []); // eslint-disable-line react-hooks/exhaustive-deps\n\n if (!showDeleted && !comment.body) {\n return null;\n }\n\n return (\n <TooltipProvider>\n <ComponentsProvider components={components}>\n {autoMarkReadThreadId && (\n <AutoMarkReadThreadIdHandler\n commentRef={ref}\n threadId={autoMarkReadThreadId}\n roomId={comment.roomId}\n />\n )}\n <div\n id={comment.id}\n className={cn(\n \"lb-root lb-comment\",\n indentContent && \"lb-comment:indent-content\",\n showActions === \"hover\" && \"lb-comment:show-actions-hover\",\n (isMoreActionOpen || isReactionActionOpen) &&\n \"lb-comment:action-open\",\n className\n )}\n data-deleted={!comment.body ? \"\" : undefined}\n data-editing={isEditing ? \"\" : undefined}\n // In some cases, `:target` doesn't work as expected so we also define it manually.\n data-target={isTarget ? \"\" : undefined}\n dir={$.dir}\n {...props}\n ref={mergedRefs}\n >\n <div className=\"lb-comment-header\">\n <div className=\"lb-comment-details\">\n <Avatar\n className=\"lb-comment-avatar\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-details-labels\">\n <User\n className=\"lb-comment-author\"\n userId={comment.userId}\n onClick={handleAuthorClick}\n />\n <span className=\"lb-comment-date\">\n <Timestamp\n locale={$.locale}\n date={comment.createdAt}\n className=\"lb-date lb-comment-date-created\"\n />\n {comment.editedAt && comment.body && (\n <>\n {\" \"}\n <span className=\"lb-comment-date-edited\">\n {$.COMMENT_EDITED}\n </span>\n </>\n )}\n </span>\n </span>\n </div>\n {showActions && !isEditing && (\n <div\n className={cn(\n \"lb-comment-actions\",\n additionalActionsClassName\n )}\n >\n {additionalActions ?? null}\n {showReactions && canComment ? (\n <EmojiPicker\n onEmojiSelect={handleReactionSelect}\n onOpenChange={setReactionActionOpen}\n >\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n {comment.userId === currentUserId ||\n additionalDropdownItemsBefore ||\n additionalDropdownItemsAfter ? (\n <Dropdown\n open={isMoreActionOpen}\n onOpenChange={setMoreActionOpen}\n align=\"end\"\n content={\n <>\n {additionalDropdownItemsBefore}\n {comment.userId === currentUserId && (\n <>\n <DropdownItem\n onSelect={handleEdit}\n onClick={stopPropagation}\n icon={<EditIcon />}\n >\n {$.COMMENT_EDIT}\n </DropdownItem>\n <DropdownItem\n onSelect={handleDelete}\n onClick={stopPropagation}\n icon={<DeleteIcon />}\n >\n {$.COMMENT_DELETE}\n </DropdownItem>\n </>\n )}\n {additionalDropdownItemsAfter}\n </>\n }\n >\n <Tooltip content={$.COMMENT_MORE}>\n <DropdownTrigger asChild>\n <Button\n className=\"lb-comment-action\"\n disabled={!comment.body}\n onClick={stopPropagation}\n aria-label={$.COMMENT_MORE}\n icon={<EllipsisIcon />}\n />\n </DropdownTrigger>\n </Tooltip>\n </Dropdown>\n ) : null}\n </div>\n )}\n </div>\n <div className=\"lb-comment-content\">\n {isEditing ? (\n <Composer\n className=\"lb-comment-composer\"\n onComposerSubmit={handleEditSubmit}\n defaultValue={comment.body}\n defaultAttachments={comment.attachments}\n autoFocus\n showAttribution={false}\n showAttachments={showAttachments}\n showFormattingControls={showComposerFormattingControls}\n actions={\n <>\n <Tooltip\n content={$.COMMENT_EDIT_COMPOSER_CANCEL}\n aria-label={$.COMMENT_EDIT_COMPOSER_CANCEL}\n >\n <Button\n className=\"lb-composer-action\"\n onClick={handleEditCancel}\n icon={<CrossIcon />}\n />\n </Tooltip>\n <ShortcutTooltip\n content={$.COMMENT_EDIT_COMPOSER_SAVE}\n shortcut=\"Enter\"\n >\n <ComposerPrimitive.Submit asChild>\n <Button\n variant=\"primary\"\n className=\"lb-composer-action\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_EDIT_COMPOSER_SAVE}\n icon={<CheckIcon />}\n />\n </ComposerPrimitive.Submit>\n </ShortcutTooltip>\n </>\n }\n overrides={{\n COMPOSER_PLACEHOLDER: $.COMMENT_EDIT_COMPOSER_PLACEHOLDER,\n }}\n roomId={comment.roomId}\n />\n ) : comment.body ? (\n <>\n <CommentPrimitive.Body\n className=\"lb-comment-body\"\n body={comment.body}\n components={{\n Mention: ({ mention }) => (\n <CommentMention\n mention={mention}\n onClick={(event) => onMentionClick?.(mention, event)}\n />\n ),\n Link: CommentLink,\n }}\n />\n {showAttachments &&\n (mediaAttachments.length > 0 ||\n fileAttachments.length > 0) ? (\n <div className=\"lb-comment-attachments\">\n {mediaAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {mediaAttachments.map((attachment) => (\n <CommentMediaAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n {fileAttachments.length > 0 ? (\n <div className=\"lb-attachments\">\n {fileAttachments.map((attachment) => (\n <CommentFileAttachment\n key={attachment.id}\n attachment={attachment}\n overrides={overrides}\n onAttachmentClick={onAttachmentClick}\n roomId={comment.roomId}\n />\n ))}\n </div>\n ) : null}\n </div>\n ) : null}\n {showReactions && comment.reactions.length > 0 && (\n <div className=\"lb-comment-reactions\">\n {comment.reactions.map((reaction) => (\n <CommentReaction\n key={reaction.emoji}\n comment={comment}\n reaction={reaction}\n overrides={overrides}\n disabled={!canComment}\n />\n ))}\n {canComment ? (\n <EmojiPicker onEmojiSelect={handleReactionSelect}>\n <Tooltip content={$.COMMENT_ADD_REACTION}>\n <EmojiPickerTrigger asChild>\n <Button\n className=\"lb-comment-reaction lb-comment-reaction-add\"\n variant=\"outline\"\n onClick={stopPropagation}\n aria-label={$.COMMENT_ADD_REACTION}\n icon={<EmojiPlusIcon />}\n />\n </EmojiPickerTrigger>\n </Tooltip>\n </EmojiPicker>\n ) : null}\n </div>\n )}\n </>\n ) : (\n <div className=\"lb-comment-body\">\n <p className=\"lb-comment-deleted\">{$.COMMENT_DELETED}</p>\n </div>\n )}\n </div>\n </div>\n </ComponentsProvider>\n </TooltipProvider>\n );\n }\n);\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyFA;AA4HO;AAAwB;AAC7B;AACA;AAEF;AACE;AAEA;AAAsB;AAElB;AACG;AACyD;AACb;AACvC;AAEJ;AAAC;AAAe;AAAqB;AAAkB;AACtD;AAAqB;AAAI;AAAA;AAC5B;AAIF;AAAyD;AAE/D;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AAEA;AACG;AAC2C;AAC1C;AACI;AACG;AAEN;AAAW;AAAQ;AAAS;AAGnC;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AACG;AAA+C;AAAO;AACpD;AAGP;AAEA;AAIE;AACA;AACG;AAC+C;AACtC;AACM;AACH;AACM;AACjB;AACI;AACC;AAEL;AAAC;AAAgB;AAA4C;AAAO;AACnE;AAAe;AAA4C;AAAO;AAAA;AAGzE;AAEa;AAIX;AACA;AACA;AACA;AACE;AAA4D;AAE9D;AACA;AAAuB;AAElB;AACI;AACA;AAEI;AAAkC;AAAe;AACnD;AACkB;AACT;AACA;AACZ;AACS;AACM;AACjB;AACF;AAEU;AAGd;AACE;AAAsB;AAGxB;AAA4B;AAExB;AACE;AAAY;AACQ;AACC;AACH;AACjB;AAED;AAAe;AACK;AACC;AACH;AACjB;AACH;AACF;AAC0E;AAG5E;AACG;AACU;AACA;AACC;AAET;AACQ;AACE;AACQ;AACR;AACT;AACK;AAEJ;AAC4B;AAC3B;AACA;AACI;AACN;AACF;AAGN;AAEa;AAIX;AACA;AACE;AAA4D;AAG9D;AACG;AACc;AACc;AAC3B;AACA;AACI;AACC;AAGX;AAEA;AAGE;AAME;AAAyB;AAEzB;AAA6B;AAEjC;AAEA;AAAgC;AAC9B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACG;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAGN;AAEA;AAA+B;AAC7B;AACA;AACA;AACA;AACA;AAEF;AAGE;AAEA;AAAoB;AAEhB;AACE;AAAA;AAGF;AAEA;AAEA;AACE;AAAA;AAGF;AAAmB;AACrB;AACmC;AAGrC;AACG;AACiD;AAC5C;AACJ;AACA;AAC6B;AAC7B;AAGN;AAEO;AAA6C;AAClD;AAEF;AACE;AACG;AACiD;AAC7B;AACf;AAGV;AAMA;AAAqC;AACnC;AACA;AAEF;AAKE;AACA;AAEA;AAAA;AACE;AAEE;AACE;AAAyB;AAC3B;AACF;AACA;AAEW;AACX;AAGF;AACF;AAYO;AAAgB;AAEnB;AACE;AACgB;AAChB;AACc;AACE;AACE;AACe;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACG;AAIL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACE;AAAmD;AAGrD;AACA;AAMA;AACE;AAAsB;AAGxB;AACE;AAAe;AAGjB;AAAyB;AAErB;AACA;AAAgB;AAClB;AACC;AAGH;AAAyB;AAMrB;AAEA;AACE;AAAA;AAGF;AACA;AAEA;AACA;AAAY;AACS;AACD;AAClB;AACA;AACD;AACH;AACoC;AAGtC;AAEE;AAEA;AAAc;AACO;AACD;AACnB;AAGH;AAA0B;AAEtB;AAAqC;AACvC;AAC8B;AAGhC;AAA6B;AAEzB;AAAwC;AACL;AAGnC;AAG0C;AAChB;AAGxB;AAAe;AACK;AACC;AACnB;AACD;AAED;AAAY;AACQ;AACC;AACnB;AACD;AACH;AACF;AACA;AACE;AACQ;AACA;AACA;AACR;AACA;AACF;AAGF;AACE;AACA;AAAsB;AAEtB;AACA;AAEA;AACE;AAAc;AAChB;AAGF;AACE;AAAO;AAGT;AACG;AACE;AAAmB;AACjB;AACE;AACa;AACF;AACM;AAClB;AAED;AACa;AACD;AACT;AACiB;AACU;AAEzB;AACF;AACF;AACmC;AACJ;AAEF;AACtB;AACH;AACC;AAEL;AAAC;AAAc;AACb;AAAC;AAAc;AACb;AAAC;AACW;AACM;AACP;AACX;AACC;AAAe;AACd;AAAC;AACW;AACM;AACP;AACX;AACC;AAAe;AACd;AAAC;AACW;AACI;AACJ;AACZ;AAEE;AACG;AAAA;AACA;AAAe;AACX;AACL;AAAA;AACF;AAAA;AAEJ;AAAA;AACF;AAAA;AACF;AAEG;AACY;AACT;AACA;AACF;AAEC;AAAqB;AAEnB;AACgB;AACD;AAEb;AAAmB;AACjB;AAA0B;AACxB;AACW;AACD;AACK;AACO;AACvB;AACF;AACF;AAEA;AAID;AACO;AACQ;AACR;AAEJ;AACG;AAAA;AAEC;AACE;AAAC;AACW;AACD;AACO;AAEb;AACL;AACC;AACW;AACD;AACS;AAEf;AACL;AAAA;AACF;AAED;AAAA;AACH;AAGD;AAAmB;AACjB;AAAuB;AACrB;AACW;AACS;AACV;AACK;AACM;AACtB;AACF;AACF;AAEA;AAAA;AACN;AAAA;AAEJ;AACC;AAAc;AAEV;AACW;AACQ;AACI;AACM;AACnB;AACQ;AACjB;AACwB;AAEtB;AACE;AAAC;AACY;AACG;AAEb;AACW;AACD;AACQ;AACnB;AACF;AACC;AACY;AACF;AAER;AAAgC;AAC9B;AACS;AACE;AACD;AACK;AACG;AACnB;AACF;AACF;AAAA;AACF;AAES;AACe;AAC1B;AACgB;AAGlB;AACE;AAAC;AACW;AACI;AACF;AAEP;AACC;AACmD;AACrD;AAEI;AACR;AACF;AAIG;AAAc;AACZ;AACE;AAAc;AAEV;AAEC;AACA;AACA;AACgB;AAEnB;AAED;AAED;AAAc;AAEV;AAEC;AACA;AACA;AACgB;AAEnB;AAED;AAAA;AAEJ;AAED;AAAc;AACZ;AACE;AAEC;AACA;AACA;AACW;AAEd;AAEE;AAA2B;AACzB;AAAmB;AACjB;AAA0B;AACxB;AACW;AACF;AACC;AACK;AACO;AACvB;AACF;AACF;AAEA;AAAA;AACN;AAAA;AAIH;AAAc;AACZ;AAAY;AAAwB;AAAgB;AACvD;AAEJ;AAAA;AACF;AAAA;AACF;AACF;AAGN;;;;;;;;"}
@@ -47,11 +47,14 @@ function CommentMention({
47
47
  switch (mention.kind) {
48
48
  case "user":
49
49
  return /* @__PURE__ */ jsxs(CommentMention$1, {
50
- className: cn("lb-comment-mention", className),
50
+ className: cn("lb-mention lb-comment-mention", className),
51
51
  "data-self": mention.id === currentId ? "" : void 0,
52
52
  ...props,
53
53
  children: [
54
- MENTION_CHARACTER,
54
+ /* @__PURE__ */ jsx("span", {
55
+ className: "lb-mention-symbol",
56
+ children: MENTION_CHARACTER
57
+ }),
55
58
  /* @__PURE__ */ jsx(User, {
56
59
  userId: mention.id
57
60
  })