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

+14
View File
@@ -277,6 +277,17 @@ code { font: 0.9em ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; bac
.pager-btn.is-current { background: var(--accent); border-color: var(--accent); color: var(--accent-text); }
.pager-gap { color: var(--faint); padding: 0 4px; }
/* ---------- dropdown menu ---------- */
.menu { position: relative; }
.menu > summary { list-style: none; }
.menu > summary::-webkit-details-marker { display: none; }
.menu-pop {
position: absolute; right: 0; top: calc(100% + 6px); z-index: 30; min-width: 200px; padding: 6px;
background: var(--surface); border: 1px solid var(--border); border-radius: calc(var(--radius) + 2px); box-shadow: var(--shadow);
}
.menu-pop a { display: flex; justify-content: space-between; gap: 12px; padding: 8px 10px; border-radius: 8px; text-decoration: none; font-size: 14px; }
.menu-pop a:hover { background: var(--surface-2); }
/* ---------- empty / error ---------- */
.empty { text-align: center; padding-top: 80px; padding-bottom: 40px; display: flex; flex-direction: column; align-items: center; gap: 10px; }
.empty-art { display: grid; place-items: center; width: 72px; height: 72px; border-radius: 20px; background: var(--accent-soft); color: var(--accent); margin-bottom: 10px; }
@@ -445,6 +456,9 @@ input[type="checkbox"] { accent-color: var(--accent); width: 18px; height: 18px;
}
.cat-option .icon, .quick-cats .icon { width: 22px; height: 22px; color: var(--accent); }
.cat-option span:hover, .quick-cats button:hover { border-color: var(--accent); }
.cat-option--wide { grid-column: 1 / -1; }
.cat-option--wide span { flex-direction: row; justify-content: center; flex-wrap: wrap; gap: 4px 8px; }
.cat-option--wide small { flex-basis: 100%; font-weight: 400; }
.cat-option input:checked + span { border-color: var(--accent); background: var(--accent-soft); box-shadow: 0 0 0 1px var(--accent) inset; }
.cat-option input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.cat-option input:disabled + span { opacity: 0.45; cursor: not-allowed; }