feat(where): merge been→where, add callout, fix checkin list
- Overwrite /where/ with full map+list page (was /been/), delete been.njk
- Add "I'm currently in [venue]" callout above map using newest checkin
- Fix invisible checkin list: use Nunjucks groupby dict iteration
({% for groupName, groupItems in grouped %} instead of group.list)
- Deduplicated venues with visit count (e.g. "Murnau 2×")
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,94 +0,0 @@
|
|||||||
---
|
|
||||||
layout: layouts/been.njk
|
|
||||||
title: Been
|
|
||||||
permalink: /been/
|
|
||||||
description: All past check-ins captured by this site via Micropub.
|
|
||||||
withSidebar: true
|
|
||||||
leafletMap: true
|
|
||||||
---
|
|
||||||
{% set checkins = whereCheckins.checkins or [] %}
|
|
||||||
|
|
||||||
<div class="been-page">
|
|
||||||
<header class="mb-6 sm:mb-8">
|
|
||||||
<h1 class="text-2xl sm:text-3xl md:text-4xl font-bold text-surface-900 dark:text-surface-100 mb-2">Been</h1>
|
|
||||||
<p class="p-summary text-lg text-surface-600 dark:text-surface-400">
|
|
||||||
All past check-ins captured by this site via Micropub.
|
|
||||||
</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{% set mapPoints = [] %}
|
|
||||||
{% for c in checkins %}
|
|
||||||
{% if c.latitude and c.longitude %}
|
|
||||||
{% set mapPoints = (mapPoints.push({name: c.name, lat: c.latitude, lng: c.longitude, url: c.venueWebsiteUrl}), mapPoints) %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{% if mapPoints.length %}
|
|
||||||
<div id="checkin-map" class="mb-8 rounded-lg overflow-hidden border border-surface-200 dark:border-surface-700" style="height: 420px;" aria-label="Map of past check-ins"></div>
|
|
||||||
<script>
|
|
||||||
var checkinPoints = {{ mapPoints | dump | safe }};
|
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
L.Icon.Default.mergeOptions({
|
|
||||||
iconUrl: '/css/images/marker-icon.png',
|
|
||||||
iconRetinaUrl: '/css/images/marker-icon-2x.png',
|
|
||||||
shadowUrl: '/css/images/marker-shadow.png'
|
|
||||||
});
|
|
||||||
var map = L.map('checkin-map');
|
|
||||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
||||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
|
||||||
maxZoom: 19
|
|
||||||
}).addTo(map);
|
|
||||||
function escHtml(s) {
|
|
||||||
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
||||||
}
|
|
||||||
var cluster = L.markerClusterGroup({ maxClusterRadius: 40 });
|
|
||||||
var markers = checkinPoints.map(function(p) {
|
|
||||||
var safeName = escHtml(p.name);
|
|
||||||
var safeUrl = (p.url && p.url.indexOf('https://') === 0) ? p.url : '';
|
|
||||||
var popup = safeUrl
|
|
||||||
? '<a href="' + safeUrl + '" target="_blank" rel="noopener">' + safeName + '</a>'
|
|
||||||
: safeName;
|
|
||||||
return L.marker([p.lat, p.lng]).bindPopup(popup);
|
|
||||||
});
|
|
||||||
markers.forEach(function(m) { cluster.addLayer(m); });
|
|
||||||
map.addLayer(cluster);
|
|
||||||
map.fitBounds(cluster.getBounds().pad(0.2), { maxZoom: 13 });
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if checkins.length %}
|
|
||||||
{% set grouped = checkins | groupby("name") %}
|
|
||||||
<ul class="divide-y divide-surface-200 dark:divide-surface-700" aria-label="Past check-ins">
|
|
||||||
{% for group in grouped %}
|
|
||||||
{% set checkin = group.list[0] %}
|
|
||||||
<li class="py-3">
|
|
||||||
<div class="font-medium text-surface-900 dark:text-surface-100">
|
|
||||||
{% if checkin.venueWebsiteUrl %}
|
|
||||||
<a href="{{ checkin.venueWebsiteUrl }}" target="_blank" rel="noopener" class="hover:underline">{{ checkin.name }}</a>
|
|
||||||
{% else %}
|
|
||||||
{{ checkin.name }}
|
|
||||||
{% endif %}
|
|
||||||
{% if group.list | length > 1 %}
|
|
||||||
<span class="ml-2 text-xs font-normal text-surface-500 dark:text-surface-400">{{ group.list | length }}×</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if checkin.isPrivate %}<span class="ml-2 text-xs text-amber-700 dark:text-amber-400">(private)</span>{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="text-sm text-surface-600 dark:text-surface-400 mt-0.5">
|
|
||||||
{% if checkin.locationText %}{{ checkin.locationText }}{% if checkin.postalCode %}, {{ checkin.postalCode }}{% endif %}{% elif checkin.postalCode %}{{ checkin.postalCode }}{% endif %}
|
|
||||||
{% if checkin.published %}
|
|
||||||
<span class="mx-1">·</span><time datetime="{{ checkin.published }}">{{ checkin.published | date("MMM d, yyyy") }}</time>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% else %}
|
|
||||||
<p class="text-surface-600 dark:text-surface-400">No past check-ins found.</p>
|
|
||||||
{% endif %}
|
|
||||||
<p class="mt-2 text-sm">
|
|
||||||
<a href="/where/" class="text-accent-700 hover:underline">Back to newest check-in →</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
@@ -1,113 +1,100 @@
|
|||||||
---
|
---
|
||||||
layout: layouts/base.njk
|
layout: layouts/been.njk
|
||||||
title: Where
|
title: Where
|
||||||
permalink: /where/
|
permalink: /where/
|
||||||
|
description: All past check-ins captured by this site via Micropub.
|
||||||
withSidebar: true
|
withSidebar: true
|
||||||
|
leafletMap: true
|
||||||
---
|
---
|
||||||
{% set checkins = whereCheckins.checkins or [] %}
|
{% set checkins = whereCheckins.checkins or [] %}
|
||||||
|
|
||||||
<div class="where-page">
|
<div class="been-page">
|
||||||
<header class="mb-6 sm:mb-8">
|
<header class="mb-6 sm:mb-8">
|
||||||
<h1 class="text-2xl sm:text-3xl md:text-4xl font-bold text-surface-900 dark:text-surface-100 mb-2">Where</h1>
|
<h1 class="text-2xl sm:text-3xl md:text-4xl font-bold text-surface-900 dark:text-surface-100 mb-2">Where</h1>
|
||||||
<p class="p-summary text-lg text-surface-600 dark:text-surface-400">
|
<p class="p-summary text-lg text-surface-600 dark:text-surface-400">
|
||||||
Recent check-ins captured by this site via Micropub.
|
All past check-ins captured by this site via Micropub.
|
||||||
</p>
|
</p>
|
||||||
<div class="prose prose-surface dark:prose-invert max-w-none text-surface-600 dark:text-surface-400 mb-2">
|
|
||||||
<p>
|
|
||||||
A <a href="https://niqwithq.com/posts/where-are-you">where page</a> is a place on the web to let others know about your current location. Thanks to <a href="https://aaronparecki.com/">Aaron Parecki</a> for ownyourswarm.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{% if checkins.length %}
|
{% if checkins.length %}
|
||||||
<section class="space-y-4" aria-label="Recent check-ins">
|
{% set newest = checkins[0] %}
|
||||||
{% set checkin = checkins[0] %}
|
<div class="mb-6 p-4 rounded-lg bg-emerald-50 dark:bg-emerald-900/20 border border-emerald-200 dark:border-emerald-800">
|
||||||
<article class="p-4 sm:p-5 bg-surface-50 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700 shadow-sm">
|
<p class="text-sm font-medium text-emerald-800 dark:text-emerald-300">
|
||||||
<div class="flex items-start gap-3 sm:gap-4">
|
I'm currently in <strong>{% if newest.venueWebsiteUrl %}<a href="{{ newest.venueWebsiteUrl }}" target="_blank" rel="noopener" class="underline hover:opacity-80">{{ newest.name }}</a>{% else %}{{ newest.name }}{% endif %}</strong>{% if newest.locationText %}, {{ newest.locationText }}{% endif %}
|
||||||
<div class="w-10 h-10 rounded-full bg-emerald-100 dark:bg-emerald-900/40 flex items-center justify-center flex-shrink-0 mt-0.5">
|
|
||||||
<svg class="w-5 h-5 text-emerald-700 dark:text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a2 2 0 01-2.828 0l-4.243-4.243a8 8 0 1111.314 0z"/>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-1 min-w-0">
|
|
||||||
<h2 class="text-base sm:text-lg font-semibold text-surface-900 dark:text-surface-100">
|
|
||||||
{{ checkin.name }}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{% if checkin.locationText or checkin.postalCode %}
|
|
||||||
<p class="text-sm text-surface-600 dark:text-surface-400 mt-1">
|
|
||||||
{{ checkin.locationText }}{% if checkin.locationText and checkin.postalCode %}, {% endif %}{{ checkin.postalCode }}
|
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="text-xs text-surface-600 dark:text-surface-400 mt-2 flex flex-wrap items-center gap-2">
|
|
||||||
{% if checkin.published %}
|
|
||||||
<time class="font-mono" datetime="{{ checkin.published }}">{{ checkin.published | date("MMM d, yyyy") }}</time>
|
|
||||||
{% endif %}
|
|
||||||
{% if checkin.coordinatesText %}
|
|
||||||
<span class="px-2 py-0.5 rounded-full bg-surface-100 dark:bg-surface-700">{{ checkin.coordinatesText }}</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if checkin.isPrivate %}
|
|
||||||
<span class="px-2 py-0.5 rounded-full bg-amber-100 dark:bg-amber-900/40 text-amber-800 dark:text-amber-300">Private</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if checkin.checkedInBy and (checkin.checkedInBy.name or checkin.checkedInBy.url) %}
|
|
||||||
<span class="px-2 py-0.5 rounded-full bg-surface-100 dark:bg-surface-700">
|
|
||||||
Checked in by {% if checkin.checkedInBy.url %}<a href="{{ checkin.checkedInBy.url }}" class="hover:text-emerald-600 dark:hover:text-emerald-400" target="_blank" rel="noopener">{{ checkin.checkedInBy.name or checkin.checkedInBy.url }}</a>{% else %}{{ checkin.checkedInBy.name }}{% endif %}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if checkin.tags and checkin.tags.length %}
|
|
||||||
<div class="mt-3 flex flex-wrap gap-2 text-xs">
|
|
||||||
{% for tag in checkin.tags | head(8) %}
|
|
||||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full bg-indigo-100 dark:bg-indigo-900/30 text-indigo-800 dark:text-indigo-300">#{{ tag }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if checkin.taggedPeople and checkin.taggedPeople.length %}
|
{% set mapPoints = [] %}
|
||||||
<div class="mt-2 flex flex-wrap gap-2 text-xs">
|
{% for c in checkins %}
|
||||||
{% for person in checkin.taggedPeople | head(6) %}
|
{% if c.latitude and c.longitude %}
|
||||||
{% if person.url %}
|
{% set mapPoints = (mapPoints.push({name: c.name, lat: c.latitude, lng: c.longitude, url: c.venueWebsiteUrl}), mapPoints) %}
|
||||||
<a href="{{ person.url }}" class="inline-flex items-center px-2.5 py-1 rounded-full bg-surface-100 dark:bg-surface-700 text-surface-700 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-600" target="_blank" rel="noopener">{{ person.name or person.url }}</a>
|
|
||||||
{% else %}
|
|
||||||
<span class="inline-flex items-center px-2.5 py-1 rounded-full bg-surface-100 dark:bg-surface-700 text-surface-700 dark:text-surface-300">{{ person.name }}</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
|
||||||
|
{% if mapPoints.length %}
|
||||||
|
<div id="checkin-map" class="mb-8 rounded-lg overflow-hidden border border-surface-200 dark:border-surface-700" style="height: 420px;" aria-label="Map of past check-ins"></div>
|
||||||
|
<script>
|
||||||
|
var checkinPoints = {{ mapPoints | dump | safe }};
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
L.Icon.Default.mergeOptions({
|
||||||
|
iconUrl: '/css/images/marker-icon.png',
|
||||||
|
iconRetinaUrl: '/css/images/marker-icon-2x.png',
|
||||||
|
shadowUrl: '/css/images/marker-shadow.png'
|
||||||
|
});
|
||||||
|
var map = L.map('checkin-map');
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||||
|
maxZoom: 19
|
||||||
|
}).addTo(map);
|
||||||
|
function escHtml(s) {
|
||||||
|
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
|
}
|
||||||
|
var cluster = L.markerClusterGroup({ maxClusterRadius: 40 });
|
||||||
|
var markers = checkinPoints.map(function(p) {
|
||||||
|
var safeName = escHtml(p.name);
|
||||||
|
var safeUrl = (p.url && p.url.indexOf('https://') === 0) ? p.url : '';
|
||||||
|
var popup = safeUrl
|
||||||
|
? '<a href="' + safeUrl + '" target="_blank" rel="noopener">' + safeName + '</a>'
|
||||||
|
: safeName;
|
||||||
|
return L.marker([p.lat, p.lng]).bindPopup(popup);
|
||||||
|
});
|
||||||
|
markers.forEach(function(m) { cluster.addLayer(m); });
|
||||||
|
map.addLayer(cluster);
|
||||||
|
map.fitBounds(cluster.getBounds().pad(0.2), { maxZoom: 13 });
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="mt-3 flex flex-wrap gap-2 text-xs">
|
{% if checkins.length %}
|
||||||
{% if checkin.mapUrl %}
|
{% set grouped = checkins | groupby("name") %}
|
||||||
<a href="{{ checkin.mapUrl }}" class="inline-flex items-center px-2.5 py-1 rounded-full bg-emerald-100/70 dark:bg-emerald-900/30 text-emerald-800 dark:text-emerald-300 hover:bg-emerald-200/70 dark:hover:bg-emerald-900/50" target="_blank" rel="noopener">Map</a>
|
<ul class="divide-y divide-surface-200 dark:divide-surface-700" aria-label="Past check-ins">
|
||||||
{% endif %}
|
{% for groupName, groupItems in grouped %}
|
||||||
|
{% set checkin = groupItems[0] %}
|
||||||
|
<li class="py-3">
|
||||||
|
<div class="font-medium text-surface-900 dark:text-surface-100">
|
||||||
{% if checkin.venueWebsiteUrl %}
|
{% if checkin.venueWebsiteUrl %}
|
||||||
<a href="{{ checkin.venueWebsiteUrl }}" class="inline-flex items-center px-2.5 py-1 rounded-full bg-surface-100 dark:bg-surface-700 text-surface-700 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-600" target="_blank" rel="noopener">Website</a>
|
<a href="{{ checkin.venueWebsiteUrl }}" target="_blank" rel="noopener" class="hover:underline">{{ checkin.name }}</a>
|
||||||
{% endif %}
|
|
||||||
{% if checkin.venueSocialUrl %}
|
|
||||||
<a href="{{ checkin.venueSocialUrl }}" class="inline-flex items-center px-2.5 py-1 rounded-full bg-surface-100 dark:bg-surface-700 text-surface-700 dark:text-surface-300 hover:bg-surface-200 dark:hover:bg-surface-600" target="_blank" rel="noopener">Social</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if checkin.photos and checkin.photos.length %}
|
|
||||||
<div class="mt-3 flex flex-wrap gap-2">
|
|
||||||
{% for photo in checkin.photos | head(3) %}
|
|
||||||
<a href="{{ photo }}" target="_blank" rel="noopener" class="block">
|
|
||||||
<img src="{{ photo }}" alt="Check-in photo" class="w-24 h-24 object-cover rounded-lg border border-surface-200 dark:border-surface-700" loading="lazy" />
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-surface-600 dark:text-surface-400">No check-ins found.</p>
|
{{ checkin.name }}
|
||||||
|
{% endif %}
|
||||||
|
{% if groupItems | length > 1 %}
|
||||||
|
<span class="ml-2 text-xs font-normal text-surface-500 dark:text-surface-400">{{ groupItems | length }}×</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if checkin.isPrivate %}<span class="ml-2 text-xs text-amber-700 dark:text-amber-400">(private)</span>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-surface-600 dark:text-surface-400 mt-0.5">
|
||||||
|
{% if checkin.locationText %}{{ checkin.locationText }}{% if checkin.postalCode %}, {{ checkin.postalCode }}{% endif %}{% elif checkin.postalCode %}{{ checkin.postalCode }}{% endif %}
|
||||||
|
{% if checkin.published %}
|
||||||
|
<span class="mx-1">·</span><time datetime="{{ checkin.published }}">{{ checkin.published | date("MMM d, yyyy") }}</time>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-surface-600 dark:text-surface-400">No past check-ins found.</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="mt-2 text-sm">
|
|
||||||
<a href="/been/" class="text-accent-700 hover:underline">See all past check-ins →</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user