CommitDetail.tsx
⎇
Raw
1import { escapeHtml } from "@kitajs/html";
2import type { RepositoryRow } from "../../db/index.ts";
3import type { SessionUser } from "../../middleware/session.ts";
4import type { RenderedDiffFile } from "../../services/diffHighlight.ts";
5import type { CommitMeta } from "../../services/git.ts";
6import { DiffView } from "../DiffView.tsx";
7import { Layout } from "../layout.tsx";
8import { RepoHeader } from "../repos/RepoHeader.tsx";
9import { RepoNav } from "./RepoNav.tsx";
10
11interface CommitDetailProps {
12 user: SessionUser | null;
13 repo: RepositoryRow;
14 sha: string;
15 meta: CommitMeta;
16 files: RenderedDiffFile[];
17}
18
19function formatDate(iso: string): string {
20 try {
21 return new Date(iso).toLocaleString(undefined, {
22 dateStyle: "medium",
23 timeStyle: "short",
24 });
25 } catch {
26 return iso;
27 }
28}
29
30export function CommitDetail({
31 user,
32 repo,
33 sha,
34 meta,
35 files,
36}: CommitDetailProps) {
37 return (
38 <Layout user={user} title={`${sha.slice(0, 7)} — ${repo.name}`}>
39 <div class="container">
40 <RepoHeader repo={repo} />
41 <RepoNav repo={repo} active="commits" user={user} />
42
43 {/* Back link */}
44 <div class="commit-page-top">
45 <a
46 href={`/${repo.name}/commits/${repo.default_branch}`}
47 class="btn btn-ghost btn-sm"
48 >
49 ← Back to log
50 </a>
51 <a
52 href={`/${repo.name}/tree/${sha}`}
53 class="btn btn-secondary btn-sm"
54 title={`Browse tree at ${sha.slice(0, 7)}`}
55 >
56 @ {sha.slice(0, 7)}
57 </a>
58 </div>
59
60 {/* Commit metadata card */}
61 <div class="commit-card">
62 <h2 class="commit-card-subject">{meta.subject}</h2>
63 {meta.body && (
64 <pre class="commit-card-body">{meta.body}</pre>
65 )}
66 <div class="commit-card-meta">
67 <div class="commit-card-meta-row">
68 <span class="commit-meta-label">Author</span>
69 <span class="commit-meta-value">
70 {escapeHtml(`${meta.author} <${meta.email}>`)}
71 </span>
72 </div>
73 <div class="commit-card-meta-row">
74 <span class="commit-meta-label">Date</span>
75 <time
76 class="commit-meta-value"
77 datetime={meta.date}
78 >
79 {formatDate(meta.date)}
80 </time>
81 </div>
82 {(meta.committer !== meta.author ||
83 meta.committerEmail !== meta.email) && (
84 <>
85 <div class="commit-card-meta-row">
86 <span class="commit-meta-label">
87 Committer
88 </span>
89 <span class="commit-meta-value">
90 {escapeHtml(
91 `${meta.committer} <${meta.committerEmail}>`,
92 )}
93 </span>
94 </div>
95 <div class="commit-card-meta-row">
96 <span class="commit-meta-label">
97 Commit date
98 </span>
99 <time
100 class="commit-meta-value"
101 datetime={meta.committerDate}
102 >
103 {formatDate(meta.committerDate)}
104 </time>
105 </div>
106 </>
107 )}
108 <div class="commit-card-meta-row">
109 <span class="commit-meta-label">Commit</span>
110 <code class="commit-meta-value commit-sha-full mono">
111 {meta.hash}
112 </code>
113 </div>
114 {meta.parents.length > 0 && (
115 <div class="commit-card-meta-row">
116 <span class="commit-meta-label">
117 Parent{meta.parents.length > 1 ? "s" : ""}
118 </span>
119 <span class="commit-meta-value">
120 {meta.parents.map((p) => (
121 <a
122 href={`/${repo.name}/commit/${p}`}
123 class="commit-hash mono"
124 >
125 {p.slice(0, 7)}
126 </a>
127 ))}
128 </span>
129 </div>
130 )}
131 </div>
132 </div>
133
134 <DiffView files={files} repo={repo} sha={sha} />
135 </div>
136 </Layout>
137 );
138}
139