Make SxExpr a str subclass, sx_call/render functions return SxExpr

SxExpr is now a str subclass so it works everywhere a plain string
does (join, isinstance, f-strings) while serialize() still emits it
unquoted. sx_call() and all internal render functions (_render_to_sx,
async_eval_to_sx, etc.) return SxExpr, eliminating the "forgot to
wrap" bug class that caused the sx_content leak and list serialization
bugs.

- Phase 0: SxExpr(str) with .source property, __add__/__radd__
- Phase 1: sx_call returns SxExpr (drop-in, all 200+ sites unchanged)
- Phase 2: async_eval_to_sx, async_eval_slot_to_sx, _render_to_sx,
  mobile_menu_sx return SxExpr; remove isinstance(str) workaround
- Phase 3: Remove ~150 redundant SxExpr() wrappings across 45 files
- Phase 4: serialize() docstring, handler return docs, ;; returns: sx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 21:47:00 +00:00
parent ad75798ab7
commit 278ae3e8f6
45 changed files with 378 additions and 379 deletions

View File

@@ -1,4 +1,5 @@
;; Account auth-menu fragment handler ;; Account auth-menu fragment handler
;; returns: sx
;; ;;
;; Renders the desktop + mobile auth menu (sign-in or user link). ;; Renders the desktop + mobile auth menu (sign-in or user link).

View File

@@ -144,7 +144,7 @@ def _render_page_search_results(pages, query, page, has_more) -> str:
items_sx = "(<> " + " ".join(items) + ")" items_sx = "(<> " + " ".join(items) + ")"
return sx_call("page-search-results", return sx_call("page-search-results",
items=SxExpr(items_sx), items=SxExpr(items_sx),
sentinel=SxExpr(sentinel) if sentinel else None) sentinel=sentinel or None)
def _render_menu_items_nav_oob(menu_items) -> str: def _render_menu_items_nav_oob(menu_items) -> str:
@@ -191,12 +191,12 @@ def _render_menu_items_nav_oob(menu_items) -> str:
if item_slug != "cart": if item_slug != "cart":
item_parts.append(sx_call("blog-nav-item-link", item_parts.append(sx_call("blog-nav-item-link",
href=href, hx_get=f"/{item_slug}/", selected=selected, href=href, hx_get=f"/{item_slug}/", selected=selected,
nav_cls=nav_button_cls, img=SxExpr(img_sx), label=label, nav_cls=nav_button_cls, img=img_sx, label=label,
)) ))
else: else:
item_parts.append(sx_call("blog-nav-item-plain", item_parts.append(sx_call("blog-nav-item-plain",
href=href, selected=selected, nav_cls=nav_button_cls, href=href, selected=selected, nav_cls=nav_button_cls,
img=SxExpr(img_sx), label=label, img=img_sx, label=label,
)) ))
items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else "" items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else ""

View File

@@ -226,7 +226,7 @@ def _render_associated_entries(all_calendars, associated_entry_ids, post_slug: s
confirm_text=f"This will remove {e_name} from this post", confirm_text=f"This will remove {e_name} from this post",
toggle_url=toggle_url, toggle_url=toggle_url,
hx_headers=f'{{"X-CSRFToken": "{csrf}"}}', hx_headers=f'{{"X-CSRFToken": "{csrf}"}}',
img=SxExpr(img_sx), name=e_name, img=img_sx, name=e_name,
date_str=f"{cal_name} \u2022 {date_str}", date_str=f"{cal_name} \u2022 {date_str}",
)) ))
@@ -237,7 +237,7 @@ def _render_associated_entries(all_calendars, associated_entry_ids, post_slug: s
else: else:
content_sx = sx_call("blog-associated-entries-empty") content_sx = sx_call("blog-associated-entries-empty")
return sx_call("blog-associated-entries-panel", content=SxExpr(content_sx)) return sx_call("blog-associated-entries-panel", content=content_sx)
def _render_nav_entries_oob(associated_entries, calendars, post: dict) -> str: def _render_nav_entries_oob(associated_entries, calendars, post: dict) -> str:

View File

@@ -1,4 +1,5 @@
;; Blog link-card fragment handler ;; Blog link-card fragment handler
;; returns: sx
;; ;;
;; Renders link-card(s) for blog posts by slug. ;; Renders link-card(s) for blog posts by slug.
;; Supports single mode (?slug=x) and batch mode (?keys=x,y,z). ;; Supports single mode (?slug=x) and batch mode (?keys=x,y,z).

View File

@@ -1,4 +1,5 @@
;; Blog nav-tree fragment handler ;; Blog nav-tree fragment handler
;; returns: sx
;; ;;
;; Renders the full scrollable navigation menu bar with app icons. ;; Renders the full scrollable navigation menu bar with app icons.
;; Uses nav-tree I/O primitive to fetch menu nodes from the blog DB. ;; Uses nav-tree I/O primitive to fetch menu nodes from the blog DB.

View File

@@ -279,11 +279,11 @@ async def _h_post_preview_content(slug=None, **kw):
if preview.get("sx_rendered"): if preview.get("sx_rendered"):
rendered_sx = sx_call("blog-preview-rendered", html=preview["sx_rendered"]) rendered_sx = sx_call("blog-preview-rendered", html=preview["sx_rendered"])
sections.append(sx_call("blog-preview-section", sections.append(sx_call("blog-preview-section",
title="SX Rendered", content=SxExpr(rendered_sx))) title="SX Rendered", content=rendered_sx))
if preview.get("lex_rendered"): if preview.get("lex_rendered"):
rendered_sx = sx_call("blog-preview-rendered", html=preview["lex_rendered"]) rendered_sx = sx_call("blog-preview-rendered", html=preview["lex_rendered"])
sections.append(sx_call("blog-preview-section", sections.append(sx_call("blog-preview-section",
title="Lexical Rendered", content=SxExpr(rendered_sx))) title="Lexical Rendered", content=rendered_sx))
if not sections: if not sections:
return sx_call("blog-preview-empty") return sx_call("blog-preview-empty")

View File

@@ -10,7 +10,6 @@ from typing import Any
def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str: def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
from shared.sx.helpers import sx_call from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
from quart import url_for as qurl from quart import url_for as qurl
settings_href = qurl("settings.defpage_settings_home") settings_href = qurl("settings.defpage_settings_home")
@@ -20,8 +19,8 @@ def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
return sx_call("menu-row-sx", return sx_call("menu-row-sx",
id="root-settings-row", level=1, id="root-settings-row", level=1,
link_href=settings_href, link_href=settings_href,
link_label_content=SxExpr(label_sx), link_label_content=label_sx,
nav=SxExpr(nav_sx) if nav_sx else None, nav=nav_sx or None,
child_id="root-settings-header-child", oob=oob) child_id="root-settings-header-child", oob=oob)
@@ -41,7 +40,7 @@ def _sub_settings_header_sx(row_id: str, child_id: str, href: str,
return sx_call("menu-row-sx", return sx_call("menu-row-sx",
id=row_id, level=2, id=row_id, level=2,
link_href=href, link_href=href,
link_label_content=SxExpr(label_sx), link_label_content=label_sx,
nav=SxExpr(nav_sx) if nav_sx else None, nav=SxExpr(nav_sx) if nav_sx else None,
child_id=child_id, oob=oob) child_id=child_id, oob=oob)
@@ -80,16 +79,14 @@ async def _blog_oob(ctx: dict, **kw: Any) -> str:
async def _settings_full(ctx: dict, **kw: Any) -> str: async def _settings_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("settings-layout-full", {}, return await render_to_sx_with_env("settings-layout-full", {},
settings_header=SxExpr(_settings_header_sx(ctx))) settings_header=_settings_header_sx(ctx))
async def _settings_oob(ctx: dict, **kw: Any) -> str: async def _settings_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
rows = await render_to_sx_with_env("settings-layout-full", {}, rows = await render_to_sx_with_env("settings-layout-full", {},
settings_header=SxExpr(_settings_header_sx(ctx))) settings_header=_settings_header_sx(ctx))
return await oob_header_sx("root-header-child", "root-settings-header-child", rows) return await oob_header_sx("root-header-child", "root-settings-header-child", rows)
@@ -102,26 +99,24 @@ def _settings_mobile(ctx: dict, **kw: Any) -> str:
async def _sub_settings_full(ctx: dict, row_id: str, child_id: str, async def _sub_settings_full(ctx: dict, row_id: str, child_id: str,
endpoint: str, icon: str, label: str) -> str: endpoint: str, icon: str, label: str) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
from quart import url_for as qurl from quart import url_for as qurl
return await render_to_sx_with_env("sub-settings-layout-full", {}, return await render_to_sx_with_env("sub-settings-layout-full", {},
settings_header=SxExpr(_settings_header_sx(ctx)), settings_header=_settings_header_sx(ctx),
sub_header=SxExpr(_sub_settings_header_sx( sub_header=_sub_settings_header_sx(
row_id, child_id, qurl(endpoint), icon, label, ctx))) row_id, child_id, qurl(endpoint), icon, label, ctx))
async def _sub_settings_oob(ctx: dict, row_id: str, child_id: str, async def _sub_settings_oob(ctx: dict, row_id: str, child_id: str,
endpoint: str, icon: str, label: str) -> str: endpoint: str, icon: str, label: str) -> str:
from shared.sx.helpers import oob_header_sx, sx_call from shared.sx.helpers import oob_header_sx, sx_call
from shared.sx.parser import SxExpr
from quart import url_for as qurl from quart import url_for as qurl
settings_hdr_oob = _settings_header_sx(ctx, oob=True) settings_hdr_oob = _settings_header_sx(ctx, oob=True)
sub_hdr = _sub_settings_header_sx( sub_hdr = _sub_settings_header_sx(
row_id, child_id, qurl(endpoint), icon, label, ctx) row_id, child_id, qurl(endpoint), icon, label, ctx)
sub_oob = await oob_header_sx("root-settings-header-child", child_id, sub_hdr) sub_oob = await oob_header_sx("root-settings-header-child", child_id, sub_hdr)
return sx_call("sub-settings-layout-oob", return sx_call("sub-settings-layout-oob",
settings_header_oob=SxExpr(settings_hdr_oob), settings_header_oob=settings_hdr_oob,
sub_header_oob=SxExpr(sub_oob)) sub_header_oob=sub_oob)
# --- Cache --- # --- Cache ---
@@ -177,20 +172,18 @@ async def _tag_groups_oob(ctx: dict, **kw: Any) -> str:
async def _tag_group_edit_full(ctx: dict, **kw: Any) -> str: async def _tag_group_edit_full(ctx: dict, **kw: Any) -> str:
from quart import request, url_for as qurl from quart import request, url_for as qurl
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
g_id = (request.view_args or {}).get("id") g_id = (request.view_args or {}).get("id")
return await render_to_sx_with_env("sub-settings-layout-full", {}, return await render_to_sx_with_env("sub-settings-layout-full", {},
settings_header=SxExpr(_settings_header_sx(ctx)), settings_header=_settings_header_sx(ctx),
sub_header=SxExpr(_sub_settings_header_sx( sub_header=_sub_settings_header_sx(
"tag-groups-row", "tag-groups-header-child", "tag-groups-row", "tag-groups-header-child",
qurl("defpage_tag_group_edit", id=g_id), qurl("defpage_tag_group_edit", id=g_id),
"tags", "Tag Groups", ctx))) "tags", "Tag Groups", ctx))
async def _tag_group_edit_oob(ctx: dict, **kw: Any) -> str: async def _tag_group_edit_oob(ctx: dict, **kw: Any) -> str:
from quart import request, url_for as qurl from quart import request, url_for as qurl
from shared.sx.helpers import oob_header_sx, sx_call from shared.sx.helpers import oob_header_sx, sx_call
from shared.sx.parser import SxExpr
g_id = (request.view_args or {}).get("id") g_id = (request.view_args or {}).get("id")
settings_hdr_oob = _settings_header_sx(ctx, oob=True) settings_hdr_oob = _settings_header_sx(ctx, oob=True)
sub_hdr = _sub_settings_header_sx( sub_hdr = _sub_settings_header_sx(
@@ -199,5 +192,5 @@ async def _tag_group_edit_oob(ctx: dict, **kw: Any) -> str:
"tags", "Tag Groups", ctx) "tags", "Tag Groups", ctx)
sub_oob = await oob_header_sx("root-settings-header-child", "tag-groups-header-child", sub_hdr) sub_oob = await oob_header_sx("root-settings-header-child", "tag-groups-header-child", sub_hdr)
return sx_call("sub-settings-layout-oob", return sx_call("sub-settings-layout-oob",
settings_header_oob=SxExpr(settings_hdr_oob), settings_header_oob=settings_hdr_oob,
sub_header_oob=SxExpr(sub_oob)) sub_header_oob=sub_oob)

View File

@@ -1,4 +1,5 @@
;; Cart account-nav-item fragment handler ;; Cart account-nav-item fragment handler
;; returns: sx
;; ;;
;; Renders the "orders" link for the account dashboard nav. ;; Renders the "orders" link for the account dashboard nav.

View File

@@ -1,4 +1,5 @@
;; Cart cart-mini fragment handler ;; Cart cart-mini fragment handler
;; returns: sx
;; ;;
;; Renders the cart icon with badge (or logo when empty). ;; Renders the cart icon with badge (or logo when empty).

View File

@@ -3,8 +3,6 @@ from __future__ import annotations
from typing import Any from typing import Any
from shared.sx.parser import SxExpr
def _register_cart_layouts() -> None: def _register_cart_layouts() -> None:
from shared.sx.layouts import register_custom_layout from shared.sx.layouts import register_custom_layout
@@ -80,8 +78,8 @@ def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> str
"menu-row-sx", "menu-row-sx",
id="page-cart-row", level=2, colour="sky", id="page-cart-row", level=2, colour="sky",
link_href=call_url(ctx, "cart_url", f"/{slug}/"), link_href=call_url(ctx, "cart_url", f"/{slug}/"),
link_label_content=SxExpr(label_sx), link_label_content=label_sx,
nav=SxExpr(nav_sx), oob=oob, nav=nav_sx, oob=oob,
) )
@@ -102,8 +100,8 @@ async def _cart_page_full(ctx: dict, **kw: Any) -> str:
page_post = ctx.get("page_post") page_post = ctx.get("page_post")
env = {} env = {}
return await render_to_sx_with_env("cart-page-layout-full", env, return await render_to_sx_with_env("cart-page-layout-full", env,
cart_row=SxExpr(_cart_header_sx(ctx)), cart_row=_cart_header_sx(ctx),
page_cart_row=SxExpr(_page_cart_header_sx(ctx, page_post)), page_cart_row=_page_cart_header_sx(ctx, page_post),
) )
@@ -112,9 +110,9 @@ async def _cart_page_oob(ctx: dict, **kw: Any) -> str:
page_post = ctx.get("page_post") page_post = ctx.get("page_post")
env = {} env = {}
return await render_to_sx_with_env("cart-page-layout-oob", env, return await render_to_sx_with_env("cart-page-layout-oob", env,
root_header_oob=SxExpr(await root_header_sx(ctx, oob=True)), root_header_oob=await root_header_sx(ctx, oob=True),
cart_row_oob=SxExpr(_cart_header_sx(ctx, oob=True)), cart_row_oob=_cart_header_sx(ctx, oob=True),
page_cart_row=SxExpr(_page_cart_header_sx(ctx, page_post)), page_cart_row=_page_cart_header_sx(ctx, page_post),
) )
@@ -124,8 +122,8 @@ async def _cart_admin_full(ctx: dict, **kw: Any) -> str:
selected = kw.get("selected", "") selected = kw.get("selected", "")
env = {} env = {}
return await render_to_sx_with_env("cart-admin-layout-full", env, return await render_to_sx_with_env("cart-admin-layout-full", env,
post_header=SxExpr(await _post_header_sx(ctx, page_post)), post_header=await _post_header_sx(ctx, page_post),
admin_header=SxExpr(await _cart_page_admin_header_sx(ctx, page_post, selected=selected)), admin_header=await _cart_page_admin_header_sx(ctx, page_post, selected=selected),
) )

View File

@@ -20,7 +20,7 @@ async def render_orders_page(ctx, orders, page, total_pages, search, search_coun
header_rows = await render_to_sx_with_env("cart-orders-layout-full", {}, header_rows = await render_to_sx_with_env("cart-orders-layout-full", {},
list_url=list_url, list_url=list_url,
) )
filt = sx_call("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx))) filt = sx_call("order-list-header", search_mobile=await search_mobile_sx(ctx))
return await full_page_sx(ctx, header_rows=header_rows, filter=filt, return await full_page_sx(ctx, header_rows=header_rows, filter=filt,
aside=await search_desktop_sx(ctx), content=content) aside=await search_desktop_sx(ctx), content=content)
@@ -44,7 +44,7 @@ def render_orders_rows(ctx, orders, page, total_pages, url_for_fn, qs_fn):
next_scroll = sx_call("order-end-row") next_scroll = sx_call("order-end-row")
return sx_call("cart-orders-rows", return sx_call("cart-orders-rows",
rows=SxExpr("(<> " + " ".join(parts) + ")"), rows=SxExpr("(<> " + " ".join(parts) + ")"),
next_scroll=SxExpr(next_scroll), next_scroll=next_scroll,
) )
@@ -62,7 +62,7 @@ async def render_orders_oob(ctx, orders, page, total_pages, search, search_count
oobs = await render_to_sx_with_env("cart-orders-layout-oob", {}, oobs = await render_to_sx_with_env("cart-orders-layout-oob", {},
list_url=list_url, list_url=list_url,
) )
filt = sx_call("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx))) filt = sx_call("order-list-header", search_mobile=await search_mobile_sx(ctx))
return await oob_page_sx(oobs=oobs, filter=filt, aside=await search_desktop_sx(ctx), content=content) return await oob_page_sx(oobs=oobs, filter=filt, aside=await search_desktop_sx(ctx), content=content)
@@ -116,7 +116,7 @@ async def render_checkout_error_page(ctx, error=None, order=None):
hdr = await render_to_sx_with_env("layout-root-full", {}) hdr = await render_to_sx_with_env("layout-root-full", {})
filt = sx_call("checkout-error-header") filt = sx_call("checkout-error-header")
content = sx_call("checkout-error-content", msg=err_msg, content = sx_call("checkout-error-content", msg=err_msg,
order=SxExpr(order_sx) if order_sx else None, back_url=cart_url("/")) order=order_sx or None, back_url=cart_url("/"))
return await full_page_sx(ctx, header_rows=hdr, filter=filt, content=content) return await full_page_sx(ctx, header_rows=hdr, filter=filt, content=content)

View File

@@ -1,4 +1,5 @@
;; Events account-nav-item fragment handler ;; Events account-nav-item fragment handler
;; returns: sx
;; ;;
;; Renders tickets + bookings links for the account dashboard nav. ;; Renders tickets + bookings links for the account dashboard nav.

View File

@@ -1,4 +1,5 @@
;; Account-page fragment handler ;; Account-page fragment handler
;; returns: sx
;; ;;
;; Renders tickets or bookings panel for the account dashboard. ;; Renders tickets or bookings panel for the account dashboard.
;; slug=tickets → ticket list; slug=bookings → booking list. ;; slug=tickets → ticket list; slug=bookings → booking list.

View File

@@ -1,4 +1,5 @@
;; Container-cards fragment handler ;; Container-cards fragment handler
;; returns: sx
;; ;;
;; Returns HTML with <!-- card-widget:ID --> comment markers so the ;; Returns HTML with <!-- card-widget:ID --> comment markers so the
;; blog consumer can split per-post fragments. Each post section ;; blog consumer can split per-post fragments. Each post section

View File

@@ -1,4 +1,5 @@
;; Events container-nav fragment handler ;; Events container-nav fragment handler
;; returns: sx
;; ;;
;; Renders calendar entry nav items + calendar link nav items ;; Renders calendar entry nav items + calendar link nav items
;; for the scrollable navigation panel on blog post pages. ;; for the scrollable navigation panel on blog post pages.

View File

@@ -1,4 +1,5 @@
;; Events link-card fragment handler ;; Events link-card fragment handler
;; returns: sx
;; ;;
;; Renders event page preview card(s) by slug. ;; Renders event page preview card(s) by slug.
;; Supports single mode (?slug=x) and batch mode (?keys=x,y,z). ;; Supports single mode (?slug=x) and batch mode (?keys=x,y,z).

View File

@@ -78,7 +78,7 @@ def _calendars_header_sx(ctx: dict, *, oob: bool = False) -> str:
link_href = url_for("calendars.home") link_href = url_for("calendars.home")
return sx_call("menu-row-sx", id="calendars-row", level=3, return sx_call("menu-row-sx", id="calendars-row", level=3,
link_href=link_href, link_href=link_href,
link_label_content=SxExpr(sx_call("events-calendars-label")), link_label_content=sx_call("events-calendars-label"),
child_id="calendars-header-child", oob=oob) child_id="calendars-header-child", oob=oob)
@@ -104,7 +104,7 @@ def _calendar_header_sx(ctx: dict, *, oob: bool = False) -> str:
nav_html = _calendar_nav_sx(ctx) nav_html = _calendar_nav_sx(ctx)
return sx_call("menu-row-sx", id="calendar-row", level=3, return sx_call("menu-row-sx", id="calendar-row", level=3,
link_href=link_href, link_label_content=SxExpr(label_html), link_href=link_href, link_label_content=label_html,
nav=SxExpr(nav_html) if nav_html else None, child_id="calendar-header-child", oob=oob) nav=SxExpr(nav_html) if nav_html else None, child_id="calendar-header-child", oob=oob)
@@ -158,7 +158,7 @@ def _day_header_sx(ctx: dict, *, oob: bool = False) -> str:
nav_html = _day_nav_sx(ctx) nav_html = _day_nav_sx(ctx)
return sx_call("menu-row-sx", id="day-row", level=4, return sx_call("menu-row-sx", id="day-row", level=4,
link_href=link_href, link_label_content=SxExpr(label_html), link_href=link_href, link_label_content=label_html,
nav=SxExpr(nav_html) if nav_html else None, child_id="day-header-child", oob=oob) nav=SxExpr(nav_html) if nav_html else None, child_id="day-header-child", oob=oob)
@@ -271,7 +271,7 @@ def _markets_header_sx(ctx: dict, *, oob: bool = False) -> str:
link_href = url_for("defpage_events_markets") link_href = url_for("defpage_events_markets")
return sx_call("menu-row-sx", id="markets-row", level=3, return sx_call("menu-row-sx", id="markets-row", level=3,
link_href=link_href, link_href=link_href,
link_label_content=SxExpr(sx_call("events-markets-label")), link_label_content=sx_call("events-markets-label"),
child_id="markets-header-child", oob=oob) child_id="markets-header-child", oob=oob)
@@ -544,7 +544,7 @@ def _day_row_html(ctx: dict, entry) -> str:
state = getattr(entry, "state", "pending") or "pending" state = getattr(entry, "state", "pending") or "pending"
state_badge = _entry_state_badge_html(state) state_badge = _entry_state_badge_html(state)
state_td = sx_call("events-day-row-state", state_td = sx_call("events-day-row-state",
state_id=f"entry-state-{entry.id}", badge=SxExpr(state_badge)) state_id=f"entry-state-{entry.id}", badge=state_badge)
# Cost # Cost
cost = getattr(entry, "cost", None) cost = getattr(entry, "cost", None)
@@ -564,9 +564,9 @@ def _day_row_html(ctx: dict, entry) -> str:
actions_td = sx_call("events-day-row-actions") actions_td = sx_call("events-day-row-actions")
return sx_call("events-day-row", return sx_call("events-day-row",
tr_cls=tr_cls, name=SxExpr(name_html), slot=SxExpr(slot_html), tr_cls=tr_cls, name=name_html, slot=slot_html,
state=SxExpr(state_td), cost=SxExpr(cost_td), state=state_td, cost=cost_td,
tickets=SxExpr(tickets_td), actions=SxExpr(actions_td)) tickets=tickets_td, actions=actions_td)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -598,7 +598,7 @@ def _calendar_admin_main_panel_html(ctx: dict) -> str:
description_html = _calendar_description_display_html(calendar, desc_edit_url) description_html = _calendar_description_display_html(calendar, desc_edit_url)
return sx_call("events-calendar-admin-panel", return sx_call("events-calendar-admin-panel",
description_content=SxExpr(description_html), csrf=csrf, description_content=description_html, csrf=csrf,
description=desc) description=desc)

View File

@@ -73,10 +73,10 @@ def _entry_card_html(entry, page_info: dict, pending_tickets: dict,
if tp is not None: if tp is not None:
qty = pending_tickets.get(entry.id, 0) qty = pending_tickets.get(entry.id, 0)
widget_html = sx_call("events-entry-widget-wrapper", widget_html = sx_call("events-entry-widget-wrapper",
widget=SxExpr(_ticket_widget_html(entry, qty, ticket_url, ctx={}))) widget=_ticket_widget_html(entry, qty, ticket_url, ctx={}))
return sx_call("events-entry-card", return sx_call("events-entry-card",
title=SxExpr(title_html), badges=SxExpr(badges_html), title=title_html, badges=SxExpr(badges_html),
time_parts=SxExpr(time_parts), cost=SxExpr(cost_html), time_parts=SxExpr(time_parts), cost=SxExpr(cost_html),
widget=SxExpr(widget_html)) widget=SxExpr(widget_html))
@@ -137,10 +137,10 @@ def _entry_card_tile_html(entry, page_info: dict, pending_tickets: dict,
if tp is not None: if tp is not None:
qty = pending_tickets.get(entry.id, 0) qty = pending_tickets.get(entry.id, 0)
widget_html = sx_call("events-entry-tile-widget-wrapper", widget_html = sx_call("events-entry-tile-widget-wrapper",
widget=SxExpr(_ticket_widget_html(entry, qty, ticket_url, ctx={}))) widget=_ticket_widget_html(entry, qty, ticket_url, ctx={}))
return sx_call("events-entry-card-tile", return sx_call("events-entry-card-tile",
title=SxExpr(title_html), badges=SxExpr(badges_html), title=title_html, badges=SxExpr(badges_html),
time=SxExpr(time_html), cost=SxExpr(cost_html), time=SxExpr(time_html), cost=SxExpr(cost_html),
widget=SxExpr(widget_html)) widget=SxExpr(widget_html))
@@ -199,7 +199,7 @@ def _events_main_panel_html(ctx: dict, entries, has_more, pending_tickets, page_
cls="px-3 py-12 text-center text-stone-400") cls="px-3 py-12 text-center text-stone-400")
return sx_call("events-main-panel-body", return sx_call("events-main-panel-body",
toggle=SxExpr(toggle), body=SxExpr(body)) toggle=toggle, body=body)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -253,7 +253,7 @@ def _entry_main_panel_html(ctx: dict) -> str:
# State # State
state_html = _field("State", sx_call("events-entry-state-field", state_html = _field("State", sx_call("events-entry-state-field",
entry_id=str(eid), entry_id=str(eid),
badge=SxExpr(_entry_state_badge_html(state)))) badge=_entry_state_badge_html(state)))
# Cost # Cost
cost = getattr(entry, "cost", None) cost = getattr(entry, "cost", None)
@@ -284,7 +284,7 @@ def _entry_main_panel_html(ctx: dict) -> str:
entry_posts = ctx.get("entry_posts") or [] entry_posts = ctx.get("entry_posts") or []
posts_html = _field("Associated Posts", sx_call("events-entry-posts-field", posts_html = _field("Associated Posts", sx_call("events-entry-posts-field",
entry_id=str(eid), entry_id=str(eid),
posts_panel=SxExpr(render_entry_posts_panel(entry_posts, entry, calendar, day, month, year)))) posts_panel=render_entry_posts_panel(entry_posts, entry, calendar, day, month, year)))
# Options and Edit Button # Options and Edit Button
edit_url = url_for( edit_url = url_for(
@@ -295,12 +295,12 @@ def _entry_main_panel_html(ctx: dict) -> str:
return sx_call("events-entry-panel", return sx_call("events-entry-panel",
entry_id=str(eid), list_container=list_container, entry_id=str(eid), list_container=list_container,
name=SxExpr(name_html), slot=SxExpr(slot_html), name=name_html, slot=slot_html,
time=SxExpr(time_html), state=SxExpr(state_html), time=time_html, state=state_html,
cost=SxExpr(cost_html), tickets=SxExpr(tickets_html), cost=cost_html, tickets=tickets_html,
buy=SxExpr(buy_html), date=SxExpr(date_html), buy=SxExpr(buy_html), date=date_html,
posts=SxExpr(posts_html), posts=posts_html,
options=SxExpr(_entry_options_html(entry, calendar, day, month, year)), options=_entry_options_html(entry, calendar, day, month, year),
pre_action=pre_action, edit_url=edit_url) pre_action=pre_action, edit_url=edit_url)
@@ -331,13 +331,13 @@ def _entry_header_html(ctx: dict, *, oob: bool = False) -> str:
) )
label_html = sx_call("events-entry-label", label_html = sx_call("events-entry-label",
entry_id=str(entry.id), entry_id=str(entry.id),
title=SxExpr(_entry_title_html(entry)), title=_entry_title_html(entry),
times=SxExpr(_entry_times_html(entry))) times=SxExpr(_entry_times_html(entry)))
nav_html = _entry_nav_html(ctx) nav_html = _entry_nav_html(ctx)
return sx_call("menu-row-sx", id="entry-row", level=5, return sx_call("menu-row-sx", id="entry-row", level=5,
link_href=link_href, link_label_content=SxExpr(label_html), link_href=link_href, link_label_content=label_html,
nav=SxExpr(nav_html) if nav_html else None, child_id="entry-header-child", oob=oob) nav=SxExpr(nav_html) if nav_html else None, child_id="entry-header-child", oob=oob)
@@ -391,7 +391,7 @@ def _entry_nav_html(ctx: dict) -> str:
else: else:
img_html = sx_call("events-post-img-placeholder") img_html = sx_call("events-post-img-placeholder")
post_links += sx_call("events-entry-nav-post-link", post_links += sx_call("events-entry-nav-post-link",
href=href, img=SxExpr(img_html), title=title) href=href, img=img_html, title=title)
parts.append((sx_call("events-entry-posts-nav-oob", parts.append((sx_call("events-entry-posts-nav-oob",
items=SxExpr(post_links))).replace(' :hx-swap-oob "true"', '')) items=SxExpr(post_links))).replace(' :hx-swap-oob "true"', ''))
@@ -420,7 +420,7 @@ def render_entry_optioned(entry, calendar, day, month, year) -> str:
return options + sx_call("events-entry-optioned-oob", return options + sx_call("events-entry-optioned-oob",
entry_id=str(entry.id), entry_id=str(entry.id),
title=SxExpr(title), state=SxExpr(state)) title=title, state=state)
def _entry_title_html(entry) -> str: def _entry_title_html(entry) -> str:
@@ -428,7 +428,7 @@ def _entry_title_html(entry) -> str:
state = getattr(entry, "state", "pending") or "pending" state = getattr(entry, "state", "pending") or "pending"
return sx_call("events-entry-title", return sx_call("events-entry-title",
name=entry.name, name=entry.name,
badge=SxExpr(_entry_state_badge_html(state))) badge=_entry_state_badge_html(state))
def _entry_options_html(entry, calendar, day, month, year) -> str: def _entry_options_html(entry, calendar, day, month, year) -> str:
@@ -550,7 +550,7 @@ def render_entry_posts_panel(entry_posts, entry, calendar, day, month, year) ->
entry_id=eid, post_id=ep_id, entry_id=eid, post_id=ep_id,
) )
items += sx_call("events-entry-post-item", items += sx_call("events-entry-post-item",
img=SxExpr(img_html), title=ep_title, img=img_html, title=ep_title,
del_url=del_url, entry_id=eid_s, del_url=del_url, entry_id=eid_s,
csrf_hdr=f'{{"X-CSRFToken": "{csrf}"}}') csrf_hdr=f'{{"X-CSRFToken": "{csrf}"}}')
posts_html = sx_call("events-entry-posts-list", items=SxExpr(items)) posts_html = sx_call("events-entry-posts-list", items=SxExpr(items))
@@ -563,7 +563,7 @@ def render_entry_posts_panel(entry_posts, entry, calendar, day, month, year) ->
) )
return sx_call("events-entry-posts-panel", return sx_call("events-entry-posts-panel",
posts=SxExpr(posts_html), search_url=search_url, posts=posts_html, search_url=search_url,
entry_id=eid_s) entry_id=eid_s)
@@ -591,7 +591,7 @@ def render_entry_posts_nav_oob(entry_posts) -> str:
if feat else sx_call("events-post-img-placeholder")) if feat else sx_call("events-post-img-placeholder"))
items += sx_call("events-entry-nav-post", items += sx_call("events-entry-nav-post",
href=href, nav_btn=nav_btn, href=href, nav_btn=nav_btn,
img=SxExpr(img_html), title=title) img=img_html, title=title)
return sx_call("events-entry-posts-nav-oob", items=SxExpr(items)) return sx_call("events-entry-posts-nav-oob", items=SxExpr(items))
@@ -743,7 +743,7 @@ def _entry_admin_header_html(ctx: dict, *, oob: bool = False) -> str:
return sx_call("menu-row-sx", id="entry-admin-row", level=6, return sx_call("menu-row-sx", id="entry-admin-row", level=6,
link_href=link_href, link_label="admin", icon="fa fa-cog", link_href=link_href, link_label="admin", icon="fa fa-cog",
nav=SxExpr(nav_html) if nav_html else None, child_id="entry-admin-header-child", oob=oob) nav=nav_html or None, child_id="entry-admin-header-child", oob=oob)
def _entry_admin_nav_html(ctx: dict) -> str: def _entry_admin_nav_html(ctx: dict) -> str:
@@ -822,7 +822,7 @@ def render_post_search_results(search_posts, search_query, page, total_pages,
parts.append(sx_call("events-post-search-item", parts.append(sx_call("events-post-search-item",
post_url=post_url, entry_id=str(eid), csrf=csrf, post_url=post_url, entry_id=str(eid), csrf=csrf,
post_id=str(sp.id), img=SxExpr(img_html), title=title)) post_id=str(sp.id), img=img_html, title=title))
result = "".join(parts) result = "".join(parts)
@@ -882,7 +882,7 @@ def render_entry_edit_form(entry, calendar, day, month, year, day_slots) -> str:
html = sx_call("events-entry-edit-form", html = sx_call("events-entry-edit-form",
entry_id=str(eid), list_container=list_container, entry_id=str(eid), list_container=list_container,
put_url=put_url, cancel_url=cancel_url, csrf=csrf, put_url=put_url, cancel_url=cancel_url, csrf=csrf,
name_val=entry.name or "", slot_picker=SxExpr(slot_picker_html), name_val=entry.name or "", slot_picker=slot_picker_html,
start_val=start_val, end_val=end_val, cost_display=cost_display, start_val=start_val, end_val=end_val, cost_display=cost_display,
ticket_price_val=tp_val, ticket_count_val=tc_val, ticket_price_val=tp_val, ticket_count_val=tc_val,
action_btn=action_btn, cancel_btn=cancel_btn) action_btn=action_btn, cancel_btn=cancel_btn)
@@ -920,7 +920,7 @@ def render_entry_add_form(calendar, day, month, year, day_slots) -> str:
html = sx_call("events-entry-add-form", html = sx_call("events-entry-add-form",
post_url=post_url, csrf=csrf, post_url=post_url, csrf=csrf,
slot_picker=SxExpr(slot_picker_html), slot_picker=slot_picker_html,
action_btn=action_btn, cancel_btn=cancel_btn, action_btn=action_btn, cancel_btn=cancel_btn,
cancel_url=cancel_url) cancel_url=cancel_url)
return html + _SLOT_PICKER_JS return html + _SLOT_PICKER_JS
@@ -998,13 +998,13 @@ def render_fragment_account_tickets(tickets) -> str:
items_html += sx_call("events-frag-ticket-item", items_html += sx_call("events-frag-ticket-item",
href=href, entry_name=ticket.entry_name, href=href, entry_name=ticket.entry_name,
date_str=date_str, calendar_name=cal_name, date_str=date_str, calendar_name=cal_name,
type_name=type_name, badge=SxExpr(badge_html)) type_name=type_name, badge=badge_html)
body = sx_call("events-frag-tickets-list", items=SxExpr(items_html)) body = sx_call("events-frag-tickets-list", items=SxExpr(items_html))
else: else:
body = sx_call("empty-state", message="No tickets yet.", body = sx_call("empty-state", message="No tickets yet.",
cls="text-sm text-stone-500") cls="text-sm text-stone-500")
return sx_call("events-frag-tickets-panel", items=SxExpr(body)) return sx_call("events-frag-tickets-panel", items=body)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -1033,10 +1033,10 @@ def render_fragment_account_bookings(bookings) -> str:
name=booking.name, name=booking.name,
date_str=date_str + date_str_extra, date_str=date_str + date_str_extra,
calendar_name=cal_name, cost_str=cost_str, calendar_name=cal_name, cost_str=cost_str,
badge=SxExpr(badge_html)) badge=badge_html)
body = sx_call("events-frag-bookings-list", items=SxExpr(items_html)) body = sx_call("events-frag-bookings-list", items=SxExpr(items_html))
else: else:
body = sx_call("empty-state", message="No bookings yet.", body = sx_call("empty-state", message="No bookings yet.",
cls="text-sm text-stone-500") cls="text-sm text-stone-500")
return sx_call("events-frag-bookings-panel", items=SxExpr(body)) return sx_call("events-frag-bookings-panel", items=body)

View File

@@ -229,14 +229,13 @@ def _register_events_layouts() -> None:
async def _cal_admin_full(ctx: dict, **kw: Any) -> str: async def _cal_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx
from shared.sx.parser import SxExpr
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-cal-admin-layout-full", {}, return await render_to_sx_with_env("events-cal-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")), admin_header=await post_admin_header_sx(ctx, slug, selected="calendars"),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
calendar_admin_header=SxExpr(_calendar_admin_header_sx(ctx)), calendar_admin_header=_calendar_admin_header_sx(ctx),
) )
@@ -246,10 +245,10 @@ async def _cal_admin_oob(ctx: dict, **kw: Any) -> str:
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-cal-admin-layout-oob", {}, return await render_to_sx_with_env("events-cal-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")), admin_oob=await post_admin_header_sx(ctx, slug, oob=True, selected="calendars"),
cal_oob=SxExpr(_calendar_header_sx(ctx, oob=True)), cal_oob=_calendar_header_sx(ctx, oob=True),
cal_admin_oob_wrap=SxExpr(await oob_header_sx("calendar-header-child", cal_admin_oob_wrap=await oob_header_sx("calendar-header-child",
"calendar-admin-header-child", _calendar_admin_header_sx(ctx))), "calendar-admin-header-child", _calendar_admin_header_sx(ctx)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child", "post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
@@ -269,8 +268,8 @@ async def _slots_oob(ctx: dict, **kw: Any) -> str:
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True}) ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-slots-layout-oob", {}, return await render_to_sx_with_env("events-slots-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")), admin_oob=await post_admin_header_sx(ctx, slug, oob=True, selected="calendars"),
cal_admin_oob=SxExpr(_calendar_admin_header_sx(ctx, oob=True)), cal_admin_oob=_calendar_admin_header_sx(ctx, oob=True),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child", "post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
@@ -282,15 +281,14 @@ async def _slots_oob(ctx: dict, **kw: Any) -> str:
async def _slot_full(ctx: dict, **kw: Any) -> str: async def _slot_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx
from shared.sx.parser import SxExpr
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True}) ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-slot-layout-full", {}, return await render_to_sx_with_env("events-slot-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")), admin_header=await post_admin_header_sx(ctx, slug, selected="calendars"),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
calendar_admin_header=SxExpr(_calendar_admin_header_sx(ctx)), calendar_admin_header=_calendar_admin_header_sx(ctx),
slot_header=SxExpr(_slot_header_html(ctx)), slot_header=_slot_header_html(ctx),
) )
@@ -300,10 +298,10 @@ async def _slot_oob(ctx: dict, **kw: Any) -> str:
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True}) ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-slot-layout-oob", {}, return await render_to_sx_with_env("events-slot-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")), admin_oob=await post_admin_header_sx(ctx, slug, oob=True, selected="calendars"),
cal_admin_oob=SxExpr(_calendar_admin_header_sx(ctx, oob=True)), cal_admin_oob=_calendar_admin_header_sx(ctx, oob=True),
slot_oob_wrap=SxExpr(await oob_header_sx("calendar-admin-header-child", slot_oob_wrap=await oob_header_sx("calendar-admin-header-child",
"slot-header-child", _slot_header_html(ctx))), "slot-header-child", _slot_header_html(ctx)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child", "post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
@@ -316,15 +314,14 @@ async def _slot_oob(ctx: dict, **kw: Any) -> str:
async def _day_admin_full(ctx: dict, **kw: Any) -> str: async def _day_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx
from shared.sx.parser import SxExpr
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-day-admin-layout-full", {}, return await render_to_sx_with_env("events-day-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")), admin_header=await post_admin_header_sx(ctx, slug, selected="calendars"),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
day_header=SxExpr(_day_header_sx(ctx)), day_header=_day_header_sx(ctx),
day_admin_header=SxExpr(_day_admin_header_sx(ctx)), day_admin_header=_day_admin_header_sx(ctx),
) )
@@ -334,10 +331,10 @@ async def _day_admin_oob(ctx: dict, **kw: Any) -> str:
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-day-admin-layout-oob", {}, return await render_to_sx_with_env("events-day-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")), admin_oob=await post_admin_header_sx(ctx, slug, oob=True, selected="calendars"),
cal_oob=SxExpr(_calendar_header_sx(ctx, oob=True)), cal_oob=_calendar_header_sx(ctx, oob=True),
day_admin_oob_wrap=SxExpr(await oob_header_sx("day-header-child", day_admin_oob_wrap=await oob_header_sx("day-header-child",
"day-admin-header-child", _day_admin_header_sx(ctx))), "day-admin-header-child", _day_admin_header_sx(ctx)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child", "post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
@@ -350,12 +347,11 @@ async def _day_admin_oob(ctx: dict, **kw: Any) -> str:
async def _entry_full(ctx: dict, **kw: Any) -> str: async def _entry_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-entry-layout-full", {}, return await render_to_sx_with_env("events-entry-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
day_header=SxExpr(_day_header_sx(ctx)), day_header=_day_header_sx(ctx),
entry_header=SxExpr(_entry_header_html(ctx)), entry_header=_entry_header_html(ctx),
) )
@@ -363,9 +359,9 @@ async def _entry_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-entry-layout-oob", {}, return await render_to_sx_with_env("events-entry-layout-oob", {},
day_oob=SxExpr(_day_header_sx(ctx, oob=True)), day_oob=_day_header_sx(ctx, oob=True),
entry_oob_wrap=SxExpr(await oob_header_sx("day-header-child", entry_oob_wrap=await oob_header_sx("day-header-child",
"entry-header-child", _entry_header_html(ctx))), "entry-header-child", _entry_header_html(ctx)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
"day-row", "day-header-child", "day-row", "day-header-child",
@@ -377,16 +373,15 @@ async def _entry_oob(ctx: dict, **kw: Any) -> str:
async def _entry_admin_full(ctx: dict, **kw: Any) -> str: async def _entry_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx
from shared.sx.parser import SxExpr
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-entry-admin-layout-full", {}, return await render_to_sx_with_env("events-entry-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")), admin_header=await post_admin_header_sx(ctx, slug, selected="calendars"),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
day_header=SxExpr(_day_header_sx(ctx)), day_header=_day_header_sx(ctx),
entry_header=SxExpr(_entry_header_html(ctx)), entry_header=_entry_header_html(ctx),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)), entry_admin_header=_entry_admin_header_html(ctx),
) )
@@ -396,10 +391,10 @@ async def _entry_admin_oob(ctx: dict, **kw: Any) -> str:
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-entry-admin-layout-oob", {}, return await render_to_sx_with_env("events-entry-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")), admin_oob=await post_admin_header_sx(ctx, slug, oob=True, selected="calendars"),
entry_oob=SxExpr(_entry_header_html(ctx, oob=True)), entry_oob=_entry_header_html(ctx, oob=True),
entry_admin_oob_wrap=SxExpr(await oob_header_sx("entry-header-child", entry_admin_oob_wrap=await oob_header_sx("entry-header-child",
"entry-admin-header-child", _entry_admin_header_html(ctx))), "entry-admin-header-child", _entry_admin_header_html(ctx)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child", "post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
@@ -413,24 +408,22 @@ async def _entry_admin_oob(ctx: dict, **kw: Any) -> str:
async def _ticket_types_full(ctx: dict, **kw: Any) -> str: async def _ticket_types_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-types-layout-full", {}, return await render_to_sx_with_env("events-ticket-types-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
day_header=SxExpr(_day_header_sx(ctx)), day_header=_day_header_sx(ctx),
entry_header=SxExpr(_entry_header_html(ctx)), entry_header=_entry_header_html(ctx),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)), entry_admin_header=_entry_admin_header_html(ctx),
ticket_types_header=SxExpr(_ticket_types_header_html(ctx)), ticket_types_header=_ticket_types_header_html(ctx),
) )
async def _ticket_types_oob(ctx: dict, **kw: Any) -> str: async def _ticket_types_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-types-layout-oob", {}, return await render_to_sx_with_env("events-ticket-types-layout-oob", {},
entry_admin_oob=SxExpr(_entry_admin_header_html(ctx, oob=True)), entry_admin_oob=_entry_admin_header_html(ctx, oob=True),
ticket_types_oob_wrap=SxExpr(await oob_header_sx("entry-admin-header-child", ticket_types_oob_wrap=await oob_header_sx("entry-admin-header-child",
"ticket_types-header-child", _ticket_types_header_html(ctx))), "ticket_types-header-child", _ticket_types_header_html(ctx)),
) )
@@ -438,25 +431,23 @@ async def _ticket_types_oob(ctx: dict, **kw: Any) -> str:
async def _ticket_type_full(ctx: dict, **kw: Any) -> str: async def _ticket_type_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-type-layout-full", {}, return await render_to_sx_with_env("events-ticket-type-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
day_header=SxExpr(_day_header_sx(ctx)), day_header=_day_header_sx(ctx),
entry_header=SxExpr(_entry_header_html(ctx)), entry_header=_entry_header_html(ctx),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)), entry_admin_header=_entry_admin_header_html(ctx),
ticket_types_header=SxExpr(_ticket_types_header_html(ctx)), ticket_types_header=_ticket_types_header_html(ctx),
ticket_type_header=SxExpr(_ticket_type_header_html(ctx)), ticket_type_header=_ticket_type_header_html(ctx),
) )
async def _ticket_type_oob(ctx: dict, **kw: Any) -> str: async def _ticket_type_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-type-layout-oob", {}, return await render_to_sx_with_env("events-ticket-type-layout-oob", {},
ticket_types_oob=SxExpr(_ticket_types_header_html(ctx, oob=True)), ticket_types_oob=_ticket_types_header_html(ctx, oob=True),
ticket_type_oob_wrap=SxExpr(await oob_header_sx("ticket_types-header-child", ticket_type_oob_wrap=await oob_header_sx("ticket_types-header-child",
"ticket_type-header-child", _ticket_type_header_html(ctx))), "ticket_type-header-child", _ticket_type_header_html(ctx)),
) )
@@ -464,20 +455,18 @@ async def _ticket_type_oob(ctx: dict, **kw: Any) -> str:
async def _markets_full(ctx: dict, **kw: Any) -> str: async def _markets_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-markets-layout-full", {}, return await render_to_sx_with_env("events-markets-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
markets_header=SxExpr(_markets_header_sx(ctx)), markets_header=_markets_header_sx(ctx),
) )
async def _markets_oob(ctx: dict, **kw: Any) -> str: async def _markets_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-markets-layout-oob", {}, return await render_to_sx_with_env("events-markets-layout-oob", {},
post_oob=SxExpr(await _post_header_sx(ctx, oob=True)), post_oob=await _post_header_sx(ctx, oob=True),
markets_oob_wrap=SxExpr(await oob_header_sx("post-header-child", markets_oob_wrap=await oob_header_sx("post-header-child",
"markets-header-child", _markets_header_sx(ctx))), "markets-header-child", _markets_header_sx(ctx)),
) )

View File

@@ -36,14 +36,13 @@ def _register_events_layouts() -> None:
async def _cal_admin_full(ctx: dict, **kw: Any) -> str: async def _cal_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx
from shared.sx.parser import SxExpr
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-cal-admin-layout-full", {}, return await render_to_sx_with_env("events-cal-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")), admin_header=await post_admin_header_sx(ctx, slug, selected="calendars"),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
calendar_admin_header=SxExpr(_calendar_admin_header_sx(ctx)), calendar_admin_header=_calendar_admin_header_sx(ctx),
) )
@@ -53,10 +52,10 @@ async def _cal_admin_oob(ctx: dict, **kw: Any) -> str:
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-cal-admin-layout-oob", {}, return await render_to_sx_with_env("events-cal-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")), admin_oob=await post_admin_header_sx(ctx, slug, oob=True, selected="calendars"),
cal_oob=SxExpr(_calendar_header_sx(ctx, oob=True)), cal_oob=_calendar_header_sx(ctx, oob=True),
cal_admin_oob_wrap=SxExpr(await oob_header_sx("calendar-header-child", cal_admin_oob_wrap=await oob_header_sx("calendar-header-child",
"calendar-admin-header-child", _calendar_admin_header_sx(ctx))), "calendar-admin-header-child", _calendar_admin_header_sx(ctx)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child", "post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
@@ -76,8 +75,8 @@ async def _slots_oob(ctx: dict, **kw: Any) -> str:
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True}) ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-slots-layout-oob", {}, return await render_to_sx_with_env("events-slots-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")), admin_oob=await post_admin_header_sx(ctx, slug, oob=True, selected="calendars"),
cal_admin_oob=SxExpr(_calendar_admin_header_sx(ctx, oob=True)), cal_admin_oob=_calendar_admin_header_sx(ctx, oob=True),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child", "post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
@@ -89,15 +88,14 @@ async def _slots_oob(ctx: dict, **kw: Any) -> str:
async def _slot_full(ctx: dict, **kw: Any) -> str: async def _slot_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx
from shared.sx.parser import SxExpr
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True}) ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-slot-layout-full", {}, return await render_to_sx_with_env("events-slot-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")), admin_header=await post_admin_header_sx(ctx, slug, selected="calendars"),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
calendar_admin_header=SxExpr(_calendar_admin_header_sx(ctx)), calendar_admin_header=_calendar_admin_header_sx(ctx),
slot_header=SxExpr(_slot_header_html(ctx)), slot_header=_slot_header_html(ctx),
) )
@@ -107,10 +105,10 @@ async def _slot_oob(ctx: dict, **kw: Any) -> str:
ctx = await _ensure_container_nav({**ctx, "is_admin_section": True}) ctx = await _ensure_container_nav({**ctx, "is_admin_section": True})
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-slot-layout-oob", {}, return await render_to_sx_with_env("events-slot-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")), admin_oob=await post_admin_header_sx(ctx, slug, oob=True, selected="calendars"),
cal_admin_oob=SxExpr(_calendar_admin_header_sx(ctx, oob=True)), cal_admin_oob=_calendar_admin_header_sx(ctx, oob=True),
slot_oob_wrap=SxExpr(await oob_header_sx("calendar-admin-header-child", slot_oob_wrap=await oob_header_sx("calendar-admin-header-child",
"slot-header-child", _slot_header_html(ctx))), "slot-header-child", _slot_header_html(ctx)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child", "post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
@@ -123,15 +121,14 @@ async def _slot_oob(ctx: dict, **kw: Any) -> str:
async def _day_admin_full(ctx: dict, **kw: Any) -> str: async def _day_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx
from shared.sx.parser import SxExpr
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-day-admin-layout-full", {}, return await render_to_sx_with_env("events-day-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")), admin_header=await post_admin_header_sx(ctx, slug, selected="calendars"),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
day_header=SxExpr(_day_header_sx(ctx)), day_header=_day_header_sx(ctx),
day_admin_header=SxExpr(_day_admin_header_sx(ctx)), day_admin_header=_day_admin_header_sx(ctx),
) )
@@ -141,10 +138,10 @@ async def _day_admin_oob(ctx: dict, **kw: Any) -> str:
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-day-admin-layout-oob", {}, return await render_to_sx_with_env("events-day-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")), admin_oob=await post_admin_header_sx(ctx, slug, oob=True, selected="calendars"),
cal_oob=SxExpr(_calendar_header_sx(ctx, oob=True)), cal_oob=_calendar_header_sx(ctx, oob=True),
day_admin_oob_wrap=SxExpr(await oob_header_sx("day-header-child", day_admin_oob_wrap=await oob_header_sx("day-header-child",
"day-admin-header-child", _day_admin_header_sx(ctx))), "day-admin-header-child", _day_admin_header_sx(ctx)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child", "post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
@@ -157,12 +154,11 @@ async def _day_admin_oob(ctx: dict, **kw: Any) -> str:
async def _entry_full(ctx: dict, **kw: Any) -> str: async def _entry_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-entry-layout-full", {}, return await render_to_sx_with_env("events-entry-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
day_header=SxExpr(_day_header_sx(ctx)), day_header=_day_header_sx(ctx),
entry_header=SxExpr(_entry_header_html(ctx)), entry_header=_entry_header_html(ctx),
) )
@@ -170,9 +166,9 @@ async def _entry_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-entry-layout-oob", {}, return await render_to_sx_with_env("events-entry-layout-oob", {},
day_oob=SxExpr(_day_header_sx(ctx, oob=True)), day_oob=_day_header_sx(ctx, oob=True),
entry_oob_wrap=SxExpr(await oob_header_sx("day-header-child", entry_oob_wrap=await oob_header_sx("day-header-child",
"entry-header-child", _entry_header_html(ctx))), "entry-header-child", _entry_header_html(ctx)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
"day-row", "day-header-child", "day-row", "day-header-child",
@@ -184,16 +180,15 @@ async def _entry_oob(ctx: dict, **kw: Any) -> str:
async def _entry_admin_full(ctx: dict, **kw: Any) -> str: async def _entry_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx from shared.sx.helpers import render_to_sx_with_env, post_admin_header_sx
from shared.sx.parser import SxExpr
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-entry-admin-layout-full", {}, return await render_to_sx_with_env("events-entry-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")), admin_header=await post_admin_header_sx(ctx, slug, selected="calendars"),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
day_header=SxExpr(_day_header_sx(ctx)), day_header=_day_header_sx(ctx),
entry_header=SxExpr(_entry_header_html(ctx)), entry_header=_entry_header_html(ctx),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)), entry_admin_header=_entry_admin_header_html(ctx),
) )
@@ -203,10 +198,10 @@ async def _entry_admin_oob(ctx: dict, **kw: Any) -> str:
ctx = await _ensure_container_nav(ctx) ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "") slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-entry-admin-layout-oob", {}, return await render_to_sx_with_env("events-entry-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")), admin_oob=await post_admin_header_sx(ctx, slug, oob=True, selected="calendars"),
entry_oob=SxExpr(_entry_header_html(ctx, oob=True)), entry_oob=_entry_header_html(ctx, oob=True),
entry_admin_oob_wrap=SxExpr(await oob_header_sx("entry-header-child", entry_admin_oob_wrap=await oob_header_sx("entry-header-child",
"entry-admin-header-child", _entry_admin_header_html(ctx))), "entry-admin-header-child", _entry_admin_header_html(ctx)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child", "post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child", "calendar-row", "calendar-header-child",
@@ -220,24 +215,22 @@ async def _entry_admin_oob(ctx: dict, **kw: Any) -> str:
async def _ticket_types_full(ctx: dict, **kw: Any) -> str: async def _ticket_types_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-types-layout-full", {}, return await render_to_sx_with_env("events-ticket-types-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
day_header=SxExpr(_day_header_sx(ctx)), day_header=_day_header_sx(ctx),
entry_header=SxExpr(_entry_header_html(ctx)), entry_header=_entry_header_html(ctx),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)), entry_admin_header=_entry_admin_header_html(ctx),
ticket_types_header=SxExpr(_ticket_types_header_html(ctx)), ticket_types_header=_ticket_types_header_html(ctx),
) )
async def _ticket_types_oob(ctx: dict, **kw: Any) -> str: async def _ticket_types_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-types-layout-oob", {}, return await render_to_sx_with_env("events-ticket-types-layout-oob", {},
entry_admin_oob=SxExpr(_entry_admin_header_html(ctx, oob=True)), entry_admin_oob=_entry_admin_header_html(ctx, oob=True),
ticket_types_oob_wrap=SxExpr(await oob_header_sx("entry-admin-header-child", ticket_types_oob_wrap=await oob_header_sx("entry-admin-header-child",
"ticket_types-header-child", _ticket_types_header_html(ctx))), "ticket_types-header-child", _ticket_types_header_html(ctx)),
) )
@@ -245,25 +238,23 @@ async def _ticket_types_oob(ctx: dict, **kw: Any) -> str:
async def _ticket_type_full(ctx: dict, **kw: Any) -> str: async def _ticket_type_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-type-layout-full", {}, return await render_to_sx_with_env("events-ticket-type-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
calendar_header=SxExpr(_calendar_header_sx(ctx)), calendar_header=_calendar_header_sx(ctx),
day_header=SxExpr(_day_header_sx(ctx)), day_header=_day_header_sx(ctx),
entry_header=SxExpr(_entry_header_html(ctx)), entry_header=_entry_header_html(ctx),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)), entry_admin_header=_entry_admin_header_html(ctx),
ticket_types_header=SxExpr(_ticket_types_header_html(ctx)), ticket_types_header=_ticket_types_header_html(ctx),
ticket_type_header=SxExpr(_ticket_type_header_html(ctx)), ticket_type_header=_ticket_type_header_html(ctx),
) )
async def _ticket_type_oob(ctx: dict, **kw: Any) -> str: async def _ticket_type_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-type-layout-oob", {}, return await render_to_sx_with_env("events-ticket-type-layout-oob", {},
ticket_types_oob=SxExpr(_ticket_types_header_html(ctx, oob=True)), ticket_types_oob=_ticket_types_header_html(ctx, oob=True),
ticket_type_oob_wrap=SxExpr(await oob_header_sx("ticket_types-header-child", ticket_type_oob_wrap=await oob_header_sx("ticket_types-header-child",
"ticket_type-header-child", _ticket_type_header_html(ctx))), "ticket_type-header-child", _ticket_type_header_html(ctx)),
) )
@@ -271,18 +262,16 @@ async def _ticket_type_oob(ctx: dict, **kw: Any) -> str:
async def _markets_full(ctx: dict, **kw: Any) -> str: async def _markets_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-markets-layout-full", {}, return await render_to_sx_with_env("events-markets-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
markets_header=SxExpr(_markets_header_sx(ctx)), markets_header=_markets_header_sx(ctx),
) )
async def _markets_oob(ctx: dict, **kw: Any) -> str: async def _markets_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-markets-layout-oob", {}, return await render_to_sx_with_env("events-markets-layout-oob", {},
post_oob=SxExpr(await _post_header_sx(ctx, oob=True)), post_oob=await _post_header_sx(ctx, oob=True),
markets_oob_wrap=SxExpr(await oob_header_sx("post-header-child", markets_oob_wrap=await oob_header_sx("post-header-child",
"markets-header-child", _markets_header_sx(ctx))), "markets-header-child", _markets_header_sx(ctx)),
) )

View File

@@ -160,7 +160,7 @@ def _slot_header_html(ctx: dict, *, oob: bool = False) -> str:
name=slot.name, description=desc) name=slot.name, description=desc)
return sx_call("menu-row-sx", id="slot-row", level=5, return sx_call("menu-row-sx", id="slot-row", level=5,
link_label_content=SxExpr(label_sx), link_label_content=label_sx,
child_id="slot-header-child", oob=oob) child_id="slot-header-child", oob=oob)
@@ -201,7 +201,7 @@ def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
result = sx_call("events-slot-panel", result = sx_call("events-slot-panel",
slot_id=sid, list_container=list_container, slot_id=sid, list_container=list_container,
days=SxExpr(days_html), days=days_html,
flexible="yes" if flexible else "no", flexible="yes" if flexible else "no",
time_str=f"{time_start} \u2014 {time_end}", time_str=f"{time_start} \u2014 {time_end}",
cost_str=cost_str, cost_str=cost_str,
@@ -259,7 +259,7 @@ def render_slots_table(slots, calendar) -> str:
pill_cls=pill_cls, hx_select=hx_select, pill_cls=pill_cls, hx_select=hx_select,
slot_name=s.name, description=desc, slot_name=s.name, description=desc,
flexible="yes" if s.flexible else "no", flexible="yes" if s.flexible else "no",
days=SxExpr(days_html), days=days_html,
time_str=f"{time_start} - {time_end}", time_str=f"{time_start} - {time_end}",
cost_str=cost_str, action_btn=action_btn, cost_str=cost_str, action_btn=action_btn,
del_url=del_url, del_url=del_url,

View File

@@ -36,7 +36,7 @@ def _ticket_widget_html(entry, qty: int, ticket_url: str, *, ctx: dict) -> str:
return sx_call("events-tw-form", return sx_call("events-tw-form",
ticket_url=ticket_url, target=tgt, ticket_url=ticket_url, target=tgt,
csrf=csrf_token_val, entry_id=str(eid), csrf=csrf_token_val, entry_id=str(eid),
count_val=str(count_val), btn=SxExpr(btn_html)) count_val=str(count_val), btn=btn_html)
if qty == 0: if qty == 0:
inner = _tw_form(1, sx_call("events-tw-cart-plus")) inner = _tw_form(1, sx_call("events-tw-cart-plus"))
@@ -80,7 +80,7 @@ def _tickets_main_panel_html(ctx: dict, tickets: list) -> str:
type_name=tt.name if tt else None, type_name=tt.name if tt else None,
time_str=time_str or None, time_str=time_str or None,
cal_name=cal.name if cal else None, cal_name=cal.name if cal else None,
badge=SxExpr(_ticket_state_badge_html(state)), badge=_ticket_state_badge_html(state),
code_prefix=ticket.code[:8])) code_prefix=ticket.code[:8]))
cards_html = "".join(ticket_cards) cards_html = "".join(ticket_cards)
@@ -193,7 +193,7 @@ def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -> str:
entry_name=entry.name if entry else "\u2014", entry_name=entry.name if entry else "\u2014",
date=SxExpr(date_html), date=SxExpr(date_html),
type_name=tt.name if tt else "\u2014", type_name=tt.name if tt else "\u2014",
badge=SxExpr(_ticket_state_badge_html(state)), badge=_ticket_state_badge_html(state),
action=SxExpr(action_html)) action=SxExpr(action_html))
return sx_call("events-ticket-admin-panel", return sx_call("events-ticket-admin-panel",
@@ -238,7 +238,7 @@ def render_checkin_result(success: bool, error: str | None, ticket) -> str:
entry_name=entry.name if entry else "\u2014", entry_name=entry.name if entry else "\u2014",
date=SxExpr(date_html), date=SxExpr(date_html),
type_name=tt.name if tt else "\u2014", type_name=tt.name if tt else "\u2014",
badge=SxExpr(_ticket_state_badge_html("checked_in")), badge=_ticket_state_badge_html("checked_in"),
time_str=time_str) time_str=time_str)
@@ -275,7 +275,7 @@ def render_lookup_result(ticket, error: str | None) -> str:
if cal: if cal:
info_html += sx_call("events-lookup-cal", cal_name=cal.name) info_html += sx_call("events-lookup-cal", cal_name=cal.name)
info_html += sx_call("events-lookup-status", info_html += sx_call("events-lookup-status",
badge=SxExpr(_ticket_state_badge_html(state)), code=code) badge=_ticket_state_badge_html(state), code=code)
if checked_in_at: if checked_in_at:
info_html += sx_call("events-lookup-checkin-time", info_html += sx_call("events-lookup-checkin-time",
date_str=checked_in_at.strftime("%B %d, %Y at %H:%M")) date_str=checked_in_at.strftime("%B %d, %Y at %H:%M"))
@@ -328,7 +328,7 @@ def render_entry_tickets_admin(entry, tickets: list) -> str:
rows_html += sx_call("events-entry-tickets-admin-row", rows_html += sx_call("events-entry-tickets-admin-row",
code=code, code_short=code[:12] + "...", code=code, code_short=code[:12] + "...",
type_name=tt.name if tt else "\u2014", type_name=tt.name if tt else "\u2014",
badge=SxExpr(_ticket_state_badge_html(state)), badge=_ticket_state_badge_html(state),
action=SxExpr(action_html)) action=SxExpr(action_html))
if tickets: if tickets:
@@ -340,7 +340,7 @@ def render_entry_tickets_admin(entry, tickets: list) -> str:
return sx_call("events-entry-tickets-admin-panel", return sx_call("events-entry-tickets-admin-panel",
entry_name=entry.name, entry_name=entry.name,
count_label=f"{count} ticket{suffix}", count_label=f"{count} ticket{suffix}",
body=SxExpr(body_html)) body=body_html)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -519,16 +519,16 @@ def render_buy_form(entry, ticket_remaining, ticket_sold_count,
cost_str = f"\u00a3{tt.cost:.2f}" if tt.cost is not None else "\u00a30.00" cost_str = f"\u00a3{tt.cost:.2f}" if tt.cost is not None else "\u00a30.00"
type_items += sx_call("events-buy-type-item", type_items += sx_call("events-buy-type-item",
type_name=tt.name, cost_str=cost_str, type_name=tt.name, cost_str=cost_str,
adjust_controls=SxExpr(_ticket_adjust_controls(csrf, adjust_url, target, eid, type_count, ticket_type_id=tt.id))) adjust_controls=_ticket_adjust_controls(csrf, adjust_url, target, eid, type_count, ticket_type_id=tt.id))
body_html = sx_call("events-buy-types-wrapper", items=SxExpr(type_items)) body_html = sx_call("events-buy-types-wrapper", items=SxExpr(type_items))
else: else:
qty = user_ticket_count or 0 qty = user_ticket_count or 0
body_html = sx_call("events-buy-default", body_html = sx_call("events-buy-default",
price_str=f"\u00a3{tp:.2f}", price_str=f"\u00a3{tp:.2f}",
adjust_controls=SxExpr(_ticket_adjust_controls(csrf, adjust_url, target, eid, qty))) adjust_controls=_ticket_adjust_controls(csrf, adjust_url, target, eid, qty))
return sx_call("events-buy-panel", return sx_call("events-buy-panel",
entry_id=eid_s, info=SxExpr(info_html), body=SxExpr(body_html)) entry_id=eid_s, info=SxExpr(info_html), body=body_html)
def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket_type_id=None): def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket_type_id=None):
@@ -543,8 +543,8 @@ def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket
return sx_call("events-adjust-form", return sx_call("events-adjust-form",
adjust_url=adjust_url, target=target, adjust_url=adjust_url, target=target,
extra_cls=extra_cls, csrf=csrf, extra_cls=extra_cls, csrf=csrf,
entry_id=eid_s, tt=SxExpr(tt_html) if tt_html else None, entry_id=eid_s, tt=tt_html or None,
count_val=str(count_val), btn=SxExpr(btn_html)) count_val=str(count_val), btn=btn_html)
if count == 0: if count == 0:
return _adj_form(1, sx_call("events-adjust-cart-plus"), return _adj_form(1, sx_call("events-adjust-cart-plus"),
@@ -557,7 +557,7 @@ def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket
plus = _adj_form(count + 1, sx_call("events-adjust-plus")) plus = _adj_form(count + 1, sx_call("events-adjust-plus"))
return sx_call("events-adjust-controls", return sx_call("events-adjust-controls",
minus=SxExpr(minus), cart_icon=SxExpr(cart_icon), plus=SxExpr(plus)) minus=minus, cart_icon=cart_icon, plus=plus)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -603,7 +603,7 @@ def _ticket_types_header_html(ctx: dict, *, oob: bool = False) -> str:
return sx_call("menu-row-sx", id="ticket_types-row", level=7, return sx_call("menu-row-sx", id="ticket_types-row", level=7,
link_href=link_href, link_label_content=SxExpr(label_html), link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="ticket_type-header-child", oob=oob) nav=nav_html or None, child_id="ticket_type-header-child", oob=oob)
@@ -639,7 +639,7 @@ def _ticket_type_header_html(ctx: dict, *, oob: bool = False) -> str:
return sx_call("menu-row-sx", id="ticket_type-row", level=8, return sx_call("menu-row-sx", id="ticket_type-row", level=8,
link_href=link_href, link_label_content=SxExpr(label_html), link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="ticket_type-header-child-inner", oob=oob) nav=nav_html or None, child_id="ticket_type-header-child-inner", oob=oob)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
from shared.sx.helpers import sx_call from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -146,7 +145,7 @@ def _view_toggle_html(ctx: dict, view: str) -> str:
list_href=list_href, tile_href=tile_href, list_href=list_href, tile_href=tile_href,
hx_select=hx_select, list_cls=list_active, hx_select=hx_select, list_cls=list_active,
tile_cls=tile_active, storage_key="events_view", tile_cls=tile_active, storage_key="events_view",
list_svg=SxExpr(_get_list_svg()), tile_svg=SxExpr(_get_tile_svg())) list_svg=_get_list_svg(), tile_svg=_get_tile_svg())
def _cart_icon_oob(count: int) -> str: def _cart_icon_oob(count: int) -> str:

View File

@@ -31,7 +31,6 @@ async def _render_choose_username(*, actor=None, error="", username=""):
from shared.browser.app.csrf import generate_csrf_token from shared.browser.app.csrf import generate_csrf_token
from shared.config import config from shared.config import config
from shared.sx.helpers import sx_call from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
from shared.sx.page import get_template_context from shared.sx.page import get_template_context
from sxc.pages.utils import _social_page from sxc.pages.utils import _social_page
from markupsafe import escape from markupsafe import escape
@@ -45,7 +44,7 @@ async def _render_choose_username(*, actor=None, error="", username=""):
content = sx_call( content = sx_call(
"federation-choose-username", "federation-choose-username",
domain=str(escape(ap_domain)), domain=str(escape(ap_domain)),
error=SxExpr(error_sx) if error_sx else None, error=error_sx or None,
csrf=csrf, username=str(escape(username)), csrf=csrf, username=str(escape(username)),
check_url=check_url, check_url=check_url,
) )

View File

@@ -212,7 +212,6 @@ def register(url_prefix="/social"):
"""Re-render interaction buttons after a like/boost action.""" """Re-render interaction buttons after a like/boost action."""
from shared.models.federation import APInteraction from shared.models.federation import APInteraction
from shared.browser.app.csrf import generate_csrf_token from shared.browser.app.csrf import generate_csrf_token
from shared.sx.parser import SxExpr
from sqlalchemy import select from sqlalchemy import select
svc = services.federation svc = services.federation
@@ -290,9 +289,9 @@ def register(url_prefix="/social"):
count=str(boost_count)) count=str(boost_count))
return sx_response(sx_call("federation-interaction-buttons", return sx_response(sx_call("federation-interaction-buttons",
like=SxExpr(like_form), like=like_form,
boost=SxExpr(boost_form), boost=boost_form,
reply=SxExpr(reply_sx) if reply_sx else None)) reply=reply_sx or None))
# -- Following / Followers pagination -------------------------------------- # -- Following / Followers pagination --------------------------------------

View File

@@ -1,4 +1,5 @@
;; Federation link-card fragment handler ;; Federation link-card fragment handler
;; returns: sx
;; ;;
;; Renders actor profile preview card(s) by username. ;; Renders actor profile preview card(s) by username.
;; Supports single mode (?slug=x or ?username=x) and batch mode (?keys=x,y,z). ;; Supports single mode (?slug=x or ?username=x) and batch mode (?keys=x,y,z).

View File

@@ -1,4 +1,5 @@
;; Market container-nav fragment handler ;; Market container-nav fragment handler
;; returns: sx
;; ;;
;; Renders marketplace link nav items for blog post pages. ;; Renders marketplace link nav items for blog post pages.

View File

@@ -1,4 +1,5 @@
;; Market link-card fragment handler ;; Market link-card fragment handler
;; returns: sx
;; ;;
;; Renders product preview card(s) by slug. ;; Renders product preview card(s) by slug.
;; Supports single mode (?slug=x) and batch mode (?keys=x,y,z). ;; Supports single mode (?slug=x) and batch mode (?keys=x,y,z).

View File

@@ -188,9 +188,9 @@ def _market_card_sx(market: Any, page_info: dict, *, show_page_badge: bool = Tru
return sx_call( return sx_call(
"market-market-card", "market-market-card",
title_content=SxExpr(title_sx) if title_sx else None, title_content=title_sx or None,
desc_content=SxExpr(desc_sx) if desc_sx else None, desc_content=desc_sx or None,
badge_content=SxExpr(badge_sx) if badge_sx else None, badge_content=badge_sx or None,
) )

View File

@@ -126,7 +126,7 @@ async def _desktop_filter_sx(ctx: dict) -> str:
if brands: if brands:
brand_inner = _brand_filter_sx(brands, selected_brands, ctx) brand_inner = _brand_filter_sx(brands, selected_brands, ctx)
brand_summary = sx_call("market-desktop-brand-summary", brand_summary = sx_call("market-desktop-brand-summary",
inner=SxExpr(brand_inner) if brand_inner else None) inner=brand_inner or None)
return "(<> " + " ".join([search_sx, cat_summary, brand_summary]) + ")" return "(<> " + " ".join([search_sx, cat_summary, brand_summary]) + ")"
@@ -226,8 +226,8 @@ async def _mobile_filter_summary_sx(ctx: dict) -> str:
return sx_call( return sx_call(
"market-mobile-filter-summary", "market-mobile-filter-summary",
search_bar=SxExpr(search_bar), search_bar=search_bar,
chips=SxExpr(chips_row), chips=chips_row,
filter=SxExpr(mobile_filter), filter=SxExpr(mobile_filter),
) )

View File

@@ -151,10 +151,9 @@ async def _h_page_markets_content(slug=None, **kw):
async def _h_page_admin_content(slug=None, **kw): async def _h_page_admin_content(slug=None, **kw):
from shared.sx.page import get_template_context from shared.sx.page import get_template_context
from shared.sx.helpers import sx_call from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
ctx = await get_template_context() ctx = await get_template_context()
content = await _markets_admin_panel_sx(ctx) content = await _markets_admin_panel_sx(ctx)
return sx_call("market-admin-content-wrap", inner=SxExpr(content)) return sx_call("market-admin-content-wrap", inner=content)
def _h_market_home_content(page_slug=None, market_slug=None, **kw): def _h_market_home_content(page_slug=None, market_slug=None, **kw):

View File

@@ -44,8 +44,8 @@ def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
return sx_call( return sx_call(
"menu-row-sx", "menu-row-sx",
id="market-row", level=2, id="market-row", level=2,
link_href=link_href, link_label_content=SxExpr(label_sx), link_href=link_href, link_label_content=label_sx,
nav=SxExpr(nav_sx) if nav_sx else None, nav=nav_sx or None,
child_id="market-header-child", oob=oob, child_id="market-header-child", oob=oob,
) )
@@ -87,7 +87,7 @@ def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
return sx_call("market-desktop-category-nav", return sx_call("market-desktop-category-nav",
links=SxExpr(links_sx), links=SxExpr(links_sx),
admin=SxExpr(admin_sx) if admin_sx else None) admin=admin_sx or None)
def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str: def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
@@ -117,7 +117,7 @@ def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
return sx_call( return sx_call(
"menu-row-sx", "menu-row-sx",
id="product-row", level=3, id="product-row", level=3,
link_href=link_href, link_label_content=SxExpr(label_sx), link_href=link_href, link_label_content=label_sx,
nav=SxExpr(nav_sx), child_id="product-header-child", oob=oob, nav=SxExpr(nav_sx), child_id="product-header-child", oob=oob,
) )
@@ -219,7 +219,7 @@ def _mobile_nav_panel_sx(ctx: dict) -> str:
bg_cls=bg_cls, href=cat_href, hx_select=hx_select, bg_cls=bg_cls, href=cat_href, hx_select=hx_select,
select_colours=select_colours, cat_name=cat, select_colours=select_colours, cat_name=cat,
count_label=f"{cat_count} products", count_str=str(cat_count), count_label=f"{cat_count} products", count_str=str(cat_count),
chevron=SxExpr(chevron_sx), chevron=chevron_sx,
) )
subs = data.get("subs", []) subs = data.get("subs", [])
@@ -246,8 +246,8 @@ def _mobile_nav_panel_sx(ctx: dict) -> str:
item_parts.append(sx_call( item_parts.append(sx_call(
"market-mobile-cat-details", "market-mobile-cat-details",
open=cat_active or None, open=cat_active or None,
summary=SxExpr(summary_sx), summary=summary_sx,
subs=SxExpr(subs_sx), subs=subs_sx,
)) ))
items_sx = "(<> " + " ".join(item_parts) + ")" items_sx = "(<> " + " ".join(item_parts) + ")"
@@ -295,16 +295,16 @@ def _register_market_layouts() -> None:
async def _market_full(ctx: dict, **kw: Any) -> str: async def _market_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
return await render_to_sx_with_env("market-browse-layout-full", {}, return await render_to_sx_with_env("market-browse-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
market_header=SxExpr(_market_header_sx(ctx))) market_header=_market_header_sx(ctx))
async def _market_oob(ctx: dict, **kw: Any) -> str: async def _market_oob(ctx: dict, **kw: Any) -> str:
oob_hdr = await _oob_header_sx("post-header-child", "market-header-child", oob_hdr = await _oob_header_sx("post-header-child", "market-header-child",
_market_header_sx(ctx)) _market_header_sx(ctx))
return sx_call("market-browse-layout-oob", return sx_call("market-browse-layout-oob",
oob_header=SxExpr(oob_hdr), oob_header=oob_hdr,
post_header_oob=SxExpr(await _post_header_sx(ctx, oob=True)), post_header_oob=await _post_header_sx(ctx, oob=True),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child"))) "market-row", "market-header-child")))
@@ -317,17 +317,17 @@ async def _market_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
selected = kw.get("selected", "") selected = kw.get("selected", "")
return await render_to_sx_with_env("market-admin-layout-full", {}, return await render_to_sx_with_env("market-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
market_header=SxExpr(_market_header_sx(ctx)), market_header=_market_header_sx(ctx),
admin_header=SxExpr(await _market_admin_header_sx(ctx, selected=selected))) admin_header=await _market_admin_header_sx(ctx, selected=selected))
async def _market_admin_oob(ctx: dict, **kw: Any) -> str: async def _market_admin_oob(ctx: dict, **kw: Any) -> str:
selected = kw.get("selected", "") selected = kw.get("selected", "")
return sx_call("market-admin-layout-oob", return sx_call("market-admin-layout-oob",
market_header_oob=SxExpr(_market_header_sx(ctx, oob=True)), market_header_oob=_market_header_sx(ctx, oob=True),
admin_oob_header=SxExpr(await _oob_header_sx("market-header-child", "market-admin-header-child", admin_oob_header=await _oob_header_sx("market-header-child", "market-admin-header-child",
await _market_admin_header_sx(ctx, selected=selected))), await _market_admin_header_sx(ctx, selected=selected)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child", "market-row", "market-header-child",
"market-admin-row", "market-admin-header-child"))) "market-admin-row", "market-admin-header-child")))

View File

@@ -37,8 +37,8 @@ async def render_browse_page(ctx: dict) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
hdr = await render_to_sx_with_env("market-browse-layout-full", {}, hdr = await render_to_sx_with_env("market-browse-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
market_header=SxExpr(_market_header_sx(ctx))) market_header=_market_header_sx(ctx))
menu = _mobile_nav_panel_sx(ctx) menu = _mobile_nav_panel_sx(ctx)
filter_sx = await _mobile_filter_summary_sx(ctx) filter_sx = await _mobile_filter_summary_sx(ctx)
aside_sx = await _desktop_filter_sx(ctx) aside_sx = await _desktop_filter_sx(ctx)
@@ -55,8 +55,8 @@ async def render_browse_oob(ctx: dict) -> str:
oob_hdr = await _oob_header_sx("post-header-child", "market-header-child", oob_hdr = await _oob_header_sx("post-header-child", "market-header-child",
_market_header_sx(ctx)) _market_header_sx(ctx))
oobs = sx_call("market-browse-layout-oob", oobs = sx_call("market-browse-layout-oob",
oob_header=SxExpr(oob_hdr), oob_header=oob_hdr,
post_header_oob=SxExpr(await _post_header_sx(ctx, oob=True)), post_header_oob=await _post_header_sx(ctx, oob=True),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child", clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child"))) "market-row", "market-header-child")))
menu = _mobile_nav_panel_sx(ctx) menu = _mobile_nav_panel_sx(ctx)
@@ -83,9 +83,9 @@ async def render_product_page(ctx: dict, d: dict) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
hdr = await render_to_sx_with_env("market-product-layout-full", {}, hdr = await render_to_sx_with_env("market-product-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
market_header=SxExpr(_market_header_sx(ctx)), market_header=_market_header_sx(ctx),
product_header=SxExpr(_product_header_sx(ctx, d))) product_header=_product_header_sx(ctx, d))
return await full_page_sx(ctx, header_rows=hdr, content=content, meta=meta) return await full_page_sx(ctx, header_rows=hdr, content=content, meta=meta)
@@ -114,10 +114,10 @@ async def render_product_admin_page(ctx: dict, d: dict) -> str:
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
hdr = await render_to_sx_with_env("market-product-admin-layout-full", {}, hdr = await render_to_sx_with_env("market-product-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)), post_header=await _post_header_sx(ctx),
market_header=SxExpr(_market_header_sx(ctx)), market_header=_market_header_sx(ctx),
product_header=SxExpr(_product_header_sx(ctx, d)), product_header=_product_header_sx(ctx, d),
admin_header=SxExpr(_product_admin_header_sx(ctx, d))) admin_header=_product_admin_header_sx(ctx, d))
return await full_page_sx(ctx, header_rows=hdr, content=content) return await full_page_sx(ctx, header_rows=hdr, content=content)
@@ -243,7 +243,7 @@ def render_cart_added_response(cart: list, item: Any, d: dict) -> str:
add_sx = sx_call( add_sx = sx_call(
"market-cart-add-oob", "market-cart-add-oob",
id=f"cart-add-{slug}", id=f"cart-add-{slug}",
inner=SxExpr(cart_add), inner=cart_add,
) )
return "(<> " + cart_mini + " " + add_sx + ")" return "(<> " + cart_mini + " " + add_sx + ")"

View File

@@ -110,7 +110,7 @@ def _product_detail_sx(d: dict, ctx: dict) -> str:
gallery_inner = sx_call( gallery_inner = sx_call(
"market-detail-gallery-inner", "market-detail-gallery-inner",
like=SxExpr(like_sx) if like_sx else None, like=like_sx or None,
image=images[0], alt=d.get("title", ""), image=images[0], alt=d.get("title", ""),
labels=SxExpr(labels_sx) if labels_sx else None, labels=SxExpr(labels_sx) if labels_sx else None,
brand=brand, brand=brand,
@@ -123,8 +123,8 @@ def _product_detail_sx(d: dict, ctx: dict) -> str:
gallery_sx = sx_call( gallery_sx = sx_call(
"market-detail-gallery", "market-detail-gallery",
inner=SxExpr(gallery_inner), inner=gallery_inner,
nav=SxExpr(nav_buttons) if nav_buttons else None, nav=nav_buttons or None,
) )
# Thumbnails # Thumbnails
@@ -144,7 +144,7 @@ def _product_detail_sx(d: dict, ctx: dict) -> str:
if user: if user:
like_sx = _like_button_sx(slug, liked_by_current_user, csrf, ctx) like_sx = _like_button_sx(slug, liked_by_current_user, csrf, ctx)
gallery_final = sx_call("market-detail-no-image", gallery_final = sx_call("market-detail-no-image",
like=SxExpr(like_sx) if like_sx else None) like=like_sx or None)
# Stickers below gallery # Stickers below gallery
stickers_sx = "" stickers_sx = ""
@@ -206,8 +206,8 @@ def _product_detail_sx(d: dict, ctx: dict) -> str:
return sx_call( return sx_call(
"market-detail-layout", "market-detail-layout",
gallery=SxExpr(gallery_final), gallery=SxExpr(gallery_final),
stickers=SxExpr(stickers_sx) if stickers_sx else None, stickers=stickers_sx or None,
details=SxExpr(details_sx), details=details_sx,
) )

View File

@@ -45,7 +45,7 @@ async def _render_checkout_return(ctx: dict, order=None, status: str = "",
else: else:
img = sx_call("order-item-no-image") img = sx_call("order-item-no-image")
item_parts.append(sx_call("order-item-row", item_parts.append(sx_call("order-item-row",
href=product_url, img=SxExpr(img), href=product_url, img=img,
title=item.product_title or "Unknown product", title=item.product_title or "Unknown product",
pid=f"Product ID: {item.product_id}", pid=f"Product ID: {item.product_id}",
qty=f"Qty: {item.quantity}", qty=f"Qty: {item.quantity}",
@@ -109,11 +109,11 @@ async def _render_checkout_return(ctx: dict, order=None, status: str = "",
status_msg = sx_call("checkout-return-paid") status_msg = sx_call("checkout-return-paid")
content = sx_call("checkout-return-content", content = sx_call("checkout-return-content",
summary=SxExpr(summary), summary=summary,
items=SxExpr(items) if items else None, items=items or None,
calendar=SxExpr(calendar) if calendar else None, calendar=calendar or None,
tickets=SxExpr(tickets) if tickets else None, tickets=tickets or None,
status_message=SxExpr(status_msg) if status_msg else None, status_message=status_msg or None,
) )
account_url = call_url(ctx, "account_url", "") account_url = call_url(ctx, "account_url", "")

View File

@@ -71,7 +71,6 @@ def register() -> Blueprint:
if not hosted_url: if not hosted_url:
from shared.sx.page import get_template_context from shared.sx.page import get_template_context
from shared.sx.helpers import sx_call, root_header_sx, header_child_sx, full_page_sx, call_url from shared.sx.helpers import sx_call, root_header_sx, header_child_sx, full_page_sx, call_url
from shared.sx.parser import SxExpr
from shared.infrastructure.urls import cart_url from shared.infrastructure.urls import cart_url
tctx = await get_template_context() tctx = await get_template_context()
account_url = call_url(tctx, "account_url", "") account_url = call_url(tctx, "account_url", "")
@@ -82,7 +81,7 @@ def register() -> Blueprint:
content = sx_call( content = sx_call(
"checkout-error-content", "checkout-error-content",
msg="No hosted checkout URL returned from SumUp when trying to reopen payment.", msg="No hosted checkout URL returned from SumUp when trying to reopen payment.",
order=SxExpr(order_sx), order=order_sx,
back_url=cart_url("/"), back_url=cart_url("/"),
) )
html = await full_page_sx(tctx, header_rows=hdr, filter=filt, content=content) html = await full_page_sx(tctx, header_rows=hdr, filter=filt, content=content)

View File

@@ -1,4 +1,5 @@
;; Orders account-nav-item fragment handler ;; Orders account-nav-item fragment handler
;; returns: sx
;; ;;
;; Renders the "orders" link for the account dashboard nav. ;; Renders the "orders" link for the account dashboard nav.

View File

@@ -1,4 +1,5 @@
;; Relations container-nav fragment handler ;; Relations container-nav fragment handler
;; returns: sx
;; ;;
;; Generic navigation fragment driven by the relation registry. ;; Generic navigation fragment driven by the relation registry.
;; Renders nav items for all related entities of a container. ;; Renders nav items for all related entities of a container.

View File

@@ -1010,10 +1010,10 @@ async def async_eval_to_sx(
ctx = RequestContext() ctx = RequestContext()
result = await _aser(expr, env, ctx) result = await _aser(expr, env, ctx)
if isinstance(result, SxExpr): if isinstance(result, SxExpr):
return result.source return result
if result is None or result is NIL: if result is None or result is NIL:
return "" return SxExpr("")
return serialize(result) return SxExpr(serialize(result))
async def async_eval_slot_to_sx( async def async_eval_slot_to_sx(
@@ -1039,10 +1039,10 @@ async def async_eval_slot_to_sx(
if isinstance(comp, Component): if isinstance(comp, Component):
result = await _aser_component(comp, expr[1:], env, ctx) result = await _aser_component(comp, expr[1:], env, ctx)
if isinstance(result, SxExpr): if isinstance(result, SxExpr):
return result.source return result
if result is None or result is NIL: if result is None or result is NIL:
return "" return SxExpr("")
return serialize(result) return SxExpr(serialize(result))
else: else:
import logging import logging
logging.getLogger("sx.eval").error( logging.getLogger("sx.eval").error(
@@ -1056,14 +1056,10 @@ async def async_eval_slot_to_sx(
# Fall back to normal async_eval_to_sx # Fall back to normal async_eval_to_sx
result = await _aser(expr, env, ctx) result = await _aser(expr, env, ctx)
if isinstance(result, SxExpr): if isinstance(result, SxExpr):
return result.source
if result is None or result is NIL:
return ""
# Page helpers return SX source strings from render_to_sx() —
# pass through directly instead of quoting via serialize().
if isinstance(result, str):
return result return result
return serialize(result) if result is None or result is NIL:
return SxExpr("")
return SxExpr(serialize(result))
async def _aser(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any: async def _aser(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any:
@@ -1071,10 +1067,10 @@ async def _aser(expr: Any, env: dict[str, Any], ctx: RequestContext) -> Any:
for everything else.""" for everything else."""
if isinstance(expr, (int, float, bool)): if isinstance(expr, (int, float, bool)):
return expr return expr
if isinstance(expr, str):
return expr
if isinstance(expr, SxExpr): if isinstance(expr, SxExpr):
return expr return expr
if isinstance(expr, str):
return expr
if expr is None or expr is NIL: if expr is None or expr is NIL:
return NIL return NIL

View File

@@ -111,16 +111,19 @@ async def execute_handler(
service_name: str, service_name: str,
args: dict[str, str] | None = None, args: dict[str, str] | None = None,
) -> str: ) -> str:
"""Execute a declarative handler and return rendered sx/HTML string. """Execute a declarative handler and return SX wire format (``SxExpr``).
Uses the async evaluator+renderer so I/O primitives (``query``, Uses the async evaluator so I/O primitives (``query``, ``service``,
``service``, ``request-arg``, etc.) are awaited inline within ``request-arg``, etc.) are awaited inline within control flow.
control flow — no collect-then-substitute limitations.
Returns ``SxExpr`` — pre-built sx source. Callers like
``fetch_fragment`` check ``content-type: text/sx`` and wrap the
response in ``SxExpr`` when consuming cross-service fragments.
1. Build env from component env + handler closure 1. Build env from component env + handler closure
2. Bind handler params from args (typically request.args) 2. Bind handler params from args (typically request.args)
3. Evaluate + render via async_render (handles I/O inline) 3. Evaluate via ``async_eval_to_sx`` (I/O inline, components serialized)
4. Return rendered string 4. Return ``SxExpr`` wire format
""" """
from .jinja_bridge import get_component_env, _get_request_context from .jinja_bridge import get_component_env, _get_request_context
from .async_eval import async_eval_to_sx from .async_eval import async_eval_to_sx

View File

@@ -74,10 +74,10 @@ async def root_header_sx(ctx: dict, *, oob: bool = False) -> str:
) )
def mobile_menu_sx(*sections: str) -> str: def mobile_menu_sx(*sections: str) -> SxExpr:
"""Assemble mobile menu from pre-built sections (deepest first).""" """Assemble mobile menu from pre-built sections (deepest first)."""
parts = [s for s in sections if s] parts = [s for s in sections if s]
return "(<> " + " ".join(parts) + ")" if parts else "" return SxExpr("(<> " + " ".join(parts) + ")") if parts else SxExpr("")
async def mobile_root_nav_sx(ctx: dict) -> str: async def mobile_root_nav_sx(ctx: dict) -> str:
@@ -96,13 +96,13 @@ async def mobile_root_nav_sx(ctx: dict) -> str:
# Shared nav-item builders — used by BOTH desktop headers and mobile menus # Shared nav-item builders — used by BOTH desktop headers and mobile menus
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
async def _post_nav_items_sx(ctx: dict) -> str: async def _post_nav_items_sx(ctx: dict) -> SxExpr:
"""Build post-level nav items (container_nav + admin cog). Shared by """Build post-level nav items (container_nav + admin cog). Shared by
``post_header_sx`` (desktop) and ``post_mobile_nav_sx`` (mobile).""" ``post_header_sx`` (desktop) and ``post_mobile_nav_sx`` (mobile)."""
post = ctx.get("post") or {} post = ctx.get("post") or {}
slug = post.get("slug", "") slug = post.get("slug", "")
if not slug: if not slug:
return "" return SxExpr("")
parts: list[str] = [] parts: list[str] = []
page_cart_count = ctx.get("page_cart_count", 0) page_cart_count = ctx.get("page_cart_count", 0)
if page_cart_count and page_cart_count > 0: if page_cart_count and page_cart_count > 0:
@@ -130,11 +130,11 @@ async def _post_nav_items_sx(ctx: dict) -> str:
is_admin_page=is_admin_page or None) is_admin_page=is_admin_page or None)
if admin_nav: if admin_nav:
parts.append(admin_nav) parts.append(admin_nav)
return "(<> " + " ".join(parts) + ")" if parts else "" return SxExpr("(<> " + " ".join(parts) + ")") if parts else SxExpr("")
async def _post_admin_nav_items_sx(ctx: dict, slug: str, async def _post_admin_nav_items_sx(ctx: dict, slug: str,
selected: str = "") -> str: selected: str = "") -> SxExpr:
"""Build post-admin nav items (calendars, markets, etc.). Shared by """Build post-admin nav items (calendars, markets, etc.). Shared by
``post_admin_header_sx`` (desktop) and mobile menu.""" ``post_admin_header_sx`` (desktop) and mobile menu."""
select_colours = ctx.get("select_colours", "") select_colours = ctx.get("select_colours", "")
@@ -158,7 +158,7 @@ async def _post_admin_nav_items_sx(ctx: dict, slug: str,
parts.append(await _render_to_sx("nav-link", href=href, label=label, parts.append(await _render_to_sx("nav-link", href=href, label=label,
select_colours=select_colours, select_colours=select_colours,
is_selected=is_sel or None)) is_selected=is_sel or None))
return "(<> " + " ".join(parts) + ")" if parts else "" return SxExpr("(<> " + " ".join(parts) + ")") if parts else SxExpr("")
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -177,7 +177,7 @@ async def post_mobile_nav_sx(ctx: dict) -> str:
label=title, label=title,
href=call_url(ctx, "blog_url", f"/{slug}/"), href=call_url(ctx, "blog_url", f"/{slug}/"),
level=1, level=1,
items=SxExpr(nav), items=nav,
) )
@@ -220,8 +220,8 @@ async def post_header_sx(ctx: dict, *, oob: bool = False, child: str = "") -> st
return await _render_to_sx("menu-row-sx", return await _render_to_sx("menu-row-sx",
id="post-row", level=1, id="post-row", level=1,
link_href=link_href, link_href=link_href,
link_label_content=SxExpr(label_sx), link_label_content=label_sx,
nav=SxExpr(nav_sx) if nav_sx else None, nav=nav_sx,
child_id="post-header-child", child_id="post-header-child",
child=SxExpr(child) if child else None, child=SxExpr(child) if child else None,
oob=oob, external=True, oob=oob, external=True,
@@ -244,8 +244,8 @@ async def post_admin_header_sx(ctx: dict, slug: str, *, oob: bool = False,
return await _render_to_sx("menu-row-sx", return await _render_to_sx("menu-row-sx",
id="post-admin-row", level=2, id="post-admin-row", level=2,
link_href=admin_href, link_href=admin_href,
link_label_content=SxExpr(label_sx), link_label_content=label_sx,
nav=SxExpr(nav_sx) if nav_sx else None, nav=nav_sx,
child_id="post-admin-header-child", oob=oob, child_id="post-admin-header-child", oob=oob,
) )
@@ -352,7 +352,7 @@ async def _render_to_sx_with_env(__name: str, extra_env: dict, **kwargs: Any) ->
env = dict(get_component_env()) env = dict(get_component_env())
env.update(extra_env) env.update(extra_env)
ctx = _get_request_context() ctx = _get_request_context()
return await async_eval_slot_to_sx(ast, env, ctx) return SxExpr(await async_eval_slot_to_sx(ast, env, ctx))
async def _render_to_sx(__name: str, **kwargs: Any) -> str: async def _render_to_sx(__name: str, **kwargs: Any) -> str:
@@ -371,7 +371,7 @@ async def _render_to_sx(__name: str, **kwargs: Any) -> str:
ast = _build_component_ast(__name, **kwargs) ast = _build_component_ast(__name, **kwargs)
env = dict(get_component_env()) env = dict(get_component_env())
ctx = _get_request_context() ctx = _get_request_context()
return await async_eval_to_sx(ast, env, ctx) return SxExpr(await async_eval_to_sx(ast, env, ctx))
# Backwards-compat alias — layout infrastructure still imports this. # Backwards-compat alias — layout infrastructure still imports this.
@@ -420,7 +420,7 @@ def sx_call(component_name: str, **kwargs: Any) -> str:
parts.append("(list " + " ".join(items) + ")") parts.append("(list " + " ".join(items) + ")")
else: else:
parts.append(serialize(val)) parts.append(serialize(val))
return "(" + " ".join(parts) + ")" return SxExpr("(" + " ".join(parts) + ")")

View File

@@ -25,31 +25,37 @@ from .types import Keyword, Symbol, NIL
# SxExpr — pre-built sx source marker # SxExpr — pre-built sx source marker
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
class SxExpr: class SxExpr(str):
"""Pre-built sx source that serialize() outputs unquoted. """Pre-built sx source that serialize() outputs unquoted.
``SxExpr`` is a ``str`` subclass, so it works everywhere a plain
string does (join, startswith, f-strings, isinstance checks). The
only difference: ``serialize()`` emits it unquoted instead of
wrapping it in double-quotes.
Use this to nest sx call strings inside other sx_call() invocations Use this to nest sx call strings inside other sx_call() invocations
without them being quoted as strings:: without them being quoted as strings::
sx_call("parent", child=SxExpr(sx_call("child", x=1))) sx_call("parent", child=sx_call("child", x=1))
# => (~parent :child (~child :x 1)) # => (~parent :child (~child :x 1))
""" """
__slots__ = ("source",)
def __init__(self, source: str): def __new__(cls, source: str = "") -> "SxExpr":
self.source = source return str.__new__(cls, source)
@property
def source(self) -> str:
"""The raw SX source string (backward compat)."""
return str.__str__(self)
def __repr__(self) -> str: def __repr__(self) -> str:
return f"SxExpr({self.source!r})" return f"SxExpr({str.__repr__(self)})"
def __str__(self) -> str:
return self.source
def __add__(self, other: object) -> "SxExpr": def __add__(self, other: object) -> "SxExpr":
return SxExpr(self.source + str(other)) return SxExpr(str.__add__(self, str(other)))
def __radd__(self, other: object) -> "SxExpr": def __radd__(self, other: object) -> "SxExpr":
return SxExpr(str(other) + self.source) return SxExpr(str.__add__(str(other), self))
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -283,7 +289,26 @@ def _parse_map(tok: Tokenizer) -> dict[str, Any]:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def serialize(expr: Any, indent: int = 0, pretty: bool = False) -> str: def serialize(expr: Any, indent: int = 0, pretty: bool = False) -> str:
"""Serialize a value back to s-expression text.""" """Serialize a value back to s-expression text.
Type dispatch order (first match wins):
- ``SxExpr`` → emitted unquoted (pre-built sx source)
- ``list`` → ``(head ...)`` (s-expression list)
- ``Symbol`` → bare name
- ``Keyword`` → ``:name``
- ``str`` → ``"quoted"`` (with escapes)
- ``bool`` → ``true`` / ``false``
- ``int/float`` → numeric literal
- ``None/NIL`` → ``nil``
- ``dict`` → ``{:key val ...}``
List serialization conventions (for ``sx_call`` kwargs):
- ``(list ...)`` — data array: client gets iterable for map/filter
- ``(<> ...)`` — rendered content: client treats as DocumentFragment
- ``(head ...)`` — AST: head is called as function (never use for data)
"""
if isinstance(expr, SxExpr): if isinstance(expr, SxExpr):
return expr.source return expr.source

View File

@@ -16,30 +16,27 @@ def _register_sx_layouts() -> None:
async def _sx_full_headers(ctx: dict, **kw: Any) -> str: async def _sx_full_headers(ctx: dict, **kw: Any) -> str:
"""Full headers for sx home page: root + sx menu row.""" """Full headers for sx home page: root + sx menu row."""
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
main_nav = _main_nav_sx(kw.get("section")) main_nav = _main_nav_sx(kw.get("section"))
sx_row = _sx_header_sx(main_nav) sx_row = _sx_header_sx(main_nav)
return await render_to_sx_with_env("sx-layout-full", {}, return await render_to_sx_with_env("sx-layout-full", {},
sx_row=SxExpr(sx_row)) sx_row=sx_row)
async def _sx_oob_headers(ctx: dict, **kw: Any) -> str: async def _sx_oob_headers(ctx: dict, **kw: Any) -> str:
"""OOB headers for sx home page.""" """OOB headers for sx home page."""
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
main_nav = _main_nav_sx(kw.get("section")) main_nav = _main_nav_sx(kw.get("section"))
sx_row = _sx_header_sx(main_nav) sx_row = _sx_header_sx(main_nav)
rows = await render_to_sx_with_env("sx-layout-full", {}, rows = await render_to_sx_with_env("sx-layout-full", {},
sx_row=SxExpr(sx_row)) sx_row=sx_row)
return await oob_header_sx("root-header-child", "sx-header-child", rows) return await oob_header_sx("root-header-child", "sx-header-child", rows)
async def _sx_section_full_headers(ctx: dict, **kw: Any) -> str: async def _sx_section_full_headers(ctx: dict, **kw: Any) -> str:
"""Full headers for sx section pages: root + sx row + sub row.""" """Full headers for sx section pages: root + sx row + sub row."""
from shared.sx.helpers import render_to_sx_with_env from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
section = kw.get("section", "") section = kw.get("section", "")
sub_label = kw.get("sub_label", section) sub_label = kw.get("sub_label", section)
@@ -51,13 +48,12 @@ async def _sx_section_full_headers(ctx: dict, **kw: Any) -> str:
sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected) sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected)
sx_row = _sx_header_sx(main_nav, child=sub_row) sx_row = _sx_header_sx(main_nav, child=sub_row)
return await render_to_sx_with_env("sx-section-layout-full", {}, return await render_to_sx_with_env("sx-section-layout-full", {},
sx_row=SxExpr(sx_row)) sx_row=sx_row)
async def _sx_section_oob_headers(ctx: dict, **kw: Any) -> str: async def _sx_section_oob_headers(ctx: dict, **kw: Any) -> str:
"""OOB headers for sx section pages.""" """OOB headers for sx section pages."""
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
section = kw.get("section", "") section = kw.get("section", "")
sub_label = kw.get("sub_label", section) sub_label = kw.get("sub_label", section)
@@ -69,7 +65,7 @@ async def _sx_section_oob_headers(ctx: dict, **kw: Any) -> str:
sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected) sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected)
sx_row = _sx_header_sx(main_nav, child=sub_row) sx_row = _sx_header_sx(main_nav, child=sub_row)
rows = await render_to_sx_with_env("sx-section-layout-full", {}, rows = await render_to_sx_with_env("sx-section-layout-full", {},
sx_row=SxExpr(sx_row)) sx_row=sx_row)
return await oob_header_sx("root-header-child", "sx-header-child", rows) return await oob_header_sx("root-header-child", "sx-header-child", rows)

View File

@@ -68,7 +68,7 @@ def _sx_header_sx(nav: str | None = None, *, child: str | None = None) -> str:
return sx_call("menu-row-sx", return sx_call("menu-row-sx",
id="sx-row", level=1, colour="violet", id="sx-row", level=1, colour="violet",
link_href="/", link_label="sx", link_href="/", link_label="sx",
link_label_content=SxExpr(label_sx), link_label_content=label_sx,
nav=SxExpr(nav) if nav else None, nav=SxExpr(nav) if nav else None,
child_id="sx-header-child", child_id="sx-header-child",
child=SxExpr(child) if child else None, child=SxExpr(child) if child else None,

View File

@@ -5,7 +5,7 @@ import os
from datetime import datetime from datetime import datetime
from shared.sx.jinja_bridge import load_service_components from shared.sx.jinja_bridge import load_service_components
from shared.sx.helpers import sx_call, SxExpr, render_to_sx_with_env, full_page_sx from shared.sx.helpers import sx_call, render_to_sx_with_env, full_page_sx
# Load test-specific .sx components at import time # Load test-specific .sx components at import time
load_service_components(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) load_service_components(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
@@ -98,7 +98,7 @@ async def render_dashboard_page_sx(ctx: dict, result: dict | None,
inner = sx_call("test-results-partial", inner = sx_call("test-results-partial",
summary_data=summary_data, sections=sections, has_failures=has_failures) summary_data=summary_data, sections=sections, has_failures=has_failures)
content = sx_call("test-results-wrap", running=running, inner=SxExpr(inner)) content = sx_call("test-results-wrap", running=running, inner=inner)
hdr = await render_to_sx_with_env("test-layout-full", {}, hdr = await render_to_sx_with_env("test-layout-full", {},
services=_service_list(), services=_service_list(),
active_service=active_service, active_service=active_service,
@@ -126,7 +126,7 @@ async def render_results_partial_sx(result: dict | None, running: bool,
inner = sx_call("test-results-partial", inner = sx_call("test-results-partial",
summary_data=summary_data, sections=sections, has_failures=has_failures) summary_data=summary_data, sections=sections, has_failures=has_failures)
return sx_call("test-results-wrap", running=running, inner=SxExpr(inner)) return sx_call("test-results-wrap", running=running, inner=inner)
async def render_test_detail_page_sx(ctx: dict, test: dict) -> str: async def render_test_detail_page_sx(ctx: dict, test: dict) -> str: