tenseleyflow/shithub / 0063bfe

Browse files

Match README card presentation

Authored by espadonne
SHA
0063bfe3b645a1b9f70be9ca9175660c0f7e3bf3
Parents
adf15d6
Tree
0176264

6 changed files

StatusFile+-
M internal/web/handlers/repo/about_sidebar.go 35 0
M internal/web/handlers/repo/about_sidebar_test.go 26 0
M internal/web/handlers/repo/code.go 7 3
M internal/web/handlers/repo/markdown_links_test.go 11 0
M internal/web/static/css/shithub.css 81 3
M internal/web/templates/repo/tree.html 14 1
internal/web/handlers/repo/about_sidebar.gomodified
@@ -27,6 +27,13 @@ type repoAboutResource struct {
2727
 	Href  string
2828
 }
2929
 
30
+type repoReadmeTab struct {
31
+	Icon   string
32
+	Label  string
33
+	Href   string
34
+	Active bool
35
+}
36
+
3037
 type repoAboutContributor struct {
3138
 	User          bool
3239
 	Username      string
@@ -115,6 +122,34 @@ func repoAboutResources(owner, repoName, ref string, row reposdb.Repo, entries [
115122
 	return resources
116123
 }
117124
 
125
+func repoReadmeTabs(resources []repoAboutResource) []repoReadmeTab {
126
+	tabs := make([]repoReadmeTab, 0, len(resources))
127
+	for _, resource := range resources {
128
+		if resource.Href == "" {
129
+			continue
130
+		}
131
+		label := resource.Label
132
+		active := false
133
+		switch lower := strings.ToLower(resource.Label); {
134
+		case lower == "readme":
135
+			label = "README"
136
+			active = true
137
+		case lower == "code of conduct", lower == "contributing", strings.Contains(lower, "license"):
138
+		case strings.HasPrefix(lower, "security"):
139
+			label = "Security"
140
+		default:
141
+			continue
142
+		}
143
+		tabs = append(tabs, repoReadmeTab{
144
+			Icon:   resource.Icon,
145
+			Label:  label,
146
+			Href:   resource.Href,
147
+			Active: active,
148
+		})
149
+	}
150
+	return tabs
151
+}
152
+
118153
 func (h *Handlers) repoAboutContributors(ctx context.Context, gitDir, ref string) []repoAboutContributor {
119154
 	commits, err := git.Log(ctx, gitDir, git.LogOptions{Ref: ref, MaxCount: 500})
120155
 	if err != nil {
internal/web/handlers/repo/about_sidebar_test.gomodified
@@ -49,6 +49,32 @@ func TestRepoAboutResources_GitHubResourceOrder(t *testing.T) {
4949
 	}
5050
 }
5151
 
52
+func TestRepoReadmeTabs_FiltersDocumentTabs(t *testing.T) {
53
+	t.Parallel()
54
+	resources := []repoAboutResource{
55
+		{Icon: "book", Label: "Readme", Href: "#readme"},
56
+		{Icon: "law", Label: "AGPL-3.0 license", Href: "/LICENSE"},
57
+		{Icon: "people", Label: "Code of conduct", Href: "/CODE_OF_CONDUCT.md"},
58
+		{Icon: "people", Label: "Contributing", Href: "/CONTRIBUTING.md"},
59
+		{Icon: "law", Label: "Security policy", Href: "/SECURITY.md"},
60
+		{Icon: "pulse", Label: "Activity", Href: "/activity"},
61
+		{Icon: "note", Label: "Custom properties", Href: "/settings/custom-properties"},
62
+	}
63
+	got := repoReadmeTabs(resources)
64
+	want := []string{"README", "AGPL-3.0 license", "Code of conduct", "Contributing", "Security"}
65
+	if len(got) != len(want) {
66
+		t.Fatalf("tabs = %#v, want labels %#v", got, want)
67
+	}
68
+	for i := range want {
69
+		if got[i].Label != want[i] {
70
+			t.Fatalf("tabs = %#v, want labels %#v", got, want)
71
+		}
72
+	}
73
+	if !got[0].Active {
74
+		t.Fatalf("README tab should be active: %#v", got[0])
75
+	}
76
+}
77
+
5278
 func TestRepoLanguageForPath_ApproximatesGitHubLinguist(t *testing.T) {
5379
 	t.Parallel()
5480
 	cases := []struct {
internal/web/handlers/repo/code.gomodified
@@ -166,6 +166,7 @@ func (h *Handlers) renderRepoTree(w http.ResponseWriter, r *http.Request, cc *co
166166
 			h.d.Logger.WarnContext(r.Context(), "code: about root LsTree", "error", rerr)
167167
 		}
168168
 	}
169
+	about := h.repoAbout(r.Context(), cc.gitDir, cc.ref, cc.owner, cc.row, aboutEntries)
169170
 
170171
 	h.d.Render.RenderPage(w, r, "repo/tree", map[string]any{
171172
 		"Title":         cc.row.Name + " · " + cc.owner,
@@ -188,7 +189,8 @@ func (h *Handlers) renderRepoTree(w http.ResponseWriter, r *http.Request, cc *co
188189
 		"SSHEnabled":    h.d.CloneURLs.SSHEnabled,
189190
 		"SSHCloneURL":   h.cloneSSH(cc.owner, cc.row.Name),
190191
 		"RepoTopics":    topics,
191
-		"RepoAbout":     h.repoAbout(r.Context(), cc.gitDir, cc.ref, cc.owner, cc.row, aboutEntries),
192
+		"RepoAbout":     about,
193
+		"ReadmeTabs":    repoReadmeTabs(about.Resources),
192194
 		"RepoActions":   h.repoActions(r, cc.row.ID),
193195
 		"RepoCounts":    h.subnavCounts(r.Context(), cc.row.ID, cc.row.ForkCount),
194196
 		"CanSettings":   h.canViewSettings(middleware.CurrentUserFromContext(r.Context())),
@@ -450,8 +452,10 @@ func isHex(s string) bool {
450452
 func rawContentType(p string) (string, bool) {
451453
 	ext := strings.ToLower(path.Ext(p))
452454
 	switch ext {
453
-	case ".html", ".htm", ".xhtml", ".svg", ".js", ".mjs", ".wasm":
455
+	case ".html", ".htm", ".xhtml", ".js", ".mjs", ".wasm":
454456
 		return "text/plain; charset=utf-8", true
457
+	case ".svg":
458
+		return "image/svg+xml", false
455459
 	case ".png":
456460
 		return "image/png", false
457461
 	case ".jpg", ".jpeg":
@@ -477,7 +481,7 @@ func rawContentType(p string) (string, bool) {
477481
 
478482
 func isImageExt(p string) bool {
479483
 	switch strings.ToLower(path.Ext(p)) {
480
-	case ".png", ".jpg", ".jpeg", ".gif", ".webp":
484
+	case ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg":
481485
 		return true
482486
 	}
483487
 	return false
internal/web/static/css/shithub.cssmodified
@@ -1614,13 +1614,91 @@ button.shithub-repo-action {
16141614
 .shithub-readme-head {
16151615
   display: flex;
16161616
   align-items: center;
1617
+  min-height: 48px;
1618
+  padding: 0 1rem;
1619
+  border-bottom: 1px solid var(--border-default);
1620
+  overflow-x: auto;
1621
+}
1622
+.shithub-readme-tabs {
1623
+  display: flex;
1624
+  align-items: stretch;
1625
+  gap: 1.25rem;
1626
+  min-width: 0;
1627
+}
1628
+.shithub-readme-tab {
1629
+  display: inline-flex;
1630
+  align-items: center;
16171631
   gap: 0.45rem;
1618
-  padding: 0.75rem 1rem;
1632
+  margin-bottom: -1px;
1633
+  padding: 0.85rem 0 0.78rem;
1634
+  border-bottom: 2px solid transparent;
1635
+  color: var(--fg-muted);
1636
+  font-size: 0.85rem;
1637
+  font-weight: 600;
1638
+  text-decoration: none;
1639
+  white-space: nowrap;
1640
+}
1641
+.shithub-readme-tab:hover {
1642
+  color: var(--fg-default);
1643
+  text-decoration: none;
1644
+}
1645
+.shithub-readme-tab.is-active {
1646
+  border-bottom-color: var(--accent-emphasis, #fd8c73);
1647
+  color: var(--fg-default);
1648
+}
1649
+.shithub-readme-body { padding: 2rem; }
1650
+.markdown-body {
1651
+  color: var(--fg-default);
1652
+  font-size: 0.875rem;
1653
+  line-height: 1.5;
1654
+  overflow-wrap: break-word;
1655
+}
1656
+.markdown-body > :first-child { margin-top: 0; }
1657
+.markdown-body > :last-child { margin-bottom: 0; }
1658
+.markdown-body p,
1659
+.markdown-body blockquote,
1660
+.markdown-body ul,
1661
+.markdown-body ol,
1662
+.markdown-body dl,
1663
+.markdown-body table,
1664
+.markdown-body pre,
1665
+.markdown-body details {
1666
+  margin-top: 0;
1667
+  margin-bottom: 1rem;
1668
+}
1669
+.markdown-body h1,
1670
+.markdown-body h2 {
1671
+  margin-top: 1.5rem;
1672
+  margin-bottom: 1rem;
1673
+  padding-bottom: 0.3em;
16191674
   border-bottom: 1px solid var(--border-default);
1675
+}
1676
+.markdown-body h1 { font-size: 2em; }
1677
+.markdown-body h2 { font-size: 1.5em; }
1678
+.markdown-body h3 { font-size: 1.25em; }
1679
+.markdown-body h1,
1680
+.markdown-body h2,
1681
+.markdown-body h3,
1682
+.markdown-body h4,
1683
+.markdown-body h5,
1684
+.markdown-body h6 {
16201685
   font-weight: 600;
1686
+  line-height: 1.25;
1687
+}
1688
+.markdown-body hr {
1689
+  height: 0.25em;
1690
+  padding: 0;
1691
+  margin: 1.5rem 0;
1692
+  background: var(--border-default);
1693
+  border: 0;
1694
+}
1695
+.markdown-body img {
1696
+  max-width: 100%;
1697
+  box-sizing: content-box;
16211698
 }
1622
-.shithub-readme-body { padding: 1rem; }
1623
-.shithub-readme h1, .shithub-readme h2 { border-bottom: 1px solid var(--border-default); padding-bottom: 0.3rem; }
1699
+.markdown-body [align="center"] { text-align: center; }
1700
+.markdown-body [align="right"] { text-align: right; }
1701
+.markdown-body [align="left"] { text-align: left; }
16241702
 .shithub-readme code { font-family: monospace; padding: 0.1em 0.3em; background: var(--canvas-default); border-radius: 3px; }
16251703
 .shithub-readme pre code { padding: 0; background: none; }
16261704
 .shithub-readme-plain { white-space: pre-wrap; }
internal/web/templates/repo/tree.htmlmodified
@@ -122,7 +122,20 @@
122122
       </section>
123123
       {{ if .README }}
124124
       <section class="shithub-readme markdown-body" id="readme" aria-label="README">
125
-        <div class="shithub-readme-head">{{ octicon "book" }} README</div>
125
+        <div class="shithub-readme-head">
126
+          {{ if .ReadmeTabs }}
127
+          <nav class="shithub-readme-tabs" aria-label="Repository documents">
128
+            {{ range .ReadmeTabs }}
129
+            <a href="{{ .Href }}" class="shithub-readme-tab{{ if .Active }} is-active{{ end }}">
130
+              {{ octicon .Icon }}
131
+              <span>{{ .Label }}</span>
132
+            </a>
133
+            {{ end }}
134
+          </nav>
135
+          {{ else }}
136
+          <span class="shithub-readme-tab is-active">{{ octicon "book" }} <span>README</span></span>
137
+          {{ end }}
138
+        </div>
126139
         <div class="shithub-readme-body">{{ .README }}</div>
127140
       </section>
128141
       {{ end }}