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 class={`commit-card-subject${meta.subject ? "" : " commit-card-subject-empty"}`}>
53 {meta.subject || "No commit message"}
54 </h2>
55 {meta.body && (
56 <pre class="commit-card-body">{meta.body}</pre>
57 )}
58 <div class="commit-card-meta">
59 <div class="commit-card-meta-row">
60 <span class="commit-meta-label">Author</span>
61 <span class="commit-meta-value">
62 {escapeHtml(`${meta.author} <${meta.email}>`)}
63 </span>
64 </div>
65 <div class="commit-card-meta-row">
66 <span class="commit-meta-label">Date</span>
67 <time
68 class="commit-meta-value"
69 datetime={meta.date}
70 >
71 {formatDateTime(meta.date)}
72 </time>
73 </div>
74 {(meta.committer !== meta.author ||
75 meta.committerEmail !== meta.email) && (
76 <>
77 <div class="commit-card-meta-row">
78 <span class="commit-meta-label">
79 Committer
80 </span>
81 <span class="commit-meta-value">
82 {escapeHtml(
83 `${meta.committer} <${meta.committerEmail}>`,
84 )}
85 </span>
86 </div>
87 <div class="commit-card-meta-row">
88 <span class="commit-meta-label">
89 Commit date
90 </span>
91 <time
92 class="commit-meta-value"
93 datetime={meta.committerDate}
94 >
95 {formatDateTime(meta.committerDate)}
96 </time>
97 </div>
98 </>
99 )}
100 <div class="commit-card-meta-row">
101 <span class="commit-meta-label">Commit</span>
102 <code class="commit-meta-value commit-sha-full mono">
103 {meta.hash}
104 </code>
105 </div>
106 {meta.parents.length > 0 && (
107 <div class="commit-card-meta-row">
108 <span class="commit-meta-label">
109 Parent{meta.parents.length > 1 ? "s" : ""}
110 </span>
111 <span class="commit-meta-value">
112 {meta.parents.map((p) => (
113 <a
114 href={`/${repo.name}/commit/${p}`}
115 class="commit-hash mono"
116 >
117 {p.slice(0, 7)}
118 </a>
119 ))}
120 </span>
121 </div>
122 )}
123 {meta.sigStatus !== "none" && (
124 <div class="commit-card-meta-row">
125 <span class="commit-meta-label">Signature</span>
126 <span class="commit-meta-value">
127 {meta.sigStatus === "good" ? (
128 <span class="sig-badge verified">
129 verified
130 </span>
131 ) : (
132 <span class="sig-badge unverified">
133 unverified
134 </span>
135 )}
136 </span>
137 </div>
138 )}
139 </div>
140 </div>
141
142 <DiffView files={files} repo={repo} sha={sha} />
143 </div>
144 </Layout>
145 );
146}
147