From c458a3532584df8a3db552fa252aad34c0fd4425 Mon Sep 17 00:00:00 2001
From: tri <tri@thac.loan>
Date: Fri, 3 Oct 2025 14:31:08 +0700
Subject: [PATCH] preview image blobs

---
 src/git.zig   |  6 +++++-
 src/main.zig  | 38 ++++++++++++++++++++++++++++++++++++--
 src/utils.zig | 20 ++++++++++++++++++++
 3 files changed, 61 insertions(+), 3 deletions(-)

diff --git a/src/git.zig b/src/git.zig
index b8f227d..c8ebed3 100644
--- a/src/git.zig
+++ b/src/git.zig
@@ -245,7 +245,11 @@ pub fn objectType(arena: mem.Allocator, dir: fs.Dir, object_hash: []const u8) !O
         return .tree;
     }
 
-    std.debug.panic("Unrecognized object type: \"{s}\" - {s}", .{ result, proc.stderr });
+    std.debug.panic("Unrecognized object type: {s} \"{s}\" - {s}", .{
+        object_hash,
+        result,
+        proc.stderr,
+    });
 }
 
 /// Replicates git's simple heuristic: if there's a null byte in the first 8k
diff --git a/src/main.zig b/src/main.zig
index 0a2965d..269bc9a 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -419,6 +419,7 @@ pub fn writeCommitsPage(
     defer commits_dir.close();
 
     var commits_file = try commits_dir.createFile("index.html", .{});
+    errdefer commits_dir.deleteFile("index.html") catch {};
     defer commits_file.close();
 
     var writerBuf: [4096]u8 = undefined;
@@ -513,7 +514,11 @@ pub fn writeFilePage(
     _ = out_dir.statFile(file_name) catch |err| {
         switch (err) {
             error.FileNotFound => {
-                println("    writing {s}/{s}", .{ constants.web_objects_path, file_name });
+                println("    writing {s}: {s}/{s}", .{
+                    args.repo_name,
+                    constants.web_objects_path,
+                    file_name,
+                });
                 var file = try out_dir.createFile(file_name, .{});
                 errdefer out_dir.deleteFile(file_name) catch {};
                 defer file.close();
@@ -569,7 +574,36 @@ pub fn writeFilePage(
                     args.in_repo_dir,
                     src_file.hash,
                 );
-                if (git.isBinary(file_content)) {
+
+                if (utils.isImg(src_file.path) and file_content.len <= 1024 * 1024 * 16) {
+                    const src_file_name =
+                        if (mem.lastIndexOfScalar(u8, src_file.path, '/')) |slash_idx|
+                            src_file.path[slash_idx + 1 ..]
+                        else
+                            src_file.path;
+
+                    const blob_file_name = try std.fmt.allocPrint(
+                        arena,
+                        "{s}.{s}",
+                        .{ src_file.hash, src_file_name },
+                    );
+
+                    // write the blob's actual content next to its html page
+                    var blob_file = try out_dir.createFile(blob_file_name, .{});
+                    errdefer out_dir.deleteFile(blob_file_name) catch {};
+                    defer blob_file.close();
+                    try blob_file.writeAll(file_content);
+
+                    // write <img> markup in blob page
+                    const escaped_file_name = try html.escapeAlloc(arena, src_file_name);
+                    try writer.print(
+                        \\<img src="{0s}.{1s}" alt="{1s}">
+                        \\
+                    , .{
+                        src_file.hash,
+                        escaped_file_name,
+                    });
+                } else if (git.isBinary(file_content)) {
                     try writer.writeAll(
                         \\<span style="opacity: 0.5">(binary data)</span>
                         \\
diff --git a/src/utils.zig b/src/utils.zig
index e6a5508..9463deb 100644
--- a/src/utils.zig
+++ b/src/utils.zig
@@ -60,3 +60,23 @@ test "humanReadableSize" {
     try t.expectEqualStrings("4 TiB", try humanReadableSize(arena, 1024 * 1024 * 1024 * 1024 * 4));
     try t.expectEqualStrings("1024 TiB", try humanReadableSize(arena, 1024 * 1024 * 1024 * 1024 * 1024));
 }
+
+pub fn isImg(file_path: []const u8) bool {
+    const img_extensions: []const []const u8 = &.{
+        ".apng",
+        ".avif",
+        ".gif",
+        ".jpeg",
+        ".jpg",
+        ".png",
+        ".svg",
+        ".webp",
+        ".bmp",
+    };
+    for (img_extensions) |ext| {
+        if (std.ascii.endsWithIgnoreCase(file_path, ext)) {
+            return true;
+        }
+    }
+    return false;
+}
-- 
2.47.3

