Add stats, profiles, avatars and palaute
Step 6. The surfaces around the review loop. - Nine leaderboards, ordered and limited in SQL, each with a deterministic tie-break so a tied board doesn't reshuffle between reloads. Min 3 reviews to qualify, for reviewer boards too - Profiles show counts and history-wide averages and the member's songs, never a list of their reviews — per-song opinion stays gated - Avatars: 5MB in, 256px JPEG out, ffmpeg's re-encode being the validation. No upload still means initials, and avatars are public - Changing your own password requires the current one and drops your other sessions - Palaute: free text plus the page you were on, carried in a footer link, and the user agent from the header. Reporters see their own; the admin resolves them with a timestamp rather than a status enum - Admin gained the song list with delete, the reports page, and an open-report count on the dashboard Two theme fixes the screenshots caught: leaderboard ranks need a CSS counter because display:grid suppresses list markers, and count-based boards were printing 3.0 where they mean 3.
This commit is contained in:
@@ -17,6 +17,16 @@ var assetFS embed.FS
|
||||
var funcs = template.FuncMap{
|
||||
"fidate": func(t time.Time) string { return t.Local().Format("2.1.2006 15:04") },
|
||||
"score": func(f *float64) string { return strconv.FormatFloat(*f, 'f', 1, 64) },
|
||||
"value": func(f float64) string { return strconv.FormatFloat(f, 'f', 1, 64) },
|
||||
// Lets one board partial be called with a title and a list, instead of two near-identical
|
||||
// partials per leaderboard.
|
||||
"dict": func(pairs ...any) map[string]any {
|
||||
m := map[string]any{}
|
||||
for i := 0; i+1 < len(pairs); i += 2 {
|
||||
m[pairs[i].(string)] = pairs[i+1]
|
||||
}
|
||||
return m
|
||||
},
|
||||
}
|
||||
|
||||
// Each page is parsed with the layout into its own set, so two pages may both define "content".
|
||||
|
||||
Reference in New Issue
Block a user