From a458f8738428da71b5b00518bdd5d224698e477f Mon Sep 17 00:00:00 2001
From: tri <tri@thac.loan>
Date: Sun, 28 Sep 2025 20:44:45 +0700
Subject: [PATCH] write repo index page

---
 Makefile             |   2 +-
 src/assets/style.css |  36 +++++++++++-
 src/main.zig         | 129 +++++++++++++++++++++++++++++++++++++++----
 3 files changed, 151 insertions(+), 16 deletions(-)

diff --git a/Makefile b/Makefile
index e3c5cc4..2f8c180 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 watch:
-	zig build run --watch -- demo
+	zig build run --watch -- demo http://localhost:8000
 
 # TODO: write a simple zig server instead
 serve:
diff --git a/src/assets/style.css b/src/assets/style.css
index 7c3d652..a7a18dc 100644
--- a/src/assets/style.css
+++ b/src/assets/style.css
@@ -5,13 +5,41 @@
   --table-odd-row-bg: #eee;
   --table-border-color: #ddd;
   --table-row-hover-bg: papayawhip;
+  --table-border-color: black;
+  --code-bg: gainsboro;
+}
+
+.monospace,
+code {
+  font-family: monospace;
+  font-size: 1rem;
+}
+
+#breadcrumbs {
+  font-family: monospace;
+  font-size: 1.2rem;
+}
+
+h1 {
+  font-size: inherit;
+  font-weight: normal;
+  display: inline-block;
+  margin: 0;
+}
+
+code {
+  background-color: var(--code-bg);
+  padding: 0 5px;
 }
 
 table {
   text-align: left;
   border-collapse: collapse;
-  width: 100%;
+  max-width: 100%;
   overflow: scroll;
+
+  white-space: nowrap;
+  border-top: 2px solid var(--table-border-color);
 }
 /*
 tbody tr:nth-child(odd) {
@@ -26,10 +54,10 @@ tbody tr:hover {
 }
 td,
 th {
-  padding: 0.1rem 5px;
+  padding: 0.1rem 10px;
 }
 table {
-  margin-left: -5px;
+  margin-left: -10px;
   font-variant-numeric: tabular-nums;
 }
 
@@ -47,6 +75,8 @@ img {
     --table-odd-row-bg: #333;
     --table-border-color: #444;
     --table-row-hover-bg: #444;
+    --table-border-color: #ddd;
+    --code-bg: #444;
   }
 
   a {
diff --git a/src/main.zig b/src/main.zig
index 6f1d53a..fe3d279 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -6,11 +6,18 @@ const git = @import("git.zig");
 const web_prefix = "-";
 
 pub fn main() !u8 {
-    if (std.os.argv.len != 2) {
-        println("Usage: khoe <dir>", .{});
+    if (std.os.argv.len != 3) {
+        println("Usage: khoe <dir> <site-url>", .{});
+        println(
+            \\For example:
+            \\    khoe /srv/git/repos https://khoe.thac.loan
+            \\
+        , .{});
         return 1;
     }
 
+    const site_url = std.os.argv[2];
+
     var dba_impl: std.heap.DebugAllocator(.{}) = .init;
     defer _ = dba_impl.deinit();
     const dba = dba_impl.allocator();
@@ -65,7 +72,8 @@ pub fn main() !u8 {
             .last_commit_time = if (commits.len == 0) "" else try arena.dupe(u8, commits[0].time),
         });
 
-        // TODO: write repo's index
+        try writeRepoPage(site_url, entry.name, target_dir, repo_dir, commits);
+
         // TODO: write repo's commits
     }
 
@@ -103,7 +111,7 @@ pub fn writeHomePage(dir: fs.Dir, repos: []RepoSummary) !void {
     var file = try dir.createFile("index.html", .{});
     defer file.close();
 
-    var buf: [4096]u8 = undefined;
+    var buf: [1024 * 16]u8 = undefined;
     var writer = file.writer(&buf);
     try writer.interface.print(
         \\<!doctype html>
@@ -116,17 +124,20 @@ pub fn writeHomePage(dir: fs.Dir, repos: []RepoSummary) !void {
         \\    <script src="/script.js"></script>
         \\  </head>
         \\  <body>
-        \\    <h1>Khoe</h1>
-        \\    <p>Listing <b>{d}</b> repos:</p>
-        \\    <hr>
+        \\    <header>
+        \\      <div id="breadcrumbs">
+        \\        /<h1>repos</h1>/
+        \\      </div>
+        \\    </header>
+        \\    <p>listing <b>{d}</b> repos:</p>
         \\    <div style="overflow-x:auto; padding-bottom:1rem">
-        \\      <table style="white-space:nowrap">
+        \\      <table>
         \\        <thead>
         \\          <tr>
-        \\            <th>Name</th>
-        \\            <th>Commits</th>
-        \\            <th>Last commit</th>
-        \\            <th>Last modified</th>
+        \\            <th>name</th>
+        \\            <th>commits</th>
+        \\            <th>last commit</th>
+        \\            <th>last modified</th>
         \\          </tr>
         \\        </thead>
         \\        <tbody>
@@ -164,6 +175,100 @@ pub fn writeHomePage(dir: fs.Dir, repos: []RepoSummary) !void {
     try writer.interface.flush();
 }
 
+// TODO: write repo's index
+pub fn writeRepoPage(
+    site_url: [*:0]const u8,
+    repo_name: []const u8,
+    target_dir: fs.Dir,
+    in_repo_dir: fs.Dir,
+    commits: []git.Commit,
+) !void {
+    var buf: [1024]u8 = undefined;
+    var out_repo_dir = try target_dir.makeOpenPath(
+        try std.fmt.bufPrint(&buf, "{s}/{s}", .{ web_prefix, repo_name }),
+        .{},
+    );
+    defer out_repo_dir.close();
+
+    _ = in_repo_dir; // TODO: do I need this?
+
+    var file = try out_repo_dir.createFile("index.html", .{});
+    defer file.close();
+
+    var buf2: [1024 * 16]u8 = undefined;
+    var writer = file.writer(&buf2);
+    try writer.interface.print(
+        \\<!doctype html>
+        \\<html lang="en">
+        \\  <head>
+        \\    <meta charset="utf-8" />
+        \\    <title>{0s} | Khoe</title>
+        \\    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+        \\    <link rel="stylesheet" href="/style.css">
+        \\    <script src="/script.js"></script>
+        \\  </head>
+        \\  <body>
+        \\    <header>
+        \\      <div id="breadcrumbs">
+        \\        /<a href="/">repos</a>/<h1>{0s}</h1>/
+        \\      </div>
+        \\    </header>
+        \\
+        \\    <p>
+        \\      clone command: <code>git clone {2s}/{3s}/</code><br>
+        \\      commits: <b>{1d}</b><br>
+        \\    </p>
+        \\
+        \\    <div style="overflow-x:auto; padding-bottom:1rem">
+        \\      <table>
+        \\        <thead>
+        \\          <tr>
+        \\            <th>hash</th>
+        \\            <th>subject</th>
+        \\            <th>time</th>
+        \\          </tr>
+        \\        </thead>
+        \\        <tbody>
+        \\
+    , .{
+        repo_name,
+        commits.len,
+        site_url,
+        if (std.mem.endsWith(u8, repo_name, ".git"))
+            repo_name
+        else
+            try std.fmt.bufPrint(&buf, "{s}/.git", .{repo_name}),
+    });
+
+    for (commits) |cmt| {
+        try writer.interface.print(
+            \\<tr>
+            \\  <td class="monospace" title="commit diff coming Soon™">{0s}</td>
+            \\  <td>{1s}</td>
+            \\  <td><time class="relative" datetime="{2s}" title="{2s}">{2s}</time></td>
+            \\</tr>
+            \\
+        ,
+            .{
+                //cmt.hash,
+                cmt.hash[0..10],
+                cmt.subject[0..@min(cmt.subject.len, 80)],
+                cmt.time,
+            },
+        );
+    }
+
+    try writer.interface.writeAll(
+        \\        </tbody>
+        \\      </table>
+        \\    </div>
+        \\  </body>
+        \\</html>
+    );
+
+    try writer.interface.flush();
+}
+
 test "all" {
     _ = @import("html.zig");
     std.testing.refAllDeclsRecursive(@This());
-- 
2.47.3

