@liveblocks/react-ui 3.0.0 → 3.1.0-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_private/index.cjs +4 -0
- package/dist/_private/index.cjs.map +1 -1
- package/dist/_private/index.d.cts +15 -2
- package/dist/_private/index.d.ts +15 -2
- package/dist/_private/index.js +2 -0
- package/dist/_private/index.js.map +1 -1
- package/dist/components/AiChat.cjs +2 -2
- package/dist/components/AiChat.cjs.map +1 -1
- package/dist/components/AiChat.js +2 -2
- package/dist/components/AiChat.js.map +1 -1
- package/dist/components/AiTool.cjs +9 -7
- package/dist/components/AiTool.cjs.map +1 -1
- package/dist/components/AiTool.js +9 -7
- package/dist/components/AiTool.js.map +1 -1
- package/dist/components/Comment.cjs +10 -10
- package/dist/components/Comment.cjs.map +1 -1
- package/dist/components/Comment.js +10 -10
- package/dist/components/Comment.js.map +1 -1
- package/dist/components/Composer.cjs +7 -10
- package/dist/components/Composer.cjs.map +1 -1
- package/dist/components/Composer.js +7 -10
- package/dist/components/Composer.js.map +1 -1
- package/dist/components/HistoryVersionSummary.cjs +2 -2
- package/dist/components/HistoryVersionSummary.cjs.map +1 -1
- package/dist/components/HistoryVersionSummary.js +2 -2
- package/dist/components/HistoryVersionSummary.js.map +1 -1
- package/dist/components/HistoryVersionSummaryList.cjs +2 -5
- package/dist/components/HistoryVersionSummaryList.cjs.map +1 -1
- package/dist/components/HistoryVersionSummaryList.js +2 -5
- package/dist/components/HistoryVersionSummaryList.js.map +1 -1
- package/dist/components/InboxNotification.cjs +4 -4
- package/dist/components/InboxNotification.cjs.map +1 -1
- package/dist/components/InboxNotification.js +4 -4
- package/dist/components/InboxNotification.js.map +1 -1
- package/dist/components/InboxNotificationList.cjs +2 -2
- package/dist/components/InboxNotificationList.cjs.map +1 -1
- package/dist/components/InboxNotificationList.js +2 -2
- package/dist/components/InboxNotificationList.js.map +1 -1
- package/dist/components/Thread.cjs +2 -2
- package/dist/components/Thread.cjs.map +1 -1
- package/dist/components/Thread.js +2 -2
- package/dist/components/Thread.js.map +1 -1
- package/dist/components/internal/AiChatAssistantMessage.cjs +9 -4
- package/dist/components/internal/AiChatAssistantMessage.cjs.map +1 -1
- package/dist/components/internal/AiChatAssistantMessage.js +10 -5
- package/dist/components/internal/AiChatAssistantMessage.js.map +1 -1
- package/dist/components/internal/AiChatComposer.cjs +2 -2
- package/dist/components/internal/AiChatComposer.cjs.map +1 -1
- package/dist/components/internal/AiChatComposer.js +2 -2
- package/dist/components/internal/AiChatComposer.js.map +1 -1
- package/dist/components/internal/AiChatUserMessage.cjs +2 -2
- package/dist/components/internal/AiChatUserMessage.cjs.map +1 -1
- package/dist/components/internal/AiChatUserMessage.js +2 -2
- package/dist/components/internal/AiChatUserMessage.js.map +1 -1
- package/dist/components/internal/Attachment.cjs +3 -3
- package/dist/components/internal/Attachment.cjs.map +1 -1
- package/dist/components/internal/Attachment.js +3 -3
- package/dist/components/internal/Attachment.js.map +1 -1
- package/dist/components/internal/Attribution.cjs +2 -2
- package/dist/components/internal/Attribution.cjs.map +1 -1
- package/dist/components/internal/Attribution.js +2 -2
- package/dist/components/internal/Attribution.js.map +1 -1
- package/dist/components/internal/Avatar.cjs +2 -2
- package/dist/components/internal/Avatar.cjs.map +1 -1
- package/dist/components/internal/Avatar.js +2 -2
- package/dist/components/internal/Avatar.js.map +1 -1
- package/dist/components/internal/Button.cjs +3 -3
- package/dist/components/internal/Button.cjs.map +1 -1
- package/dist/components/internal/Button.js +3 -3
- package/dist/components/internal/Button.js.map +1 -1
- package/dist/components/internal/Dropdown.cjs +3 -3
- package/dist/components/internal/Dropdown.cjs.map +1 -1
- package/dist/components/internal/Dropdown.js +3 -3
- package/dist/components/internal/Dropdown.js.map +1 -1
- package/dist/components/internal/Emoji.cjs +2 -2
- package/dist/components/internal/Emoji.cjs.map +1 -1
- package/dist/components/internal/Emoji.js +2 -2
- package/dist/components/internal/Emoji.js.map +1 -1
- package/dist/components/internal/EmojiPicker.cjs +5 -5
- package/dist/components/internal/EmojiPicker.cjs.map +1 -1
- package/dist/components/internal/EmojiPicker.js +5 -5
- package/dist/components/internal/EmojiPicker.js.map +1 -1
- package/dist/components/internal/Icon.cjs +2 -2
- package/dist/components/internal/Icon.cjs.map +1 -1
- package/dist/components/internal/Icon.js +2 -2
- package/dist/components/internal/Icon.js.map +1 -1
- package/dist/components/internal/InboxNotificationThread.cjs +2 -2
- package/dist/components/internal/InboxNotificationThread.cjs.map +1 -1
- package/dist/components/internal/InboxNotificationThread.js +2 -2
- package/dist/components/internal/InboxNotificationThread.js.map +1 -1
- package/dist/components/internal/List.cjs +2 -2
- package/dist/components/internal/List.cjs.map +1 -1
- package/dist/components/internal/List.js +2 -2
- package/dist/components/internal/List.js.map +1 -1
- package/dist/components/internal/Prose.cjs +2 -2
- package/dist/components/internal/Prose.cjs.map +1 -1
- package/dist/components/internal/Prose.js +2 -2
- package/dist/components/internal/Prose.js.map +1 -1
- package/dist/components/internal/Room.cjs +2 -2
- package/dist/components/internal/Room.cjs.map +1 -1
- package/dist/components/internal/Room.js +2 -2
- package/dist/components/internal/Room.js.map +1 -1
- package/dist/components/internal/Tooltip.cjs +2 -2
- package/dist/components/internal/Tooltip.cjs.map +1 -1
- package/dist/components/internal/Tooltip.js +2 -2
- package/dist/components/internal/Tooltip.js.map +1 -1
- package/dist/components/internal/User.cjs +2 -2
- package/dist/components/internal/User.cjs.map +1 -1
- package/dist/components/internal/User.js +2 -2
- package/dist/components/internal/User.js.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/utils/{class-names.cjs → cn.cjs} +3 -3
- package/dist/utils/cn.cjs.map +1 -0
- package/dist/utils/{class-names.js → cn.js} +3 -3
- package/dist/utils/cn.js.map +1 -0
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +4 -4
- package/src/styles/index.css +4 -3
- package/styles.css +1 -1
- package/styles.css.map +1 -1
- package/dist/utils/class-names.cjs.map +0 -1
- package/dist/utils/class-names.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiTool.js","sources":["../../src/components/AiTool.tsx"],"sourcesContent":["import type {\n AiToolExecuteCallback,\n AiToolTypePack,\n JsonObject,\n NoInfr,\n} from \"@liveblocks/core\";\nimport { kInternal } from \"@liveblocks/core\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { Children, forwardRef, useCallback, useMemo } from \"react\";\n\nimport { Button } from \"../_private\";\nimport {\n CheckCircleFillIcon,\n ChevronRightIcon,\n CrossCircleFillIcon,\n MinusCircleIcon,\n SpinnerIcon,\n} from \"../icons\";\nimport {\n type AiToolConfirmationOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport { useAiToolInvocationContext } from \"../primitives/AiMessage/contexts\";\nimport * as Collapsible from \"../primitives/Collapsible\";\nimport { classNames } from \"../utils/class-names\";\nimport { useSemiControllableState } from \"../utils/use-controllable-state\";\nimport { CodeBlock } from \"./internal/CodeBlock\";\n\nexport interface AiToolProps\n extends Omit<ComponentProps<\"div\">, \"title\" | \"children\"> {\n /**\n * The tool's title.\n *\n * By default, a human-readable version of the tool's name is used:\n * - `\"showTodo\"` → \"Show todo\"\n * - `\"get_weather\"` → \"Get weather\"\n */\n title?: string;\n\n /**\n * An optional icon displayed next to the title.\n */\n icon?: ReactNode;\n\n /**\n * The content shown in the tool.\n */\n children?: ReactNode;\n\n /**\n * Whether the content is currently collapsed.\n * It is not a traditional controlled value, as in if you set it to `true` it would only stay expanded.\n * Instead, it is \"semi-controlled\", meaning that setting it to `true` will expand it, but it\n * can still be collapsed/expanded by clicking on it.\n */\n collapsed?: boolean;\n\n /**\n * The event handler called when the content is collapsed or expanded by clicking on it.\n */\n onCollapsedChange?: (collapsed: boolean) => void;\n}\n\nexport type AiToolIconProps = ComponentProps<\"div\">;\n\nexport type AiToolInspectorProps = ComponentProps<\"div\">;\n\nexport interface AiToolConfirmationProps<\n A extends JsonObject,\n R extends JsonObject,\n> extends ComponentProps<\"div\"> {\n /**\n * The callback invoked when the user clicks the confirm button.\n */\n confirm: AiToolExecuteCallback<A, R>;\n\n /**\n * The callback invoked when the user clicks the cancel button.\n */\n cancel?: AiToolExecuteCallback<A, R>;\n\n /**\n * The visual appearance.\n */\n variant?: \"default\" | \"destructive\";\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiToolConfirmationOverrides>;\n\n /**\n * The tool's result type, to be used with the `types` prop in the `render` method.\n *\n * @example\n * defineAiTool<{ value: number }>()({\n * // ...\n * render: ({ types }) => (\n * <AiTool.Confirmation\n * types={types}\n * confirm={() => {\n * return {\n * // Using `types` makes the result type-safe\n * // based on the tool's definition\n * data: { value: 123 },\n * };\n * }}\n * />\n * ),\n * })\n */\n types?: NoInfr<AiToolTypePack<A, R>>;\n}\n\nfunction AiToolIcon({ className, ...props }: AiToolIconProps) {\n return (\n <div className={classNames(\"lb-ai-tool-icon\", className)} {...props} />\n );\n}\n\nfunction AiToolInspector({ className, ...props }: AiToolInspectorProps) {\n const { args, partialArgs, result } = useAiToolInvocationContext();\n\n return (\n <div className={classNames(\"lb-ai-tool-inspector\", className)} {...props}>\n <CodeBlock\n title=\"Arguments\"\n code={JSON.stringify(args ?? partialArgs, null, 2)}\n />\n {result !== undefined ? (\n <CodeBlock title=\"Result\" code={JSON.stringify(result, null, 2)} />\n ) : null}\n </div>\n );\n}\n\nfunction AiToolConfirmation<\n TPack extends AiToolTypePack,\n A extends JsonObject = TPack[\"A\"],\n R extends JsonObject = TPack[\"R\"],\n>({\n children,\n variant = \"default\",\n confirm,\n cancel,\n overrides,\n className,\n ...props\n}: AiToolConfirmationProps<A, R>) {\n const { stage, args, respond, name, invocationId } =\n useAiToolInvocationContext();\n const $ = useOverrides(overrides);\n\n const enabled = stage === \"executing\";\n\n const context = useMemo(() => ({ name, invocationId }), [name, invocationId]);\n\n const onConfirmClick = useCallback(async () => {\n if (enabled) {\n const result = await confirm(args as A, context);\n respond(result ?? undefined);\n }\n }, [enabled, args, confirm, respond, context]);\n\n const onCancelClick = useCallback(async () => {\n if (enabled) {\n if (cancel === undefined) {\n respond({ cancel: true });\n } else {\n const result = await cancel(args as A, context);\n respond(result ?? undefined);\n }\n }\n }, [enabled, args, cancel, respond, context]);\n\n // If there's no content and the tool has been executed (so there's no\n // confirmation UI displayed either), don't render anything.\n if (stage === \"executed\" && !children) {\n return null;\n }\n\n return (\n <div\n className={classNames(\"lb-ai-tool-confirmation\", className)}\n {...props}\n >\n {children ? (\n <div className=\"lb-ai-tool-confirmation-content\">{children}</div>\n ) : null}\n {stage !== \"executed\" && (\n <div className=\"lb-ai-tool-confirmation-footer\">\n <div className=\"lb-ai-tool-confirmation-actions\">\n <Button\n disabled={!enabled}\n onClick={onCancelClick}\n variant=\"secondary\"\n >\n {$.AI_TOOL_CONFIRMATION_CANCEL}\n </Button>\n <Button\n disabled={!enabled}\n onClick={onConfirmClick}\n variant={variant === \"destructive\" ? \"destructive\" : \"primary\"}\n >\n {$.AI_TOOL_CONFIRMATION_CONFIRM}\n </Button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction prettifyString(string: string) {\n return (\n string\n // Convert camelCase to spaces\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n // Convert snake_case and kebab-case to spaces\n .replace(/[_-]+/g, \" \")\n // Collapse multiple following spaces\n .replace(/\\s+/g, \" \")\n // Trim leading and trailing spaces\n .trim()\n // Capitalize first word\n .toLowerCase()\n .replace(/^\\w/, (character) => character.toUpperCase())\n );\n}\n\n/**\n * A pre-built component which displays a tool call.\n *\n * By default, a human-readable version of the tool's name is used as a title:\n * - `\"showTodo\"` → \"Show todo\"\n * - `\"get_weather\"` → \"Get weather\"\n *\n * @example\n * defineAiTool()({\n * // ...\n * render: () => (\n * <AiTool />\n * ),\n * })\n *\n * It can be customized in various ways:\n * - adding an icon\n * - customizing the title (even dynamically)\n * - adding custom content inside it\n * - collapsing it conditionally\n * - etc.\n *\n * @example\n * defineAiTool()({\n * // ...\n * render: ({ stage, result }) => (\n * <AiTool\n * icon=\"🔍\"\n *\n * // Override the default title based on the tool's stage\n * title={stage === \"executing\" ? \"Searching…\" : \"Search results\"}\n *\n * // Start open and automatically collapse after it is executed\n * // The user can still expand/collapse it manually at any time\n * collapsed={stage === \"executed\"}\n * >\n * <SearchResults data={result.data} />\n * </AiTool>\n * ),\n * })\n *\n * It also comes with a few built-in sub-components:\n * - `AiTool.Confirmation` to display a human-in-the-loop confirmation step\n * which can be accepted or cancelled by the user.\n * - `AiTool.Inspector` to display the tool's arguments and result which can\n * be useful during development.\n *\n * @example\n * defineAiTool()({\n * // ...\n * render: () => (\n * <AiTool>\n * <AiTool.Confirmation\n * // Use a destructive visual appearance\n * variant=\"destructive\"\n *\n * // The tool's arguments can be directly accessed like in `execute`\n * confirm={({ pageIds }) => {\n * const deletedPageTitles = pages\n * .filter((p) => pageIds.includes(p.id))\n * .map((page) => page.title);\n *\n * deletePages(pageIds);\n *\n * // This result will be available as `result` in the tool's `render` props\n * return { data: { deletedPageTitles } };\n * }}\n *\n * // If needed, `cancel={() => ...}` would work similarly\n * >\n * Do you want to delete these pages?\n * <PagesPreviews />\n * </AiTool.Confirmation>\n * </AiTool>\n * ),\n * })\n *\n * @example\n * defineAiTool()({\n * // ...\n * render: () => (\n * <AiTool>\n * <AiTool.Inspector />\n * </AiTool>\n * ),\n * })\n */\nexport const AiTool = Object.assign(\n forwardRef<HTMLDivElement, AiToolProps>(\n (\n {\n children,\n title,\n icon,\n collapsed,\n onCollapsedChange,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const {\n stage,\n result,\n name,\n [kInternal]: { execute },\n } = useAiToolInvocationContext();\n const [semiControlledCollapsed, onSemiControlledCollapsed] =\n useSemiControllableState(collapsed ?? false, onCollapsedChange);\n // TODO: This check won't work for cases like:\n // <AiTool>\n // <ComponentThatRendersNull />\n // <ComponentThatAlsoRendersNull />\n // </AiTool>\n // One solution could be to check the DOM on every render with `useLayoutEffect`\n // to see if there's any actual content.\n // For now we're limiting the visual issues caused by the above by using CSS's\n // `:empty` pseudo-class to make the content 0px high if it's actually empty.\n const hasContent = Children.count(children) > 0;\n const resolvedTitle = useMemo(() => {\n return title ?? prettifyString(name);\n }, [title, name]);\n\n // `AiTool` uses \"collapsed\" instead of \"open\" (like the `Composer` component) because \"open\"\n // makes sense next to something called \"Collapsible\" but less so for something called \"AiTool\".\n const handleCollapsibleOpenChange = useCallback(\n (open: boolean) => {\n onSemiControlledCollapsed(!open);\n },\n [onSemiControlledCollapsed]\n );\n\n return (\n <Collapsible.Root\n ref={forwardedRef}\n className={classNames(\"lb-collapsible lb-ai-tool\", className)}\n {...props}\n // Regardless of `semiControlledCollapsed`, the collapsible is closed if there's no content.\n open={hasContent ? !semiControlledCollapsed : false}\n onOpenChange={handleCollapsibleOpenChange}\n disabled={!hasContent}\n data-result={result?.type}\n data-stage={stage}\n >\n <Collapsible.Trigger className=\"lb-collapsible-trigger lb-ai-tool-header\">\n {icon ? (\n <div className=\"lb-ai-tool-header-icon-container\">{icon}</div>\n ) : null}\n <span className=\"lb-ai-tool-header-title\">{resolvedTitle}</span>\n {hasContent ? (\n <span className=\"lb-collapsible-chevron lb-icon-container\">\n <ChevronRightIcon />\n </span>\n ) : null}\n <div className=\"lb-ai-tool-header-status\">\n {stage === \"executed\" ? (\n result.type === \"success\" ? (\n <CheckCircleFillIcon />\n ) : result.type === \"error\" ? (\n <CrossCircleFillIcon />\n ) : result.type === \"cancelled\" ? (\n <MinusCircleIcon />\n ) : null\n ) : execute !== undefined ? (\n // Only show a spinner if the tool has an `execute` method.\n <SpinnerIcon />\n ) : null}\n </div>\n </Collapsible.Trigger>\n\n {hasContent ? (\n <Collapsible.Content className=\"lb-collapsible-content lb-ai-tool-content-container\">\n <div className=\"lb-ai-tool-content\">{children}</div>\n </Collapsible.Content>\n ) : null}\n </Collapsible.Root>\n );\n }\n ),\n {\n /**\n * Display an icon in a container.\n *\n * @example\n * <AiTool\n * icon={\n * <AiTool.Icon>🔍</AiTool.Icon>\n * }\n * />\n */\n Icon: AiToolIcon,\n\n /**\n * Display the tool's arguments and result, which can be useful during\n * development.\n *\n * @example\n * <AiTool>\n * <AiTool.Inspector />\n * </AiTool>\n */\n Inspector: AiToolInspector,\n\n /**\n * Display a human-in-the-loop confirmation step which can be accepted\n * or cancelled by the user.\n *\n * The `confirm` and `cancel` callbacks work like `execute` in tool definitions: they can\n * perform side-effects, be async if needed, and return a result. The tool call will stay\n * pending until either `confirm` or `cancel` is called.\n *\n * @example\n * <AiTool>\n * <AiTool.Confirmation\n * // Use a destructive visual appearance\n * variant=\"destructive\"\n *\n * // The tool's arguments can be directly accessed like in `execute`\n * confirm={({ pageIds }) => {\n * const deletedPageTitles = pages\n * .filter((p) => pageIds.includes(p.id))\n * .map((page) => page.title);\n *\n * deletePages(pageIds);\n *\n * // This result will be available as `result` in the tool's `render` props\n * return { data: { deletedPageTitles } };\n * }}\n *\n * // If needed, `cancel={() => ...}` would work similarly\n * >\n * Do you want to delete these pages?\n * <PagesPreviews />\n * </AiTool.Confirmation>\n * </AiTool>\n */\n Confirmation: AiToolConfirmation,\n }\n);\n"],"names":["Collapsible.Root","Collapsible.Trigger","Collapsible.Content"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AiTool.js","sources":["../../src/components/AiTool.tsx"],"sourcesContent":["import type {\n AiToolExecuteCallback,\n AiToolTypePack,\n JsonObject,\n NoInfr,\n} from \"@liveblocks/core\";\nimport { kInternal } from \"@liveblocks/core\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { Children, forwardRef, useCallback, useMemo } from \"react\";\n\nimport { Button } from \"../_private\";\nimport {\n CheckCircleFillIcon,\n ChevronRightIcon,\n CrossCircleFillIcon,\n MinusCircleIcon,\n SpinnerIcon,\n} from \"../icons\";\nimport {\n type AiToolConfirmationOverrides,\n type GlobalOverrides,\n useOverrides,\n} from \"../overrides\";\nimport { useAiToolInvocationContext } from \"../primitives/AiMessage/contexts\";\nimport * as Collapsible from \"../primitives/Collapsible\";\nimport { cn } from \"../utils/cn\";\nimport { useSemiControllableState } from \"../utils/use-controllable-state\";\nimport { CodeBlock } from \"./internal/CodeBlock\";\n\nexport interface AiToolProps\n extends Omit<ComponentProps<\"div\">, \"title\" | \"children\"> {\n /**\n * The tool's title.\n *\n * By default, a human-readable version of the tool's name is used:\n * - `\"showTodo\"` → \"Show todo\"\n * - `\"get_weather\"` → \"Get weather\"\n */\n title?: string;\n\n /**\n * An optional icon displayed next to the title.\n */\n icon?: ReactNode;\n\n /**\n * The content shown in the tool.\n */\n children?: ReactNode;\n\n /**\n * Whether the content is currently collapsed.\n * It is not a traditional controlled value, as in if you set it to `true` it would only stay expanded.\n * Instead, it is \"semi-controlled\", meaning that setting it to `true` will expand it, but it\n * can still be collapsed/expanded by clicking on it.\n */\n collapsed?: boolean;\n\n /**\n * The event handler called when the content is collapsed or expanded by clicking on it.\n */\n onCollapsedChange?: (collapsed: boolean) => void;\n\n /**\n * Whether the content can be collapsed/expanded.\n * If set to `false`, clicking on it will have no effect.\n * If there's no content, this prop has no effect.\n */\n collapsible?: boolean;\n}\n\nexport type AiToolIconProps = ComponentProps<\"div\">;\n\nexport type AiToolInspectorProps = ComponentProps<\"div\">;\n\nexport interface AiToolConfirmationProps<\n A extends JsonObject,\n R extends JsonObject,\n> extends ComponentProps<\"div\"> {\n /**\n * The callback invoked when the user clicks the confirm button.\n */\n confirm: AiToolExecuteCallback<A, R>;\n\n /**\n * The callback invoked when the user clicks the cancel button.\n */\n cancel?: AiToolExecuteCallback<A, R>;\n\n /**\n * The visual appearance.\n */\n variant?: \"default\" | \"destructive\";\n\n /**\n * Override the component's strings.\n */\n overrides?: Partial<GlobalOverrides & AiToolConfirmationOverrides>;\n\n /**\n * The tool's result type, to be used with the `types` prop in the `render` method.\n *\n * @example\n * defineAiTool<{ value: number }>()({\n * // ...\n * render: ({ types }) => (\n * <AiTool.Confirmation\n * types={types}\n * confirm={() => {\n * return {\n * // Using `types` makes the result type-safe\n * // based on the tool's definition\n * data: { value: 123 },\n * };\n * }}\n * />\n * ),\n * })\n */\n types?: NoInfr<AiToolTypePack<A, R>>;\n}\n\nfunction AiToolIcon({ className, ...props }: AiToolIconProps) {\n return <div className={cn(\"lb-ai-tool-icon\", className)} {...props} />;\n}\n\nfunction AiToolInspector({ className, ...props }: AiToolInspectorProps) {\n const { args, partialArgs, result } = useAiToolInvocationContext();\n\n return (\n <div className={cn(\"lb-ai-tool-inspector\", className)} {...props}>\n <CodeBlock\n title=\"Arguments\"\n code={JSON.stringify(args ?? partialArgs, null, 2)}\n />\n {result !== undefined ? (\n <CodeBlock title=\"Result\" code={JSON.stringify(result, null, 2)} />\n ) : null}\n </div>\n );\n}\n\nfunction AiToolConfirmation<\n TPack extends AiToolTypePack,\n A extends JsonObject = TPack[\"A\"],\n R extends JsonObject = TPack[\"R\"],\n>({\n children,\n variant = \"default\",\n confirm,\n cancel,\n overrides,\n className,\n ...props\n}: AiToolConfirmationProps<A, R>) {\n const { stage, args, respond, name, invocationId } =\n useAiToolInvocationContext();\n const $ = useOverrides(overrides);\n\n const enabled = stage === \"executing\";\n\n const context = useMemo(() => ({ name, invocationId }), [name, invocationId]);\n\n const onConfirmClick = useCallback(async () => {\n if (enabled) {\n const result = await confirm(args as A, context);\n respond(result ?? undefined);\n }\n }, [enabled, args, confirm, respond, context]);\n\n const onCancelClick = useCallback(async () => {\n if (enabled) {\n if (cancel === undefined) {\n respond({ cancel: true });\n } else {\n const result = await cancel(args as A, context);\n respond(result ?? undefined);\n }\n }\n }, [enabled, args, cancel, respond, context]);\n\n // If there's no content and the tool has been executed (so there's no\n // confirmation UI displayed either), don't render anything.\n if (stage === \"executed\" && !children) {\n return null;\n }\n\n return (\n <div className={cn(\"lb-ai-tool-confirmation\", className)} {...props}>\n {children ? (\n <div className=\"lb-ai-tool-confirmation-content\">{children}</div>\n ) : null}\n {stage !== \"executed\" && (\n <div className=\"lb-ai-tool-confirmation-footer\">\n <div className=\"lb-ai-tool-confirmation-actions\">\n <Button\n disabled={!enabled}\n onClick={onCancelClick}\n variant=\"secondary\"\n >\n {$.AI_TOOL_CONFIRMATION_CANCEL}\n </Button>\n <Button\n disabled={!enabled}\n onClick={onConfirmClick}\n variant={variant === \"destructive\" ? \"destructive\" : \"primary\"}\n >\n {$.AI_TOOL_CONFIRMATION_CONFIRM}\n </Button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction prettifyString(string: string) {\n return (\n string\n // Convert camelCase to spaces\n .replace(/([a-z])([A-Z])/g, \"$1 $2\")\n // Convert snake_case and kebab-case to spaces\n .replace(/[_-]+/g, \" \")\n // Collapse multiple following spaces\n .replace(/\\s+/g, \" \")\n // Trim leading and trailing spaces\n .trim()\n // Capitalize first word\n .toLowerCase()\n .replace(/^\\w/, (character) => character.toUpperCase())\n );\n}\n\n/**\n * A pre-built component which displays a tool call.\n *\n * By default, a human-readable version of the tool's name is used as a title:\n * - `\"showTodo\"` → \"Show todo\"\n * - `\"get_weather\"` → \"Get weather\"\n *\n * @example\n * defineAiTool()({\n * // ...\n * render: () => (\n * <AiTool />\n * ),\n * })\n *\n * It can be customized in various ways:\n * - adding an icon\n * - customizing the title (even dynamically)\n * - adding custom content inside it\n * - collapsing it conditionally\n * - etc.\n *\n * @example\n * defineAiTool()({\n * // ...\n * render: ({ stage, result }) => (\n * <AiTool\n * icon=\"🔍\"\n *\n * // Override the default title based on the tool's stage\n * title={stage === \"executing\" ? \"Searching…\" : \"Search results\"}\n *\n * // Start open and automatically collapse after it is executed\n * // The user can still expand/collapse it manually at any time\n * collapsed={stage === \"executed\"}\n * >\n * <SearchResults data={result.data} />\n * </AiTool>\n * ),\n * })\n *\n * It also comes with a few built-in sub-components:\n * - `AiTool.Confirmation` to display a human-in-the-loop confirmation step\n * which can be accepted or cancelled by the user.\n * - `AiTool.Inspector` to display the tool's arguments and result which can\n * be useful during development.\n *\n * @example\n * defineAiTool()({\n * // ...\n * render: () => (\n * <AiTool>\n * <AiTool.Confirmation\n * // Use a destructive visual appearance\n * variant=\"destructive\"\n *\n * // The tool's arguments can be directly accessed like in `execute`\n * confirm={({ pageIds }) => {\n * const deletedPageTitles = pages\n * .filter((p) => pageIds.includes(p.id))\n * .map((page) => page.title);\n *\n * deletePages(pageIds);\n *\n * // This result will be available as `result` in the tool's `render` props\n * return { data: { deletedPageTitles } };\n * }}\n *\n * // If needed, `cancel={() => ...}` would work similarly\n * >\n * Do you want to delete these pages?\n * <PagesPreviews />\n * </AiTool.Confirmation>\n * </AiTool>\n * ),\n * })\n *\n * @example\n * defineAiTool()({\n * // ...\n * render: () => (\n * <AiTool>\n * <AiTool.Inspector />\n * </AiTool>\n * ),\n * })\n */\nexport const AiTool = Object.assign(\n forwardRef<HTMLDivElement, AiToolProps>(\n (\n {\n children,\n title,\n icon,\n collapsible,\n collapsed,\n onCollapsedChange,\n className,\n ...props\n },\n forwardedRef\n ) => {\n const {\n stage,\n result,\n name,\n [kInternal]: { execute },\n } = useAiToolInvocationContext();\n const [semiControlledCollapsed, onSemiControlledCollapsed] =\n useSemiControllableState(collapsed ?? false, onCollapsedChange);\n // TODO: This check won't work for cases like:\n // <AiTool>\n // <ComponentThatRendersNull />\n // <ComponentThatAlsoRendersNull />\n // </AiTool>\n // One solution could be to check the DOM on every render with `useLayoutEffect`\n // to see if there's any actual content.\n // For now we're limiting the visual issues caused by the above by using CSS's\n // `:empty` pseudo-class to make the content 0px high if it's actually empty.\n const hasContent = Children.count(children) > 0;\n // If there's no content, the tool is never collapsible.\n const isCollapsible = hasContent ? (collapsible ?? true) : false;\n const resolvedTitle = useMemo(() => {\n return title ?? prettifyString(name);\n }, [title, name]);\n\n // `AiTool` uses \"collapsed\" instead of \"open\" (like the `Composer` component) because \"open\"\n // makes sense next to something called \"Collapsible\" but less so for something called \"AiTool\".\n const handleCollapsibleOpenChange = useCallback(\n (open: boolean) => {\n onSemiControlledCollapsed(!open);\n },\n [onSemiControlledCollapsed]\n );\n\n return (\n <Collapsible.Root\n ref={forwardedRef}\n className={cn(\"lb-collapsible lb-ai-tool\", className)}\n {...props}\n // Regardless of `semiControlledCollapsed`, the collapsible is closed if there's no content.\n open={hasContent ? !semiControlledCollapsed : false}\n onOpenChange={handleCollapsibleOpenChange}\n disabled={!isCollapsible}\n data-result={result?.type}\n data-stage={stage}\n >\n <Collapsible.Trigger className=\"lb-collapsible-trigger lb-ai-tool-header\">\n {icon ? (\n <div className=\"lb-ai-tool-header-icon-container\">{icon}</div>\n ) : null}\n <span className=\"lb-ai-tool-header-title\">{resolvedTitle}</span>\n {isCollapsible ? (\n <span className=\"lb-collapsible-chevron lb-icon-container\">\n <ChevronRightIcon />\n </span>\n ) : null}\n <div className=\"lb-ai-tool-header-status\">\n {stage === \"executed\" ? (\n result.type === \"success\" ? (\n <CheckCircleFillIcon />\n ) : result.type === \"error\" ? (\n <CrossCircleFillIcon />\n ) : result.type === \"cancelled\" ? (\n <MinusCircleIcon />\n ) : null\n ) : execute !== undefined ? (\n // Only show a spinner if the tool has an `execute` method.\n <SpinnerIcon />\n ) : null}\n </div>\n </Collapsible.Trigger>\n\n {hasContent ? (\n <Collapsible.Content className=\"lb-collapsible-content lb-ai-tool-content-container\">\n <div className=\"lb-ai-tool-content\">{children}</div>\n </Collapsible.Content>\n ) : null}\n </Collapsible.Root>\n );\n }\n ),\n {\n /**\n * Display an icon in a container.\n *\n * @example\n * <AiTool\n * icon={\n * <AiTool.Icon>🔍</AiTool.Icon>\n * }\n * />\n */\n Icon: AiToolIcon,\n\n /**\n * Display the tool's arguments and result, which can be useful during\n * development.\n *\n * @example\n * <AiTool>\n * <AiTool.Inspector />\n * </AiTool>\n */\n Inspector: AiToolInspector,\n\n /**\n * Display a human-in-the-loop confirmation step which can be accepted\n * or cancelled by the user.\n *\n * The `confirm` and `cancel` callbacks work like `execute` in tool definitions: they can\n * perform side-effects, be async if needed, and return a result. The tool call will stay\n * pending until either `confirm` or `cancel` is called.\n *\n * @example\n * <AiTool>\n * <AiTool.Confirmation\n * // Use a destructive visual appearance\n * variant=\"destructive\"\n *\n * // The tool's arguments can be directly accessed like in `execute`\n * confirm={({ pageIds }) => {\n * const deletedPageTitles = pages\n * .filter((p) => pageIds.includes(p.id))\n * .map((page) => page.title);\n *\n * deletePages(pageIds);\n *\n * // This result will be available as `result` in the tool's `render` props\n * return { data: { deletedPageTitles } };\n * }}\n *\n * // If needed, `cancel={() => ...}` would work similarly\n * >\n * Do you want to delete these pages?\n * <PagesPreviews />\n * </AiTool.Confirmation>\n * </AiTool>\n */\n Confirmation: AiToolConfirmation,\n }\n);\n"],"names":["Collapsible.Root","Collapsible.Trigger","Collapsible.Content"],"mappings":"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"}
|
|
@@ -19,7 +19,7 @@ var index = require('../primitives/Comment/index.cjs');
|
|
|
19
19
|
var index$1 = require('../primitives/Composer/index.cjs');
|
|
20
20
|
var Timestamp = require('../primitives/Timestamp.cjs');
|
|
21
21
|
var shared = require('../shared.cjs');
|
|
22
|
-
var
|
|
22
|
+
var cn = require('../utils/cn.cjs');
|
|
23
23
|
var download = require('../utils/download.cjs');
|
|
24
24
|
var useRefs = require('../utils/use-refs.cjs');
|
|
25
25
|
var useVisible = require('../utils/use-visible.cjs');
|
|
@@ -68,7 +68,7 @@ function CommentMention({
|
|
|
68
68
|
switch (mention.kind) {
|
|
69
69
|
case "user":
|
|
70
70
|
return /* @__PURE__ */ jsxRuntime.jsxs(index.Mention, {
|
|
71
|
-
className:
|
|
71
|
+
className: cn.cn("lb-comment-mention", className),
|
|
72
72
|
"data-self": mention.id === currentId ? "" : void 0,
|
|
73
73
|
...props,
|
|
74
74
|
children: [
|
|
@@ -90,7 +90,7 @@ function CommentLink({
|
|
|
90
90
|
}) {
|
|
91
91
|
const { Anchor } = components.useComponents();
|
|
92
92
|
return /* @__PURE__ */ jsxRuntime.jsx(index.Link, {
|
|
93
|
-
className:
|
|
93
|
+
className: cn.cn("lb-comment-link", className),
|
|
94
94
|
href,
|
|
95
95
|
...props,
|
|
96
96
|
asChild: true,
|
|
@@ -107,7 +107,7 @@ function CommentNonInteractiveLink({
|
|
|
107
107
|
...props
|
|
108
108
|
}) {
|
|
109
109
|
return /* @__PURE__ */ jsxRuntime.jsx("span", {
|
|
110
|
-
className:
|
|
110
|
+
className: cn.cn("lb-comment-link", className),
|
|
111
111
|
...props,
|
|
112
112
|
children
|
|
113
113
|
});
|
|
@@ -115,7 +115,7 @@ function CommentNonInteractiveLink({
|
|
|
115
115
|
const CommentReactionButton = react.forwardRef(({ reaction, overrides: overrides$1, className, ...props }, forwardedRef) => {
|
|
116
116
|
const $ = overrides.useOverrides(overrides$1);
|
|
117
117
|
return /* @__PURE__ */ jsxRuntime.jsxs(Button.CustomButton, {
|
|
118
|
-
className:
|
|
118
|
+
className: cn.cn("lb-comment-reaction", className),
|
|
119
119
|
variant: "outline",
|
|
120
120
|
"aria-label": $.COMMENT_REACTION_DESCRIPTION(
|
|
121
121
|
reaction.emoji,
|
|
@@ -247,7 +247,7 @@ function CommentMediaAttachment({
|
|
|
247
247
|
[attachment, onAttachmentClick, url]
|
|
248
248
|
);
|
|
249
249
|
return /* @__PURE__ */ jsxRuntime.jsx(Attachment.MediaAttachment, {
|
|
250
|
-
className:
|
|
250
|
+
className: cn.cn("lb-comment-attachment", className),
|
|
251
251
|
...props,
|
|
252
252
|
attachment,
|
|
253
253
|
overrides,
|
|
@@ -279,7 +279,7 @@ function CommentFileAttachment({
|
|
|
279
279
|
[attachment, onAttachmentClick, url]
|
|
280
280
|
);
|
|
281
281
|
return /* @__PURE__ */ jsxRuntime.jsx(Attachment.FileAttachment, {
|
|
282
|
-
className:
|
|
282
|
+
className: cn.cn("lb-comment-attachment", className),
|
|
283
283
|
...props,
|
|
284
284
|
attachment,
|
|
285
285
|
overrides,
|
|
@@ -292,7 +292,7 @@ function CommentNonInteractiveFileAttachment({
|
|
|
292
292
|
...props
|
|
293
293
|
}) {
|
|
294
294
|
return /* @__PURE__ */ jsxRuntime.jsx(Attachment.FileAttachment, {
|
|
295
|
-
className:
|
|
295
|
+
className: cn.cn("lb-comment-attachment", className),
|
|
296
296
|
allowMediaPreview: false,
|
|
297
297
|
...props
|
|
298
298
|
});
|
|
@@ -456,7 +456,7 @@ const Comment = react.forwardRef(
|
|
|
456
456
|
}),
|
|
457
457
|
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
458
458
|
id: comment.id,
|
|
459
|
-
className:
|
|
459
|
+
className: cn.cn(
|
|
460
460
|
"lb-root lb-comment",
|
|
461
461
|
indentContent && "lb-comment:indent-content",
|
|
462
462
|
showActions === "hover" && "lb-comment:show-actions-hover",
|
|
@@ -513,7 +513,7 @@ const Comment = react.forwardRef(
|
|
|
513
513
|
]
|
|
514
514
|
}),
|
|
515
515
|
showActions && !isEditing && /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
516
|
-
className:
|
|
516
|
+
className: cn.cn(
|
|
517
517
|
"lb-comment-actions",
|
|
518
518
|
additionalActionsClassName
|
|
519
519
|
),
|
|
@@ -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 { classNames } from \"../utils/class-names\";\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={classNames(\"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={classNames(\"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={classNames(\"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={classNames(\"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={classNames(\"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={classNames(\"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={classNames(\"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={classNames(\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={classNames(\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;AACsD;AACV;AACvC;AAEH;AAAA;AACA;AAAqB;AAAI;AAAA;AAC5B;AAIF;AAAyD;AAE/D;AAEO;AAAqB;AAC1B;AACA;AACA;AAEF;AACE;AAEA;AACG;AACmD;AAClD;AACI;AACG;AAEN;AAAW;AAAQ;AAAS;AAGnC;AAEO;AAAmC;AAClC;AACN;AACA;AAEF;AACE;AACG;AAAuD;AAAO;AAC5D;AAGP;AAEA;AAIE;AACA;AACG;AACuD;AAC9C;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;AACyD;AACpD;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;AACyD;AACpD;AACJ;AACA;AAC6B;AAC7B;AAGN;AAEO;AAA6C;AAClD;AAEF;AACE;AACG;AACyD;AACrC;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-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;;;;;;;;"}
|
|
@@ -17,7 +17,7 @@ import { Mention as CommentMention$1, Link as CommentLink$1, Body as CommentBody
|
|
|
17
17
|
import { Submit as ComposerSubmit } from '../primitives/Composer/index.js';
|
|
18
18
|
import { Timestamp } from '../primitives/Timestamp.js';
|
|
19
19
|
import { useCurrentUserId } from '../shared.js';
|
|
20
|
-
import {
|
|
20
|
+
import { cn } from '../utils/cn.js';
|
|
21
21
|
import { download } from '../utils/download.js';
|
|
22
22
|
import { useRefs } from '../utils/use-refs.js';
|
|
23
23
|
import { useIntersectionCallback } from '../utils/use-visible.js';
|
|
@@ -47,7 +47,7 @@ function CommentMention({
|
|
|
47
47
|
switch (mention.kind) {
|
|
48
48
|
case "user":
|
|
49
49
|
return /* @__PURE__ */ jsxs(CommentMention$1, {
|
|
50
|
-
className:
|
|
50
|
+
className: cn("lb-comment-mention", className),
|
|
51
51
|
"data-self": mention.id === currentId ? "" : void 0,
|
|
52
52
|
...props,
|
|
53
53
|
children: [
|
|
@@ -69,7 +69,7 @@ function CommentLink({
|
|
|
69
69
|
}) {
|
|
70
70
|
const { Anchor } = useComponents();
|
|
71
71
|
return /* @__PURE__ */ jsx(CommentLink$1, {
|
|
72
|
-
className:
|
|
72
|
+
className: cn("lb-comment-link", className),
|
|
73
73
|
href,
|
|
74
74
|
...props,
|
|
75
75
|
asChild: true,
|
|
@@ -86,7 +86,7 @@ function CommentNonInteractiveLink({
|
|
|
86
86
|
...props
|
|
87
87
|
}) {
|
|
88
88
|
return /* @__PURE__ */ jsx("span", {
|
|
89
|
-
className:
|
|
89
|
+
className: cn("lb-comment-link", className),
|
|
90
90
|
...props,
|
|
91
91
|
children
|
|
92
92
|
});
|
|
@@ -94,7 +94,7 @@ function CommentNonInteractiveLink({
|
|
|
94
94
|
const CommentReactionButton = forwardRef(({ reaction, overrides, className, ...props }, forwardedRef) => {
|
|
95
95
|
const $ = useOverrides(overrides);
|
|
96
96
|
return /* @__PURE__ */ jsxs(CustomButton, {
|
|
97
|
-
className:
|
|
97
|
+
className: cn("lb-comment-reaction", className),
|
|
98
98
|
variant: "outline",
|
|
99
99
|
"aria-label": $.COMMENT_REACTION_DESCRIPTION(
|
|
100
100
|
reaction.emoji,
|
|
@@ -226,7 +226,7 @@ function CommentMediaAttachment({
|
|
|
226
226
|
[attachment, onAttachmentClick, url]
|
|
227
227
|
);
|
|
228
228
|
return /* @__PURE__ */ jsx(MediaAttachment, {
|
|
229
|
-
className:
|
|
229
|
+
className: cn("lb-comment-attachment", className),
|
|
230
230
|
...props,
|
|
231
231
|
attachment,
|
|
232
232
|
overrides,
|
|
@@ -258,7 +258,7 @@ function CommentFileAttachment({
|
|
|
258
258
|
[attachment, onAttachmentClick, url]
|
|
259
259
|
);
|
|
260
260
|
return /* @__PURE__ */ jsx(FileAttachment, {
|
|
261
|
-
className:
|
|
261
|
+
className: cn("lb-comment-attachment", className),
|
|
262
262
|
...props,
|
|
263
263
|
attachment,
|
|
264
264
|
overrides,
|
|
@@ -271,7 +271,7 @@ function CommentNonInteractiveFileAttachment({
|
|
|
271
271
|
...props
|
|
272
272
|
}) {
|
|
273
273
|
return /* @__PURE__ */ jsx(FileAttachment, {
|
|
274
|
-
className:
|
|
274
|
+
className: cn("lb-comment-attachment", className),
|
|
275
275
|
allowMediaPreview: false,
|
|
276
276
|
...props
|
|
277
277
|
});
|
|
@@ -435,7 +435,7 @@ const Comment = forwardRef(
|
|
|
435
435
|
}),
|
|
436
436
|
/* @__PURE__ */ jsxs("div", {
|
|
437
437
|
id: comment.id,
|
|
438
|
-
className:
|
|
438
|
+
className: cn(
|
|
439
439
|
"lb-root lb-comment",
|
|
440
440
|
indentContent && "lb-comment:indent-content",
|
|
441
441
|
showActions === "hover" && "lb-comment:show-actions-hover",
|
|
@@ -492,7 +492,7 @@ const Comment = forwardRef(
|
|
|
492
492
|
]
|
|
493
493
|
}),
|
|
494
494
|
showActions && !isEditing && /* @__PURE__ */ jsxs("div", {
|
|
495
|
-
className:
|
|
495
|
+
className: cn(
|
|
496
496
|
"lb-comment-actions",
|
|
497
497
|
additionalActionsClassName
|
|
498
498
|
),
|