@@ 139,6 139,24 @@ type Page struct {
ContainerClass string
}
+// ListItem is one project in a listing — a repository, a database, a space.
+// Title is the display name ("~owner/name"); Visibility is the service's
+// literal enum value ("PUBLIC"/"UNLISTED"/"PRIVATE", "" to render nothing —
+// the srht-repo-list partial shows it lowercase, non-public only).
+type ListItem struct {
+ Href string
+ Title string
+ Visibility string
+ Description string
+}
+
+// RepoList is the dot for the srht-repo-list partial: the items, and the
+// muted text shown when there are none.
+type RepoList struct {
+ Items []ListItem
+ Empty string
+}
+
// Service is the static half of the chrome, built once at startup. The
// exported fields may be adjusted between NewService and the first Page call
// (they are read, never written, by Page).
@@ 140,6 140,39 @@ func TestNavTemplateEmbeddedPage(t *testing.T) {
assert.Contains(t, out, "Logged in as")
}
+// renderList executes the srht-repo-list partial alone against v.
+func renderList(t *testing.T, v RepoList) string {
+ t.Helper()
+ tpl := MustAttach(template.New("t"))
+ tpl, err := tpl.Parse(`{{template "srht-repo-list" .}}`)
+ require.NoError(t, err)
+ var b strings.Builder
+ require.NoError(t, tpl.Execute(&b, v))
+ return b.String()
+}
+
+func TestRepoListCards(t *testing.T) {
+ out := renderList(t, RepoList{Items: []ListItem{
+ {Href: "/~a/pub", Title: "~a/pub", Visibility: "PUBLIC", Description: "described"},
+ {Href: "/~a/unl", Title: "~a/unl", Visibility: "UNLISTED"},
+ {Href: "/~a/prv", Title: "~a/prv", Visibility: "PRIVATE"},
+ {Href: "/~a/none", Title: "~a/none"}, // service without visibility (spec spaces)
+ }})
+
+ assert.Equal(t, 4, strings.Count(out, `class="event"`))
+ assert.Contains(t, out, "<p>described</p>")
+ // PUBLIC and empty visibility render no label; the others render lowercase.
+ assert.Equal(t, 1, strings.Count(out, ">unlisted</small>"))
+ assert.Equal(t, 1, strings.Count(out, ">private</small>"))
+ assert.Equal(t, 2, strings.Count(out, "pull-right"))
+}
+
+func TestRepoListEmptyState(t *testing.T) {
+ out := renderList(t, RepoList{Empty: "No databases yet."})
+ assert.NotContains(t, out, "event-list")
+ assert.Contains(t, out, "No databases yet.")
+}
+
func TestFuncs(t *testing.T) {
assert.Equal(t, "12345678", ShortSHA("1234567890abcdef"))
assert.Equal(t, "abc", ShortSHA("abc"))
@@ 16,8 16,43 @@
{{end}}
{{- end}}
+{{/*
+ srht-repo-list renders a listing of projects (repos, databases, spaces) as
+ the family's event-list cards. Dot is a chrome.RepoList. The rules the four
+ services converged on: h4 title link; visibility as small muted text on the
+ right, non-public only, lowercase; description as a paragraph when present.
+*/}}
+{{define "srht-repo-list" -}}
+{{if .Items}}
+<div class="event-list">
+ {{range .Items}}
+ <div class="event">
+ <h4>
+ <a href="{{.Href}}">{{.Title}}</a>
+ {{if and .Visibility (ne .Visibility "PUBLIC")}}
+ <small class="pull-right">{{if eq .Visibility "UNLISTED"}}unlisted{{else}}private{{end}}</small>
+ {{end}}
+ </h4>
+ {{if .Description}}
+ <p>{{.Description}}</p>
+ {{end}}
+ </div>
+ {{end}}
+</div>
+{{else}}
+<p class="text-muted">{{.Empty}}</p>
+{{end}}
+{{- end}}
+
{{define "srht-nav" -}}
-<span class="navbar-brand">
+{{/*
+ The brand carries a fixed min-width so the service switcher starts at the
+ same x-coordinate on every service: without it the menu shifts by the width
+ of the red service label ("dolt" vs "compare") when hopping between
+ services. Sized for the longest label on the instance; inline because the
+ services build their CSS from the shared core tree and ecore ships none.
+*/}}
+<span class="navbar-brand" style="min-width: 15rem">
<span class="icon icon-circle" aria-hidden="true"><svg width="22" height="22" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200z"/></svg></span>
<a class="navbar-brand" href="/">
{{.SiteName}}