The /upload page now takes several photos at once, mirroring the Android and Qt
apps: a card per photo with its own preview, EXIF-prefilled date, caption and
tag-autocompleting tags field, a remove button, and per-photo status. "Post
photos" uploads them sequentially with an x/N progress line; failures can be
retried by pressing Post again, successes link straight to the new photo page.
No server change — each photo is still a separate POST /upload, exactly as the
apps' per-photo queue does it, so slugs come from filenames as before.
Add GET /api/session, an unguarded probe that reports whether the request
carries valid credentials. It returns 200 with {"authenticated":bool} and never
sends WWW-Authenticate, so the static public pages can check login state without
triggering the browser's Basic-auth dialog. To keep it from being a
password-guessing oracle that bypasses the /upload limiter, a request with no
credentials is answered cheaply as false, while wrong credentials go through the
same failed-attempt limiter (429 once drained).
static/session.js, loaded by every page template, calls it and, only when
authenticated, builds a fixed toolbar: Upload / Manage links, an "Edit this"
link on photo pages (via <body data-slug>, deep-linking to /manage?edit=<slug>,
which scrolls to and focuses that row), a signed-in indicator, and a log-out
control. Logged-out visitors see nothing and get no dialog.
Log out is best-effort: HTTP Basic has no reliable sign-out (the browser caches
credentials until the window closes), so it overwrites the cached credential and
reloads, which works in some browsers but not all. A dependable logout would
need cookie sessions.
A small shared widget (static/tagcomplete.js) completes the tag after the last
comma from the existing tag list, keeping earlier tags intact. It is wired to
every comma-separated tag input: the upload form, and the manage page's per-row
edit field and bulk add/remove fields.
Suggestions come from /tags.json, which the generator already writes on every
build and nginx serves statically, so no server change is needed; the list is
fetched once, cached, and invalidated after an edit that may add a tag. Keyboard
navigation (up/down, enter/tab, escape) and click selection are supported.
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.
- Web: favicon.ico + png icons under static/, served from the site root
and linked from every page head.
- Android: the artwork becomes the launcher icon — the 432px adaptive
foreground over the gruvbox background, replacing the placeholder vector
drawables.
- Desktop: the icon is the Qt window/taskbar icon (embedded via app.qrc),
and the app is now an installable package — a wrapped binary, a
photog.desktop entry, and hicolor icons — so `nix profile install
./desktop` puts "Photog" in the application menu.
Once a file was picked the preview <img> overlaid the file input and
blocked further clicks, so the drop zone became read-only. Making the
preview and the new 'Click to change' hint pointer-events:none lets
clicks pass through to the input again.
Also parses the JPEG APP1 EXIF segment inline (no dependency) to read
DateTimeOriginal and prefill the date field. Falls back to the file's
lastModified for PNGs or JPEGs without EXIF. The date field stays
editable in either case.
Vollkorn read as too chunky next to the Malayalam bio in Manjari.
Crimson Pro is a calmer book-face serif that pairs more evenly.
Renames the CSS variable from --vk to --serif to reflect that the
value is no longer Vollkorn-specific, and bumps .ml to 17px/1.5
so the Malayalam subtitle sits at a comfortable reading size.