Add albums: group photos by free-text tags

An album is a tag. Photos carry lowercase free-text tags in their .toml
sidecar (`tags = [...]`), parallel to the existing day grouping — no
database, all derived at build time.

- albums.go: parseTags (lowercase/trim/dedupe, slug via sanitiseSlug),
  groupByTag mirroring groupByDay, and rendering for album pages, the
  index, and the JSON API. Slug collisions between distinct names are
  suffixed and logged.
- Routes: /album/<slug>/ (paginated square grid per tag), /albums/ (index
  with counts), /tags.json (machine-readable tag list, served statically
  like feed.xml).
- POST /upload gains a `tags` field written into the sidecar; the web
  upload form gains a tags input.
- Tags shown on each photo page linking to their album; an Albums link in
  the timeline/grid header.
This commit is contained in:
puttaalu committed 2026-10-08 16:14:09 +02:00
1 parent f33bdd55b1
commit a42abfab48
11 files changed
+469 -6

No files matched your search

+7
View File
@@ -161,6 +161,11 @@
<input type="date" id="date">
</div>
<div class="field">
<label for="tags">Tags</label>
<input type="text" id="tags" placeholder="comma-separated, lowercase" autocomplete="off">
</div>
<button class="submit-btn" id="submit-btn" disabled>Upload photo</button>
<div id="feedback"></div>
</div>
@@ -256,6 +261,7 @@
form.append('image', file);
form.append('caption', document.getElementById('caption').value);
form.append('date', dateInput.value);
form.append('tags', document.getElementById('tags').value);
try {
const res = await fetch('/upload', { method: 'POST', body: form });
@@ -268,6 +274,7 @@
prompt.style.display = 'flex';
dropZone.classList.remove('has-image');
document.getElementById('caption').value = '';
document.getElementById('tags').value = '';
submitBtn.disabled = true;
} else {
feedback.className = 'err';