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