Work the design critique down: chapter format, colour law, search, strip, a11y #11
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
@@ -41,6 +42,41 @@ func (b Bookmark) HasNewChapter() bool {
|
||||
return b.LatestChapterNum != nil && *b.LatestChapterNum > b.LastChapterNum
|
||||
}
|
||||
|
||||
// chapterLeadIn matches the prefix the userscript and the poller both write
|
||||
// ("Chapter 250"), so the UI can add exactly one "Ch " of its own instead of
|
||||
// doubling it. A manual edit through the web UI stores a bare "250", which is
|
||||
// the same string minus the lead-in.
|
||||
var chapterLeadIn = regexp.MustCompile(`(?i)^\s*(?:chapter|ch\.?)\s*`)
|
||||
|
||||
func displayChapter(raw string, num float64) string {
|
||||
rest := strings.TrimSpace(chapterLeadIn.ReplaceAllString(raw, ""))
|
||||
if rest == "" {
|
||||
rest = strconv.FormatFloat(num, 'f', -1, 64)
|
||||
}
|
||||
// "Ch " only makes sense in front of a number; anything else is a label the
|
||||
// site gave us, so pass it through as written.
|
||||
if rest[0] < '0' || rest[0] > '9' {
|
||||
return rest
|
||||
}
|
||||
return "Ch " + rest
|
||||
}
|
||||
|
||||
// DisplayChapter is the read-progress line: one canonical "Ch N" whatever
|
||||
// format the write came in as.
|
||||
func (b Bookmark) DisplayChapter() string {
|
||||
return displayChapter(b.LastChapter, b.LastChapterNum)
|
||||
}
|
||||
|
||||
// DisplayLatest is the same for the newest published chapter, which arrives
|
||||
// with the same "Chapter N" lead-in from both the userscript and the poller.
|
||||
func (b Bookmark) DisplayLatest() string {
|
||||
var num float64
|
||||
if b.LatestChapterNum != nil {
|
||||
num = *b.LatestChapterNum
|
||||
}
|
||||
return displayChapter(b.LatestChapter, num)
|
||||
}
|
||||
|
||||
// ContinueURL is where the Continue button points: the chapter last read, or
|
||||
// the series page when no chapter URL was ever captured.
|
||||
func (b Bookmark) ContinueURL() string {
|
||||
|
||||
@@ -1034,3 +1034,41 @@ func TestOpenStoreMigratesAsuraHashlessCollision(t *testing.T) {
|
||||
t.Fatalf("merge kept wrong row: %+v", merged)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisplayChapter(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
raw string
|
||||
num float64
|
||||
want string
|
||||
}{
|
||||
{"userscript label", "Chapter 250", 250, "Ch 250"},
|
||||
{"abbreviated with dot", "Ch. 12", 12, "Ch 12"},
|
||||
{"lowercase abbreviation", "ch 3", 3, "Ch 3"},
|
||||
{"bare number from a manual edit", "45.5", 45.5, "Ch 45.5"},
|
||||
{"empty falls back to the number", "", 7, "Ch 7"},
|
||||
{"empty and zero", "", 0, "Ch 0"},
|
||||
{"label with no number passes through", "Prologue", 0, "Prologue"},
|
||||
{"title-like label keeps its words", "Chapter of the Sun", 0, "of the Sun"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := displayChapter(tc.raw, tc.num); got != tc.want {
|
||||
t.Errorf("displayChapter(%q, %v) = %q, want %q", tc.raw, tc.num, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
b := Bookmark{LastChapter: "Chapter 9", LastChapterNum: 9}
|
||||
if got := b.DisplayChapter(); got != "Ch 9" {
|
||||
t.Errorf("DisplayChapter() = %q, want %q", got, "Ch 9")
|
||||
}
|
||||
if got := b.DisplayLatest(); got != "Ch 0" {
|
||||
t.Errorf("DisplayLatest() with nothing captured = %q, want %q", got, "Ch 0")
|
||||
}
|
||||
num := 11.0
|
||||
b.LatestChapter, b.LatestChapterNum = "Chapter 11", &num
|
||||
if got := b.DisplayLatest(); got != "Ch 11" {
|
||||
t.Errorf("DisplayLatest() = %q, want %q", got, "Ch 11")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
{{else if .Favorite}}<span class="foot-rule brass"></span>{{end}}
|
||||
</span>
|
||||
<span class="recent-title">{{.Title}}</span>
|
||||
<span class="recent-chapter">Ch {{.LastChapter}}{{if .HasNewChapter}} · New{{end}}</span>
|
||||
<span class="recent-chapter">{{.DisplayChapter}}{{if .HasNewChapter}} · New{{end}}</span>
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
<p class="meta">
|
||||
<span class="site-{{.Site}}">{{.Site}}</span>
|
||||
<span class="sep">/</span>
|
||||
<span class="chapter">Ch {{.LastChapter}}</span>
|
||||
<span class="chapter">{{.DisplayChapter}}</span>
|
||||
{{if and (eq .Status "reading") .HasNewChapter}}
|
||||
<span class="sep">/</span>
|
||||
<span class="new-chapter">Ch {{.LatestChapter}} out</span>
|
||||
<span class="new-chapter">{{.DisplayLatest}} out</span>
|
||||
{{end}}
|
||||
{{if eq .Status "archived"}}
|
||||
<span class="sep">/</span>
|
||||
@@ -86,7 +86,7 @@
|
||||
hx-post="/ui/bookmarks/{{.Key}}/chapter"
|
||||
hx-target="[id='card-{{.Key}}']" hx-swap="outerHTML"
|
||||
hx-indicator="[id='card-{{.Key}}']" hx-disabled-elt="input, button">
|
||||
{{if .LatestChapter}}<p class="hint">Latest known: Ch {{.LatestChapter}}</p>{{end}}
|
||||
{{if .LatestChapter}}<p class="hint">Latest known: {{.DisplayLatest}}</p>{{end}}
|
||||
<div class="field">
|
||||
{{/* max is a fat-finger guard, not a real ceiling — no series is near it. */}}
|
||||
<input name="chapter" type="number" step="0.1" min="0" max="9999"
|
||||
|
||||
Reference in New Issue
Block a user