Add titles-only list export and Custom lists

Export
- GET /l/<list>/export?format=csv|txt downloads a list's titles in rank
  order, one per line, with no header or other fields (CSV gets a UTF-8
  BOM for Excel)
- Export menu in the list editor and, for the owner, on the public list
  page; hidden lists stay owner-only

Custom lists
- New "Custom" category for anything not built in (sports teams,
  drinks…): owner-named, slug from the title, entries added by hand or
  from an unfiltered Wikipedia search
- Per-list artwork shape (square or tall), stored in a new lists.art
  column (migration 2)
- Art can be uploaded while adding an entry, not only after

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-24 17:18:24 -07:00
1 parent e9a75a968a
commit 58a29b7339
23 files changed
+314 -37

No files matched your search

+2 -1
View File
@@ -98,6 +98,7 @@ func (s *Server) Handler() http.Handler {
// Public
mux.HandleFunc("GET /{$}", s.handleHome)
mux.HandleFunc("GET /l/{slug}", s.handleList)
mux.HandleFunc("GET /l/{slug}/export", s.handleExport)
mux.HandleFunc("GET /images/{name}", s.handleImage)
mux.HandleFunc("GET /custom.css", s.handleCustomCSS)
mux.Handle("GET /static/", http.StripPrefix("/static/", s.staticHandler()))
@@ -186,7 +187,7 @@ func (s *Server) recoverer(next http.Handler) http.Handler {
func (s *Server) securityHeaders(next http.Handler) http.Handler {
// Remote https images are allowed so search results can preview art
// before it's cached locally.
const csp = "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; " +
const csp = "default-src 'self'; img-src 'self' data: blob: https:; style-src 'self' 'unsafe-inline'; script-src 'self'; " +
"object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'"
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h := w.Header()