feat(post-form): U3 — auth-aware feed collection; drafts owner-only

Owner-aware draft visibility (R5, KTD7/KTD8) on trip.html.twig and home.html.twig
(active-trip branch):
- Compute owner_can_edit = authenticated AND username == site.owner_username AND
  (this is the active trip). The super-admin tester authenticates too, so the gate
  is owner identity, not mere login.
- The feed list (all_items) uses an owner-aware journal collection: owner sees
  drafts, everyone else (and every non-active-trip view) sees published only.
- journal_entries stays published-only — it feeds stats/counts. map_entries now
  filters on item.page.published, so drafts get a feed card but no marker and no
  stat contribution.
- Thread owner_can_edit into trip-feed-col (defaults false) for the U4 controls.
Between-trips home grid stays published-only. Stories untouched (journal drafts only).

Verified on the container: draft entry with coords shows in the owner's feed but
not the map or count; absent entirely for anonymous (V4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H1FrCYNq6RXdGYbn5PFrhM
This commit is contained in:
2026-07-04 23:44:00 +02:00
co-authored by Claude Opus 4.8
parent 4e91492f65
commit a2d4f81bfe
3 changed files with 35 additions and 6 deletions
+13 -3
View File
@@ -14,11 +14,19 @@
{% set dailies_page = grav.pages.find(trip_route ~ '/dailies') %} {% set dailies_page = grav.pages.find(trip_route ~ '/dailies') %}
{% set stories_page = grav.pages.find(trip_route ~ '/stories') %} {% set stories_page = grav.pages.find(trip_route ~ '/stories') %}
{# published-only — feeds the map, stats and counts (drafts excluded, R5) #}
{% set journal_entries = dailies_page ? dailies_page.children.published() : [] %} {% set journal_entries = dailies_page ? dailies_page.children.published() : [] %}
{% set story_entries = stories_page ? stories_page.children.published() : [] %} {% set story_entries = stories_page ? stories_page.children.published() : [] %}
{# This branch IS the active trip, so the owner gate is just owner identity
(KTD8). The super-admin tester authenticates too, so gate on owner_username. #}
{% set owner_can_edit = grav.user.authenticated
and grav.user.username == grav.config.site.owner_username %}
{# Owner-aware feed list: owner sees drafts; everyone else published only #}
{% set journal_feed = (owner_can_edit and dailies_page) ? dailies_page.children : journal_entries %}
{% set all_items = [] %} {% set all_items = [] %}
{% for e in journal_entries %} {% for e in journal_feed %}
{% set all_items = all_items|merge([{'type': 'journal', 'page': e, 'date': e.header.date}]) %} {% set all_items = all_items|merge([{'type': 'journal', 'page': e, 'date': e.header.date}]) %}
{% endfor %} {% endfor %}
{% for s in story_entries %} {% for s in story_entries %}
@@ -38,7 +46,8 @@
{% set map_entries = [] %} {% set map_entries = [] %}
{% for item in all_items %} {% for item in all_items %}
{% if item.type == 'journal' and item.page.header.lat is not empty and item.page.header.lng is not empty %} {# drafts render as a feed card only — never a map marker (R5) #}
{% if item.type == 'journal' and item.page.published and item.page.header.lat is not empty and item.page.header.lng is not empty %}
{% set map_entries = map_entries|merge([{ {% set map_entries = map_entries|merge([{
'lat': item.page.header.lat|number_format(6, '.', ''), 'lat': item.page.header.lat|number_format(6, '.', ''),
'lng': item.page.header.lng|number_format(6, '.', ''), 'lng': item.page.header.lng|number_format(6, '.', ''),
@@ -87,7 +96,8 @@
has_gpx: home_gpx_urls|length > 0, has_gpx: home_gpx_urls|length > 0,
gpx_urls: home_gpx_urls, gpx_urls: home_gpx_urls,
gps_points: gps_points, gps_points: gps_points,
show_sort: false show_sort: false,
owner_can_edit: owner_can_edit
} only %} } only %}
{% endif %} {% endif %}
</div> </div>
@@ -1,5 +1,9 @@
{% import 'macros/stats.html.twig' as stats_m %} {% import 'macros/stats.html.twig' as stats_m %}
{% import 'macros/cycling.html.twig' as cycling_m %} {% import 'macros/cycling.html.twig' as cycling_m %}
{# owner_can_edit gates the Draft badge + Edit/Delete controls on each card
(threaded into entry-journal below). Default false so any caller that doesn't
pass it renders a read-only feed. #}
{% set owner_can_edit = owner_can_edit ?? false %}
<div class="home-feed-col"> <div class="home-feed-col">
<div class="home-trip-header"> <div class="home-trip-header">
<h1 class="home-trip-name">{{ trip_page.title }}</h1> <h1 class="home-trip-name">{{ trip_page.title }}</h1>
+18 -3
View File
@@ -9,11 +9,24 @@
{% endblock %} {% endblock %}
{% set dailies_page = grav.pages.find(page.route ~ '/dailies') %} {% set dailies_page = grav.pages.find(page.route ~ '/dailies') %}
{% set stories_page = grav.pages.find(page.route ~ '/stories') %} {% set stories_page = grav.pages.find(page.route ~ '/stories') %}
{# journal_entries stays published-only — it feeds the map, stats and counts,
which must never include drafts (R5). #}
{% set journal_entries = dailies_page ? dailies_page.children.published() : [] %} {% set journal_entries = dailies_page ? dailies_page.children.published() : [] %}
{% set story_entries = stories_page ? stories_page.children.published() : [] %} {% set story_entries = stories_page ? stories_page.children.published() : [] %}
{# Owner gate (KTD8): the site owner (not merely any login — the super-admin
tester also authenticates) viewing the ACTIVE trip. Drives draft visibility
in the feed and the Edit/Delete controls (threaded to the card partial). #}
{% set active_trip_slug = (grav.config.site.active_trip|default(''))|split('/')|last %}
{% set owner_can_edit = grav.user.authenticated
and grav.user.username == grav.config.site.owner_username
and page.slug == active_trip_slug %}
{# Feed list is owner-aware: the owner sees drafts (unpublished) too; everyone
else (and every non-active-trip view) sees published only (R5, KTD7). #}
{% set journal_feed = (owner_can_edit and dailies_page) ? dailies_page.children : journal_entries %}
{% set all_items = [] %} {% set all_items = [] %}
{% for e in journal_entries %} {% for e in journal_feed %}
{% set all_items = all_items|merge([{'type': 'journal', 'page': e, 'date': e.header.date}]) %} {% set all_items = all_items|merge([{'type': 'journal', 'page': e, 'date': e.header.date}]) %}
{% endfor %} {% endfor %}
{% for s in story_entries %} {% for s in story_entries %}
@@ -41,7 +54,8 @@
{% set map_entries = [] %} {% set map_entries = [] %}
{% for item in all_items %} {% for item in all_items %}
{% if item.page.header.lat is not empty and item.page.header.lng is not empty %} {# drafts render as a feed card only — never a map marker (R5) #}
{% if item.page.published and item.page.header.lat is not empty and item.page.header.lng is not empty %}
{% set map_entries = map_entries|merge([{ {% set map_entries = map_entries|merge([{
'type': item.type, 'type': item.type,
'lat': item.page.header.lat|number_format(6, '.', ''), 'lat': item.page.header.lat|number_format(6, '.', ''),
@@ -78,7 +92,8 @@
has_gpx: has_gpx, has_gpx: has_gpx,
gpx_urls: gpx_urls, gpx_urls: gpx_urls,
gps_points: gps_points, gps_points: gps_points,
show_sort: true show_sort: true,
owner_can_edit: owner_can_edit
} only %} } only %}
</div> </div>