Release 2026.08.01-1
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.
This commit is contained in:
@@ -148,6 +148,7 @@ func (a *app) browsePage(w http.ResponseWriter, r *http.Request) {
|
||||
type songDetail struct {
|
||||
songSummary
|
||||
Description string
|
||||
Lyrics string
|
||||
SourceURL *string
|
||||
Reviews []*review // nil when the reveal rule is withholding them
|
||||
ViewerReview *review
|
||||
@@ -159,14 +160,19 @@ type songDetail struct {
|
||||
|
||||
func (s *songDetail) Locked() bool { return s.ReviewCount > 0 }
|
||||
|
||||
// Synced lyrics get a line-by-line highlight; plain text scrolls continuously instead, because a
|
||||
// highlight on guessed timings makes every second of drift look like a bug.
|
||||
func (s *songDetail) LyricLines() []lyricLine { return parseLRC(s.Lyrics) }
|
||||
|
||||
func (a *app) song(ctx context.Context, viewerID, songID int64) (*songDetail, error) {
|
||||
var d songDetail
|
||||
err := a.pool.QueryRow(ctx, `select`+songColumns+`, coalesce(s.description, ''), s.source_url
|
||||
err := a.pool.QueryRow(ctx, `select`+songColumns+`,
|
||||
coalesce(s.description, ''), coalesce(s.lyrics, ''), s.source_url
|
||||
from songs s join users u on u.id = s.submitted_by
|
||||
where s.id = $2`, viewerID, songID).
|
||||
Scan(&d.ID, &d.Title, &d.Artist, &d.Genre, &d.Duration, &d.CreatedAt,
|
||||
&d.SubmitterID, &d.Submitter, &d.ReviewCount, &d.Average, &d.Own, &d.Reviewed,
|
||||
&d.Description, &d.SourceURL)
|
||||
&d.Description, &d.Lyrics, &d.SourceURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -231,6 +237,31 @@ func (a *app) songPage(w http.ResponseWriter, r *http.Request) {
|
||||
a.render(w, r, http.StatusOK, "song.html", page{Title: d.Title, Data: d})
|
||||
}
|
||||
|
||||
// Lyrics are not covered by the lock: it freezes what the song claims to be, and nobody reviewed
|
||||
// the lyrics. So this checks the submitter and nothing else, which also lets someone paste them for
|
||||
// an old song a year later.
|
||||
func (a *app) editLyrics(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
tag, err := a.pool.Exec(r.Context(),
|
||||
`update songs set lyrics = nullif($3, '') where id = $1 and submitted_by = $2`,
|
||||
id, memberFrom(r.Context()).ID, cleanLyrics(r.FormValue("lyrics")))
|
||||
if err != nil {
|
||||
slog.Error("edit lyrics", "ctx", "songs", "error", err, "song", id)
|
||||
http.Error(w, "virhe", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
a.flash(w, "Sanoitukset tallennettu.")
|
||||
http.Redirect(w, r, fmt.Sprintf("/songs/%d", id), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// --- edit and delete ---
|
||||
|
||||
// The submitter may change the four text fields while the song is unlocked. Once people have
|
||||
|
||||
Reference in New Issue
Block a user