diff --git a/bp/calendar_entry/routes.py b/bp/calendar_entry/routes.py index 8aeda90..ab46095 100644 --- a/bp/calendar_entry/routes.py +++ b/bp/calendar_entry/routes.py @@ -27,7 +27,7 @@ from datetime import datetime, timezone import math import logging -from shared.services.widget_registry import widgets +from shared.infrastructure.fragments import fetch_fragment from ..ticket_types.routes import register as register_ticket_types @@ -222,25 +222,17 @@ def register(): ticket_type_id=tt.id, ) - # Widget-driven container nav (market links, etc.) - container_nav_loaded = [] + # Fetch container nav from market (skip calendar — we're on a calendar page) + container_nav_html = "" post_data = getattr(g, "post_data", None) if post_data: post_id = post_data["post"]["id"] post_slug = post_data["post"]["slug"] - for w in widgets.container_nav: - if w.domain.startswith("calendar"): - continue # skip — we're already on a calendar page - try: - wctx = await w.context_fn( - g.s, container_type="page", container_id=post_id, - post_slug=post_slug, - ) - has_data = any(v for v in wctx.values() if isinstance(v, list) and v) - if has_data: - container_nav_loaded.append({"widget": w, "ctx": wctx}) - except Exception: - pass + container_nav_html = await fetch_fragment("market", "container-nav", params={ + "container_type": "page", + "container_id": str(post_id), + "post_slug": post_slug, + }) return { "entry": calendar_entry, @@ -249,7 +241,7 @@ def register(): "ticket_sold_count": ticket_sold_count, "user_ticket_count": user_ticket_count, "user_ticket_counts_by_type": user_ticket_counts_by_type, - "container_nav_widgets": container_nav_loaded, + "container_nav_html": container_nav_html, } @bp.get("/") @require_admin diff --git a/bp/day/routes.py b/bp/day/routes.py index f7fc74a..7fbe550 100644 --- a/bp/day/routes.py +++ b/bp/day/routes.py @@ -11,7 +11,7 @@ from bp.calendar_entries.routes import register as register_calendar_entries from .admin.routes import register as register_admin from shared.browser.app.redis_cacher import cache_page -from shared.services.widget_registry import widgets +from shared.infrastructure.fragments import fetch_fragment from models.calendars import CalendarSlot # add this import @@ -78,25 +78,17 @@ def register(): result = await g.s.execute(stmt) day_slots = list(result.scalars()) - # Widget-driven container nav (market links, etc.) - container_nav_loaded = [] + # Fetch container nav from market (skip calendar — we're on a calendar page) + container_nav_html = "" post_data = getattr(g, "post_data", None) if post_data: post_id = post_data["post"]["id"] post_slug = post_data["post"]["slug"] - for w in widgets.container_nav: - if w.domain.startswith("calendar"): - continue # skip — we're already on a calendar page - try: - wctx = await w.context_fn( - g.s, container_type="page", container_id=post_id, - post_slug=post_slug, - ) - has_data = any(v for v in wctx.values() if isinstance(v, list) and v) - if has_data: - container_nav_loaded.append({"widget": w, "ctx": wctx}) - except Exception: - pass + container_nav_html = await fetch_fragment("market", "container-nav", params={ + "container_type": "page", + "container_id": str(post_id), + "post_slug": post_slug, + }) return { "qsession": qsession, @@ -108,7 +100,7 @@ def register(): "user_entries": visible.user_entries, "confirmed_entries": visible.confirmed_entries, "day_slots": day_slots, - "container_nav_widgets": container_nav_loaded, + "container_nav_html": container_nav_html, } @@ -142,23 +134,21 @@ def register(): @bp.get("/w//") async def widget_paginate(widget_domain: str, **kwargs): - """Generic paginated widget endpoint for infinite scroll.""" + """Proxies paginated widget requests to the appropriate fragment provider.""" page = int(request.args.get("page", 1)) post_data = getattr(g, "post_data", None) if not post_data: abort(404) post_id = post_data["post"]["id"] post_slug = post_data["post"]["slug"] - for w in widgets.container_nav: - if w.domain == widget_domain: - ctx = await w.context_fn( - g.s, container_type="page", container_id=post_id, - post_slug=post_slug, page=page, - ) - html = await render_template( - w.template, ctx=ctx, post=post_data["post"], - ) - return await make_response(html) + + if widget_domain == "market": + html = await fetch_fragment("market", "container-nav", params={ + "container_type": "page", + "container_id": str(post_id), + "post_slug": post_slug, + }) + return await make_response(html or "") abort(404) return bp diff --git a/bp/fragments/routes.py b/bp/fragments/routes.py index ef4046e..243b245 100644 --- a/bp/fragments/routes.py +++ b/bp/fragments/routes.py @@ -6,9 +6,10 @@ by other coop apps via the fragment client. from __future__ import annotations -from quart import Blueprint, Response, request +from quart import Blueprint, Response, g, render_template, request from shared.infrastructure.fragments import FRAGMENT_HEADER +from shared.services.registry import services def register(): @@ -29,6 +30,70 @@ def register(): html = await handler() return Response(html, status=200, content_type="text/html") + # --- container-nav fragment: calendar entries + calendar links ----------- + + async def _container_nav_handler(): + container_type = request.args.get("container_type", "page") + container_id = int(request.args.get("container_id", 0)) + post_slug = request.args.get("post_slug", "") + paginate_url_base = request.args.get("paginate_url", "") + page = int(request.args.get("page", 1)) + exclude = request.args.get("exclude", "") + excludes = [e.strip() for e in exclude.split(",") if e.strip()] + + html_parts = [] + + # Calendar entries nav + if not any(e.startswith("calendar") for e in excludes): + entries, has_more = await services.calendar.associated_entries( + g.s, container_type, container_id, page, + ) + if entries: + html_parts.append(await render_template( + "fragments/container_nav_entries.html", + entries=entries, has_more=has_more, + page=page, post_slug=post_slug, + paginate_url_base=paginate_url_base, + )) + + # Calendar links nav + if not any(e.startswith("calendar") for e in excludes): + calendars = await services.calendar.calendars_for_container( + g.s, container_type, container_id, + ) + if calendars: + html_parts.append(await render_template( + "fragments/container_nav_calendars.html", + calendars=calendars, post_slug=post_slug, + )) + + return "\n".join(html_parts) + + _handlers["container-nav"] = _container_nav_handler + + # --- container-cards fragment: entries for blog listing cards ------------ + + async def _container_cards_handler(): + post_ids_raw = request.args.get("post_ids", "") + post_slugs_raw = request.args.get("post_slugs", "") + post_ids = [int(x) for x in post_ids_raw.split(",") if x.strip()] + post_slugs = [x.strip() for x in post_slugs_raw.split(",") if x.strip()] + if not post_ids: + return "" + + # Build post_id -> slug mapping + slug_map = {} + for i, pid in enumerate(post_ids): + slug_map[pid] = post_slugs[i] if i < len(post_slugs) else "" + + batch = await services.calendar.confirmed_entries_for_posts(g.s, post_ids) + return await render_template( + "fragments/container_cards_entries.html", + batch=batch, post_ids=post_ids, slug_map=slug_map, + ) + + _handlers["container-cards"] = _container_cards_handler + bp._fragment_handlers = _handlers return bp diff --git a/shared b/shared index ab674ad..d2e07e0 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit ab674ada31f09edb1a4333e520d482e13e0c90da +Subproject commit d2e07e047eb161dc7cffbc215b728d5556dae82b diff --git a/templates/fragments/container_cards_entries.html b/templates/fragments/container_cards_entries.html new file mode 100644 index 0000000..53ce49f --- /dev/null +++ b/templates/fragments/container_cards_entries.html @@ -0,0 +1,33 @@ +{# Calendar entries for blog listing cards — served as fragment from events app. + Each post's entries are delimited by comment markers so the consumer can + extract per-post HTML via simple string splitting. #} +{% for post_id in post_ids %} + +{% set widget_entries = batch.get(post_id, []) %} +{% if widget_entries %} +
+

Events:

+
+
+ {% for entry in widget_entries %} + {% set _post_slug = slug_map.get(post_id, '') %} + {% 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 + '/' %} + +
{{ entry.name }}
+
+ {{ entry.start_at.strftime('%a, %b %d') }} +
+
+ {{ entry.start_at.strftime('%H:%M') }} + {% if entry.end_at %} – {{ entry.end_at.strftime('%H:%M') }}{% endif %} +
+
+ {% endfor %} +
+
+
+{% endif %} + +{% endfor %} diff --git a/templates/fragments/container_nav_calendars.html b/templates/fragments/container_nav_calendars.html new file mode 100644 index 0000000..cdf50e3 --- /dev/null +++ b/templates/fragments/container_nav_calendars.html @@ -0,0 +1,10 @@ +{# Calendar links nav — served as fragment from events app #} +{% for calendar in calendars %} + {% set local_href=events_url('/' + post_slug + '/calendars/' + calendar.slug + '/') %} + + +
{{calendar.name}}
+
+{% endfor %} diff --git a/templates/fragments/container_nav_entries.html b/templates/fragments/container_nav_entries.html new file mode 100644 index 0000000..d217565 --- /dev/null +++ b/templates/fragments/container_nav_entries.html @@ -0,0 +1,28 @@ +{# Calendar entries nav — served as fragment from events app #} +{% for entry in 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 + '/' %} + +
+
+
{{ entry.name }}
+
+ {{ entry.start_at.strftime('%b %d, %Y at %H:%M') }} + {% if entry.end_at %} – {{ entry.end_at.strftime('%H:%M') }}{% endif %} +
+
+
+{% endfor %} + +{# Infinite scroll sentinel — URL points back to the consumer app #} +{% if has_more and paginate_url_base %} +
+
+{% endif %}