gitdataai/src/components/shared/CodeReference.tsx
ZhenYi 7620f2f281 feat(command): use real API data for navigation, fix notification button
CommandPalette: replace workspaceProjects with getCurrentUserProjects
(no workspace dependency so it works outside WorkspaceProvider).
Repos fetched per-project to preserve correct /repository/ns/repo
routes. Keyboard shortcut correctly matches Ctrl+Alt+F / Cmd+Ctrl+F.

sidebar-user: fix notification button layout — bell icon and label
now on the same row instead of separate stacked elements.
2026-04-25 09:53:12 +08:00

155 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
/**
* Renders a compact code reference block (file path + line range).
* Clicking navigates to the file in the repository browser.
*/
import { useState } from 'react';
import { FileCode2, ChevronDown, ChevronRight } from 'lucide-react';
import { cn } from '@/lib/utils';
import type { CodeRef } from '@/lib/code-ref-parser';
interface CodeReferenceProps {
ref: CodeRef;
/** API to fetch line content (optional — shows skeleton if not provided) */
getLineContent?: (filePath: string, startLine: number, endLine: number) => Promise<string[]>;
/** Called when the reference is clicked — navigate to file browser */
onClick?: (ref: CodeRef) => void;
/** Base URL for the repository file browser (e.g. /repository/ns/repo) */
repoBaseUrl?: string;
/** Branch name to use in the link */
branch?: string;
className?: string;
}
export function CodeReference({
ref,
getLineContent,
onClick,
repoBaseUrl,
branch = 'main',
className,
}: CodeReferenceProps) {
const [expanded, setExpanded] = useState(false);
const [lines, setLines] = useState<string[] | null>(null);
const [loading, setLoading] = useState(false);
const handleClick = () => {
if (onClick) {
onClick(ref);
return;
}
if (repoBaseUrl) {
// Navigate to file browser with line highlighted
window.location.href = `${repoBaseUrl}/blob/${branch}/${ref.filePath}#L${ref.startLine}`;
}
};
const loadLines = async () => {
if (!getLineContent || lines !== null) return;
setLoading(true);
try {
const content = await getLineContent(ref.filePath, ref.startLine, ref.endLine);
setLines(content);
} catch {
setLines([]);
} finally {
setLoading(false);
}
};
const toggleExpand = () => {
setExpanded((prev) => {
if (!prev) loadLines();
return !prev;
});
};
const lineLabel = ref.endLine === ref.startLine
? `L${ref.startLine}`
: `L${ref.startLine}L${ref.endLine}`;
return (
<div
className={cn(
'rounded-md border bg-muted/50 my-1.5 overflow-hidden',
className,
)}
>
{/* Header bar */}
<button
type="button"
className="w-full flex items-center gap-2 px-3 py-1.5 hover:bg-muted transition-colors text-left"
onClick={handleClick}
title={ref.filePath ? `View ${ref.filePath}` : `Jump to ${lineLabel}`}
>
<FileCode2 className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground" />
{ref.filePath && (
<span className="text-xs font-mono text-muted-foreground truncate">
{ref.filePath}
</span>
)}
<span className="text-xs font-mono bg-muted px-1.5 py-0.5 rounded text-muted-foreground">
{lineLabel}
</span>
</button>
{/* Code preview (optional) */}
{getLineContent && (
<>
<button
type="button"
className="w-full flex items-center gap-1 px-3 py-0.5 hover:bg-muted/70 transition-colors text-left"
onClick={toggleExpand}
>
{expanded ? (
<ChevronDown className="h-3 w-3 text-muted-foreground" />
) : (
<ChevronRight className="h-3 w-3 text-muted-foreground" />
)}
<span className="text-xs text-muted-foreground">
{expanded ? 'Hide preview' : 'Show preview'}
</span>
</button>
{expanded && (
<div className="border-t">
{loading ? (
<div className="px-3 py-2 space-y-1">
{Array.from({ length: Math.min(ref.endLine - ref.startLine + 1, 5) }).map((_, i) => (
<div key={i} className="flex gap-2">
<div className="h-3 w-6 bg-muted rounded animate-pulse" />
<div className="h-3 flex-1 bg-muted rounded animate-pulse" />
</div>
))}
</div>
) : lines && lines.length > 0 ? (
<div className="py-1">
{lines.map((line, i) => {
const lineNum = ref.startLine + i;
return (
<div key={i} className="flex gap-0 px-1 hover:bg-muted/50">
<span className="select-none w-10 text-right pr-2 text-xs text-muted-foreground/50 font-mono shrink-0">
{lineNum}
</span>
<span className="text-xs font-mono leading-5 text-foreground/90 whitespace-pre">
{line || ' '}
</span>
</div>
);
})}
</div>
) : (
<div className="px-3 py-2 text-xs text-muted-foreground italic">
No content available
</div>
)}
</div>
)}
</>
)}
</div>
);
}