File
Blob: src/client/pages/BlobPage.tsx
| 1 | import { Breadcrumbs } from "@/client/components/Breadcrumbs"; |
| 2 | import { CodeViewer } from "@/client/components/CodeViewer"; |
| 3 | import { MarkdownContent } from "@/client/components/MarkdownContent"; |
| 4 | import { RepoNav } from "@/client/components/RepoNav"; |
| 5 | import { BlobActionsIsland } from "@/client/islands/blob-actions"; |
| 6 | import { IslandHost } from "@/client/server/IslandHost"; |
| 7 | |
| 8 | type BreadcrumbItem = { |
| 9 | name: string; |
| 10 | href: string | null; |
| 11 | }; |
| 12 | |
| 13 | export type BlobPageProps = { |
| 14 | owner: string; |
| 15 | repo: string; |
| 16 | refEnc: string; |
| 17 | fileName: string; |
| 18 | codeLang?: string | null; |
| 19 | lineCount?: number; |
| 20 | sizeStr?: string; |
| 21 | viewRawHref: string; |
| 22 | rawHref: string; |
| 23 | tooLarge?: boolean; |
| 24 | isImage?: boolean; |
| 25 | isPdf?: boolean; |
| 26 | mediaSrc?: string; |
| 27 | isMarkdown?: boolean; |
| 28 | markdownRaw?: string; |
| 29 | mdOwner?: string; |
| 30 | mdRepo?: string; |
| 31 | mdRef?: string; |
| 32 | mdBase?: string; |
| 33 | isBinary?: boolean; |
| 34 | codeText?: string; |
| 35 | breadcrumbs?: BreadcrumbItem[]; |
| 36 | parentHref?: string | null; |
| 37 | }; |
| 38 | |
| 39 | export function BlobPage(props: BlobPageProps) { |
| 40 | const { |
| 41 | owner, |
| 42 | repo, |
| 43 | refEnc, |
| 44 | fileName, |
| 45 | codeLang, |
| 46 | lineCount, |
| 47 | sizeStr, |
| 48 | viewRawHref, |
| 49 | rawHref, |
| 50 | tooLarge, |
| 51 | isImage, |
| 52 | isPdf, |
| 53 | mediaSrc, |
| 54 | isMarkdown, |
| 55 | markdownRaw, |
| 56 | mdOwner, |
| 57 | mdRepo, |
| 58 | mdRef, |
| 59 | mdBase, |
| 60 | isBinary, |
| 61 | codeText, |
| 62 | breadcrumbs, |
| 63 | parentHref, |
| 64 | } = props; |
| 65 | |
| 66 | const showCopy = !isMarkdown && !isImage && !isPdf && !isBinary && !tooLarge; |
| 67 | |
| 68 | return ( |
| 69 | <div> |
| 70 | <RepoNav owner={owner} repo={repo} refEnc={refEnc} currentTab="browse" /> |
| 71 | <span className="mb-1 inline-block text-xs font-semibold uppercase tracking-wider text-accent-500 dark:text-accent-400"> |
| 72 | File |
| 73 | </span> |
| 74 | <h2 className="font-display tracking-tight">Blob: {fileName}</h2> |
| 75 | <Breadcrumbs items={breadcrumbs} parentHref={parentHref} /> |
| 76 | <div className="blob-container mt-4 rounded-2xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800/60 dark:bg-zinc-900/50"> |
| 77 | <div className="flex items-center justify-between border-b border-zinc-200 px-3 py-2 dark:border-zinc-800/60"> |
| 78 | <div className="flex items-center gap-2 text-xs text-zinc-600 dark:text-zinc-400"> |
| 79 | {codeLang ? ( |
| 80 | <span className="inline-flex items-center rounded-lg border border-zinc-300 bg-zinc-50 px-2 py-0.5 dark:border-zinc-700 dark:bg-zinc-800/50"> |
| 81 | {codeLang} |
| 82 | </span> |
| 83 | ) : null} |
| 84 | {isMarkdown ? ( |
| 85 | <span className="inline-flex items-center rounded-lg border border-zinc-300 bg-zinc-50 px-2 py-0.5 dark:border-zinc-700 dark:bg-zinc-800/50"> |
| 86 | Markdown |
| 87 | </span> |
| 88 | ) : null} |
| 89 | {isMarkdown || codeLang ? ( |
| 90 | lineCount ? ( |
| 91 | <span className="text-zinc-500 dark:text-zinc-400"> |
| 92 | {lineCount} line{lineCount === 1 ? "" : "s"} |
| 93 | </span> |
| 94 | ) : null |
| 95 | ) : sizeStr ? ( |
| 96 | <span className="text-zinc-500 dark:text-zinc-400">{sizeStr}</span> |
| 97 | ) : null} |
| 98 | </div> |
| 99 | <IslandHost |
| 100 | name="blob-actions" |
| 101 | props={{ viewRawHref, rawHref, showCopy, isImage, isPdf }} |
| 102 | className="flex items-center gap-2" |
| 103 | > |
| 104 | <BlobActionsIsland |
| 105 | viewRawHref={viewRawHref} |
| 106 | rawHref={rawHref} |
| 107 | showCopy={showCopy} |
| 108 | isImage={isImage} |
| 109 | isPdf={isPdf} |
| 110 | /> |
| 111 | </IslandHost> |
| 112 | </div> |
| 113 | <div className={`${isMarkdown ? "p-5" : isBinary || tooLarge ? "p-3" : "p-1"}`}> |
| 114 | {tooLarge ? ( |
| 115 | <div className="text-zinc-500 dark:text-zinc-400">File too large to preview.</div> |
| 116 | ) : null} |
| 117 | {!tooLarge && isImage && mediaSrc ? ( |
| 118 | <div className="flex items-center justify-center"> |
| 119 | <img src={mediaSrc} alt={fileName} className="max-w-full rounded-md" loading="lazy" /> |
| 120 | </div> |
| 121 | ) : null} |
| 122 | {!tooLarge && isPdf && mediaSrc ? ( |
| 123 | <div className="w-full" style={{ height: "75vh" }}> |
| 124 | <iframe |
| 125 | src={mediaSrc} |
| 126 | className="h-full w-full rounded-md" |
| 127 | title={fileName} |
| 128 | loading="lazy" |
| 129 | ></iframe> |
| 130 | </div> |
| 131 | ) : null} |
| 132 | {!tooLarge && isMarkdown && markdownRaw && mdOwner && mdRepo && mdRef ? ( |
| 133 | <MarkdownContent |
| 134 | markdown={markdownRaw} |
| 135 | context={{ owner: mdOwner, repo: mdRepo, ref: mdRef, baseDir: mdBase || "" }} |
| 136 | /> |
| 137 | ) : null} |
| 138 | {!tooLarge && isBinary && !isImage && !isPdf ? ( |
| 139 | <div className="text-zinc-500 dark:text-zinc-400">Binary file.</div> |
| 140 | ) : null} |
| 141 | {!tooLarge && !isBinary && !isMarkdown && codeText !== undefined ? ( |
| 142 | <CodeViewer code={codeText} language={codeLang || null} /> |
| 143 | ) : null} |
| 144 | </div> |
| 145 | </div> |
| 146 | </div> |
| 147 | ); |
| 148 | } |