bc64a1d894
(*Store).RemoveSeries deletes one series row by (site, series_id) via a
plain parameterized DELETE; a bookmarks_series_fk violation outside
23503 is translated into the ErrSeriesHasBookmarks sentinel so no
driver type escapes the store. The caller reads the row's cover
address before the delete and reclaims it after: ReclaimCover's guard
cannot pass while a series row still points at the address.
POST /admin/series/{key}/remove answers the list row with the removed
row's fragment plus the heading re-rendered out of band at the fresh
count (HX-Reswap: delete removes the row through the same button that
swaps the refusal back in), and navigates from the detail page to the
No-Readers list (HX-Redirect for htmx, a 303 for plain clients). A
removal that races a fresh Bookmark is a refusal, not a 500: the row
re-renders at its new count with the fact spelled out. The control
renders only at zero Reader count on both surfaces, gated by hx-confirm
with the brief's copy.
62 lines
3.1 KiB
HTML
62 lines
3.1 KiB
HTML
{{/* Per-Series page: one address per Series, keyed "<site>:<series_id>" so the
|
|
list row is one hop from it. Everything here is a Series-level fact plus
|
|
the anonymous Reader count. Check now lands in its own .dform below the
|
|
.detail-grid; the correction form is the grid's first column and the URL
|
|
repair the second (issue #151). The pending and corrected markers ride the
|
|
meta line with the other marks. */}}
|
|
{{define "series-detail"}}
|
|
<a class="ghost detail-back" href="/admin/series">← Series</a>
|
|
<h1 class="detail-title">{{.Title}}</h1>
|
|
<p class="detail-key">{{.Key}} · {{.Site}} · {{.Kind}}</p>
|
|
{{if .Cover}}<div class="cover"><img src="{{.Cover}}" alt="" loading="lazy"></div>
|
|
{{else}}<div class="cover"></div>{{end}}
|
|
{{template "series-detail-meta" .}}
|
|
<div class="detail-grid">
|
|
<form class="dform" hx-post="/admin/series/{{.Key}}/latest" hx-target="#detail-meta" hx-swap="outerHTML">
|
|
<h3>Correct latest chapter</h3>
|
|
<p class="hint">The next successful Poll overwrites this value.</p>
|
|
<div class="field">
|
|
<input type="number" name="chapter" step="any" placeholder="{{.Chapter}}" required>
|
|
<button type="submit" class="ghost">Set</button>
|
|
</div>
|
|
</form>
|
|
<form class="dform" hx-post="/admin/series/{{.Key}}/series-url" hx-target="#detail-meta" hx-swap="outerHTML">
|
|
<h3>Repair series URL</h3>
|
|
<p class="hint">The Poll fetches this address — storing is not verifying it. A Site-wide host change is a SQL migration, not two hundred forms.</p>
|
|
<div class="field">
|
|
<input type="url" name="series_url" value="{{.URL}}" required>
|
|
<button type="submit" class="ghost">Set</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
{{if .CanPoll}}
|
|
<div class="dform">
|
|
<div class="field"><a class="ghost act" hx-post="/admin/series/{{.Key}}/poll" hx-target="#detail-meta" hx-swap="outerHTML" href="#">Check now</a></div>
|
|
</div>
|
|
{{end}}
|
|
{{if .CanRemove}}
|
|
<div class="dform">
|
|
<div class="field"><button class="ghost danger" hx-post="/admin/series/{{.Key}}/remove" hx-target="#detail-meta" hx-confirm="Removes this series and its stored cover. No Reader has it bookmarked; one re-bookmarking it recreates the row.">Remove</button></div>
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* series-detail-meta is the meta line, and the answer a Check now or
|
|
correction press on the detail page swaps into its place: the same marks,
|
|
re-rendered after the stamp so the pending and corrected markers — and
|
|
the provenance line beside the number — describe the value they sit
|
|
next to. */}}
|
|
{{define "series-detail-meta"}}
|
|
<div class="detail-meta" id="detail-meta">
|
|
<span>ch {{.Chapter}}</span>
|
|
{{if .Provenance}}<span>{{.Provenance}}</span>{{end}}
|
|
<span>checked {{.Checked}}</span>
|
|
<span>{{.Readers}} readers</span>
|
|
{{if .Corrected}}<span class="mark">{{.Corrected}}</span>{{end}}
|
|
{{if .Pending}}<span class="mark">{{.Requested}}</span>{{end}}
|
|
{{if .Unpollable}}<span class="mark">unpollable</span>{{end}}
|
|
{{if .NoCover}}<span class="mark">no cover</span>{{end}}
|
|
{{if .Orphan}}<span class="mark">orphan</span>{{end}}
|
|
{{if .SightingRaised}}<span class="mark">sighting-raised</span>{{end}}
|
|
</div>
|
|
{{end}} |