style: calmer listings: dirs first, quiet table links, human sizes
commit 543c549514307219eba835be813eba1e6a6659ab · parent 240bb1d0c2
· browse this tree
Ximianth, 2026-10-26 20:18:31 UTC
The tree and log tables are columns of links; underlining every one was noise, so table links go bare and the accent color plus row hover carry the affordance (hover restores the underline). Name cells stop wrapping (hyphenated repo names were breaking mid-token on the index). Blob sizes read as 1.7 KiB rather than 1714; exact counts remain on the raw view's Content-Length. Directories list before files, the usual forge convention. Go files view with tab-size 4.
diff --git a/pages.go b/pages.go index bc3b7c7..51e822d 100644 --- a/pages.go +++ b/pages.go @@ -9,6 +9,7 @@ import ( "mime" "net/http" "path" + "sort" "strings" "vitrine/display" @@ -32,12 +33,18 @@ article table.tree td, article table.tree th, article table.log td, article table.log th { border: none; padding: .15rem .6rem; } article table.tree tbody tr:hover, article table.log tbody tr:hover { background: var(--well); } article th { text-align: left; } -article td.size { text-align: right; color: var(--ink-soft); } +/* Table links skip the underline: in a column of links it is pure noise; + the accent color and the row hover carry the affordance. Hover restores it. */ +article table.tree a, article table.log a { text-decoration: none; } +article table.tree a:hover, article table.log a:hover { text-decoration: underline; } +article td.name { white-space: nowrap; } +article td.size { text-align: right; color: var(--ink-soft); white-space: nowrap; } article td.date { color: var(--ink-soft); white-space: nowrap; } article td.soft { color: var(--ink-soft); } article td.hash a { font-family: monospace; } article p.clone code { user-select: all; } article img.file { max-width: 100%; } +article pre.file { tab-size: 4; } ` // renderPage fills in the site-wide theme and writes the full page. @@ -114,7 +121,7 @@ func repoCrumbs(name string, more ...crumbLink) []display.Crumb { var indexTmpl = template.Must(template.New("index").Parse(`<h1>repositories</h1> <table class="log"> <tbody> -{{range .}}<tr><td><a href="/{{.Name}}/">{{.Name}}</a></td><td>{{.Description}}</td><td class="soft">{{.Head}}</td></tr> +{{range .}}<tr><td class="name"><a href="/{{.Name}}/">{{.Name}}</a></td><td>{{.Description}}</td><td class="soft">{{.Head}}</td></tr> {{end}}</tbody> </table> `)) @@ -255,7 +262,7 @@ func (s *server) renderReadme(rp *repo, ref, sha string) (template.HTML, error) var treeTmpl = template.Must(template.New("tree").Parse(`<h1>{{.Title}}</h1> <table class="tree"> <tbody> -{{range .Rows}}<tr><td>{{if .Href}}<a href="{{.Href}}">{{.Name}}</a>{{else}}{{.Name}}{{end}}</td><td class="size">{{.Size}}</td></tr> +{{range .Rows}}<tr><td class="name">{{if .Href}}<a href="{{.Href}}">{{.Name}}</a>{{else}}{{.Name}}{{end}}</td><td class="size">{{.Size}}</td></tr> {{end}}</tbody> </table> `)) @@ -284,6 +291,10 @@ func (s *server) serveTreeListing(w http.ResponseWriter, rp *repo, ref, sha, pth if pth != "" { d.Rows = append(d.Rows, treeRow{Name: "..", Href: treeHref(rp.name, ref, parentDir(pth)) + "/"}) } + // Directories list before files (the usual forge convention), each group in git's own order. + sort.SliceStable(entries, func(i, j int) bool { + return entries[i].typ == "tree" && entries[j].typ != "tree" + }) for _, e := range entries { full := e.name if pth != "" { @@ -296,7 +307,7 @@ func (s *server) serveTreeListing(w http.ResponseWriter, rp *repo, ref, sha, pth row.Href = treeHref(rp.name, ref, full) + "/" case "blob": row.Href = treeHref(rp.name, ref, full) - row.Size = fmt.Sprint(e.size) + row.Size = humanSize(e.size) default: // a submodule pointer: nothing here to link to row.Name += " @ " + shortSha(e.hash) } @@ -320,6 +331,22 @@ func treeCrumbs(name, ref, pth string, more ...crumbLink) []display.Crumb { return repoCrumbs(name, append(cs, more...)...) } +// humanSize renders a byte count the way a listing wants it: exact while it is small, +// then one decimal of the binary unit. (The raw view's Content-Length keeps the exact number.) +func humanSize(n int64) string { + if n < 1024 { + return fmt.Sprintf("%d B", n) + } + v, unit := float64(n)/1024, "KiB" + for _, u := range []string{"MiB", "GiB", "TiB"} { + if v < 1024 { + break + } + v, unit = v/1024, u + } + return fmt.Sprintf("%.1f %s", v, unit) +} + // parentDir is path.Dir for tree paths: "" (the root), not ".", for a top-level entry. func parentDir(pth string) string { if d := path.Dir(pth); d != "." { diff --git a/pages_test.go b/pages_test.go index 53e8116..8d8ee42 100644 --- a/pages_test.go +++ b/pages_test.go @@ -68,3 +68,23 @@ func describeResponse(s *server, target string) string { sb.Write(rec.Body.Bytes()) return sb.String() } + +// TestHumanSize pins the listing size format at its boundaries. +func TestHumanSize(t *testing.T) { + for _, c := range []struct { + n int64 + want string + }{ + {0, "0 B"}, + {1023, "1023 B"}, + {1024, "1.0 KiB"}, + {14323, "14.0 KiB"}, + {4 << 20, "4.0 MiB"}, + {5<<30 + 123, "5.0 GiB"}, + {3 << 40, "3.0 TiB"}, + } { + if got := humanSize(c.n); got != c.want { + t.Errorf("humanSize(%d) = %q, want %q", c.n, got, c.want) + } + } +} diff --git a/testdata/pages.md b/testdata/pages.md index 0053254..2407dc7 100644 --- a/testdata/pages.md +++ b/testdata/pages.md @@ -40,7 +40,7 @@ socle<span class="theme">@test</span> <article><h1>repositories</h1> <table class="log"> <tbody> -<tr><td><a href="/fixturama/">fixturama</a></td><td>the fixture</td><td class="soft">add docs and src</td></tr> +<tr><td class="name"><a href="/fixturama/">fixturama</a></td><td>the fixture</td><td class="soft">add docs and src</td></tr> </tbody> </table> </article> @@ -125,9 +125,9 @@ Content-Type: text/html; charset=utf-8 <article><h1>fixturama</h1> <table class="tree"> <tbody> -<tr><td><a href="/fixturama/tree/main/README.md">README.md</a></td><td class="size">49</td></tr> -<tr><td><a href="/fixturama/tree/main/docs/">docs/</a></td><td class="size"></td></tr> -<tr><td><a href="/fixturama/tree/main/src/">src/</a></td><td class="size"></td></tr> +<tr><td class="name"><a href="/fixturama/tree/main/docs/">docs/</a></td><td class="size"></td></tr> +<tr><td class="name"><a href="/fixturama/tree/main/src/">src/</a></td><td class="size"></td></tr> +<tr><td class="name"><a href="/fixturama/tree/main/README.md">README.md</a></td><td class="size">49 B</td></tr> </tbody> </table> </article> @@ -163,9 +163,9 @@ Content-Type: text/html; charset=utf-8 <article><h1>docs</h1> <table class="tree"> <tbody> -<tr><td><a href="/fixturama/tree/main/">..</a></td><td class="size"></td></tr> -<tr><td><a href="/fixturama/tree/main/docs/notes.md">notes.md</a></td><td class="size">17</td></tr> -<tr><td><a href="/fixturama/tree/main/docs/with%20space.txt">with space.txt</a></td><td class="size">25</td></tr> +<tr><td class="name"><a href="/fixturama/tree/main/">..</a></td><td class="size"></td></tr> +<tr><td class="name"><a href="/fixturama/tree/main/docs/notes.md">notes.md</a></td><td class="size">17 B</td></tr> +<tr><td class="name"><a href="/fixturama/tree/main/docs/with%20space.txt">with space.txt</a></td><td class="size">25 B</td></tr> </tbody> </table> </article>