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
View File
@@ -112,6 +112,8 @@ func (r *Registry) For(ctx context.Context, category string) (Searcher, error) {
}
return &igdb{r: r, id: c.IGDBClientID, secret: c.IGDBClientSecret}, nil
case "theater":
return &wikipedia{r: r, stage: true}, nil
case "custom":
return &wikipedia{r: r}, nil
}
return nil, fmt.Errorf("no provider for category %q", category)
+6
View File
@@ -15,6 +15,9 @@ import (
// specific production (Broadway, a tour, …) is chosen by the owner.
type wikipedia struct {
r *Registry
// stage limits results to musicals, plays and operas. Custom lists
// search all of Wikipedia.
stage bool
}
func (w *wikipedia) Name() string { return "wikipedia" }
@@ -23,6 +26,9 @@ func (w *wikipedia) Label() string { return "Wikipedia" }
const theaterTemplates = `hastemplate:"Infobox musical|Infobox play|Infobox opera"`
func (w *wikipedia) Search(ctx context.Context, query string) ([]Result, error) {
if !w.stage {
return w.search(ctx, query)
}
res, err := w.search(ctx, query+" "+theaterTemplates)
if err == nil && len(res) == 0 {
res, err = w.search(ctx, query)