diff --git a/src/git_repo.cpp b/src/git_repo.cpp index 9e720e0..e7f898a 100644 --- a/src/git_repo.cpp +++ b/src/git_repo.cpp @@ -358,6 +358,8 @@ std::vector GitRepo::list_tree(const std::string &commit_oid, TreeEntry e = make_entry(git_tree_entry_byindex(tree, i), clean, repo_); if (e.is_dir) collapse_single_child_dirs(repo_, e); + e.is_image = + !e.is_dir && !e.is_submodule && util::image_mime_type(e.name).length() > 0; entries.push_back(std::move(e)); } diff --git a/src/git_repo.h b/src/git_repo.h index f364cf0..6c165a5 100644 --- a/src/git_repo.h +++ b/src/git_repo.h @@ -22,6 +22,7 @@ struct TreeEntry { bool is_dir = false; // tree bool is_submodule = false; uint64_t size = 0; // blob size in bytes (0 for non-blobs) + bool is_image = false; // blob whose name has an image extension }; struct CommitInfo { diff --git a/static/app.js b/static/app.js index eafa089..bd6b21f 100644 --- a/static/app.js +++ b/static/app.js @@ -77,9 +77,34 @@ }); } + // Toggle between the file list table and a thumbnail grid. The choice is + // remembered in localStorage so it persists while browsing into subfolders. + function setupViewToggle() { + var btn = document.querySelector(".view-toggle"); + if (!btn) return; + var table = document.querySelector("table.tree"); + var grid = document.querySelector(".tree-grid"); + if (!table || !grid) return; + + function apply(grid_on) { + btn.setAttribute("aria-pressed", grid_on ? "true" : "false"); + btn.textContent = "view: " + (grid_on ? "grid" : "list"); + table.hidden = grid_on; + grid.hidden = !grid_on; + } + + apply(localStorage.getItem("treeView") === "grid"); + btn.addEventListener("click", function () { + var grid_on = btn.getAttribute("aria-pressed") !== "true"; + localStorage.setItem("treeView", grid_on ? "grid" : "list"); + apply(grid_on); + }); + } + document.addEventListener("DOMContentLoaded", function () { highlight(); renderMarkdown(); setupLogToggle(); + setupViewToggle(); }); })(); diff --git a/static/style.css b/static/style.css index 23933bb..0ed38b1 100644 --- a/static/style.css +++ b/static/style.css @@ -54,6 +54,17 @@ table.tree .size { text-align: right; } .dir { font-weight: 600; } .submodule { color: var(--muted); } +.tree-controls { margin: 0 0 0.75rem; } +.view-toggle { font-size: 0.85rem; color: var(--fg); background: var(--code-bg); border: 1px solid var(--border); border-radius: 4px; padding: 0.25rem 0.6rem; cursor: pointer; } +.view-toggle[aria-pressed="true"] { border-color: var(--link); color: var(--link); } + +.tree-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 0.75rem; } +.tree-grid[hidden] { display: none; } +.tree-grid .tile { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 0.4rem; padding: 0.6rem; border: 1px solid var(--border); border-radius: 6px; text-align: center; word-break: break-word; } +.tree-grid .tile-icon { font-size: 1.8rem; line-height: 1; } +.tree-grid .tile-name { font-size: 0.85rem; } +.tree-grid img.thumb { max-width: 100%; max-height: 120px; object-fit: contain; } + pre.code { background: var(--code-bg); border: 1px solid var(--border); diff --git a/templates/skin.tmpl b/templates/skin.tmpl index dd426ec..23eb34c 100644 --- a/templates/skin.tmpl +++ b/templates/skin.tmpl @@ -100,6 +100,9 @@ <% if (content.tags.size() > 0) %>

tags: <% foreach t in tags %><% item %><%= t %> <% end item %><% end foreach %>

<% end %> +<% if (content.entries.size() > 0) %> +

+<% end %> <% if (not content.is_root) %> @@ -123,6 +126,28 @@ <% end %>
..
empty tree
+ <% end template %> <% end view %>