Lyrics, versioning, and the song page's fact line. - Lyrics are suggested at submission and never imposed: the worker makes one LRCLIB lookup, and Hae sanoitukset re-queries with whatever title and artist are typed. Neither overwrites what the submitter wrote. They live on the submission, travel to the song at publish, and stay editable after the song locks — the lock freezes what a song claims to be, and nobody reviewed the lyrics - The review strip reads and writes side by side: lyrics left, review right. Synced LRC highlights the playing line and seeks on click; plain text scrolls continuously with a nudge knob. Following can be turned off - CalVer YYYY.MM.DD-N, injected from the git tag with -ldflags, shown in the footer, the startup log and /healthz - The song page's metadata became four labelled cells instead of one flat run of five different kinds of fact Fixes: publishing wiped lyrics the worker had just fetched (a request that omitted a field cleared it), lyric auto-scroll landed in the wrong place, and the fader shifted the deck sideways at score 100.
180 lines
7.0 KiB
HTML
180 lines
7.0 KiB
HTML
{{define "content"}}
|
|
{{$s := .Data}}
|
|
<h1>{{$s.Title}}</h1>
|
|
<p class="by">{{$s.Artist}}</p>
|
|
|
|
<!-- Four different kinds of fact, so four labelled cells rather than one run of text. "Oma
|
|
kappale" belongs here as the submitter, not as a badge: it is a fact about you. -->
|
|
<dl class="spec">
|
|
<div><dt>Genre</dt><dd>{{$s.GenreLabel}}</dd></div>
|
|
<div><dt>Kesto</dt><dd>{{$s.Length}}</dd></div>
|
|
<div><dt>Lähetti</dt>
|
|
<dd>{{if $s.Own}}sinä{{else}}<a href="/profile/{{$s.SubmitterID}}">{{$s.Submitter}}</a>{{end}}</dd>
|
|
</div>
|
|
<div><dt>Julkaistu</dt><dd>{{fiday $s.CreatedAt}}</dd></div>
|
|
</dl>
|
|
|
|
{{if $s.CanEdit}}
|
|
<details class="editbox">
|
|
<summary>Muokkaa tietoja</summary>
|
|
<form method="post" action="/songs/{{$s.ID}}" class="stack">
|
|
<label>Nimi <input name="title" value="{{$s.Title}}" maxlength="100" required></label>
|
|
<label>Esittäjä <input name="artist" value="{{$s.Artist}}" maxlength="100" required></label>
|
|
<label>Genre
|
|
<select name="genre" required>
|
|
{{$current := $s.Genre}}
|
|
{{range $s.Genres}}
|
|
<option value="{{.Code}}" {{if eq .Code $current}}selected{{end}}>{{.Label}}</option>
|
|
{{end}}
|
|
</select>
|
|
</label>
|
|
<label>Esittely <textarea name="description" rows="4" maxlength="2000">{{$s.Description}}</textarea></label>
|
|
<button type="submit">Tallenna</button>
|
|
</form>
|
|
<form method="post" action="/songs/{{$s.ID}}/delete"
|
|
onsubmit="return confirm('Poistetaanko kappale lopullisesti?')">
|
|
<button type="submit" class="ghost danger">Poista kappale</button>
|
|
</form>
|
|
<p class="muted small">Muokkaus ja poisto ovat mahdollisia vain ennen ensimmäistä arvostelua.</p>
|
|
</details>
|
|
{{else if $s.Own}}
|
|
<p class="muted small">Kappaletta on jo arvosteltu, joten tietoja ei voi enää muuttaa.</p>
|
|
{{end}}
|
|
|
|
{{if $s.CanReview}}
|
|
<!-- The channel strip: the fader is the score, the panel beside it is everything else you do
|
|
while the track plays. -->
|
|
<form method="post" action="/songs/{{$s.ID}}/review" class="strip">
|
|
{{template "fader" 50}}
|
|
<div class="deck">
|
|
{{template "player" $s}}
|
|
{{with $s.Description}}<p class="intro">{{.}}</p>{{end}}
|
|
|
|
<!-- Two panes when the song has lyrics: read on the left, write on the right, so following
|
|
the words costs no scrolling. Without lyrics the pane is absent, not empty. -->
|
|
<div class="panes{{if not $s.Lyrics}} solo{{end}}">
|
|
{{if $s.Lyrics}}
|
|
<div class="lyricspane">{{template "lyricsview" $s}}</div>
|
|
{{end}}
|
|
<div class="writepane">
|
|
<label class="grow">Arvostelu
|
|
<textarea name="text" maxlength="5000" required placeholder="Mitä kuulit?"></textarea>
|
|
</label>
|
|
<div class="deckfoot">
|
|
<button type="submit">Tallenna arvostelu</button>
|
|
<span class="muted small">Muiden pisteet paljastuvat kun tallennat omasi. Voit muokata
|
|
tai poistaa arvostelusi 30 minuutin ajan.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
{{else}}
|
|
{{template "player" $s}}
|
|
{{with $s.Description}}<p class="intro">{{.}}</p>{{end}}
|
|
{{end}}
|
|
|
|
{{with $s.SourceURL}}<p class="muted small"><a href="{{.}}" rel="noreferrer">Kuuntele YouTubessa</a></p>{{end}}
|
|
|
|
{{if and (not $s.CanReview) (or $s.Lyrics $s.Own)}}
|
|
<!-- Only when the review strip is not already showing them: while reviewing, the lyrics live in
|
|
the left pane. This panel is for reading afterwards and for the submitter's edits. -->
|
|
<details class="lyrics">
|
|
<summary>Sanoitukset{{if not $s.Lyrics}} <span class="muted small">— ei vielä lisätty</span>{{end}}</summary>
|
|
{{if $s.Lyrics}}<pre class="lyricstext">{{lyricstext $s.Lyrics}}</pre>{{end}}
|
|
{{if $s.Own}}
|
|
<form method="post" action="/songs/{{$s.ID}}/lyrics" class="stack">
|
|
<label>Muokkaa sanoituksia
|
|
<textarea name="lyrics" rows="10" maxlength="20000"
|
|
placeholder="Liitä sanoitukset tähän.">{{$s.Lyrics}}</textarea>
|
|
</label>
|
|
<button type="submit">Tallenna sanoitukset</button>
|
|
</form>
|
|
<p class="muted small">Sanoituksia voi muokata vielä arvostelujenkin jälkeen.</p>
|
|
{{end}}
|
|
</details>
|
|
{{end}}
|
|
|
|
{{if $s.ViewerReview}}
|
|
<section>
|
|
<h2>Oma arvostelusi</h2>
|
|
{{with $s.ViewerReview}}
|
|
{{if .CanEdit}}
|
|
<form method="post" action="/reviews/{{.ID}}" class="strip">
|
|
<input type="hidden" name="from" value="/songs/{{$s.ID}}">
|
|
{{template "fader" .Score}}
|
|
<div class="deck">
|
|
<label class="grow">Arvostelu
|
|
<textarea name="text" rows="6" maxlength="5000" required>{{.Text}}</textarea>
|
|
</label>
|
|
<div class="deckfoot">
|
|
<button type="submit">Päivitä</button>
|
|
<span class="muted small">Muokkausaika päättyy {{fidate .EditableUntil}}.</span>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
<form method="post" action="/reviews/{{.ID}}/delete"
|
|
onsubmit="return confirm('Poistetaanko arvostelu?')">
|
|
<input type="hidden" name="from" value="/songs/{{$s.ID}}">
|
|
<button type="submit" class="ghost danger">Poista arvostelu</button>
|
|
</form>
|
|
{{else}}
|
|
<p class="score">{{.Score}}</p>
|
|
<p class="reviewtext">{{.Text}}</p>
|
|
<p class="muted small">Muokkausaika on päättynyt.</p>
|
|
{{end}}
|
|
{{end}}
|
|
</section>
|
|
{{end}}
|
|
|
|
<section>
|
|
<h2>Arvostelut{{if $s.ReviewCount}} ({{$s.ReviewCount}}){{end}}</h2>
|
|
|
|
{{if $s.Revealed}}
|
|
{{if $s.Reviews}}
|
|
<!-- One channel per reviewer: the spread across the row is what "divisive" looks like. -->
|
|
<div class="channels">
|
|
{{range $i, $r := $s.Reviews}}
|
|
<div class="chan{{if $r.Own}} own{{end}}" style="--v: {{$r.Score}}; --i: {{$i}}">
|
|
<span class="chan-track"><span class="chan-cap"></span></span>
|
|
<span class="chan-score">{{$r.Score}}</span>
|
|
<span class="chan-who" title="{{$r.Reviewer}}">{{$r.Initials}}</span>
|
|
</div>
|
|
{{end}}
|
|
{{if $s.Average}}
|
|
<div class="chan avg" style="--v: {{score $s.Average}}">
|
|
<span class="chan-track"><span class="chan-cap"></span></span>
|
|
<span class="chan-score">{{score $s.Average}}</span>
|
|
<span class="chan-who">ka.</span>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
{{range $s.Reviews}}
|
|
<article class="review{{if .Own}} own{{end}}">
|
|
<header>
|
|
<span class="avatar small">{{.Initials}}</span>
|
|
<span class="who">{{.Reviewer}}</span>
|
|
<span class="score">{{.Score}}</span>
|
|
<span class="meta">{{fidate .CreatedAt}}</span>
|
|
</header>
|
|
<p>{{.Text}}</p>
|
|
</article>
|
|
{{end}}
|
|
{{else}}
|
|
<p class="muted">Kukaan ei ole vielä arvostellut tätä kappaletta.</p>
|
|
{{end}}
|
|
{{else}}
|
|
<p class="sealed-note">
|
|
<span class="sealed" aria-hidden="true"></span>
|
|
Muiden pisteet ja arvostelut paljastuvat kun kirjoitat omasi.
|
|
{{if $s.ReviewCount}}Arvosteluja on {{$s.ReviewCount}}.{{end}}
|
|
</p>
|
|
{{end}}
|
|
</section>
|
|
|
|
{{if $s.NextInQueue}}
|
|
<p class="nextup"><a href="/songs/{{$s.NextInQueue}}">Seuraava jonossa →</a></p>
|
|
{{end}}
|
|
{{end}}
|