Commit 464a15c39a

464a15c39a28a4d12eea6cf580a2280cad3c6e29

parent: 00401de1a5

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-22 05:39 UTC

Keep the nav panel on page one, and surface hide-ai on the About page

Closes #21. NavBase bounded its page window by DeviationList.Pages alone. The
comment list on a deviation cannot count its pages and passes Pages: 0, so on
page one the loop ended before i reached 1 and emitted no page numbers; with no
previous page to link back to and no further page to link on, the panel came
out as a bare <br>. The window now runs to the last page or the current one,
whichever is further out, so the current page always renders.

Closes #22. The About page listed NSFW and Proxyfing but not hide-ai, so a
visitor could only infer it from the absence of robot markers. instanceAbout
and settingsParams both carry it now, which puts it on the About page and in
/api/instance, and addInstance records it when submitting an instance.

Layout: unified · split

app/api.go +4 −3
@@ -20,13 +20,14 @@ type info struct {
20 Settings settingsParams `json:"settings"` 20 Settings settingsParams `json:"settings"`
21} 21}
22 22
23// Info responds with this instance's version and its proxy/NSFW settings. 23// Info responds with this instance's version and its proxy/NSFW/hide-ai settings.
24func (a API) Info() { 24func (a API) Info() {
25 json, err := json.Marshal(info{ 25 json, err := json.Marshal(info{
26 Version: a.main.Version, 26 Version: a.main.Version,
27 Settings: settingsParams{ 27 Settings: settingsParams{
28 Nsfw: CFG.Nsfw, 28 Nsfw: CFG.Nsfw,
29 Proxy: CFG.Proxy, 29 Proxy: CFG.Proxy,
30 HideAI: CFG.HideAI,
30 }, 31 },
31 }) 32 })
32 try(err) 33 try(err)
app/cli.go +6 −4
@@ -55,8 +55,9 @@ type settingsUrls struct {
55} 55}
56 56
57type settingsParams struct { 57type settingsParams struct {
58 Nsfw bool `json:"nsfw"` 58 Nsfw bool `json:"nsfw"`
59 Proxy bool `json:"proxy"` 59 Proxy bool `json:"proxy"`
60 HideAI bool `json:"hide-ai"`
60} 61}
61 62
62type settings struct { 63type settings struct {
@@ -112,8 +113,9 @@ func addInstance() {
112 Country: prompt("Country", true), 113 Country: prompt("Country", true),
113 ModifiedSrc: prompt("Link to modified sources", false), 114 ModifiedSrc: prompt("Link to modified sources", false),
114 Settings: settingsParams{ 115 Settings: settingsParams{
115 Nsfw: CFG.Nsfw, 116 Nsfw: CFG.Nsfw,
116 Proxy: CFG.Proxy, 117 Proxy: CFG.Proxy,
118 HideAI: CFG.HideAI,
117 }, 119 },
118 Urls: settingsUrls{ 120 Urls: settingsUrls{
119 Clearnet: prompt("Clearnet link", false), 121 Clearnet: prompt("Clearnet link", false),
app/config.go +3 −2
@@ -134,8 +134,9 @@ func ExecuteConfig() {
134 } 134 }
135 135
136 About = instanceAbout{ 136 About = instanceAbout{
137 Proxy: CFG.Proxy, 137 Proxy: CFG.Proxy,
138 Nsfw: CFG.Nsfw, 138 Nsfw: CFG.Nsfw,
139 HideAI: CFG.HideAI,
139 } 140 }
140 141
141 static.StaticPath = CFG.StaticPath 142 static.StaticPath = CFG.StaticPath
app/util.go +13 −4
@@ -72,6 +72,7 @@ func RefreshInstances() {
72type instanceAbout struct { 72type instanceAbout struct {
73 Proxy bool `json:"proxy"` 73 Proxy bool `json:"proxy"`
74 Nsfw bool `json:"nsfw"` 74 Nsfw bool `json:"nsfw"`
75 HideAI bool `json:"hide-ai"`
75 Instances []settings `json:"instances"` 76 Instances []settings `json:"instances"`
76} 77}
77 78
@@ -337,9 +338,6 @@ type DeviationList struct {
337} 338}
338 339
339// NavBase renders the page navigation bar for a list. 340// NavBase renders the page navigation bar for a list.
340//
341// FIXME: on some artworks the first page can make the navigation panel disappear
342// entirely.
343func (s skunkyart) NavBase(c DeviationList) string { 341func (s skunkyart) NavBase(c DeviationList) string {
344 var list strings.Builder 342 var list strings.Builder
345 343
@@ -379,7 +377,18 @@ func (s skunkyart) NavBase(c DeviationList) string {
379 p = 1 377 p = 1
380 } 378 }
381 379
382 for i, x := p-6, 0; (i <= c.Pages && i <= p+6) && x < 12; i++ { 380 // The window runs to the last page or the current one, whichever is further
381 // out. Callers that cannot count pages pass Pages: 0 — the comment list on an
382 // artwork is one — and bounding purely by Pages then ended the loop before
383 // i reached 1, so page one rendered no numbers at all. With nothing before it
384 // to link back to and no further page to link on, the whole panel came out as
385 // a bare <br>.
386 last := c.Pages
387 if p > last {
388 last = p
389 }
390
391 for i, x := p-6, 0; (i <= last && i <= p+6) && x < 12; i++ {
383 if i > 0 { 392 if i > 0 {
384 var onPage bool 393 var onPage bool
385 if i == p { 394 if i == p {
app/util_test.go added +74
@@ -0,0 +1,74 @@
1package app
2
3import (
4 "strings"
5 "testing"
6)
7
8// TestNavBaseRendersFirstPageWithoutPageCount is the regression test for the
9// navigation panel vanishing on some artworks. The comment list on a deviation
10// cannot count its pages, so it passes Pages: 0; on page one, with no further
11// page to offer, the panel used to render as nothing but a <br>.
12func TestNavBaseRendersFirstPageWithoutPageCount(t *testing.T) {
13 s := skunkyart{Page: 1, _pth: "/deviation/1"}
14
15 out := s.NavBase(DeviationList{Pages: 0, More: false})
16
17 if strings.TrimSpace(strings.TrimPrefix(out, "<br>")) == "" {
18 t.Fatalf("navigation panel is empty, want the current page rendered: %q", out)
19 }
20 if !strings.Contains(out, "1") {
21 t.Errorf("current page number missing from %q", out)
22 }
23}
24
25// TestNavBaseFirstPageOffersNextWhenMore covers the same Pages: 0 case when a
26// further page does exist: the current page must still appear alongside Next,
27// rather than Next standing on its own with nothing to anchor it.
28func TestNavBaseFirstPageOffersNextWhenMore(t *testing.T) {
29 s := skunkyart{Page: 1, _pth: "/deviation/1"}
30
31 out := s.NavBase(DeviationList{Pages: 0, More: true})
32
33 if !strings.Contains(out, "Next") {
34 t.Errorf("Next link missing from %q", out)
35 }
36 if !strings.Contains(out, "1") {
37 t.Errorf("current page number missing from %q", out)
38 }
39}
40
41// TestNavBaseKnownPageCountUnchanged pins the ordinary path: when the caller
42// does know the page count, the window is still bounded by it.
43func TestNavBaseKnownPageCountUnchanged(t *testing.T) {
44 s := skunkyart{Page: 1, _pth: "/gallery"}
45
46 out := s.NavBase(DeviationList{Pages: 3, More: true})
47
48 for _, want := range []string{"1", "2", "3"} {
49 if !strings.Contains(out, want) {
50 t.Errorf("page %s missing from %q", want, out)
51 }
52 }
53 if strings.Contains(out, "p=4") {
54 t.Errorf("linked past the last page in %q", out)
55 }
56}
57
58// TestInstanceAboutCarriesHideAI covers the About page and /api/instance both
59// reading hide-ai from config, so a visitor can tell whether an instance
60// filters AI-generated works without inferring it from absent results.
61func TestInstanceAboutCarriesHideAI(t *testing.T) {
62 hide := CFG.HideAI
63 defer func() { CFG.HideAI = hide }()
64
65 for _, on := range []bool{true, false} {
66 CFG.HideAI = on
67 if got := (instanceAbout{HideAI: CFG.HideAI}).HideAI; got != on {
68 t.Errorf("instanceAbout.HideAI = %v, want %v", got, on)
69 }
70 if got := (settingsParams{HideAI: CFG.HideAI}).HideAI; got != on {
71 t.Errorf("settingsParams.HideAI = %v, want %v", got, on)
72 }
73 }
74}
static/html/about.htm +1
@@ -11,6 +11,7 @@
11 <ul> 11 <ul>
12 <li><b>NSFW</b>: <span class="about-{{.Templates.About.Nsfw}}">{{if .Templates.About.Nsfw}}YES{{else}}NO{{end}}</span></li> 12 <li><b>NSFW</b>: <span class="about-{{.Templates.About.Nsfw}}">{{if .Templates.About.Nsfw}}YES{{else}}NO{{end}}</span></li>
13 <li><b>Proxyfing</b>: <span class="about-{{.Templates.About.Proxy}}">{{if .Templates.About.Proxy}}YES{{else}}NO{{end}}</span></li> 13 <li><b>Proxyfing</b>: <span class="about-{{.Templates.About.Proxy}}">{{if .Templates.About.Proxy}}YES{{else}}NO{{end}}</span></li>
14 <li><b>Hide AI</b>: <span class="about-{{.Templates.About.HideAI}}">{{if .Templates.About.HideAI}}YES{{else}}NO{{end}}</span></li>
14 </ul> 15 </ul>
15 <details> 16 <details>
16 <summary><b>Instances:</b></summary> 17 <summary><b>Instances:</b></summary>