feat(post-form): reverse-geocode City/Country + drop hero-image field

Get Location now reverse-geocodes the captured coordinates into City +
Country via BigDataCloud's free client endpoint, filling only blank fields
(never clobbering a manual entry) and appending the resolved place to the
location status. Best-effort — a failure leaves the coordinates intact.

Removes the redundant hero_image field: journal entries render their hero
from the first uploaded photo (entry-journal uses entry.media.images|first),
so an explicit hero filename served no purpose. Stories still use hero_image
but are not posted through this form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-04 21:06:01 +02:00
co-authored by Claude Opus 4.8
parent 8b037511fd
commit 8cf1145e6f
3 changed files with 99 additions and 75 deletions
+5 -9
View File
@@ -129,15 +129,11 @@ form:
'car': '🚗 Car' 'car': '🚗 Car'
'plane': '✈️ Plane' 'plane': '✈️ Plane'
# Advanced fields — collapsed behind "More options" (see U5) # Advanced fields — collapsed behind "More options" (see U5).
- # No hero_image field: journal entries render their hero from the first
name: hero_image # uploaded photo (entry-journal.html.twig uses entry.media.images|first),
label: Hero Image Filename # so an explicit hero filename was redundant. Stories still use hero_image
type: text # but they aren't posted through this form.
classes: advanced-field
placeholder: 'photo.jpg'
help: 'Filename of the hero/header image. Leave blank to use the first uploaded photo.'
- -
name: force_connect name: force_connect
label: Force connector line label: Force connector line
File diff suppressed because one or more lines are too long
+30 -2
View File
@@ -290,8 +290,8 @@ function field(name) {
} }
/* ── "More options" disclosure (U5, KTD5) ───────────────────── /* ── "More options" disclosure (U5, KTD5) ─────────────────────
* The blueprint renders the advanced trio (hero_image/force_connect/featured) * The blueprint renders the advanced fields (force_connect/featured) flat;
* flat; here we relocate their .form-field wrappers into a native <details> so * here we relocate their .form-field wrappers into a native <details> so
* they collapse by default with full keyboard/AT support. Auto-opens if any * they collapse by default with full keyboard/AT support. Auto-opens if any
* advanced field already carries a value (edit / draft restore). * advanced field already carries a value (edit / draft restore).
*/ */
@@ -370,6 +370,33 @@ function initGeo() {
} }
syncWeatherEnabled(); syncWeatherEnabled();
// Reverse-geocode the captured coordinates into City + Country via
// BigDataCloud's free client endpoint (no API key, CORS-enabled). Only
// fills fields the traveller left blank — never clobbers a manual entry —
// and appends the resolved place to the location status. Best-effort: a
// failure leaves the coordinates (and the manual city/country fields) intact.
function reverseGeocode(lat, lng) {
var cityEl = field('location_city');
var countryEl = field('location_country');
// Nothing to fill if the traveller already typed both.
if ((!cityEl || cityEl.value.trim()) && (!countryEl || countryEl.value.trim())) return;
var url = 'https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=' +
encodeURIComponent(lat) + '&longitude=' + encodeURIComponent(lng) + '&localityLanguage=en';
fetch(url).then(function (r) { return r.json(); }).then(function (data) {
// BigDataCloud's `city` is often empty in rural areas; `locality`
// is the more reliably-populated place name, so fall back to it.
var city = (data.city || data.locality || '').trim();
var country = (data.countryName || '').trim();
if (cityEl && !cityEl.value.trim() && city) cityEl.value = city;
if (countryEl && !countryEl.value.trim() && country) countryEl.value = country;
var place = [city, country].filter(Boolean).join(', ');
if (place) {
setStatus(locStatus, '✓ Location captured · ' + place, 'ok');
}
}).catch(function () { /* keep the coordinates-only status */ });
}
if (locBtn) { if (locBtn) {
locBtn.addEventListener('click', function () { locBtn.addEventListener('click', function () {
if (!navigator.geolocation) { if (!navigator.geolocation) {
@@ -390,6 +417,7 @@ function initGeo() {
locBtn.disabled = false; locBtn.disabled = false;
setStatus(locStatus, '✓ Location captured · ' + lat + ', ' + lng, 'ok'); setStatus(locStatus, '✓ Location captured · ' + lat + ', ' + lng, 'ok');
syncWeatherEnabled(); syncWeatherEnabled();
reverseGeocode(lat, lng); // fill City/Country in the background
}, function (err) { }, function (err) {
locBtn.classList.remove('is-loading'); locBtn.classList.remove('is-loading');
locBtn.disabled = false; locBtn.disabled = false;