Files
Levyraati26_go/templates/submission.html
T
Esa Kataja 4f337b6202 Add lyrics: paste, fetch, and read them while reviewing
Lyrics are suggested at submission and never imposed. The conversion worker
makes one LRCLIB lookup with whatever metadata exists, and the waiting page
has a Hae sanoitukset button that re-queries with whatever title and artist
are currently typed — which is the case that matters, since our metadata comes
from ID3 tags and YouTube uploaders. Neither path overwrites typed text.

- lyrics text on both submissions and songs, copied across at publish. Nothing
  has launched, so the column goes into 001_init.sql rather than a migration
- The lock does not cover lyrics: it freezes what the song claims to be, and
  nobody reviewed the lyrics. So the submitter can still fix them afterwards,
  or paste them for an old song a year later
- The review strip gained a second pane: lyrics on the left, review on the
  right, so following the words costs no scrolling. No lyrics means no pane,
  not an empty one. Below 1024px the panes stack
- LRC timestamps are stored but stripped for reading — they belong to the
  player, not the reader
- The lyrics box is capped and scrolls inside itself, so a long song cannot
  stretch the strip past the screen

Fixes a real bug found on the way: saveMetadata cleared any field the request
did not carry, so publishing wiped the lyrics the worker had just fetched.
Fields absent from a request now keep their stored value.

The client identifies itself to LRCLIB as "levyraati" and nothing more.

Tests cover cleanLyrics keeping line breaks, and fetchLyrics against a local
server: synced beats plain, instrumentals and wrong-length takes are skipped,
and a miss is empty with no error.
2026-08-01 00:21:31 +03:00

93 lines
4.0 KiB
HTML

{{define "submission-status"}}
<div class="status{{if .Failed}} failed{{end}}" {{if not .Done}}hx-get="/submit/{{.ID}}/status" hx-trigger="every 2s" hx-swap="outerHTML"{{end}}>
<ol class="segments" aria-label="Lähetyksen tila">
<li class="{{if .Done}}done{{else}}now{{end}}">Lähetetty</li>
<li class="{{if .Done}}done{{else if eq .Status "converting"}}now{{else if eq .Status "downloading"}}now{{end}}">
{{if eq .Status "downloading"}}Ladataan{{else}}Muunnetaan{{end}}</li>
<li class="{{if .Ready}}done{{else if .Failed}}failed{{end}}">{{if .Failed}}Epäonnistui{{else}}Valmis{{end}}</li>
</ol>
<p class="{{if .Failed}}error{{end}}"><strong>{{.Label}}</strong></p>
{{if .Failed}}
{{with .StatusMsg}}<p class="muted small">{{.}}</p>{{end}}
<div class="actions">
{{if .CanRetry}}
<form method="post" action="/submit/{{.ID}}/retry">
<button type="submit">Yritä uudelleen</button>
</form>
{{end}}
<form method="post" action="/submit/{{.ID}}/discard">
<button type="submit" class="danger">Poista lähetys</button>
</form>
</div>
{{if not .CanRetry}}<p class="muted small">Lataa tiedosto uudelleen, jos haluat yrittää toisen kerran.</p>{{end}}
{{else}}
<!-- Outside the form and attached to it with form=, so one button both submits the metadata
and publishes. Disabled until the audio has finished converting. -->
<button type="submit" form="meta" {{if not .Ready}}disabled{{end}}>Julkaise</button>
{{if not .Ready}}<p class="muted small">Julkaise aukeaa kun muunnos on valmis.</p>{{end}}
{{end}}
</div>
{{end}}
{{define "saved"}}<span id="saved" class="saved">{{if .}}Tallennettu {{.}}{{end}}</span>{{end}}
<!-- Included by the page and returned alone by the suggestion button, so the markup exists once.
hx-include sends the current title and artist, which is the whole point: the lookup uses what
the submitter just fixed, not what the tags claimed. -->
{{define "lyricsfield"}}
<div id="lyricsfield">
<label>Sanoitukset <span class="muted small">(vapaaehtoinen)</span>
<textarea name="lyrics" rows="8" maxlength="20000"
placeholder="Liitä sanoitukset tähän tai hae ne alta.">{{.Lyrics}}</textarea>
</label>
<div class="lyricsbar">
<button type="button" class="ghost"
hx-post="/submit/{{.ID}}/lyrics"
hx-include="[name='title'], [name='artist']"
hx-target="#lyricsfield" hx-swap="outerHTML">Hae sanoitukset</button>
{{if .Found}}<span class="muted small">Löytyi — tarkista ja muokkaa tarvittaessa.</span>{{end}}
{{if .Searched}}{{if not .Found}}
<span class="muted small">Ei löytynyt. Tarkista nimi ja esittäjä tai liitä sanoitukset itse.</span>
{{end}}{{end}}
</div>
</div>
{{end}}
{{define "content"}}
<h1>Lähetys</h1>
{{if .Data.Failed}}{{template "submission-status" .Data}}{{end}}
{{if not .Data.Failed}}
<form id="meta" method="post" action="/submit/{{.Data.ID}}/publish" class="stack"
hx-post="/submit/{{.Data.ID}}" hx-trigger="input changed delay:1.2s, change"
hx-target="#saved" hx-swap="outerHTML">
<label>Kappaleen nimi
<input name="title" value="{{.Data.Title}}" maxlength="100" required>
</label>
<label>Esittäjä
<input name="artist" value="{{.Data.Artist}}" maxlength="100" required>
</label>
<label>Genre
<select name="genre" required>
<option value="">— valitse —</option>
{{$current := .Data.Genre}}
{{range .Data.Genres}}
<option value="{{.Code}}" {{if eq .Code $current}}selected{{end}}>{{.Label}}</option>
{{end}}
</select>
</label>
<label>Esittely <span class="muted small">(vapaaehtoinen)</span>
<textarea name="description" rows="5" maxlength="2000">{{.Data.Description}}</textarea>
</label>
{{template "lyricsfield" dict "ID" .Data.ID "Lyrics" .Data.Lyrics}}
{{template "saved" ""}}
</form>
<p class="muted">Tiedot tallentuvat itsestään kirjoittaessasi. Nimi, esittäjä ja genre tarvitaan
ennen julkaisua.</p>
{{template "submission-status" .Data}}
{{end}}
{{end}}