diff --git a/account/sx/sx_components.py b/account/sx/sx_components.py index 04119e7..229466a 100644 --- a/account/sx/sx_components.py +++ b/account/sx/sx_components.py @@ -294,26 +294,49 @@ async def render_newsletters_oob(ctx: dict, newsletter_list: list) -> str: # Public API: Fragment pages # --------------------------------------------------------------------------- -async def render_fragment_page(ctx: dict, page_fragment_html: str) -> str: - """Full page: fragment-provided content.""" +async def render_fragment_page(ctx: dict, page_fragment: str) -> str: + """Full page: fragment-provided content. + + *page_fragment* may be sx source (from text/sx fragments wrapped in + SxExpr) or HTML (from text/html fragments). Sx source is embedded + directly; HTML is wrapped in ``~rich-text``. + """ + from shared.sx.parser import SxExpr hdr = root_header_sx(ctx) hdr_child = header_child_sx(_auth_header_sx(ctx)) header_rows = "(<> " + hdr + " " + hdr_child + ")" + content = _fragment_content(page_fragment) return full_page_sx(ctx, header_rows=header_rows, - content=f'(~rich-text :html "{_sx_escape(page_fragment_html)}")', + content=content, menu=_auth_nav_mobile_sx(ctx)) -async def render_fragment_oob(ctx: dict, page_fragment_html: str) -> str: +async def render_fragment_oob(ctx: dict, page_fragment: str) -> str: """OOB response for fragment pages.""" oobs = "(<> " + _auth_header_sx(ctx, oob=True) + " " + root_header_sx(ctx, oob=True) + ")" + content = _fragment_content(page_fragment) return oob_page_sx(oobs=oobs, - content=f'(~rich-text :html "{_sx_escape(page_fragment_html)}")', + content=content, menu=_auth_nav_mobile_sx(ctx)) +def _fragment_content(frag: object) -> str: + """Convert a fragment response to sx content string. + + SxExpr (from text/sx responses) is embedded as-is; plain strings + (from text/html) are wrapped in ``~rich-text``. + """ + from shared.sx.parser import SxExpr + if isinstance(frag, SxExpr): + return frag.source + s = str(frag) if frag else "" + if not s: + return "" + return f'(~rich-text :html "{_sx_escape(s)}")' + + # --------------------------------------------------------------------------- # Public API: Auth pages (login, device) # --------------------------------------------------------------------------- diff --git a/events/bp/fragments/routes.py b/events/bp/fragments/routes.py index 796f8a8..01c9f4b 100644 --- a/events/bp/fragments/routes.py +++ b/events/bp/fragments/routes.py @@ -3,16 +3,17 @@ Exposes sx fragments at ``/internal/fragments/`` for consumption by other coop apps via the fragment client. -Most handlers are defined declaratively in .sx files under +All handlers are defined declaratively in .sx files under ``events/sx/handlers/`` and dispatched via the sx handler registry. -Jinja HTML handlers (container-cards, account-page) remain as Python -because they return ``text/html`` templates, not sx source. +container-cards and account-page remain as Python handlers because they +call domain service methods and return batched/conditional content, but +they use sx_call() for rendering (no Jinja templates). """ from __future__ import annotations -from quart import Blueprint, Response, g, render_template, request +from quart import Blueprint, Response, g, request from shared.infrastructure.fragments import FRAGMENT_HEADER from shared.services.registry import services @@ -24,8 +25,8 @@ def register(): _handlers: dict[str, object] = {} - # Fragment types that return HTML (Jinja templates) - _html_types = {"container-cards", "account-page"} + # Fragment types that return HTML (comment-delimited batch) + _html_types = {"container-cards"} @bp.before_request async def _require_fragment_header(): @@ -34,7 +35,7 @@ def register(): @bp.get("/") async def get_fragment(fragment_type: str): - # 1. Check Python handlers first (Jinja HTML types) + # 1. Check Python handlers first handler = _handlers.get(fragment_type) if handler is not None: result = await handler() @@ -51,9 +52,13 @@ def register(): return Response("", status=200, content_type="text/sx") - # --- container-cards fragment: entries for blog listing cards (Jinja HTML) -- + # --- container-cards fragment: entries for blog listing cards ----------- + # Returns text/html with comment markers + # so the blog consumer can split per-post fragments. async def _container_cards_handler(): + from sx.sx_components import render_fragment_container_cards + 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()] @@ -66,16 +71,19 @@ def register(): 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, - ) + return render_fragment_container_cards(batch, post_ids, slug_map) _handlers["container-cards"] = _container_cards_handler - # --- account-page fragment: tickets or bookings panel (Jinja HTML) ------ + # --- account-page fragment: tickets or bookings panel ------------------ + # Returns text/sx — the account app embeds this as sx source. async def _account_page_handler(): + from sx.sx_components import ( + render_fragment_account_tickets, + render_fragment_account_bookings, + ) + slug = request.args.get("slug", "") user_id = request.args.get("user_id", type=int) if not user_id: @@ -83,16 +91,10 @@ def register(): if slug == "tickets": tickets = await services.calendar.user_tickets(g.s, user_id=user_id) - return await render_template( - "fragments/account_page_tickets.html", - tickets=tickets, - ) + return render_fragment_account_tickets(tickets) elif slug == "bookings": bookings = await services.calendar.user_bookings(g.s, user_id=user_id) - return await render_template( - "fragments/account_page_bookings.html", - bookings=bookings, - ) + return render_fragment_account_bookings(bookings) return "" _handlers["account-page"] = _account_page_handler diff --git a/events/templates/fragments/account_nav_items.html b/events/templates/fragments/account_nav_items.html deleted file mode 100644 index c047598..0000000 --- a/events/templates/fragments/account_nav_items.html +++ /dev/null @@ -1,23 +0,0 @@ -{# Account nav items: tickets + bookings links for the account dashboard #} - - diff --git a/events/templates/fragments/account_page_bookings.html b/events/templates/fragments/account_page_bookings.html deleted file mode 100644 index 28f8280..0000000 --- a/events/templates/fragments/account_page_bookings.html +++ /dev/null @@ -1,44 +0,0 @@ -
-
- -

Bookings

- - {% if bookings %} -
- {% for booking in bookings %} -
-
-
-

{{ booking.name }}

-
- {{ booking.start_at.strftime('%d %b %Y, %H:%M') }} - {% if booking.end_at %} - – {{ booking.end_at.strftime('%H:%M') }} - {% endif %} - {% if booking.calendar_name %} - · {{ booking.calendar_name }} - {% endif %} - {% if booking.cost %} - · £{{ booking.cost }} - {% endif %} -
-
-
- {% if booking.state == 'confirmed' %} - confirmed - {% elif booking.state == 'provisional' %} - provisional - {% else %} - {{ booking.state }} - {% endif %} -
-
-
- {% endfor %} -
- {% else %} -

No bookings yet.

- {% endif %} - -
-
diff --git a/events/templates/fragments/account_page_tickets.html b/events/templates/fragments/account_page_tickets.html deleted file mode 100644 index 69f7596..0000000 --- a/events/templates/fragments/account_page_tickets.html +++ /dev/null @@ -1,44 +0,0 @@ -
-
- -

Tickets

- - {% if tickets %} -
- {% for ticket in tickets %} -
-
-
- - {{ ticket.entry_name }} - -
- {{ ticket.entry_start_at.strftime('%d %b %Y, %H:%M') }} - {% if ticket.calendar_name %} - · {{ ticket.calendar_name }} - {% endif %} - {% if ticket.ticket_type_name %} - · {{ ticket.ticket_type_name }} - {% endif %} -
-
-
- {% if ticket.state == 'checked_in' %} - checked in - {% elif ticket.state == 'confirmed' %} - confirmed - {% else %} - {{ ticket.state }} - {% endif %} -
-
-
- {% endfor %} -
- {% else %} -

No tickets yet.

- {% endif %} - -
-
diff --git a/events/templates/fragments/container_cards_entries.html b/events/templates/fragments/container_cards_entries.html deleted file mode 100644 index 0242f1c..0000000 --- a/events/templates/fragments/container_cards_entries.html +++ /dev/null @@ -1,33 +0,0 @@ -{# 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 + '/' + 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 %}