@agimon-ai/doompi-web-components 0.0.1-alpha.37 → 0.0.1-alpha.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.d.mts +2 -1
- package/dist/index.mjs +1 -1
- package/dist/src/components/Accordion.cjs +1 -1
- package/dist/src/components/Accordion.cjs.map +1 -1
- package/dist/src/components/Accordion.mjs +1 -1
- package/dist/src/components/Accordion.mjs.map +1 -1
- package/dist/src/components/AnsiText.cjs +3 -3
- package/dist/src/components/AnsiText.cjs.map +1 -1
- package/dist/src/components/AnsiText.mjs +3 -3
- package/dist/src/components/AnsiText.mjs.map +1 -1
- package/dist/src/components/Avatar.cjs +1 -1
- package/dist/src/components/Avatar.cjs.map +1 -1
- package/dist/src/components/Avatar.mjs +1 -1
- package/dist/src/components/Avatar.mjs.map +1 -1
- package/dist/src/components/Badge.cjs +1 -1
- package/dist/src/components/Badge.cjs.map +1 -1
- package/dist/src/components/Badge.mjs +1 -1
- package/dist/src/components/Badge.mjs.map +1 -1
- package/dist/src/components/Breadcrumb.cjs +1 -1
- package/dist/src/components/Breadcrumb.cjs.map +1 -1
- package/dist/src/components/Breadcrumb.mjs +1 -1
- package/dist/src/components/Breadcrumb.mjs.map +1 -1
- package/dist/src/components/Button.cjs +1 -1
- package/dist/src/components/Button.cjs.map +1 -1
- package/dist/src/components/Button.mjs +1 -1
- package/dist/src/components/Button.mjs.map +1 -1
- package/dist/src/components/Checkbox.cjs +1 -1
- package/dist/src/components/Checkbox.cjs.map +1 -1
- package/dist/src/components/Checkbox.mjs +1 -1
- package/dist/src/components/Checkbox.mjs.map +1 -1
- package/dist/src/components/CodeBlock.cjs +1 -1
- package/dist/src/components/CodeBlock.cjs.map +1 -1
- package/dist/src/components/CodeBlock.mjs +1 -1
- package/dist/src/components/CodeBlock.mjs.map +1 -1
- package/dist/src/components/CodeEditor.cjs +1 -1
- package/dist/src/components/CodeEditor.cjs.map +1 -1
- package/dist/src/components/CodeEditor.mjs +1 -1
- package/dist/src/components/CodeEditor.mjs.map +1 -1
- package/dist/src/components/CodeEditorView.cjs +1 -1
- package/dist/src/components/CodeEditorView.cjs.map +1 -1
- package/dist/src/components/CodeEditorView.mjs +1 -1
- package/dist/src/components/CodeEditorView.mjs.map +1 -1
- package/dist/src/components/Collapsible.cjs +1 -1
- package/dist/src/components/Collapsible.cjs.map +1 -1
- package/dist/src/components/Collapsible.mjs +1 -1
- package/dist/src/components/Collapsible.mjs.map +1 -1
- package/dist/src/components/Command.cjs +1 -1
- package/dist/src/components/Command.cjs.map +1 -1
- package/dist/src/components/Command.mjs +1 -1
- package/dist/src/components/Command.mjs.map +1 -1
- package/dist/src/components/CopyButton.cjs +1 -1
- package/dist/src/components/CopyButton.cjs.map +1 -1
- package/dist/src/components/CopyButton.mjs +1 -1
- package/dist/src/components/CopyButton.mjs.map +1 -1
- package/dist/src/components/Dialog.cjs +1 -1
- package/dist/src/components/Dialog.cjs.map +1 -1
- package/dist/src/components/Dialog.mjs +1 -1
- package/dist/src/components/Dialog.mjs.map +1 -1
- package/dist/src/components/Dot.cjs +1 -1
- package/dist/src/components/Dot.cjs.map +1 -1
- package/dist/src/components/Dot.mjs +1 -1
- package/dist/src/components/Dot.mjs.map +1 -1
- package/dist/src/components/Drawer.cjs +2 -0
- package/dist/src/components/Drawer.cjs.map +1 -0
- package/dist/src/components/Drawer.d.mts +17 -0
- package/dist/src/components/Drawer.d.mts.map +1 -0
- package/dist/src/components/Drawer.mjs +2 -0
- package/dist/src/components/Drawer.mjs.map +1 -0
- package/dist/src/components/DropdownMenu.cjs +1 -1
- package/dist/src/components/DropdownMenu.cjs.map +1 -1
- package/dist/src/components/DropdownMenu.mjs +1 -1
- package/dist/src/components/DropdownMenu.mjs.map +1 -1
- package/dist/src/components/EmptyState.cjs +1 -1
- package/dist/src/components/EmptyState.cjs.map +1 -1
- package/dist/src/components/EmptyState.mjs +1 -1
- package/dist/src/components/EmptyState.mjs.map +1 -1
- package/dist/src/components/HashlineLines.cjs +1 -1
- package/dist/src/components/HashlineLines.cjs.map +1 -1
- package/dist/src/components/HashlineLines.mjs +1 -1
- package/dist/src/components/HashlineLines.mjs.map +1 -1
- package/dist/src/components/Input.cjs +1 -1
- package/dist/src/components/Input.cjs.map +1 -1
- package/dist/src/components/Input.mjs +1 -1
- package/dist/src/components/Input.mjs.map +1 -1
- package/dist/src/components/Kbd.cjs +1 -1
- package/dist/src/components/Kbd.cjs.map +1 -1
- package/dist/src/components/Kbd.mjs +1 -1
- package/dist/src/components/Kbd.mjs.map +1 -1
- package/dist/src/components/Label.cjs +1 -1
- package/dist/src/components/Label.cjs.map +1 -1
- package/dist/src/components/Label.mjs +1 -1
- package/dist/src/components/Label.mjs.map +1 -1
- package/dist/src/components/Markdown.cjs +1 -1
- package/dist/src/components/Markdown.cjs.map +1 -1
- package/dist/src/components/Markdown.mjs +1 -1
- package/dist/src/components/Markdown.mjs.map +1 -1
- package/dist/src/components/MediaPreview.cjs +1 -1
- package/dist/src/components/MediaPreview.cjs.map +1 -1
- package/dist/src/components/MediaPreview.mjs +1 -1
- package/dist/src/components/MediaPreview.mjs.map +1 -1
- package/dist/src/components/MermaidDiagram.cjs +1 -1
- package/dist/src/components/MermaidDiagram.cjs.map +1 -1
- package/dist/src/components/MermaidDiagram.mjs +1 -1
- package/dist/src/components/MermaidDiagram.mjs.map +1 -1
- package/dist/src/components/MessageItem.cjs +1 -1
- package/dist/src/components/MessageItem.cjs.map +1 -1
- package/dist/src/components/MessageItem.mjs +1 -1
- package/dist/src/components/MessageItem.mjs.map +1 -1
- package/dist/src/components/MessageLines.cjs +1 -1
- package/dist/src/components/MessageLines.cjs.map +1 -1
- package/dist/src/components/MessageLines.mjs +1 -1
- package/dist/src/components/MessageLines.mjs.map +1 -1
- package/dist/src/components/OptionList.cjs +1 -1
- package/dist/src/components/OptionList.cjs.map +1 -1
- package/dist/src/components/OptionList.mjs +1 -1
- package/dist/src/components/OptionList.mjs.map +1 -1
- package/dist/src/components/Panel.cjs +1 -1
- package/dist/src/components/Panel.cjs.map +1 -1
- package/dist/src/components/Panel.mjs +1 -1
- package/dist/src/components/Panel.mjs.map +1 -1
- package/dist/src/components/PdfPreview.cjs +1 -1
- package/dist/src/components/PdfPreview.cjs.map +1 -1
- package/dist/src/components/PdfPreview.mjs +1 -1
- package/dist/src/components/PdfPreview.mjs.map +1 -1
- package/dist/src/components/Popover.cjs +1 -1
- package/dist/src/components/Popover.cjs.map +1 -1
- package/dist/src/components/Popover.mjs +1 -1
- package/dist/src/components/Popover.mjs.map +1 -1
- package/dist/src/components/Progress.cjs +1 -1
- package/dist/src/components/Progress.cjs.map +1 -1
- package/dist/src/components/Progress.mjs +1 -1
- package/dist/src/components/Progress.mjs.map +1 -1
- package/dist/src/components/RadioGroup.cjs +1 -1
- package/dist/src/components/RadioGroup.cjs.map +1 -1
- package/dist/src/components/RadioGroup.mjs +1 -1
- package/dist/src/components/RadioGroup.mjs.map +1 -1
- package/dist/src/components/ScrollArea.cjs +1 -1
- package/dist/src/components/ScrollArea.cjs.map +1 -1
- package/dist/src/components/ScrollArea.mjs +1 -1
- package/dist/src/components/ScrollArea.mjs.map +1 -1
- package/dist/src/components/SectionLabel.cjs +1 -1
- package/dist/src/components/SectionLabel.cjs.map +1 -1
- package/dist/src/components/SectionLabel.mjs +1 -1
- package/dist/src/components/SectionLabel.mjs.map +1 -1
- package/dist/src/components/Select.cjs +1 -1
- package/dist/src/components/Select.cjs.map +1 -1
- package/dist/src/components/Select.mjs +1 -1
- package/dist/src/components/Select.mjs.map +1 -1
- package/dist/src/components/Separator.cjs +1 -1
- package/dist/src/components/Separator.cjs.map +1 -1
- package/dist/src/components/Separator.mjs +1 -1
- package/dist/src/components/Separator.mjs.map +1 -1
- package/dist/src/components/Sheet.cjs +1 -1
- package/dist/src/components/Sheet.cjs.map +1 -1
- package/dist/src/components/Sheet.mjs +1 -1
- package/dist/src/components/Sheet.mjs.map +1 -1
- package/dist/src/components/Skeleton.cjs +1 -1
- package/dist/src/components/Skeleton.cjs.map +1 -1
- package/dist/src/components/Skeleton.mjs +1 -1
- package/dist/src/components/Skeleton.mjs.map +1 -1
- package/dist/src/components/Spinner.cjs +1 -1
- package/dist/src/components/Spinner.cjs.map +1 -1
- package/dist/src/components/Spinner.mjs +1 -1
- package/dist/src/components/Spinner.mjs.map +1 -1
- package/dist/src/components/StatusBadge.cjs +1 -1
- package/dist/src/components/StatusBadge.cjs.map +1 -1
- package/dist/src/components/StatusBadge.mjs +1 -1
- package/dist/src/components/StatusBadge.mjs.map +1 -1
- package/dist/src/components/StreamCursor.cjs +1 -1
- package/dist/src/components/StreamCursor.cjs.map +1 -1
- package/dist/src/components/StreamCursor.mjs +1 -1
- package/dist/src/components/StreamCursor.mjs.map +1 -1
- package/dist/src/components/Switch.cjs +1 -1
- package/dist/src/components/Switch.cjs.map +1 -1
- package/dist/src/components/Switch.mjs +1 -1
- package/dist/src/components/Switch.mjs.map +1 -1
- package/dist/src/components/SyntaxText.cjs +3 -3
- package/dist/src/components/SyntaxText.cjs.map +1 -1
- package/dist/src/components/SyntaxText.mjs +3 -3
- package/dist/src/components/SyntaxText.mjs.map +1 -1
- package/dist/src/components/Tabs.cjs +1 -1
- package/dist/src/components/Tabs.cjs.map +1 -1
- package/dist/src/components/Tabs.mjs +1 -1
- package/dist/src/components/Tabs.mjs.map +1 -1
- package/dist/src/components/TerminalView.cjs +1 -1
- package/dist/src/components/TerminalView.cjs.map +1 -1
- package/dist/src/components/TerminalView.mjs +1 -1
- package/dist/src/components/TerminalView.mjs.map +1 -1
- package/dist/src/components/Textarea.cjs +1 -1
- package/dist/src/components/Textarea.cjs.map +1 -1
- package/dist/src/components/Textarea.mjs +1 -1
- package/dist/src/components/Textarea.mjs.map +1 -1
- package/dist/src/components/Toast.cjs +1 -1
- package/dist/src/components/Toast.cjs.map +1 -1
- package/dist/src/components/Toast.mjs +1 -1
- package/dist/src/components/Toast.mjs.map +1 -1
- package/dist/src/components/ToolPathLink.cjs +1 -1
- package/dist/src/components/ToolPathLink.cjs.map +1 -1
- package/dist/src/components/ToolPathLink.mjs +1 -1
- package/dist/src/components/ToolPathLink.mjs.map +1 -1
- package/dist/src/components/Tooltip.cjs +1 -1
- package/dist/src/components/Tooltip.cjs.map +1 -1
- package/dist/src/components/Tooltip.mjs +1 -1
- package/dist/src/components/Tooltip.mjs.map +1 -1
- package/package.json +11 -11
- package/dist/components/Accordion.d.cts +0 -11
- package/dist/components/Accordion.d.cts.map +0 -1
- package/dist/components/AnsiText.d.cts +0 -16
- package/dist/components/AnsiText.d.cts.map +0 -1
- package/dist/components/Avatar.d.cts +0 -10
- package/dist/components/Avatar.d.cts.map +0 -1
- package/dist/components/Badge.d.cts +0 -17
- package/dist/components/Badge.d.cts.map +0 -1
- package/dist/components/Breadcrumb.d.cts +0 -36
- package/dist/components/Breadcrumb.d.cts.map +0 -1
- package/dist/components/Button.d.cts +0 -24
- package/dist/components/Button.d.cts.map +0 -1
- package/dist/components/Checkbox.d.cts +0 -8
- package/dist/components/Checkbox.d.cts.map +0 -1
- package/dist/components/CodeEditor.d.cts +0 -5
- package/dist/components/CodeEditor.d.cts.map +0 -1
- package/dist/components/Collapsible.d.cts +0 -10
- package/dist/components/Collapsible.d.cts.map +0 -1
- package/dist/components/Command.d.cts +0 -41
- package/dist/components/Command.d.cts.map +0 -1
- package/dist/components/Dialog.d.cts +0 -39
- package/dist/components/Dialog.d.cts.map +0 -1
- package/dist/components/Dot.d.cts +0 -15
- package/dist/components/Dot.d.cts.map +0 -1
- package/dist/components/DropdownMenu.d.cts +0 -34
- package/dist/components/DropdownMenu.d.cts.map +0 -1
- package/dist/components/EmptyState.d.cts +0 -17
- package/dist/components/EmptyState.d.cts.map +0 -1
- package/dist/components/HashlineLines.d.cts +0 -23
- package/dist/components/HashlineLines.d.cts.map +0 -1
- package/dist/components/Input.d.cts +0 -19
- package/dist/components/Input.d.cts.map +0 -1
- package/dist/components/Kbd.d.cts +0 -6
- package/dist/components/Kbd.d.cts.map +0 -1
- package/dist/components/Label.d.cts +0 -8
- package/dist/components/Label.d.cts.map +0 -1
- package/dist/components/Markdown.d.cts +0 -17
- package/dist/components/Markdown.d.cts.map +0 -1
- package/dist/components/MediaPreview.d.cts +0 -61
- package/dist/components/MediaPreview.d.cts.map +0 -1
- package/dist/components/MessageItem.d.cts +0 -102
- package/dist/components/MessageItem.d.cts.map +0 -1
- package/dist/components/MessageLines.d.cts +0 -23
- package/dist/components/MessageLines.d.cts.map +0 -1
- package/dist/components/OptionList.d.cts +0 -49
- package/dist/components/OptionList.d.cts.map +0 -1
- package/dist/components/Panel.d.cts +0 -14
- package/dist/components/Panel.d.cts.map +0 -1
- package/dist/components/PdfPreview.d.cts +0 -52
- package/dist/components/PdfPreview.d.cts.map +0 -1
- package/dist/components/Popover.d.cts +0 -19
- package/dist/components/Popover.d.cts.map +0 -1
- package/dist/components/Progress.d.cts +0 -8
- package/dist/components/Progress.d.cts.map +0 -1
- package/dist/components/RadioGroup.d.cts +0 -15
- package/dist/components/RadioGroup.d.cts.map +0 -1
- package/dist/components/ScrollArea.d.cts +0 -13
- package/dist/components/ScrollArea.d.cts.map +0 -1
- package/dist/components/SectionLabel.d.cts +0 -6
- package/dist/components/SectionLabel.d.cts.map +0 -1
- package/dist/components/Select.d.cts +0 -19
- package/dist/components/Select.d.cts.map +0 -1
- package/dist/components/Separator.d.cts +0 -13
- package/dist/components/Separator.d.cts.map +0 -1
- package/dist/components/Sheet.d.cts +0 -35
- package/dist/components/Sheet.d.cts.map +0 -1
- package/dist/components/Skeleton.d.cts +0 -10
- package/dist/components/Skeleton.d.cts.map +0 -1
- package/dist/components/Spinner.d.cts +0 -14
- package/dist/components/Spinner.d.cts.map +0 -1
- package/dist/components/StatusBadge.d.cts +0 -23
- package/dist/components/StatusBadge.d.cts.map +0 -1
- package/dist/components/StreamCursor.d.cts +0 -6
- package/dist/components/StreamCursor.d.cts.map +0 -1
- package/dist/components/Switch.d.cts +0 -8
- package/dist/components/Switch.d.cts.map +0 -1
- package/dist/components/SyntaxText.d.cts +0 -50
- package/dist/components/SyntaxText.d.cts.map +0 -1
- package/dist/components/Tabs.d.cts +0 -36
- package/dist/components/Tabs.d.cts.map +0 -1
- package/dist/components/TerminalView.d.cts +0 -31
- package/dist/components/TerminalView.d.cts.map +0 -1
- package/dist/components/Textarea.d.cts +0 -9
- package/dist/components/Textarea.d.cts.map +0 -1
- package/dist/components/Toast.d.cts +0 -24
- package/dist/components/Toast.d.cts.map +0 -1
- package/dist/components/ToolPathLink.d.cts +0 -22
- package/dist/components/ToolPathLink.d.cts.map +0 -1
- package/dist/components/Tooltip.d.cts +0 -11
- package/dist/components/Tooltip.d.cts.map +0 -1
- package/dist/icons/icons.d.cts +0 -2
- package/dist/index.d.cts +0 -59
- package/dist/lib/ansiSpans.d.cts +0 -42
- package/dist/lib/ansiSpans.d.cts.map +0 -1
- package/dist/lib/cn.d.cts +0 -6
- package/dist/lib/cn.d.cts.map +0 -1
- package/dist/lib/collapse.d.cts +0 -14
- package/dist/lib/collapse.d.cts.map +0 -1
- package/dist/lib/editorLanguage.d.cts +0 -21
- package/dist/lib/editorLanguage.d.cts.map +0 -1
- package/dist/lib/editorTheme.d.cts +0 -76
- package/dist/lib/editorTheme.d.cts.map +0 -1
- package/dist/lib/hashlineHighlight.d.cts +0 -30
- package/dist/lib/hashlineHighlight.d.cts.map +0 -1
- package/dist/lib/hashlineView.d.cts +0 -60
- package/dist/lib/hashlineView.d.cts.map +0 -1
- package/dist/lib/media.d.cts +0 -11
- package/dist/lib/media.d.cts.map +0 -1
- package/dist/lib/optionList.d.cts +0 -21
- package/dist/lib/optionList.d.cts.map +0 -1
- package/dist/lib/syntaxHighlight.d.cts +0 -54
- package/dist/lib/syntaxHighlight.d.cts.map +0 -1
- package/dist/lib/tone.d.cts +0 -25
- package/dist/lib/tone.d.cts.map +0 -1
- package/dist/theme/apply.d.cts +0 -25
- package/dist/theme/apply.d.cts.map +0 -1
- package/dist/theme/builtinThemes.d.cts +0 -11
- package/dist/theme/builtinThemes.d.cts.map +0 -1
- package/dist/theme/derive.d.cts +0 -14
- package/dist/theme/derive.d.cts.map +0 -1
- package/dist/theme/parse.d.cts +0 -10
- package/dist/theme/parse.d.cts.map +0 -1
- package/dist/theme/piTheme.d.cts +0 -19
- package/dist/theme/piTheme.d.cts.map +0 -1
- package/dist/theme/preference.d.cts +0 -15
- package/dist/theme/preference.d.cts.map +0 -1
- package/dist/theme.d.cts +0 -8
- package/dist/types/editor.d.cts +0 -78
- package/dist/types/editor.d.cts.map +0 -1
- package/dist/types/theme.d.cts +0 -38
- package/dist/types/theme.d.cts.map +0 -1
- package/dist/types/tone.d.cts +0 -28
- package/dist/types/tone.d.cts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HashlineLines.mjs","names":[],"sources":["../../../src/components/HashlineLines.tsx"],"sourcesContent":["import { type ComponentProps, useEffect, useMemo, useState } from 'react';\n\nimport { cn } from '../lib/cn';\nimport { type HashlineGroup, hashlineGroups, hashlineGroupsKey } from '../lib/hashlineHighlight';\nimport type { PresentedLine } from '../lib/hashlineView';\nimport { detectGrammar, highlightToLines, type SyntaxSpan } from '../lib/syntaxHighlight';\nimport { SyntaxLine } from './SyntaxText';\n\n/**\n * The anchored body shared by hashline results: a file heading per group, a\n * right-aligned line-number gutter, and a `>>` marker on grep matches. The\n * gutter width is a style, not a class, because it follows the widest number.\n *\n * The anchored lines are coloured with the editor's grammars when the body\n * names a file, or when `path` names one for a result that does not. Colour\n * arrives a frame or two late and never blocks the text: an unhighlighted body\n * is the same body without the colours.\n */\nexport interface HashlineLinesProps extends ComponentProps<'div'> {\n lines: readonly PresentedLine[];\n /** Width of the line-number column, in characters, from `hashlineBody`. */\n gutter: number;\n /** The file a body of bare anchored lines came from, for highlighting. */\n path?: string | undefined;\n}\n\nconst NO_SPANS: ReadonlyMap<number, readonly SyntaxSpan[]> = new Map();\n\n/**\n * Spans per presented-line index, once every file's run has parsed. The result\n * is stored with the body it belongs to, so a body that has changed since\n * reads as plain rather than wearing the previous body's colours.\n */\nfunction useHashlineSpans(groups: readonly HashlineGroup[]): ReadonlyMap<number, readonly SyntaxSpan[]> {\n const key = hashlineGroupsKey(groups);\n const [done, setDone] = useState<{ key: string; spans: ReadonlyMap<number, readonly SyntaxSpan[]> } | undefined>(\n undefined,\n );\n\n useEffect(() => {\n let live = true;\n const next = new Map<number, readonly SyntaxSpan[]>();\n const work = groups.map(async (group) => {\n const grammar = detectGrammar({ path: group.path, text: group.text });\n if (grammar === undefined) return;\n const highlighted = await highlightToLines(group.text, grammar);\n if (highlighted === undefined) return;\n for (const [offset, index] of group.indices.entries()) {\n const line = highlighted[offset];\n if (line !== undefined) next.set(index, line);\n }\n });\n // One state write for the whole body, so a many-file grep result does not\n // repaint once per file. A grammar that fails to load leaves its run plain.\n void Promise.allSettled(work).then(() => {\n if (live && next.size > 0) setDone({ key, spans: next });\n });\n return () => {\n live = false;\n };\n // The groups are rebuilt every render; their key is what actually changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [key]);\n\n return done?.key === key ? done.spans : NO_SPANS;\n}\n\nexport function HashlineLines({ className, lines, gutter, path, ...props }: HashlineLinesProps) {\n const groups = useMemo(() => hashlineGroups(lines, path), [lines, path]);\n const spans = useHashlineSpans(groups);\n // Bound to a name rather than written inline: this file ships as a scaffold\n // template too, and an inline style object opens with the same two braces\n // Liquid reads as an output tag, which fails to render.\n const gutterStyle = { width: `${gutter}ch` };\n return (\n <div data-slot=\"hashline-lines\" className={cn('flex flex-col font-mono', className)} {...props}>\n {lines.map((line, index) => {\n if (line.type === 'file') {\n return (\n <span key={index} className=\"mt-1 truncate text-doom-faint first:mt-0\">\n {line.path}\n </span>\n );\n }\n if (line.type === 'plain') {\n return (\n <span key={index} className=\"whitespace-pre-wrap break-words text-doom-dim\">\n {line.text}\n </span>\n );\n }\n const { value } = line;\n return (\n <span key={index} className=\"flex gap-2\">\n {value.marker !== undefined ? (\n <span className={`w-4 shrink-0 ${value.marker === 'match' ? 'text-doom-blue' : 'text-doom-faint'}`}>\n {value.marker === 'match' ? '>>' : ''}\n </span>\n ) : null}\n <span className=\"shrink-0 text-right text-doom-faint\" style={gutterStyle}>\n {value.line}\n </span>\n <SyntaxLine\n spans={spans.get(index)}\n text={value.content}\n className={`min-w-0 whitespace-pre-wrap break-words ${value.marker === 'match' ? 'text-doom-hi' : 'text-doom-text'}`}\n />\n </span>\n );\n })}\n </div>\n );\n}\n"],"mappings":"sWA0BA,MAAM,
|
|
1
|
+
{"version":3,"file":"HashlineLines.mjs","names":[],"sources":["../../../src/components/HashlineLines.tsx"],"sourcesContent":["import { type ComponentProps, useEffect, useMemo, useState } from 'react';\n\nimport { cn } from '../lib/cn';\nimport { type HashlineGroup, hashlineGroups, hashlineGroupsKey } from '../lib/hashlineHighlight';\nimport type { PresentedLine } from '../lib/hashlineView';\nimport { detectGrammar, highlightToLines, type SyntaxSpan } from '../lib/syntaxHighlight';\nimport { SyntaxLine } from './SyntaxText';\n\n/**\n * The anchored body shared by hashline results: a file heading per group, a\n * right-aligned line-number gutter, and a `>>` marker on grep matches. The\n * gutter width is a style, not a class, because it follows the widest number.\n *\n * The anchored lines are coloured with the editor's grammars when the body\n * names a file, or when `path` names one for a result that does not. Colour\n * arrives a frame or two late and never blocks the text: an unhighlighted body\n * is the same body without the colours.\n */\nexport interface HashlineLinesProps extends ComponentProps<'div'> {\n lines: readonly PresentedLine[];\n /** Width of the line-number column, in characters, from `hashlineBody`. */\n gutter: number;\n /** The file a body of bare anchored lines came from, for highlighting. */\n path?: string | undefined;\n}\n\nconst NO_SPANS: ReadonlyMap<number, readonly SyntaxSpan[]> = new Map();\n\n/**\n * Spans per presented-line index, once every file's run has parsed. The result\n * is stored with the body it belongs to, so a body that has changed since\n * reads as plain rather than wearing the previous body's colours.\n */\nfunction useHashlineSpans(groups: readonly HashlineGroup[]): ReadonlyMap<number, readonly SyntaxSpan[]> {\n const key = hashlineGroupsKey(groups);\n const [done, setDone] = useState<{ key: string; spans: ReadonlyMap<number, readonly SyntaxSpan[]> } | undefined>(\n undefined,\n );\n\n useEffect(() => {\n let live = true;\n const next = new Map<number, readonly SyntaxSpan[]>();\n const work = groups.map(async (group) => {\n const grammar = detectGrammar({ path: group.path, text: group.text });\n if (grammar === undefined) return;\n const highlighted = await highlightToLines(group.text, grammar);\n if (highlighted === undefined) return;\n for (const [offset, index] of group.indices.entries()) {\n const line = highlighted[offset];\n if (line !== undefined) next.set(index, line);\n }\n });\n // One state write for the whole body, so a many-file grep result does not\n // repaint once per file. A grammar that fails to load leaves its run plain.\n void Promise.allSettled(work).then(() => {\n if (live && next.size > 0) setDone({ key, spans: next });\n });\n return () => {\n live = false;\n };\n // The groups are rebuilt every render; their key is what actually changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [key]);\n\n return done?.key === key ? done.spans : NO_SPANS;\n}\n\nexport function HashlineLines({ className, lines, gutter, path, ...props }: HashlineLinesProps) {\n const groups = useMemo(() => hashlineGroups(lines, path), [lines, path]);\n const spans = useHashlineSpans(groups);\n // Bound to a name rather than written inline: this file ships as a scaffold\n // template too, and an inline style object opens with the same two braces\n // Liquid reads as an output tag, which fails to render.\n const gutterStyle = { width: `${gutter}ch` };\n return (\n <div data-slot=\"hashline-lines\" className={cn('flex flex-col font-mono', className)} {...props}>\n {lines.map((line, index) => {\n if (line.type === 'file') {\n return (\n <span key={index} className=\"mt-1 truncate text-doom-faint first:mt-0\">\n {line.path}\n </span>\n );\n }\n if (line.type === 'plain') {\n return (\n <span key={index} className=\"whitespace-pre-wrap break-words text-doom-dim\">\n {line.text}\n </span>\n );\n }\n const { value } = line;\n return (\n <span key={index} className=\"flex gap-2\">\n {value.marker !== undefined ? (\n <span className={`w-4 shrink-0 ${value.marker === 'match' ? 'text-doom-blue' : 'text-doom-faint'}`}>\n {value.marker === 'match' ? '>>' : ''}\n </span>\n ) : null}\n <span className=\"shrink-0 text-right text-doom-faint\" style={gutterStyle}>\n {value.line}\n </span>\n <SyntaxLine\n spans={spans.get(index)}\n text={value.content}\n className={`min-w-0 whitespace-pre-wrap break-words ${value.marker === 'match' ? 'text-doom-hi' : 'text-doom-text'}`}\n />\n </span>\n );\n })}\n </div>\n );\n}\n"],"mappings":"sWA0BA,MAAM,kBAAuD,IAAI,IAOjE,SAAS,EAAiB,EAA8E,CACtG,IAAM,EAAM,EAAkB,CAAM,EAC9B,CAAC,EAAM,GAAW,EACtB,IAAA,EACF,EA2BA,OAzBA,MAAgB,CACd,IAAI,EAAO,GACL,kBAAO,IAAI,IACX,EAAO,EAAO,IAAI,KAAO,IAAU,CACvC,IAAM,EAAU,EAAc,CAAE,KAAM,EAAM,KAAM,KAAM,EAAM,IAAK,CAAC,EACpE,GAAI,IAAY,IAAA,GAAW,OAC3B,IAAM,EAAc,MAAM,EAAiB,EAAM,KAAM,CAAO,EAC1D,OAAgB,IAAA,GACpB,IAAK,GAAM,CAAC,EAAQ,KAAU,EAAM,QAAQ,QAAQ,EAAG,CACrD,IAAM,EAAO,EAAY,GACrB,IAAS,IAAA,IAAW,EAAK,IAAI,EAAO,CAAI,CAC9C,CACF,CAAC,EAMD,OAHA,QAAa,WAAW,CAAI,CAAC,CAAC,SAAW,CACnC,GAAQ,EAAK,KAAO,GAAG,EAAQ,CAAE,MAAK,MAAO,CAAK,CAAC,CACzD,CAAC,MACY,CACX,EAAO,EACT,CAGF,EAAG,CAAC,CAAG,CAAC,EAED,GAAM,MAAQ,EAAM,EAAK,MAAQ,CAC1C,CAEA,SAAgB,EAAc,CAAE,YAAW,QAAO,SAAQ,OAAM,GAAG,GAA6B,CAE9F,IAAM,EAAQ,EADC,MAAc,EAAe,EAAO,CAAI,EAAG,CAAC,EAAO,CAAI,CAClC,CAAC,EAI/B,EAAc,CAAE,MAAO,GAAG,EAAO,GAAI,EAC3C,MACE,kBAAC,MAAD,CAAK,YAAU,iBAAiB,UAAW,EAAG,0BAA2B,CAAS,EAAG,GAAI,EACtF,SAAA,EAAM,KAAK,EAAM,IAAU,CAC1B,GAAI,EAAK,OAAS,OAChB,MACE,kBAAC,OAAD,CAAkB,UAAU,2CACzB,SAAA,EAAK,IACF,EAFK,CAEL,EAGV,GAAI,EAAK,OAAS,QAChB,MACE,kBAAC,OAAD,CAAkB,UAAU,gDACzB,SAAA,EAAK,IACF,EAFK,CAEL,EAGV,GAAM,CAAE,SAAU,EAClB,MACE,kBAAC,OAAD,CAAkB,UAAU,aAA5B,SAAA,CACG,EAAM,SAAW,IAAA,GAId,KAHF,kBAAC,OAAD,CAAM,UAAW,gBAAgB,EAAM,SAAW,QAAU,iBAAmB,oBAC5E,SAAA,EAAM,SAAW,QAAU,KAAO,EAC/B,CAAA,EAER,kBAAC,OAAD,CAAM,UAAU,sCAAsC,MAAO,EAC1D,SAAA,EAAM,IACH,CAAA,EACN,kBAAC,EAAD,CACE,MAAO,EAAM,IAAI,CAAK,EACtB,KAAM,EAAM,QACZ,UAAW,2CAA2C,EAAM,SAAW,QAAU,eAAiB,kBACnG,CAAA,CACG,CAdK,EAAA,CAcL,CAEV,CAAC,CACE,CAAA,CAET"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../lib/cn.cjs");let t=require("react/jsx-runtime");const n=(0,require("class-variance-authority").cva)(`min-w-0 font-mono text-doom-hi outline-none placeholder:text-doom-faint disabled:opacity-50 aria-invalid:border-doom-edge-red aria-invalid:text-doom-red`,{variants:{variant:{default:`rounded border border-doom-border bg-doom-deep transition-colors focus:border-doom-blue/60`,bare:`border-0 bg-transparent`},size:{xs:`px-1.5 py-0.5 text-2xs`,sm:`px-2 py-1 text-xs`,md:`px-2.5 py-1.5 text-sm`,lg:`px-3 py-2 text-base`}},compoundVariants:[{variant:`bare`,class:`p-0`}],defaultVariants:{variant:`default`,size:`md`}});function r({className:r,type:i,variant:a,size:o,...s}){return(0,t.jsx)(`input`,{type:i??`text`,"data-slot":`input`,spellCheck:!1,className:e.cn(n({variant:a,size:o}),r),...s})}exports.Input=r,exports.fieldVariants=n;
|
|
1
|
+
const e=require("../lib/cn.cjs");let t=require("react/jsx-runtime");const n=(0,require("class-variance-authority").cva)(`min-w-0 font-mono text-doom-hi outline-none placeholder:text-doom-faint disabled:opacity-50 aria-invalid:border-doom-edge-red aria-invalid:text-doom-red`,{variants:{variant:{default:`rounded border border-doom-border bg-doom-deep transition-colors focus:border-doom-blue/60`,bare:`border-0 bg-transparent`},size:{xs:`px-1.5 py-0.5 text-2xs`,sm:`px-2 py-1 text-xs`,md:`px-2.5 py-1.5 text-sm`,lg:`px-3 py-2 text-base`}},compoundVariants:[{variant:`bare`,class:`p-0`}],defaultVariants:{variant:`default`,size:`md`}});function r({className:r,type:i,variant:a,size:o,...s}){return/* @__PURE__ */ (0,t.jsx)(`input`,{type:i??`text`,"data-slot":`input`,spellCheck:!1,className:e.cn(n({variant:a,size:o}),r),...s})}exports.Input=r,exports.fieldVariants=n;
|
|
2
2
|
//# sourceMappingURL=Input.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Input.cjs","names":["cva","cn"],"sources":["../../../src/components/Input.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/**\n * The shape both fields wear, so a single-line and a multi-line field in the\n * same form agree on their well, their type ramp, and their invalid state.\n * Textarea builds on it rather than restating it.\n */\nexport const fieldVariants = cva(\n 'min-w-0 font-mono text-doom-hi outline-none placeholder:text-doom-faint disabled:opacity-50 aria-invalid:border-doom-edge-red aria-invalid:text-doom-red',\n {\n variants: {\n variant: {\n default: 'rounded border border-doom-border bg-doom-deep transition-colors focus:border-doom-blue/60',\n /** No well: a composer or a palette search that draws its own surface. */\n bare: 'border-0 bg-transparent',\n },\n /** The same rungs Button names, so a field and the button beside it line up. */\n size: {\n xs: 'px-1.5 py-0.5 text-2xs',\n sm: 'px-2 py-1 text-xs',\n md: 'px-2.5 py-1.5 text-sm',\n lg: 'px-3 py-2 text-base',\n },\n },\n compoundVariants: [\n // A bare field sits inside a surface that already has its own padding,\n // so it keeps the size's type ramp and gives up the size's box.\n { variant: 'bare', class: 'p-0' },\n ],\n defaultVariants: { variant: 'default', size: 'md' },\n },\n);\n\nexport type FieldVariant = NonNullable<VariantProps<typeof fieldVariants>['variant']>;\nexport type FieldSize = NonNullable<VariantProps<typeof fieldVariants>['size']>;\n\nexport interface InputProps extends Omit<ComponentProps<'input'>, 'size'>, VariantProps<typeof fieldVariants> {}\n\n/** The one text field: deep well, quiet border, accent border on focus, red edge when invalid. */\nexport function Input({ className, type, variant, size, ...props }: InputProps) {\n return (\n <input\n type={type ?? 'text'}\n data-slot=\"input\"\n spellCheck={false}\n className={cn(fieldVariants({ variant, size }), className)}\n {...props}\n />\n );\n}\n"],"mappings":"oEAUA,MAAa,GAAA,oCAAgBA,CAAAA,CAAAA,IAAAA,CAC3B,2JACA,CACE,SAAU,CACR,QAAS,CACP,QAAS,6FAET,KAAM,yBACR,EAEA,KAAM,CACJ,GAAI,yBACJ,GAAI,oBACJ,GAAI,wBACJ,GAAI,qBACN,CACF,EACA,iBAAkB,CAGhB,CAAE,QAAS,OAAQ,MAAO,KAAM,CAClC,EACA,gBAAiB,CAAE,QAAS,UAAW,KAAM,IAAK,CACpD,CACF,EAQA,SAAgB,EAAM,CAAE,YAAW,OAAM,UAAS,OAAM,GAAG,GAAqB,CAC9E,
|
|
1
|
+
{"version":3,"file":"Input.cjs","names":["cva","cn"],"sources":["../../../src/components/Input.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/**\n * The shape both fields wear, so a single-line and a multi-line field in the\n * same form agree on their well, their type ramp, and their invalid state.\n * Textarea builds on it rather than restating it.\n */\nexport const fieldVariants = cva(\n 'min-w-0 font-mono text-doom-hi outline-none placeholder:text-doom-faint disabled:opacity-50 aria-invalid:border-doom-edge-red aria-invalid:text-doom-red',\n {\n variants: {\n variant: {\n default: 'rounded border border-doom-border bg-doom-deep transition-colors focus:border-doom-blue/60',\n /** No well: a composer or a palette search that draws its own surface. */\n bare: 'border-0 bg-transparent',\n },\n /** The same rungs Button names, so a field and the button beside it line up. */\n size: {\n xs: 'px-1.5 py-0.5 text-2xs',\n sm: 'px-2 py-1 text-xs',\n md: 'px-2.5 py-1.5 text-sm',\n lg: 'px-3 py-2 text-base',\n },\n },\n compoundVariants: [\n // A bare field sits inside a surface that already has its own padding,\n // so it keeps the size's type ramp and gives up the size's box.\n { variant: 'bare', class: 'p-0' },\n ],\n defaultVariants: { variant: 'default', size: 'md' },\n },\n);\n\nexport type FieldVariant = NonNullable<VariantProps<typeof fieldVariants>['variant']>;\nexport type FieldSize = NonNullable<VariantProps<typeof fieldVariants>['size']>;\n\nexport interface InputProps extends Omit<ComponentProps<'input'>, 'size'>, VariantProps<typeof fieldVariants> {}\n\n/** The one text field: deep well, quiet border, accent border on focus, red edge when invalid. */\nexport function Input({ className, type, variant, size, ...props }: InputProps) {\n return (\n <input\n type={type ?? 'text'}\n data-slot=\"input\"\n spellCheck={false}\n className={cn(fieldVariants({ variant, size }), className)}\n {...props}\n />\n );\n}\n"],"mappings":"oEAUA,MAAa,GAAA,oCAAgBA,CAAAA,CAAAA,IAAAA,CAC3B,2JACA,CACE,SAAU,CACR,QAAS,CACP,QAAS,6FAET,KAAM,yBACR,EAEA,KAAM,CACJ,GAAI,yBACJ,GAAI,oBACJ,GAAI,wBACJ,GAAI,qBACN,CACF,EACA,iBAAkB,CAGhB,CAAE,QAAS,OAAQ,MAAO,KAAM,CAClC,EACA,gBAAiB,CAAE,QAAS,UAAW,KAAM,IAAK,CACpD,CACF,EAQA,SAAgB,EAAM,CAAE,YAAW,OAAM,UAAS,OAAM,GAAG,GAAqB,CAC9E,MACE,iBAAA,EAAA,EAAA,IAAA,CAAC,QAAD,CACE,KAAM,GAAQ,OACd,YAAU,QACV,WAAY,GACZ,UAAWC,EAAAA,GAAG,EAAc,CAAE,UAAS,MAAK,CAAC,EAAG,CAAS,EACzD,GAAI,CACL,CAAA,CAEL"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{cn as e}from"../lib/cn.mjs";import{jsx as t}from"react/jsx-runtime";import{cva as n}from"class-variance-authority";const r=n(`min-w-0 font-mono text-doom-hi outline-none placeholder:text-doom-faint disabled:opacity-50 aria-invalid:border-doom-edge-red aria-invalid:text-doom-red`,{variants:{variant:{default:`rounded border border-doom-border bg-doom-deep transition-colors focus:border-doom-blue/60`,bare:`border-0 bg-transparent`},size:{xs:`px-1.5 py-0.5 text-2xs`,sm:`px-2 py-1 text-xs`,md:`px-2.5 py-1.5 text-sm`,lg:`px-3 py-2 text-base`}},compoundVariants:[{variant:`bare`,class:`p-0`}],defaultVariants:{variant:`default`,size:`md`}});function i({className:n,type:i,variant:a,size:o,...s}){return t(`input`,{type:i??`text`,"data-slot":`input`,spellCheck:!1,className:e(r({variant:a,size:o}),n),...s})}export{i as Input,r as fieldVariants};
|
|
1
|
+
import{cn as e}from"../lib/cn.mjs";import{jsx as t}from"react/jsx-runtime";import{cva as n}from"class-variance-authority";const r=n(`min-w-0 font-mono text-doom-hi outline-none placeholder:text-doom-faint disabled:opacity-50 aria-invalid:border-doom-edge-red aria-invalid:text-doom-red`,{variants:{variant:{default:`rounded border border-doom-border bg-doom-deep transition-colors focus:border-doom-blue/60`,bare:`border-0 bg-transparent`},size:{xs:`px-1.5 py-0.5 text-2xs`,sm:`px-2 py-1 text-xs`,md:`px-2.5 py-1.5 text-sm`,lg:`px-3 py-2 text-base`}},compoundVariants:[{variant:`bare`,class:`p-0`}],defaultVariants:{variant:`default`,size:`md`}});function i({className:n,type:i,variant:a,size:o,...s}){return/* @__PURE__ */ t(`input`,{type:i??`text`,"data-slot":`input`,spellCheck:!1,className:e(r({variant:a,size:o}),n),...s})}export{i as Input,r as fieldVariants};
|
|
2
2
|
//# sourceMappingURL=Input.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Input.mjs","names":[],"sources":["../../../src/components/Input.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/**\n * The shape both fields wear, so a single-line and a multi-line field in the\n * same form agree on their well, their type ramp, and their invalid state.\n * Textarea builds on it rather than restating it.\n */\nexport const fieldVariants = cva(\n 'min-w-0 font-mono text-doom-hi outline-none placeholder:text-doom-faint disabled:opacity-50 aria-invalid:border-doom-edge-red aria-invalid:text-doom-red',\n {\n variants: {\n variant: {\n default: 'rounded border border-doom-border bg-doom-deep transition-colors focus:border-doom-blue/60',\n /** No well: a composer or a palette search that draws its own surface. */\n bare: 'border-0 bg-transparent',\n },\n /** The same rungs Button names, so a field and the button beside it line up. */\n size: {\n xs: 'px-1.5 py-0.5 text-2xs',\n sm: 'px-2 py-1 text-xs',\n md: 'px-2.5 py-1.5 text-sm',\n lg: 'px-3 py-2 text-base',\n },\n },\n compoundVariants: [\n // A bare field sits inside a surface that already has its own padding,\n // so it keeps the size's type ramp and gives up the size's box.\n { variant: 'bare', class: 'p-0' },\n ],\n defaultVariants: { variant: 'default', size: 'md' },\n },\n);\n\nexport type FieldVariant = NonNullable<VariantProps<typeof fieldVariants>['variant']>;\nexport type FieldSize = NonNullable<VariantProps<typeof fieldVariants>['size']>;\n\nexport interface InputProps extends Omit<ComponentProps<'input'>, 'size'>, VariantProps<typeof fieldVariants> {}\n\n/** The one text field: deep well, quiet border, accent border on focus, red edge when invalid. */\nexport function Input({ className, type, variant, size, ...props }: InputProps) {\n return (\n <input\n type={type ?? 'text'}\n data-slot=\"input\"\n spellCheck={false}\n className={cn(fieldVariants({ variant, size }), className)}\n {...props}\n />\n );\n}\n"],"mappings":"0HAUA,MAAa,EAAgB,EAC3B,2JACA,CACE,SAAU,CACR,QAAS,CACP,QAAS,6FAET,KAAM,yBACR,EAEA,KAAM,CACJ,GAAI,yBACJ,GAAI,oBACJ,GAAI,wBACJ,GAAI,qBACN,CACF,EACA,iBAAkB,CAGhB,CAAE,QAAS,OAAQ,MAAO,KAAM,CAClC,EACA,gBAAiB,CAAE,QAAS,UAAW,KAAM,IAAK,CACpD,CACF,EAQA,SAAgB,EAAM,CAAE,YAAW,OAAM,UAAS,OAAM,GAAG,GAAqB,CAC9E,
|
|
1
|
+
{"version":3,"file":"Input.mjs","names":[],"sources":["../../../src/components/Input.tsx"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\nimport type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/**\n * The shape both fields wear, so a single-line and a multi-line field in the\n * same form agree on their well, their type ramp, and their invalid state.\n * Textarea builds on it rather than restating it.\n */\nexport const fieldVariants = cva(\n 'min-w-0 font-mono text-doom-hi outline-none placeholder:text-doom-faint disabled:opacity-50 aria-invalid:border-doom-edge-red aria-invalid:text-doom-red',\n {\n variants: {\n variant: {\n default: 'rounded border border-doom-border bg-doom-deep transition-colors focus:border-doom-blue/60',\n /** No well: a composer or a palette search that draws its own surface. */\n bare: 'border-0 bg-transparent',\n },\n /** The same rungs Button names, so a field and the button beside it line up. */\n size: {\n xs: 'px-1.5 py-0.5 text-2xs',\n sm: 'px-2 py-1 text-xs',\n md: 'px-2.5 py-1.5 text-sm',\n lg: 'px-3 py-2 text-base',\n },\n },\n compoundVariants: [\n // A bare field sits inside a surface that already has its own padding,\n // so it keeps the size's type ramp and gives up the size's box.\n { variant: 'bare', class: 'p-0' },\n ],\n defaultVariants: { variant: 'default', size: 'md' },\n },\n);\n\nexport type FieldVariant = NonNullable<VariantProps<typeof fieldVariants>['variant']>;\nexport type FieldSize = NonNullable<VariantProps<typeof fieldVariants>['size']>;\n\nexport interface InputProps extends Omit<ComponentProps<'input'>, 'size'>, VariantProps<typeof fieldVariants> {}\n\n/** The one text field: deep well, quiet border, accent border on focus, red edge when invalid. */\nexport function Input({ className, type, variant, size, ...props }: InputProps) {\n return (\n <input\n type={type ?? 'text'}\n data-slot=\"input\"\n spellCheck={false}\n className={cn(fieldVariants({ variant, size }), className)}\n {...props}\n />\n );\n}\n"],"mappings":"0HAUA,MAAa,EAAgB,EAC3B,2JACA,CACE,SAAU,CACR,QAAS,CACP,QAAS,6FAET,KAAM,yBACR,EAEA,KAAM,CACJ,GAAI,yBACJ,GAAI,oBACJ,GAAI,wBACJ,GAAI,qBACN,CACF,EACA,iBAAkB,CAGhB,CAAE,QAAS,OAAQ,MAAO,KAAM,CAClC,EACA,gBAAiB,CAAE,QAAS,UAAW,KAAM,IAAK,CACpD,CACF,EAQA,SAAgB,EAAM,CAAE,YAAW,OAAM,UAAS,OAAM,GAAG,GAAqB,CAC9E,MACE,kBAAC,QAAD,CACE,KAAM,GAAQ,OACd,YAAU,QACV,WAAY,GACZ,UAAW,EAAG,EAAc,CAAE,UAAS,MAAK,CAAC,EAAG,CAAS,EACzD,GAAI,CACL,CAAA,CAEL"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../lib/cn.cjs");let t=require("react/jsx-runtime");function n({className:n,...r}){return(0,t.jsx)(`kbd`,{"data-slot":`kbd`,className:e.cn(`inline-flex items-center rounded bg-doom-deep px-1.5 py-0.5 font-mono text-2xs font-bold text-doom-faint`,n),...r})}exports.Kbd=n;
|
|
1
|
+
const e=require("../lib/cn.cjs");let t=require("react/jsx-runtime");function n({className:n,...r}){return/* @__PURE__ */ (0,t.jsx)(`kbd`,{"data-slot":`kbd`,className:e.cn(`inline-flex items-center rounded bg-doom-deep px-1.5 py-0.5 font-mono text-2xs font-bold text-doom-faint`,n),...r})}exports.Kbd=n;
|
|
2
2
|
//# sourceMappingURL=Kbd.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Kbd.cjs","names":["cn"],"sources":["../../../src/components/Kbd.tsx"],"sourcesContent":["import type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/** A key or key path, drawn as the leader hints draw them. */\nexport function Kbd({ className, ...props }: ComponentProps<'kbd'>) {\n return (\n <kbd\n data-slot=\"kbd\"\n className={cn(\n 'inline-flex items-center rounded bg-doom-deep px-1.5 py-0.5 font-mono text-2xs font-bold text-doom-faint',\n className,\n )}\n {...props}\n />\n );\n}\n"],"mappings":"oEAKA,SAAgB,EAAI,CAAE,YAAW,GAAG,GAAgC,CAClE,
|
|
1
|
+
{"version":3,"file":"Kbd.cjs","names":["cn"],"sources":["../../../src/components/Kbd.tsx"],"sourcesContent":["import type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/** A key or key path, drawn as the leader hints draw them. */\nexport function Kbd({ className, ...props }: ComponentProps<'kbd'>) {\n return (\n <kbd\n data-slot=\"kbd\"\n className={cn(\n 'inline-flex items-center rounded bg-doom-deep px-1.5 py-0.5 font-mono text-2xs font-bold text-doom-faint',\n className,\n )}\n {...props}\n />\n );\n}\n"],"mappings":"oEAKA,SAAgB,EAAI,CAAE,YAAW,GAAG,GAAgC,CAClE,MACE,iBAAA,EAAA,EAAA,IAAA,CAAC,MAAD,CACE,YAAU,MACV,UAAWA,EAAAA,GACT,2GACA,CACF,EACA,GAAI,CACL,CAAA,CAEL"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{cn as e}from"../lib/cn.mjs";import{jsx as t}from"react/jsx-runtime";function n({className:n,...r}){return t(`kbd`,{"data-slot":`kbd`,className:e(`inline-flex items-center rounded bg-doom-deep px-1.5 py-0.5 font-mono text-2xs font-bold text-doom-faint`,n),...r})}export{n as Kbd};
|
|
1
|
+
import{cn as e}from"../lib/cn.mjs";import{jsx as t}from"react/jsx-runtime";function n({className:n,...r}){return/* @__PURE__ */ t(`kbd`,{"data-slot":`kbd`,className:e(`inline-flex items-center rounded bg-doom-deep px-1.5 py-0.5 font-mono text-2xs font-bold text-doom-faint`,n),...r})}export{n as Kbd};
|
|
2
2
|
//# sourceMappingURL=Kbd.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Kbd.mjs","names":[],"sources":["../../../src/components/Kbd.tsx"],"sourcesContent":["import type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/** A key or key path, drawn as the leader hints draw them. */\nexport function Kbd({ className, ...props }: ComponentProps<'kbd'>) {\n return (\n <kbd\n data-slot=\"kbd\"\n className={cn(\n 'inline-flex items-center rounded bg-doom-deep px-1.5 py-0.5 font-mono text-2xs font-bold text-doom-faint',\n className,\n )}\n {...props}\n />\n );\n}\n"],"mappings":"2EAKA,SAAgB,EAAI,CAAE,YAAW,GAAG,GAAgC,CAClE,
|
|
1
|
+
{"version":3,"file":"Kbd.mjs","names":[],"sources":["../../../src/components/Kbd.tsx"],"sourcesContent":["import type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/** A key or key path, drawn as the leader hints draw them. */\nexport function Kbd({ className, ...props }: ComponentProps<'kbd'>) {\n return (\n <kbd\n data-slot=\"kbd\"\n className={cn(\n 'inline-flex items-center rounded bg-doom-deep px-1.5 py-0.5 font-mono text-2xs font-bold text-doom-faint',\n className,\n )}\n {...props}\n />\n );\n}\n"],"mappings":"2EAKA,SAAgB,EAAI,CAAE,YAAW,GAAG,GAAgC,CAClE,MACE,kBAAC,MAAD,CACE,YAAU,MACV,UAAW,EACT,2GACA,CACF,EACA,GAAI,CACL,CAAA,CAEL"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../lib/cn.cjs");let t=require("radix-ui"),n=require("react/jsx-runtime");function r({className:r,...i}){return(0,n.jsx)(t.Label.Root,{"data-slot":`label`,className:e.cn(`flex items-center gap-1.5 text-xs text-doom-faint select-none has-[+:disabled]:opacity-50`,r),...i})}exports.Label=r;
|
|
1
|
+
const e=require("../lib/cn.cjs");let t=require("radix-ui"),n=require("react/jsx-runtime");function r({className:r,...i}){return/* @__PURE__ */ (0,n.jsx)(t.Label.Root,{"data-slot":`label`,className:e.cn(`flex items-center gap-1.5 text-xs text-doom-faint select-none has-[+:disabled]:opacity-50`,r),...i})}exports.Label=r;
|
|
2
2
|
//# sourceMappingURL=Label.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Label.cjs","names":["LabelPrimitive","cn"],"sources":["../../../src/components/Label.tsx"],"sourcesContent":["import { Label as LabelPrimitive } from 'radix-ui';\nimport type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/** A field's name: clicking it focuses the control, which a bare span never does. */\nexport function Label({ className, ...props }: ComponentProps<typeof LabelPrimitive.Root>) {\n return (\n <LabelPrimitive.Root\n data-slot=\"label\"\n className={cn(\n 'flex items-center gap-1.5 text-xs text-doom-faint select-none has-[+:disabled]:opacity-50',\n className,\n )}\n {...props}\n />\n );\n}\n"],"mappings":"0FAMA,SAAgB,EAAM,CAAE,YAAW,GAAG,GAAqD,CACzF,
|
|
1
|
+
{"version":3,"file":"Label.cjs","names":["LabelPrimitive","cn"],"sources":["../../../src/components/Label.tsx"],"sourcesContent":["import { Label as LabelPrimitive } from 'radix-ui';\nimport type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/** A field's name: clicking it focuses the control, which a bare span never does. */\nexport function Label({ className, ...props }: ComponentProps<typeof LabelPrimitive.Root>) {\n return (\n <LabelPrimitive.Root\n data-slot=\"label\"\n className={cn(\n 'flex items-center gap-1.5 text-xs text-doom-faint select-none has-[+:disabled]:opacity-50',\n className,\n )}\n {...props}\n />\n );\n}\n"],"mappings":"0FAMA,SAAgB,EAAM,CAAE,YAAW,GAAG,GAAqD,CACzF,MACE,iBAAA,EAAA,EAAA,IAAA,CAACA,EAAAA,MAAe,KAAhB,CACE,YAAU,QACV,UAAWC,EAAAA,GACT,4FACA,CACF,EACA,GAAI,CACL,CAAA,CAEL"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{cn as e}from"../lib/cn.mjs";import{Label as t}from"radix-ui";import{jsx as n}from"react/jsx-runtime";function r({className:r,...i}){return n(t.Root,{"data-slot":`label`,className:e(`flex items-center gap-1.5 text-xs text-doom-faint select-none has-[+:disabled]:opacity-50`,r),...i})}export{r as Label};
|
|
1
|
+
import{cn as e}from"../lib/cn.mjs";import{Label as t}from"radix-ui";import{jsx as n}from"react/jsx-runtime";function r({className:r,...i}){return/* @__PURE__ */ n(t.Root,{"data-slot":`label`,className:e(`flex items-center gap-1.5 text-xs text-doom-faint select-none has-[+:disabled]:opacity-50`,r),...i})}export{r as Label};
|
|
2
2
|
//# sourceMappingURL=Label.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Label.mjs","names":["Label","LabelPrimitive"],"sources":["../../../src/components/Label.tsx"],"sourcesContent":["import { Label as LabelPrimitive } from 'radix-ui';\nimport type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/** A field's name: clicking it focuses the control, which a bare span never does. */\nexport function Label({ className, ...props }: ComponentProps<typeof LabelPrimitive.Root>) {\n return (\n <LabelPrimitive.Root\n data-slot=\"label\"\n className={cn(\n 'flex items-center gap-1.5 text-xs text-doom-faint select-none has-[+:disabled]:opacity-50',\n className,\n )}\n {...props}\n />\n );\n}\n"],"mappings":"4GAMA,SAAgBA,EAAM,CAAE,YAAW,GAAG,GAAqD,CACzF,
|
|
1
|
+
{"version":3,"file":"Label.mjs","names":["Label","LabelPrimitive"],"sources":["../../../src/components/Label.tsx"],"sourcesContent":["import { Label as LabelPrimitive } from 'radix-ui';\nimport type { ComponentProps } from 'react';\n\nimport { cn } from '../lib/cn';\n\n/** A field's name: clicking it focuses the control, which a bare span never does. */\nexport function Label({ className, ...props }: ComponentProps<typeof LabelPrimitive.Root>) {\n return (\n <LabelPrimitive.Root\n data-slot=\"label\"\n className={cn(\n 'flex items-center gap-1.5 text-xs text-doom-faint select-none has-[+:disabled]:opacity-50',\n className,\n )}\n {...props}\n />\n );\n}\n"],"mappings":"4GAMA,SAAgBA,EAAM,CAAE,YAAW,GAAG,GAAqD,CACzF,MACE,kBAACC,EAAe,KAAhB,CACE,YAAU,QACV,UAAW,EACT,4FACA,CACF,EACA,GAAI,CACL,CAAA,CAEL"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./CodeBlock.cjs");let n=require("react/jsx-runtime"),r=require("react"),i=require("react-markdown");i=e.__toESM(i,1);let a=require("remark-gfm");a=e.__toESM(a,1);const o=[a.default],s=`rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green`,c=(0,r.createContext)(void 0);function l({className:e,children:t,...i}){let a=(0,r.useContext)(c),o=typeof t==`string`&&!t.includes(`
|
|
2
|
-
`)?t:void 0,l=o===void 0||e!==void 0?void 0:a?.(o);return l!==void 0&&o!==void 0
|
|
2
|
+
`)?t:void 0,l=o===void 0||e!==void 0?void 0:a?.(o);return l!==void 0&&o!==void 0?/* @__PURE__ */ (0,n.jsx)(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${o}`,onClick:l,className:`${s} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`,children:o}):/* @__PURE__ */ (0,n.jsx)(`code`,{...i,className:`${e??``} ${s} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`,children:t})}function u({href:e,children:t}){let i=(0,r.useContext)(c),a=e;try{a=e===void 0?void 0:decodeURIComponent(e)}catch{a=void 0}let o=a!==void 0&&a!==``&&!/^(?:[a-z][a-z\d+.-]*:|\/\/|#|\?)/i.test(a)?i?.(a,!0):void 0;return o===void 0?/* @__PURE__ */ (0,n.jsx)(`a`,{href:e,target:`_blank`,rel:`noreferrer`,className:`text-doom-blue underline decoration-doom-blue/50`,children:t}):/* @__PURE__ */ (0,n.jsx)(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${a}`,onClick:o,className:`text-doom-blue underline decoration-doom-blue/50`,children:t})}function d(e){return typeof e==`string`?e:Array.isArray(e)?e.map(e=>d(e)).join(``):``}function f({children:e}){let i=(0,r.isValidElement)(e)?e:void 0;return i===void 0?/* @__PURE__ */ (0,n.jsx)(`pre`,{className:`overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed`,children:e}):/* @__PURE__ */ (0,n.jsx)(t.CodeBlock,{text:d(i.props.children).replace(/\n$/,``),className:i.props.className})}const p={p:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`p`,{className:`whitespace-pre-wrap break-words`,children:e}),strong:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`strong`,{className:`font-bold text-doom-hi`,children:e}),em:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`em`,{className:`italic`,children:e}),del:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`del`,{className:`text-doom-dim line-through`,children:e}),a:u,code:l,pre:f,ul:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`ul`,{className:`list-disc pl-5`,children:e}),ol:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`ol`,{className:`list-decimal pl-5`,children:e}),li:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`li`,{className:`whitespace-pre-wrap break-words`,children:e}),h1:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h1`,{className:`text-lg font-bold text-doom-hi`,children:e}),h2:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h2`,{className:`text-base font-bold text-doom-hi`,children:e}),h3:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h3`,{className:`text-base font-bold text-doom-hi`,children:e}),h4:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`h4`,{className:`text-base font-bold text-doom-text`,children:e}),blockquote:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`blockquote`,{className:`border-l-2 border-doom-border pl-3 text-doom-dim`,children:e}),hr:()=>/* @__PURE__ */ (0,n.jsx)(`hr`,{className:`border-doom-border-soft`}),table:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`div`,{className:`overflow-x-auto`,children:/* @__PURE__ */ (0,n.jsx)(`table`,{className:`border-collapse text-sm`,children:e})}),th:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`th`,{className:`border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi`,children:e}),td:({children:e})=>/* @__PURE__ */ (0,n.jsx)(`td`,{className:`border border-doom-border px-2 py-1 align-top`,children:e}),img:({src:e,alt:t})=>/* @__PURE__ */ (0,n.jsx)(`img`,{src:e,alt:t??``,className:`max-h-[480px] max-w-full rounded border border-doom-border`})};function m({text:e,onFileLink:t}){return/* @__PURE__ */ (0,n.jsx)(c.Provider,{value:t,children:/* @__PURE__ */ (0,n.jsx)(`div`,{className:`flex min-w-0 flex-col gap-2`,children:/* @__PURE__ */ (0,n.jsx)(i.default,{remarkPlugins:o,components:p,children:e})})})}exports.Markdown=m;
|
|
3
3
|
//# sourceMappingURL=Markdown.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Markdown.cjs","names":["remarkGfm","createContext","useContext","isValidElement","CodeBlock","ReactMarkdown"],"sources":["../../../src/components/Markdown.tsx"],"sourcesContent":["import { createContext, type ComponentProps, isValidElement, type ReactNode, useContext } from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\n\nimport { CodeBlock } from './CodeBlock';\n\nconst REMARK_PLUGINS = [remarkGfm];\n\nconst CODE_CLASS = 'rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green';\n\n/**\n * What a piece of inline code opens, when the host knows.\n *\n * Returning undefined leaves the span as plain code, which is the answer for\n * everything a message quotes that is not a file: a class name, a flag, a\n * command. The caller decides what counts, because only it knows which files\n * the session has.\n */\nexport type FileLinkHandler = (text: string, explicit?: boolean) => (() => void) | undefined;\n\n/**\n * Passed by context rather than as a component prop so the component map stays\n * a module constant. Rebuilding that map per render gives react-markdown new\n * component identities, which remounts the whole rendered tree on every frame\n * of a streaming reply.\n */\nconst FileLinkContext = createContext<FileLinkHandler | undefined>(undefined);\n\nfunction Code({ className, children, ...rest }: ComponentProps<'code'>) {\n const onFileLink = useContext(FileLinkContext);\n // Only a single-line span with no language class is a candidate: a fenced\n // block is content, not a reference, and its own renderer owns it.\n const label = typeof children === 'string' && !children.includes('\\n') ? children : undefined;\n const open = label === undefined || className !== undefined ? undefined : onFileLink?.(label);\n if (open !== undefined && label !== undefined) {\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${label}`}\n onClick={open}\n className={`${CODE_CLASS} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`}\n >\n {label}\n </button>\n );\n }\n return (\n <code\n {...rest}\n className={`${className ?? ''} ${CODE_CLASS} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`}\n >\n {children}\n </code>\n );\n}\n\nfunction Link({ href, children }: ComponentProps<'a'>) {\n const onFileLink = useContext(FileLinkContext);\n let path = href;\n try {\n path = href === undefined ? undefined : decodeURIComponent(href);\n } catch {\n path = undefined;\n }\n const isFile = path !== undefined && path !== '' && !/^(?:[a-z][a-z\\d+.-]*:|\\/\\/|#|\\?)/i.test(path);\n const open = isFile ? onFileLink?.(path!, true) : undefined;\n if (open !== undefined)\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${path}`}\n onClick={open}\n className=\"text-doom-blue underline decoration-doom-blue/50\"\n >\n {children}\n </button>\n );\n return (\n <a href={href} target=\"_blank\" rel=\"noreferrer\" className=\"text-doom-blue underline decoration-doom-blue/50\">\n {children}\n </a>\n );\n}\n\n/** The text inside a fence, which react-markdown hands over as nested strings. */\nfunction textOf(children: ReactNode): string {\n if (typeof children === 'string') return children;\n if (Array.isArray(children)) return children.map((child) => textOf(child as ReactNode)).join('');\n return '';\n}\n\n/**\n * A fenced block, read off the <code> child that carries its language.\n *\n * The block is handed to `CodeBlock` whole rather than rendered as a styled\n * <pre> around inline code, because a fence is content with its own\n * presentation: it is coloured, it can be copied, and a mermaid fence is a\n * picture. A <pre> that came from raw HTML has no such child and keeps the\n * plain box.\n */\nfunction Pre({ children }: ComponentProps<'pre'>) {\n const code = isValidElement<{ className?: string; children?: ReactNode }>(children) ? children : undefined;\n if (code === undefined)\n return (\n <pre className=\"overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed\">\n {children}\n </pre>\n );\n // Markdown ends every fence with a newline the author did not type.\n return <CodeBlock text={textOf(code.props.children).replace(/\\n$/, '')} className={code.props.className} />;\n}\nconst COMPONENTS: Components = {\n p: ({ children }) => <p className=\"whitespace-pre-wrap break-words\">{children}</p>,\n strong: ({ children }) => <strong className=\"font-bold text-doom-hi\">{children}</strong>,\n em: ({ children }) => <em className=\"italic\">{children}</em>,\n del: ({ children }) => <del className=\"text-doom-dim line-through\">{children}</del>,\n a: Link,\n code: Code,\n pre: Pre,\n ul: ({ children }) => <ul className=\"list-disc pl-5\">{children}</ul>,\n ol: ({ children }) => <ol className=\"list-decimal pl-5\">{children}</ol>,\n li: ({ children }) => <li className=\"whitespace-pre-wrap break-words\">{children}</li>,\n h1: ({ children }) => <h1 className=\"text-lg font-bold text-doom-hi\">{children}</h1>,\n h2: ({ children }) => <h2 className=\"text-base font-bold text-doom-hi\">{children}</h2>,\n h3: ({ children }) => <h3 className=\"text-base font-bold text-doom-hi\">{children}</h3>,\n h4: ({ children }) => <h4 className=\"text-base font-bold text-doom-text\">{children}</h4>,\n blockquote: ({ children }) => (\n <blockquote className=\"border-l-2 border-doom-border pl-3 text-doom-dim\">{children}</blockquote>\n ),\n hr: () => <hr className=\"border-doom-border-soft\" />,\n table: ({ children }) => (\n <div className=\"overflow-x-auto\">\n <table className=\"border-collapse text-sm\">{children}</table>\n </div>\n ),\n th: ({ children }) => (\n <th className=\"border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi\">{children}</th>\n ),\n td: ({ children }) => <td className=\"border border-doom-border px-2 py-1 align-top\">{children}</td>,\n img: ({ src, alt }) => (\n <img src={src} alt={alt ?? ''} className=\"max-h-[480px] max-w-full rounded border border-doom-border\" />\n ),\n};\n\n/** GitHub-flavored Markdown using the cockpit's safe, shared presentation. */\nexport function Markdown({ text, onFileLink }: { text: string; onFileLink?: FileLinkHandler }) {\n return (\n <FileLinkContext.Provider value={onFileLink}>\n <div className=\"flex min-w-0 flex-col gap-2\">\n <ReactMarkdown remarkPlugins={REMARK_PLUGINS} components={COMPONENTS}>\n {text}\n </ReactMarkdown>\n </div>\n </FileLinkContext.Provider>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Markdown.cjs","names":["remarkGfm","createContext","useContext","isValidElement","CodeBlock","ReactMarkdown"],"sources":["../../../src/components/Markdown.tsx"],"sourcesContent":["import { createContext, type ComponentProps, isValidElement, type ReactNode, useContext } from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\n\nimport { CodeBlock } from './CodeBlock';\n\nconst REMARK_PLUGINS = [remarkGfm];\n\nconst CODE_CLASS = 'rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green';\n\n/**\n * What a piece of inline code opens, when the host knows.\n *\n * Returning undefined leaves the span as plain code, which is the answer for\n * everything a message quotes that is not a file: a class name, a flag, a\n * command. The caller decides what counts, because only it knows which files\n * the session has.\n */\nexport type FileLinkHandler = (text: string, explicit?: boolean) => (() => void) | undefined;\n\n/**\n * Passed by context rather than as a component prop so the component map stays\n * a module constant. Rebuilding that map per render gives react-markdown new\n * component identities, which remounts the whole rendered tree on every frame\n * of a streaming reply.\n */\nconst FileLinkContext = createContext<FileLinkHandler | undefined>(undefined);\n\nfunction Code({ className, children, ...rest }: ComponentProps<'code'>) {\n const onFileLink = useContext(FileLinkContext);\n // Only a single-line span with no language class is a candidate: a fenced\n // block is content, not a reference, and its own renderer owns it.\n const label = typeof children === 'string' && !children.includes('\\n') ? children : undefined;\n const open = label === undefined || className !== undefined ? undefined : onFileLink?.(label);\n if (open !== undefined && label !== undefined) {\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${label}`}\n onClick={open}\n className={`${CODE_CLASS} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`}\n >\n {label}\n </button>\n );\n }\n return (\n <code\n {...rest}\n className={`${className ?? ''} ${CODE_CLASS} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`}\n >\n {children}\n </code>\n );\n}\n\nfunction Link({ href, children }: ComponentProps<'a'>) {\n const onFileLink = useContext(FileLinkContext);\n let path = href;\n try {\n path = href === undefined ? undefined : decodeURIComponent(href);\n } catch {\n path = undefined;\n }\n const isFile = path !== undefined && path !== '' && !/^(?:[a-z][a-z\\d+.-]*:|\\/\\/|#|\\?)/i.test(path);\n const open = isFile ? onFileLink?.(path!, true) : undefined;\n if (open !== undefined)\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${path}`}\n onClick={open}\n className=\"text-doom-blue underline decoration-doom-blue/50\"\n >\n {children}\n </button>\n );\n return (\n <a href={href} target=\"_blank\" rel=\"noreferrer\" className=\"text-doom-blue underline decoration-doom-blue/50\">\n {children}\n </a>\n );\n}\n\n/** The text inside a fence, which react-markdown hands over as nested strings. */\nfunction textOf(children: ReactNode): string {\n if (typeof children === 'string') return children;\n if (Array.isArray(children)) return children.map((child) => textOf(child as ReactNode)).join('');\n return '';\n}\n\n/**\n * A fenced block, read off the <code> child that carries its language.\n *\n * The block is handed to `CodeBlock` whole rather than rendered as a styled\n * <pre> around inline code, because a fence is content with its own\n * presentation: it is coloured, it can be copied, and a mermaid fence is a\n * picture. A <pre> that came from raw HTML has no such child and keeps the\n * plain box.\n */\nfunction Pre({ children }: ComponentProps<'pre'>) {\n const code = isValidElement<{ className?: string; children?: ReactNode }>(children) ? children : undefined;\n if (code === undefined)\n return (\n <pre className=\"overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed\">\n {children}\n </pre>\n );\n // Markdown ends every fence with a newline the author did not type.\n return <CodeBlock text={textOf(code.props.children).replace(/\\n$/, '')} className={code.props.className} />;\n}\nconst COMPONENTS: Components = {\n p: ({ children }) => <p className=\"whitespace-pre-wrap break-words\">{children}</p>,\n strong: ({ children }) => <strong className=\"font-bold text-doom-hi\">{children}</strong>,\n em: ({ children }) => <em className=\"italic\">{children}</em>,\n del: ({ children }) => <del className=\"text-doom-dim line-through\">{children}</del>,\n a: Link,\n code: Code,\n pre: Pre,\n ul: ({ children }) => <ul className=\"list-disc pl-5\">{children}</ul>,\n ol: ({ children }) => <ol className=\"list-decimal pl-5\">{children}</ol>,\n li: ({ children }) => <li className=\"whitespace-pre-wrap break-words\">{children}</li>,\n h1: ({ children }) => <h1 className=\"text-lg font-bold text-doom-hi\">{children}</h1>,\n h2: ({ children }) => <h2 className=\"text-base font-bold text-doom-hi\">{children}</h2>,\n h3: ({ children }) => <h3 className=\"text-base font-bold text-doom-hi\">{children}</h3>,\n h4: ({ children }) => <h4 className=\"text-base font-bold text-doom-text\">{children}</h4>,\n blockquote: ({ children }) => (\n <blockquote className=\"border-l-2 border-doom-border pl-3 text-doom-dim\">{children}</blockquote>\n ),\n hr: () => <hr className=\"border-doom-border-soft\" />,\n table: ({ children }) => (\n <div className=\"overflow-x-auto\">\n <table className=\"border-collapse text-sm\">{children}</table>\n </div>\n ),\n th: ({ children }) => (\n <th className=\"border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi\">{children}</th>\n ),\n td: ({ children }) => <td className=\"border border-doom-border px-2 py-1 align-top\">{children}</td>,\n img: ({ src, alt }) => (\n <img src={src} alt={alt ?? ''} className=\"max-h-[480px] max-w-full rounded border border-doom-border\" />\n ),\n};\n\n/** GitHub-flavored Markdown using the cockpit's safe, shared presentation. */\nexport function Markdown({ text, onFileLink }: { text: string; onFileLink?: FileLinkHandler }) {\n return (\n <FileLinkContext.Provider value={onFileLink}>\n <div className=\"flex min-w-0 flex-col gap-2\">\n <ReactMarkdown remarkPlugins={REMARK_PLUGINS} components={COMPONENTS}>\n {text}\n </ReactMarkdown>\n </div>\n </FileLinkContext.Provider>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import{CodeBlock as e}from"./CodeBlock.mjs";import{jsx as t}from"react/jsx-runtime";import{createContext as n,isValidElement as r,useContext as i}from"react";import a from"react-markdown";import o from"remark-gfm";const s=[o],c=`rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green`,l=n(void 0);function u({className:e,children:n,...r}){let a=i(l),o=typeof n==`string`&&!n.includes(`
|
|
2
|
-
`)?n:void 0,s=o===void 0||e!==void 0?void 0:a?.(o);return s!==void 0&&o!==void 0
|
|
2
|
+
`)?n:void 0,s=o===void 0||e!==void 0?void 0:a?.(o);return s!==void 0&&o!==void 0?/* @__PURE__ */ t(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${o}`,onClick:s,className:`${c} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`,children:o}):/* @__PURE__ */ t(`code`,{...r,className:`${e??``} ${c} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`,children:n})}function d({href:e,children:n}){let r=i(l),a=e;try{a=e===void 0?void 0:decodeURIComponent(e)}catch{a=void 0}let o=a!==void 0&&a!==``&&!/^(?:[a-z][a-z\d+.-]*:|\/\/|#|\?)/i.test(a)?r?.(a,!0):void 0;return o===void 0?/* @__PURE__ */ t(`a`,{href:e,target:`_blank`,rel:`noreferrer`,className:`text-doom-blue underline decoration-doom-blue/50`,children:n}):/* @__PURE__ */ t(`button`,{type:`button`,"data-testid":`markdown-file-link`,title:`open ${a}`,onClick:o,className:`text-doom-blue underline decoration-doom-blue/50`,children:n})}function f(e){return typeof e==`string`?e:Array.isArray(e)?e.map(e=>f(e)).join(``):``}function p({children:n}){let i=r(n)?n:void 0;return i===void 0?/* @__PURE__ */ t(`pre`,{className:`overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed`,children:n}):/* @__PURE__ */ t(e,{text:f(i.props.children).replace(/\n$/,``),className:i.props.className})}const m={p:({children:e})=>/* @__PURE__ */ t(`p`,{className:`whitespace-pre-wrap break-words`,children:e}),strong:({children:e})=>/* @__PURE__ */ t(`strong`,{className:`font-bold text-doom-hi`,children:e}),em:({children:e})=>/* @__PURE__ */ t(`em`,{className:`italic`,children:e}),del:({children:e})=>/* @__PURE__ */ t(`del`,{className:`text-doom-dim line-through`,children:e}),a:d,code:u,pre:p,ul:({children:e})=>/* @__PURE__ */ t(`ul`,{className:`list-disc pl-5`,children:e}),ol:({children:e})=>/* @__PURE__ */ t(`ol`,{className:`list-decimal pl-5`,children:e}),li:({children:e})=>/* @__PURE__ */ t(`li`,{className:`whitespace-pre-wrap break-words`,children:e}),h1:({children:e})=>/* @__PURE__ */ t(`h1`,{className:`text-lg font-bold text-doom-hi`,children:e}),h2:({children:e})=>/* @__PURE__ */ t(`h2`,{className:`text-base font-bold text-doom-hi`,children:e}),h3:({children:e})=>/* @__PURE__ */ t(`h3`,{className:`text-base font-bold text-doom-hi`,children:e}),h4:({children:e})=>/* @__PURE__ */ t(`h4`,{className:`text-base font-bold text-doom-text`,children:e}),blockquote:({children:e})=>/* @__PURE__ */ t(`blockquote`,{className:`border-l-2 border-doom-border pl-3 text-doom-dim`,children:e}),hr:()=>/* @__PURE__ */ t(`hr`,{className:`border-doom-border-soft`}),table:({children:e})=>/* @__PURE__ */ t(`div`,{className:`overflow-x-auto`,children:/* @__PURE__ */ t(`table`,{className:`border-collapse text-sm`,children:e})}),th:({children:e})=>/* @__PURE__ */ t(`th`,{className:`border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi`,children:e}),td:({children:e})=>/* @__PURE__ */ t(`td`,{className:`border border-doom-border px-2 py-1 align-top`,children:e}),img:({src:e,alt:n})=>/* @__PURE__ */ t(`img`,{src:e,alt:n??``,className:`max-h-[480px] max-w-full rounded border border-doom-border`})};function h({text:e,onFileLink:n}){return/* @__PURE__ */ t(l.Provider,{value:n,children:/* @__PURE__ */ t(`div`,{className:`flex min-w-0 flex-col gap-2`,children:/* @__PURE__ */ t(a,{remarkPlugins:s,components:m,children:e})})})}export{h as Markdown};
|
|
3
3
|
//# sourceMappingURL=Markdown.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Markdown.mjs","names":[],"sources":["../../../src/components/Markdown.tsx"],"sourcesContent":["import { createContext, type ComponentProps, isValidElement, type ReactNode, useContext } from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\n\nimport { CodeBlock } from './CodeBlock';\n\nconst REMARK_PLUGINS = [remarkGfm];\n\nconst CODE_CLASS = 'rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green';\n\n/**\n * What a piece of inline code opens, when the host knows.\n *\n * Returning undefined leaves the span as plain code, which is the answer for\n * everything a message quotes that is not a file: a class name, a flag, a\n * command. The caller decides what counts, because only it knows which files\n * the session has.\n */\nexport type FileLinkHandler = (text: string, explicit?: boolean) => (() => void) | undefined;\n\n/**\n * Passed by context rather than as a component prop so the component map stays\n * a module constant. Rebuilding that map per render gives react-markdown new\n * component identities, which remounts the whole rendered tree on every frame\n * of a streaming reply.\n */\nconst FileLinkContext = createContext<FileLinkHandler | undefined>(undefined);\n\nfunction Code({ className, children, ...rest }: ComponentProps<'code'>) {\n const onFileLink = useContext(FileLinkContext);\n // Only a single-line span with no language class is a candidate: a fenced\n // block is content, not a reference, and its own renderer owns it.\n const label = typeof children === 'string' && !children.includes('\\n') ? children : undefined;\n const open = label === undefined || className !== undefined ? undefined : onFileLink?.(label);\n if (open !== undefined && label !== undefined) {\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${label}`}\n onClick={open}\n className={`${CODE_CLASS} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`}\n >\n {label}\n </button>\n );\n }\n return (\n <code\n {...rest}\n className={`${className ?? ''} ${CODE_CLASS} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`}\n >\n {children}\n </code>\n );\n}\n\nfunction Link({ href, children }: ComponentProps<'a'>) {\n const onFileLink = useContext(FileLinkContext);\n let path = href;\n try {\n path = href === undefined ? undefined : decodeURIComponent(href);\n } catch {\n path = undefined;\n }\n const isFile = path !== undefined && path !== '' && !/^(?:[a-z][a-z\\d+.-]*:|\\/\\/|#|\\?)/i.test(path);\n const open = isFile ? onFileLink?.(path!, true) : undefined;\n if (open !== undefined)\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${path}`}\n onClick={open}\n className=\"text-doom-blue underline decoration-doom-blue/50\"\n >\n {children}\n </button>\n );\n return (\n <a href={href} target=\"_blank\" rel=\"noreferrer\" className=\"text-doom-blue underline decoration-doom-blue/50\">\n {children}\n </a>\n );\n}\n\n/** The text inside a fence, which react-markdown hands over as nested strings. */\nfunction textOf(children: ReactNode): string {\n if (typeof children === 'string') return children;\n if (Array.isArray(children)) return children.map((child) => textOf(child as ReactNode)).join('');\n return '';\n}\n\n/**\n * A fenced block, read off the <code> child that carries its language.\n *\n * The block is handed to `CodeBlock` whole rather than rendered as a styled\n * <pre> around inline code, because a fence is content with its own\n * presentation: it is coloured, it can be copied, and a mermaid fence is a\n * picture. A <pre> that came from raw HTML has no such child and keeps the\n * plain box.\n */\nfunction Pre({ children }: ComponentProps<'pre'>) {\n const code = isValidElement<{ className?: string; children?: ReactNode }>(children) ? children : undefined;\n if (code === undefined)\n return (\n <pre className=\"overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed\">\n {children}\n </pre>\n );\n // Markdown ends every fence with a newline the author did not type.\n return <CodeBlock text={textOf(code.props.children).replace(/\\n$/, '')} className={code.props.className} />;\n}\nconst COMPONENTS: Components = {\n p: ({ children }) => <p className=\"whitespace-pre-wrap break-words\">{children}</p>,\n strong: ({ children }) => <strong className=\"font-bold text-doom-hi\">{children}</strong>,\n em: ({ children }) => <em className=\"italic\">{children}</em>,\n del: ({ children }) => <del className=\"text-doom-dim line-through\">{children}</del>,\n a: Link,\n code: Code,\n pre: Pre,\n ul: ({ children }) => <ul className=\"list-disc pl-5\">{children}</ul>,\n ol: ({ children }) => <ol className=\"list-decimal pl-5\">{children}</ol>,\n li: ({ children }) => <li className=\"whitespace-pre-wrap break-words\">{children}</li>,\n h1: ({ children }) => <h1 className=\"text-lg font-bold text-doom-hi\">{children}</h1>,\n h2: ({ children }) => <h2 className=\"text-base font-bold text-doom-hi\">{children}</h2>,\n h3: ({ children }) => <h3 className=\"text-base font-bold text-doom-hi\">{children}</h3>,\n h4: ({ children }) => <h4 className=\"text-base font-bold text-doom-text\">{children}</h4>,\n blockquote: ({ children }) => (\n <blockquote className=\"border-l-2 border-doom-border pl-3 text-doom-dim\">{children}</blockquote>\n ),\n hr: () => <hr className=\"border-doom-border-soft\" />,\n table: ({ children }) => (\n <div className=\"overflow-x-auto\">\n <table className=\"border-collapse text-sm\">{children}</table>\n </div>\n ),\n th: ({ children }) => (\n <th className=\"border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi\">{children}</th>\n ),\n td: ({ children }) => <td className=\"border border-doom-border px-2 py-1 align-top\">{children}</td>,\n img: ({ src, alt }) => (\n <img src={src} alt={alt ?? ''} className=\"max-h-[480px] max-w-full rounded border border-doom-border\" />\n ),\n};\n\n/** GitHub-flavored Markdown using the cockpit's safe, shared presentation. */\nexport function Markdown({ text, onFileLink }: { text: string; onFileLink?: FileLinkHandler }) {\n return (\n <FileLinkContext.Provider value={onFileLink}>\n <div className=\"flex min-w-0 flex-col gap-2\">\n <ReactMarkdown remarkPlugins={REMARK_PLUGINS} components={COMPONENTS}>\n {text}\n </ReactMarkdown>\n </div>\n </FileLinkContext.Provider>\n );\n}\n"],"mappings":"sNAMA,MAAM,EAAiB,CAAC,CAAS,EAE3B,EAAa,oEAkBb,EAAkB,EAA2C,IAAA,EAAS,EAE5E,SAAS,EAAK,CAAE,YAAW,WAAU,GAAG,GAAgC,CACtE,IAAM,EAAa,EAAW,CAAe,EAGvC,EAAQ,OAAO,GAAa,UAAY,CAAC,EAAS,SAAS;CAAI,EAAI,EAAW,IAAA,GAC9E,EAAO,IAAU,IAAA,IAAa,IAAc,IAAA,GAAY,IAAA,GAAY,IAAa,CAAK,EAc5F,OAbI,IAAS,IAAA,IAAa,IAAU,IAAA,GAEhC,
|
|
1
|
+
{"version":3,"file":"Markdown.mjs","names":[],"sources":["../../../src/components/Markdown.tsx"],"sourcesContent":["import { createContext, type ComponentProps, isValidElement, type ReactNode, useContext } from 'react';\nimport ReactMarkdown, { type Components } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\n\nimport { CodeBlock } from './CodeBlock';\n\nconst REMARK_PLUGINS = [remarkGfm];\n\nconst CODE_CLASS = 'rounded bg-doom-deep px-1 py-px font-mono text-sm text-doom-green';\n\n/**\n * What a piece of inline code opens, when the host knows.\n *\n * Returning undefined leaves the span as plain code, which is the answer for\n * everything a message quotes that is not a file: a class name, a flag, a\n * command. The caller decides what counts, because only it knows which files\n * the session has.\n */\nexport type FileLinkHandler = (text: string, explicit?: boolean) => (() => void) | undefined;\n\n/**\n * Passed by context rather than as a component prop so the component map stays\n * a module constant. Rebuilding that map per render gives react-markdown new\n * component identities, which remounts the whole rendered tree on every frame\n * of a streaming reply.\n */\nconst FileLinkContext = createContext<FileLinkHandler | undefined>(undefined);\n\nfunction Code({ className, children, ...rest }: ComponentProps<'code'>) {\n const onFileLink = useContext(FileLinkContext);\n // Only a single-line span with no language class is a candidate: a fenced\n // block is content, not a reference, and its own renderer owns it.\n const label = typeof children === 'string' && !children.includes('\\n') ? children : undefined;\n const open = label === undefined || className !== undefined ? undefined : onFileLink?.(label);\n if (open !== undefined && label !== undefined) {\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${label}`}\n onClick={open}\n className={`${CODE_CLASS} cursor-pointer underline decoration-doom-green/40 underline-offset-2 hover:text-doom-hi hover:decoration-doom-hi`}\n >\n {label}\n </button>\n );\n }\n return (\n <code\n {...rest}\n className={`${className ?? ''} ${CODE_CLASS} [pre_&]:bg-transparent [pre_&]:p-0 [pre_&]:text-doom-hi`}\n >\n {children}\n </code>\n );\n}\n\nfunction Link({ href, children }: ComponentProps<'a'>) {\n const onFileLink = useContext(FileLinkContext);\n let path = href;\n try {\n path = href === undefined ? undefined : decodeURIComponent(href);\n } catch {\n path = undefined;\n }\n const isFile = path !== undefined && path !== '' && !/^(?:[a-z][a-z\\d+.-]*:|\\/\\/|#|\\?)/i.test(path);\n const open = isFile ? onFileLink?.(path!, true) : undefined;\n if (open !== undefined)\n return (\n <button\n type=\"button\"\n data-testid=\"markdown-file-link\"\n title={`open ${path}`}\n onClick={open}\n className=\"text-doom-blue underline decoration-doom-blue/50\"\n >\n {children}\n </button>\n );\n return (\n <a href={href} target=\"_blank\" rel=\"noreferrer\" className=\"text-doom-blue underline decoration-doom-blue/50\">\n {children}\n </a>\n );\n}\n\n/** The text inside a fence, which react-markdown hands over as nested strings. */\nfunction textOf(children: ReactNode): string {\n if (typeof children === 'string') return children;\n if (Array.isArray(children)) return children.map((child) => textOf(child as ReactNode)).join('');\n return '';\n}\n\n/**\n * A fenced block, read off the <code> child that carries its language.\n *\n * The block is handed to `CodeBlock` whole rather than rendered as a styled\n * <pre> around inline code, because a fence is content with its own\n * presentation: it is coloured, it can be copied, and a mermaid fence is a\n * picture. A <pre> that came from raw HTML has no such child and keeps the\n * plain box.\n */\nfunction Pre({ children }: ComponentProps<'pre'>) {\n const code = isValidElement<{ className?: string; children?: ReactNode }>(children) ? children : undefined;\n if (code === undefined)\n return (\n <pre className=\"overflow-x-auto rounded border border-doom-border bg-doom-deep p-2.5 text-sm leading-relaxed\">\n {children}\n </pre>\n );\n // Markdown ends every fence with a newline the author did not type.\n return <CodeBlock text={textOf(code.props.children).replace(/\\n$/, '')} className={code.props.className} />;\n}\nconst COMPONENTS: Components = {\n p: ({ children }) => <p className=\"whitespace-pre-wrap break-words\">{children}</p>,\n strong: ({ children }) => <strong className=\"font-bold text-doom-hi\">{children}</strong>,\n em: ({ children }) => <em className=\"italic\">{children}</em>,\n del: ({ children }) => <del className=\"text-doom-dim line-through\">{children}</del>,\n a: Link,\n code: Code,\n pre: Pre,\n ul: ({ children }) => <ul className=\"list-disc pl-5\">{children}</ul>,\n ol: ({ children }) => <ol className=\"list-decimal pl-5\">{children}</ol>,\n li: ({ children }) => <li className=\"whitespace-pre-wrap break-words\">{children}</li>,\n h1: ({ children }) => <h1 className=\"text-lg font-bold text-doom-hi\">{children}</h1>,\n h2: ({ children }) => <h2 className=\"text-base font-bold text-doom-hi\">{children}</h2>,\n h3: ({ children }) => <h3 className=\"text-base font-bold text-doom-hi\">{children}</h3>,\n h4: ({ children }) => <h4 className=\"text-base font-bold text-doom-text\">{children}</h4>,\n blockquote: ({ children }) => (\n <blockquote className=\"border-l-2 border-doom-border pl-3 text-doom-dim\">{children}</blockquote>\n ),\n hr: () => <hr className=\"border-doom-border-soft\" />,\n table: ({ children }) => (\n <div className=\"overflow-x-auto\">\n <table className=\"border-collapse text-sm\">{children}</table>\n </div>\n ),\n th: ({ children }) => (\n <th className=\"border border-doom-border bg-doom-deep px-2 py-1 text-left font-bold text-doom-hi\">{children}</th>\n ),\n td: ({ children }) => <td className=\"border border-doom-border px-2 py-1 align-top\">{children}</td>,\n img: ({ src, alt }) => (\n <img src={src} alt={alt ?? ''} className=\"max-h-[480px] max-w-full rounded border border-doom-border\" />\n ),\n};\n\n/** GitHub-flavored Markdown using the cockpit's safe, shared presentation. */\nexport function Markdown({ text, onFileLink }: { text: string; onFileLink?: FileLinkHandler }) {\n return (\n <FileLinkContext.Provider value={onFileLink}>\n <div className=\"flex min-w-0 flex-col gap-2\">\n <ReactMarkdown remarkPlugins={REMARK_PLUGINS} components={COMPONENTS}>\n {text}\n </ReactMarkdown>\n </div>\n </FileLinkContext.Provider>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("../icons/icons.cjs");const e=require("../lib/cn.cjs"),t=require("./Badge.cjs"),n=require("../lib/media.cjs"),r=require("../lib/mediaPlayback.cjs"),i=require("./PdfPreview.cjs");let a=require("react/jsx-runtime"),o=require("react"),s=require("lucide-react");const c=`max-w-full rounded border border-doom-border bg-doom-deep`;function l(e){return{playing:!e.paused&&!e.ended,currentTime:e.currentTime,duration:Number.isFinite(e.duration)?e.duration:0}}function u(e,t={current:void 0}){return{play:async()=>{let t=e.current;t!==null&&await t.play()},pause:()=>e.current?.pause(),seek:t=>{let n=e.current;n!==null&&(n.currentTime=r.boundedMediaTime(t,n.duration))},getState:()=>{let t=e.current;return t===null?{playing:!1,currentTime:0,duration:0}:l(t)},isFrameReady:()=>{let n=e.current;return n!==null&&!n.seeking&&n.readyState>=2&&(n.requestVideoFrameCallback===void 0||t.current!==void 0)},getIntrinsicSize:()=>{let t=e.current;return t===null||t.videoWidth<=0||t.videoHeight<=0?null:{width:t.videoWidth,height:t.videoHeight}},captureFrame:async(n=`image/png`,r)=>{let i=e.current;if(i===null||i.videoWidth<=0||i.videoHeight<=0||i.seeking)return null;let a=t.current===void 0?void 0:{...t.current},o=a?.mediaTime??i.currentTime,s=document.createElement(`canvas`);s.width=i.videoWidth,s.height=i.videoHeight;let c=s.getContext(`2d`);if(c===null)return null;c.drawImage(i,0,0,s.width,s.height);let l=await new Promise(e=>s.toBlob(e,n,r));return l===null?null:{blob:l,width:s.width,height:s.height,timeSeconds:o,...a===void 0?{}:{metadata:a}}}}}function d({src:t,className:n,testId:r,controllerRef:i,onPlaybackStateChange:s,videoControls:d=!0}){let f=(0,o.useRef)(null),p=(0,o.useRef)(void 0),m=()=>{let e=f.current;e!==null&&s?.(l(e))},h=(0,o.useRef)(m);return(0,o.useEffect)(()=>{h.current=m}),(0,o.useImperativeHandle)(i,()=>u(f,p),[]),(0,o.useEffect)(()=>{let e=f.current;if(e===null||e.requestVideoFrameCallback===void 0)return;let t=0,n=(r,i)=>{p.current={mediaTime:i.mediaTime,...Number.isFinite(i.presentedFrames)?{presentedFrames:i.presentedFrames}:{}},h.current(),t=e.requestVideoFrameCallback(n)};return t=e.requestVideoFrameCallback(n),()=>e.cancelVideoFrameCallback(t)},[])
|
|
1
|
+
require("../icons/icons.cjs");const e=require("../lib/cn.cjs"),t=require("./Badge.cjs"),n=require("../lib/media.cjs"),r=require("../lib/mediaPlayback.cjs"),i=require("./PdfPreview.cjs");let a=require("react/jsx-runtime"),o=require("react"),s=require("lucide-react");const c=`max-w-full rounded border border-doom-border bg-doom-deep`;function l(e){return{playing:!e.paused&&!e.ended,currentTime:e.currentTime,duration:Number.isFinite(e.duration)?e.duration:0}}function u(e,t={current:void 0}){return{play:async()=>{let t=e.current;t!==null&&await t.play()},pause:()=>e.current?.pause(),seek:t=>{let n=e.current;n!==null&&(n.currentTime=r.boundedMediaTime(t,n.duration))},getState:()=>{let t=e.current;return t===null?{playing:!1,currentTime:0,duration:0}:l(t)},isFrameReady:()=>{let n=e.current;return n!==null&&!n.seeking&&n.readyState>=2&&(n.requestVideoFrameCallback===void 0||t.current!==void 0)},getIntrinsicSize:()=>{let t=e.current;return t===null||t.videoWidth<=0||t.videoHeight<=0?null:{width:t.videoWidth,height:t.videoHeight}},captureFrame:async(n=`image/png`,r)=>{let i=e.current;if(i===null||i.videoWidth<=0||i.videoHeight<=0||i.seeking)return null;let a=t.current===void 0?void 0:{...t.current},o=a?.mediaTime??i.currentTime,s=document.createElement(`canvas`);s.width=i.videoWidth,s.height=i.videoHeight;let c=s.getContext(`2d`);if(c===null)return null;c.drawImage(i,0,0,s.width,s.height);let l=await new Promise(e=>s.toBlob(e,n,r));return l===null?null:{blob:l,width:s.width,height:s.height,timeSeconds:o,...a===void 0?{}:{metadata:a}}}}}function d({src:t,className:n,testId:r,controllerRef:i,onPlaybackStateChange:s,videoControls:d=!0}){let f=(0,o.useRef)(null),p=(0,o.useRef)(void 0),m=()=>{let e=f.current;e!==null&&s?.(l(e))},h=(0,o.useRef)(m);return(0,o.useEffect)(()=>{h.current=m}),(0,o.useImperativeHandle)(i,()=>u(f,p),[]),(0,o.useEffect)(()=>{let e=f.current;if(e===null||e.requestVideoFrameCallback===void 0)return;let t=0,n=(r,i)=>{p.current={mediaTime:i.mediaTime,...Number.isFinite(i.presentedFrames)?{presentedFrames:i.presentedFrames}:{}},h.current(),t=e.requestVideoFrameCallback(n)};return t=e.requestVideoFrameCallback(n),()=>e.cancelVideoFrameCallback(t)},[]),/* @__PURE__ */ (0,a.jsx)(`video`,{ref:f,src:t,controls:d,playsInline:!0,preload:`metadata`,"data-testid":r,"data-kind":`video`,className:e.cn(c,`max-h-[36rem]`,n),onPlay:m,onPause:m,onTimeUpdate:m,onDurationChange:m,onEnded:m,onLoadedMetadata:m,onLoadedData:m,onSeeking:()=>{p.current=void 0,m()},onSeeked:m,children:/* @__PURE__ */ (0,a.jsx)(`track`,{kind:`captions`})})}function f({src:r,path:o,kind:l,className:u,controllerRef:f,pdfControllerRef:p,onPlaybackStateChange:m,videoControls:h,"data-testid":g}){let _=l??n.mediaKindOf(o),v=o.split(`/`).at(-1)??o;return _===`image`?/* @__PURE__ */ (0,a.jsx)(`a`,{href:r,target:`_blank`,rel:`noreferrer`,"data-testid":g,"data-kind":_,children:/* @__PURE__ */ (0,a.jsx)(`img`,{src:r,alt:o,className:e.cn(c,`max-h-[36rem]`,u)})}):_===`video`?/* @__PURE__ */ (0,a.jsx)(d,{src:r,className:u,controllerRef:f,onPlaybackStateChange:m,testId:g,videoControls:h}):_===`pdf`?/* @__PURE__ */ (0,a.jsx)(i.PdfPreview,{src:r,path:o,controllerRef:p,"data-testid":g,className:u}):/* @__PURE__ */ (0,a.jsx)(t.Badge,{asChild:!0,size:`md`,"data-testid":g,"data-kind":_,className:e.cn(`self-start bg-doom-panel text-doom-text hover:border-doom-blue/50`,u),children:/* @__PURE__ */ (0,a.jsxs)(`a`,{href:r,download:v,children:[/* @__PURE__ */ (0,a.jsx)(s.FileText,{className:`h-3 w-3 shrink-0 text-doom-faint`}),`download `,v]})})}exports.MediaPreview=f,exports.mediaPlaybackState=l,exports.mediaPreviewController=u;
|
|
2
2
|
//# sourceMappingURL=MediaPreview.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MediaPreview.cjs","names":["boundedMediaTime","useRef","cn","mediaKindOf","PdfPreview","Badge","FileIcon"],"sources":["../../../src/components/MediaPreview.tsx"],"sourcesContent":["import { useEffect, useImperativeHandle, useRef } from 'react';\n\nimport { FileIcon } from '../icons/icons';\nimport { cn } from '../lib/cn';\nimport { mediaKindOf } from '../lib/media';\nimport { boundedMediaTime } from '../lib/mediaPlayback';\nimport type { MediaKind } from '../types/editor';\nimport { Badge } from './Badge';\nimport { PdfPreview, type PdfPreviewController } from './PdfPreview';\n/**\n * A file the browser can show but not edit.\n *\n * It renders bytes a server is already serving, so the caller supplies the\n * URL and this decides only which element reads it. Everything the browser\n * cannot render is a download rather than a converted approximation: a `.docx`\n * turned into HTML is a different document, and offering to edit that one\n * would be offering to overwrite the real one with it.\n */\n\nexport interface MediaPlaybackState {\n playing: boolean;\n currentTime: number;\n duration: number;\n}\n\nexport interface MediaIntrinsicSize {\n width: number;\n height: number;\n}\n\nexport interface MediaFrameMetadata {\n mediaTime: number;\n presentedFrames?: number;\n}\n\nexport interface MediaFrameCapture {\n blob: Blob;\n width: number;\n height: number;\n timeSeconds: number;\n metadata?: MediaFrameMetadata;\n}\n\nexport interface MediaPreviewController {\n play: () => Promise<void>;\n pause: () => void;\n seek: (seconds: number) => void;\n getState: () => MediaPlaybackState;\n isFrameReady: () => boolean;\n getIntrinsicSize: () => MediaIntrinsicSize | null;\n captureFrame: (type?: 'image/png' | 'image/jpeg', quality?: number) => Promise<MediaFrameCapture | null>;\n}\n\nexport interface MediaPreviewProps {\n /** Where the bytes are served from. */\n src: string;\n /** The file's path, used for the accessible name and the download name. */\n path: string;\n /** Overrides the kind inferred from the path, for a server that knows better. */\n kind?: MediaKind;\n className?: string;\n videoControls?: boolean;\n 'data-testid'?: string;\n /** Video-only controller. It is assigned while the video element is mounted. */\n controllerRef?: import('react').Ref<MediaPreviewController>;\n /** PDF-only page and geometry controller. */\n pdfControllerRef?: import('react').Ref<PdfPreviewController>;\n /** Reports browser playback state changes for video previews. */\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nconst FRAME = 'max-w-full rounded border border-doom-border bg-doom-deep';\n\ninterface VideoPreviewProps {\n src: string;\n className?: string;\n testId?: string;\n videoControls?: boolean;\n controllerRef?: import('react').Ref<MediaPreviewController>;\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nexport function mediaPlaybackState(video: HTMLVideoElement): MediaPlaybackState {\n return {\n playing: !video.paused && !video.ended,\n currentTime: video.currentTime,\n duration: Number.isFinite(video.duration) ? video.duration : 0,\n };\n}\n\nexport function mediaPreviewController(\n videoRef: { readonly current: HTMLVideoElement | null },\n frameMetadataRef: { readonly current: MediaFrameMetadata | undefined } = { current: undefined },\n): MediaPreviewController {\n return {\n play: async () => {\n const video = videoRef.current;\n if (video !== null) await video.play();\n },\n pause: () => videoRef.current?.pause(),\n seek: (seconds) => {\n const video = videoRef.current;\n if (video !== null) video.currentTime = boundedMediaTime(seconds, video.duration);\n },\n getState: () => {\n const video = videoRef.current;\n return video === null ? { playing: false, currentTime: 0, duration: 0 } : mediaPlaybackState(video);\n },\n isFrameReady: () => {\n const video = videoRef.current;\n return (\n video !== null &&\n !video.seeking &&\n video.readyState >= 2 &&\n (video.requestVideoFrameCallback === undefined || frameMetadataRef.current !== undefined)\n );\n },\n getIntrinsicSize: () => {\n const video = videoRef.current;\n return video === null || video.videoWidth <= 0 || video.videoHeight <= 0\n ? null\n : { width: video.videoWidth, height: video.videoHeight };\n },\n captureFrame: async (type = 'image/png', quality) => {\n const video = videoRef.current;\n if (video === null || video.videoWidth <= 0 || video.videoHeight <= 0 || video.seeking) return null;\n const metadata = frameMetadataRef.current === undefined ? undefined : { ...frameMetadataRef.current };\n const timeSeconds = metadata?.mediaTime ?? video.currentTime;\n const canvas = document.createElement('canvas');\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n const context = canvas.getContext('2d');\n if (context === null) return null;\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (blob === null) return null;\n return {\n blob,\n width: canvas.width,\n height: canvas.height,\n timeSeconds,\n ...(metadata === undefined ? {} : { metadata }),\n };\n },\n };\n}\n\nfunction VideoPreview({\n src,\n className,\n testId,\n controllerRef,\n onPlaybackStateChange,\n videoControls = true,\n}: VideoPreviewProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n const frameMetadataRef = useRef<MediaFrameMetadata | undefined>(undefined);\n const reportState = () => {\n const video = videoRef.current;\n if (video !== null) onPlaybackStateChange?.(mediaPlaybackState(video));\n };\n const reportStateRef = useRef(reportState);\n useEffect(() => {\n reportStateRef.current = reportState;\n });\n useImperativeHandle(controllerRef, () => mediaPreviewController(videoRef, frameMetadataRef), []);\n useEffect(() => {\n const video = videoRef.current;\n if (video === null || video.requestVideoFrameCallback === undefined) return;\n let request = 0;\n const report: VideoFrameRequestCallback = (_now, metadata) => {\n frameMetadataRef.current = {\n mediaTime: metadata.mediaTime,\n ...(Number.isFinite(metadata.presentedFrames) ? { presentedFrames: metadata.presentedFrames } : {}),\n };\n reportStateRef.current();\n request = video.requestVideoFrameCallback(report);\n };\n request = video.requestVideoFrameCallback(report);\n return () => video.cancelVideoFrameCallback(request);\n }, []);\n return (\n <video\n ref={videoRef}\n src={src}\n controls={videoControls}\n playsInline\n preload=\"metadata\"\n data-testid={testId}\n data-kind=\"video\"\n className={cn(FRAME, 'max-h-[36rem]', className)}\n onPlay={reportState}\n onPause={reportState}\n onTimeUpdate={reportState}\n onDurationChange={reportState}\n onEnded={reportState}\n onLoadedMetadata={reportState}\n onLoadedData={reportState}\n onSeeking={() => {\n frameMetadataRef.current = undefined;\n reportState();\n }}\n onSeeked={reportState}\n >\n <track kind=\"captions\" />\n </video>\n );\n}\n\nexport function MediaPreview({\n src,\n path,\n kind,\n className,\n controllerRef,\n pdfControllerRef,\n onPlaybackStateChange,\n videoControls,\n 'data-testid': testId,\n}: MediaPreviewProps) {\n const resolved = kind ?? mediaKindOf(path);\n const name = path.split('/').at(-1) ?? path;\n\n if (resolved === 'image') {\n return (\n <a href={src} target=\"_blank\" rel=\"noreferrer\" data-testid={testId} data-kind={resolved}>\n <img src={src} alt={path} className={cn(FRAME, 'max-h-[36rem]', className)} />\n </a>\n );\n }\n if (resolved === 'video') {\n return (\n <VideoPreview\n src={src}\n className={className}\n controllerRef={controllerRef}\n onPlaybackStateChange={onPlaybackStateChange}\n testId={testId}\n videoControls={videoControls}\n />\n );\n }\n if (resolved === 'pdf') {\n return (\n <PdfPreview src={src} path={path} controllerRef={pdfControllerRef} data-testid={testId} className={className} />\n );\n }\n return (\n <Badge\n asChild\n size=\"md\"\n data-testid={testId}\n data-kind={resolved}\n className={cn('self-start bg-doom-panel text-doom-text hover:border-doom-blue/50', className)}\n >\n <a href={src} download={name}>\n <FileIcon className=\"h-3 w-3 shrink-0 text-doom-faint\" />\n download {name}\n </a>\n </Badge>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MediaPreview.cjs","names":["boundedMediaTime","useRef","cn","mediaKindOf","PdfPreview","Badge","FileIcon"],"sources":["../../../src/components/MediaPreview.tsx"],"sourcesContent":["import { useEffect, useImperativeHandle, useRef } from 'react';\n\nimport { FileIcon } from '../icons/icons';\nimport { cn } from '../lib/cn';\nimport { mediaKindOf } from '../lib/media';\nimport { boundedMediaTime } from '../lib/mediaPlayback';\nimport type { MediaKind } from '../types/editor';\nimport { Badge } from './Badge';\nimport { PdfPreview, type PdfPreviewController } from './PdfPreview';\n/**\n * A file the browser can show but not edit.\n *\n * It renders bytes a server is already serving, so the caller supplies the\n * URL and this decides only which element reads it. Everything the browser\n * cannot render is a download rather than a converted approximation: a `.docx`\n * turned into HTML is a different document, and offering to edit that one\n * would be offering to overwrite the real one with it.\n */\n\nexport interface MediaPlaybackState {\n playing: boolean;\n currentTime: number;\n duration: number;\n}\n\nexport interface MediaIntrinsicSize {\n width: number;\n height: number;\n}\n\nexport interface MediaFrameMetadata {\n mediaTime: number;\n presentedFrames?: number;\n}\n\nexport interface MediaFrameCapture {\n blob: Blob;\n width: number;\n height: number;\n timeSeconds: number;\n metadata?: MediaFrameMetadata;\n}\n\nexport interface MediaPreviewController {\n play: () => Promise<void>;\n pause: () => void;\n seek: (seconds: number) => void;\n getState: () => MediaPlaybackState;\n isFrameReady: () => boolean;\n getIntrinsicSize: () => MediaIntrinsicSize | null;\n captureFrame: (type?: 'image/png' | 'image/jpeg', quality?: number) => Promise<MediaFrameCapture | null>;\n}\n\nexport interface MediaPreviewProps {\n /** Where the bytes are served from. */\n src: string;\n /** The file's path, used for the accessible name and the download name. */\n path: string;\n /** Overrides the kind inferred from the path, for a server that knows better. */\n kind?: MediaKind;\n className?: string;\n videoControls?: boolean;\n 'data-testid'?: string;\n /** Video-only controller. It is assigned while the video element is mounted. */\n controllerRef?: import('react').Ref<MediaPreviewController>;\n /** PDF-only page and geometry controller. */\n pdfControllerRef?: import('react').Ref<PdfPreviewController>;\n /** Reports browser playback state changes for video previews. */\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nconst FRAME = 'max-w-full rounded border border-doom-border bg-doom-deep';\n\ninterface VideoPreviewProps {\n src: string;\n className?: string;\n testId?: string;\n videoControls?: boolean;\n controllerRef?: import('react').Ref<MediaPreviewController>;\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nexport function mediaPlaybackState(video: HTMLVideoElement): MediaPlaybackState {\n return {\n playing: !video.paused && !video.ended,\n currentTime: video.currentTime,\n duration: Number.isFinite(video.duration) ? video.duration : 0,\n };\n}\n\nexport function mediaPreviewController(\n videoRef: { readonly current: HTMLVideoElement | null },\n frameMetadataRef: { readonly current: MediaFrameMetadata | undefined } = { current: undefined },\n): MediaPreviewController {\n return {\n play: async () => {\n const video = videoRef.current;\n if (video !== null) await video.play();\n },\n pause: () => videoRef.current?.pause(),\n seek: (seconds) => {\n const video = videoRef.current;\n if (video !== null) video.currentTime = boundedMediaTime(seconds, video.duration);\n },\n getState: () => {\n const video = videoRef.current;\n return video === null ? { playing: false, currentTime: 0, duration: 0 } : mediaPlaybackState(video);\n },\n isFrameReady: () => {\n const video = videoRef.current;\n return (\n video !== null &&\n !video.seeking &&\n video.readyState >= 2 &&\n (video.requestVideoFrameCallback === undefined || frameMetadataRef.current !== undefined)\n );\n },\n getIntrinsicSize: () => {\n const video = videoRef.current;\n return video === null || video.videoWidth <= 0 || video.videoHeight <= 0\n ? null\n : { width: video.videoWidth, height: video.videoHeight };\n },\n captureFrame: async (type = 'image/png', quality) => {\n const video = videoRef.current;\n if (video === null || video.videoWidth <= 0 || video.videoHeight <= 0 || video.seeking) return null;\n const metadata = frameMetadataRef.current === undefined ? undefined : { ...frameMetadataRef.current };\n const timeSeconds = metadata?.mediaTime ?? video.currentTime;\n const canvas = document.createElement('canvas');\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n const context = canvas.getContext('2d');\n if (context === null) return null;\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (blob === null) return null;\n return {\n blob,\n width: canvas.width,\n height: canvas.height,\n timeSeconds,\n ...(metadata === undefined ? {} : { metadata }),\n };\n },\n };\n}\n\nfunction VideoPreview({\n src,\n className,\n testId,\n controllerRef,\n onPlaybackStateChange,\n videoControls = true,\n}: VideoPreviewProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n const frameMetadataRef = useRef<MediaFrameMetadata | undefined>(undefined);\n const reportState = () => {\n const video = videoRef.current;\n if (video !== null) onPlaybackStateChange?.(mediaPlaybackState(video));\n };\n const reportStateRef = useRef(reportState);\n useEffect(() => {\n reportStateRef.current = reportState;\n });\n useImperativeHandle(controllerRef, () => mediaPreviewController(videoRef, frameMetadataRef), []);\n useEffect(() => {\n const video = videoRef.current;\n if (video === null || video.requestVideoFrameCallback === undefined) return;\n let request = 0;\n const report: VideoFrameRequestCallback = (_now, metadata) => {\n frameMetadataRef.current = {\n mediaTime: metadata.mediaTime,\n ...(Number.isFinite(metadata.presentedFrames) ? { presentedFrames: metadata.presentedFrames } : {}),\n };\n reportStateRef.current();\n request = video.requestVideoFrameCallback(report);\n };\n request = video.requestVideoFrameCallback(report);\n return () => video.cancelVideoFrameCallback(request);\n }, []);\n return (\n <video\n ref={videoRef}\n src={src}\n controls={videoControls}\n playsInline\n preload=\"metadata\"\n data-testid={testId}\n data-kind=\"video\"\n className={cn(FRAME, 'max-h-[36rem]', className)}\n onPlay={reportState}\n onPause={reportState}\n onTimeUpdate={reportState}\n onDurationChange={reportState}\n onEnded={reportState}\n onLoadedMetadata={reportState}\n onLoadedData={reportState}\n onSeeking={() => {\n frameMetadataRef.current = undefined;\n reportState();\n }}\n onSeeked={reportState}\n >\n <track kind=\"captions\" />\n </video>\n );\n}\n\nexport function MediaPreview({\n src,\n path,\n kind,\n className,\n controllerRef,\n pdfControllerRef,\n onPlaybackStateChange,\n videoControls,\n 'data-testid': testId,\n}: MediaPreviewProps) {\n const resolved = kind ?? mediaKindOf(path);\n const name = path.split('/').at(-1) ?? path;\n\n if (resolved === 'image') {\n return (\n <a href={src} target=\"_blank\" rel=\"noreferrer\" data-testid={testId} data-kind={resolved}>\n <img src={src} alt={path} className={cn(FRAME, 'max-h-[36rem]', className)} />\n </a>\n );\n }\n if (resolved === 'video') {\n return (\n <VideoPreview\n src={src}\n className={className}\n controllerRef={controllerRef}\n onPlaybackStateChange={onPlaybackStateChange}\n testId={testId}\n videoControls={videoControls}\n />\n );\n }\n if (resolved === 'pdf') {\n return (\n <PdfPreview src={src} path={path} controllerRef={pdfControllerRef} data-testid={testId} className={className} />\n );\n }\n return (\n <Badge\n asChild\n size=\"md\"\n data-testid={testId}\n data-kind={resolved}\n className={cn('self-start bg-doom-panel text-doom-text hover:border-doom-blue/50', className)}\n >\n <a href={src} download={name}>\n <FileIcon className=\"h-3 w-3 shrink-0 text-doom-faint\" />\n download {name}\n </a>\n </Badge>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{FileIcon as e}from"../icons/icons.mjs";import{cn as t}from"../lib/cn.mjs";import{Badge as n}from"./Badge.mjs";import{mediaKindOf as r}from"../lib/media.mjs";import{boundedMediaTime as i}from"../lib/mediaPlayback.mjs";import{PdfPreview as a}from"./PdfPreview.mjs";import{jsx as o,jsxs as s}from"react/jsx-runtime";import{useEffect as c,useImperativeHandle as l,useRef as u}from"react";const d=`max-w-full rounded border border-doom-border bg-doom-deep`;function f(e){return{playing:!e.paused&&!e.ended,currentTime:e.currentTime,duration:Number.isFinite(e.duration)?e.duration:0}}function p(e,t={current:void 0}){return{play:async()=>{let t=e.current;t!==null&&await t.play()},pause:()=>e.current?.pause(),seek:t=>{let n=e.current;n!==null&&(n.currentTime=i(t,n.duration))},getState:()=>{let t=e.current;return t===null?{playing:!1,currentTime:0,duration:0}:f(t)},isFrameReady:()=>{let n=e.current;return n!==null&&!n.seeking&&n.readyState>=2&&(n.requestVideoFrameCallback===void 0||t.current!==void 0)},getIntrinsicSize:()=>{let t=e.current;return t===null||t.videoWidth<=0||t.videoHeight<=0?null:{width:t.videoWidth,height:t.videoHeight}},captureFrame:async(n=`image/png`,r)=>{let i=e.current;if(i===null||i.videoWidth<=0||i.videoHeight<=0||i.seeking)return null;let a=t.current===void 0?void 0:{...t.current},o=a?.mediaTime??i.currentTime,s=document.createElement(`canvas`);s.width=i.videoWidth,s.height=i.videoHeight;let c=s.getContext(`2d`);if(c===null)return null;c.drawImage(i,0,0,s.width,s.height);let l=await new Promise(e=>s.toBlob(e,n,r));return l===null?null:{blob:l,width:s.width,height:s.height,timeSeconds:o,...a===void 0?{}:{metadata:a}}}}}function m({src:e,className:n,testId:r,controllerRef:i,onPlaybackStateChange:a,videoControls:s=!0}){let m=u(null),h=u(void 0),g=()=>{let e=m.current;e!==null&&a?.(f(e))},_=u(g);return c(()=>{_.current=g}),l(i,()=>p(m,h),[]),c(()=>{let e=m.current;if(e===null||e.requestVideoFrameCallback===void 0)return;let t=0,n=(r,i)=>{h.current={mediaTime:i.mediaTime,...Number.isFinite(i.presentedFrames)?{presentedFrames:i.presentedFrames}:{}},_.current(),t=e.requestVideoFrameCallback(n)};return t=e.requestVideoFrameCallback(n),()=>e.cancelVideoFrameCallback(t)},[])
|
|
1
|
+
import{FileIcon as e}from"../icons/icons.mjs";import{cn as t}from"../lib/cn.mjs";import{Badge as n}from"./Badge.mjs";import{mediaKindOf as r}from"../lib/media.mjs";import{boundedMediaTime as i}from"../lib/mediaPlayback.mjs";import{PdfPreview as a}from"./PdfPreview.mjs";import{jsx as o,jsxs as s}from"react/jsx-runtime";import{useEffect as c,useImperativeHandle as l,useRef as u}from"react";const d=`max-w-full rounded border border-doom-border bg-doom-deep`;function f(e){return{playing:!e.paused&&!e.ended,currentTime:e.currentTime,duration:Number.isFinite(e.duration)?e.duration:0}}function p(e,t={current:void 0}){return{play:async()=>{let t=e.current;t!==null&&await t.play()},pause:()=>e.current?.pause(),seek:t=>{let n=e.current;n!==null&&(n.currentTime=i(t,n.duration))},getState:()=>{let t=e.current;return t===null?{playing:!1,currentTime:0,duration:0}:f(t)},isFrameReady:()=>{let n=e.current;return n!==null&&!n.seeking&&n.readyState>=2&&(n.requestVideoFrameCallback===void 0||t.current!==void 0)},getIntrinsicSize:()=>{let t=e.current;return t===null||t.videoWidth<=0||t.videoHeight<=0?null:{width:t.videoWidth,height:t.videoHeight}},captureFrame:async(n=`image/png`,r)=>{let i=e.current;if(i===null||i.videoWidth<=0||i.videoHeight<=0||i.seeking)return null;let a=t.current===void 0?void 0:{...t.current},o=a?.mediaTime??i.currentTime,s=document.createElement(`canvas`);s.width=i.videoWidth,s.height=i.videoHeight;let c=s.getContext(`2d`);if(c===null)return null;c.drawImage(i,0,0,s.width,s.height);let l=await new Promise(e=>s.toBlob(e,n,r));return l===null?null:{blob:l,width:s.width,height:s.height,timeSeconds:o,...a===void 0?{}:{metadata:a}}}}}function m({src:e,className:n,testId:r,controllerRef:i,onPlaybackStateChange:a,videoControls:s=!0}){let m=u(null),h=u(void 0),g=()=>{let e=m.current;e!==null&&a?.(f(e))},_=u(g);return c(()=>{_.current=g}),l(i,()=>p(m,h),[]),c(()=>{let e=m.current;if(e===null||e.requestVideoFrameCallback===void 0)return;let t=0,n=(r,i)=>{h.current={mediaTime:i.mediaTime,...Number.isFinite(i.presentedFrames)?{presentedFrames:i.presentedFrames}:{}},_.current(),t=e.requestVideoFrameCallback(n)};return t=e.requestVideoFrameCallback(n),()=>e.cancelVideoFrameCallback(t)},[]),/* @__PURE__ */ o(`video`,{ref:m,src:e,controls:s,playsInline:!0,preload:`metadata`,"data-testid":r,"data-kind":`video`,className:t(d,`max-h-[36rem]`,n),onPlay:g,onPause:g,onTimeUpdate:g,onDurationChange:g,onEnded:g,onLoadedMetadata:g,onLoadedData:g,onSeeking:()=>{h.current=void 0,g()},onSeeked:g,children:/* @__PURE__ */ o(`track`,{kind:`captions`})})}function h({src:i,path:c,kind:l,className:u,controllerRef:f,pdfControllerRef:p,onPlaybackStateChange:h,videoControls:g,"data-testid":_}){let v=l??r(c),y=c.split(`/`).at(-1)??c;return v===`image`?/* @__PURE__ */ o(`a`,{href:i,target:`_blank`,rel:`noreferrer`,"data-testid":_,"data-kind":v,children:/* @__PURE__ */ o(`img`,{src:i,alt:c,className:t(d,`max-h-[36rem]`,u)})}):v===`video`?/* @__PURE__ */ o(m,{src:i,className:u,controllerRef:f,onPlaybackStateChange:h,testId:_,videoControls:g}):v===`pdf`?/* @__PURE__ */ o(a,{src:i,path:c,controllerRef:p,"data-testid":_,className:u}):/* @__PURE__ */ o(n,{asChild:!0,size:`md`,"data-testid":_,"data-kind":v,className:t(`self-start bg-doom-panel text-doom-text hover:border-doom-blue/50`,u),children:/* @__PURE__ */ s(`a`,{href:i,download:y,children:[/* @__PURE__ */ o(e,{className:`h-3 w-3 shrink-0 text-doom-faint`}),`download `,y]})})}export{h as MediaPreview,f as mediaPlaybackState,p as mediaPreviewController};
|
|
2
2
|
//# sourceMappingURL=MediaPreview.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MediaPreview.mjs","names":[],"sources":["../../../src/components/MediaPreview.tsx"],"sourcesContent":["import { useEffect, useImperativeHandle, useRef } from 'react';\n\nimport { FileIcon } from '../icons/icons';\nimport { cn } from '../lib/cn';\nimport { mediaKindOf } from '../lib/media';\nimport { boundedMediaTime } from '../lib/mediaPlayback';\nimport type { MediaKind } from '../types/editor';\nimport { Badge } from './Badge';\nimport { PdfPreview, type PdfPreviewController } from './PdfPreview';\n/**\n * A file the browser can show but not edit.\n *\n * It renders bytes a server is already serving, so the caller supplies the\n * URL and this decides only which element reads it. Everything the browser\n * cannot render is a download rather than a converted approximation: a `.docx`\n * turned into HTML is a different document, and offering to edit that one\n * would be offering to overwrite the real one with it.\n */\n\nexport interface MediaPlaybackState {\n playing: boolean;\n currentTime: number;\n duration: number;\n}\n\nexport interface MediaIntrinsicSize {\n width: number;\n height: number;\n}\n\nexport interface MediaFrameMetadata {\n mediaTime: number;\n presentedFrames?: number;\n}\n\nexport interface MediaFrameCapture {\n blob: Blob;\n width: number;\n height: number;\n timeSeconds: number;\n metadata?: MediaFrameMetadata;\n}\n\nexport interface MediaPreviewController {\n play: () => Promise<void>;\n pause: () => void;\n seek: (seconds: number) => void;\n getState: () => MediaPlaybackState;\n isFrameReady: () => boolean;\n getIntrinsicSize: () => MediaIntrinsicSize | null;\n captureFrame: (type?: 'image/png' | 'image/jpeg', quality?: number) => Promise<MediaFrameCapture | null>;\n}\n\nexport interface MediaPreviewProps {\n /** Where the bytes are served from. */\n src: string;\n /** The file's path, used for the accessible name and the download name. */\n path: string;\n /** Overrides the kind inferred from the path, for a server that knows better. */\n kind?: MediaKind;\n className?: string;\n videoControls?: boolean;\n 'data-testid'?: string;\n /** Video-only controller. It is assigned while the video element is mounted. */\n controllerRef?: import('react').Ref<MediaPreviewController>;\n /** PDF-only page and geometry controller. */\n pdfControllerRef?: import('react').Ref<PdfPreviewController>;\n /** Reports browser playback state changes for video previews. */\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nconst FRAME = 'max-w-full rounded border border-doom-border bg-doom-deep';\n\ninterface VideoPreviewProps {\n src: string;\n className?: string;\n testId?: string;\n videoControls?: boolean;\n controllerRef?: import('react').Ref<MediaPreviewController>;\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nexport function mediaPlaybackState(video: HTMLVideoElement): MediaPlaybackState {\n return {\n playing: !video.paused && !video.ended,\n currentTime: video.currentTime,\n duration: Number.isFinite(video.duration) ? video.duration : 0,\n };\n}\n\nexport function mediaPreviewController(\n videoRef: { readonly current: HTMLVideoElement | null },\n frameMetadataRef: { readonly current: MediaFrameMetadata | undefined } = { current: undefined },\n): MediaPreviewController {\n return {\n play: async () => {\n const video = videoRef.current;\n if (video !== null) await video.play();\n },\n pause: () => videoRef.current?.pause(),\n seek: (seconds) => {\n const video = videoRef.current;\n if (video !== null) video.currentTime = boundedMediaTime(seconds, video.duration);\n },\n getState: () => {\n const video = videoRef.current;\n return video === null ? { playing: false, currentTime: 0, duration: 0 } : mediaPlaybackState(video);\n },\n isFrameReady: () => {\n const video = videoRef.current;\n return (\n video !== null &&\n !video.seeking &&\n video.readyState >= 2 &&\n (video.requestVideoFrameCallback === undefined || frameMetadataRef.current !== undefined)\n );\n },\n getIntrinsicSize: () => {\n const video = videoRef.current;\n return video === null || video.videoWidth <= 0 || video.videoHeight <= 0\n ? null\n : { width: video.videoWidth, height: video.videoHeight };\n },\n captureFrame: async (type = 'image/png', quality) => {\n const video = videoRef.current;\n if (video === null || video.videoWidth <= 0 || video.videoHeight <= 0 || video.seeking) return null;\n const metadata = frameMetadataRef.current === undefined ? undefined : { ...frameMetadataRef.current };\n const timeSeconds = metadata?.mediaTime ?? video.currentTime;\n const canvas = document.createElement('canvas');\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n const context = canvas.getContext('2d');\n if (context === null) return null;\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (blob === null) return null;\n return {\n blob,\n width: canvas.width,\n height: canvas.height,\n timeSeconds,\n ...(metadata === undefined ? {} : { metadata }),\n };\n },\n };\n}\n\nfunction VideoPreview({\n src,\n className,\n testId,\n controllerRef,\n onPlaybackStateChange,\n videoControls = true,\n}: VideoPreviewProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n const frameMetadataRef = useRef<MediaFrameMetadata | undefined>(undefined);\n const reportState = () => {\n const video = videoRef.current;\n if (video !== null) onPlaybackStateChange?.(mediaPlaybackState(video));\n };\n const reportStateRef = useRef(reportState);\n useEffect(() => {\n reportStateRef.current = reportState;\n });\n useImperativeHandle(controllerRef, () => mediaPreviewController(videoRef, frameMetadataRef), []);\n useEffect(() => {\n const video = videoRef.current;\n if (video === null || video.requestVideoFrameCallback === undefined) return;\n let request = 0;\n const report: VideoFrameRequestCallback = (_now, metadata) => {\n frameMetadataRef.current = {\n mediaTime: metadata.mediaTime,\n ...(Number.isFinite(metadata.presentedFrames) ? { presentedFrames: metadata.presentedFrames } : {}),\n };\n reportStateRef.current();\n request = video.requestVideoFrameCallback(report);\n };\n request = video.requestVideoFrameCallback(report);\n return () => video.cancelVideoFrameCallback(request);\n }, []);\n return (\n <video\n ref={videoRef}\n src={src}\n controls={videoControls}\n playsInline\n preload=\"metadata\"\n data-testid={testId}\n data-kind=\"video\"\n className={cn(FRAME, 'max-h-[36rem]', className)}\n onPlay={reportState}\n onPause={reportState}\n onTimeUpdate={reportState}\n onDurationChange={reportState}\n onEnded={reportState}\n onLoadedMetadata={reportState}\n onLoadedData={reportState}\n onSeeking={() => {\n frameMetadataRef.current = undefined;\n reportState();\n }}\n onSeeked={reportState}\n >\n <track kind=\"captions\" />\n </video>\n );\n}\n\nexport function MediaPreview({\n src,\n path,\n kind,\n className,\n controllerRef,\n pdfControllerRef,\n onPlaybackStateChange,\n videoControls,\n 'data-testid': testId,\n}: MediaPreviewProps) {\n const resolved = kind ?? mediaKindOf(path);\n const name = path.split('/').at(-1) ?? path;\n\n if (resolved === 'image') {\n return (\n <a href={src} target=\"_blank\" rel=\"noreferrer\" data-testid={testId} data-kind={resolved}>\n <img src={src} alt={path} className={cn(FRAME, 'max-h-[36rem]', className)} />\n </a>\n );\n }\n if (resolved === 'video') {\n return (\n <VideoPreview\n src={src}\n className={className}\n controllerRef={controllerRef}\n onPlaybackStateChange={onPlaybackStateChange}\n testId={testId}\n videoControls={videoControls}\n />\n );\n }\n if (resolved === 'pdf') {\n return (\n <PdfPreview src={src} path={path} controllerRef={pdfControllerRef} data-testid={testId} className={className} />\n );\n }\n return (\n <Badge\n asChild\n size=\"md\"\n data-testid={testId}\n data-kind={resolved}\n className={cn('self-start bg-doom-panel text-doom-text hover:border-doom-blue/50', className)}\n >\n <a href={src} download={name}>\n <FileIcon className=\"h-3 w-3 shrink-0 text-doom-faint\" />\n download {name}\n </a>\n </Badge>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MediaPreview.mjs","names":[],"sources":["../../../src/components/MediaPreview.tsx"],"sourcesContent":["import { useEffect, useImperativeHandle, useRef } from 'react';\n\nimport { FileIcon } from '../icons/icons';\nimport { cn } from '../lib/cn';\nimport { mediaKindOf } from '../lib/media';\nimport { boundedMediaTime } from '../lib/mediaPlayback';\nimport type { MediaKind } from '../types/editor';\nimport { Badge } from './Badge';\nimport { PdfPreview, type PdfPreviewController } from './PdfPreview';\n/**\n * A file the browser can show but not edit.\n *\n * It renders bytes a server is already serving, so the caller supplies the\n * URL and this decides only which element reads it. Everything the browser\n * cannot render is a download rather than a converted approximation: a `.docx`\n * turned into HTML is a different document, and offering to edit that one\n * would be offering to overwrite the real one with it.\n */\n\nexport interface MediaPlaybackState {\n playing: boolean;\n currentTime: number;\n duration: number;\n}\n\nexport interface MediaIntrinsicSize {\n width: number;\n height: number;\n}\n\nexport interface MediaFrameMetadata {\n mediaTime: number;\n presentedFrames?: number;\n}\n\nexport interface MediaFrameCapture {\n blob: Blob;\n width: number;\n height: number;\n timeSeconds: number;\n metadata?: MediaFrameMetadata;\n}\n\nexport interface MediaPreviewController {\n play: () => Promise<void>;\n pause: () => void;\n seek: (seconds: number) => void;\n getState: () => MediaPlaybackState;\n isFrameReady: () => boolean;\n getIntrinsicSize: () => MediaIntrinsicSize | null;\n captureFrame: (type?: 'image/png' | 'image/jpeg', quality?: number) => Promise<MediaFrameCapture | null>;\n}\n\nexport interface MediaPreviewProps {\n /** Where the bytes are served from. */\n src: string;\n /** The file's path, used for the accessible name and the download name. */\n path: string;\n /** Overrides the kind inferred from the path, for a server that knows better. */\n kind?: MediaKind;\n className?: string;\n videoControls?: boolean;\n 'data-testid'?: string;\n /** Video-only controller. It is assigned while the video element is mounted. */\n controllerRef?: import('react').Ref<MediaPreviewController>;\n /** PDF-only page and geometry controller. */\n pdfControllerRef?: import('react').Ref<PdfPreviewController>;\n /** Reports browser playback state changes for video previews. */\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nconst FRAME = 'max-w-full rounded border border-doom-border bg-doom-deep';\n\ninterface VideoPreviewProps {\n src: string;\n className?: string;\n testId?: string;\n videoControls?: boolean;\n controllerRef?: import('react').Ref<MediaPreviewController>;\n onPlaybackStateChange?: (state: MediaPlaybackState) => void;\n}\n\nexport function mediaPlaybackState(video: HTMLVideoElement): MediaPlaybackState {\n return {\n playing: !video.paused && !video.ended,\n currentTime: video.currentTime,\n duration: Number.isFinite(video.duration) ? video.duration : 0,\n };\n}\n\nexport function mediaPreviewController(\n videoRef: { readonly current: HTMLVideoElement | null },\n frameMetadataRef: { readonly current: MediaFrameMetadata | undefined } = { current: undefined },\n): MediaPreviewController {\n return {\n play: async () => {\n const video = videoRef.current;\n if (video !== null) await video.play();\n },\n pause: () => videoRef.current?.pause(),\n seek: (seconds) => {\n const video = videoRef.current;\n if (video !== null) video.currentTime = boundedMediaTime(seconds, video.duration);\n },\n getState: () => {\n const video = videoRef.current;\n return video === null ? { playing: false, currentTime: 0, duration: 0 } : mediaPlaybackState(video);\n },\n isFrameReady: () => {\n const video = videoRef.current;\n return (\n video !== null &&\n !video.seeking &&\n video.readyState >= 2 &&\n (video.requestVideoFrameCallback === undefined || frameMetadataRef.current !== undefined)\n );\n },\n getIntrinsicSize: () => {\n const video = videoRef.current;\n return video === null || video.videoWidth <= 0 || video.videoHeight <= 0\n ? null\n : { width: video.videoWidth, height: video.videoHeight };\n },\n captureFrame: async (type = 'image/png', quality) => {\n const video = videoRef.current;\n if (video === null || video.videoWidth <= 0 || video.videoHeight <= 0 || video.seeking) return null;\n const metadata = frameMetadataRef.current === undefined ? undefined : { ...frameMetadataRef.current };\n const timeSeconds = metadata?.mediaTime ?? video.currentTime;\n const canvas = document.createElement('canvas');\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n const context = canvas.getContext('2d');\n if (context === null) return null;\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, type, quality));\n if (blob === null) return null;\n return {\n blob,\n width: canvas.width,\n height: canvas.height,\n timeSeconds,\n ...(metadata === undefined ? {} : { metadata }),\n };\n },\n };\n}\n\nfunction VideoPreview({\n src,\n className,\n testId,\n controllerRef,\n onPlaybackStateChange,\n videoControls = true,\n}: VideoPreviewProps) {\n const videoRef = useRef<HTMLVideoElement>(null);\n const frameMetadataRef = useRef<MediaFrameMetadata | undefined>(undefined);\n const reportState = () => {\n const video = videoRef.current;\n if (video !== null) onPlaybackStateChange?.(mediaPlaybackState(video));\n };\n const reportStateRef = useRef(reportState);\n useEffect(() => {\n reportStateRef.current = reportState;\n });\n useImperativeHandle(controllerRef, () => mediaPreviewController(videoRef, frameMetadataRef), []);\n useEffect(() => {\n const video = videoRef.current;\n if (video === null || video.requestVideoFrameCallback === undefined) return;\n let request = 0;\n const report: VideoFrameRequestCallback = (_now, metadata) => {\n frameMetadataRef.current = {\n mediaTime: metadata.mediaTime,\n ...(Number.isFinite(metadata.presentedFrames) ? { presentedFrames: metadata.presentedFrames } : {}),\n };\n reportStateRef.current();\n request = video.requestVideoFrameCallback(report);\n };\n request = video.requestVideoFrameCallback(report);\n return () => video.cancelVideoFrameCallback(request);\n }, []);\n return (\n <video\n ref={videoRef}\n src={src}\n controls={videoControls}\n playsInline\n preload=\"metadata\"\n data-testid={testId}\n data-kind=\"video\"\n className={cn(FRAME, 'max-h-[36rem]', className)}\n onPlay={reportState}\n onPause={reportState}\n onTimeUpdate={reportState}\n onDurationChange={reportState}\n onEnded={reportState}\n onLoadedMetadata={reportState}\n onLoadedData={reportState}\n onSeeking={() => {\n frameMetadataRef.current = undefined;\n reportState();\n }}\n onSeeked={reportState}\n >\n <track kind=\"captions\" />\n </video>\n );\n}\n\nexport function MediaPreview({\n src,\n path,\n kind,\n className,\n controllerRef,\n pdfControllerRef,\n onPlaybackStateChange,\n videoControls,\n 'data-testid': testId,\n}: MediaPreviewProps) {\n const resolved = kind ?? mediaKindOf(path);\n const name = path.split('/').at(-1) ?? path;\n\n if (resolved === 'image') {\n return (\n <a href={src} target=\"_blank\" rel=\"noreferrer\" data-testid={testId} data-kind={resolved}>\n <img src={src} alt={path} className={cn(FRAME, 'max-h-[36rem]', className)} />\n </a>\n );\n }\n if (resolved === 'video') {\n return (\n <VideoPreview\n src={src}\n className={className}\n controllerRef={controllerRef}\n onPlaybackStateChange={onPlaybackStateChange}\n testId={testId}\n videoControls={videoControls}\n />\n );\n }\n if (resolved === 'pdf') {\n return (\n <PdfPreview src={src} path={path} controllerRef={pdfControllerRef} data-testid={testId} className={className} />\n );\n }\n return (\n <Badge\n asChild\n size=\"md\"\n data-testid={testId}\n data-kind={resolved}\n className={cn('self-start bg-doom-panel text-doom-text hover:border-doom-blue/50', className)}\n >\n <a href={src} download={name}>\n <FileIcon className=\"h-3 w-3 shrink-0 text-doom-faint\" />\n download {name}\n </a>\n </Badge>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./SyntaxText.cjs"),t=require("../lib/mermaidDiagram.cjs");let n=require("react/jsx-runtime"),r=require("react");function i({code:i}){let a=`mermaid-${(0,r.useId)().replaceAll(/[^a-zA-Z0-9]/g,``)}`,[o,s]=(0,r.useState)(void 0);return(0,r.useEffect)(()=>{let e=!0;return t.renderMermaid(a,i).then(t=>{e&&s({source:i,svg:t})},()=>void 0),()=>{e=!1}},[i,a]),o?.source===i
|
|
1
|
+
const e=require("./SyntaxText.cjs"),t=require("../lib/mermaidDiagram.cjs");let n=require("react/jsx-runtime"),r=require("react");function i({code:i}){let a=`mermaid-${(0,r.useId)().replaceAll(/[^a-zA-Z0-9]/g,``)}`,[o,s]=(0,r.useState)(void 0);return(0,r.useEffect)(()=>{let e=!0;return t.renderMermaid(a,i).then(t=>{e&&s({source:i,svg:t})},()=>void 0),()=>{e=!1}},[i,a]),o?.source===i?/* @__PURE__ */ (0,n.jsx)(`div`,{"data-testid":`mermaid-diagram`,dangerouslySetInnerHTML:{__html:o.svg},className:`flex justify-center overflow-x-auto [&_svg]:h-auto [&_svg]:max-w-full`}):/* @__PURE__ */ (0,n.jsx)(e.SyntaxText,{text:i,className:`text-doom-text`})}exports.MermaidDiagram=i;
|
|
2
2
|
//# sourceMappingURL=MermaidDiagram.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MermaidDiagram.cjs","names":["useId","useState","renderMermaid","SyntaxText"],"sources":["../../../src/components/MermaidDiagram.tsx"],"sourcesContent":["import { useEffect, useId, useState } from 'react';\n\nimport { renderMermaid } from '../lib/mermaidDiagram';\nimport { SyntaxText } from './SyntaxText';\n\n/**\n * A mermaid fence, drawn.\n *\n * DESIGN PATTERNS:\n * - The source is the fallback, never a spinner or an error box. A diagram\n * that has not loaded yet, a diagram that fails to parse and a diagram in a\n * half-streamed message all show the text the author wrote, which stays\n * useful on its own.\n * - The rendered markup is stored with the source it came from, so a message\n * that grows never paints the previous frame's picture over new text.\n *\n * AVOID:\n * - Rendering at module scope or during render: mermaid measures text in the\n * document, so it only runs in an effect, in a browser.\n */\n\ninterface Drawn {\n source: string;\n svg: string;\n}\n\nexport function MermaidDiagram({ code }: { code: string }) {\n // Mermaid puts the id in the DOM and in the SVG's own ids, so React's\n // colon-wrapped value has to lose its punctuation first.\n const id = `mermaid-${useId().replaceAll(/[^a-zA-Z0-9]/g, '')}`;\n const [drawn, setDrawn] = useState<Drawn | undefined>(undefined);\n\n useEffect(() => {\n let live = true;\n void renderMermaid(id, code).then(\n (svg) => {\n if (live) setDrawn({ source: code, svg });\n },\n // Invalid syntax and a chunk that will not load are the same outcome\n // here: the block keeps showing its source.\n () => undefined,\n );\n return () => {\n live = false;\n };\n }, [code, id]);\n\n if (drawn?.source !== code) return <SyntaxText text={code} className=\"text-doom-text\" />;\n return (\n <div\n data-testid=\"mermaid-diagram\"\n // Mermaid sanitises its own output under the strict security level set\n // in lib/mermaidDiagram.ts; this is the only way it hands back a figure.\n dangerouslySetInnerHTML={{ __html: drawn.svg }}\n className=\"flex justify-center overflow-x-auto [&_svg]:h-auto [&_svg]:max-w-full\"\n />\n );\n}\n"],"mappings":"iIA0BA,SAAgB,EAAe,CAAE,QAA0B,CAGzD,IAAM,EAAK,YAAA,EAAWA,EAAAA,MAAAA,CAAM,CAAC,CAAC,WAAW,gBAAiB,EAAE,IACtD,CAAC,EAAO,IAAA,EAAYC,EAAAA,SAAAA,CAA4B,IAAA,EAAS,EAkB/D,OAhBA,EAAA,EAAA,UAAA,KAAgB,CACd,IAAI,EAAO,GASX,OARA,EAAKC,cAAc,EAAI,CAAI,CAAC,CAAC,KAC1B,GAAQ,CACH,GAAM,EAAS,CAAE,OAAQ,EAAM,KAAI,CAAC,CAC1C,MAGM,IAAA,EACR,MACa,CACX,EAAO,EACT,CACF,EAAG,CAAC,EAAM,CAAE,CAAC,EAET,GAAO,SAAW,
|
|
1
|
+
{"version":3,"file":"MermaidDiagram.cjs","names":["useId","useState","renderMermaid","SyntaxText"],"sources":["../../../src/components/MermaidDiagram.tsx"],"sourcesContent":["import { useEffect, useId, useState } from 'react';\n\nimport { renderMermaid } from '../lib/mermaidDiagram';\nimport { SyntaxText } from './SyntaxText';\n\n/**\n * A mermaid fence, drawn.\n *\n * DESIGN PATTERNS:\n * - The source is the fallback, never a spinner or an error box. A diagram\n * that has not loaded yet, a diagram that fails to parse and a diagram in a\n * half-streamed message all show the text the author wrote, which stays\n * useful on its own.\n * - The rendered markup is stored with the source it came from, so a message\n * that grows never paints the previous frame's picture over new text.\n *\n * AVOID:\n * - Rendering at module scope or during render: mermaid measures text in the\n * document, so it only runs in an effect, in a browser.\n */\n\ninterface Drawn {\n source: string;\n svg: string;\n}\n\nexport function MermaidDiagram({ code }: { code: string }) {\n // Mermaid puts the id in the DOM and in the SVG's own ids, so React's\n // colon-wrapped value has to lose its punctuation first.\n const id = `mermaid-${useId().replaceAll(/[^a-zA-Z0-9]/g, '')}`;\n const [drawn, setDrawn] = useState<Drawn | undefined>(undefined);\n\n useEffect(() => {\n let live = true;\n void renderMermaid(id, code).then(\n (svg) => {\n if (live) setDrawn({ source: code, svg });\n },\n // Invalid syntax and a chunk that will not load are the same outcome\n // here: the block keeps showing its source.\n () => undefined,\n );\n return () => {\n live = false;\n };\n }, [code, id]);\n\n if (drawn?.source !== code) return <SyntaxText text={code} className=\"text-doom-text\" />;\n return (\n <div\n data-testid=\"mermaid-diagram\"\n // Mermaid sanitises its own output under the strict security level set\n // in lib/mermaidDiagram.ts; this is the only way it hands back a figure.\n dangerouslySetInnerHTML={{ __html: drawn.svg }}\n className=\"flex justify-center overflow-x-auto [&_svg]:h-auto [&_svg]:max-w-full\"\n />\n );\n}\n"],"mappings":"iIA0BA,SAAgB,EAAe,CAAE,QAA0B,CAGzD,IAAM,EAAK,YAAA,EAAWA,EAAAA,MAAAA,CAAM,CAAC,CAAC,WAAW,gBAAiB,EAAE,IACtD,CAAC,EAAO,IAAA,EAAYC,EAAAA,SAAAA,CAA4B,IAAA,EAAS,EAkB/D,OAhBA,EAAA,EAAA,UAAA,KAAgB,CACd,IAAI,EAAO,GASX,OARA,EAAKC,cAAc,EAAI,CAAI,CAAC,CAAC,KAC1B,GAAQ,CACH,GAAM,EAAS,CAAE,OAAQ,EAAM,KAAI,CAAC,CAC1C,MAGM,IAAA,EACR,MACa,CACX,EAAO,EACT,CACF,EAAG,CAAC,EAAM,CAAE,CAAC,EAET,GAAO,SAAW,EAEpB,iBAAA,EAAA,EAAA,IAAA,CAAC,MAAD,CACE,cAAY,kBAGZ,wBAAyB,CAAE,OAAQ,EAAM,GAAI,EAC7C,UAAU,uEACX,CAAA,EARgC,iBAAA,EAAA,EAAA,IAAA,CAACC,EAAAA,WAAD,CAAY,KAAM,EAAM,UAAU,gBAAkB,CAAA,CAUzF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{SyntaxText as e}from"./SyntaxText.mjs";import{renderMermaid as t}from"../lib/mermaidDiagram.mjs";import{jsx as n}from"react/jsx-runtime";import{useEffect as r,useId as i,useState as a}from"react";function o({code:o}){let s=`mermaid-${i().replaceAll(/[^a-zA-Z0-9]/g,``)}`,[c,l]=a(void 0);return r(()=>{let e=!0;return t(s,o).then(t=>{e&&l({source:o,svg:t})},()=>void 0),()=>{e=!1}},[o,s]),c?.source===o
|
|
1
|
+
import{SyntaxText as e}from"./SyntaxText.mjs";import{renderMermaid as t}from"../lib/mermaidDiagram.mjs";import{jsx as n}from"react/jsx-runtime";import{useEffect as r,useId as i,useState as a}from"react";function o({code:o}){let s=`mermaid-${i().replaceAll(/[^a-zA-Z0-9]/g,``)}`,[c,l]=a(void 0);return r(()=>{let e=!0;return t(s,o).then(t=>{e&&l({source:o,svg:t})},()=>void 0),()=>{e=!1}},[o,s]),c?.source===o?/* @__PURE__ */ n(`div`,{"data-testid":`mermaid-diagram`,dangerouslySetInnerHTML:{__html:c.svg},className:`flex justify-center overflow-x-auto [&_svg]:h-auto [&_svg]:max-w-full`}):/* @__PURE__ */ n(e,{text:o,className:`text-doom-text`})}export{o as MermaidDiagram};
|
|
2
2
|
//# sourceMappingURL=MermaidDiagram.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MermaidDiagram.mjs","names":[],"sources":["../../../src/components/MermaidDiagram.tsx"],"sourcesContent":["import { useEffect, useId, useState } from 'react';\n\nimport { renderMermaid } from '../lib/mermaidDiagram';\nimport { SyntaxText } from './SyntaxText';\n\n/**\n * A mermaid fence, drawn.\n *\n * DESIGN PATTERNS:\n * - The source is the fallback, never a spinner or an error box. A diagram\n * that has not loaded yet, a diagram that fails to parse and a diagram in a\n * half-streamed message all show the text the author wrote, which stays\n * useful on its own.\n * - The rendered markup is stored with the source it came from, so a message\n * that grows never paints the previous frame's picture over new text.\n *\n * AVOID:\n * - Rendering at module scope or during render: mermaid measures text in the\n * document, so it only runs in an effect, in a browser.\n */\n\ninterface Drawn {\n source: string;\n svg: string;\n}\n\nexport function MermaidDiagram({ code }: { code: string }) {\n // Mermaid puts the id in the DOM and in the SVG's own ids, so React's\n // colon-wrapped value has to lose its punctuation first.\n const id = `mermaid-${useId().replaceAll(/[^a-zA-Z0-9]/g, '')}`;\n const [drawn, setDrawn] = useState<Drawn | undefined>(undefined);\n\n useEffect(() => {\n let live = true;\n void renderMermaid(id, code).then(\n (svg) => {\n if (live) setDrawn({ source: code, svg });\n },\n // Invalid syntax and a chunk that will not load are the same outcome\n // here: the block keeps showing its source.\n () => undefined,\n );\n return () => {\n live = false;\n };\n }, [code, id]);\n\n if (drawn?.source !== code) return <SyntaxText text={code} className=\"text-doom-text\" />;\n return (\n <div\n data-testid=\"mermaid-diagram\"\n // Mermaid sanitises its own output under the strict security level set\n // in lib/mermaidDiagram.ts; this is the only way it hands back a figure.\n dangerouslySetInnerHTML={{ __html: drawn.svg }}\n className=\"flex justify-center overflow-x-auto [&_svg]:h-auto [&_svg]:max-w-full\"\n />\n );\n}\n"],"mappings":"2MA0BA,SAAgB,EAAe,CAAE,QAA0B,CAGzD,IAAM,EAAK,WAAW,EAAM,CAAC,CAAC,WAAW,gBAAiB,EAAE,IACtD,CAAC,EAAO,GAAY,EAA4B,IAAA,EAAS,EAkB/D,OAhBA,MAAgB,CACd,IAAI,EAAO,GASX,OARA,EAAmB,EAAI,CAAI,CAAC,CAAC,KAC1B,GAAQ,CACH,GAAM,EAAS,CAAE,OAAQ,EAAM,KAAI,CAAC,CAC1C,MAGM,IAAA,EACR,MACa,CACX,EAAO,EACT,CACF,EAAG,CAAC,EAAM,CAAE,CAAC,EAET,GAAO,SAAW,EAEpB,
|
|
1
|
+
{"version":3,"file":"MermaidDiagram.mjs","names":[],"sources":["../../../src/components/MermaidDiagram.tsx"],"sourcesContent":["import { useEffect, useId, useState } from 'react';\n\nimport { renderMermaid } from '../lib/mermaidDiagram';\nimport { SyntaxText } from './SyntaxText';\n\n/**\n * A mermaid fence, drawn.\n *\n * DESIGN PATTERNS:\n * - The source is the fallback, never a spinner or an error box. A diagram\n * that has not loaded yet, a diagram that fails to parse and a diagram in a\n * half-streamed message all show the text the author wrote, which stays\n * useful on its own.\n * - The rendered markup is stored with the source it came from, so a message\n * that grows never paints the previous frame's picture over new text.\n *\n * AVOID:\n * - Rendering at module scope or during render: mermaid measures text in the\n * document, so it only runs in an effect, in a browser.\n */\n\ninterface Drawn {\n source: string;\n svg: string;\n}\n\nexport function MermaidDiagram({ code }: { code: string }) {\n // Mermaid puts the id in the DOM and in the SVG's own ids, so React's\n // colon-wrapped value has to lose its punctuation first.\n const id = `mermaid-${useId().replaceAll(/[^a-zA-Z0-9]/g, '')}`;\n const [drawn, setDrawn] = useState<Drawn | undefined>(undefined);\n\n useEffect(() => {\n let live = true;\n void renderMermaid(id, code).then(\n (svg) => {\n if (live) setDrawn({ source: code, svg });\n },\n // Invalid syntax and a chunk that will not load are the same outcome\n // here: the block keeps showing its source.\n () => undefined,\n );\n return () => {\n live = false;\n };\n }, [code, id]);\n\n if (drawn?.source !== code) return <SyntaxText text={code} className=\"text-doom-text\" />;\n return (\n <div\n data-testid=\"mermaid-diagram\"\n // Mermaid sanitises its own output under the strict security level set\n // in lib/mermaidDiagram.ts; this is the only way it hands back a figure.\n dangerouslySetInnerHTML={{ __html: drawn.svg }}\n className=\"flex justify-center overflow-x-auto [&_svg]:h-auto [&_svg]:max-w-full\"\n />\n );\n}\n"],"mappings":"2MA0BA,SAAgB,EAAe,CAAE,QAA0B,CAGzD,IAAM,EAAK,WAAW,EAAM,CAAC,CAAC,WAAW,gBAAiB,EAAE,IACtD,CAAC,EAAO,GAAY,EAA4B,IAAA,EAAS,EAkB/D,OAhBA,MAAgB,CACd,IAAI,EAAO,GASX,OARA,EAAmB,EAAI,CAAI,CAAC,CAAC,KAC1B,GAAQ,CACH,GAAM,EAAS,CAAE,OAAQ,EAAM,KAAI,CAAC,CAC1C,MAGM,IAAA,EACR,MACa,CACX,EAAO,EACT,CACF,EAAG,CAAC,EAAM,CAAE,CAAC,EAET,GAAO,SAAW,EAEpB,kBAAC,MAAD,CACE,cAAY,kBAGZ,wBAAyB,CAAE,OAAQ,EAAM,GAAI,EAC7C,UAAU,uEACX,CAAA,EARgC,kBAAC,EAAD,CAAY,KAAM,EAAM,UAAU,gBAAkB,CAAA,CAUzF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
require("../icons/icons.cjs");const e=require("../lib/cn.cjs"),t=require("./Button.cjs"),n=require("./StatusBadge.cjs");let r=require("react/jsx-runtime"),i=require("react"),a=require("class-variance-authority"),o=require("lucide-react");const s=(0,i.createContext)({tone:`neutral`,expandable:!1,expanded:!1,toggle:()=>void 0});function c(){return(0,i.useContext)(s)}const l=(0,i.createContext)(null),u=(0,a.cva)(`overflow-hidden rounded-md border bg-doom-panel transition-colors`,{variants:{tone:n.STATUS_EDGE},defaultVariants:{tone:`neutral`}}),d=(0,a.cva)(`overflow-hidden border-l-2 transition-colors`,{variants:{tone:n.STATUS_EDGE},defaultVariants:{tone:`neutral`}});function f({className:t,tone:n,expandable:a=!1,defaultExpanded:o=!1,expanded:c,onExpandedChange:f,children:p,...m}){let[h,g]=(0,i.useState)(o),_=(0,i.useContext)(l)!==null,v=c??h,y={tone:n??`neutral`,expandable:a,expanded:v,toggle:()=>{let e=!v;c===void 0&&g(e),f?.(e)}};return(0,r.jsx)(s.Provider,{value:y,children
|
|
1
|
+
require("../icons/icons.cjs");const e=require("../lib/cn.cjs"),t=require("./Button.cjs"),n=require("./StatusBadge.cjs");let r=require("react/jsx-runtime"),i=require("react"),a=require("class-variance-authority"),o=require("lucide-react");const s=(0,i.createContext)({tone:`neutral`,expandable:!1,expanded:!1,toggle:()=>void 0});function c(){return(0,i.useContext)(s)}const l=(0,i.createContext)(null),u=(0,a.cva)(`overflow-hidden rounded-md border bg-doom-panel transition-colors`,{variants:{tone:n.STATUS_EDGE},defaultVariants:{tone:`neutral`}}),d=(0,a.cva)(`overflow-hidden border-l-2 transition-colors`,{variants:{tone:n.STATUS_EDGE},defaultVariants:{tone:`neutral`}});function f({className:t,tone:n,expandable:a=!1,defaultExpanded:o=!1,expanded:c,onExpandedChange:f,children:p,...m}){let[h,g]=(0,i.useState)(o),_=(0,i.useContext)(l)!==null,v=c??h,y={tone:n??`neutral`,expandable:a,expanded:v,toggle:()=>{let e=!v;c===void 0&&g(e),f?.(e)}};return/* @__PURE__ */ (0,r.jsx)(s.Provider,{value:y,children:/* @__PURE__ */ (0,r.jsx)(`div`,{"data-slot":`message-item`,"data-expanded":v,"data-grouped":_,className:e.cn(_?d({tone:n}):u({tone:n}),t),...m,children:typeof p==`function`?p(y):p})})}function p({className:t,tone:i,title:a,summary:o,badge:s,children:c,...d}){let f=s===void 0?h[i??`neutral`]:s;return/* @__PURE__ */ (0,r.jsx)(l.Provider,{value:{tone:i??`neutral`},children:/* @__PURE__ */ (0,r.jsxs)(`div`,{"data-slot":`message-item-group`,className:e.cn(u({tone:i}),t),...d,children:[/* @__PURE__ */ (0,r.jsxs)(`div`,{className:`flex min-h-8 items-center gap-2 border-doom-border-soft border-b px-[11px] text-sm text-doom-dim`,children:[/* @__PURE__ */ (0,r.jsx)(`span`,{className:`shrink-0 font-bold text-doom-hi`,children:a}),/* @__PURE__ */ (0,r.jsx)(`span`,{className:`min-w-0 flex-1 truncate text-doom-faint`,children:o}),f!==null&&f!==``?/* @__PURE__ */ (0,r.jsx)(n.StatusBadge,{tone:i??`neutral`,children:f}):null]}),/* @__PURE__ */ (0,r.jsx)(`div`,{className:`flex flex-col divide-y divide-doom-border-soft`,children:c})]})})}function m(e){return e.running?`running`:e.isError?`error`:`ok`}const h={neutral:``,running:`RUNNING`,ok:`OK`,error:`ERROR`,info:`INFO`,accent:`NOTE`},g=/* @__PURE__ */ new Set([`ok`,`neutral`]);function _({className:a,title:s,badge:u,children:d,...f}){let{tone:p,expandable:m,expanded:_,toggle:v}=c(),y=(0,i.useContext)(l),b=y!==null&&g.has(p)?null:h[p],x=u===void 0?b:u;return/* @__PURE__ */ (0,r.jsxs)(`div`,{"data-slot":`message-item-header`,className:e.cn(`flex min-h-8 items-center gap-2 px-[11px] text-sm text-doom-dim`,a),...f,children:[y===null?/* @__PURE__ */ (0,r.jsx)(`span`,{className:`shrink-0 font-bold text-doom-hi`,children:s}):null,/* @__PURE__ */ (0,r.jsx)(`div`,{"data-slot":`message-item-summary`,className:`flex min-w-0 flex-1 items-center gap-2`,children:d}),x!==null&&x!==``?/* @__PURE__ */ (0,r.jsx)(n.StatusBadge,{tone:p,"data-testid":`tool-status`,children:x}):null,m?/* @__PURE__ */ (0,r.jsx)(t.Button,{variant:`ghost`,size:`icon`,"data-testid":`tool-expand`,"aria-expanded":_,"aria-label":_?`collapse`:`expand`,onClick:v,className:`text-doom-faint hover:text-doom-blue`,children:_?/* @__PURE__ */ (0,r.jsx)(o.ChevronUp,{className:`h-3 w-3`}):/* @__PURE__ */ (0,r.jsx)(o.ChevronDown,{className:`h-3 w-3`})}):null]})}function v({className:t,...n}){return/* @__PURE__ */ (0,r.jsx)(`div`,{"data-slot":`message-item-body`,className:e.cn(`border-t border-doom-border-soft bg-doom-deep px-3 py-2 text-sm text-doom-dim`,t),...n})}const y={neutral:`…`,running:`◐`,ok:`✓`,error:`✗`,info:`●`,accent:`●`},b=(0,a.cva)(`shrink-0`,{variants:{tone:{neutral:`text-doom-faint`,running:`text-doom-yellow`,ok:`text-doom-green`,error:`text-doom-red`,info:`text-doom-blue`,accent:`text-doom-magenta`}},defaultVariants:{tone:`neutral`}});function x({className:t,tone:n,glyph:i,expands:a=!1,children:o,...s}){let{toggle:l}=c(),u=/* @__PURE__ */ (0,r.jsxs)(r.Fragment,{children:[/* @__PURE__ */ (0,r.jsx)(`span`,{className:b({tone:n}),children:i??y[n??`neutral`]}),/* @__PURE__ */ (0,r.jsx)(`span`,{children:o})]});return a?/* @__PURE__ */ (0,r.jsx)(`button`,{type:`button`,"data-slot":`message-item-status`,"data-testid":`tool-more`,onClick:l,className:e.cn(`flex cursor-pointer items-center gap-1.5 text-doom-faint hover:text-doom-blue`,t),children:u}):/* @__PURE__ */ (0,r.jsx)(`span`,{"data-slot":`message-item-status`,className:e.cn(`flex items-center gap-1.5 text-doom-faint`,t),...s,children:u})}exports.MessageItem=f,exports.MessageItemBody=v,exports.MessageItemGroup=p,exports.MessageItemHeader=_,exports.MessageItemStatus=x,exports.STATUS_GLYPH=y,exports.STATUS_LABEL=h,exports.messageItemRowVariants=d,exports.messageItemStatusVariants=b,exports.messageItemVariants=u,exports.toolTone=m,exports.useMessageItem=c;
|
|
2
2
|
//# sourceMappingURL=MessageItem.cjs.map
|