Replace container nav/card widgets with fragment composition (Phase 4)
Templates now consume container_nav_html and card_widgets_html from fragment fetches instead of iterating widgets.container_nav / container_cards. OOB nav template renders entry/calendar links directly from data. Calendar and market widget registrations removed (account widgets remain). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -69,10 +69,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{# Widget-driven card decorations #}
|
{# Card decorations — via fragments #}
|
||||||
{% for w in widgets.container_cards %}
|
{% if card_widgets_html %}
|
||||||
{% include w.template with context %}
|
{% set _card_html = card_widgets_html.get(post.id|string, "") %}
|
||||||
{% endfor %}
|
{% if _card_html %}{{ _card_html | safe }}{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% include '_types/blog/_card/at_bar.html' %}
|
{% include '_types/blog/_card/at_bar.html' %}
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,9 @@
|
|||||||
{% endcall %}
|
{% endcall %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Container nav widgets (market links, etc.) #}
|
{# Container nav (market links, etc.) — via fragments #}
|
||||||
{% if container_nav_widgets %}
|
{% if container_nav_html %}
|
||||||
{% for wdata in container_nav_widgets %}
|
{{ container_nav_html | safe }}
|
||||||
{% with ctx=wdata.ctx %}
|
|
||||||
{% include wdata.widget.template with context %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Admin link #}
|
{# Admin link #}
|
||||||
|
|||||||
@@ -22,12 +22,9 @@
|
|||||||
{% endcall %}
|
{% endcall %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if container_nav_widgets %}
|
{# Container nav (market links, etc.) — via fragments #}
|
||||||
{% for wdata in container_nav_widgets %}
|
{% if container_nav_html %}
|
||||||
{% with ctx=wdata.ctx %}
|
{{ container_nav_html | safe }}
|
||||||
{% include wdata.widget.template with context %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Admin link #}
|
{# Admin link #}
|
||||||
|
|||||||
@@ -1,8 +1,42 @@
|
|||||||
{% import 'macros/links.html' as links %}
|
{% import 'macros/links.html' as links %}
|
||||||
{# Widget-driven container nav — entries, calendars, markets #}
|
{# Container nav — entries, calendars, markets (via fragments) #}
|
||||||
{% if container_nav_widgets %}
|
{% if container_nav_html %}
|
||||||
<div class="flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
|
<div class="flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
|
||||||
id="entries-calendars-nav-wrapper">
|
id="entries-calendars-nav-wrapper">
|
||||||
{% include '_types/post/admin/_nav_entries.html' %}
|
{# Left scroll arrow - desktop only #}
|
||||||
|
<button
|
||||||
|
class="entries-nav-arrow hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
|
||||||
|
aria-label="Scroll left"
|
||||||
|
_="on click
|
||||||
|
set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft - 200">
|
||||||
|
<i class="fa fa-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
<div id="associated-items-container"
|
||||||
|
class="overflow-y-auto sm:overflow-x-auto sm:overflow-y-visible scrollbar-hide max-h-[50vh] sm:max-h-none"
|
||||||
|
style="scroll-behavior: smooth;"
|
||||||
|
_="on load or scroll
|
||||||
|
if window.innerWidth >= 640 and my.scrollWidth > my.clientWidth
|
||||||
|
remove .hidden from .entries-nav-arrow
|
||||||
|
add .flex to .entries-nav-arrow
|
||||||
|
else
|
||||||
|
add .hidden to .entries-nav-arrow
|
||||||
|
remove .flex from .entries-nav-arrow
|
||||||
|
end">
|
||||||
|
<div class="flex flex-col sm:flex-row gap-1">
|
||||||
|
{{ container_nav_html | safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
.scrollbar-hide::-webkit-scrollbar { display: none; }
|
||||||
|
.scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
|
||||||
|
</style>
|
||||||
|
{# Right scroll arrow - desktop only #}
|
||||||
|
<button
|
||||||
|
class="entries-nav-arrow hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
|
||||||
|
aria-label="Scroll right"
|
||||||
|
_="on click
|
||||||
|
set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft + 200">
|
||||||
|
<i class="fa fa-chevron-right"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -6,7 +6,73 @@
|
|||||||
<div class="flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
|
<div class="flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
|
||||||
id="entries-calendars-nav-wrapper"
|
id="entries-calendars-nav-wrapper"
|
||||||
hx-swap-oob="true">
|
hx-swap-oob="true">
|
||||||
{% include '_types/post/admin/_nav_entries.html' %}
|
{# Left scroll arrow - desktop only #}
|
||||||
|
<button
|
||||||
|
class="entries-nav-arrow hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
|
||||||
|
aria-label="Scroll left"
|
||||||
|
_="on click
|
||||||
|
set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft - 200">
|
||||||
|
<i class="fa fa-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div id="associated-items-container"
|
||||||
|
class="overflow-y-auto sm:overflow-x-auto sm:overflow-y-visible scrollbar-hide max-h-[50vh] sm:max-h-none"
|
||||||
|
style="scroll-behavior: smooth;"
|
||||||
|
_="on load or scroll
|
||||||
|
if window.innerWidth >= 640 and my.scrollWidth > my.clientWidth
|
||||||
|
remove .hidden from .entries-nav-arrow
|
||||||
|
add .flex to .entries-nav-arrow
|
||||||
|
else
|
||||||
|
add .hidden to .entries-nav-arrow
|
||||||
|
remove .flex from .entries-nav-arrow
|
||||||
|
end">
|
||||||
|
<div class="flex flex-col sm:flex-row gap-1">
|
||||||
|
{# Calendar entries #}
|
||||||
|
{% if associated_entries and associated_entries.entries %}
|
||||||
|
{% for entry in associated_entries.entries %}
|
||||||
|
{% set _entry_path = '/' + post.slug + '/calendars/' + entry.calendar_slug + '/' + entry.start_at.year|string + '/' + entry.start_at.month|string + '/' + entry.start_at.day|string + '/entries/' + entry.id|string + '/' %}
|
||||||
|
<a
|
||||||
|
href="{{ events_url(_entry_path) }}"
|
||||||
|
class="{{styles.nav_button_less_pad}}">
|
||||||
|
<div class="w-8 h-8 rounded bg-stone-200 flex-shrink-0"></div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<div class="font-medium truncate">{{ entry.name }}</div>
|
||||||
|
<div class="text-xs text-stone-600 truncate">
|
||||||
|
{{ entry.start_at.strftime('%b %d, %Y at %H:%M') }}
|
||||||
|
{% if entry.end_at %} – {{ entry.end_at.strftime('%H:%M') }}{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{# Calendar links #}
|
||||||
|
{% if calendars %}
|
||||||
|
{% for calendar in calendars %}
|
||||||
|
{% set local_href=events_url('/' + post.slug + '/calendars/' + calendar.slug + '/') %}
|
||||||
|
<a
|
||||||
|
href="{{ local_href }}"
|
||||||
|
class="{{styles.nav_button_less_pad}}">
|
||||||
|
<i class="fa fa-calendar" aria-hidden="true"></i>
|
||||||
|
<div>{{calendar.name}}</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.scrollbar-hide::-webkit-scrollbar { display: none; }
|
||||||
|
.scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{# Right scroll arrow - desktop only #}
|
||||||
|
<button
|
||||||
|
class="entries-nav-arrow hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
|
||||||
|
aria-label="Scroll right"
|
||||||
|
_="on click
|
||||||
|
set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft + 200">
|
||||||
|
<i class="fa fa-chevron-right"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{# Empty placeholder to remove nav items when all are disassociated/deleted #}
|
{# Empty placeholder to remove nav items when all are disassociated/deleted #}
|
||||||
|
|||||||
@@ -1,44 +1,15 @@
|
|||||||
"""Calendar-domain widgets: entries nav, calendar links, card entries, account pages."""
|
"""Calendar-domain widgets: account pages (tickets & bookings).
|
||||||
|
|
||||||
|
Container nav and card widgets have been replaced by fragments
|
||||||
|
(events app serves them at /internal/fragments/).
|
||||||
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from shared.contracts.widgets import NavWidget, CardWidget, AccountPageWidget
|
from shared.contracts.widgets import AccountPageWidget
|
||||||
from shared.services.widget_registry import widgets
|
from shared.services.widget_registry import widgets
|
||||||
from shared.services.registry import services
|
from shared.services.registry import services
|
||||||
|
|
||||||
|
|
||||||
# -- container_nav: associated entries ----------------------------------------
|
|
||||||
|
|
||||||
async def _nav_entries_context(
|
|
||||||
session, *, container_type, container_id, post_slug, page=1, **kw,
|
|
||||||
):
|
|
||||||
entries, has_more = await services.calendar.associated_entries(
|
|
||||||
session, container_type, container_id, page,
|
|
||||||
)
|
|
||||||
return {
|
|
||||||
"entries": entries,
|
|
||||||
"has_more": has_more,
|
|
||||||
"page": page,
|
|
||||||
"post_slug": post_slug,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# -- container_nav: calendar links -------------------------------------------
|
|
||||||
|
|
||||||
async def _nav_calendars_context(
|
|
||||||
session, *, container_type, container_id, post_slug, **kw,
|
|
||||||
):
|
|
||||||
calendars = await services.calendar.calendars_for_container(
|
|
||||||
session, container_type, container_id,
|
|
||||||
)
|
|
||||||
return {"calendars": calendars, "post_slug": post_slug}
|
|
||||||
|
|
||||||
|
|
||||||
# -- container_card: confirmed entries for post listings ----------------------
|
|
||||||
|
|
||||||
async def _card_entries_batch(session, post_ids):
|
|
||||||
return await services.calendar.confirmed_entries_for_posts(session, post_ids)
|
|
||||||
|
|
||||||
|
|
||||||
# -- account pages: tickets & bookings ---------------------------------------
|
# -- account pages: tickets & bookings ---------------------------------------
|
||||||
|
|
||||||
async def _tickets_context(session, *, user_id, **kw):
|
async def _tickets_context(session, *, user_id, **kw):
|
||||||
@@ -54,25 +25,6 @@ async def _bookings_context(session, *, user_id, **kw):
|
|||||||
# -- registration entry point ------------------------------------------------
|
# -- registration entry point ------------------------------------------------
|
||||||
|
|
||||||
def register_calendar_widgets() -> None:
|
def register_calendar_widgets() -> None:
|
||||||
widgets.add_container_nav(NavWidget(
|
|
||||||
domain="calendar",
|
|
||||||
order=10,
|
|
||||||
context_fn=_nav_entries_context,
|
|
||||||
template="_widgets/container_nav/calendar_entries.html",
|
|
||||||
))
|
|
||||||
widgets.add_container_nav(NavWidget(
|
|
||||||
domain="calendar_links",
|
|
||||||
order=20,
|
|
||||||
context_fn=_nav_calendars_context,
|
|
||||||
template="_widgets/container_nav/calendar_links.html",
|
|
||||||
))
|
|
||||||
widgets.add_container_card(CardWidget(
|
|
||||||
domain="calendar",
|
|
||||||
order=10,
|
|
||||||
batch_fn=_card_entries_batch,
|
|
||||||
context_key="associated_entries",
|
|
||||||
template="_widgets/container_card/calendar_entries.html",
|
|
||||||
))
|
|
||||||
widgets.add_account_page(AccountPageWidget(
|
widgets.add_account_page(AccountPageWidget(
|
||||||
domain="calendar",
|
domain="calendar",
|
||||||
slug="tickets",
|
slug="tickets",
|
||||||
|
|||||||
@@ -1,24 +1,10 @@
|
|||||||
"""Market-domain widgets: marketplace links on container pages."""
|
"""Market-domain widgets.
|
||||||
|
|
||||||
|
Container nav widgets have been replaced by fragments
|
||||||
|
(market app serves them at /internal/fragments/).
|
||||||
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from shared.contracts.widgets import NavWidget
|
|
||||||
from shared.services.widget_registry import widgets
|
|
||||||
from shared.services.registry import services
|
|
||||||
|
|
||||||
|
|
||||||
async def _nav_markets_context(
|
|
||||||
session, *, container_type, container_id, post_slug, **kw,
|
|
||||||
):
|
|
||||||
markets = await services.market.marketplaces_for_container(
|
|
||||||
session, container_type, container_id,
|
|
||||||
)
|
|
||||||
return {"markets": markets, "post_slug": post_slug}
|
|
||||||
|
|
||||||
|
|
||||||
def register_market_widgets() -> None:
|
def register_market_widgets() -> None:
|
||||||
widgets.add_container_nav(NavWidget(
|
pass
|
||||||
domain="market",
|
|
||||||
order=30,
|
|
||||||
context_fn=_nav_markets_context,
|
|
||||||
template="_widgets/container_nav/market_links.html",
|
|
||||||
))
|
|
||||||
|
|||||||
Reference in New Issue
Block a user