fix(backend): valid hx-target selector and reject NaN/Infinity chapter input

hx-target="#card-<key>" is an invalid CSS selector for any key containing
a colon (every real bookmark key is "<site>:<series_id>"), so htmx threw
before swapping and the favourite/delete/chapter-override controls were
dead in the browser. Switch to the attribute-selector form
[id='card-<key>'], which querySelectorAll accepts regardless of the id's
characters.

Also close a validation gap in uiChapter: strconv.ParseFloat accepts
"NaN"/"Infinity"/"-Inf" with err == nil, and every comparison against NaN
is false, so num < 0 let both through to last_chapter_num and permanently
broke HasNewChapter. Reject non-finite values explicitly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 23:19:16 +07:00
parent 2c3d687b6d
commit daec18546c
3 changed files with 42 additions and 5 deletions
+3 -3
View File
@@ -15,19 +15,19 @@
<button class="icon {{if .Favorite}}on{{end}}"
title="Favourite" aria-label="Toggle favourite"
hx-post="/ui/bookmarks/{{.Key}}/favorite"
hx-target="#card-{{.Key}}" hx-swap="outerHTML">
hx-target="[id='card-{{.Key}}']" hx-swap="outerHTML">
{{if .Favorite}}★{{else}}☆{{end}}
</button>
<button class="icon" title="Set chapter" aria-label="Set chapter"
onclick="toggleChapterForm('{{.Key}}')">✎</button>
<button class="icon danger" title="Remove" aria-label="Remove"
hx-delete="/ui/bookmarks/{{.Key}}"
hx-target="#card-{{.Key}}" hx-swap="outerHTML"
hx-target="[id='card-{{.Key}}']" hx-swap="outerHTML"
hx-confirm="Remove {{.Title}} from the list?">🗑</button>
</div>
<form class="chapter-form" id="chapter-form-{{.Key}}" hidden
hx-post="/ui/bookmarks/{{.Key}}/chapter"
hx-target="#card-{{.Key}}" hx-swap="outerHTML">
hx-target="[id='card-{{.Key}}']" hx-swap="outerHTML">
<input name="chapter" type="number" step="0.1" min="0"
value="{{.LastChapterNum}}" aria-label="Chapter number" required>
<button type="submit">Save</button>