@flanksource/clicky-ui 0.3.47 → 0.3.48
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/README.md +49 -0
- package/dist/comments/CommentSidePanel.cjs +10 -4
- package/dist/comments/CommentSidePanel.cjs.map +1 -1
- package/dist/comments/CommentSidePanel.d.ts +2 -0
- package/dist/comments/CommentSidePanel.d.ts.map +1 -1
- package/dist/comments/CommentSidePanel.js +10 -4
- package/dist/comments/CommentSidePanel.js.map +1 -1
- package/dist/comments/CommentThread.cjs +2 -0
- package/dist/comments/CommentThread.cjs.map +1 -1
- package/dist/comments/CommentThread.d.ts +2 -1
- package/dist/comments/CommentThread.d.ts.map +1 -1
- package/dist/comments/CommentThread.js +2 -0
- package/dist/comments/CommentThread.js.map +1 -1
- package/dist/comments/CommentThreadList.cjs +3 -0
- package/dist/comments/CommentThreadList.cjs.map +1 -1
- package/dist/comments/CommentThreadList.d.ts +3 -1
- package/dist/comments/CommentThreadList.d.ts.map +1 -1
- package/dist/comments/CommentThreadList.js +3 -0
- package/dist/comments/CommentThreadList.js.map +1 -1
- package/dist/data/CodeLine.cjs +4 -2
- package/dist/data/CodeLine.cjs.map +1 -1
- package/dist/data/CodeLine.d.ts.map +1 -1
- package/dist/data/CodeLine.js +4 -2
- package/dist/data/CodeLine.js.map +1 -1
- package/dist/data/SymbolBadge.cjs +61 -0
- package/dist/data/SymbolBadge.cjs.map +1 -0
- package/dist/data/SymbolBadge.d.ts +21 -0
- package/dist/data/SymbolBadge.d.ts.map +1 -0
- package/dist/data/SymbolBadge.js +61 -0
- package/dist/data/SymbolBadge.js.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.cjs +26 -12
- package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.js +27 -13
- package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
- package/dist/data/test-runner/TestRunner.cjs +6 -0
- package/dist/data/test-runner/TestRunner.cjs.map +1 -1
- package/dist/data/test-runner/TestRunner.d.ts +3 -1
- package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
- package/dist/data/test-runner/TestRunner.js +6 -0
- package/dist/data/test-runner/TestRunner.js.map +1 -1
- package/dist/data/test-runner/TestTreeNode.cjs +2 -1
- package/dist/data/test-runner/TestTreeNode.cjs.map +1 -1
- package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -1
- package/dist/data/test-runner/TestTreeNode.js +2 -1
- package/dist/data/test-runner/TestTreeNode.js.map +1 -1
- package/dist/data/test-runner/adapter.cjs +4 -0
- package/dist/data/test-runner/adapter.cjs.map +1 -1
- package/dist/data/test-runner/adapter.d.ts +9 -0
- package/dist/data/test-runner/adapter.d.ts.map +1 -1
- package/dist/data/test-runner/adapter.js +4 -0
- package/dist/data/test-runner/adapter.js.map +1 -1
- package/dist/data/test-runner/context.cjs.map +1 -1
- package/dist/data/test-runner/context.d.ts +2 -0
- package/dist/data/test-runner/context.d.ts.map +1 -1
- package/dist/data/test-runner/context.js.map +1 -1
- package/dist/data/test-runner/index.d.ts +2 -2
- package/dist/data/test-runner/index.d.ts.map +1 -1
- package/dist/data/test-runner/marks.cjs +42 -1
- package/dist/data/test-runner/marks.cjs.map +1 -1
- package/dist/data/test-runner/marks.d.ts +10 -0
- package/dist/data/test-runner/marks.d.ts.map +1 -1
- package/dist/data/test-runner/marks.js +42 -1
- package/dist/data/test-runner/marks.js.map +1 -1
- package/dist/data/traces/TerminalCastPlayer.d.ts +21 -0
- package/dist/data/traces/TerminalCastPlayer.d.ts.map +1 -0
- package/dist/data/traces/TraceHarView.d.ts +13 -0
- package/dist/data/traces/TraceHarView.d.ts.map +1 -0
- package/dist/data/traces/cast.d.ts +9 -0
- package/dist/data/traces/cast.d.ts.map +1 -0
- package/dist/data/traces/fixtures.d.ts +8 -0
- package/dist/data/traces/fixtures.d.ts.map +1 -0
- package/dist/data/traces/har-adapter.d.ts +13 -0
- package/dist/data/traces/har-adapter.d.ts.map +1 -0
- package/dist/data/traces/terminal-screen.d.ts +45 -0
- package/dist/data/traces/terminal-screen.d.ts.map +1 -0
- package/dist/data/traces/terminal-timeline.d.ts +26 -0
- package/dist/data/traces/terminal-timeline.d.ts.map +1 -0
- package/dist/data/traces/types.d.ts +141 -0
- package/dist/data/traces/types.d.ts.map +1 -0
- package/dist/data/verification/VerificationResults.cjs +4 -0
- package/dist/data/verification/VerificationResults.cjs.map +1 -1
- package/dist/data/verification/VerificationResults.d.ts +3 -1
- package/dist/data/verification/VerificationResults.d.ts.map +1 -1
- package/dist/data/verification/VerificationResults.js +4 -0
- package/dist/data/verification/VerificationResults.js.map +1 -1
- package/dist/data/verification/adapterViews.cjs +12 -3
- package/dist/data/verification/adapterViews.cjs.map +1 -1
- package/dist/data/verification/adapterViews.d.ts +4 -1
- package/dist/data/verification/adapterViews.d.ts.map +1 -1
- package/dist/data/verification/adapterViews.js +12 -3
- package/dist/data/verification/adapterViews.js.map +1 -1
- package/dist/data/verification/adapters.cjs +53 -10
- package/dist/data/verification/adapters.cjs.map +1 -1
- package/dist/data/verification/adapters.d.ts +7 -1
- package/dist/data/verification/adapters.d.ts.map +1 -1
- package/dist/data/verification/adapters.js +53 -10
- package/dist/data/verification/adapters.js.map +1 -1
- package/dist/data/verification/index.d.ts +4 -2
- package/dist/data/verification/index.d.ts.map +1 -1
- package/dist/data/verification/stepKind.cjs +217 -0
- package/dist/data/verification/stepKind.cjs.map +1 -0
- package/dist/data/verification/stepKind.d.ts +87 -0
- package/dist/data/verification/stepKind.d.ts.map +1 -0
- package/dist/data/verification/stepKind.js +217 -0
- package/dist/data/verification/stepKind.js.map +1 -0
- package/dist/data/verification/stepKindViews.cjs +111 -0
- package/dist/data/verification/stepKindViews.cjs.map +1 -0
- package/dist/data/verification/stepKindViews.d.ts +18 -0
- package/dist/data/verification/stepKindViews.d.ts.map +1 -0
- package/dist/data/verification/stepKindViews.js +111 -0
- package/dist/data/verification/stepKindViews.js.map +1 -0
- package/dist/data/verification/verifyReportTests.cjs +1 -0
- package/dist/data/verification/verifyReportTests.cjs.map +1 -1
- package/dist/data/verification/verifyReportTests.d.ts +2 -1
- package/dist/data/verification/verifyReportTests.d.ts.map +1 -1
- package/dist/data/verification/verifyReportTests.js +1 -0
- package/dist/data/verification/verifyReportTests.js.map +1 -1
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +14 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +1 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +16 -2
- package/dist/data.js.map +1 -1
- package/dist/index.cjs +16 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -1
- package/dist/rpc/FilterForm.cjs +9 -0
- package/dist/rpc/FilterForm.cjs.map +1 -1
- package/dist/rpc/FilterForm.d.ts +5 -1
- package/dist/rpc/FilterForm.d.ts.map +1 -1
- package/dist/rpc/FilterForm.js +9 -0
- package/dist/rpc/FilterForm.js.map +1 -1
- package/dist/rpc/OperationArgsForm.cjs +153 -0
- package/dist/rpc/OperationArgsForm.cjs.map +1 -0
- package/dist/rpc/OperationArgsForm.d.ts +16 -0
- package/dist/rpc/OperationArgsForm.d.ts.map +1 -0
- package/dist/rpc/OperationArgsForm.js +153 -0
- package/dist/rpc/OperationArgsForm.js.map +1 -0
- package/dist/rpc/OperationScheduleDialog.cjs +61 -17
- package/dist/rpc/OperationScheduleDialog.cjs.map +1 -1
- package/dist/rpc/OperationScheduleDialog.d.ts +3 -0
- package/dist/rpc/OperationScheduleDialog.d.ts.map +1 -1
- package/dist/rpc/OperationScheduleDialog.js +62 -18
- package/dist/rpc/OperationScheduleDialog.js.map +1 -1
- package/dist/rpc/OperationSchedules.cjs +6 -1
- package/dist/rpc/OperationSchedules.cjs.map +1 -1
- package/dist/rpc/OperationSchedules.d.ts.map +1 -1
- package/dist/rpc/OperationSchedules.js +6 -1
- package/dist/rpc/OperationSchedules.js.map +1 -1
- package/dist/rpc/operation-schedule-model.cjs +0 -7
- package/dist/rpc/operation-schedule-model.cjs.map +1 -1
- package/dist/rpc/operation-schedule-model.d.ts +0 -2
- package/dist/rpc/operation-schedule-model.d.ts.map +1 -1
- package/dist/rpc/operation-schedule-model.js +0 -7
- package/dist/rpc/operation-schedule-model.js.map +1 -1
- package/dist/rpc/operation-schedule-types.d.ts +6 -1
- package/dist/rpc/operation-schedule-types.d.ts.map +1 -1
- package/dist/rpc.cjs +2 -0
- package/dist/rpc.cjs.map +1 -1
- package/dist/rpc.d.ts +1 -0
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +2 -0
- package/dist/rpc.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThreadList.cjs","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":["useState","useRef","jsxs","jsx","Button","getRoots","buildReplyMap","sortReplies","Fragment","cn","CommentCard","copyText","Modal","Tabs","Markdown"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommentThreadList.cjs","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Extra content rendered with each root thread, including its maximized view. */\n renderRootDetails?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n renderRootDetails,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {renderRootDetails?.(root)}\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {message.id === maximizedRoot.id &&\n renderRootDetails?.(maximizedRoot)}\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":["useState","useRef","jsxs","jsx","Button","getRoots","buildReplyMap","sortReplies","Fragment","cn","CommentCard","copyText","Modal","Tabs","Markdown"],"mappings":"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"}
|
|
@@ -8,6 +8,8 @@ export type CommentThreadListProps = {
|
|
|
8
8
|
defaultExpanded?: boolean;
|
|
9
9
|
/** Extra content rendered above each root card (e.g. an anchor label). */
|
|
10
10
|
renderRootMeta?: (comment: Comment) => ReactNode;
|
|
11
|
+
/** Extra content rendered with each root thread, including its maximized view. */
|
|
12
|
+
renderRootDetails?: (comment: Comment) => ReactNode;
|
|
11
13
|
/** Props merged onto each thread wrapper (e.g. for scroll anchoring). */
|
|
12
14
|
getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;
|
|
13
15
|
/** Custom body renderer forwarded to each card. */
|
|
@@ -26,5 +28,5 @@ export type CommentThreadListProps = {
|
|
|
26
28
|
* optional inline reply box. Fully controlled — mutations route through the
|
|
27
29
|
* id-keyed callbacks.
|
|
28
30
|
*/
|
|
29
|
-
export declare function CommentThreadList({ comments, config, compact, defaultExpanded, renderRootMeta, getThreadProps, renderBody, threadToMarkdown, onUpdateStatus, onClose, onChecklistToggle, onDelete, onReply, }: CommentThreadListProps): import("react").JSX.Element | null;
|
|
31
|
+
export declare function CommentThreadList({ comments, config, compact, defaultExpanded, renderRootMeta, renderRootDetails, getThreadProps, renderBody, threadToMarkdown, onUpdateStatus, onClose, onChecklistToggle, onDelete, onReply, }: CommentThreadListProps): import("react").JSX.Element | null;
|
|
30
32
|
//# sourceMappingURL=CommentThreadList.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThreadList.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThreadList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAS9E,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,yEAAyE;IACzE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,cAAc,CAAC,cAAc,CAAC,CAAC;IACtE,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IACzC,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;IAC1D,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC;AAsDF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,MAAM,EACN,OAAe,EACf,eAAuB,EACvB,cAAc,EACd,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,EAAE,sBAAsB,
|
|
1
|
+
{"version":3,"file":"CommentThreadList.d.ts","sourceRoot":"","sources":["../../src/comments/CommentThreadList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAS9E,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gGAAgG;IAChG,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACjD,kFAAkF;IAClF,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,CAAC;IACpD,yEAAyE;IACzE,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,cAAc,CAAC,cAAc,CAAC,CAAC;IACtE,mDAAmD;IACnD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,CAAC;IACzC,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,KAAK,MAAM,CAAC;IAC1D,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,iBAAiB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnE,CAAC;AAsDF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,MAAM,EACN,OAAe,EACf,eAAuB,EACvB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,OAAO,EACP,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,EAAE,sBAAsB,sCAiKxB"}
|
|
@@ -65,6 +65,7 @@ function CommentThreadList({
|
|
|
65
65
|
compact = false,
|
|
66
66
|
defaultExpanded = false,
|
|
67
67
|
renderRootMeta,
|
|
68
|
+
renderRootDetails,
|
|
68
69
|
getThreadProps,
|
|
69
70
|
renderBody,
|
|
70
71
|
threadToMarkdown,
|
|
@@ -126,6 +127,7 @@ function CommentThreadList({
|
|
|
126
127
|
...onReply ? { onReply: () => setReplyingTo(root.id) } : {}
|
|
127
128
|
}
|
|
128
129
|
),
|
|
130
|
+
renderRootDetails == null ? void 0 : renderRootDetails(root),
|
|
129
131
|
replies.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: replies.map((reply) => /* @__PURE__ */ jsx(
|
|
130
132
|
CommentCard,
|
|
131
133
|
{
|
|
@@ -182,6 +184,7 @@ function CommentThreadList({
|
|
|
182
184
|
...onReply ? { onReply: () => setModalReplyTo(message.id) } : {}
|
|
183
185
|
}
|
|
184
186
|
),
|
|
187
|
+
message.id === maximizedRoot.id && (renderRootDetails == null ? void 0 : renderRootDetails(maximizedRoot)),
|
|
185
188
|
onReply && modalReplyTo === message.id && /* @__PURE__ */ jsx(
|
|
186
189
|
ReplyInput,
|
|
187
190
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommentThreadList.js","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommentThreadList.js","sources":["../../src/comments/CommentThreadList.tsx"],"sourcesContent":["import { useRef, useState, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../components/button\";\nimport { copyText } from \"../components/clipboard\";\nimport { Markdown } from \"../data/Markdown\";\nimport { Tabs } from \"../layout/Tabs\";\nimport { Modal } from \"../overlay/Modal\";\nimport { CommentCard } from \"./CommentCard\";\nimport { buildReplyMap, getRoots, sortReplies } from \"./comment-utils\";\nimport type { Comment, CommentConfig } from \"./comment-types\";\n\nexport type CommentThreadListProps = {\n comments: Comment[];\n config: CommentConfig;\n compact?: boolean;\n /** Expand each thread immediately (roots and their replies), exposing actions such as Reply. */\n defaultExpanded?: boolean;\n /** Extra content rendered above each root card (e.g. an anchor label). */\n renderRootMeta?: (comment: Comment) => ReactNode;\n /** Extra content rendered with each root thread, including its maximized view. */\n renderRootDetails?: (comment: Comment) => ReactNode;\n /** Props merged onto each thread wrapper (e.g. for scroll anchoring). */\n getThreadProps?: (comment: Comment) => HTMLAttributes<HTMLDivElement>;\n /** Custom body renderer forwarded to each card. */\n renderBody?: (body: string) => ReactNode;\n /** Serialize a root and its replies for Copy and the Markdown preview. */\n threadToMarkdown?: (thread: readonly Comment[]) => string;\n onUpdateStatus?: (id: string, status: string) => void | Promise<void>;\n onClose?: (id: string) => void | Promise<void>;\n onChecklistToggle?: (id: string, index: number) => void;\n onDelete?: (id: string) => void;\n /** Submit a reply to `parent`. Presence enables the inline reply affordance. */\n onReply?: (parent: Comment, body: string) => void | Promise<void>;\n};\n\nfunction ReplyInput({\n onSubmit,\n onCancel,\n}: {\n onSubmit: (body: string) => void | Promise<void>;\n onCancel: () => void;\n}) {\n const [body, setBody] = useState(\"\");\n const [submitting, setSubmitting] = useState(false);\n const submittingRef = useRef(false);\n async function send() {\n if (!body.trim() || submittingRef.current) return;\n submittingRef.current = true;\n setSubmitting(true);\n try {\n await onSubmit(body.trim());\n setBody(\"\");\n } finally {\n submittingRef.current = false;\n setSubmitting(false);\n }\n }\n return (\n <div className=\"ml-6 flex gap-1.5 py-1\" aria-busy={submitting}>\n <input\n autoFocus\n value={body}\n disabled={submitting}\n onChange={(e) => setBody(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void send();\n }\n if (e.key === \"Escape\") onCancel();\n }}\n placeholder=\"Write a reply…\"\n data-testid=\"comment-reply-input\"\n className=\"h-7 flex-1 rounded-md border border-border bg-background px-2 py-1 text-xs outline-none focus:border-primary/40\"\n />\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n disabled={submitting || !body.trim()}\n onClick={() => void send()}\n >\n Send\n </Button>\n </div>\n );\n}\n\n/**\n * Renders a list of threaded comments: each root with its sorted replies and an\n * optional inline reply box. Fully controlled — mutations route through the\n * id-keyed callbacks.\n */\nexport function CommentThreadList({\n comments,\n config,\n compact = false,\n defaultExpanded = false,\n renderRootMeta,\n renderRootDetails,\n getThreadProps,\n renderBody,\n threadToMarkdown,\n onUpdateStatus,\n onClose,\n onChecklistToggle,\n onDelete,\n onReply,\n}: CommentThreadListProps) {\n const [replyingTo, setReplyingTo] = useState<string | null>(null);\n // Maximizing is owned here, not by the card: a thread is the unit a reader\n // wants enlarged, and only this list knows a root's replies.\n const [maximizedRootId, setMaximizedRootId] = useState<string | null>(null);\n const [maximizedTab, setMaximizedTab] = useState<\"thread\" | \"markdown\">(\n \"thread\",\n );\n // Which message in the maximized thread has its reply composer open.\n const [modalReplyTo, setModalReplyTo] = useState<string | null>(null);\n const roots = getRoots(comments);\n const replyMap = buildReplyMap(comments);\n\n if (roots.length === 0) return null;\n\n const maximizedRoot = roots.find((root) => root.id === maximizedRootId);\n const maximizedThread = maximizedRoot\n ? [maximizedRoot, ...sortReplies(replyMap.get(maximizedRoot.id) ?? [])]\n : [];\n\n function closeMaximized() {\n setMaximizedRootId(null);\n setModalReplyTo(null);\n setMaximizedTab(\"thread\");\n }\n\n function cardHandlers(id: string) {\n return {\n ...(onUpdateStatus\n ? { onUpdateStatus: (status: string) => onUpdateStatus(id, status) }\n : {}),\n ...(onClose ? { onClose: () => onClose(id) } : {}),\n ...(onChecklistToggle\n ? { onChecklistToggle: (index: number) => onChecklistToggle(id, index) }\n : {}),\n ...(onDelete ? { onDelete: () => onDelete(id) } : {}),\n };\n }\n\n return (\n <>\n <div className=\"space-y-2\">\n {roots.map((root) => {\n const replies = sortReplies(replyMap.get(root.id) ?? []);\n const thread = [root, ...replies];\n const threadProps = getThreadProps?.(root) ?? {};\n return (\n <div\n key={root.id}\n data-testid=\"comment-thread-block\"\n data-comment-id={root.id}\n {...threadProps}\n className={cn(\"space-y-1.5 p-1\", threadProps.className)}\n >\n {renderRootMeta ? (\n <div className=\"px-1\">{renderRootMeta(root)}</div>\n ) : null}\n <CommentCard\n comment={root}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(root.id)}\n {...(onReply ? { onReply: () => setReplyingTo(root.id) } : {})}\n />\n {renderRootDetails?.(root)}\n {replies.length > 0 && (\n <div className=\"space-y-1.5\">\n {replies.map((reply) => (\n <CommentCard\n key={reply.id}\n comment={reply}\n config={config}\n compact={compact}\n defaultExpanded={defaultExpanded}\n onMaximize={() => setMaximizedRootId(root.id)}\n {...(threadToMarkdown\n ? { onCopy: () => copyText(threadToMarkdown(thread)) }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(reply.id)}\n />\n ))}\n </div>\n )}\n {onReply && replyingTo === root.id && (\n <ReplyInput\n onCancel={() => setReplyingTo(null)}\n onSubmit={async (body) => {\n await onReply(root, body);\n setReplyingTo(null);\n }}\n />\n )}\n </div>\n );\n })}\n </div>\n {maximizedRoot && (\n <Modal open onClose={closeMaximized} title=\"Comment thread\" size=\"lg\">\n <div className=\"space-y-3\" data-testid=\"comment-thread-modal\">\n {threadToMarkdown && (\n <Tabs\n tabs={[\n { id: \"thread\", label: \"Thread\" },\n { id: \"markdown\", label: \"Markdown\" },\n ]}\n value={maximizedTab}\n onChange={(tab) =>\n setMaximizedTab(tab === \"markdown\" ? \"markdown\" : \"thread\")\n }\n />\n )}\n {maximizedTab === \"markdown\" && threadToMarkdown ? (\n <Markdown text={threadToMarkdown(maximizedThread)} />\n ) : (\n <div className=\"space-y-2\">\n {/* No onMaximize inside: the thread is already maximized. */}\n {maximizedThread.map((message) => (\n <div key={message.id} className=\"space-y-1.5\">\n <CommentCard\n comment={message}\n config={config}\n defaultExpanded\n {...(threadToMarkdown\n ? {\n onCopy: () =>\n copyText(threadToMarkdown(maximizedThread)),\n }\n : {})}\n {...(renderBody ? { renderBody } : {})}\n {...cardHandlers(message.id)}\n {...(onReply\n ? { onReply: () => setModalReplyTo(message.id) }\n : {})}\n />\n {message.id === maximizedRoot.id &&\n renderRootDetails?.(maximizedRoot)}\n {onReply && modalReplyTo === message.id && (\n <ReplyInput\n onCancel={() => setModalReplyTo(null)}\n onSubmit={async (body) => {\n await onReply(maximizedRoot, body);\n setModalReplyTo(null);\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n </Modal>\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
package/dist/data/CodeLine.cjs
CHANGED
|
@@ -2,20 +2,22 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const jsxRuntime = require("react/jsx-runtime");
|
|
4
4
|
const react = require("react");
|
|
5
|
+
const useTheme = require("../hooks/use-theme.cjs");
|
|
5
6
|
const codeHighlight = require("./code-highlight.cjs");
|
|
6
7
|
const HighlightedTokens = require("./HighlightedTokens.cjs");
|
|
7
8
|
function CodeLine({ code, language, className }) {
|
|
8
9
|
const [tokens, setTokens] = react.useState(void 0);
|
|
10
|
+
const theme = useTheme.useResolvedTheme();
|
|
9
11
|
react.useEffect(() => {
|
|
10
12
|
let cancelled = false;
|
|
11
13
|
setTokens(void 0);
|
|
12
|
-
codeHighlight.highlightToLines(code, { lang: language }).then((out) => {
|
|
14
|
+
codeHighlight.highlightToLines(code, { lang: language, theme: theme === "dark" ? "github-dark" : "github-light" }).then((out) => {
|
|
13
15
|
if (!cancelled) setTokens(out == null ? void 0 : out[0]);
|
|
14
16
|
});
|
|
15
17
|
return () => {
|
|
16
18
|
cancelled = true;
|
|
17
19
|
};
|
|
18
|
-
}, [code, language]);
|
|
20
|
+
}, [code, language, theme]);
|
|
19
21
|
return /* @__PURE__ */ jsxRuntime.jsx("code", { className: ["whitespace-pre font-mono", className].filter(Boolean).join(" "), children: /* @__PURE__ */ jsxRuntime.jsx(HighlightedTokens.HighlightedTokens, { tokens, content: code }) });
|
|
20
22
|
}
|
|
21
23
|
exports.CodeLine = CodeLine;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeLine.cjs","sources":["../../src/data/CodeLine.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { highlightToLines, type HighlightedLine } from \"./code-highlight\";\nimport { HighlightedTokens } from \"./HighlightedTokens\";\n\nexport interface CodeLineProps {\n code: string;\n /** Shiki language id; without one the line renders as plain text. */\n language?: string;\n className?: string;\n}\n\n// CodeLine paints a single source line, syntax-highlighted, inline — the line a\n// stack frame or trace node ran, shown without the gutter and frame of a block.\nexport function CodeLine({ code, language, className }: CodeLineProps) {\n const [tokens, setTokens] = useState<HighlightedLine | undefined>(undefined);\n\n useEffect(() => {\n let cancelled = false;\n setTokens(undefined);\n highlightToLines(code, { lang: language }).then((out) => {\n if (!cancelled) setTokens(out?.[0]);\n });\n return () => {\n cancelled = true;\n };\n }, [code, language]);\n\n return (\n <code className={[\"whitespace-pre font-mono\", className].filter(Boolean).join(\" \")}>\n <HighlightedTokens tokens={tokens} content={code} />\n </code>\n );\n}\n"],"names":["useState","useEffect","highlightToLines","jsx","HighlightedTokens"],"mappings":"
|
|
1
|
+
{"version":3,"file":"CodeLine.cjs","sources":["../../src/data/CodeLine.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useResolvedTheme } from \"../hooks/use-theme\";\nimport { highlightToLines, type HighlightedLine } from \"./code-highlight\";\nimport { HighlightedTokens } from \"./HighlightedTokens\";\n\nexport interface CodeLineProps {\n code: string;\n /** Shiki language id; without one the line renders as plain text. */\n language?: string;\n className?: string;\n}\n\n// CodeLine paints a single source line, syntax-highlighted, inline — the line a\n// stack frame or trace node ran, shown without the gutter and frame of a block.\nexport function CodeLine({ code, language, className }: CodeLineProps) {\n const [tokens, setTokens] = useState<HighlightedLine | undefined>(undefined);\n const theme = useResolvedTheme();\n\n useEffect(() => {\n let cancelled = false;\n setTokens(undefined);\n highlightToLines(code, { lang: language, theme: theme === \"dark\" ? \"github-dark\" : \"github-light\" }).then((out) => {\n if (!cancelled) setTokens(out?.[0]);\n });\n return () => {\n cancelled = true;\n };\n }, [code, language, theme]);\n\n return (\n <code className={[\"whitespace-pre font-mono\", className].filter(Boolean).join(\" \")}>\n <HighlightedTokens tokens={tokens} content={code} />\n </code>\n );\n}\n"],"names":["useState","useResolvedTheme","useEffect","highlightToLines","jsx","HighlightedTokens"],"mappings":";;;;;;;AAcO,SAAS,SAAS,EAAE,MAAM,UAAU,aAA4B;AACrE,QAAM,CAAC,QAAQ,SAAS,IAAIA,MAAAA,SAAsC,MAAS;AAC3E,QAAM,QAAQC,SAAAA,iBAAA;AAEdC,QAAAA,UAAU,MAAM;AACd,QAAI,YAAY;AAChB,cAAU,MAAS;AACnBC,kBAAAA,iBAAiB,MAAM,EAAE,MAAM,UAAU,OAAO,UAAU,SAAS,gBAAgB,eAAA,CAAgB,EAAE,KAAK,CAAC,QAAQ;AACjH,UAAI,CAAC,UAAW,WAAU,2BAAM,EAAE;AAAA,IACpC,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,UAAU,KAAK,CAAC;AAE1B,wCACG,QAAA,EAAK,WAAW,CAAC,4BAA4B,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAC/E,UAAAC,2BAAAA,IAACC,kBAAAA,qBAAkB,QAAgB,SAAS,MAAM,GACpD;AAEJ;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeLine.d.ts","sourceRoot":"","sources":["../../src/data/CodeLine.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CodeLine.d.ts","sourceRoot":"","sources":["../../src/data/CodeLine.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,aAAa,+BAoBpE"}
|
package/dist/data/CodeLine.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from "react";
|
|
3
|
+
import { useResolvedTheme } from "../hooks/use-theme.js";
|
|
3
4
|
import { highlightToLines } from "./code-highlight.js";
|
|
4
5
|
import { HighlightedTokens } from "./HighlightedTokens.js";
|
|
5
6
|
function CodeLine({ code, language, className }) {
|
|
6
7
|
const [tokens, setTokens] = useState(void 0);
|
|
8
|
+
const theme = useResolvedTheme();
|
|
7
9
|
useEffect(() => {
|
|
8
10
|
let cancelled = false;
|
|
9
11
|
setTokens(void 0);
|
|
10
|
-
highlightToLines(code, { lang: language }).then((out) => {
|
|
12
|
+
highlightToLines(code, { lang: language, theme: theme === "dark" ? "github-dark" : "github-light" }).then((out) => {
|
|
11
13
|
if (!cancelled) setTokens(out == null ? void 0 : out[0]);
|
|
12
14
|
});
|
|
13
15
|
return () => {
|
|
14
16
|
cancelled = true;
|
|
15
17
|
};
|
|
16
|
-
}, [code, language]);
|
|
18
|
+
}, [code, language, theme]);
|
|
17
19
|
return /* @__PURE__ */ jsx("code", { className: ["whitespace-pre font-mono", className].filter(Boolean).join(" "), children: /* @__PURE__ */ jsx(HighlightedTokens, { tokens, content: code }) });
|
|
18
20
|
}
|
|
19
21
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeLine.js","sources":["../../src/data/CodeLine.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { highlightToLines, type HighlightedLine } from \"./code-highlight\";\nimport { HighlightedTokens } from \"./HighlightedTokens\";\n\nexport interface CodeLineProps {\n code: string;\n /** Shiki language id; without one the line renders as plain text. */\n language?: string;\n className?: string;\n}\n\n// CodeLine paints a single source line, syntax-highlighted, inline — the line a\n// stack frame or trace node ran, shown without the gutter and frame of a block.\nexport function CodeLine({ code, language, className }: CodeLineProps) {\n const [tokens, setTokens] = useState<HighlightedLine | undefined>(undefined);\n\n useEffect(() => {\n let cancelled = false;\n setTokens(undefined);\n highlightToLines(code, { lang: language }).then((out) => {\n if (!cancelled) setTokens(out?.[0]);\n });\n return () => {\n cancelled = true;\n };\n }, [code, language]);\n\n return (\n <code className={[\"whitespace-pre font-mono\", className].filter(Boolean).join(\" \")}>\n <HighlightedTokens tokens={tokens} content={code} />\n </code>\n );\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CodeLine.js","sources":["../../src/data/CodeLine.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useResolvedTheme } from \"../hooks/use-theme\";\nimport { highlightToLines, type HighlightedLine } from \"./code-highlight\";\nimport { HighlightedTokens } from \"./HighlightedTokens\";\n\nexport interface CodeLineProps {\n code: string;\n /** Shiki language id; without one the line renders as plain text. */\n language?: string;\n className?: string;\n}\n\n// CodeLine paints a single source line, syntax-highlighted, inline — the line a\n// stack frame or trace node ran, shown without the gutter and frame of a block.\nexport function CodeLine({ code, language, className }: CodeLineProps) {\n const [tokens, setTokens] = useState<HighlightedLine | undefined>(undefined);\n const theme = useResolvedTheme();\n\n useEffect(() => {\n let cancelled = false;\n setTokens(undefined);\n highlightToLines(code, { lang: language, theme: theme === \"dark\" ? \"github-dark\" : \"github-light\" }).then((out) => {\n if (!cancelled) setTokens(out?.[0]);\n });\n return () => {\n cancelled = true;\n };\n }, [code, language, theme]);\n\n return (\n <code className={[\"whitespace-pre font-mono\", className].filter(Boolean).join(\" \")}>\n <HighlightedTokens tokens={tokens} content={code} />\n </code>\n );\n}\n"],"names":[],"mappings":";;;;;AAcO,SAAS,SAAS,EAAE,MAAM,UAAU,aAA4B;AACrE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAsC,MAAS;AAC3E,QAAM,QAAQ,iBAAA;AAEd,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,cAAU,MAAS;AACnB,qBAAiB,MAAM,EAAE,MAAM,UAAU,OAAO,UAAU,SAAS,gBAAgB,eAAA,CAAgB,EAAE,KAAK,CAAC,QAAQ;AACjH,UAAI,CAAC,UAAW,WAAU,2BAAM,EAAE;AAAA,IACpC,CAAC;AACD,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,MAAM,UAAU,KAAK,CAAC;AAE1B,6BACG,QAAA,EAAK,WAAW,CAAC,4BAA4B,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GAC/E,UAAA,oBAAC,qBAAkB,QAAgB,SAAS,MAAM,GACpD;AAEJ;"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const react = require("react");
|
|
5
|
+
const utils = require("../lib/utils.cjs");
|
|
6
|
+
const badgeVariants = require("./badge-variants.cjs");
|
|
7
|
+
const CodeLine = require("./CodeLine.cjs");
|
|
8
|
+
const Icon = require("./Icon.cjs");
|
|
9
|
+
const COLORS = /* @__PURE__ */ new Map([
|
|
10
|
+
["neutral", "text-foreground"],
|
|
11
|
+
["primary", "text-primary"],
|
|
12
|
+
["blue", "text-blue-700 [[data-theme=dark]_&]:text-blue-300"],
|
|
13
|
+
["cyan", "text-cyan-700 [[data-theme=dark]_&]:text-cyan-300"],
|
|
14
|
+
["indigo", "text-indigo-700 [[data-theme=dark]_&]:text-indigo-300"],
|
|
15
|
+
["sky", "text-sky-700 [[data-theme=dark]_&]:text-sky-300"],
|
|
16
|
+
["slate", "text-slate-700 [[data-theme=dark]_&]:text-slate-300"],
|
|
17
|
+
["violet", "text-violet-700 [[data-theme=dark]_&]:text-violet-300"]
|
|
18
|
+
]);
|
|
19
|
+
function SymbolBadge({
|
|
20
|
+
style = "badge",
|
|
21
|
+
color = "indigo",
|
|
22
|
+
icon,
|
|
23
|
+
iconClassName,
|
|
24
|
+
language,
|
|
25
|
+
children,
|
|
26
|
+
className,
|
|
27
|
+
title,
|
|
28
|
+
...props
|
|
29
|
+
}) {
|
|
30
|
+
if (typeof children === "string" ? !children.trim() || /[\r\n]/.test(children) : !react.isValidElement(children)) {
|
|
31
|
+
throw new Error("SymbolBadge requires a nonempty, single-line symbol; use CodeBlock for multiline source.");
|
|
32
|
+
}
|
|
33
|
+
const colorClass = COLORS.get(color);
|
|
34
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
35
|
+
"span",
|
|
36
|
+
{
|
|
37
|
+
...props,
|
|
38
|
+
"data-symbol-style": style,
|
|
39
|
+
title: title ?? (typeof children === "string" ? children : void 0),
|
|
40
|
+
className: utils.cn(
|
|
41
|
+
badgeVariants.badgeVariants({ variant: "outline", size: "md" }),
|
|
42
|
+
"max-w-full items-baseline gap-1.5 rounded-md align-baseline text-[12px] leading-4",
|
|
43
|
+
style === "badge" && "border-transparent",
|
|
44
|
+
style === "badge-outline" && "border-current",
|
|
45
|
+
style === "inline" && "rounded-none border-0 p-0",
|
|
46
|
+
colorClass,
|
|
47
|
+
className
|
|
48
|
+
),
|
|
49
|
+
style: {
|
|
50
|
+
...colorClass === void 0 ? { color } : {},
|
|
51
|
+
...style === "badge" ? { backgroundColor: `color-mix(in srgb, ${colorClass === void 0 ? color : "currentColor"} 16%, transparent)` } : {}
|
|
52
|
+
},
|
|
53
|
+
children: [
|
|
54
|
+
icon !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { "data-symbol-icon": true, "aria-hidden": true, className: "relative w-[1.25em] shrink-0 before:invisible before:content-['.']", children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon, width: "1.25em", className: utils.cn("absolute top-1/2 -translate-y-1/2", iconClassName) }) }),
|
|
55
|
+
typeof children === "string" ? /* @__PURE__ */ jsxRuntime.jsx(CodeLine.CodeLine, { code: children, ...language !== void 0 ? { language } : {}, className: "min-w-0 truncate" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 whitespace-normal", children })
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
exports.SymbolBadge = SymbolBadge;
|
|
61
|
+
//# sourceMappingURL=SymbolBadge.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SymbolBadge.cjs","sources":["../../src/data/SymbolBadge.tsx"],"sourcesContent":["import { isValidElement, type HTMLAttributes, type ReactElement } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { badgeVariants } from \"./badge-variants\";\nimport { CodeLine } from \"./CodeLine\";\nimport { Icon, type StaticIconComponent } from \"./Icon\";\n\nconst COLORS = new Map([\n [\"neutral\", \"text-foreground\"],\n [\"primary\", \"text-primary\"],\n [\"blue\", \"text-blue-700 [[data-theme=dark]_&]:text-blue-300\"],\n [\"cyan\", \"text-cyan-700 [[data-theme=dark]_&]:text-cyan-300\"],\n [\"indigo\", \"text-indigo-700 [[data-theme=dark]_&]:text-indigo-300\"],\n [\"sky\", \"text-sky-700 [[data-theme=dark]_&]:text-sky-300\"],\n [\"slate\", \"text-slate-700 [[data-theme=dark]_&]:text-slate-300\"],\n [\"violet\", \"text-violet-700 [[data-theme=dark]_&]:text-violet-300\"],\n]);\n\nexport type SymbolBadgeStyle = \"badge\" | \"badge-outline\" | \"inline\";\n\nexport type SymbolBadgeProps = Omit<HTMLAttributes<HTMLSpanElement>, \"style\" | \"color\" | \"children\"> & {\n /** Filled badge, colored outline, or an unframed inline symbol. */\n style?: SymbolBadgeStyle;\n /** Named palette hue or CSS color, including CSS custom properties. */\n color?: string;\n /** Imported icon component placed before the symbol. */\n icon?: StaticIconComponent;\n /** Additional glyph classes, including adjustments for fixed-color SVGs. */\n iconClassName?: string;\n} & (\n | { /** Literal, nonempty, single-line symbol or expression. */ children: string; language?: string }\n | { /** Content already formatted by the caller, such as MathML. */ children: ReactElement; language?: never }\n);\n\nexport function SymbolBadge({\n style = \"badge\",\n color = \"indigo\",\n icon,\n iconClassName,\n language,\n children,\n className,\n title,\n ...props\n}: SymbolBadgeProps) {\n if (typeof children === \"string\" ? !children.trim() || /[\\r\\n]/.test(children) : !isValidElement(children)) {\n throw new Error(\"SymbolBadge requires a nonempty, single-line symbol; use CodeBlock for multiline source.\");\n }\n const colorClass = COLORS.get(color);\n return (\n <span\n {...props}\n data-symbol-style={style}\n title={title ?? (typeof children === \"string\" ? children : undefined)}\n className={cn(\n badgeVariants({ variant: \"outline\", size: \"md\" }),\n \"max-w-full items-baseline gap-1.5 rounded-md align-baseline text-[12px] leading-4\",\n style === \"badge\" && \"border-transparent\",\n style === \"badge-outline\" && \"border-current\",\n style === \"inline\" && \"rounded-none border-0 p-0\",\n colorClass,\n className,\n )}\n style={{\n ...(colorClass === undefined ? { color } : {}),\n ...(style === \"badge\" ? { backgroundColor: `color-mix(in srgb, ${colorClass === undefined ? color : \"currentColor\"} 16%, transparent)` } : {}),\n }}\n >\n {icon !== undefined && <span data-symbol-icon aria-hidden className=\"relative w-[1.25em] shrink-0 before:invisible before:content-['.']\">\n <Icon icon={icon} width=\"1.25em\" className={cn(\"absolute top-1/2 -translate-y-1/2\", iconClassName)} />\n </span>}\n {typeof children === \"string\"\n ? <CodeLine code={children} {...(language !== undefined ? { language } : {})} className=\"min-w-0 truncate\" />\n : <span className=\"min-w-0 whitespace-normal\">{children}</span>}\n </span>\n );\n}\n"],"names":["isValidElement","jsxs","cn","badgeVariants","jsx","Icon","CodeLine"],"mappings":";;;;;;;;AAMA,MAAM,6BAAa,IAAI;AAAA,EACrB,CAAC,WAAW,iBAAiB;AAAA,EAC7B,CAAC,WAAW,cAAc;AAAA,EAC1B,CAAC,QAAQ,mDAAmD;AAAA,EAC5D,CAAC,QAAQ,mDAAmD;AAAA,EAC5D,CAAC,UAAU,uDAAuD;AAAA,EAClE,CAAC,OAAO,iDAAiD;AAAA,EACzD,CAAC,SAAS,qDAAqD;AAAA,EAC/D,CAAC,UAAU,uDAAuD;AACpE,CAAC;AAkBM,SAAS,YAAY;AAAA,EAC1B,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,MAAI,OAAO,aAAa,WAAW,CAAC,SAAS,KAAA,KAAU,SAAS,KAAK,QAAQ,IAAI,CAACA,MAAAA,eAAe,QAAQ,GAAG;AAC1G,UAAM,IAAI,MAAM,0FAA0F;AAAA,EAC5G;AACA,QAAM,aAAa,OAAO,IAAI,KAAK;AACnC,SACEC,2BAAAA;AAAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ,qBAAmB;AAAA,MACnB,OAAO,UAAU,OAAO,aAAa,WAAW,WAAW;AAAA,MAC3D,WAAWC,MAAAA;AAAAA,QACTC,cAAAA,cAAc,EAAE,SAAS,WAAW,MAAM,MAAM;AAAA,QAChD;AAAA,QACA,UAAU,WAAW;AAAA,QACrB,UAAU,mBAAmB;AAAA,QAC7B,UAAU,YAAY;AAAA,QACtB;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,OAAO;AAAA,QACL,GAAI,eAAe,SAAY,EAAE,MAAA,IAAU,CAAA;AAAA,QAC3C,GAAI,UAAU,UAAU,EAAE,iBAAiB,sBAAsB,eAAe,SAAY,QAAQ,cAAc,qBAAA,IAAyB,CAAA;AAAA,MAAC;AAAA,MAG7I,UAAA;AAAA,QAAA,SAAS,UAAaC,+BAAC,QAAA,EAAK,oBAAgB,MAAC,eAAW,MAAC,WAAU,sEAClE,yCAACC,WAAA,EAAK,MAAY,OAAM,UAAS,WAAWH,MAAAA,GAAG,qCAAqC,aAAa,GAAG,EAAA,CACtG;AAAA,QACC,OAAO,aAAa,WACjBE,2BAAAA,IAACE,SAAAA,YAAS,MAAM,UAAW,GAAI,aAAa,SAAY,EAAE,aAAa,CAAA,GAAK,WAAU,oBAAmB,IACzGF,+BAAC,QAAA,EAAK,WAAU,6BAA6B,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGhE;;"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactElement } from 'react';
|
|
2
|
+
import { StaticIconComponent } from './Icon.js';
|
|
3
|
+
export type SymbolBadgeStyle = "badge" | "badge-outline" | "inline";
|
|
4
|
+
export type SymbolBadgeProps = Omit<HTMLAttributes<HTMLSpanElement>, "style" | "color" | "children"> & {
|
|
5
|
+
/** Filled badge, colored outline, or an unframed inline symbol. */
|
|
6
|
+
style?: SymbolBadgeStyle;
|
|
7
|
+
/** Named palette hue or CSS color, including CSS custom properties. */
|
|
8
|
+
color?: string;
|
|
9
|
+
/** Imported icon component placed before the symbol. */
|
|
10
|
+
icon?: StaticIconComponent;
|
|
11
|
+
/** Additional glyph classes, including adjustments for fixed-color SVGs. */
|
|
12
|
+
iconClassName?: string;
|
|
13
|
+
} & ({
|
|
14
|
+
children: string;
|
|
15
|
+
language?: string;
|
|
16
|
+
} | {
|
|
17
|
+
children: ReactElement;
|
|
18
|
+
language?: never;
|
|
19
|
+
});
|
|
20
|
+
export declare function SymbolBadge({ style, color, icon, iconClassName, language, children, className, title, ...props }: SymbolBadgeProps): import("react").JSX.Element;
|
|
21
|
+
//# sourceMappingURL=SymbolBadge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SymbolBadge.d.ts","sourceRoot":"","sources":["../../src/data/SymbolBadge.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAI/E,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AAaxD,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,eAAe,GAAG,QAAQ,CAAC;AAEpE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,UAAU,CAAC,GAAG;IACrG,mEAAmE;IACnE,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,CACA;IAA8D,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACnG;IAAkE,QAAQ,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,CAC/G,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,KAAe,EACf,KAAgB,EAChB,IAAI,EACJ,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,KAAK,EACL,GAAG,KAAK,EACT,EAAE,gBAAgB,+BAgClB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { isValidElement } from "react";
|
|
3
|
+
import { cn } from "../lib/utils.js";
|
|
4
|
+
import { badgeVariants } from "./badge-variants.js";
|
|
5
|
+
import { CodeLine } from "./CodeLine.js";
|
|
6
|
+
import { Icon } from "./Icon.js";
|
|
7
|
+
const COLORS = /* @__PURE__ */ new Map([
|
|
8
|
+
["neutral", "text-foreground"],
|
|
9
|
+
["primary", "text-primary"],
|
|
10
|
+
["blue", "text-blue-700 [[data-theme=dark]_&]:text-blue-300"],
|
|
11
|
+
["cyan", "text-cyan-700 [[data-theme=dark]_&]:text-cyan-300"],
|
|
12
|
+
["indigo", "text-indigo-700 [[data-theme=dark]_&]:text-indigo-300"],
|
|
13
|
+
["sky", "text-sky-700 [[data-theme=dark]_&]:text-sky-300"],
|
|
14
|
+
["slate", "text-slate-700 [[data-theme=dark]_&]:text-slate-300"],
|
|
15
|
+
["violet", "text-violet-700 [[data-theme=dark]_&]:text-violet-300"]
|
|
16
|
+
]);
|
|
17
|
+
function SymbolBadge({
|
|
18
|
+
style = "badge",
|
|
19
|
+
color = "indigo",
|
|
20
|
+
icon,
|
|
21
|
+
iconClassName,
|
|
22
|
+
language,
|
|
23
|
+
children,
|
|
24
|
+
className,
|
|
25
|
+
title,
|
|
26
|
+
...props
|
|
27
|
+
}) {
|
|
28
|
+
if (typeof children === "string" ? !children.trim() || /[\r\n]/.test(children) : !isValidElement(children)) {
|
|
29
|
+
throw new Error("SymbolBadge requires a nonempty, single-line symbol; use CodeBlock for multiline source.");
|
|
30
|
+
}
|
|
31
|
+
const colorClass = COLORS.get(color);
|
|
32
|
+
return /* @__PURE__ */ jsxs(
|
|
33
|
+
"span",
|
|
34
|
+
{
|
|
35
|
+
...props,
|
|
36
|
+
"data-symbol-style": style,
|
|
37
|
+
title: title ?? (typeof children === "string" ? children : void 0),
|
|
38
|
+
className: cn(
|
|
39
|
+
badgeVariants({ variant: "outline", size: "md" }),
|
|
40
|
+
"max-w-full items-baseline gap-1.5 rounded-md align-baseline text-[12px] leading-4",
|
|
41
|
+
style === "badge" && "border-transparent",
|
|
42
|
+
style === "badge-outline" && "border-current",
|
|
43
|
+
style === "inline" && "rounded-none border-0 p-0",
|
|
44
|
+
colorClass,
|
|
45
|
+
className
|
|
46
|
+
),
|
|
47
|
+
style: {
|
|
48
|
+
...colorClass === void 0 ? { color } : {},
|
|
49
|
+
...style === "badge" ? { backgroundColor: `color-mix(in srgb, ${colorClass === void 0 ? color : "currentColor"} 16%, transparent)` } : {}
|
|
50
|
+
},
|
|
51
|
+
children: [
|
|
52
|
+
icon !== void 0 && /* @__PURE__ */ jsx("span", { "data-symbol-icon": true, "aria-hidden": true, className: "relative w-[1.25em] shrink-0 before:invisible before:content-['.']", children: /* @__PURE__ */ jsx(Icon, { icon, width: "1.25em", className: cn("absolute top-1/2 -translate-y-1/2", iconClassName) }) }),
|
|
53
|
+
typeof children === "string" ? /* @__PURE__ */ jsx(CodeLine, { code: children, ...language !== void 0 ? { language } : {}, className: "min-w-0 truncate" }) : /* @__PURE__ */ jsx("span", { className: "min-w-0 whitespace-normal", children })
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
SymbolBadge
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=SymbolBadge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SymbolBadge.js","sources":["../../src/data/SymbolBadge.tsx"],"sourcesContent":["import { isValidElement, type HTMLAttributes, type ReactElement } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { badgeVariants } from \"./badge-variants\";\nimport { CodeLine } from \"./CodeLine\";\nimport { Icon, type StaticIconComponent } from \"./Icon\";\n\nconst COLORS = new Map([\n [\"neutral\", \"text-foreground\"],\n [\"primary\", \"text-primary\"],\n [\"blue\", \"text-blue-700 [[data-theme=dark]_&]:text-blue-300\"],\n [\"cyan\", \"text-cyan-700 [[data-theme=dark]_&]:text-cyan-300\"],\n [\"indigo\", \"text-indigo-700 [[data-theme=dark]_&]:text-indigo-300\"],\n [\"sky\", \"text-sky-700 [[data-theme=dark]_&]:text-sky-300\"],\n [\"slate\", \"text-slate-700 [[data-theme=dark]_&]:text-slate-300\"],\n [\"violet\", \"text-violet-700 [[data-theme=dark]_&]:text-violet-300\"],\n]);\n\nexport type SymbolBadgeStyle = \"badge\" | \"badge-outline\" | \"inline\";\n\nexport type SymbolBadgeProps = Omit<HTMLAttributes<HTMLSpanElement>, \"style\" | \"color\" | \"children\"> & {\n /** Filled badge, colored outline, or an unframed inline symbol. */\n style?: SymbolBadgeStyle;\n /** Named palette hue or CSS color, including CSS custom properties. */\n color?: string;\n /** Imported icon component placed before the symbol. */\n icon?: StaticIconComponent;\n /** Additional glyph classes, including adjustments for fixed-color SVGs. */\n iconClassName?: string;\n} & (\n | { /** Literal, nonempty, single-line symbol or expression. */ children: string; language?: string }\n | { /** Content already formatted by the caller, such as MathML. */ children: ReactElement; language?: never }\n);\n\nexport function SymbolBadge({\n style = \"badge\",\n color = \"indigo\",\n icon,\n iconClassName,\n language,\n children,\n className,\n title,\n ...props\n}: SymbolBadgeProps) {\n if (typeof children === \"string\" ? !children.trim() || /[\\r\\n]/.test(children) : !isValidElement(children)) {\n throw new Error(\"SymbolBadge requires a nonempty, single-line symbol; use CodeBlock for multiline source.\");\n }\n const colorClass = COLORS.get(color);\n return (\n <span\n {...props}\n data-symbol-style={style}\n title={title ?? (typeof children === \"string\" ? children : undefined)}\n className={cn(\n badgeVariants({ variant: \"outline\", size: \"md\" }),\n \"max-w-full items-baseline gap-1.5 rounded-md align-baseline text-[12px] leading-4\",\n style === \"badge\" && \"border-transparent\",\n style === \"badge-outline\" && \"border-current\",\n style === \"inline\" && \"rounded-none border-0 p-0\",\n colorClass,\n className,\n )}\n style={{\n ...(colorClass === undefined ? { color } : {}),\n ...(style === \"badge\" ? { backgroundColor: `color-mix(in srgb, ${colorClass === undefined ? color : \"currentColor\"} 16%, transparent)` } : {}),\n }}\n >\n {icon !== undefined && <span data-symbol-icon aria-hidden className=\"relative w-[1.25em] shrink-0 before:invisible before:content-['.']\">\n <Icon icon={icon} width=\"1.25em\" className={cn(\"absolute top-1/2 -translate-y-1/2\", iconClassName)} />\n </span>}\n {typeof children === \"string\"\n ? <CodeLine code={children} {...(language !== undefined ? { language } : {})} className=\"min-w-0 truncate\" />\n : <span className=\"min-w-0 whitespace-normal\">{children}</span>}\n </span>\n );\n}\n"],"names":[],"mappings":";;;;;;AAMA,MAAM,6BAAa,IAAI;AAAA,EACrB,CAAC,WAAW,iBAAiB;AAAA,EAC7B,CAAC,WAAW,cAAc;AAAA,EAC1B,CAAC,QAAQ,mDAAmD;AAAA,EAC5D,CAAC,QAAQ,mDAAmD;AAAA,EAC5D,CAAC,UAAU,uDAAuD;AAAA,EAClE,CAAC,OAAO,iDAAiD;AAAA,EACzD,CAAC,SAAS,qDAAqD;AAAA,EAC/D,CAAC,UAAU,uDAAuD;AACpE,CAAC;AAkBM,SAAS,YAAY;AAAA,EAC1B,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAqB;AACnB,MAAI,OAAO,aAAa,WAAW,CAAC,SAAS,KAAA,KAAU,SAAS,KAAK,QAAQ,IAAI,CAAC,eAAe,QAAQ,GAAG;AAC1G,UAAM,IAAI,MAAM,0FAA0F;AAAA,EAC5G;AACA,QAAM,aAAa,OAAO,IAAI,KAAK;AACnC,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ,qBAAmB;AAAA,MACnB,OAAO,UAAU,OAAO,aAAa,WAAW,WAAW;AAAA,MAC3D,WAAW;AAAA,QACT,cAAc,EAAE,SAAS,WAAW,MAAM,MAAM;AAAA,QAChD;AAAA,QACA,UAAU,WAAW;AAAA,QACrB,UAAU,mBAAmB;AAAA,QAC7B,UAAU,YAAY;AAAA,QACtB;AAAA,QACA;AAAA,MAAA;AAAA,MAEF,OAAO;AAAA,QACL,GAAI,eAAe,SAAY,EAAE,MAAA,IAAU,CAAA;AAAA,QAC3C,GAAI,UAAU,UAAU,EAAE,iBAAiB,sBAAsB,eAAe,SAAY,QAAQ,cAAc,qBAAA,IAAyB,CAAA;AAAA,MAAC;AAAA,MAG7I,UAAA;AAAA,QAAA,SAAS,UAAa,oBAAC,QAAA,EAAK,oBAAgB,MAAC,eAAW,MAAC,WAAU,sEAClE,8BAAC,MAAA,EAAK,MAAY,OAAM,UAAS,WAAW,GAAG,qCAAqC,aAAa,GAAG,EAAA,CACtG;AAAA,QACC,OAAO,aAAa,WACjB,oBAAC,YAAS,MAAM,UAAW,GAAI,aAAa,SAAY,EAAE,aAAa,CAAA,GAAK,WAAU,oBAAmB,IACzG,oBAAC,QAAA,EAAK,WAAU,6BAA6B,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGhE;"}
|
|
@@ -4,6 +4,7 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
4
4
|
const react = require("react");
|
|
5
5
|
const Icon = require("../Icon.cjs");
|
|
6
6
|
const button = require("../../components/button.cjs");
|
|
7
|
+
const select = require("../../components/select.cjs");
|
|
7
8
|
const SplitButton = require("../../components/SplitButton.cjs");
|
|
8
9
|
const clipboard = require("../../components/clipboard.cjs");
|
|
9
10
|
const utils = require("../../lib/utils.cjs");
|
|
@@ -12,6 +13,7 @@ const TabButton = require("../TabButton.cjs");
|
|
|
12
13
|
const context = require("./context.cjs");
|
|
13
14
|
const copy = require("./copy.cjs");
|
|
14
15
|
const status = require("./status.cjs");
|
|
16
|
+
const marks = require("./marks.cjs");
|
|
15
17
|
const MarkIcons = require("./MarkIcons.cjs");
|
|
16
18
|
const TestFailureDetail = require("./TestFailureDetail.cjs");
|
|
17
19
|
const UiClass = require("../../icons/components/UiClass.cjs");
|
|
@@ -87,11 +89,11 @@ function CopyDetailsButton({ node }) {
|
|
|
87
89
|
}
|
|
88
90
|
);
|
|
89
91
|
}
|
|
90
|
-
function DetailHeader({ node, actions }) {
|
|
92
|
+
function DetailHeader({ node, actionNode, actions, attemptChooser }) {
|
|
91
93
|
const runner = context.useTestRunner();
|
|
92
94
|
const outcome = status.outcomeFor(node);
|
|
93
|
-
const canRerun = !!runner.onRerun &&
|
|
94
|
-
const canStop = !!runner.onStop && !!
|
|
95
|
+
const canRerun = !!runner.onRerun && actionNode.framework !== "task";
|
|
96
|
+
const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;
|
|
95
97
|
const location = node.file ? `${node.file}${node.line ? `:${node.line}` : ""}` : void 0;
|
|
96
98
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
97
99
|
"div",
|
|
@@ -102,7 +104,7 @@ function DetailHeader({ node, actions }) {
|
|
|
102
104
|
outcome ? /* @__PURE__ */ jsxRuntime.jsx(MarkIcons.OutcomeIcon, { outcome, className: "text-lg" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiClass.UiClass, className: "shrink-0 text-lg text-muted-foreground" }),
|
|
103
105
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "min-w-0 shrink truncate text-base font-semibold", title: node.name, children: node.name }),
|
|
104
106
|
node.framework && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground", children: [
|
|
105
|
-
/* @__PURE__ */ jsxRuntime.jsx(MarkIcons.ToolIcon, { tool: node
|
|
107
|
+
/* @__PURE__ */ jsxRuntime.jsx(MarkIcons.ToolIcon, { tool: marks.toolKeyFor(node), className: "text-sm" }),
|
|
106
108
|
node.framework
|
|
107
109
|
] }),
|
|
108
110
|
node.duration ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground", children: [
|
|
@@ -118,6 +120,7 @@ function DetailHeader({ node, actions }) {
|
|
|
118
120
|
}
|
|
119
121
|
),
|
|
120
122
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-1", children: [
|
|
123
|
+
attemptChooser,
|
|
121
124
|
actions,
|
|
122
125
|
/* @__PURE__ */ jsxRuntime.jsx(CopyDetailsButton, { node }),
|
|
123
126
|
canRerun && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -125,7 +128,7 @@ function DetailHeader({ node, actions }) {
|
|
|
125
128
|
{
|
|
126
129
|
size: "sm",
|
|
127
130
|
disabled: runner.busy.rerun,
|
|
128
|
-
onClick: () => runner.onRerun(
|
|
131
|
+
onClick: () => runner.onRerun(actionNode),
|
|
129
132
|
title: "Rerun this test or subtree",
|
|
130
133
|
children: [
|
|
131
134
|
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiRefresh.UiRefresh, className: "mr-1 text-sm" }),
|
|
@@ -139,7 +142,7 @@ function DetailHeader({ node, actions }) {
|
|
|
139
142
|
size: "sm",
|
|
140
143
|
variant: "destructive",
|
|
141
144
|
disabled: runner.busy.stop,
|
|
142
|
-
onClick: () => runner.onStop(
|
|
145
|
+
onClick: () => runner.onStop(actionNode),
|
|
143
146
|
title: "Stop this running task",
|
|
144
147
|
children: [
|
|
145
148
|
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiStop.UiStop, className: "mr-1 text-sm" }),
|
|
@@ -155,7 +158,8 @@ function DetailHeader({ node, actions }) {
|
|
|
155
158
|
function TestDetailPanel() {
|
|
156
159
|
var _a;
|
|
157
160
|
const runner = context.useTestRunner();
|
|
158
|
-
const
|
|
161
|
+
const selected = runner.selected;
|
|
162
|
+
const [attemptSelection, setAttemptSelection] = react.useState(null);
|
|
159
163
|
const controlled = runner.activeTab !== void 0;
|
|
160
164
|
const [internalTab, setInternalTab] = react.useState("detail");
|
|
161
165
|
const activeTab = controlled ? runner.activeTab : internalTab;
|
|
@@ -164,22 +168,32 @@ function TestDetailPanel() {
|
|
|
164
168
|
if (controlled) (_a2 = runner.onTabChange) == null ? void 0 : _a2.call(runner, id);
|
|
165
169
|
else setInternalTab(id);
|
|
166
170
|
};
|
|
167
|
-
if (!
|
|
171
|
+
if (!selected) {
|
|
168
172
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full items-center justify-center text-sm text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
169
173
|
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiClass.UiClass, className: "mb-2 block text-4xl" }),
|
|
170
174
|
"Select a test to view its details."
|
|
171
175
|
] }) });
|
|
172
176
|
}
|
|
177
|
+
const attempts = runner.adapters.resolveAttempts(selected, runner);
|
|
178
|
+
const key = selected.task_id ?? selected.name;
|
|
179
|
+
const attemptId = runner.activeAttempt ?? ((attemptSelection == null ? void 0 : attemptSelection.key) === key ? attemptSelection.id : void 0);
|
|
180
|
+
const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);
|
|
181
|
+
const node = attempts.length > 1 && attempt ? attempt.node : selected;
|
|
182
|
+
const attemptChooser = attempts.length > 1 && attempt ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-44 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(select.Select, { "aria-label": "Attempt", value: String(attempt.id), className: "h-7 text-xs", onChange: (event) => {
|
|
183
|
+
const id = Number(event.target.value);
|
|
184
|
+
if (runner.onAttemptChange) runner.onAttemptChange(id);
|
|
185
|
+
else setAttemptSelection({ key, id });
|
|
186
|
+
}, options: attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== void 0 ? ` · ${status.formatTestDuration(a.durationMs * 1e6)}` : ""}` })) }) }) : null;
|
|
173
187
|
const detailAdapter = runner.adapters.resolveDetail(node, runner);
|
|
174
|
-
const actionAdapter = runner.adapters.resolveActions(
|
|
188
|
+
const actionAdapter = runner.adapters.resolveActions(selected, runner);
|
|
175
189
|
const tabs = runner.adapters.resolveTabs(node, runner);
|
|
176
190
|
const ownsScroll = runner.adapters.ownsScroll(node, runner);
|
|
177
191
|
const body = (detailAdapter == null ? void 0 : detailAdapter.renderDetail) ? detailAdapter.renderDetail({ node, runner }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultDetailBody, { node });
|
|
178
|
-
const actions = (_a = actionAdapter == null ? void 0 : actionAdapter.nodeActions) == null ? void 0 : _a.call(actionAdapter, { node, runner });
|
|
192
|
+
const actions = (_a = actionAdapter == null ? void 0 : actionAdapter.nodeActions) == null ? void 0 : _a.call(actionAdapter, { node: selected, runner });
|
|
179
193
|
const detailRegion = utils.cn("min-h-0 flex-1 [container-type:size]", !ownsScroll && "overflow-y-auto");
|
|
180
194
|
if (tabs.length === 0) {
|
|
181
195
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
182
|
-
/* @__PURE__ */ jsxRuntime.jsx(DetailHeader, { node, actions }),
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsx(DetailHeader, { node, actionNode: selected, actions, attemptChooser }),
|
|
183
197
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: detailRegion, children: body })
|
|
184
198
|
] });
|
|
185
199
|
}
|
|
@@ -187,7 +201,7 @@ function TestDetailPanel() {
|
|
|
187
201
|
const allTabs = [detailTab, ...tabs];
|
|
188
202
|
const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;
|
|
189
203
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [
|
|
190
|
-
/* @__PURE__ */ jsxRuntime.jsx(DetailHeader, { node, actions }),
|
|
204
|
+
/* @__PURE__ */ jsxRuntime.jsx(DetailHeader, { node, actionNode: selected, actions, attemptChooser }),
|
|
191
205
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1", children: allTabs.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
192
206
|
TabButton.TabButton,
|
|
193
207
|
{
|