diff --git a/account/bp/account/routes.py b/account/bp/account/routes.py
index 835353f..c4d2ce0 100644
--- a/account/bp/account/routes.py
+++ b/account/bp/account/routes.py
@@ -56,6 +56,6 @@ def register(url_prefix="/"):
await g.s.flush()
from sx.sx_components import render_newsletter_toggle
- return sx_response(render_newsletter_toggle(un))
+ return sx_response(await render_newsletter_toggle(un))
return account_bp
diff --git a/account/sx/sx_components.py b/account/sx/sx_components.py
index a094a5e..9073111 100644
--- a/account/sx/sx_components.py
+++ b/account/sx/sx_components.py
@@ -11,7 +11,7 @@ from typing import Any
from shared.sx.jinja_bridge import load_service_components
from shared.sx.helpers import (
- sx_call, SxExpr,
+ render_to_sx,
root_header_sx, full_page_sx,
)
@@ -28,9 +28,10 @@ async def render_login_page(ctx: dict) -> str:
"""Full page: login form."""
error = ctx.get("error", "")
email = ctx.get("email", "")
- hdr = root_header_sx(ctx)
- content = sx_call("account-login-content", error=error or None, email=email)
- return full_page_sx(ctx, header_rows=hdr,
+ hdr = await root_header_sx(ctx)
+ content = await render_to_sx("account-login-content",
+ error=error or None, email=email)
+ return await full_page_sx(ctx, header_rows=hdr,
content=content,
meta_html='
Login \u2014 Rose Ash')
@@ -39,18 +40,19 @@ async def render_device_page(ctx: dict) -> str:
"""Full page: device authorization form."""
error = ctx.get("error", "")
code = ctx.get("code", "")
- hdr = root_header_sx(ctx)
- content = sx_call("account-device-content", error=error or None, code=code)
- return full_page_sx(ctx, header_rows=hdr,
+ hdr = await root_header_sx(ctx)
+ content = await render_to_sx("account-device-content",
+ error=error or None, code=code)
+ return await full_page_sx(ctx, header_rows=hdr,
content=content,
meta_html='Authorize Device \u2014 Rose Ash')
async def render_device_approved_page(ctx: dict) -> str:
"""Full page: device approved."""
- hdr = root_header_sx(ctx)
- content = sx_call("account-device-approved")
- return full_page_sx(ctx, header_rows=hdr,
+ hdr = await root_header_sx(ctx)
+ content = await render_to_sx("account-device-approved")
+ return await full_page_sx(ctx, header_rows=hdr,
content=content,
meta_html='Device Authorized \u2014 Rose Ash')
@@ -59,10 +61,10 @@ async def render_check_email_page(ctx: dict) -> str:
"""Full page: check email after magic link sent."""
email = ctx.get("email", "")
email_error = ctx.get("email_error")
- hdr = root_header_sx(ctx)
- content = sx_call("account-check-email-content",
- email=email, email_error=email_error)
- return full_page_sx(ctx, header_rows=hdr,
+ hdr = await root_header_sx(ctx)
+ content = await render_to_sx("account-check-email-content",
+ email=email, email_error=email_error)
+ return await full_page_sx(ctx, header_rows=hdr,
content=content,
meta_html='Check your email \u2014 Rose Ash')
@@ -71,7 +73,7 @@ async def render_check_email_page(ctx: dict) -> str:
# Public API: Fragment renderers for POST handlers
# ---------------------------------------------------------------------------
-def render_newsletter_toggle(un) -> str:
+async def render_newsletter_toggle(un) -> str:
"""Render a newsletter toggle switch for POST response."""
from shared.browser.app.csrf import generate_csrf_token
@@ -94,7 +96,7 @@ def render_newsletter_toggle(un) -> str:
translate = "translate-x-1"
checked = "false"
- return sx_call(
+ return await render_to_sx(
"account-newsletter-toggle",
id=f"nl-{nid}", url=toggle_url,
hdrs=f'{{"X-CSRFToken": "{csrf}"}}',
@@ -103,27 +105,3 @@ def render_newsletter_toggle(un) -> str:
checked=checked,
knob_cls=f"inline-block h-4 w-4 rounded-full bg-white shadow transform transition-transform {translate}",
)
-
-
-# ---------------------------------------------------------------------------
-# Internal helpers
-# ---------------------------------------------------------------------------
-
-def _fragment_content(frag: object) -> str:
- """Convert a fragment response to sx content string.
-
- SxExpr (from text/sx responses) is embedded as-is; plain strings
- (from text/html) are wrapped in ``~rich-text``.
- """
- from shared.sx.parser import SxExpr
- if isinstance(frag, SxExpr):
- return frag.source
- s = str(frag) if frag else ""
- if not s:
- return ""
- return f'(~rich-text :html "{_sx_escape(s)}")'
-
-
-def _sx_escape(s: str) -> str:
- """Escape a string for embedding in sx string literals."""
- return s.replace("\\", "\\\\").replace('"', '\\"')
diff --git a/account/sxc/pages/__init__.py b/account/sxc/pages/__init__.py
index 12aebc4..3624fb7 100644
--- a/account/sxc/pages/__init__.py
+++ b/account/sxc/pages/__init__.py
@@ -26,43 +26,51 @@ def _register_account_layouts() -> None:
register_custom_layout("account", _account_full, _account_oob, _account_mobile)
-def _account_full(ctx: dict, **kw: Any) -> str:
- from shared.sx.helpers import root_header_sx, header_child_sx, call_url, sx_call, SxExpr
+async def _account_full(ctx: dict, **kw: Any) -> str:
+ from shared.sx.helpers import root_header_sx, header_child_sx, render_to_sx
- root_hdr = root_header_sx(ctx)
- auth_hdr = sx_call("auth-header-row",
- account_url=call_url(ctx, "account_url", ""),
+ root_hdr = await root_header_sx(ctx)
+ auth_hdr = await render_to_sx("auth-header-row",
+ account_url=_call_url(ctx, "account_url", ""),
select_colours=ctx.get("select_colours", ""),
account_nav=_as_sx_nav(ctx),
)
- hdr_child = header_child_sx(auth_hdr)
+ hdr_child = await header_child_sx(auth_hdr)
return "(<> " + root_hdr + " " + hdr_child + ")"
-def _account_oob(ctx: dict, **kw: Any) -> str:
- from shared.sx.helpers import root_header_sx, call_url, sx_call
+async def _account_oob(ctx: dict, **kw: Any) -> str:
+ from shared.sx.helpers import root_header_sx, render_to_sx
- auth_hdr = sx_call("auth-header-row",
- account_url=call_url(ctx, "account_url", ""),
+ auth_hdr = await render_to_sx("auth-header-row",
+ account_url=_call_url(ctx, "account_url", ""),
select_colours=ctx.get("select_colours", ""),
account_nav=_as_sx_nav(ctx),
oob=True,
)
- return "(<> " + auth_hdr + " " + root_header_sx(ctx, oob=True) + ")"
+ return "(<> " + auth_hdr + " " + await root_header_sx(ctx, oob=True) + ")"
-def _account_mobile(ctx: dict, **kw: Any) -> str:
- from shared.sx.helpers import mobile_menu_sx, mobile_root_nav_sx, sx_call, SxExpr, call_url
+async def _account_mobile(ctx: dict, **kw: Any) -> str:
+ from shared.sx.helpers import mobile_menu_sx, mobile_root_nav_sx, render_to_sx
+ from shared.sx.parser import SxExpr
ctx = _inject_account_nav(ctx)
- nav_items = sx_call("auth-nav-items",
- account_url=call_url(ctx, "account_url", ""),
+ nav_items = await render_to_sx("auth-nav-items",
+ account_url=_call_url(ctx, "account_url", ""),
select_colours=ctx.get("select_colours", ""),
account_nav=_as_sx_nav(ctx),
)
- auth_section = sx_call("mobile-menu-section",
+ auth_section = await render_to_sx("mobile-menu-section",
label="account", href="/", level=1, colour="sky",
items=SxExpr(nav_items))
- return mobile_menu_sx(auth_section, mobile_root_nav_sx(ctx))
+ return mobile_menu_sx(auth_section, await mobile_root_nav_sx(ctx))
+
+
+def _call_url(ctx: dict, key: str, path: str = "/") -> str:
+ fn = ctx.get(key)
+ if callable(fn):
+ return fn(path)
+ return str(fn or "") + path
def _inject_account_nav(ctx: dict) -> dict:
@@ -75,7 +83,7 @@ def _inject_account_nav(ctx: dict) -> dict:
def _as_sx_nav(ctx: dict) -> Any:
- """Convert account_nav fragment to SxExpr for use in sx_call."""
+ """Convert account_nav fragment to SxExpr for use in component calls."""
from shared.sx.helpers import _as_sx
ctx = _inject_account_nav(ctx)
return _as_sx(ctx.get("account_nav"))
@@ -100,8 +108,7 @@ async def _h_newsletters_content(**kw):
from sqlalchemy import select
from shared.models import UserNewsletter
from shared.models.ghost_membership_entities import GhostNewsletter
- from shared.sx.helpers import sx_call, SxExpr
- from shared.sx.parser import serialize
+ from shared.sx.helpers import render_to_sx
result = await g.s.execute(
select(GhostNewsletter).order_by(GhostNewsletter.name)
@@ -131,8 +138,8 @@ async def _h_newsletters_content(**kw):
# Call account_url to get the base URL string
account_url_str = account_url("") if callable(account_url) else str(account_url or "")
- return sx_call("account-newsletters-content",
- newsletter_list=SxExpr(serialize(newsletter_list)),
+ return await render_to_sx("account-newsletters-content",
+ newsletter_list=newsletter_list,
account_url=account_url_str)
@@ -148,5 +155,11 @@ async def _h_fragment_content(slug=None, **kw):
)
if not fragment_html:
abort(404)
- from sx.sx_components import _fragment_content
- return _fragment_content(fragment_html)
+ from shared.sx.parser import SxExpr
+ if isinstance(fragment_html, SxExpr):
+ return fragment_html.source
+ s = str(fragment_html) if fragment_html else ""
+ if not s:
+ return ""
+ from shared.sx.helpers import render_to_sx
+ return await render_to_sx("rich-text", html=s)
diff --git a/blog/bp/blog/routes.py b/blog/bp/blog/routes.py
index 1a3001f..b51ee40 100644
--- a/blog/bp/blog/routes.py
+++ b/blog/bp/blog/routes.py
@@ -235,7 +235,7 @@ def register(url_prefix, title):
from shared.sx.page import get_template_context
from sx.sx_components import render_new_post_page, render_editor_panel
tctx = await get_template_context()
- tctx["editor_html"] = render_editor_panel(save_error="Invalid JSON in editor content.")
+ tctx["editor_html"] = await render_editor_panel(save_error="Invalid JSON in editor content.")
html = await render_new_post_page(tctx)
return await make_response(html, 400)
@@ -244,7 +244,7 @@ def register(url_prefix, title):
from shared.sx.page import get_template_context
from sx.sx_components import render_new_post_page, render_editor_panel
tctx = await get_template_context()
- tctx["editor_html"] = render_editor_panel(save_error=reason)
+ tctx["editor_html"] = await render_editor_panel(save_error=reason)
html = await render_new_post_page(tctx)
return await make_response(html, 400)
@@ -291,7 +291,7 @@ def register(url_prefix, title):
from shared.sx.page import get_template_context
from sx.sx_components import render_new_post_page, render_editor_panel
tctx = await get_template_context()
- tctx["editor_html"] = render_editor_panel(save_error="Invalid JSON in editor content.", is_page=True)
+ tctx["editor_html"] = await render_editor_panel(save_error="Invalid JSON in editor content.", is_page=True)
tctx["is_page"] = True
html = await render_new_post_page(tctx)
return await make_response(html, 400)
@@ -301,7 +301,7 @@ def register(url_prefix, title):
from shared.sx.page import get_template_context
from sx.sx_components import render_new_post_page, render_editor_panel
tctx = await get_template_context()
- tctx["editor_html"] = render_editor_panel(save_error=reason, is_page=True)
+ tctx["editor_html"] = await render_editor_panel(save_error=reason, is_page=True)
tctx["is_page"] = True
html = await render_new_post_page(tctx)
return await make_response(html, 400)
diff --git a/blog/bp/menu_items/routes.py b/blog/bp/menu_items/routes.py
index e43381d..166a81c 100644
--- a/blog/bp/menu_items/routes.py
+++ b/blog/bp/menu_items/routes.py
@@ -17,10 +17,10 @@ from shared.sx.helpers import sx_response
def register():
bp = Blueprint("menu_items", __name__, url_prefix='/settings/menu_items')
- def get_menu_items_nav_oob_sync(menu_items):
+ async def get_menu_items_nav_oob_async(menu_items):
"""Helper to generate OOB update for root nav menu items"""
from sx.sx_components import render_menu_items_nav_oob
- return render_menu_items_nav_oob(menu_items)
+ return await render_menu_items_nav_oob(menu_items)
@bp.get("/new/")
@require_admin
@@ -51,8 +51,8 @@ def register():
# Get updated list and nav OOB
menu_items = await get_all_menu_items(g.s)
from sx.sx_components import render_menu_items_list
- html = render_menu_items_list(menu_items)
- nav_oob = get_menu_items_nav_oob_sync(menu_items)
+ html = await render_menu_items_list(menu_items)
+ nav_oob = await get_menu_items_nav_oob_async(menu_items)
return sx_response(html + nav_oob)
except MenuItemError as e:
@@ -91,8 +91,8 @@ def register():
# Get updated list and nav OOB
menu_items = await get_all_menu_items(g.s)
from sx.sx_components import render_menu_items_list
- html = render_menu_items_list(menu_items)
- nav_oob = get_menu_items_nav_oob_sync(menu_items)
+ html = await render_menu_items_list(menu_items)
+ nav_oob = await get_menu_items_nav_oob_async(menu_items)
return sx_response(html + nav_oob)
except MenuItemError as e:
@@ -112,8 +112,8 @@ def register():
# Get updated list and nav OOB
menu_items = await get_all_menu_items(g.s)
from sx.sx_components import render_menu_items_list
- html = render_menu_items_list(menu_items)
- nav_oob = get_menu_items_nav_oob_sync(menu_items)
+ html = await render_menu_items_list(menu_items)
+ nav_oob = await get_menu_items_nav_oob_async(menu_items)
return sx_response(html + nav_oob)
@bp.get("/pages/search/")
@@ -128,7 +128,7 @@ def register():
has_more = (page * per_page) < total
from sx.sx_components import render_page_search_results
- return sx_response(render_page_search_results(pages, query, page, has_more))
+ return sx_response(await render_page_search_results(pages, query, page, has_more))
@bp.post("/reorder/")
@require_admin
@@ -153,8 +153,8 @@ def register():
# Get updated list and nav OOB
menu_items = await get_all_menu_items(g.s)
from sx.sx_components import render_menu_items_list
- html = render_menu_items_list(menu_items)
- nav_oob = get_menu_items_nav_oob_sync(menu_items)
+ html = await render_menu_items_list(menu_items)
+ nav_oob = await get_menu_items_nav_oob_async(menu_items)
return sx_response(html + nav_oob)
return bp
diff --git a/blog/bp/post/admin/routes.py b/blog/bp/post/admin/routes.py
index 227b658..4479cd1 100644
--- a/blog/bp/post/admin/routes.py
+++ b/blog/bp/post/admin/routes.py
@@ -90,7 +90,7 @@ def register():
features = result.get("features", {})
from sx.sx_components import render_features_panel
- html = render_features_panel(
+ html = await render_features_panel(
features, post,
sumup_configured=result.get("sumup_configured", False),
sumup_merchant_code=result.get("sumup_merchant_code") or "",
@@ -129,7 +129,7 @@ def register():
features = result.get("features", {})
from sx.sx_components import render_features_panel
- html = render_features_panel(
+ html = await render_features_panel(
features, post,
sumup_configured=result.get("sumup_configured", False),
sumup_merchant_code=result.get("sumup_merchant_code") or "",
@@ -259,8 +259,8 @@ def register():
from sx.sx_components import render_associated_entries, render_nav_entries_oob
post = g.post_data["post"]
- admin_list = render_associated_entries(all_calendars, associated_entry_ids, post["slug"])
- nav_entries_html = render_nav_entries_oob(associated_entries, calendars, post)
+ admin_list = await render_associated_entries(all_calendars, associated_entry_ids, post["slug"])
+ nav_entries_html = await render_nav_entries_oob(associated_entries, calendars, post)
return sx_response(admin_list + nav_entries_html)
@@ -436,7 +436,7 @@ def register():
page_markets = await _fetch_page_markets(post_id)
from sx.sx_components import render_markets_panel
- return sx_response(render_markets_panel(page_markets, post))
+ return sx_response(await render_markets_panel(page_markets, post))
@bp.post("/markets/new/")
@require_admin
@@ -462,7 +462,7 @@ def register():
page_markets = await _fetch_page_markets(post_id)
from sx.sx_components import render_markets_panel
- return sx_response(render_markets_panel(page_markets, post))
+ return sx_response(await render_markets_panel(page_markets, post))
@bp.delete("/markets//")
@require_admin
@@ -482,6 +482,6 @@ def register():
page_markets = await _fetch_page_markets(post_id)
from sx.sx_components import render_markets_panel
- return sx_response(render_markets_panel(page_markets, post))
+ return sx_response(await render_markets_panel(page_markets, post))
return bp
diff --git a/blog/bp/post/routes.py b/blog/bp/post/routes.py
index 8951937..1ea2dfa 100644
--- a/blog/bp/post/routes.py
+++ b/blog/bp/post/routes.py
@@ -125,7 +125,7 @@ def register():
# Get post_id from g.post_data
if not g.user:
- return sx_response(render_like_toggle_button(slug, False, like_url), status=403)
+ return sx_response(await render_like_toggle_button(slug, False, like_url), status=403)
post_id = g.post_data["post"]["id"]
user_id = g.user.id
@@ -135,7 +135,7 @@ def register():
})
liked = result["liked"]
- return sx_response(render_like_toggle_button(slug, liked, like_url))
+ return sx_response(await render_like_toggle_button(slug, liked, like_url))
@bp.get("/w//")
async def widget_paginate(slug: str, widget_domain: str):
diff --git a/blog/bp/snippets/routes.py b/blog/bp/snippets/routes.py
index f64d00b..6e47d59 100644
--- a/blog/bp/snippets/routes.py
+++ b/blog/bp/snippets/routes.py
@@ -47,7 +47,7 @@ def register():
snippets = await _visible_snippets(g.s)
from sx.sx_components import render_snippets_list
- return sx_response(render_snippets_list(snippets, is_admin))
+ return sx_response(await render_snippets_list(snippets, is_admin))
@bp.patch("//visibility/")
@require_login
@@ -71,6 +71,6 @@ def register():
snippets = await _visible_snippets(g.s)
from sx.sx_components import render_snippets_list
- return sx_response(render_snippets_list(snippets, True))
+ return sx_response(await render_snippets_list(snippets, True))
return bp
diff --git a/blog/sx/sx_components.py b/blog/sx/sx_components.py
index 9f383c1..2c46b9f 100644
--- a/blog/sx/sx_components.py
+++ b/blog/sx/sx_components.py
@@ -13,9 +13,9 @@ from typing import Any
from markupsafe import escape
from shared.sx.jinja_bridge import load_service_components
-from shared.sx.parser import serialize as sx_serialize
+from shared.sx.parser import serialize as sx_serialize, SxExpr
from shared.sx.helpers import (
- SxExpr, sx_call,
+ render_to_sx,
call_url, get_asset_url,
root_header_sx,
post_header_sx,
@@ -53,9 +53,9 @@ _oob_header_sx = oob_header_sx
# Blog header (root-header-child -> blog-header-child)
# ---------------------------------------------------------------------------
-def _blog_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _blog_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Blog header row — empty child of root."""
- return sx_call("menu-row-sx",
+ return await render_to_sx("menu-row-sx",
id="blog-row", level=1,
link_label_content=SxExpr("(div)"),
child_id="blog-header-child", oob=oob,
@@ -65,28 +65,28 @@ def _blog_header_sx(ctx: dict, *, oob: bool = False) -> str:
# Post header helpers — thin wrapper over shared post_header_sx
# ---------------------------------------------------------------------------
-def _post_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _post_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build the post-level header row as sx — delegates to shared helper."""
- return post_header_sx(ctx, oob=oob)
+ return await post_header_sx(ctx, oob=oob)
# ---------------------------------------------------------------------------
# Post admin header
# ---------------------------------------------------------------------------
-def _post_admin_header_sx(ctx: dict, *, oob: bool = False, selected: str = "") -> str:
+async def _post_admin_header_sx(ctx: dict, *, oob: bool = False, selected: str = "") -> str:
"""Post admin header row as sx — delegates to shared helper."""
slug = (ctx.get("post") or {}).get("slug", "")
- return post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
+ return await post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
-def _post_admin_mobile_menu(ctx: dict, selected: str = "") -> str:
+async def _post_admin_mobile_menu(ctx: dict, selected: str = "") -> str:
"""Full mobile menu for any post admin page (admin + post + root)."""
slug = (ctx.get("post") or {}).get("slug", "")
return mobile_menu_sx(
- post_admin_mobile_nav_sx(ctx, slug, selected),
- post_mobile_nav_sx(ctx),
- mobile_root_nav_sx(ctx),
+ await post_admin_mobile_nav_sx(ctx, slug, selected),
+ await post_mobile_nav_sx(ctx),
+ await mobile_root_nav_sx(ctx),
)
@@ -94,15 +94,15 @@ def _post_admin_mobile_menu(ctx: dict, selected: str = "") -> str:
# Settings header (root-header-child -> root-settings-header-child)
# ---------------------------------------------------------------------------
-def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Settings header row with admin icon and nav links (sx)."""
from quart import url_for as qurl
settings_href = qurl("settings.defpage_settings_home")
- label_sx = sx_call("blog-admin-label")
- nav_sx = _settings_nav_sx(ctx)
+ label_sx = await render_to_sx("blog-admin-label")
+ nav_sx = await _settings_nav_sx(ctx)
- return sx_call("menu-row-sx",
+ return await render_to_sx("menu-row-sx",
id="root-settings-row", level=1,
link_href=settings_href,
link_label_content=SxExpr(label_sx),
@@ -112,7 +112,7 @@ def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
-def _settings_nav_sx(ctx: dict) -> str:
+async def _settings_nav_sx(ctx: dict) -> str:
"""Settings desktop nav as sx."""
from quart import url_for as qurl
@@ -126,7 +126,7 @@ def _settings_nav_sx(ctx: dict) -> str:
("settings.defpage_cache_page", "refresh", "Cache"),
]:
href = qurl(endpoint)
- parts.append(sx_call("nav-link",
+ parts.append(await render_to_sx("nav-link",
href=href, icon=f"fa fa-{icon}", label=label,
select_colours=select_colours,
))
@@ -139,15 +139,15 @@ def _settings_nav_sx(ctx: dict) -> str:
# Sub-settings headers (root-settings-header-child -> X-header-child)
# ---------------------------------------------------------------------------
-def _sub_settings_header_sx(row_id: str, child_id: str, href: str,
+async def _sub_settings_header_sx(row_id: str, child_id: str, href: str,
icon: str, label: str, ctx: dict,
*, oob: bool = False, nav_sx: str = "") -> str:
"""Generic sub-settings header row as sx."""
- label_sx = sx_call("blog-sub-settings-label",
+ label_sx = await render_to_sx("blog-sub-settings-label",
icon=f"fa fa-{icon}", label=label,
)
- return sx_call("menu-row-sx",
+ return await render_to_sx("menu-row-sx",
id=row_id, level=2,
link_href=href,
link_label_content=SxExpr(label_sx),
@@ -163,16 +163,15 @@ def _sub_settings_header_sx(row_id: str, child_id: str, href: str,
-def _blog_sentinel_sx(ctx: dict) -> str:
+async def _blog_sentinel_sx(ctx: dict) -> str:
"""Infinite scroll sentinels as sx calls (for wire format)."""
- from shared.sx.helpers import sx_call
page = ctx.get("page", 1)
total_pages = ctx.get("total_pages", 1)
if isinstance(total_pages, str):
total_pages = int(total_pages)
if page >= total_pages:
- return sx_call("end-of-results")
+ return await render_to_sx("end-of-results")
current_local_href = ctx.get("current_local_href", "/index")
next_url = f"{current_local_href}?page={page + 1}"
@@ -202,23 +201,23 @@ def _blog_sentinel_sx(ctx: dict) -> str:
)
return (
- sx_call("sentinel-mobile", id=f"sentinel-{page}-m", next_url=next_url, hyperscript=mobile_hs)
+ await render_to_sx("sentinel-mobile", id=f"sentinel-{page}-m", next_url=next_url, hyperscript=mobile_hs)
+ " "
- + sx_call("sentinel-desktop", id=f"sentinel-{page}-d", next_url=next_url, hyperscript=desktop_hs)
+ + await render_to_sx("sentinel-desktop", id=f"sentinel-{page}-d", next_url=next_url, hyperscript=desktop_hs)
)
-def _blog_cards_sx(ctx: dict) -> str:
+async def _blog_cards_sx(ctx: dict) -> str:
"""S-expression wire format for blog cards (client renders)."""
posts = ctx.get("posts") or []
view = ctx.get("view")
parts = []
for p in posts:
if view == "tile":
- parts.append(_blog_card_tile_sx(p, ctx))
+ parts.append(await _blog_card_tile_sx(p, ctx))
else:
- parts.append(_blog_card_sx(p, ctx))
- parts.append(_blog_sentinel_sx(ctx))
+ parts.append(await _blog_card_sx(p, ctx))
+ parts.append(await _blog_sentinel_sx(ctx))
return "(<> " + " ".join(parts) + ")"
@@ -249,7 +248,7 @@ def _author_data(authors: list) -> list[dict]:
return result
-def _blog_card_sx(post: dict, ctx: dict) -> str:
+async def _blog_card_sx(post: dict, ctx: dict) -> str:
"""Single blog card as sx call (wire format) — pure data, no HTML."""
from quart import g
@@ -296,10 +295,10 @@ def _blog_card_sx(post: dict, ctx: dict) -> str:
if widget:
kwargs["widget"] = SxExpr(widget) if widget else None
- return sx_call("blog-card", **kwargs)
+ return await render_to_sx("blog-card", **kwargs)
-def _blog_card_tile_sx(post: dict, ctx: dict) -> str:
+async def _blog_card_tile_sx(post: dict, ctx: dict) -> str:
"""Single blog card tile as sx call (wire format) — pure data."""
slug = post.get("slug", "")
href = call_url(ctx, "blog_url", f"/{slug}/")
@@ -332,10 +331,10 @@ def _blog_card_tile_sx(post: dict, ctx: dict) -> str:
if authors:
kwargs["authors"] = authors
- return sx_call("blog-card-tile", **kwargs)
+ return await render_to_sx("blog-card-tile", **kwargs)
-def _at_bar_sx(post: dict, ctx: dict) -> str:
+async def _at_bar_sx(post: dict, ctx: dict) -> str:
"""Tags + authors bar below a card as sx."""
tags = post.get("tags") or []
authors = post.get("authors") or []
@@ -355,11 +354,11 @@ def _at_bar_sx(post: dict, ctx: dict) -> str:
for a in authors
] if authors else []
- return sx_call("blog-at-bar", tags=tag_data, authors=author_data)
+ return await render_to_sx("blog-at-bar", tags=tag_data, authors=author_data)
-def _page_cards_sx(ctx: dict) -> str:
+async def _page_cards_sx(ctx: dict) -> str:
"""Render page cards with sentinel (sx)."""
pages = ctx.get("pages") or ctx.get("posts") or []
page_num = ctx.get("page", 1)
@@ -370,23 +369,23 @@ def _page_cards_sx(ctx: dict) -> str:
parts = []
for pg in pages:
- parts.append(_page_card_sx(pg, ctx))
+ parts.append(await _page_card_sx(pg, ctx))
if page_num < total_pages:
current_local_href = ctx.get("current_local_href", "/index?type=pages")
next_url = f"{current_local_href}&page={page_num + 1}" if "?" in current_local_href else f"{current_local_href}?page={page_num + 1}"
- parts.append(sx_call("sentinel-simple",
+ parts.append(await render_to_sx("sentinel-simple",
id=f"sentinel-{page_num}-d", next_url=next_url,
))
elif pages:
- parts.append(sx_call("end-of-results"))
+ parts.append(await render_to_sx("end-of-results"))
else:
- parts.append(sx_call("blog-no-pages"))
+ parts.append(await render_to_sx("blog-no-pages"))
return "(<> " + " ".join(parts) + ")" if parts else ""
-def _page_card_sx(page: dict, ctx: dict) -> str:
+async def _page_card_sx(page: dict, ctx: dict) -> str:
"""Single page card as sx."""
slug = page.get("slug", "")
href = call_url(ctx, "blog_url", f"/{slug}/")
@@ -398,7 +397,7 @@ def _page_card_sx(page: dict, ctx: dict) -> str:
fi = page.get("feature_image")
excerpt = page.get("custom_excerpt") or page.get("excerpt", "")
- return sx_call("blog-page-card",
+ return await render_to_sx("blog-page-card",
href=href, hx_select=hx_select, title=page.get("title", ""),
has_calendar=features.get("calendar", False),
has_market=features.get("market", False),
@@ -407,7 +406,7 @@ def _page_card_sx(page: dict, ctx: dict) -> str:
)
-def _view_toggle_sx(ctx: dict) -> str:
+async def _view_toggle_sx(ctx: dict) -> str:
"""View toggle bar (list/tile) for desktop."""
view = ctx.get("view")
current_local_href = ctx.get("current_local_href", "/index")
@@ -419,17 +418,17 @@ def _view_toggle_sx(ctx: dict) -> str:
list_href = f"{current_local_href}"
tile_href = f"{current_local_href}{'&' if '?' in current_local_href else '?'}view=tile"
- list_svg_sx = sx_call("list-svg")
- tile_svg_sx = sx_call("tile-svg")
+ list_svg_sx = await render_to_sx("list-svg")
+ tile_svg_sx = await render_to_sx("tile-svg")
- return sx_call("view-toggle",
+ return await render_to_sx("view-toggle",
list_href=list_href, tile_href=tile_href, hx_select=hx_select,
list_cls=list_cls, tile_cls=tile_cls, storage_key="blog_view",
list_svg=SxExpr(list_svg_sx), tile_svg=SxExpr(tile_svg_sx),
)
-def _content_type_tabs_sx(ctx: dict) -> str:
+async def _content_type_tabs_sx(ctx: dict) -> str:
"""Posts/Pages tabs."""
content_type = ctx.get("content_type", "posts")
hx_select = ctx.get("hx_select_search", "#main-panel")
@@ -440,29 +439,29 @@ def _content_type_tabs_sx(ctx: dict) -> str:
posts_cls = "bg-stone-700 text-white" if content_type != "pages" else "bg-stone-100 text-stone-600 hover:bg-stone-200"
pages_cls = "bg-stone-700 text-white" if content_type == "pages" else "bg-stone-100 text-stone-600 hover:bg-stone-200"
- return sx_call("blog-content-type-tabs",
+ return await render_to_sx("blog-content-type-tabs",
posts_href=posts_href, pages_href=pages_href, hx_select=hx_select,
posts_cls=posts_cls, pages_cls=pages_cls,
)
-def _blog_main_panel_sx(ctx: dict) -> str:
+async def _blog_main_panel_sx(ctx: dict) -> str:
"""Blog index main panel with tabs, toggle, and cards."""
content_type = ctx.get("content_type", "posts")
view = ctx.get("view")
- tabs = _content_type_tabs_sx(ctx)
+ tabs = await _content_type_tabs_sx(ctx)
if content_type == "pages":
- cards = _page_cards_sx(ctx)
- return sx_call("blog-main-panel-pages",
+ cards = await _page_cards_sx(ctx)
+ return await render_to_sx("blog-main-panel-pages",
tabs=SxExpr(tabs), cards=SxExpr(cards),
)
else:
- toggle = _view_toggle_sx(ctx)
+ toggle = await _view_toggle_sx(ctx)
grid_cls = "max-w-full px-3 py-3 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4" if view == "tile" else "max-w-full px-3 py-3 space-y-3"
- cards = _blog_cards_sx(ctx)
- return sx_call("blog-main-panel-posts",
+ cards = await _blog_cards_sx(ctx)
+ return await render_to_sx("blog-main-panel-posts",
tabs=SxExpr(tabs), toggle=SxExpr(toggle), grid_cls=grid_cls,
cards=SxExpr(cards),
)
@@ -472,48 +471,48 @@ def _blog_main_panel_sx(ctx: dict) -> str:
# Desktop aside (filter sidebar)
# ---------------------------------------------------------------------------
-def _blog_aside_sx(ctx: dict) -> str:
+async def _blog_aside_sx(ctx: dict) -> str:
"""Desktop aside with search, action buttons, and filters."""
- sd = search_desktop_sx(ctx)
- ab = _action_buttons_sx(ctx)
- tgf = _tag_groups_filter_sx(ctx)
- af = _authors_filter_sx(ctx)
- return sx_call("blog-aside",
+ sd = await search_desktop_sx(ctx)
+ ab = await _action_buttons_sx(ctx)
+ tgf = await _tag_groups_filter_sx(ctx)
+ af = await _authors_filter_sx(ctx)
+ return await render_to_sx("blog-aside",
search=SxExpr(sd), action_buttons=SxExpr(ab),
tag_groups_filter=SxExpr(tgf), authors_filter=SxExpr(af),
)
-def _blog_filter_sx(ctx: dict) -> str:
+async def _blog_filter_sx(ctx: dict) -> str:
"""Mobile filter (details/summary)."""
# Mobile filter summary tags
summary_parts = []
- tg_summary = _tag_groups_filter_summary_sx(ctx)
- au_summary = _authors_filter_summary_sx(ctx)
+ tg_summary = await _tag_groups_filter_summary_sx(ctx)
+ au_summary = await _authors_filter_summary_sx(ctx)
if tg_summary:
summary_parts.append(tg_summary)
if au_summary:
summary_parts.append(au_summary)
- search_sx = search_mobile_sx(ctx)
+ search_sx = await search_mobile_sx(ctx)
if summary_parts:
filter_content = "(<> " + search_sx + " " + " ".join(summary_parts) + ")"
else:
filter_content = search_sx
- action_buttons = _action_buttons_sx(ctx)
- tgf = _tag_groups_filter_sx(ctx)
- af = _authors_filter_sx(ctx)
+ action_buttons = await _action_buttons_sx(ctx)
+ tgf = await _tag_groups_filter_sx(ctx)
+ af = await _authors_filter_sx(ctx)
filter_details = "(<> " + tgf + " " + af + ")"
- return sx_call("mobile-filter",
+ return await render_to_sx("mobile-filter",
filter_summary=SxExpr(filter_content),
action_buttons=SxExpr(action_buttons),
filter_details=SxExpr(filter_details),
)
-def _action_buttons_sx(ctx: dict) -> str:
+async def _action_buttons_sx(ctx: dict) -> str:
"""New Post/Page + Drafts toggle buttons (sx)."""
from quart import g
@@ -529,13 +528,13 @@ def _action_buttons_sx(ctx: dict) -> str:
if has_admin:
new_href = call_url(ctx, "blog_url", "/new/")
- parts.append(sx_call("blog-action-button",
+ parts.append(await render_to_sx("blog-action-button",
href=new_href, hx_select=hx_select,
btn_class="px-3 py-1 rounded bg-stone-700 text-white text-sm hover:bg-stone-800 transition-colors",
title="New Post", icon_class="fa fa-plus mr-1", label=" New Post",
))
new_page_href = call_url(ctx, "blog_url", "/new-page/")
- parts.append(sx_call("blog-action-button",
+ parts.append(await render_to_sx("blog-action-button",
href=new_page_href, hx_select=hx_select,
btn_class="px-3 py-1 rounded bg-blue-600 text-white text-sm hover:bg-blue-700 transition-colors",
title="New Page", icon_class="fa fa-plus mr-1", label=" New Page",
@@ -544,26 +543,26 @@ def _action_buttons_sx(ctx: dict) -> str:
if user and (draft_count or drafts):
if drafts:
off_href = f"{current_local_href}"
- parts.append(sx_call("blog-drafts-button",
+ parts.append(await render_to_sx("blog-drafts-button",
href=off_href, hx_select=hx_select,
btn_class="px-3 py-1 rounded bg-stone-700 text-white text-sm hover:bg-stone-800 transition-colors",
title="Hide Drafts", label=" Drafts ", draft_count=str(draft_count),
))
else:
on_href = f"{current_local_href}{'&' if '?' in current_local_href else '?'}drafts=1"
- parts.append(sx_call("blog-drafts-button-amber",
+ parts.append(await render_to_sx("blog-drafts-button-amber",
href=on_href, hx_select=hx_select,
btn_class="px-3 py-1 rounded bg-amber-600 text-white text-sm hover:bg-amber-700 transition-colors",
title="Show Drafts", label=" Drafts ", draft_count=str(draft_count),
))
inner = "(<> " + " ".join(parts) + ")" if parts else ""
- return sx_call("blog-action-buttons-wrapper",
+ return await render_to_sx("blog-action-buttons-wrapper",
inner=SxExpr(inner) if inner else None,
)
-def _tag_groups_filter_sx(ctx: dict) -> str:
+async def _tag_groups_filter_sx(ctx: dict) -> str:
"""Tag group filter bar as sx."""
tag_groups = ctx.get("tag_groups") or []
selected_groups = ctx.get("selected_groups") or ()
@@ -573,7 +572,7 @@ def _tag_groups_filter_sx(ctx: dict) -> str:
is_any = len(selected_groups) == 0 and len(selected_tags) == 0
any_cls = "bg-stone-900 text-white border-stone-900" if is_any else "bg-white text-stone-600 border-stone-300 hover:bg-stone-50"
- li_parts = [sx_call("blog-filter-any-topic", cls=any_cls, hx_select=hx_select)]
+ li_parts = [await render_to_sx("blog-filter-any-topic", cls=any_cls, hx_select=hx_select)]
for group in tag_groups:
g_slug = getattr(group, "slug", "") if hasattr(group, "slug") else group.get("slug", "")
@@ -589,21 +588,21 @@ def _tag_groups_filter_sx(ctx: dict) -> str:
cls = "bg-stone-900 text-white border-stone-900" if is_on else "bg-white text-stone-600 border-stone-300 hover:bg-stone-50"
if g_fi:
- icon = sx_call("blog-filter-group-icon-image", src=g_fi, name=g_name)
+ icon = await render_to_sx("blog-filter-group-icon-image", src=g_fi, name=g_name)
else:
style = f"background-color: {g_colour}; color: white;" if g_colour else "background-color: #e7e5e4; color: #57534e;"
- icon = sx_call("blog-filter-group-icon-color", style=style, initial=g_name[:1])
+ icon = await render_to_sx("blog-filter-group-icon-color", style=style, initial=g_name[:1])
- li_parts.append(sx_call("blog-filter-group-li",
+ li_parts.append(await render_to_sx("blog-filter-group-li",
cls=cls, hx_get=f"?group={g_slug}&page=1", hx_select=hx_select,
icon=SxExpr(icon), name=g_name, count=str(g_count),
))
items = "(<> " + " ".join(li_parts) + ")"
- return sx_call("blog-filter-nav", items=SxExpr(items))
+ return await render_to_sx("blog-filter-nav", items=SxExpr(items))
-def _authors_filter_sx(ctx: dict) -> str:
+async def _authors_filter_sx(ctx: dict) -> str:
"""Author filter bar as sx."""
authors = ctx.get("authors") or []
selected_authors = ctx.get("selected_authors") or ()
@@ -612,7 +611,7 @@ def _authors_filter_sx(ctx: dict) -> str:
is_any = len(selected_authors) == 0
any_cls = "bg-stone-900 text-white border-stone-900" if is_any else "bg-white text-stone-600 border-stone-300 hover:bg-stone-50"
- li_parts = [sx_call("blog-filter-any-author", cls=any_cls, hx_select=hx_select)]
+ li_parts = [await render_to_sx("blog-filter-any-author", cls=any_cls, hx_select=hx_select)]
for author in authors:
a_slug = getattr(author, "slug", "") if hasattr(author, "slug") else author.get("slug", "")
@@ -625,18 +624,18 @@ def _authors_filter_sx(ctx: dict) -> str:
icon_sx = None
if a_img:
- icon_sx = sx_call("blog-filter-author-icon", src=a_img, name=a_name)
+ icon_sx = await render_to_sx("blog-filter-author-icon", src=a_img, name=a_name)
- li_parts.append(sx_call("blog-filter-author-li",
+ li_parts.append(await render_to_sx("blog-filter-author-li",
cls=cls, hx_get=f"?author={a_slug}&page=1", hx_select=hx_select,
icon=SxExpr(icon_sx) if icon_sx else None, name=a_name, count=str(a_count),
))
items = "(<> " + " ".join(li_parts) + ")"
- return sx_call("blog-filter-nav", items=SxExpr(items))
+ return await render_to_sx("blog-filter-nav", items=SxExpr(items))
-def _tag_groups_filter_summary_sx(ctx: dict) -> str:
+async def _tag_groups_filter_summary_sx(ctx: dict) -> str:
"""Mobile filter summary for tag groups (sx)."""
selected_groups = ctx.get("selected_groups") or ()
tag_groups = ctx.get("tag_groups") or []
@@ -650,11 +649,11 @@ def _tag_groups_filter_summary_sx(ctx: dict) -> str:
names.append(g_name)
if not names:
return ""
- return sx_call("blog-filter-summary", text=", ".join(names))
+ return await render_to_sx("blog-filter-summary", text=", ".join(names))
-def _authors_filter_summary_sx(ctx: dict) -> str:
+async def _authors_filter_summary_sx(ctx: dict) -> str:
"""Mobile filter summary for authors (sx)."""
selected_authors = ctx.get("selected_authors") or ()
authors = ctx.get("authors") or []
@@ -668,7 +667,7 @@ def _authors_filter_summary_sx(ctx: dict) -> str:
names.append(a_name)
if not names:
return ""
- return sx_call("blog-filter-summary", text=", ".join(names))
+ return await render_to_sx("blog-filter-summary", text=", ".join(names))
@@ -676,7 +675,7 @@ def _authors_filter_summary_sx(ctx: dict) -> str:
# Post detail main panel
# ---------------------------------------------------------------------------
-def _post_main_panel_sx(ctx: dict) -> str:
+async def _post_main_panel_sx(ctx: dict) -> str:
"""Post/page article content."""
from quart import g, url_for as qurl
@@ -693,10 +692,10 @@ def _post_main_panel_sx(ctx: dict) -> str:
edit_sx = ""
if is_admin or (user and post.get("user_id") == getattr(user, "id", None)):
edit_href = qurl("blog.post.admin.defpage_post_edit", slug=slug)
- edit_sx = sx_call("blog-detail-edit-link",
+ edit_sx = await render_to_sx("blog-detail-edit-link",
href=edit_href, hx_select=hx_select,
)
- draft_sx = sx_call("blog-detail-draft",
+ draft_sx = await render_to_sx("blog-detail-draft",
publish_requested=post.get("publish_requested"),
edit=SxExpr(edit_sx) if edit_sx else None,
)
@@ -708,7 +707,7 @@ def _post_main_panel_sx(ctx: dict) -> str:
if user:
liked = post.get("is_liked", False)
like_url = call_url(ctx, "blog_url", f"/{slug}/like/toggle/")
- like_sx = sx_call("blog-detail-like",
+ like_sx = await render_to_sx("blog-detail-like",
like_url=like_url,
hx_headers=f'{{"X-CSRFToken": "{_ctx_csrf(ctx)}"}}',
heart="\u2764\ufe0f" if liked else "\U0001f90d",
@@ -716,12 +715,12 @@ def _post_main_panel_sx(ctx: dict) -> str:
excerpt_sx = ""
if post.get("custom_excerpt"):
- excerpt_sx = sx_call("blog-detail-excerpt",
+ excerpt_sx = await render_to_sx("blog-detail-excerpt",
excerpt=post["custom_excerpt"],
)
- at_bar = _at_bar_sx(post, ctx)
- chrome_sx = sx_call("blog-detail-chrome",
+ at_bar = await _at_bar_sx(post, ctx)
+ chrome_sx = await render_to_sx("blog-detail-chrome",
like=SxExpr(like_sx) if like_sx else None,
excerpt=SxExpr(excerpt_sx) if excerpt_sx else None,
at_bar=SxExpr(at_bar) if at_bar else None,
@@ -731,7 +730,7 @@ def _post_main_panel_sx(ctx: dict) -> str:
html_content = post.get("html", "")
sx_content = post.get("sx_content", "")
- return sx_call("blog-detail-main",
+ return await render_to_sx("blog-detail-main",
draft=SxExpr(draft_sx) if draft_sx else None,
chrome=SxExpr(chrome_sx) if chrome_sx else None,
feature_image=fi, html_content=html_content,
@@ -739,7 +738,7 @@ def _post_main_panel_sx(ctx: dict) -> str:
)
-def _post_meta_sx(ctx: dict) -> str:
+async def _post_meta_sx(ctx: dict) -> str:
"""Post SEO meta tags as sx (auto-hoisted to by sx.js)."""
post = ctx.get("post") or {}
base_title = ctx.get("base_title", "")
@@ -771,7 +770,7 @@ def _post_meta_sx(ctx: dict) -> str:
tw_title = post.get("twitter_title") or page_title
is_article = not post.get("is_page")
- return sx_call("blog-meta",
+ return await render_to_sx("blog-meta",
robots=robots, page_title=page_title, desc=desc, canonical=canonical,
og_type="article" if is_article else "website",
og_title=og_title, image=image,
@@ -784,12 +783,12 @@ def _post_meta_sx(ctx: dict) -> str:
# Home page (Ghost "home" page)
# ---------------------------------------------------------------------------
-def _home_main_panel_sx(ctx: dict) -> str:
+async def _home_main_panel_sx(ctx: dict) -> str:
"""Home page content — renders the Ghost page HTML or sx_content."""
post = ctx.get("post") or {}
html = post.get("html", "")
sx_content = post.get("sx_content", "")
- return sx_call("blog-home-main",
+ return await render_to_sx("blog-home-main",
html_content=html,
sx_content=SxExpr(sx_content) if sx_content else None)
@@ -810,24 +809,24 @@ def _settings_main_panel_sx(ctx: dict) -> str:
return '(div :class "max-w-2xl mx-auto px-4 py-6")'
-def _cache_main_panel_sx(ctx: dict) -> str:
+async def _cache_main_panel_sx(ctx: dict) -> str:
from quart import url_for as qurl
csrf = _ctx_csrf(ctx)
clear_url = qurl("settings.cache_clear")
- return sx_call("blog-cache-panel", clear_url=clear_url, csrf=csrf)
+ return await render_to_sx("blog-cache-panel", clear_url=clear_url, csrf=csrf)
# ---------------------------------------------------------------------------
# Snippets main panel
# ---------------------------------------------------------------------------
-def _snippets_main_panel_sx(ctx: dict) -> str:
- sl = _snippets_list_sx(ctx)
- return sx_call("blog-snippets-panel", list=SxExpr(sl))
+async def _snippets_main_panel_sx(ctx: dict) -> str:
+ sl = await _snippets_list_sx(ctx)
+ return await render_to_sx("blog-snippets-panel", list=SxExpr(sl))
-def _snippets_list_sx(ctx: dict) -> str:
+async def _snippets_list_sx(ctx: dict) -> str:
"""Snippets list with visibility badges and delete buttons."""
from quart import url_for as qurl, g
@@ -838,7 +837,7 @@ def _snippets_list_sx(ctx: dict) -> str:
user_id = getattr(user, "id", None)
if not snippets:
- return sx_call("empty-state", icon="fa fa-puzzle-piece", message="No snippets yet. Create one from the blog editor.")
+ return await render_to_sx("empty-state", icon="fa fa-puzzle-piece", message="No snippets yet. Create one from the blog editor.")
badge_colours = {
"private": "bg-stone-200 text-stone-700",
@@ -861,10 +860,10 @@ def _snippets_list_sx(ctx: dict) -> str:
patch_url = qurl("snippets.patch_visibility", snippet_id=s_id)
opts = ""
for v in ["private", "shared", "admin"]:
- opts += sx_call("blog-snippet-option",
+ opts += await render_to_sx("blog-snippet-option",
value=v, selected=(s_vis == v), label=v,
)
- extra += sx_call("blog-snippet-visibility-select",
+ extra += await render_to_sx("blog-snippet-visibility-select",
patch_url=patch_url,
hx_headers=f'{{"X-CSRFToken": "{csrf}"}}',
options=SxExpr("(<> " + opts + ")") if opts else None,
@@ -873,7 +872,7 @@ def _snippets_list_sx(ctx: dict) -> str:
if s_uid == user_id or is_admin:
del_url = qurl("snippets.delete_snippet", snippet_id=s_id)
- extra += sx_call("delete-btn",
+ extra += await render_to_sx("delete-btn",
url=del_url, trigger_target="#snippets-list",
title="Delete snippet?",
text=f'Delete \u201c{s_name}\u201d?',
@@ -881,35 +880,35 @@ def _snippets_list_sx(ctx: dict) -> str:
cls="px-3 py-1 text-sm bg-red-200 hover:bg-red-300 rounded text-red-800 flex-shrink-0",
)
- row_parts.append(sx_call("blog-snippet-row",
+ row_parts.append(await render_to_sx("blog-snippet-row",
name=s_name, owner=owner, badge_cls=badge_cls,
visibility=s_vis, extra=SxExpr("(<> " + extra + ")") if extra else None,
))
rows = "(<> " + " ".join(row_parts) + ")"
- return sx_call("blog-snippets-list", rows=SxExpr(rows))
+ return await render_to_sx("blog-snippets-list", rows=SxExpr(rows))
# ---------------------------------------------------------------------------
# Menu items main panel
# ---------------------------------------------------------------------------
-def _menu_items_main_panel_sx(ctx: dict) -> str:
+async def _menu_items_main_panel_sx(ctx: dict) -> str:
from quart import url_for as qurl
new_url = qurl("menu_items.new_menu_item")
- ml = _menu_items_list_sx(ctx)
- return sx_call("blog-menu-items-panel", new_url=new_url, list=SxExpr(ml))
+ ml = await _menu_items_list_sx(ctx)
+ return await render_to_sx("blog-menu-items-panel", new_url=new_url, list=SxExpr(ml))
-def _menu_items_list_sx(ctx: dict) -> str:
+async def _menu_items_list_sx(ctx: dict) -> str:
from quart import url_for as qurl
menu_items = ctx.get("menu_items") or []
csrf = _ctx_csrf(ctx)
if not menu_items:
- return sx_call("empty-state", icon="fa fa-inbox", message="No menu items yet. Add one to get started!")
+ return await render_to_sx("empty-state", icon="fa fa-inbox", message="No menu items yet. Add one to get started!")
row_parts = []
for item in menu_items:
@@ -922,10 +921,10 @@ def _menu_items_list_sx(ctx: dict) -> str:
edit_url = qurl("menu_items.edit_menu_item", item_id=i_id)
del_url = qurl("menu_items.delete_menu_item_route", item_id=i_id)
- img_sx = sx_call("img-or-placeholder", src=fi, alt=label,
+ img_sx = await render_to_sx("img-or-placeholder", src=fi, alt=label,
size_cls="w-12 h-12 rounded-full object-cover flex-shrink-0")
- row_parts.append(sx_call("blog-menu-item-row",
+ row_parts.append(await render_to_sx("blog-menu-item-row",
img=SxExpr(img_sx), label=label, slug=slug,
sort_order=str(sort), edit_url=edit_url, delete_url=del_url,
confirm_text=f"Remove {label} from the menu?",
@@ -933,14 +932,14 @@ def _menu_items_list_sx(ctx: dict) -> str:
))
rows = "(<> " + " ".join(row_parts) + ")"
- return sx_call("blog-menu-items-list", rows=SxExpr(rows))
+ return await render_to_sx("blog-menu-items-list", rows=SxExpr(rows))
# ---------------------------------------------------------------------------
# Tag groups main panel
# ---------------------------------------------------------------------------
-def _tag_groups_main_panel_sx(ctx: dict) -> str:
+async def _tag_groups_main_panel_sx(ctx: dict) -> str:
from quart import url_for as qurl
groups = ctx.get("groups") or []
@@ -948,7 +947,7 @@ def _tag_groups_main_panel_sx(ctx: dict) -> str:
csrf = _ctx_csrf(ctx)
create_url = qurl("blog.tag_groups_admin.create")
- form_sx = sx_call("blog-tag-groups-create-form",
+ form_sx = await render_to_sx("blog-tag-groups-create-form",
create_url=create_url, csrf=csrf,
)
@@ -967,18 +966,18 @@ def _tag_groups_main_panel_sx(ctx: dict) -> str:
edit_href = qurl("blog.tag_groups_admin.defpage_tag_group_edit", id=g_id)
if g_fi:
- icon = sx_call("blog-tag-group-icon-image", src=g_fi, name=g_name)
+ icon = await render_to_sx("blog-tag-group-icon-image", src=g_fi, name=g_name)
else:
style = f"background-color: {g_colour}; color: white;" if g_colour else "background-color: #e7e5e4; color: #57534e;"
- icon = sx_call("blog-tag-group-icon-color", style=style, initial=g_name[:1])
+ icon = await render_to_sx("blog-tag-group-icon-color", style=style, initial=g_name[:1])
- li_parts.append(sx_call("blog-tag-group-li",
+ li_parts.append(await render_to_sx("blog-tag-group-li",
icon=SxExpr(icon), edit_href=edit_href, name=g_name,
slug=g_slug, sort_order=str(g_sort),
))
- groups_sx = sx_call("blog-tag-groups-list", items=SxExpr("(<> " + " ".join(li_parts) + ")"))
+ groups_sx = await render_to_sx("blog-tag-groups-list", items=SxExpr("(<> " + " ".join(li_parts) + ")"))
else:
- groups_sx = sx_call("empty-state", message="No tag groups yet.", cls="text-stone-500 text-sm")
+ groups_sx = await render_to_sx("empty-state", message="No tag groups yet.", cls="text-stone-500 text-sm")
# Unassigned tags
unassigned_sx = ""
@@ -986,20 +985,20 @@ def _tag_groups_main_panel_sx(ctx: dict) -> str:
tag_spans = []
for tag in unassigned_tags:
t_name = getattr(tag, "name", "") if hasattr(tag, "name") else tag.get("name", "")
- tag_spans.append(sx_call("blog-unassigned-tag", name=t_name))
- unassigned_sx = sx_call("blog-unassigned-tags",
+ tag_spans.append(await render_to_sx("blog-unassigned-tag", name=t_name))
+ unassigned_sx = await render_to_sx("blog-unassigned-tags",
heading=f"Unassigned Tags ({len(unassigned_tags)})",
spans=SxExpr("(<> " + " ".join(tag_spans) + ")"),
)
- return sx_call("blog-tag-groups-main",
+ return await render_to_sx("blog-tag-groups-main",
form=SxExpr(form_sx),
groups=SxExpr(groups_sx),
unassigned=SxExpr(unassigned_sx) if unassigned_sx else None,
)
-def _tag_groups_edit_main_panel_sx(ctx: dict) -> str:
+async def _tag_groups_edit_main_panel_sx(ctx: dict) -> str:
from quart import url_for as qurl
group = ctx.get("group")
@@ -1023,24 +1022,24 @@ def _tag_groups_edit_main_panel_sx(ctx: dict) -> str:
t_name = getattr(tag, "name", "") if hasattr(tag, "name") else tag.get("name", "")
t_fi = getattr(tag, "feature_image", None) if hasattr(tag, "feature_image") else tag.get("feature_image")
checked = t_id in assigned_tag_ids
- img = sx_call("blog-tag-checkbox-image", src=t_fi) if t_fi else ""
- tag_items.append(sx_call("blog-tag-checkbox",
+ img = (await render_to_sx("blog-tag-checkbox-image", src=t_fi)) if t_fi else ""
+ tag_items.append(await render_to_sx("blog-tag-checkbox",
tag_id=str(t_id), checked=checked,
img=SxExpr(img) if img else None, name=t_name,
))
- edit_form = sx_call("blog-tag-group-edit-form",
+ edit_form = await render_to_sx("blog-tag-group-edit-form",
save_url=save_url, csrf=csrf,
name=g_name, colour=g_colour or "", sort_order=str(g_sort),
feature_image=g_fi or "",
tags=SxExpr("(<> " + " ".join(tag_items) + ")"),
)
- del_form = sx_call("blog-tag-group-delete-form",
+ del_form = await render_to_sx("blog-tag-group-delete-form",
delete_url=del_url, csrf=csrf,
)
- return sx_call("blog-tag-group-edit-main",
+ return await render_to_sx("blog-tag-group-edit-main",
edit_form=SxExpr(edit_form), delete_form=SxExpr(del_form),
)
@@ -1061,68 +1060,64 @@ def _tag_groups_edit_main_panel_sx(ctx: dict) -> str:
# ---- Home page ----
async def render_home_page(ctx: dict) -> str:
- root_hdr = root_header_sx(ctx)
- post_hdr = _post_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx)
+ post_hdr = await _post_header_sx(ctx)
header_rows = "(<> " + root_hdr + " " + post_hdr + ")"
- content = _home_main_panel_sx(ctx)
- meta = _post_meta_sx(ctx)
- menu = mobile_menu_sx(post_mobile_nav_sx(ctx), mobile_root_nav_sx(ctx))
- return full_page_sx(ctx, header_rows=header_rows, content=content,
+ content = await _home_main_panel_sx(ctx)
+ meta = await _post_meta_sx(ctx)
+ menu = mobile_menu_sx(await post_mobile_nav_sx(ctx), await mobile_root_nav_sx(ctx))
+ return await full_page_sx(ctx, header_rows=header_rows, content=content,
meta=meta, menu=menu)
async def render_home_oob(ctx: dict) -> str:
- root_hdr = root_header_sx(ctx)
- post_hdr = _post_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx)
+ post_hdr = await _post_header_sx(ctx)
rows = "(<> " + root_hdr + " " + post_hdr + ")"
- header_oob = _oob_header_sx("root-header-child", "post-header-child", rows)
- content = _home_main_panel_sx(ctx)
- return oob_page_sx(oobs=header_oob, content=content)
+ header_oob = await _oob_header_sx("root-header-child", "post-header-child", rows)
+ content = await _home_main_panel_sx(ctx)
+ return await oob_page_sx(oobs=header_oob, content=content)
# ---- Blog index ----
async def render_blog_page(ctx: dict) -> str:
- root_hdr = root_header_sx(ctx)
- blog_hdr = _blog_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx)
+ blog_hdr = await _blog_header_sx(ctx)
header_rows = "(<> " + root_hdr + " " + blog_hdr + ")"
- content = _blog_main_panel_sx(ctx)
- aside = _blog_aside_sx(ctx)
- filter_sx = _blog_filter_sx(ctx)
- return full_page_sx(ctx, header_rows=header_rows, content=content,
+ content = await _blog_main_panel_sx(ctx)
+ aside = await _blog_aside_sx(ctx)
+ filter_sx = await _blog_filter_sx(ctx)
+ return await full_page_sx(ctx, header_rows=header_rows, content=content,
aside=aside, filter=filter_sx)
async def render_blog_oob(ctx: dict) -> str:
- root_hdr = root_header_sx(ctx)
- blog_hdr = _blog_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx)
+ blog_hdr = await _blog_header_sx(ctx)
rows = "(<> " + root_hdr + " " + blog_hdr + ")"
- header_oob = _oob_header_sx("root-header-child", "blog-header-child", rows)
- content = _blog_main_panel_sx(ctx)
- aside = _blog_aside_sx(ctx)
- filter_sx = _blog_filter_sx(ctx)
- return oob_page_sx(oobs=header_oob, content=content, aside=aside,
+ header_oob = await _oob_header_sx("root-header-child", "blog-header-child", rows)
+ content = await _blog_main_panel_sx(ctx)
+ aside = await _blog_aside_sx(ctx)
+ filter_sx = await _blog_filter_sx(ctx)
+ return await oob_page_sx(oobs=header_oob, content=content, aside=aside,
filter=filter_sx)
async def render_blog_cards(ctx: dict) -> str:
"""Pagination-only response (page > 1) — sx wire format."""
- return _blog_cards_sx(ctx)
+ return await _blog_cards_sx(ctx)
async def render_blog_page_cards(ctx: dict) -> str:
"""Page cards pagination response."""
- return _page_cards_sx(ctx)
+ return await _page_cards_sx(ctx)
# ---- New post/page editor panel ----
-def render_editor_panel(save_error: str | None = None, is_page: bool = False) -> str:
- """Build the WYSIWYG editor panel HTML (replaces _main_panel.html template).
-
- This is synchronous — it just assembles an HTML string from the current
- request context (url_for, CSRF token, asset URLs, config).
- """
+async def render_editor_panel(save_error: str | None = None, is_page: bool = False) -> str:
+ """Build the WYSIWYG editor panel HTML (replaces _main_panel.html template)."""
import os
from quart import url_for as qurl, current_app
from shared.browser.app.csrf import generate_csrf_token
@@ -1148,18 +1143,18 @@ def render_editor_panel(save_error: str | None = None, is_page: bool = False) ->
# Error banner
if save_error:
- parts.append(sx_call("blog-editor-error", error=str(save_error)))
+ parts.append(await render_to_sx("blog-editor-error", error=str(save_error)))
# Form structure
- form_html = sx_call("blog-editor-form",
+ form_html = await render_to_sx("blog-editor-form",
csrf=csrf, title_placeholder=title_placeholder,
create_label=create_label,
)
parts.append(form_html)
# Editor CSS + inline styles + sx editor styles
- parts.append(sx_call("blog-editor-styles", css_href=editor_css))
- parts.append(sx_call("sx-editor-styles"))
+ parts.append(await render_to_sx("blog-editor-styles", css_href=editor_css))
+ parts.append(await render_to_sx("sx-editor-styles"))
# Editor JS + init script
init_js = (
@@ -1298,7 +1293,7 @@ def render_editor_panel(save_error: str | None = None, is_page: bool = False) ->
" }\n"
"})();\n"
)
- parts.append(sx_call("blog-editor-scripts",
+ parts.append(await render_to_sx("blog-editor-scripts",
js_src=editor_js,
sx_editor_js_src=sx_editor_js,
init_js=init_js))
@@ -1309,35 +1304,35 @@ def render_editor_panel(save_error: str | None = None, is_page: bool = False) ->
# ---- New post/page ----
async def render_new_post_page(ctx: dict) -> str:
- root_hdr = root_header_sx(ctx)
- blog_hdr = _blog_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx)
+ blog_hdr = await _blog_header_sx(ctx)
header_rows = "(<> " + root_hdr + " " + blog_hdr + ")"
content = ctx.get("editor_html", "")
- return full_page_sx(ctx, header_rows=header_rows, content=content)
+ return await full_page_sx(ctx, header_rows=header_rows, content=content)
# ---- Post detail ----
async def render_post_page(ctx: dict) -> str:
- root_hdr = root_header_sx(ctx)
- post_hdr = _post_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx)
+ post_hdr = await _post_header_sx(ctx)
header_rows = "(<> " + root_hdr + " " + post_hdr + ")"
- content = _post_main_panel_sx(ctx)
- meta = _post_meta_sx(ctx)
- menu = mobile_menu_sx(post_mobile_nav_sx(ctx), mobile_root_nav_sx(ctx))
- return full_page_sx(ctx, header_rows=header_rows, content=content,
+ content = await _post_main_panel_sx(ctx)
+ meta = await _post_meta_sx(ctx)
+ menu = mobile_menu_sx(await post_mobile_nav_sx(ctx), await mobile_root_nav_sx(ctx))
+ return await full_page_sx(ctx, header_rows=header_rows, content=content,
meta=meta, menu=menu)
async def render_post_oob(ctx: dict) -> str:
- root_hdr = root_header_sx(ctx) # non-OOB (nested inside root-header-child)
- post_hdr = _post_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx) # non-OOB (nested inside root-header-child)
+ post_hdr = await _post_header_sx(ctx)
rows = "(<> " + root_hdr + " " + post_hdr + ")"
- post_oob = _oob_header_sx("root-header-child", "post-header-child", rows)
- content = _post_main_panel_sx(ctx)
- menu = mobile_menu_sx(post_mobile_nav_sx(ctx), mobile_root_nav_sx(ctx))
+ post_oob = await _oob_header_sx("root-header-child", "post-header-child", rows)
+ content = await _post_main_panel_sx(ctx)
+ menu = mobile_menu_sx(await post_mobile_nav_sx(ctx), await mobile_root_nav_sx(ctx))
oobs = post_oob
- return oob_page_sx(oobs=oobs, content=content, menu=menu)
+ return await oob_page_sx(oobs=oobs, content=content, menu=menu)
# ---- Post admin ----
@@ -1462,14 +1457,14 @@ def _post_data_content_sx(ctx: dict) -> str:
# ===========================================================================
-def _preview_main_panel_sx(ctx: dict) -> str:
+async def _preview_main_panel_sx(ctx: dict) -> str:
"""Build the preview panel with 4 expandable sections."""
sections: list[str] = []
# 1. Prettified SX source
sx_pretty = ctx.get("sx_pretty", "")
if sx_pretty:
- sections.append(sx_call("blog-preview-section",
+ sections.append(await render_to_sx("blog-preview-section",
title="S-Expression Source",
content=SxExpr(sx_pretty),
))
@@ -1477,7 +1472,7 @@ def _preview_main_panel_sx(ctx: dict) -> str:
# 2. Prettified Lexical JSON
json_pretty = ctx.get("json_pretty", "")
if json_pretty:
- sections.append(sx_call("blog-preview-section",
+ sections.append(await render_to_sx("blog-preview-section",
title="Lexical JSON",
content=SxExpr(json_pretty),
))
@@ -1486,7 +1481,7 @@ def _preview_main_panel_sx(ctx: dict) -> str:
sx_rendered = ctx.get("sx_rendered", "")
if sx_rendered:
rendered_sx = f'(div :class "blog-content prose max-w-none" (raw! {sx_serialize(sx_rendered)}))'
- sections.append(sx_call("blog-preview-section",
+ sections.append(await render_to_sx("blog-preview-section",
title="SX Rendered",
content=SxExpr(rendered_sx),
))
@@ -1495,7 +1490,7 @@ def _preview_main_panel_sx(ctx: dict) -> str:
lex_rendered = ctx.get("lex_rendered", "")
if lex_rendered:
rendered_sx = f'(div :class "blog-content prose max-w-none" (raw! {sx_serialize(lex_rendered)}))'
- sections.append(sx_call("blog-preview-section",
+ sections.append(await render_to_sx("blog-preview-section",
title="Lexical Rendered",
content=SxExpr(rendered_sx),
))
@@ -1504,12 +1499,12 @@ def _preview_main_panel_sx(ctx: dict) -> str:
return '(div :class "p-8 text-stone-500" "No content to preview.")'
inner = " ".join(sections)
- return sx_call("blog-preview-panel", sections=SxExpr(f"(<> {inner})"))
+ return await render_to_sx("blog-preview-panel", sections=SxExpr(f"(<> {inner})"))
# ===========================================================================
-def _post_entries_content_sx(ctx: dict) -> str:
+async def _post_entries_content_sx(ctx: dict) -> str:
"""Build post entries panel natively (replaces _types/post_entries/_main_panel.html)."""
from quart import g, url_for as qurl
from shared.utils import host_url
@@ -1519,7 +1514,7 @@ def _post_entries_content_sx(ctx: dict) -> str:
post_slug = g.post_data["post"]["slug"]
# Associated entries list (reuse existing render function)
- assoc_html = render_associated_entries(all_calendars, associated_entry_ids, post_slug)
+ assoc_html = await render_associated_entries(all_calendars, associated_entry_ids, post_slug)
# Calendar browser
cal_items: list[str] = []
@@ -1677,12 +1672,10 @@ def _raw_html_sx(html: str) -> str:
return "(raw! " + sx_serialize(html) + ")"
-def _post_edit_content_sx(ctx: dict) -> str:
+async def _post_edit_content_sx(ctx: dict) -> str:
"""Build WYSIWYG editor panel as SX expression (edit page)."""
from quart import url_for as qurl, current_app, g, request as qrequest
from shared.browser.app.csrf import generate_csrf_token
- from shared.sx.helpers import sx_call
- from shared.sx.parser import SxExpr
ghost_post = ctx.get("ghost_post", {}) or {}
save_success = ctx.get("save_success", False)
@@ -1752,10 +1745,10 @@ def _post_edit_content_sx(ctx: dict) -> str:
# Error banner
if save_error:
- parts.append(sx_call("blog-editor-error", error=save_error))
+ parts.append(await render_to_sx("blog-editor-error", error=save_error))
# Form (sx_content_val populates #sx-content-input; JS reads from there)
- parts.append(sx_call("blog-editor-edit-form",
+ parts.append(await render_to_sx("blog-editor-edit-form",
csrf=csrf,
updated_at=str(updated_at),
title_val=title_val,
@@ -1773,11 +1766,11 @@ def _post_edit_content_sx(ctx: dict) -> str:
))
# Publish-mode JS
- parts.append(sx_call("blog-editor-publish-js", already_emailed=already_emailed))
+ parts.append(await render_to_sx("blog-editor-publish-js", already_emailed=already_emailed))
# Editor CSS + styles
- parts.append(sx_call("blog-editor-styles", css_href=editor_css))
- parts.append(sx_call("sx-editor-styles"))
+ parts.append(await render_to_sx("blog-editor-styles", css_href=editor_css))
+ parts.append(await render_to_sx("sx-editor-styles"))
# Editor JS + init
init_js = (
@@ -1878,7 +1871,7 @@ def _post_edit_content_sx(ctx: dict) -> str:
' }, 50); }'
'})();'
)
- parts.append(sx_call("blog-editor-scripts",
+ parts.append(await render_to_sx("blog-editor-scripts",
js_src=editor_js,
sx_editor_js_src=sx_editor_js,
init_js=init_js))
@@ -2039,15 +2032,15 @@ def _post_settings_content_sx(ctx: dict) -> str:
# ---- Like toggle button (delegates to market impl) ----
-def render_like_toggle_button(slug: str, liked: bool, like_url: str) -> str:
+async def render_like_toggle_button(slug: str, liked: bool, like_url: str) -> str:
"""Render a like toggle button for HTMX POST response."""
from market.sx.sx_components import render_like_toggle_button as _market_like
- return _market_like(slug, liked, like_url=like_url, item_type="post")
+ return await _market_like(slug, liked, like_url=like_url, item_type="post")
# ---- Snippets list ----
-def render_snippets_list(snippets, is_admin: bool) -> str:
+async def render_snippets_list(snippets, is_admin: bool) -> str:
"""Render the snippets list fragment for HTMX DELETE/PATCH responses."""
from shared.browser.app.csrf import generate_csrf_token
from quart import g
@@ -2057,12 +2050,12 @@ def render_snippets_list(snippets, is_admin: bool) -> str:
"is_admin": is_admin,
"csrf_token": generate_csrf_token(),
}
- return _snippets_list_sx(ctx)
+ return await _snippets_list_sx(ctx)
# ---- Menu items list + nav OOB ----
-def render_menu_items_list(menu_items) -> str:
+async def render_menu_items_list(menu_items) -> str:
"""Render the menu items list fragment for HTMX responses."""
from shared.browser.app.csrf import generate_csrf_token
@@ -2070,7 +2063,7 @@ def render_menu_items_list(menu_items) -> str:
"menu_items": menu_items,
"csrf_token": generate_csrf_token(),
}
- return _menu_items_list_sx(ctx)
+ return await _menu_items_list_sx(ctx)
def render_menu_item_form(menu_item=None) -> str:
@@ -2161,19 +2154,19 @@ document.addEventListener('click', function(e) {{
return html
-def render_page_search_results(pages, query, page, has_more) -> str:
+async def render_page_search_results(pages, query, page, has_more) -> str:
"""Render page search results (replaces _types/menu_items/_page_search_results.html)."""
from quart import url_for as qurl
if not pages and query:
- return sx_call("page-search-empty", query=query)
+ return await render_to_sx("page-search-empty", query=query)
if not pages:
return ""
items = []
for post in pages:
- items.append(sx_call("page-search-item",
+ items.append(await render_to_sx("page-search-item",
id=post.id, title=post.title,
slug=post.slug,
feature_image=post.feature_image or None))
@@ -2181,17 +2174,17 @@ def render_page_search_results(pages, query, page, has_more) -> str:
sentinel = ""
if has_more:
search_url = qurl("menu_items.search_pages_route")
- sentinel = sx_call("page-search-sentinel",
+ sentinel = await render_to_sx("page-search-sentinel",
url=search_url, query=query,
next_page=page + 1)
items_sx = "(<> " + " ".join(items) + ")"
- return sx_call("page-search-results",
+ return await render_to_sx("page-search-results",
items=SxExpr(items_sx),
sentinel=SxExpr(sentinel) if sentinel else None)
-def render_menu_items_nav_oob(menu_items, ctx: dict | None = None) -> str:
+async def render_menu_items_nav_oob(menu_items, ctx: dict | None = None) -> str:
"""Render the OOB nav update for menu items.
Produces the same DOM structure as ``_types/menu_items/_nav_oob.html``:
@@ -2201,7 +2194,7 @@ def render_menu_items_nav_oob(menu_items, ctx: dict | None = None) -> str:
from quart import request as qrequest
if not menu_items:
- return sx_call("blog-nav-empty", wrapper_id="menu-items-nav-wrapper")
+ return await render_to_sx("blog-nav-empty", wrapper_id="menu-items-nav-wrapper")
# Resolve URL helpers from context or fall back to template globals
if ctx is None:
@@ -2249,23 +2242,23 @@ def render_menu_items_nav_oob(menu_items, ctx: dict | None = None) -> str:
selected = "true" if (item_slug == first_seg or item_slug == app_name) else "false"
- img_sx = sx_call("img-or-placeholder", src=fi, alt=label,
+ img_sx = await render_to_sx("img-or-placeholder", src=fi, alt=label,
size_cls="w-8 h-8 rounded-full object-cover flex-shrink-0")
if item_slug != "cart":
- item_parts.append(sx_call("blog-nav-item-link",
+ item_parts.append(await render_to_sx("blog-nav-item-link",
href=href, hx_get=f"/{item_slug}/", selected=selected,
nav_cls=nav_button_cls, img=SxExpr(img_sx), label=label,
))
else:
- item_parts.append(sx_call("blog-nav-item-plain",
+ item_parts.append(await render_to_sx("blog-nav-item-plain",
href=href, selected=selected, nav_cls=nav_button_cls,
img=SxExpr(img_sx), label=label,
))
items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else ""
- return sx_call("scroll-nav-wrapper",
+ return await render_to_sx("scroll-nav-wrapper",
wrapper_id="menu-items-nav-wrapper", container_id=container_id,
arrow_cls=arrow_cls,
left_hs=f"on click set #{container_id}.scrollLeft to #{container_id}.scrollLeft - 200",
@@ -2277,7 +2270,7 @@ def render_menu_items_nav_oob(menu_items, ctx: dict | None = None) -> str:
# ---- Features panel ----
-def render_features_panel(features: dict, post: dict,
+async def render_features_panel(features: dict, post: dict,
sumup_configured: bool,
sumup_merchant_code: str,
sumup_checkout_prefix: str) -> str:
@@ -2291,7 +2284,7 @@ def render_features_panel(features: dict, post: dict,
hs_trigger = "on change trigger submit on closest "
- form_sx = sx_call("blog-features-form",
+ form_sx = await render_to_sx("blog-features-form",
features_url=features_url,
calendar_checked=bool(features.get("calendar")),
market_checked=bool(features.get("market")),
@@ -2302,14 +2295,14 @@ def render_features_panel(features: dict, post: dict,
if features.get("calendar") or features.get("market"):
placeholder = "\u2022" * 8 if sumup_configured else "sup_sk_..."
- sumup_sx = sx_call("blog-sumup-form",
+ sumup_sx = await render_to_sx("blog-sumup-form",
sumup_url=sumup_url, merchant_code=sumup_merchant_code,
placeholder=placeholder,
sumup_configured=sumup_configured,
checkout_prefix=sumup_checkout_prefix,
)
- return sx_call("blog-features-panel",
+ return await render_to_sx("blog-features-panel",
form=SxExpr(form_sx),
sumup=SxExpr(sumup_sx) if sumup_sx else None,
)
@@ -2317,7 +2310,7 @@ def render_features_panel(features: dict, post: dict,
# ---- Markets panel ----
-def render_markets_panel(markets, post: dict) -> str:
+async def render_markets_panel(markets, post: dict) -> str:
"""Render the markets panel fragment for HTMX responses."""
from shared.utils import host_url
from quart import url_for as qurl
@@ -2332,22 +2325,22 @@ def render_markets_panel(markets, post: dict) -> str:
m_name = getattr(m, "name", "") if hasattr(m, "name") else m.get("name", "")
m_slug = getattr(m, "slug", "") if hasattr(m, "slug") else m.get("slug", "")
del_url = host_url(qurl("blog.post.admin.delete_market", slug=slug, market_slug=m_slug))
- li_parts.append(sx_call("blog-market-item",
+ li_parts.append(await render_to_sx("blog-market-item",
name=m_name, slug=m_slug, delete_url=del_url,
confirm_text=f"Delete market '{m_name}'?",
))
- list_sx = sx_call("blog-markets-list", items=SxExpr("(<> " + " ".join(li_parts) + ")"))
+ list_sx = await render_to_sx("blog-markets-list", items=SxExpr("(<> " + " ".join(li_parts) + ")"))
else:
- list_sx = sx_call("blog-markets-empty")
+ list_sx = await render_to_sx("blog-markets-empty")
- return sx_call("blog-markets-panel",
+ return await render_to_sx("blog-markets-panel",
list=SxExpr(list_sx), create_url=create_url,
)
# ---- Associated entries ----
-def render_associated_entries(all_calendars, associated_entry_ids, post_slug: str) -> str:
+async def render_associated_entries(all_calendars, associated_entry_ids, post_slug: str) -> str:
"""Render the associated entries panel for HTMX POST responses."""
from shared.browser.app.csrf import generate_csrf_token
from quart import url_for as qurl
@@ -2377,13 +2370,13 @@ def render_associated_entries(all_calendars, associated_entry_ids, post_slug: st
toggle_url = host_url(qurl("blog.post.admin.toggle_entry", slug=post_slug, entry_id=e_id))
- img_sx = sx_call("blog-entry-image", src=cal_fi, title=cal_title)
+ img_sx = await render_to_sx("blog-entry-image", src=cal_fi, title=cal_title)
date_str = e_start.strftime("%A, %B %d, %Y at %H:%M") if e_start else ""
if e_end:
date_str += f" \u2013 {e_end.strftime('%H:%M')}"
- entry_items.append(sx_call("blog-associated-entry",
+ entry_items.append(await render_to_sx("blog-associated-entry",
confirm_text=f"This will remove {e_name} from this post",
toggle_url=toggle_url,
hx_headers=f'{{"X-CSRFToken": "{csrf}"}}',
@@ -2392,18 +2385,18 @@ def render_associated_entries(all_calendars, associated_entry_ids, post_slug: st
))
if has_entries:
- content_sx = sx_call("blog-associated-entries-content",
+ content_sx = await render_to_sx("blog-associated-entries-content",
items=SxExpr("(<> " + " ".join(entry_items) + ")"),
)
else:
- content_sx = sx_call("blog-associated-entries-empty")
+ content_sx = await render_to_sx("blog-associated-entries-empty")
- return sx_call("blog-associated-entries-panel", content=SxExpr(content_sx))
+ return await render_to_sx("blog-associated-entries-panel", content=SxExpr(content_sx))
# ---- Nav entries OOB ----
-def render_nav_entries_oob(associated_entries, calendars, post: dict, ctx: dict | None = None) -> str:
+async def render_nav_entries_oob(associated_entries, calendars, post: dict, ctx: dict | None = None) -> str:
"""Render the OOB nav entries swap.
Produces the ``entries-calendars-nav-wrapper`` OOB element with links
@@ -2419,7 +2412,7 @@ def render_nav_entries_oob(associated_entries, calendars, post: dict, ctx: dict
has_items = bool(entries_list or calendars)
if not has_items:
- return sx_call("blog-nav-entries-empty")
+ return await render_to_sx("blog-nav-entries-empty")
events_url_fn = ctx.get("events_url")
@@ -2467,7 +2460,7 @@ def render_nav_entries_oob(associated_entries, calendars, post: dict, ctx: dict
href = events_url_fn(entry_path) if events_url_fn else entry_path
- item_parts.append(sx_call("calendar-entry-nav",
+ item_parts.append(await render_to_sx("calendar-entry-nav",
href=href, nav_class=nav_cls, name=e_name, date_str=date_str,
))
@@ -2478,13 +2471,13 @@ def render_nav_entries_oob(associated_entries, calendars, post: dict, ctx: dict
cal_path = f"/{post_slug}/{cal_slug}/"
href = events_url_fn(cal_path) if events_url_fn else cal_path
- item_parts.append(sx_call("blog-nav-calendar-item",
+ item_parts.append(await render_to_sx("blog-nav-calendar-item",
href=href, nav_cls=nav_cls, name=cal_name,
))
items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else ""
- return sx_call("scroll-nav-wrapper",
+ return await render_to_sx("scroll-nav-wrapper",
wrapper_id="entries-calendars-nav-wrapper", container_id="associated-items-container",
arrow_cls="entries-nav-arrow",
left_hs="on click set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft - 200",
diff --git a/blog/sxc/pages/__init__.py b/blog/sxc/pages/__init__.py
index 0536ba1..dbecb2d 100644
--- a/blog/sxc/pages/__init__.py
+++ b/blog/sxc/pages/__init__.py
@@ -129,148 +129,148 @@ def _register_blog_layouts() -> None:
# --- Blog layout (root + blog header) ---
-def _blog_full(ctx: dict, **kw: Any) -> str:
+async def _blog_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx
from sx.sx_components import _blog_header_sx
- root_hdr = root_header_sx(ctx)
- blog_hdr = _blog_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx)
+ blog_hdr = await _blog_header_sx(ctx)
return "(<> " + root_hdr + " " + blog_hdr + ")"
-def _blog_oob(ctx: dict, **kw: Any) -> str:
+async def _blog_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, oob_header_sx
from sx.sx_components import _blog_header_sx
- root_hdr = root_header_sx(ctx)
- blog_hdr = _blog_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx)
+ blog_hdr = await _blog_header_sx(ctx)
rows = "(<> " + root_hdr + " " + blog_hdr + ")"
- return oob_header_sx("root-header-child", "blog-header-child", rows)
+ return await oob_header_sx("root-header-child", "blog-header-child", rows)
# --- Settings layout (root + settings header) ---
-def _settings_full(ctx: dict, **kw: Any) -> str:
+async def _settings_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx
from sx.sx_components import _settings_header_sx
- root_hdr = root_header_sx(ctx)
- settings_hdr = _settings_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx)
+ settings_hdr = await _settings_header_sx(ctx)
return "(<> " + root_hdr + " " + settings_hdr + ")"
-def _settings_oob(ctx: dict, **kw: Any) -> str:
+async def _settings_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import root_header_sx, oob_header_sx
from sx.sx_components import _settings_header_sx
- root_hdr = root_header_sx(ctx)
- settings_hdr = _settings_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx)
+ settings_hdr = await _settings_header_sx(ctx)
rows = "(<> " + root_hdr + " " + settings_hdr + ")"
- return oob_header_sx("root-header-child", "root-settings-header-child", rows)
+ return await oob_header_sx("root-header-child", "root-settings-header-child", rows)
-def _settings_mobile(ctx: dict, **kw: Any) -> str:
+async def _settings_mobile(ctx: dict, **kw: Any) -> str:
from sx.sx_components import _settings_nav_sx
- return _settings_nav_sx(ctx)
+ return await _settings_nav_sx(ctx)
# --- Sub-settings helpers ---
-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:
from shared.sx.helpers import root_header_sx
from sx.sx_components import _settings_header_sx, _sub_settings_header_sx
from quart import url_for as qurl
- root_hdr = root_header_sx(ctx)
- settings_hdr = _settings_header_sx(ctx)
- sub_hdr = _sub_settings_header_sx(row_id, child_id,
+ root_hdr = await root_header_sx(ctx)
+ settings_hdr = await _settings_header_sx(ctx)
+ sub_hdr = await _sub_settings_header_sx(row_id, child_id,
qurl(endpoint), icon, label, ctx)
return "(<> " + root_hdr + " " + settings_hdr + " " + sub_hdr + ")"
-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:
from shared.sx.helpers import oob_header_sx
from sx.sx_components import _settings_header_sx, _sub_settings_header_sx
from quart import url_for as qurl
- settings_hdr_oob = _settings_header_sx(ctx, oob=True)
- sub_hdr = _sub_settings_header_sx(row_id, child_id,
+ settings_hdr_oob = await _settings_header_sx(ctx, oob=True)
+ sub_hdr = await _sub_settings_header_sx(row_id, child_id,
qurl(endpoint), icon, label, ctx)
- sub_oob = 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 "(<> " + settings_hdr_oob + " " + sub_oob + ")"
# --- Cache ---
-def _cache_full(ctx: dict, **kw: Any) -> str:
- return _sub_settings_full(ctx, "cache-row", "cache-header-child",
+async def _cache_full(ctx: dict, **kw: Any) -> str:
+ return await _sub_settings_full(ctx, "cache-row", "cache-header-child",
"defpage_cache_page", "refresh", "Cache")
-def _cache_oob(ctx: dict, **kw: Any) -> str:
- return _sub_settings_oob(ctx, "cache-row", "cache-header-child",
+async def _cache_oob(ctx: dict, **kw: Any) -> str:
+ return await _sub_settings_oob(ctx, "cache-row", "cache-header-child",
"defpage_cache_page", "refresh", "Cache")
# --- Snippets ---
-def _snippets_full(ctx: dict, **kw: Any) -> str:
- return _sub_settings_full(ctx, "snippets-row", "snippets-header-child",
+async def _snippets_full(ctx: dict, **kw: Any) -> str:
+ return await _sub_settings_full(ctx, "snippets-row", "snippets-header-child",
"defpage_snippets_page", "puzzle-piece", "Snippets")
-def _snippets_oob(ctx: dict, **kw: Any) -> str:
- return _sub_settings_oob(ctx, "snippets-row", "snippets-header-child",
+async def _snippets_oob(ctx: dict, **kw: Any) -> str:
+ return await _sub_settings_oob(ctx, "snippets-row", "snippets-header-child",
"defpage_snippets_page", "puzzle-piece", "Snippets")
# --- Menu Items ---
-def _menu_items_full(ctx: dict, **kw: Any) -> str:
- return _sub_settings_full(ctx, "menu_items-row", "menu_items-header-child",
+async def _menu_items_full(ctx: dict, **kw: Any) -> str:
+ return await _sub_settings_full(ctx, "menu_items-row", "menu_items-header-child",
"defpage_menu_items_page", "bars", "Menu Items")
-def _menu_items_oob(ctx: dict, **kw: Any) -> str:
- return _sub_settings_oob(ctx, "menu_items-row", "menu_items-header-child",
+async def _menu_items_oob(ctx: dict, **kw: Any) -> str:
+ return await _sub_settings_oob(ctx, "menu_items-row", "menu_items-header-child",
"defpage_menu_items_page", "bars", "Menu Items")
# --- Tag Groups ---
-def _tag_groups_full(ctx: dict, **kw: Any) -> str:
- return _sub_settings_full(ctx, "tag-groups-row", "tag-groups-header-child",
+async def _tag_groups_full(ctx: dict, **kw: Any) -> str:
+ return await _sub_settings_full(ctx, "tag-groups-row", "tag-groups-header-child",
"defpage_tag_groups_page", "tags", "Tag Groups")
-def _tag_groups_oob(ctx: dict, **kw: Any) -> str:
- return _sub_settings_oob(ctx, "tag-groups-row", "tag-groups-header-child",
+async def _tag_groups_oob(ctx: dict, **kw: Any) -> str:
+ return await _sub_settings_oob(ctx, "tag-groups-row", "tag-groups-header-child",
"defpage_tag_groups_page", "tags", "Tag Groups")
# --- Tag Group Edit ---
-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
g_id = (request.view_args or {}).get("id")
from quart import url_for as qurl
from shared.sx.helpers import root_header_sx
from sx.sx_components import _settings_header_sx, _sub_settings_header_sx
- root_hdr = root_header_sx(ctx)
- settings_hdr = _settings_header_sx(ctx)
- sub_hdr = _sub_settings_header_sx("tag-groups-row", "tag-groups-header-child",
+ root_hdr = await root_header_sx(ctx)
+ settings_hdr = await _settings_header_sx(ctx)
+ sub_hdr = await _sub_settings_header_sx("tag-groups-row", "tag-groups-header-child",
qurl("defpage_tag_group_edit", id=g_id),
"tags", "Tag Groups", ctx)
return "(<> " + root_hdr + " " + settings_hdr + " " + sub_hdr + ")"
-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
g_id = (request.view_args or {}).get("id")
from quart import url_for as qurl
from shared.sx.helpers import oob_header_sx
from sx.sx_components import _settings_header_sx, _sub_settings_header_sx
- settings_hdr_oob = _settings_header_sx(ctx, oob=True)
- sub_hdr = _sub_settings_header_sx("tag-groups-row", "tag-groups-header-child",
+ settings_hdr_oob = await _settings_header_sx(ctx, oob=True)
+ sub_hdr = await _sub_settings_header_sx("tag-groups-row", "tag-groups-header-child",
qurl("defpage_tag_group_edit", id=g_id),
"tags", "Tag Groups", ctx)
- sub_oob = 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 "(<> " + settings_hdr_oob + " " + sub_oob + ")"
@@ -302,12 +302,12 @@ def _register_blog_helpers() -> None:
async def _h_editor_content(**kw):
from sx.sx_components import render_editor_panel
- return render_editor_panel()
+ return await render_editor_panel()
async def _h_editor_page_content(**kw):
from sx.sx_components import render_editor_panel
- return render_editor_panel(is_page=True)
+ return await render_editor_panel(is_page=True)
# --- Post admin helpers ---
@@ -391,7 +391,7 @@ async def _h_post_preview_content(slug=None, **kw):
from sx.sx_components import _preview_main_panel_sx
tctx = await get_template_context()
tctx.update(preview_ctx)
- return _preview_main_panel_sx(tctx)
+ return await _preview_main_panel_sx(tctx)
async def _h_post_entries_content(slug=None, **kw):
@@ -415,7 +415,7 @@ async def _h_post_entries_content(slug=None, **kw):
tctx = await get_template_context()
tctx["all_calendars"] = all_calendars
tctx["associated_entry_ids"] = associated_entry_ids
- return _post_entries_content_sx(tctx)
+ return await _post_entries_content_sx(tctx)
async def _h_post_settings_content(slug=None, **kw):
@@ -468,7 +468,7 @@ async def _h_post_edit_content(slug=None, **kw):
tctx["save_success"] = save_success
tctx["save_error"] = save_error
tctx["newsletters"] = newsletters
- return _post_edit_content_sx(tctx)
+ return await _post_edit_content_sx(tctx)
# --- Settings helpers ---
@@ -484,7 +484,7 @@ async def _h_cache_content(**kw):
from shared.sx.page import get_template_context
from sx.sx_components import _cache_main_panel_sx
tctx = await get_template_context()
- return _cache_main_panel_sx(tctx)
+ return await _cache_main_panel_sx(tctx)
# --- Snippets helper ---
@@ -506,7 +506,7 @@ async def _h_snippets_content(**kw):
tctx = await get_template_context()
tctx["snippets"] = rows
tctx["is_admin"] = is_admin
- return _snippets_main_panel_sx(tctx)
+ return await _snippets_main_panel_sx(tctx)
# --- Menu Items helper ---
@@ -519,7 +519,7 @@ async def _h_menu_items_content(**kw):
from sx.sx_components import _menu_items_main_panel_sx
tctx = await get_template_context()
tctx["menu_items"] = menu_items
- return _menu_items_main_panel_sx(tctx)
+ return await _menu_items_main_panel_sx(tctx)
# --- Tag Groups helpers ---
@@ -539,7 +539,7 @@ async def _h_tag_groups_content(**kw):
from sx.sx_components import _tag_groups_main_panel_sx
tctx = await get_template_context()
tctx.update({"groups": groups, "unassigned_tags": unassigned})
- return _tag_groups_main_panel_sx(tctx)
+ return await _tag_groups_main_panel_sx(tctx)
async def _h_tag_group_edit_content(id=None, **kw):
@@ -571,4 +571,4 @@ async def _h_tag_group_edit_content(id=None, **kw):
"all_tags": all_tags,
"assigned_tag_ids": set(assigned_rows),
})
- return _tag_groups_edit_main_panel_sx(tctx)
+ return await _tag_groups_edit_main_panel_sx(tctx)
diff --git a/cart/bp/page_admin/routes.py b/cart/bp/page_admin/routes.py
index f77492c..566365d 100644
--- a/cart/bp/page_admin/routes.py
+++ b/cart/bp/page_admin/routes.py
@@ -49,7 +49,7 @@ def register():
from shared.sx.page import get_template_context
from sx.sx_components import render_cart_payments_panel
ctx = await get_template_context()
- html = render_cart_payments_panel(ctx)
+ html = await render_cart_payments_panel(ctx)
return sx_response(html)
return bp
diff --git a/cart/sx/sx_components.py b/cart/sx/sx_components.py
index f8174a2..2e05620 100644
--- a/cart/sx/sx_components.py
+++ b/cart/sx/sx_components.py
@@ -16,9 +16,9 @@ from shared.sx.helpers import (
post_header_sx as _shared_post_header_sx,
search_desktop_sx, search_mobile_sx,
full_page_sx, oob_page_sx, header_child_sx,
- sx_call, SxExpr,
+ render_to_sx,
)
-from shared.sx.parser import serialize
+from shared.sx.parser import SxExpr
from shared.infrastructure.urls import cart_url
# Load cart-specific .sx components + handlers at import time
@@ -69,12 +69,12 @@ async def _post_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> st
"""Build post-level header row from page_post DTO, using shared helper."""
ctx = _ensure_post_ctx(ctx, page_post)
ctx = await _ensure_container_nav(ctx)
- return _shared_post_header_sx(ctx, oob=oob)
+ return await _shared_post_header_sx(ctx, oob=oob)
-def _cart_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _cart_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build the cart section header row."""
- return sx_call(
+ return await render_to_sx(
"menu-row-sx",
id="cart-row", level=1, colour="sky",
link_href=call_url(ctx, "cart_url", "/"),
@@ -83,17 +83,17 @@ def _cart_header_sx(ctx: dict, *, oob: bool = False) -> str:
)
-def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> str:
+async def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> str:
"""Build the per-page cart header row."""
slug = page_post.slug if page_post else ""
title = ((page_post.title if page_post else None) or "")[:160]
label_parts = []
if page_post and page_post.feature_image:
- label_parts.append(sx_call("cart-page-label-img", src=page_post.feature_image))
+ label_parts.append(await render_to_sx("cart-page-label-img", src=page_post.feature_image))
label_parts.append(f'(span "{escape(title)}")')
label_sx = "(<> " + " ".join(label_parts) + ")"
- nav_sx = sx_call("cart-all-carts-link", href=call_url(ctx, "cart_url", "/"))
- return sx_call(
+ nav_sx = await render_to_sx("cart-all-carts-link", href=call_url(ctx, "cart_url", "/"))
+ return await render_to_sx(
"menu-row-sx",
id="page-cart-row", level=2, colour="sky",
link_href=call_url(ctx, "cart_url", f"/{slug}/"),
@@ -102,26 +102,26 @@ def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> str
)
-def _auth_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _auth_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build the account section header row (for orders)."""
- return sx_call(
+ return await render_to_sx(
"auth-header-row-simple",
account_url=call_url(ctx, "account_url", ""),
oob=oob,
)
-def _orders_header_sx(ctx: dict, list_url: str) -> str:
+async def _orders_header_sx(ctx: dict, list_url: str) -> str:
"""Build the orders section header row."""
- return sx_call("orders-header-row", list_url=list_url)
+ return await render_to_sx("orders-header-row", list_url=list_url)
-def _cart_page_admin_header_sx(ctx: dict, page_post: Any, *, oob: bool = False,
+async def _cart_page_admin_header_sx(ctx: dict, page_post: Any, *, oob: bool = False,
selected: str = "") -> str:
"""Build the page-level admin header row."""
slug = page_post.slug if page_post else ""
ctx = _ensure_post_ctx(ctx, page_post)
- return post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
+ return await post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
# ---------------------------------------------------------------------------
@@ -190,24 +190,25 @@ async def render_orders_page(ctx: dict, orders: list, page: int,
detail_url_prefix = pfx + url_for_fn("orders.order.order_detail", order_id=0).rsplit("0/", 1)[0]
order_dicts = [_serialize_order(o) for o in orders]
- content = sx_call("orders-list-content",
- orders=SxExpr(serialize(order_dicts)),
+ content = await render_to_sx("orders-list-content",
+ orders=order_dicts,
page=page, total_pages=total_pages,
rows_url=rows_url, detail_url_prefix=detail_url_prefix)
- hdr = root_header_sx(ctx)
- auth = _auth_header_sx(ctx)
- orders_hdr = _orders_header_sx(ctx, list_url)
- auth_child = sx_call(
+ hdr = await root_header_sx(ctx)
+ auth = await _auth_header_sx(ctx)
+ orders_hdr = await _orders_header_sx(ctx, list_url)
+ auth_child_inner = await render_to_sx("header-child-sx", id="auth-header-child", inner=SxExpr(orders_hdr))
+ auth_child = await render_to_sx(
"header-child-sx",
- inner=SxExpr("(<> " + auth + " " + sx_call("header-child-sx", id="auth-header-child", inner=SxExpr(orders_hdr)) + ")"),
+ inner=SxExpr("(<> " + auth + " " + auth_child_inner + ")"),
)
header_rows = "(<> " + hdr + " " + auth_child + ")"
- filt = sx_call("order-list-header", search_mobile=SxExpr(search_mobile_sx(ctx)))
- return full_page_sx(ctx, header_rows=header_rows,
+ filt = await render_to_sx("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
+ return await full_page_sx(ctx, header_rows=header_rows,
filter=filt,
- aside=search_desktop_sx(ctx),
+ aside=await search_desktop_sx(ctx),
content=content)
@@ -222,20 +223,21 @@ async def render_orders_rows(ctx: dict, orders: list, page: int,
detail_url_prefix = pfx + url_for_fn("orders.order.order_detail", order_id=0).rsplit("0/", 1)[0]
order_dicts = [_serialize_order(o) for o in orders]
- parts = [sx_call("order-row-pair",
- order=SxExpr(serialize(od)),
- detail_url_prefix=detail_url_prefix)
- for od in order_dicts]
+ parts = []
+ for od in order_dicts:
+ parts.append(await render_to_sx("order-row-pair",
+ order=od,
+ detail_url_prefix=detail_url_prefix))
if page < total_pages:
next_url = list_url + qs_fn(page=page + 1)
- parts.append(sx_call(
+ parts.append(await render_to_sx(
"infinite-scroll",
url=next_url, page=page, total_pages=total_pages,
id_prefix="orders", colspan=5,
))
else:
- parts.append(sx_call("order-end-row"))
+ parts.append(await render_to_sx("order-end-row"))
return "(<> " + " ".join(parts) + ")"
@@ -255,24 +257,25 @@ async def render_orders_oob(ctx: dict, orders: list, page: int,
detail_url_prefix = pfx + url_for_fn("orders.order.order_detail", order_id=0).rsplit("0/", 1)[0]
order_dicts = [_serialize_order(o) for o in orders]
- content = sx_call("orders-list-content",
- orders=SxExpr(serialize(order_dicts)),
+ content = await render_to_sx("orders-list-content",
+ orders=order_dicts,
page=page, total_pages=total_pages,
rows_url=rows_url, detail_url_prefix=detail_url_prefix)
- auth_oob = _auth_header_sx(ctx, oob=True)
- auth_child_oob = sx_call(
+ auth_oob = await _auth_header_sx(ctx, oob=True)
+ orders_hdr = await _orders_header_sx(ctx, list_url)
+ auth_child_oob = await render_to_sx(
"oob-header-sx",
parent_id="auth-header-child",
- row=SxExpr(_orders_header_sx(ctx, list_url)),
+ row=SxExpr(orders_hdr),
)
- root_oob = root_header_sx(ctx, oob=True)
+ root_oob = await root_header_sx(ctx, oob=True)
oobs = "(<> " + auth_oob + " " + auth_child_oob + " " + root_oob + ")"
- filt = sx_call("order-list-header", search_mobile=SxExpr(search_mobile_sx(ctx)))
- return oob_page_sx(oobs=oobs,
+ filt = await render_to_sx("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
+ return await oob_page_sx(oobs=oobs,
filter=filt,
- aside=search_desktop_sx(ctx),
+ aside=await search_desktop_sx(ctx),
content=content)
@@ -296,29 +299,32 @@ async def render_order_page(ctx: dict, order: Any,
order_data = _serialize_order(order)
cal_data = [_serialize_calendar_entry(e) for e in (calendar_entries or [])]
- main = sx_call("order-detail-content",
- order=SxExpr(serialize(order_data)),
- calendar_entries=SxExpr(serialize(cal_data)))
- filt = sx_call("order-detail-filter-content",
- order=SxExpr(serialize(order_data)),
+ main = await render_to_sx("order-detail-content",
+ order=order_data,
+ calendar_entries=cal_data)
+ filt = await render_to_sx("order-detail-filter-content",
+ order=order_data,
list_url=list_url, recheck_url=recheck_url,
pay_url=pay_url, csrf=generate_csrf_token())
- hdr = root_header_sx(ctx)
- order_row = sx_call(
+ hdr = await root_header_sx(ctx)
+ order_row = await render_to_sx(
"menu-row-sx",
id="order-row", level=3, colour="sky",
link_href=detail_url, link_label=f"Order {order.id}", icon="fa fa-gbp",
)
- order_child = sx_call(
+ auth = await _auth_header_sx(ctx)
+ orders_hdr = await _orders_header_sx(ctx, list_url)
+ orders_child = await render_to_sx("header-child-sx", id="orders-header-child", inner=SxExpr(order_row))
+ auth_inner = "(<> " + orders_hdr + " " + orders_child + ")"
+ auth_child = await render_to_sx("header-child-sx", id="auth-header-child", inner=SxExpr(auth_inner))
+ order_child = await render_to_sx(
"header-child-sx",
- inner=SxExpr("(<> " + _auth_header_sx(ctx) + " " + sx_call("header-child-sx", id="auth-header-child", inner=SxExpr(
- "(<> " + _orders_header_sx(ctx, list_url) + " " + sx_call("header-child-sx", id="orders-header-child", inner=SxExpr(order_row)) + ")"
- )) + ")"),
+ inner=SxExpr("(<> " + auth + " " + auth_child + ")"),
)
header_rows = "(<> " + hdr + " " + order_child + ")"
- return full_page_sx(ctx, header_rows=header_rows, filter=filt, content=main)
+ return await full_page_sx(ctx, header_rows=header_rows, filter=filt, content=main)
async def render_order_oob(ctx: dict, order: Any,
@@ -337,27 +343,27 @@ async def render_order_oob(ctx: dict, order: Any,
order_data = _serialize_order(order)
cal_data = [_serialize_calendar_entry(e) for e in (calendar_entries or [])]
- main = sx_call("order-detail-content",
- order=SxExpr(serialize(order_data)),
- calendar_entries=SxExpr(serialize(cal_data)))
- filt = sx_call("order-detail-filter-content",
- order=SxExpr(serialize(order_data)),
+ main = await render_to_sx("order-detail-content",
+ order=order_data,
+ calendar_entries=cal_data)
+ filt = await render_to_sx("order-detail-filter-content",
+ order=order_data,
list_url=list_url, recheck_url=recheck_url,
pay_url=pay_url, csrf=generate_csrf_token())
- order_row_oob = sx_call(
+ order_row_oob = await render_to_sx(
"menu-row-sx",
id="order-row", level=3, colour="sky",
link_href=detail_url, link_label=f"Order {order.id}", icon="fa fa-gbp",
oob=True,
)
- orders_child_oob = sx_call("oob-header-sx",
+ orders_child_oob = await render_to_sx("oob-header-sx",
parent_id="orders-header-child",
row=SxExpr(order_row_oob))
- root_oob = root_header_sx(ctx, oob=True)
+ root_oob = await root_header_sx(ctx, oob=True)
oobs = "(<> " + orders_child_oob + " " + root_oob + ")"
- return oob_page_sx(oobs=oobs, filter=filt, content=main)
+ return await oob_page_sx(oobs=oobs, filter=filt, content=main)
# ---------------------------------------------------------------------------
@@ -370,25 +376,25 @@ async def render_checkout_error_page(ctx: dict, error: str | None = None,
err_msg = error or "Unexpected error while creating the hosted checkout session."
order_sx = None
if order:
- order_sx = sx_call("checkout-error-order-id", oid=f"#{order.id}")
+ order_sx = await render_to_sx("checkout-error-order-id", oid=f"#{order.id}")
back_url = cart_url("/")
- hdr = root_header_sx(ctx)
- filt = sx_call("checkout-error-header")
- content = sx_call(
+ hdr = await root_header_sx(ctx)
+ filt = await render_to_sx("checkout-error-header")
+ content = await render_to_sx(
"checkout-error-content",
msg=err_msg,
order=SxExpr(order_sx) if order_sx else None,
back_url=back_url,
)
- return full_page_sx(ctx, header_rows=hdr, filter=filt, content=content)
+ return await full_page_sx(ctx, header_rows=hdr, filter=filt, content=content)
# ---------------------------------------------------------------------------
# Public API: POST response renderers
# ---------------------------------------------------------------------------
-def render_cart_payments_panel(ctx: dict) -> str:
+async def render_cart_payments_panel(ctx: dict) -> str:
"""Render the payments config panel for PUT response."""
page_config = ctx.get("page_config")
pc_data = None
@@ -398,5 +404,5 @@ def render_cart_payments_panel(ctx: dict) -> str:
"sumup_merchant_code": getattr(page_config, "sumup_merchant_code", None) or "",
"sumup_checkout_prefix": getattr(page_config, "sumup_checkout_prefix", None) or "",
}
- return sx_call("cart-payments-content",
- page_config=SxExpr(serialize(pc_data)) if pc_data else None)
+ return await render_to_sx("cart-payments-content",
+ page_config=pc_data)
diff --git a/cart/sxc/pages/__init__.py b/cart/sxc/pages/__init__.py
index b41a861..dd2576a 100644
--- a/cart/sxc/pages/__init__.py
+++ b/cart/sxc/pages/__init__.py
@@ -27,31 +27,35 @@ def _register_cart_layouts() -> None:
register_custom_layout("cart-admin", _cart_admin_full, _cart_admin_oob)
-def _cart_page_full(ctx: dict, **kw: Any) -> str:
- from shared.sx.helpers import root_header_sx, sx_call, SxExpr
+async def _cart_page_full(ctx: dict, **kw: Any) -> str:
+ from shared.sx.helpers import root_header_sx, render_to_sx
+ from shared.sx.parser import SxExpr
from sx.sx_components import _cart_header_sx, _page_cart_header_sx
page_post = ctx.get("page_post")
- root_hdr = root_header_sx(ctx)
- child = _cart_header_sx(ctx)
- page_hdr = _page_cart_header_sx(ctx, page_post)
- nested = sx_call(
+ root_hdr = await root_header_sx(ctx)
+ child = await _cart_header_sx(ctx)
+ page_hdr = await _page_cart_header_sx(ctx, page_post)
+ inner_child = await render_to_sx("header-child-sx", id="cart-header-child", inner=SxExpr(page_hdr))
+ nested = await render_to_sx(
"header-child-sx",
- inner=SxExpr("(<> " + child + " " + sx_call("header-child-sx", id="cart-header-child", inner=SxExpr(page_hdr)) + ")"),
+ inner=SxExpr("(<> " + child + " " + inner_child + ")"),
)
return "(<> " + root_hdr + " " + nested + ")"
-def _cart_page_oob(ctx: dict, **kw: Any) -> str:
- from shared.sx.helpers import root_header_sx, sx_call, SxExpr
+async def _cart_page_oob(ctx: dict, **kw: Any) -> str:
+ from shared.sx.helpers import root_header_sx, render_to_sx
+ from shared.sx.parser import SxExpr
from sx.sx_components import _cart_header_sx, _page_cart_header_sx
page_post = ctx.get("page_post")
- child_oob = sx_call("oob-header-sx",
+ page_hdr = await _page_cart_header_sx(ctx, page_post)
+ child_oob = await render_to_sx("oob-header-sx",
parent_id="cart-header-child",
- row=SxExpr(_page_cart_header_sx(ctx, page_post)))
- cart_hdr_oob = _cart_header_sx(ctx, oob=True)
- root_hdr_oob = root_header_sx(ctx, oob=True)
+ row=SxExpr(page_hdr))
+ cart_hdr_oob = await _cart_header_sx(ctx, oob=True)
+ root_hdr_oob = await root_header_sx(ctx, oob=True)
return "(<> " + child_oob + " " + cart_hdr_oob + " " + root_hdr_oob + ")"
@@ -61,9 +65,9 @@ async def _cart_admin_full(ctx: dict, **kw: Any) -> str:
page_post = ctx.get("page_post")
selected = kw.get("selected", "")
- root_hdr = root_header_sx(ctx)
+ root_hdr = await root_header_sx(ctx)
post_hdr = await _post_header_sx(ctx, page_post)
- admin_hdr = _cart_page_admin_header_sx(ctx, page_post, selected=selected)
+ admin_hdr = await _cart_page_admin_header_sx(ctx, page_post, selected=selected)
return "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
@@ -72,7 +76,7 @@ async def _cart_admin_oob(ctx: dict, **kw: Any) -> str:
page_post = ctx.get("page_post")
selected = kw.get("selected", "")
- return _cart_page_admin_header_sx(ctx, page_post, oob=True, selected=selected)
+ return await _cart_page_admin_header_sx(ctx, page_post, oob=True, selected=selected)
# ---------------------------------------------------------------------------
@@ -244,22 +248,21 @@ def _build_summary_data(ctx: dict, cart: list, cal_entries: list, tickets: list,
async def _h_overview_content(**kw):
from quart import g
- from shared.sx.helpers import sx_call, SxExpr
- from shared.sx.parser import serialize
+ from shared.sx.helpers import render_to_sx
from shared.infrastructure.urls import cart_url
from bp.cart.services import get_cart_grouped_by_page
page_groups = await get_cart_grouped_by_page(g.s)
grp_dicts = [d for d in (_serialize_page_group(grp) for grp in page_groups) if d]
- return sx_call("cart-overview-content",
- page_groups=SxExpr(serialize(grp_dicts)),
+ return await render_to_sx("cart-overview-content",
+ page_groups=grp_dicts,
cart_url_base=cart_url(""))
async def _h_page_cart_content(page_slug=None, **kw):
from quart import g
- from shared.sx.helpers import sx_call, SxExpr
- from shared.sx.parser import serialize
+ from shared.sx.helpers import render_to_sx
+ from shared.sx.parser import SxExpr
from shared.sx.page import get_template_context
from bp.cart.services import total, calendar_total, ticket_total
from bp.cart.services.page_cart import (
@@ -277,7 +280,7 @@ async def _h_page_cart_content(page_slug=None, **kw):
sd = _build_summary_data(ctx, cart, cal_entries, page_tickets,
total, calendar_total, ticket_total)
- summary_sx = sx_call("cart-summary-from-data",
+ summary_sx = await render_to_sx("cart-summary-from-data",
item_count=sd["item_count"],
grand_total=sd["grand_total"],
symbol=sd["symbol"],
@@ -286,10 +289,10 @@ async def _h_page_cart_content(page_slug=None, **kw):
login_href=sd.get("login_href"),
user_email=sd.get("user_email"))
- return sx_call("cart-page-cart-content",
- cart_items=SxExpr(serialize([_serialize_cart_item(i) for i in cart])),
- cal_entries=SxExpr(serialize([_serialize_cal_entry(e) for e in cal_entries])),
- ticket_groups=SxExpr(serialize([_serialize_ticket_group(tg) for tg in ticket_groups])),
+ return await render_to_sx("cart-page-cart-content",
+ cart_items=[_serialize_cart_item(i) for i in cart],
+ cal_entries=[_serialize_cal_entry(e) for e in cal_entries],
+ ticket_groups=[_serialize_ticket_group(tg) for tg in ticket_groups],
summary=SxExpr(summary_sx))
@@ -299,8 +302,7 @@ async def _h_cart_admin_content(page_slug=None, **kw):
async def _h_cart_payments_content(page_slug=None, **kw):
from shared.sx.page import get_template_context
- from shared.sx.helpers import sx_call, SxExpr
- from shared.sx.parser import serialize
+ from shared.sx.helpers import render_to_sx
ctx = await get_template_context()
page_config = ctx.get("page_config")
@@ -311,5 +313,5 @@ async def _h_cart_payments_content(page_slug=None, **kw):
"sumup_merchant_code": getattr(page_config, "sumup_merchant_code", None) or "",
"sumup_checkout_prefix": getattr(page_config, "sumup_checkout_prefix", None) or "",
}
- return sx_call("cart-payments-content",
- page_config=SxExpr(serialize(pc_data)) if pc_data else None)
+ return await render_to_sx("cart-payments-content",
+ page_config=pc_data)
diff --git a/events/bp/all_events/routes.py b/events/bp/all_events/routes.py
index b657b32..6534ea9 100644
--- a/events/bp/all_events/routes.py
+++ b/events/bp/all_events/routes.py
@@ -126,7 +126,7 @@ def register() -> Blueprint:
frag_params["session_id"] = ident["session_id"]
from sx.sx_components import render_ticket_widget
- widget_html = render_ticket_widget(entry, qty, "/all-tickets/adjust")
+ widget_html = await render_ticket_widget(entry, qty, "/all-tickets/adjust")
mini_html = await fetch_fragment("cart", "cart-mini", params=frag_params, required=False)
return sx_response(widget_html + (mini_html or ""))
diff --git a/events/bp/calendar/admin/routes.py b/events/bp/calendar/admin/routes.py
index e7fcc01..5f9abdb 100644
--- a/events/bp/calendar/admin/routes.py
+++ b/events/bp/calendar/admin/routes.py
@@ -19,7 +19,7 @@ def register():
@require_admin
async def calendar_description_edit(calendar_slug: str, **kwargs):
from sx.sx_components import render_calendar_description_edit
- html = render_calendar_description_edit(g.calendar)
+ html = await render_calendar_description_edit(g.calendar)
return sx_response(html)
@@ -35,7 +35,7 @@ def register():
await g.s.flush()
from sx.sx_components import render_calendar_description
- html = render_calendar_description(g.calendar, oob=True)
+ html = await render_calendar_description(g.calendar, oob=True)
return sx_response(html)
@@ -43,7 +43,7 @@ def register():
@require_admin
async def calendar_description_view(calendar_slug: str, **kwargs):
from sx.sx_components import render_calendar_description
- html = render_calendar_description(g.calendar)
+ html = await render_calendar_description(g.calendar)
return sx_response(html)
return bp
diff --git a/events/bp/calendar/routes.py b/events/bp/calendar/routes.py
index aa07ad9..d6e470a 100644
--- a/events/bp/calendar/routes.py
+++ b/events/bp/calendar/routes.py
@@ -201,7 +201,7 @@ def register():
from shared.sx.page import get_template_context
from sx.sx_components import _calendar_admin_main_panel_html
ctx = await get_template_context()
- html = _calendar_admin_main_panel_html(ctx)
+ html = await _calendar_admin_main_panel_html(ctx)
return sx_response(html)
@@ -220,7 +220,7 @@ def register():
from shared.sx.page import get_template_context
from sx.sx_components import render_calendars_list_panel
ctx = await get_template_context()
- html = render_calendars_list_panel(ctx)
+ html = await render_calendars_list_panel(ctx)
if post_data:
from shared.services.entry_associations import get_associated_entries
@@ -236,7 +236,7 @@ def register():
).scalars().all()
associated_entries = await get_associated_entries(post_id)
- nav_oob = render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
+ nav_oob = await render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
html = html + nav_oob
return sx_response(html)
diff --git a/events/bp/calendar_entries/routes.py b/events/bp/calendar_entries/routes.py
index 2ba164f..08b8475 100644
--- a/events/bp/calendar_entries/routes.py
+++ b/events/bp/calendar_entries/routes.py
@@ -259,7 +259,7 @@ def register():
}
from sx.sx_components import render_day_main_panel
- html = render_day_main_panel(ctx)
+ html = await render_day_main_panel(ctx)
mini_html = await fetch_fragment("cart", "cart-mini", params=frag_params, required=False)
return sx_response(html + (mini_html or ""))
@@ -280,12 +280,12 @@ def register():
day_slots = list(result.scalars())
from sx.sx_components import render_entry_add_form
- return sx_response(render_entry_add_form(g.calendar, day, month, year, day_slots))
+ return sx_response(await render_entry_add_form(g.calendar, day, month, year, day_slots))
@bp.get("/add-button/")
async def add_button(day: int, month: int, year: int, **kwargs):
from sx.sx_components import render_entry_add_button
- return sx_response(render_entry_add_button(g.calendar, day, month, year))
+ return sx_response(await render_entry_add_button(g.calendar, day, month, year))
diff --git a/events/bp/calendar_entry/routes.py b/events/bp/calendar_entry/routes.py
index 4139966..0df4002 100644
--- a/events/bp/calendar_entry/routes.py
+++ b/events/bp/calendar_entry/routes.py
@@ -112,7 +112,7 @@ def register():
# Render OOB nav
from sx.sx_components import render_day_entries_nav_oob
- return render_day_entries_nav_oob(visible.confirmed_entries, calendar, day_date)
+ return await render_day_entries_nav_oob(visible.confirmed_entries, calendar, day_date)
async def get_post_nav_oob(entry_id: int):
"""Helper to generate OOB update for post entries nav when entry state changes"""
@@ -149,7 +149,7 @@ def register():
# Render OOB nav for this post
from sx.sx_components import render_post_nav_entries_oob
- nav_oob = render_post_nav_entries_oob(associated_entries, calendars, post)
+ nav_oob = await render_post_nav_entries_oob(associated_entries, calendars, post)
nav_oobs.append(nav_oob)
return "".join(nav_oobs)
@@ -257,7 +257,7 @@ def register():
day_slots = list(result.scalars())
from sx.sx_components import render_entry_edit_form
- return sx_response(render_entry_edit_form(g.entry, g.calendar, day, month, year, day_slots))
+ return sx_response(await render_entry_edit_form(g.entry, g.calendar, day, month, year, day_slots))
@bp.put("/")
@require_admin
@@ -423,7 +423,7 @@ def register():
from sx.sx_components import _entry_main_panel_html
tctx = await get_template_context()
- html = _entry_main_panel_html(tctx)
+ html = await _entry_main_panel_html(tctx)
return sx_response(html + nav_oob)
@@ -449,7 +449,7 @@ def register():
# Re-read entry to get updated state
await g.s.refresh(g.entry)
from sx.sx_components import render_entry_optioned
- html = render_entry_optioned(g.entry, g.calendar, day, month, year)
+ html = await render_entry_optioned(g.entry, g.calendar, day, month, year)
return sx_response(html + day_nav_oob + post_nav_oob)
@bp.post("/decline/")
@@ -474,7 +474,7 @@ def register():
# Re-read entry to get updated state
await g.s.refresh(g.entry)
from sx.sx_components import render_entry_optioned
- html = render_entry_optioned(g.entry, g.calendar, day, month, year)
+ html = await render_entry_optioned(g.entry, g.calendar, day, month, year)
return sx_response(html + day_nav_oob + post_nav_oob)
@bp.post("/provisional/")
@@ -499,7 +499,7 @@ def register():
# Re-read entry to get updated state
await g.s.refresh(g.entry)
from sx.sx_components import render_entry_optioned
- html = render_entry_optioned(g.entry, g.calendar, day, month, year)
+ html = await render_entry_optioned(g.entry, g.calendar, day, month, year)
return sx_response(html + day_nav_oob + post_nav_oob)
@bp.post("/tickets/")
@@ -543,7 +543,7 @@ def register():
# Return just the tickets fragment (targeted by hx-target="#entry-tickets-...")
await g.s.refresh(g.entry)
from sx.sx_components import render_entry_tickets_config
- html = render_entry_tickets_config(g.entry, g.calendar, request.view_args.get("day"), request.view_args.get("month"), request.view_args.get("year"))
+ html = await render_entry_tickets_config(g.entry, g.calendar, request.view_args.get("day"), request.view_args.get("month"), request.view_args.get("year"))
return sx_response(html)
@bp.get("/posts/search/")
@@ -559,7 +559,7 @@ def register():
va = request.view_args or {}
from sx.sx_components import render_post_search_results
- return sx_response(render_post_search_results(
+ return sx_response(await render_post_search_results(
search_posts, query, page, total_pages,
g.entry, g.calendar,
va.get("day"), va.get("month"), va.get("year"),
@@ -594,8 +594,8 @@ def register():
# Return updated posts list + OOB nav update
from sx.sx_components import render_entry_posts_panel, render_entry_posts_nav_oob
va = request.view_args or {}
- html = render_entry_posts_panel(entry_posts, g.entry, g.calendar, va.get("day"), va.get("month"), va.get("year"))
- nav_oob = render_entry_posts_nav_oob(entry_posts)
+ html = await render_entry_posts_panel(entry_posts, g.entry, g.calendar, va.get("day"), va.get("month"), va.get("year"))
+ nav_oob = await render_entry_posts_nav_oob(entry_posts)
return sx_response(html + nav_oob)
@bp.delete("/posts//")
@@ -616,8 +616,8 @@ def register():
# Return updated posts list + OOB nav update
from sx.sx_components import render_entry_posts_panel, render_entry_posts_nav_oob
va = request.view_args or {}
- html = render_entry_posts_panel(entry_posts, g.entry, g.calendar, va.get("day"), va.get("month"), va.get("year"))
- nav_oob = render_entry_posts_nav_oob(entry_posts)
+ html = await render_entry_posts_panel(entry_posts, g.entry, g.calendar, va.get("day"), va.get("month"), va.get("year"))
+ nav_oob = await render_entry_posts_nav_oob(entry_posts)
return sx_response(html + nav_oob)
return bp
diff --git a/events/bp/calendars/routes.py b/events/bp/calendars/routes.py
index 350a939..a62d262 100644
--- a/events/bp/calendars/routes.py
+++ b/events/bp/calendars/routes.py
@@ -69,7 +69,7 @@ def register():
from shared.sx.page import get_template_context
from sx.sx_components import render_calendars_list_panel
ctx = await get_template_context()
- html = render_calendars_list_panel(ctx)
+ html = await render_calendars_list_panel(ctx)
# Blog-embedded mode: also update post nav
if post_data:
@@ -85,7 +85,7 @@ def register():
).scalars().all()
associated_entries = await get_associated_entries(post_id)
- nav_oob = render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
+ nav_oob = await render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
html = html + nav_oob
return sx_response(html)
diff --git a/events/bp/markets/routes.py b/events/bp/markets/routes.py
index 2335a86..1457163 100644
--- a/events/bp/markets/routes.py
+++ b/events/bp/markets/routes.py
@@ -44,7 +44,7 @@ def register():
from shared.sx.page import get_template_context
from sx.sx_components import render_markets_list_panel
ctx = await get_template_context()
- return sx_response(render_markets_list_panel(ctx))
+ return sx_response(await render_markets_list_panel(ctx))
@bp.delete("//")
@require_admin
@@ -57,6 +57,6 @@ def register():
from shared.sx.page import get_template_context
from sx.sx_components import render_markets_list_panel
ctx = await get_template_context()
- return sx_response(render_markets_list_panel(ctx))
+ return sx_response(await render_markets_list_panel(ctx))
return bp
diff --git a/events/bp/page/routes.py b/events/bp/page/routes.py
index bb1a700..87962a1 100644
--- a/events/bp/page/routes.py
+++ b/events/bp/page/routes.py
@@ -107,7 +107,7 @@ def register() -> Blueprint:
frag_params["session_id"] = ident["session_id"]
from sx.sx_components import render_ticket_widget
- widget_html = render_ticket_widget(entry, qty, f"/{g.post_slug}/tickets/adjust")
+ widget_html = await render_ticket_widget(entry, qty, f"/{g.post_slug}/tickets/adjust")
mini_html = await fetch_fragment("cart", "cart-mini", params=frag_params, required=False)
return sx_response(widget_html + (mini_html or ""))
diff --git a/events/bp/slot/routes.py b/events/bp/slot/routes.py
index 496e892..28a7449 100644
--- a/events/bp/slot/routes.py
+++ b/events/bp/slot/routes.py
@@ -36,7 +36,7 @@ def register():
if not slot:
return await make_response("Not found", 404)
from sx.sx_components import render_slot_edit_form
- return sx_response(render_slot_edit_form(slot, g.calendar))
+ return sx_response(await render_slot_edit_form(slot, g.calendar))
@bp.get("/view/")
@require_admin
@@ -45,7 +45,7 @@ def register():
if not slot:
return await make_response("Not found", 404)
from sx.sx_components import render_slot_main_panel
- return sx_response(render_slot_main_panel(slot, g.calendar))
+ return sx_response(await render_slot_main_panel(slot, g.calendar))
@bp.delete("/")
@require_admin
@@ -54,7 +54,7 @@ def register():
await svc_delete_slot(g.s, slot_id)
slots = await svc_list_slots(g.s, g.calendar.id)
from sx.sx_components import render_slots_table
- return sx_response(render_slots_table(slots, g.calendar))
+ return sx_response(await render_slots_table(slots, g.calendar))
@bp.put("/")
@require_admin
@@ -136,7 +136,7 @@ def register():
), 422
from sx.sx_components import render_slot_main_panel
- return sx_response(render_slot_main_panel(slot, g.calendar, oob=True))
+ return sx_response(await render_slot_main_panel(slot, g.calendar, oob=True))
diff --git a/events/bp/slots/routes.py b/events/bp/slots/routes.py
index e8443d0..37d9e70 100644
--- a/events/bp/slots/routes.py
+++ b/events/bp/slots/routes.py
@@ -111,19 +111,19 @@ def register():
# Success → re-render the slots table
slots = await svc_list_slots(g.s, g.calendar.id)
from sx.sx_components import render_slots_table
- return sx_response(render_slots_table(slots, g.calendar))
+ return sx_response(await render_slots_table(slots, g.calendar))
@bp.get("/add")
@require_admin
async def add_form(**kwargs):
from sx.sx_components import render_slot_add_form
- return sx_response(render_slot_add_form(g.calendar))
+ return sx_response(await render_slot_add_form(g.calendar))
@bp.get("/add-button")
@require_admin
async def add_button(**kwargs):
from sx.sx_components import render_slot_add_button
- return sx_response(render_slot_add_button(g.calendar))
+ return sx_response(await render_slot_add_button(g.calendar))
return bp
diff --git a/events/bp/ticket_admin/routes.py b/events/bp/ticket_admin/routes.py
index 993c969..1eddfcc 100644
--- a/events/bp/ticket_admin/routes.py
+++ b/events/bp/ticket_admin/routes.py
@@ -54,7 +54,7 @@ def register() -> Blueprint:
tickets = await get_tickets_for_entry(g.s, entry_id)
from sx.sx_components import render_entry_tickets_admin
- html = render_entry_tickets_admin(entry, tickets)
+ html = await render_entry_tickets_admin(entry, tickets)
return sx_response(html)
@bp.get("/lookup/")
@@ -71,9 +71,9 @@ def register() -> Blueprint:
ticket = await get_ticket_by_code(g.s, code)
from sx.sx_components import render_lookup_result
if not ticket:
- return sx_response(render_lookup_result(None, "Ticket not found"))
+ return sx_response(await render_lookup_result(None, "Ticket not found"))
- return sx_response(render_lookup_result(ticket, None))
+ return sx_response(await render_lookup_result(ticket, None))
@bp.post("//checkin/")
@require_admin
@@ -84,9 +84,9 @@ def register() -> Blueprint:
from sx.sx_components import render_checkin_result
if not success:
- return sx_response(render_checkin_result(False, error, None))
+ return sx_response(await render_checkin_result(False, error, None))
ticket = await get_ticket_by_code(g.s, code)
- return sx_response(render_checkin_result(True, None, ticket))
+ return sx_response(await render_checkin_result(True, None, ticket))
return bp
diff --git a/events/bp/ticket_type/routes.py b/events/bp/ticket_type/routes.py
index 09e1191..66d26bc 100644
--- a/events/bp/ticket_type/routes.py
+++ b/events/bp/ticket_type/routes.py
@@ -32,7 +32,7 @@ def register():
from sx.sx_components import render_ticket_type_edit_form
va = request.view_args or {}
- return sx_response(render_ticket_type_edit_form(
+ return sx_response(await render_ticket_type_edit_form(
ticket_type, g.entry, g.calendar,
va.get("day"), va.get("month"), va.get("year"),
))
@@ -47,7 +47,7 @@ def register():
from sx.sx_components import render_ticket_type_main_panel
va = request.view_args or {}
- return sx_response(render_ticket_type_main_panel(
+ return sx_response(await render_ticket_type_main_panel(
ticket_type, g.entry, g.calendar,
va.get("day"), va.get("month"), va.get("year"),
))
@@ -114,7 +114,7 @@ def register():
# Return updated view with OOB flag
from sx.sx_components import render_ticket_type_main_panel
va = request.view_args or {}
- return sx_response(render_ticket_type_main_panel(
+ return sx_response(await render_ticket_type_main_panel(
ticket_type, g.entry, g.calendar,
va.get("day"), va.get("month"), va.get("year"),
oob=True,
@@ -133,7 +133,7 @@ def register():
ticket_types = await svc_list_ticket_types(g.s, g.entry.id)
from sx.sx_components import render_ticket_types_table
va = request.view_args or {}
- return sx_response(render_ticket_types_table(
+ return sx_response(await render_ticket_types_table(
ticket_types, g.entry, g.calendar,
va.get("day"), va.get("month"), va.get("year"),
))
diff --git a/events/bp/ticket_types/routes.py b/events/bp/ticket_types/routes.py
index 2163c34..b65cb76 100644
--- a/events/bp/ticket_types/routes.py
+++ b/events/bp/ticket_types/routes.py
@@ -95,7 +95,7 @@ def register():
ticket_types = await svc_list_ticket_types(g.s, g.entry.id)
from sx.sx_components import render_ticket_types_table
va = request.view_args or {}
- return sx_response(render_ticket_types_table(
+ return sx_response(await render_ticket_types_table(
ticket_types, g.entry, g.calendar,
va.get("day"), va.get("month"), va.get("year"),
))
@@ -106,7 +106,7 @@ def register():
"""Show the add ticket type form."""
from sx.sx_components import render_ticket_type_add_form
va = request.view_args or {}
- return sx_response(render_ticket_type_add_form(
+ return sx_response(await render_ticket_type_add_form(
g.entry, g.calendar,
va.get("day"), va.get("month"), va.get("year"),
))
@@ -117,7 +117,7 @@ def register():
"""Show the add ticket type button."""
from sx.sx_components import render_ticket_type_add_button
va = request.view_args or {}
- return sx_response(render_ticket_type_add_button(
+ return sx_response(await render_ticket_type_add_button(
g.entry, g.calendar,
va.get("day"), va.get("month"), va.get("year"),
))
diff --git a/events/bp/tickets/routes.py b/events/bp/tickets/routes.py
index 54e8586..7d4106f 100644
--- a/events/bp/tickets/routes.py
+++ b/events/bp/tickets/routes.py
@@ -127,7 +127,7 @@ def register() -> Blueprint:
cart_count = summary.count + summary.calendar_count + summary.ticket_count
from sx.sx_components import render_buy_result
- return sx_response(render_buy_result(entry, created, remaining, cart_count))
+ return sx_response(await render_buy_result(entry, created, remaining, cart_count))
@bp.post("/adjust/")
@clear_cache(tag="calendars", tag_scope="all")
@@ -250,7 +250,7 @@ def register() -> Blueprint:
cart_count = summary.count + summary.calendar_count + summary.ticket_count
from sx.sx_components import render_adjust_response
- return sx_response(render_adjust_response(
+ return sx_response(await render_adjust_response(
entry, ticket_remaining, ticket_sold_count,
user_ticket_count, user_ticket_counts_by_type, cart_count,
))
diff --git a/events/sx/sx_components.py b/events/sx/sx_components.py
index 8326e25..338e6db 100644
--- a/events/sx/sx_components.py
+++ b/events/sx/sx_components.py
@@ -13,7 +13,7 @@ from markupsafe import escape
from shared.sx.jinja_bridge import load_service_components
from shared.sx.helpers import (
- call_url, get_asset_url, sx_call,
+ call_url, get_asset_url, render_to_sx,
root_header_sx, post_header_sx, post_admin_header_sx,
oob_header_sx, header_child_sx,
full_page_sx, oob_page_sx,
@@ -85,12 +85,12 @@ async def _ensure_container_nav(ctx: dict) -> dict:
return {**ctx, "container_nav": events_nav + market_nav}
-def _post_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _post_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build the post-level header row — delegates to shared sx helper."""
- return post_header_sx(ctx, oob=oob)
+ return await post_header_sx(ctx, oob=oob)
-def _post_nav_sx(ctx: dict) -> str:
+async def _post_nav_sx(ctx: dict) -> str:
"""Post desktop nav: calendar links + container nav (markets, etc.)."""
from quart import url_for, g
@@ -104,7 +104,7 @@ def _post_nav_sx(ctx: dict) -> str:
cal_name = getattr(cal, "name", "") if hasattr(cal, "name") else cal.get("name", "")
href = url_for("calendar.get", calendar_slug=cal_slug)
is_sel = (cal_slug == current_cal_slug)
- parts.append(sx_call("nav-link", href=href, icon="fa fa-calendar",
+ parts.append(await render_to_sx("nav-link", href=href, icon="fa fa-calendar",
label=cal_name, select_colours=select_colours,
is_selected=is_sel))
# Container nav fragments (markets, etc.)
@@ -143,13 +143,13 @@ def _post_nav_sx(ctx: dict) -> str:
# Calendars header
# ---------------------------------------------------------------------------
-def _calendars_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _calendars_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build the calendars section header row."""
from quart import url_for
link_href = url_for("calendars.home")
- return sx_call("menu-row-sx", id="calendars-row", level=3,
+ return await render_to_sx("menu-row-sx", id="calendars-row", level=3,
link_href=link_href,
- link_label_content=SxExpr(sx_call("events-calendars-label")),
+ link_label_content=SxExpr(await render_to_sx("events-calendars-label")),
child_id="calendars-header-child", oob=oob)
@@ -157,7 +157,7 @@ def _calendars_header_sx(ctx: dict, *, oob: bool = False) -> str:
# Calendar header
# ---------------------------------------------------------------------------
-def _calendar_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _calendar_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build a single calendar's header row."""
from quart import url_for
calendar = ctx.get("calendar")
@@ -168,18 +168,18 @@ def _calendar_header_sx(ctx: dict, *, oob: bool = False) -> str:
cal_desc = getattr(calendar, "description", "") or ""
link_href = url_for("calendar.get", calendar_slug=cal_slug)
- label_html = sx_call("events-calendar-label",
+ label_html = await render_to_sx("events-calendar-label",
name=cal_name, description=cal_desc)
# Desktop nav: slots + admin
- nav_html = _calendar_nav_sx(ctx)
+ nav_html = await _calendar_nav_sx(ctx)
- return sx_call("menu-row-sx", id="calendar-row", level=3,
+ return await render_to_sx("menu-row-sx", id="calendar-row", level=3,
link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="calendar-header-child", oob=oob)
-def _calendar_nav_sx(ctx: dict) -> str:
+async def _calendar_nav_sx(ctx: dict) -> str:
"""Calendar desktop nav: Slots + admin link."""
from quart import url_for
calendar = ctx.get("calendar")
@@ -192,11 +192,11 @@ def _calendar_nav_sx(ctx: dict) -> str:
parts = []
slots_href = url_for("defpage_slots_listing", calendar_slug=cal_slug)
- parts.append(sx_call("nav-link", href=slots_href, icon="fa fa-clock",
+ parts.append(await render_to_sx("nav-link", href=slots_href, icon="fa fa-clock",
label="Slots", select_colours=select_colours))
if is_admin:
admin_href = url_for("defpage_calendar_admin", calendar_slug=cal_slug)
- parts.append(sx_call("nav-link", href=admin_href, icon="fa fa-cog",
+ parts.append(await render_to_sx("nav-link", href=admin_href, icon="fa fa-cog",
select_colours=select_colours))
return "".join(parts)
@@ -205,7 +205,7 @@ def _calendar_nav_sx(ctx: dict) -> str:
# Day header
# ---------------------------------------------------------------------------
-def _day_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _day_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build day detail header row."""
from quart import url_for
calendar = ctx.get("calendar")
@@ -223,17 +223,17 @@ def _day_header_sx(ctx: dict, *, oob: bool = False) -> str:
month=day_date.month,
day=day_date.day,
)
- label_html = sx_call("events-day-label",
+ label_html = await render_to_sx("events-day-label",
date_str=day_date.strftime("%A %d %B %Y"))
- nav_html = _day_nav_sx(ctx)
+ nav_html = await _day_nav_sx(ctx)
- return sx_call("menu-row-sx", id="day-row", level=4,
+ return await render_to_sx("menu-row-sx", id="day-row", level=4,
link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="day-header-child", oob=oob)
-def _day_nav_sx(ctx: dict) -> str:
+async def _day_nav_sx(ctx: dict) -> str:
"""Day desktop nav: confirmed entries scrolling menu + admin link."""
from quart import url_for
calendar = ctx.get("calendar")
@@ -260,11 +260,11 @@ def _day_nav_sx(ctx: dict) -> str:
)
start = entry.start_at.strftime("%H:%M") if entry.start_at else ""
end = f" \u2013 {entry.end_at.strftime('%H:%M')}" if entry.end_at else ""
- entry_links.append(sx_call("events-day-entry-link",
+ entry_links.append(await render_to_sx("events-day-entry-link",
href=href, name=entry.name,
time_str=f"{start}{end}"))
inner = "".join(entry_links)
- parts.append(sx_call("events-day-entries-nav", inner=SxExpr(inner)))
+ parts.append(await render_to_sx("events-day-entries-nav", inner=SxExpr(inner)))
if is_admin and day_date:
admin_href = url_for(
@@ -274,7 +274,7 @@ def _day_nav_sx(ctx: dict) -> str:
month=day_date.month,
day=day_date.day,
)
- parts.append(sx_call("nav-link", href=admin_href, icon="fa fa-cog"))
+ parts.append(await render_to_sx("nav-link", href=admin_href, icon="fa fa-cog"))
return "".join(parts)
@@ -282,7 +282,7 @@ def _day_nav_sx(ctx: dict) -> str:
# Day admin header
# ---------------------------------------------------------------------------
-def _day_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _day_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build day admin header row."""
from quart import url_for
calendar = ctx.get("calendar")
@@ -300,7 +300,7 @@ def _day_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
month=day_date.month,
day=day_date.day,
)
- return sx_call("menu-row-sx", id="day-admin-row", level=5,
+ return await render_to_sx("menu-row-sx", id="day-admin-row", level=5,
link_href=link_href, link_label="admin", icon="fa fa-cog",
child_id="day-admin-header-child", oob=oob)
@@ -309,7 +309,7 @@ def _day_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
# Calendar admin header
# ---------------------------------------------------------------------------
-def _calendar_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _calendar_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build calendar admin header row with nav links."""
from quart import url_for
calendar = ctx.get("calendar")
@@ -323,11 +323,11 @@ def _calendar_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
("calendar.admin.calendar_description_edit", "description"),
]:
href = url_for(endpoint, calendar_slug=cal_slug)
- nav_parts.append(sx_call("nav-link", href=href, label=label,
+ nav_parts.append(await render_to_sx("nav-link", href=href, label=label,
select_colours=select_colours))
nav_html = "".join(nav_parts)
- return sx_call("menu-row-sx", id="calendar-admin-row", level=4,
+ return await render_to_sx("menu-row-sx", id="calendar-admin-row", level=4,
link_label="admin", icon="fa fa-cog",
nav=SxExpr(nav_html) if nav_html else None, child_id="calendar-admin-header-child", oob=oob)
@@ -336,13 +336,13 @@ def _calendar_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
# Markets header
# ---------------------------------------------------------------------------
-def _markets_header_sx(ctx: dict, *, oob: bool = False) -> str:
+async def _markets_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build the markets section header row."""
from quart import url_for
link_href = url_for("defpage_events_markets")
- return sx_call("menu-row-sx", id="markets-row", level=3,
+ return await render_to_sx("menu-row-sx", id="markets-row", level=3,
link_href=link_href,
- link_label_content=SxExpr(sx_call("events-markets-label")),
+ link_label_content=SxExpr(await render_to_sx("events-markets-label")),
child_id="markets-header-child", oob=oob)
@@ -350,7 +350,7 @@ def _markets_header_sx(ctx: dict, *, oob: bool = False) -> str:
# Calendars main panel
# ---------------------------------------------------------------------------
-def _calendars_main_panel_sx(ctx: dict) -> str:
+async def _calendars_main_panel_sx(ctx: dict) -> str:
"""Render the calendars list + create form panel."""
from quart import url_for
rights = ctx.get("rights") or {}
@@ -365,18 +365,18 @@ def _calendars_main_panel_sx(ctx: dict) -> str:
form_html = ""
if can_create:
create_url = url_for("calendars.create_calendar")
- form_html = sx_call("crud-create-form",
+ form_html = await render_to_sx("crud-create-form",
create_url=create_url, csrf=csrf,
errors_id="cal-create-errors", list_id="calendars-list",
placeholder="e.g. Events, Gigs, Meetings", btn_label="Add calendar")
- list_html = _calendars_list_sx(ctx, calendars)
- return sx_call("crud-panel",
+ list_html = await _calendars_list_sx(ctx, calendars)
+ return await render_to_sx("crud-panel",
form=SxExpr(form_html), list=SxExpr(list_html),
list_id="calendars-list")
-def _calendars_list_sx(ctx: dict, calendars: list) -> str:
+async def _calendars_list_sx(ctx: dict, calendars: list) -> str:
"""Render the calendars list items."""
from quart import url_for
from shared.utils import route_prefix
@@ -385,7 +385,7 @@ def _calendars_list_sx(ctx: dict, calendars: list) -> str:
prefix = route_prefix()
if not calendars:
- return sx_call("empty-state", message="No calendars yet. Create one above.",
+ return await render_to_sx("empty-state", message="No calendars yet. Create one above.",
cls="text-gray-500 mt-4")
parts = []
@@ -395,7 +395,7 @@ def _calendars_list_sx(ctx: dict, calendars: list) -> str:
href = prefix + url_for("calendar.get", calendar_slug=cal_slug)
del_url = url_for("calendar.delete", calendar_slug=cal_slug)
csrf_hdr = f'{{"X-CSRFToken":"{csrf}"}}'
- parts.append(sx_call("crud-item",
+ parts.append(await render_to_sx("crud-item",
href=href, name=cal_name, slug=cal_slug,
del_url=del_url, csrf_hdr=csrf_hdr,
list_id="calendars-list",
@@ -408,7 +408,7 @@ def _calendars_list_sx(ctx: dict, calendars: list) -> str:
# Calendar month grid
# ---------------------------------------------------------------------------
-def _calendar_main_panel_html(ctx: dict) -> str:
+async def _calendar_main_panel_html(ctx: dict) -> str:
"""Render the calendar month grid."""
from quart import url_for
from quart import session as qsession
@@ -446,10 +446,10 @@ def _calendar_main_panel_html(ctx: dict) -> str:
("\u2039", prev_month_year, prev_month),
]:
href = nav_link(yr, mn)
- nav_arrows.append(sx_call("events-calendar-nav-arrow",
+ nav_arrows.append(await render_to_sx("events-calendar-nav-arrow",
pill_cls=pill_cls, href=href, label=label))
- nav_arrows.append(sx_call("events-calendar-month-label",
+ nav_arrows.append(await render_to_sx("events-calendar-month-label",
month_name=month_name, year=str(year)))
for label, yr, mn in [
@@ -457,11 +457,14 @@ def _calendar_main_panel_html(ctx: dict) -> str:
("\u00bb", next_year, month),
]:
href = nav_link(yr, mn)
- nav_arrows.append(sx_call("events-calendar-nav-arrow",
+ nav_arrows.append(await render_to_sx("events-calendar-nav-arrow",
pill_cls=pill_cls, href=href, label=label))
# Weekday headers
- wd_html = "".join(sx_call("events-calendar-weekday", name=wd) for wd in weekday_names)
+ wd_parts = []
+ for wd in weekday_names:
+ wd_parts.append(await render_to_sx("events-calendar-weekday", name=wd))
+ wd_html = "".join(wd_parts)
# Day cells
cells = []
@@ -491,9 +494,9 @@ def _calendar_main_panel_html(ctx: dict) -> str:
calendar_slug=cal_slug,
year=day_date.year, month=day_date.month, day=day_date.day,
)
- day_short_html = sx_call("events-calendar-day-short",
+ day_short_html = await render_to_sx("events-calendar-day-short",
day_str=day_date.strftime("%a"))
- day_num_html = sx_call("events-calendar-day-num",
+ day_num_html = await render_to_sx("events-calendar-day-num",
pill_cls=pill_cls, href=day_href,
num=str(day_date.day))
@@ -511,12 +514,12 @@ def _calendar_main_panel_html(ctx: dict) -> str:
else:
bg_cls = "bg-sky-100 text-sky-800" if is_mine else "bg-stone-100 text-stone-700"
state_label = (e.state or "pending").replace("_", " ")
- entry_badges.append(sx_call("events-calendar-entry-badge",
+ entry_badges.append(await render_to_sx("events-calendar-entry-badge",
bg_cls=bg_cls, name=e.name,
state_label=state_label))
badges_html = "(<> " + "".join(entry_badges) + ")" if entry_badges else ""
- cells.append(sx_call("events-calendar-cell",
+ cells.append(await render_to_sx("events-calendar-cell",
cell_cls=cell_cls, day_short=SxExpr(day_short_html),
day_num=SxExpr(day_num_html),
badges=SxExpr(badges_html) if badges_html else None))
@@ -524,7 +527,7 @@ def _calendar_main_panel_html(ctx: dict) -> str:
cells_html = "(<> " + "".join(cells) + ")"
arrows_html = "(<> " + "".join(nav_arrows) + ")"
wd_html = "(<> " + wd_html + ")"
- return sx_call("events-calendar-grid",
+ return await render_to_sx("events-calendar-grid",
arrows=SxExpr(arrows_html), weekdays=SxExpr(wd_html),
cells=SxExpr(cells_html))
@@ -533,7 +536,7 @@ def _calendar_main_panel_html(ctx: dict) -> str:
# Day main panel
# ---------------------------------------------------------------------------
-def _day_main_panel_html(ctx: dict) -> str:
+async def _day_main_panel_html(ctx: dict) -> str:
"""Render the day entries table + add button."""
from quart import url_for
@@ -554,9 +557,12 @@ def _day_main_panel_html(ctx: dict) -> str:
rows_html = ""
if day_entries:
- rows_html = "".join(_day_row_html(ctx, entry) for entry in day_entries)
+ row_parts = []
+ for entry in day_entries:
+ row_parts.append(await _day_row_html(ctx, entry))
+ rows_html = "".join(row_parts)
else:
- rows_html = sx_call("events-day-empty-row")
+ rows_html = await render_to_sx("events-day-empty-row")
add_url = url_for(
"calendar.day.calendar_entries.add_form",
@@ -564,12 +570,12 @@ def _day_main_panel_html(ctx: dict) -> str:
day=day, month=month, year=year,
)
- return sx_call("events-day-table",
+ return await render_to_sx("events-day-table",
list_container=list_container, rows=SxExpr(rows_html),
pre_action=pre_action, add_url=add_url)
-def _day_row_html(ctx: dict, entry) -> str:
+async def _day_row_html(ctx: dict, entry) -> str:
"""Render a single day table row."""
from quart import url_for
calendar = ctx.get("calendar")
@@ -588,7 +594,7 @@ def _day_row_html(ctx: dict, entry) -> str:
)
# Name
- name_html = sx_call("events-day-row-name",
+ name_html = await render_to_sx("events-day-row-name",
href=entry_href, pill_cls=pill_cls, name=entry.name)
# Slot/Time
@@ -597,44 +603,44 @@ def _day_row_html(ctx: dict, entry) -> str:
slot_href = url_for("defpage_slot_detail", calendar_slug=cal_slug, slot_id=slot.id)
time_start = slot.time_start.strftime("%H:%M") if slot.time_start else ""
time_end = f" \u2192 {slot.time_end.strftime('%H:%M')}" if slot.time_end else ""
- slot_html = sx_call("events-day-row-slot",
+ slot_html = await render_to_sx("events-day-row-slot",
href=slot_href, pill_cls=pill_cls, slot_name=slot.name,
time_str=f"({time_start}{time_end})")
else:
start = entry.start_at.strftime("%H:%M") if entry.start_at else ""
end = f" \u2192 {entry.end_at.strftime('%H:%M')}" if entry.end_at else ""
- slot_html = sx_call("events-day-row-time", start=start, end=end)
+ slot_html = await render_to_sx("events-day-row-time", start=start, end=end)
# State
state = getattr(entry, "state", "pending") or "pending"
- state_badge = _entry_state_badge_html(state)
- state_td = sx_call("events-day-row-state",
+ state_badge = await _entry_state_badge_html(state)
+ state_td = await render_to_sx("events-day-row-state",
state_id=f"entry-state-{entry.id}", badge=SxExpr(state_badge))
# Cost
cost = getattr(entry, "cost", None)
cost_str = f"\u00a3{cost:.2f}" if cost is not None else "\u00a30.00"
- cost_td = sx_call("events-day-row-cost", cost_str=cost_str)
+ cost_td = await render_to_sx("events-day-row-cost", cost_str=cost_str)
# Tickets
tp = getattr(entry, "ticket_price", None)
if tp is not None:
tc = getattr(entry, "ticket_count", None)
tc_str = f"{tc} tickets" if tc is not None else "Unlimited"
- tickets_td = sx_call("events-day-row-tickets",
+ tickets_td = await render_to_sx("events-day-row-tickets",
price_str=f"\u00a3{tp:.2f}", count_str=tc_str)
else:
- tickets_td = sx_call("events-day-row-no-tickets")
+ tickets_td = await render_to_sx("events-day-row-no-tickets")
- actions_td = sx_call("events-day-row-actions")
+ actions_td = await render_to_sx("events-day-row-actions")
- return sx_call("events-day-row",
+ return await render_to_sx("events-day-row",
tr_cls=tr_cls, name=SxExpr(name_html), slot=SxExpr(slot_html),
state=SxExpr(state_td), cost=SxExpr(cost_td),
tickets=SxExpr(tickets_td), actions=SxExpr(actions_td))
-def _entry_state_badge_html(state: str) -> str:
+async def _entry_state_badge_html(state: str) -> str:
"""Render an entry state badge."""
state_classes = {
"confirmed": "bg-emerald-100 text-emerald-800",
@@ -645,23 +651,23 @@ def _entry_state_badge_html(state: str) -> str:
}
cls = state_classes.get(state, "bg-stone-100 text-stone-700")
label = state.replace("_", " ").capitalize()
- return sx_call("badge", cls=cls, label=label)
+ return await render_to_sx("badge", cls=cls, label=label)
# ---------------------------------------------------------------------------
# Day admin main panel
# ---------------------------------------------------------------------------
-def _day_admin_main_panel_html(ctx: dict) -> str:
+async def _day_admin_main_panel_html(ctx: dict) -> str:
"""Render day admin panel (placeholder nav)."""
- return sx_call("events-day-admin-panel")
+ return await render_to_sx("events-day-admin-panel")
# ---------------------------------------------------------------------------
# Calendar admin main panel
# ---------------------------------------------------------------------------
-def _calendar_admin_main_panel_html(ctx: dict) -> str:
+async def _calendar_admin_main_panel_html(ctx: dict) -> str:
"""Render calendar admin config panel with description editor."""
from quart import url_for
calendar = ctx.get("calendar")
@@ -674,17 +680,17 @@ def _calendar_admin_main_panel_html(ctx: dict) -> str:
hx_select = ctx.get("hx_select_search", "#main-panel")
desc_edit_url = url_for("calendar.admin.calendar_description_edit", calendar_slug=cal_slug)
- description_html = _calendar_description_display_html(calendar, desc_edit_url)
+ description_html = await _calendar_description_display_html(calendar, desc_edit_url)
- return sx_call("events-calendar-admin-panel",
+ return await render_to_sx("events-calendar-admin-panel",
description_content=SxExpr(description_html), csrf=csrf,
description=desc)
-def _calendar_description_display_html(calendar, edit_url: str) -> str:
+async def _calendar_description_display_html(calendar, edit_url: str) -> str:
"""Render calendar description display with edit button."""
desc = getattr(calendar, "description", "") or ""
- return sx_call("events-calendar-description-display",
+ return await render_to_sx("events-calendar-description-display",
description=desc, edit_url=edit_url)
@@ -692,7 +698,7 @@ def _calendar_description_display_html(calendar, edit_url: str) -> str:
# Markets main panel
# ---------------------------------------------------------------------------
-def _markets_main_panel_html(ctx: dict) -> str:
+async def _markets_main_panel_html(ctx: dict) -> str:
"""Render markets list + create form panel."""
from quart import url_for
rights = ctx.get("rights") or {}
@@ -706,18 +712,18 @@ def _markets_main_panel_html(ctx: dict) -> str:
form_html = ""
if can_create:
create_url = url_for("markets.create_market")
- form_html = sx_call("crud-create-form",
+ form_html = await render_to_sx("crud-create-form",
create_url=create_url, csrf=csrf,
errors_id="market-create-errors", list_id="markets-list",
placeholder="e.g. Farm Shop, Bakery", btn_label="Add market")
- list_html = _markets_list_html(ctx, markets)
- return sx_call("crud-panel",
+ list_html = await _markets_list_html(ctx, markets)
+ return await render_to_sx("crud-panel",
form=SxExpr(form_html), list=SxExpr(list_html),
list_id="markets-list")
-def _markets_list_html(ctx: dict, markets: list) -> str:
+async def _markets_list_html(ctx: dict, markets: list) -> str:
"""Render markets list items."""
from quart import url_for
csrf_token = ctx.get("csrf_token")
@@ -726,7 +732,7 @@ def _markets_list_html(ctx: dict, markets: list) -> str:
slug = post.get("slug", "")
if not markets:
- return sx_call("empty-state", message="No markets yet. Create one above.",
+ return await render_to_sx("empty-state", message="No markets yet. Create one above.",
cls="text-gray-500 mt-4")
parts = []
@@ -736,7 +742,7 @@ def _markets_list_html(ctx: dict, markets: list) -> str:
market_href = call_url(ctx, "market_url", f"/{slug}/{m_slug}/")
del_url = url_for("markets.delete_market", market_slug=m_slug)
csrf_hdr = f'{{"X-CSRFToken":"{csrf}"}}'
- parts.append(sx_call("crud-item",
+ parts.append(await render_to_sx("crud-item",
href=market_href, name=m_name,
slug=m_slug, del_url=del_url,
csrf_hdr=csrf_hdr,
@@ -750,7 +756,7 @@ def _markets_list_html(ctx: dict, markets: list) -> str:
# Ticket state badge helper
# ---------------------------------------------------------------------------
-def _ticket_state_badge_html(state: str) -> str:
+async def _ticket_state_badge_html(state: str) -> str:
"""Render a ticket state badge."""
cls_map = {
"confirmed": "bg-emerald-100 text-emerald-800",
@@ -760,14 +766,14 @@ def _ticket_state_badge_html(state: str) -> str:
}
cls = cls_map.get(state, "bg-stone-100 text-stone-700")
label = (state or "").replace("_", " ").capitalize()
- return sx_call("badge", cls=cls, label=label)
+ return await render_to_sx("badge", cls=cls, label=label)
# ---------------------------------------------------------------------------
# Tickets main panel (my tickets)
# ---------------------------------------------------------------------------
-def _tickets_main_panel_html(ctx: dict, tickets: list) -> str:
+async def _tickets_main_panel_html(ctx: dict, tickets: list) -> str:
"""Render my tickets list."""
from quart import url_for
@@ -787,16 +793,16 @@ def _tickets_main_panel_html(ctx: dict, tickets: list) -> str:
if entry.end_at:
time_str += f" \u2013 {entry.end_at.strftime('%H:%M')}"
- ticket_cards.append(sx_call("events-ticket-card",
+ ticket_cards.append(await render_to_sx("events-ticket-card",
href=href, entry_name=entry_name,
type_name=tt.name if tt else None,
time_str=time_str or None,
cal_name=cal.name if cal else None,
- badge=SxExpr(_ticket_state_badge_html(state)),
+ badge=SxExpr(await _ticket_state_badge_html(state)),
code_prefix=ticket.code[:8]))
cards_html = "".join(ticket_cards)
- return sx_call("events-tickets-panel",
+ return await render_to_sx("events-tickets-panel",
list_container=_list_container(ctx),
has_tickets=bool(tickets), cards=SxExpr(cards_html))
@@ -805,7 +811,7 @@ def _tickets_main_panel_html(ctx: dict, tickets: list) -> str:
# Ticket detail panel
# ---------------------------------------------------------------------------
-def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
+async def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
"""Render a single ticket detail with QR code."""
from quart import url_for
@@ -822,7 +828,7 @@ def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
back_href = url_for("defpage_my_tickets")
# Badge with larger sizing
- badge = _ticket_state_badge_html(state).replace('px-2 py-0.5 text-xs', 'px-3 py-1 text-sm')
+ badge = (await _ticket_state_badge_html(state)).replace('px-2 py-0.5 text-xs', 'px-3 py-1 text-sm')
# Time info
time_date = entry.start_at.strftime("%A, %B %d, %Y") if entry and entry.start_at else None
@@ -841,7 +847,7 @@ def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
"}})()"
)
- return sx_call("events-ticket-detail",
+ return await render_to_sx("events-ticket-detail",
list_container=_list_container(ctx), back_href=back_href,
header_bg=header_bg, entry_name=entry_name,
badge=SxExpr(badge), type_name=tt.name if tt else None,
@@ -855,7 +861,7 @@ def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
# Ticket admin main panel
# ---------------------------------------------------------------------------
-def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -> str:
+async def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -> str:
"""Render ticket admin dashboard."""
from quart import url_for
csrf_token = ctx.get("csrf_token")
@@ -872,7 +878,7 @@ def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -> str:
]:
val = stats.get(key, 0)
lbl_cls = text_cls.replace("700", "600").replace("900", "500") if "stone" not in text_cls else "text-stone-500"
- stats_html += sx_call("events-ticket-admin-stat",
+ stats_html += await render_to_sx("events-ticket-admin-stat",
border=border, bg=bg, text_cls=text_cls,
label_cls=lbl_cls, value=str(val), label=label)
@@ -886,29 +892,29 @@ def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -> str:
date_html = ""
if entry and entry.start_at:
- date_html = sx_call("events-ticket-admin-date",
+ date_html = await render_to_sx("events-ticket-admin-date",
date_str=entry.start_at.strftime("%d %b %Y, %H:%M"))
action_html = ""
if state in ("confirmed", "reserved"):
checkin_url = url_for("ticket_admin.do_checkin", code=code)
- action_html = sx_call("events-ticket-admin-checkin-form",
+ action_html = await render_to_sx("events-ticket-admin-checkin-form",
checkin_url=checkin_url, code=code, csrf=csrf)
elif state == "checked_in":
checked_in_at = getattr(ticket, "checked_in_at", None)
t_str = checked_in_at.strftime("%H:%M") if checked_in_at else ""
- action_html = sx_call("events-ticket-admin-checked-in",
+ action_html = await render_to_sx("events-ticket-admin-checked-in",
time_str=t_str)
- rows_html += sx_call("events-ticket-admin-row",
+ rows_html += await render_to_sx("events-ticket-admin-row",
code=code, code_short=code[:12] + "...",
entry_name=entry.name if entry else "\u2014",
date=SxExpr(date_html),
type_name=tt.name if tt else "\u2014",
- badge=SxExpr(_ticket_state_badge_html(state)),
+ badge=SxExpr(await _ticket_state_badge_html(state)),
action=SxExpr(action_html))
- return sx_call("events-ticket-admin-panel",
+ return await render_to_sx("events-ticket-admin-panel",
list_container=_list_container(ctx), stats=SxExpr(stats_html),
lookup_url=lookup_url, has_tickets=bool(tickets),
rows=SxExpr(rows_html))
@@ -918,7 +924,7 @@ def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -> str:
# All events / page summary entry cards
# ---------------------------------------------------------------------------
-def _entry_card_html(entry, page_info: dict, pending_tickets: dict,
+async def _entry_card_html(entry, page_info: dict, pending_tickets: dict,
ticket_url: str, events_url_fn, *, is_page_scoped: bool = False,
post: dict | None = None) -> str:
"""Render a list card for one event entry."""
@@ -936,36 +942,36 @@ def _entry_card_html(entry, page_info: dict, pending_tickets: dict,
# Title (linked or plain)
if entry_href:
- title_html = sx_call("events-entry-title-linked",
+ title_html = await render_to_sx("events-entry-title-linked",
href=entry_href, name=entry.name)
else:
- title_html = sx_call("events-entry-title-plain", name=entry.name)
+ title_html = await render_to_sx("events-entry-title-plain", name=entry.name)
# Badges
badges_html = ""
if page_title and (not is_page_scoped or page_title != (post or {}).get("title")):
page_href = events_url_fn(f"/{page_slug}/")
- badges_html += sx_call("events-entry-page-badge",
+ badges_html += await render_to_sx("events-entry-page-badge",
href=page_href, title=page_title)
cal_name = getattr(entry, "calendar_name", "")
if cal_name:
- badges_html += sx_call("events-entry-cal-badge", name=cal_name)
+ badges_html += await render_to_sx("events-entry-cal-badge", name=cal_name)
# Time line
time_parts = ""
if day_href and not is_page_scoped:
- time_parts += sx_call("events-entry-time-linked",
+ time_parts += await render_to_sx("events-entry-time-linked",
href=day_href,
date_str=entry.start_at.strftime("%a %-d %b"))
elif not is_page_scoped:
- time_parts += sx_call("events-entry-time-plain",
+ time_parts += await render_to_sx("events-entry-time-plain",
date_str=entry.start_at.strftime("%a %-d %b"))
time_parts += entry.start_at.strftime("%H:%M")
if entry.end_at:
time_parts += f' \u2013 {entry.end_at.strftime("%H:%M")}'
cost = getattr(entry, "cost", None)
- cost_html = sx_call("events-entry-cost",
+ cost_html = await render_to_sx("events-entry-cost",
cost=f"£{cost:.2f}") if cost else ""
# Ticket widget
@@ -973,16 +979,16 @@ def _entry_card_html(entry, page_info: dict, pending_tickets: dict,
widget_html = ""
if tp is not None:
qty = pending_tickets.get(entry.id, 0)
- widget_html = sx_call("events-entry-widget-wrapper",
- widget=SxExpr(_ticket_widget_html(entry, qty, ticket_url, ctx={})))
+ widget_html = await render_to_sx("events-entry-widget-wrapper",
+ widget=SxExpr(await _ticket_widget_html(entry, qty, ticket_url, ctx={})))
- return sx_call("events-entry-card",
+ return await render_to_sx("events-entry-card",
title=SxExpr(title_html), badges=SxExpr(badges_html),
time_parts=SxExpr(time_parts), cost=SxExpr(cost_html),
widget=SxExpr(widget_html))
-def _entry_card_tile_html(entry, page_info: dict, pending_tickets: dict,
+async def _entry_card_tile_html(entry, page_info: dict, pending_tickets: dict,
ticket_url: str, events_url_fn, *, is_page_scoped: bool = False,
post: dict | None = None) -> str:
"""Render a tile card for one event entry."""
@@ -1000,27 +1006,27 @@ def _entry_card_tile_html(entry, page_info: dict, pending_tickets: dict,
# Title
if entry_href:
- title_html = sx_call("events-entry-title-tile-linked",
+ title_html = await render_to_sx("events-entry-title-tile-linked",
href=entry_href, name=entry.name)
else:
- title_html = sx_call("events-entry-title-tile-plain", name=entry.name)
+ title_html = await render_to_sx("events-entry-title-tile-plain", name=entry.name)
# Badges
badges_html = ""
if page_title and (not is_page_scoped or page_title != (post or {}).get("title")):
page_href = events_url_fn(f"/{page_slug}/")
- badges_html += sx_call("events-entry-page-badge",
+ badges_html += await render_to_sx("events-entry-page-badge",
href=page_href, title=page_title)
cal_name = getattr(entry, "calendar_name", "")
if cal_name:
- badges_html += sx_call("events-entry-cal-badge", name=cal_name)
+ badges_html += await render_to_sx("events-entry-cal-badge", name=cal_name)
# Time
time_html = ""
if day_href:
- time_html += sx_call("events-entry-time-linked",
+ time_html += (await render_to_sx("events-entry-time-linked",
href=day_href,
- date_str=entry.start_at.strftime("%a %-d %b")).replace(" · ", "")
+ date_str=entry.start_at.strftime("%a %-d %b"))).replace(" · ", "")
else:
time_html += entry.start_at.strftime("%a %-d %b")
time_html += f' \u00b7 {entry.start_at.strftime("%H:%M")}'
@@ -1028,7 +1034,7 @@ def _entry_card_tile_html(entry, page_info: dict, pending_tickets: dict,
time_html += f' \u2013 {entry.end_at.strftime("%H:%M")}'
cost = getattr(entry, "cost", None)
- cost_html = sx_call("events-entry-cost",
+ cost_html = await render_to_sx("events-entry-cost",
cost=f"£{cost:.2f}") if cost else ""
# Ticket widget
@@ -1036,16 +1042,16 @@ def _entry_card_tile_html(entry, page_info: dict, pending_tickets: dict,
widget_html = ""
if tp is not None:
qty = pending_tickets.get(entry.id, 0)
- widget_html = sx_call("events-entry-tile-widget-wrapper",
- widget=SxExpr(_ticket_widget_html(entry, qty, ticket_url, ctx={})))
+ widget_html = await render_to_sx("events-entry-tile-widget-wrapper",
+ widget=SxExpr(await _ticket_widget_html(entry, qty, ticket_url, ctx={})))
- return sx_call("events-entry-card-tile",
+ return await render_to_sx("events-entry-card-tile",
title=SxExpr(title_html), badges=SxExpr(badges_html),
time=SxExpr(time_html), cost=SxExpr(cost_html),
widget=SxExpr(widget_html))
-def _ticket_widget_html(entry, qty: int, ticket_url: str, *, ctx: dict) -> str:
+async def _ticket_widget_html(entry, qty: int, ticket_url: str, *, ctx: dict) -> str:
"""Render the inline +/- ticket widget."""
csrf_token_val = ""
if ctx:
@@ -1062,26 +1068,26 @@ def _ticket_widget_html(entry, qty: int, ticket_url: str, *, ctx: dict) -> str:
tp = getattr(entry, "ticket_price", 0) or 0
tgt = f"#page-ticket-{eid}"
- def _tw_form(count_val, btn_html):
- return sx_call("events-tw-form",
+ async def _tw_form(count_val, btn_html):
+ return await render_to_sx("events-tw-form",
ticket_url=ticket_url, target=tgt,
csrf=csrf_token_val, entry_id=str(eid),
count_val=str(count_val), btn=SxExpr(btn_html))
if qty == 0:
- inner = _tw_form(1, sx_call("events-tw-cart-plus"))
+ inner = await _tw_form(1, await render_to_sx("events-tw-cart-plus"))
else:
- minus = _tw_form(qty - 1, sx_call("events-tw-minus"))
- cart_icon = sx_call("events-tw-cart-icon", qty=str(qty))
- plus = _tw_form(qty + 1, sx_call("events-tw-plus"))
+ minus = await _tw_form(qty - 1, await render_to_sx("events-tw-minus"))
+ cart_icon = await render_to_sx("events-tw-cart-icon", qty=str(qty))
+ plus = await _tw_form(qty + 1, await render_to_sx("events-tw-plus"))
inner = minus + cart_icon + plus
- return sx_call("events-tw-widget",
+ return await render_to_sx("events-tw-widget",
entry_id=str(eid), price=f"£{tp:.2f}",
inner=SxExpr(inner))
-def _entry_cards_html(entries, page_info, pending_tickets, ticket_url,
+async def _entry_cards_html(entries, page_info, pending_tickets, ticket_url,
events_url_fn, view, page, has_more, next_url,
*, is_page_scoped=False, post=None) -> str:
"""Render entry cards (list or tile) with sentinel."""
@@ -1089,23 +1095,23 @@ def _entry_cards_html(entries, page_info, pending_tickets, ticket_url,
last_date = None
for entry in entries:
if view == "tile":
- parts.append(_entry_card_tile_html(
+ parts.append(await _entry_card_tile_html(
entry, page_info, pending_tickets, ticket_url, events_url_fn,
is_page_scoped=is_page_scoped, post=post,
))
else:
entry_date = entry.start_at.strftime("%A %-d %B %Y") if entry.start_at else ""
if entry_date != last_date:
- parts.append(sx_call("events-date-separator",
+ parts.append(await render_to_sx("events-date-separator",
date_str=entry_date))
last_date = entry_date
- parts.append(_entry_card_html(
+ parts.append(await _entry_card_html(
entry, page_info, pending_tickets, ticket_url, events_url_fn,
is_page_scoped=is_page_scoped, post=post,
))
if has_more:
- parts.append(sx_call("sentinel-simple",
+ parts.append(await render_to_sx("sentinel-simple",
id=f"sentinel-{page}", next_url=next_url))
return "".join(parts)
@@ -1118,21 +1124,21 @@ _LIST_SVG = None
_TILE_SVG = None
-def _get_list_svg():
+async def _get_list_svg():
global _LIST_SVG
if _LIST_SVG is None:
- _LIST_SVG = sx_call("list-svg")
+ _LIST_SVG = await render_to_sx("list-svg")
return _LIST_SVG
-def _get_tile_svg():
+async def _get_tile_svg():
global _TILE_SVG
if _TILE_SVG is None:
- _TILE_SVG = sx_call("tile-svg")
+ _TILE_SVG = await render_to_sx("tile-svg")
return _TILE_SVG
-def _view_toggle_html(ctx: dict, view: str) -> str:
+async def _view_toggle_html(ctx: dict, view: str) -> str:
"""Render the list/tile view toggle bar."""
from shared.utils import route_prefix
prefix = route_prefix()
@@ -1151,34 +1157,34 @@ def _view_toggle_html(ctx: dict, view: str) -> str:
list_active = 'bg-stone-200 text-stone-800' if view != 'tile' else 'text-stone-400 hover:text-stone-600'
tile_active = 'bg-stone-200 text-stone-800' if view == 'tile' else 'text-stone-400 hover:text-stone-600'
- return sx_call("view-toggle",
+ return await render_to_sx("view-toggle",
list_href=list_href, tile_href=tile_href,
hx_select=hx_select, list_cls=list_active,
tile_cls=tile_active, storage_key="events_view",
- list_svg=SxExpr(_get_list_svg()), tile_svg=SxExpr(_get_tile_svg()))
+ list_svg=SxExpr(await _get_list_svg()), tile_svg=SxExpr(await _get_tile_svg()))
-def _events_main_panel_html(ctx: dict, entries, has_more, pending_tickets, page_info,
+async def _events_main_panel_html(ctx: dict, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url_fn,
*, is_page_scoped=False, post=None) -> str:
"""Render the events main panel with view toggle + cards."""
- toggle = _view_toggle_html(ctx, view)
+ toggle = await _view_toggle_html(ctx, view)
if entries:
- cards = _entry_cards_html(
+ cards = await _entry_cards_html(
entries, page_info, pending_tickets, ticket_url, events_url_fn,
view, page, has_more, next_url,
is_page_scoped=is_page_scoped, post=post,
)
grid_cls = ("max-w-full px-3 py-3 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4"
if view == "tile" else "max-w-full px-3 py-3 space-y-3")
- body = sx_call("events-grid", grid_cls=grid_cls, cards=SxExpr(cards))
+ body = await render_to_sx("events-grid", grid_cls=grid_cls, cards=SxExpr(cards))
else:
- body = sx_call("empty-state", icon="fa fa-calendar-xmark",
+ body = await render_to_sx("empty-state", icon="fa fa-calendar-xmark",
message="No upcoming events",
cls="px-3 py-12 text-center text-stone-400")
- return sx_call("events-main-panel-body",
+ return await render_to_sx("events-main-panel-body",
toggle=SxExpr(toggle), body=SxExpr(body))
@@ -1212,12 +1218,12 @@ async def render_all_events_page(ctx: dict, entries, has_more, pending_tickets,
ticket_url = url_for("all_events.adjust_ticket")
next_url = prefix + url_for("all_events.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
- content = _events_main_panel_html(
+ content = await _events_main_panel_html(
ctx, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url,
)
- hdr = root_header_sx(ctx)
- return full_page_sx(ctx, header_rows=hdr, content=content)
+ hdr = await root_header_sx(ctx)
+ return await full_page_sx(ctx, header_rows=hdr, content=content)
async def render_all_events_oob(ctx: dict, entries, has_more, pending_tickets,
@@ -1231,11 +1237,11 @@ async def render_all_events_oob(ctx: dict, entries, has_more, pending_tickets,
ticket_url = url_for("all_events.adjust_ticket")
next_url = prefix + url_for("all_events.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
- content = _events_main_panel_html(
+ content = await _events_main_panel_html(
ctx, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url,
)
- return oob_page_sx(content=content)
+ return await oob_page_sx(content=content)
async def render_all_events_cards(entries, has_more, pending_tickets,
@@ -1249,7 +1255,7 @@ async def render_all_events_cards(entries, has_more, pending_tickets,
ticket_url = url_for("all_events.adjust_ticket")
next_url = prefix + url_for("all_events.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
- return _entry_cards_html(
+ return await _entry_cards_html(
entries, page_info, pending_tickets, ticket_url, events_url,
view, page, has_more, next_url,
)
@@ -1271,15 +1277,15 @@ async def render_page_summary_page(ctx: dict, entries, has_more, pending_tickets
ticket_url = url_for("page_summary.adjust_ticket")
next_url = prefix + url_for("page_summary.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
- content = _events_main_panel_html(
+ content = await _events_main_panel_html(
ctx, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url,
is_page_scoped=True, post=post,
)
- hdr = root_header_sx(ctx)
- hdr += header_child_sx(_post_header_sx(ctx))
- return full_page_sx(ctx, header_rows=hdr, content=content)
+ hdr = await root_header_sx(ctx)
+ hdr += await header_child_sx(await _post_header_sx(ctx))
+ return await full_page_sx(ctx, header_rows=hdr, content=content)
async def render_page_summary_oob(ctx: dict, entries, has_more, pending_tickets,
@@ -1294,15 +1300,15 @@ async def render_page_summary_oob(ctx: dict, entries, has_more, pending_tickets,
ticket_url = url_for("page_summary.adjust_ticket")
next_url = prefix + url_for("page_summary.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
- content = _events_main_panel_html(
+ content = await _events_main_panel_html(
ctx, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url,
is_page_scoped=True, post=post,
)
- oobs = _post_header_sx(ctx, oob=True)
+ oobs = await _post_header_sx(ctx, oob=True)
oobs += _clear_deeper_oob("post-row", "post-header-child")
- return oob_page_sx(oobs=oobs, content=content)
+ return await oob_page_sx(oobs=oobs, content=content)
async def render_page_summary_cards(entries, has_more, pending_tickets,
@@ -1316,7 +1322,7 @@ async def render_page_summary_cards(entries, has_more, pending_tickets,
ticket_url = url_for("page_summary.adjust_ticket")
next_url = prefix + url_for("page_summary.entries_fragment", page=page + 1) + (f"?view={view}" if view != "list" else "")
- return _entry_cards_html(
+ return await _entry_cards_html(
entries, page_info, pending_tickets, ticket_url, events_url,
view, page, has_more, next_url,
is_page_scoped=True, post=post,
@@ -1329,24 +1335,24 @@ async def render_page_summary_cards(entries, has_more, pending_tickets,
async def render_calendars_page(ctx: dict) -> str:
"""Full page: calendars listing."""
- content = _calendars_main_panel_sx(ctx)
+ content = await _calendars_main_panel_sx(ctx)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
- root_hdr = root_header_sx(ctx)
- post_hdr = _post_header_sx(ctx)
- admin_hdr = post_admin_header_sx(ctx, slug, selected="calendars")
- return full_page_sx(ctx, header_rows=root_hdr + post_hdr + admin_hdr, content=content)
+ root_hdr = await root_header_sx(ctx)
+ post_hdr = await _post_header_sx(ctx)
+ admin_hdr = await post_admin_header_sx(ctx, slug, selected="calendars")
+ return await full_page_sx(ctx, header_rows=root_hdr + post_hdr + admin_hdr, content=content)
async def render_calendars_oob(ctx: dict) -> str:
"""OOB response: calendars listing."""
- content = _calendars_main_panel_sx(ctx)
+ content = await _calendars_main_panel_sx(ctx)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
- oobs = post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
+ oobs = await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
oobs += _clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child")
- return oob_page_sx(oobs=oobs, content=content)
+ return await oob_page_sx(oobs=oobs, content=content)
# ---------------------------------------------------------------------------
@@ -1355,22 +1361,22 @@ async def render_calendars_oob(ctx: dict) -> str:
async def render_calendar_page(ctx: dict) -> str:
"""Full page: calendar month view."""
- content = _calendar_main_panel_html(ctx)
- hdr = root_header_sx(ctx)
- child = _post_header_sx(ctx) + _calendar_header_sx(ctx)
- hdr += header_child_sx(child)
- return full_page_sx(ctx, header_rows=hdr, content=content)
+ content = await _calendar_main_panel_html(ctx)
+ hdr = await root_header_sx(ctx)
+ child = await _post_header_sx(ctx) + await _calendar_header_sx(ctx)
+ hdr += await header_child_sx(child)
+ return await full_page_sx(ctx, header_rows=hdr, content=content)
async def render_calendar_oob(ctx: dict) -> str:
"""OOB response: calendar month view."""
- content = _calendar_main_panel_html(ctx)
- oobs = _post_header_sx(ctx, oob=True)
- oobs += oob_header_sx("post-header-child", "calendar-header-child",
- _calendar_header_sx(ctx))
+ content = await _calendar_main_panel_html(ctx)
+ oobs = await _post_header_sx(ctx, oob=True)
+ oobs += await oob_header_sx("post-header-child", "calendar-header-child",
+ await _calendar_header_sx(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"calendar-row", "calendar-header-child")
- return oob_page_sx(oobs=oobs, content=content)
+ return await oob_page_sx(oobs=oobs, content=content)
# ---------------------------------------------------------------------------
@@ -1379,35 +1385,35 @@ async def render_calendar_oob(ctx: dict) -> str:
async def render_day_page(ctx: dict) -> str:
"""Full page: day detail."""
- content = _day_main_panel_html(ctx)
- hdr = root_header_sx(ctx)
- child = (_post_header_sx(ctx)
- + _calendar_header_sx(ctx) + _day_header_sx(ctx))
- hdr += header_child_sx(child)
- return full_page_sx(ctx, header_rows=hdr, content=content)
+ content = await _day_main_panel_html(ctx)
+ hdr = await root_header_sx(ctx)
+ child = (await _post_header_sx(ctx)
+ + await _calendar_header_sx(ctx) + await _day_header_sx(ctx))
+ hdr += await header_child_sx(child)
+ return await full_page_sx(ctx, header_rows=hdr, content=content)
async def render_day_oob(ctx: dict) -> str:
"""OOB response: day detail."""
- content = _day_main_panel_html(ctx)
- oobs = _calendar_header_sx(ctx, oob=True)
- oobs += oob_header_sx("calendar-header-child", "day-header-child",
- _day_header_sx(ctx))
+ content = await _day_main_panel_html(ctx)
+ oobs = await _calendar_header_sx(ctx, oob=True)
+ oobs += await oob_header_sx("calendar-header-child", "day-header-child",
+ await _day_header_sx(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"calendar-row", "calendar-header-child",
"day-row", "day-header-child")
- return oob_page_sx(oobs=oobs, content=content)
+ return await oob_page_sx(oobs=oobs, content=content)
# ---------------------------------------------------------------------------
# Calendar admin helper
# ---------------------------------------------------------------------------
-def _events_post_admin_header_sx(ctx: dict, *, oob: bool = False,
+async def _events_post_admin_header_sx(ctx: dict, *, oob: bool = False,
selected: str = "") -> str:
"""Post-level admin row for events — delegates to shared helper."""
slug = (ctx.get("post") or {}).get("slug", "")
- return post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
+ return await post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
# ===========================================================================
@@ -1419,19 +1425,19 @@ def _events_post_admin_header_sx(ctx: dict, *, oob: bool = False,
# Ticket widget (public wrapper for _ticket_widget_html)
# ---------------------------------------------------------------------------
-def render_ticket_widget(entry, qty: int, ticket_url: str) -> str:
+async def render_ticket_widget(entry, qty: int, ticket_url: str) -> str:
"""Render the +/- ticket widget for page_summary / all_events adjust_ticket."""
- return _ticket_widget_html(entry, qty, ticket_url, ctx={})
+ return await _ticket_widget_html(entry, qty, ticket_url, ctx={})
# ---------------------------------------------------------------------------
# Ticket admin: checkin result
# ---------------------------------------------------------------------------
-def render_checkin_result(success: bool, error: str | None, ticket) -> str:
+async def render_checkin_result(success: bool, error: str | None, ticket) -> str:
"""Render checkin result: table row on success, error div on failure."""
if not success:
- return sx_call("events-checkin-error",
+ return await render_to_sx("events-checkin-error",
message=error or "Check-in failed")
if not ticket:
return ""
@@ -1443,15 +1449,15 @@ def render_checkin_result(success: bool, error: str | None, ticket) -> str:
date_html = ""
if entry and entry.start_at:
- date_html = sx_call("events-ticket-admin-date",
+ date_html = await render_to_sx("events-ticket-admin-date",
date_str=entry.start_at.strftime("%d %b %Y, %H:%M"))
- return sx_call("events-checkin-success-row",
+ return await render_to_sx("events-checkin-success-row",
code=code, code_short=code[:12] + "...",
entry_name=entry.name if entry else "\u2014",
date=SxExpr(date_html),
type_name=tt.name if tt else "\u2014",
- badge=SxExpr(_ticket_state_badge_html("checked_in")),
+ badge=SxExpr(await _ticket_state_badge_html("checked_in")),
time_str=time_str)
@@ -1459,13 +1465,13 @@ def render_checkin_result(success: bool, error: str | None, ticket) -> str:
# Ticket admin: lookup result
# ---------------------------------------------------------------------------
-def render_lookup_result(ticket, error: str | None) -> str:
+async def render_lookup_result(ticket, error: str | None) -> str:
"""Render ticket lookup result: error div or ticket info card."""
from quart import url_for
from shared.browser.app.csrf import generate_csrf_token
if error:
- return sx_call("events-lookup-error", message=error)
+ return await render_to_sx("events-lookup-error", message=error)
if not ticket:
return ""
@@ -1477,34 +1483,34 @@ def render_lookup_result(ticket, error: str | None) -> str:
csrf = generate_csrf_token()
# Info section
- info_html = sx_call("events-lookup-info",
+ info_html = await render_to_sx("events-lookup-info",
entry_name=entry.name if entry else "Unknown event")
if tt:
- info_html += sx_call("events-lookup-type", type_name=tt.name)
+ info_html += await render_to_sx("events-lookup-type", type_name=tt.name)
if entry and entry.start_at:
- info_html += sx_call("events-lookup-date",
+ info_html += await render_to_sx("events-lookup-date",
date_str=entry.start_at.strftime("%A, %B %d, %Y at %H:%M"))
cal = getattr(entry, "calendar", None) if entry else None
if cal:
- info_html += sx_call("events-lookup-cal", cal_name=cal.name)
- info_html += sx_call("events-lookup-status",
- badge=SxExpr(_ticket_state_badge_html(state)), code=code)
+ info_html += await render_to_sx("events-lookup-cal", cal_name=cal.name)
+ info_html += await render_to_sx("events-lookup-status",
+ badge=SxExpr(await _ticket_state_badge_html(state)), code=code)
if checked_in_at:
- info_html += sx_call("events-lookup-checkin-time",
+ info_html += await render_to_sx("events-lookup-checkin-time",
date_str=checked_in_at.strftime("%B %d, %Y at %H:%M"))
# Action area
action_html = ""
if state in ("confirmed", "reserved"):
checkin_url = url_for("ticket_admin.do_checkin", code=code)
- action_html = sx_call("events-lookup-checkin-btn",
+ action_html = await render_to_sx("events-lookup-checkin-btn",
checkin_url=checkin_url, code=code, csrf=csrf)
elif state == "checked_in":
- action_html = sx_call("events-lookup-checked-in")
+ action_html = await render_to_sx("events-lookup-checked-in")
elif state == "cancelled":
- action_html = sx_call("events-lookup-cancelled")
+ action_html = await render_to_sx("events-lookup-cancelled")
- return sx_call("events-lookup-card",
+ return await render_to_sx("events-lookup-card",
info=SxExpr(info_html), code=code, action=SxExpr(action_html))
@@ -1512,7 +1518,7 @@ def render_lookup_result(ticket, error: str | None) -> str:
# Ticket admin: entry tickets table
# ---------------------------------------------------------------------------
-def render_entry_tickets_admin(entry, tickets: list) -> str:
+async def render_entry_tickets_admin(entry, tickets: list) -> str:
"""Render admin ticket table for a specific entry."""
from quart import url_for
from shared.browser.app.csrf import generate_csrf_token
@@ -1531,26 +1537,26 @@ def render_entry_tickets_admin(entry, tickets: list) -> str:
action_html = ""
if state in ("confirmed", "reserved"):
checkin_url = url_for("ticket_admin.do_checkin", code=code)
- action_html = sx_call("events-entry-tickets-admin-checkin",
+ action_html = await render_to_sx("events-entry-tickets-admin-checkin",
checkin_url=checkin_url, code=code, csrf=csrf)
elif state == "checked_in":
t_str = checked_in_at.strftime("%H:%M") if checked_in_at else ""
- action_html = sx_call("events-ticket-admin-checked-in",
+ action_html = await render_to_sx("events-ticket-admin-checked-in",
time_str=t_str)
- rows_html += sx_call("events-entry-tickets-admin-row",
+ rows_html += await render_to_sx("events-entry-tickets-admin-row",
code=code, code_short=code[:12] + "...",
type_name=tt.name if tt else "\u2014",
- badge=SxExpr(_ticket_state_badge_html(state)),
+ badge=SxExpr(await _ticket_state_badge_html(state)),
action=SxExpr(action_html))
if tickets:
- body_html = sx_call("events-entry-tickets-admin-table",
+ body_html = await render_to_sx("events-entry-tickets-admin-table",
rows=SxExpr(rows_html))
else:
- body_html = sx_call("events-entry-tickets-admin-empty")
+ body_html = await render_to_sx("events-entry-tickets-admin-empty")
- return sx_call("events-entry-tickets-admin-panel",
+ return await render_to_sx("events-entry-tickets-admin-panel",
entry_name=entry.name,
count_label=f"{count} ticket{suffix}",
body=SxExpr(body_html))
@@ -1560,16 +1566,16 @@ def render_entry_tickets_admin(entry, tickets: list) -> str:
# Day main panel -- public API
# ---------------------------------------------------------------------------
-def render_day_main_panel(ctx: dict) -> str:
+async def render_day_main_panel(ctx: dict) -> str:
"""Public wrapper for day main panel rendering."""
- return _day_main_panel_html(ctx)
+ return await _day_main_panel_html(ctx)
# ---------------------------------------------------------------------------
# Entry main panel
# ---------------------------------------------------------------------------
-def _entry_main_panel_html(ctx: dict) -> str:
+async def _entry_main_panel_html(ctx: dict) -> str:
"""Render the entry detail panel (name, slot, time, state, cost, tickets,
buy form, date, posts, options + edit button)."""
from quart import url_for
@@ -1590,63 +1596,63 @@ def _entry_main_panel_html(ctx: dict) -> str:
eid = entry.id
state = getattr(entry, "state", "pending") or "pending"
- def _field(label, content_html):
- return sx_call("events-entry-field", label=label, content=SxExpr(content_html))
+ async def _field(label, content_html):
+ return await render_to_sx("events-entry-field", label=label, content=SxExpr(content_html))
# Name
- name_html = _field("Name", sx_call("events-entry-name-field", name=entry.name))
+ name_html = await _field("Name", await render_to_sx("events-entry-name-field", name=entry.name))
# Slot
slot = getattr(entry, "slot", None)
if slot:
flex_label = "(flexible)" if getattr(slot, "flexible", False) else "(fixed)"
- slot_inner = sx_call("events-entry-slot-assigned",
+ slot_inner = await render_to_sx("events-entry-slot-assigned",
slot_name=slot.name, flex_label=flex_label)
else:
- slot_inner = sx_call("events-entry-slot-none")
- slot_html = _field("Slot", slot_inner)
+ slot_inner = await render_to_sx("events-entry-slot-none")
+ slot_html = await _field("Slot", slot_inner)
# Time Period
start_str = entry.start_at.strftime("%H:%M") if entry.start_at else ""
end_str = f" \u2013 {entry.end_at.strftime('%H:%M')}" if entry.end_at else " \u2013 open-ended"
- time_html = _field("Time Period", sx_call("events-entry-time-field",
+ time_html = await _field("Time Period", await render_to_sx("events-entry-time-field",
time_str=start_str + end_str))
# State
- state_html = _field("State", sx_call("events-entry-state-field",
+ state_html = await _field("State", await render_to_sx("events-entry-state-field",
entry_id=str(eid),
- badge=SxExpr(_entry_state_badge_html(state))))
+ badge=SxExpr(await _entry_state_badge_html(state))))
# Cost
cost = getattr(entry, "cost", None)
cost_str = f"{cost:.2f}" if cost is not None else "0.00"
- cost_html = _field("Cost", sx_call("events-entry-cost-field",
+ cost_html = await _field("Cost", await render_to_sx("events-entry-cost-field",
cost=f"£{cost_str}"))
# Ticket Configuration (admin)
- tickets_html = _field("Tickets", sx_call("events-entry-tickets-field",
+ tickets_html = await _field("Tickets", await render_to_sx("events-entry-tickets-field",
entry_id=str(eid),
- tickets_config=SxExpr(render_entry_tickets_config(entry, calendar, day, month, year))))
+ tickets_config=SxExpr(await render_entry_tickets_config(entry, calendar, day, month, year))))
# Buy Tickets (public-facing)
ticket_remaining = ctx.get("ticket_remaining")
ticket_sold_count = ctx.get("ticket_sold_count", 0)
user_ticket_count = ctx.get("user_ticket_count", 0)
user_ticket_counts_by_type = ctx.get("user_ticket_counts_by_type") or {}
- buy_html = render_buy_form(
+ buy_html = await render_buy_form(
entry, ticket_remaining, ticket_sold_count,
user_ticket_count, user_ticket_counts_by_type,
)
# Date
date_str = entry.start_at.strftime("%A, %B %d, %Y") if entry.start_at else ""
- date_html = _field("Date", sx_call("events-entry-date-field", date_str=date_str))
+ date_html = await _field("Date", await render_to_sx("events-entry-date-field", date_str=date_str))
# Associated Posts
entry_posts = ctx.get("entry_posts") or []
- posts_html = _field("Associated Posts", sx_call("events-entry-posts-field",
+ posts_html = await _field("Associated Posts", await render_to_sx("events-entry-posts-field",
entry_id=str(eid),
- posts_panel=SxExpr(render_entry_posts_panel(entry_posts, entry, calendar, day, month, year))))
+ posts_panel=SxExpr(await render_entry_posts_panel(entry_posts, entry, calendar, day, month, year))))
# Options and Edit Button
edit_url = url_for(
@@ -1655,14 +1661,14 @@ def _entry_main_panel_html(ctx: dict) -> str:
day=day, month=month, year=year,
)
- return sx_call("events-entry-panel",
+ return await render_to_sx("events-entry-panel",
entry_id=str(eid), list_container=list_container,
name=SxExpr(name_html), slot=SxExpr(slot_html),
time=SxExpr(time_html), state=SxExpr(state_html),
cost=SxExpr(cost_html), tickets=SxExpr(tickets_html),
buy=SxExpr(buy_html), date=SxExpr(date_html),
posts=SxExpr(posts_html),
- options=SxExpr(_entry_options_html(entry, calendar, day, month, year)),
+ options=SxExpr(await _entry_options_html(entry, calendar, day, month, year)),
pre_action=pre_action, edit_url=edit_url)
@@ -1670,7 +1676,7 @@ def _entry_main_panel_html(ctx: dict) -> str:
# Entry header row
# ---------------------------------------------------------------------------
-def _entry_header_html(ctx: dict, *, oob: bool = False) -> str:
+async def _entry_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build entry detail header row."""
from quart import url_for
@@ -1691,19 +1697,19 @@ def _entry_header_html(ctx: dict, *, oob: bool = False) -> str:
year=year, month=month, day=day,
entry_id=entry.id,
)
- label_html = sx_call("events-entry-label",
+ label_html = await render_to_sx("events-entry-label",
entry_id=str(entry.id),
- title=SxExpr(_entry_title_html(entry)),
- times=SxExpr(_entry_times_html(entry)))
+ title=SxExpr(await _entry_title_html(entry)),
+ times=SxExpr(await _entry_times_html(entry)))
- nav_html = _entry_nav_html(ctx)
+ nav_html = await _entry_nav_html(ctx)
- return sx_call("menu-row-sx", id="entry-row", level=5,
+ return await render_to_sx("menu-row-sx", id="entry-row", level=5,
link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="entry-header-child", oob=oob)
-def _entry_times_html(entry) -> str:
+async def _entry_times_html(entry) -> str:
"""Render entry times label."""
start = entry.start_at
end = entry.end_at
@@ -1711,14 +1717,14 @@ def _entry_times_html(entry) -> str:
return ""
start_str = start.strftime("%H:%M")
end_str = f" \u2192 {end.strftime('%H:%M')}" if end else ""
- return sx_call("events-entry-times", time_str=start_str + end_str)
+ return await render_to_sx("events-entry-times", time_str=start_str + end_str)
# ---------------------------------------------------------------------------
# Entry nav (desktop + admin link)
# ---------------------------------------------------------------------------
-def _entry_nav_html(ctx: dict) -> str:
+async def _entry_nav_html(ctx: dict) -> str:
"""Entry desktop nav: associated posts scrolling menu + admin link."""
from quart import url_for
@@ -1749,13 +1755,13 @@ def _entry_nav_html(ctx: dict) -> str:
feat = getattr(ep, "feature_image", None)
href = blog_url_fn(f"/{slug}/") if blog_url_fn else f"/{slug}/"
if feat:
- img_html = sx_call("events-post-img", src=feat, alt=title)
+ img_html = await render_to_sx("events-post-img", src=feat, alt=title)
else:
- img_html = sx_call("events-post-img-placeholder")
- post_links += sx_call("events-entry-nav-post-link",
+ img_html = await render_to_sx("events-post-img-placeholder")
+ post_links += await render_to_sx("events-entry-nav-post-link",
href=href, img=SxExpr(img_html), title=title)
- parts.append(sx_call("events-entry-posts-nav-oob",
- items=SxExpr(post_links)).replace(' :hx-swap-oob "true"', ''))
+ parts.append((await render_to_sx("events-entry-posts-nav-oob",
+ items=SxExpr(post_links))).replace(' :hx-swap-oob "true"', ''))
# Admin link
if is_admin:
@@ -1765,7 +1771,7 @@ def _entry_nav_html(ctx: dict) -> str:
day=day, month=month, year=year,
entry_id=entry.id,
)
- parts.append(sx_call("events-entry-admin-link", href=admin_url))
+ parts.append(await render_to_sx("events-entry-admin-link", href=admin_url))
return "".join(parts)
@@ -1774,26 +1780,26 @@ def _entry_nav_html(ctx: dict) -> str:
# Entry optioned (confirm/decline/provisional response)
# ---------------------------------------------------------------------------
-def render_entry_optioned(entry, calendar, day, month, year) -> str:
+async def render_entry_optioned(entry, calendar, day, month, year) -> str:
"""Render entry options buttons + OOB title & state swaps."""
- options = _entry_options_html(entry, calendar, day, month, year)
- title = _entry_title_html(entry)
- state = _entry_state_badge_html(getattr(entry, "state", "pending") or "pending")
+ options = await _entry_options_html(entry, calendar, day, month, year)
+ title = await _entry_title_html(entry)
+ state = await _entry_state_badge_html(getattr(entry, "state", "pending") or "pending")
- return options + sx_call("events-entry-optioned-oob",
+ return options + await render_to_sx("events-entry-optioned-oob",
entry_id=str(entry.id),
title=SxExpr(title), state=SxExpr(state))
-def _entry_title_html(entry) -> str:
+async def _entry_title_html(entry) -> str:
"""Render entry title (icon + name + state badge)."""
state = getattr(entry, "state", "pending") or "pending"
- return sx_call("events-entry-title",
+ return await render_to_sx("events-entry-title",
name=entry.name,
- badge=SxExpr(_entry_state_badge_html(state)))
+ badge=SxExpr(await _entry_state_badge_html(state)))
-def _entry_options_html(entry, calendar, day, month, year) -> str:
+async def _entry_options_html(entry, calendar, day, month, year) -> str:
"""Render confirm/decline/provisional buttons based on entry state."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -1807,13 +1813,13 @@ def _entry_options_html(entry, calendar, day, month, year) -> str:
state = getattr(entry, "state", "pending") or "pending"
target = f"#calendar_entry_options_{eid}"
- def _make_button(action_name, label, confirm_title, confirm_text, *, trigger_type="submit"):
+ async def _make_button(action_name, label, confirm_title, confirm_text, *, trigger_type="submit"):
url = url_for(
f"calendar.day.calendar_entries.calendar_entry.{action_name}",
calendar_slug=cal_slug, day=day, month=month, year=year, entry_id=eid,
)
btn_type = "button" if trigger_type == "button" else "submit"
- return sx_call("events-entry-option-button",
+ return await render_to_sx("events-entry-option-button",
url=url, target=target, csrf=csrf, btn_type=btn_type,
action_btn=action_btn, confirm_title=confirm_title,
confirm_text=confirm_text, label=label,
@@ -1821,22 +1827,22 @@ def _entry_options_html(entry, calendar, day, month, year) -> str:
buttons_html = ""
if state == "provisional":
- buttons_html += _make_button(
+ buttons_html += await _make_button(
"confirm_entry", "confirm",
"Confirm entry?", "Are you sure you want to confirm this entry?",
)
- buttons_html += _make_button(
+ buttons_html += await _make_button(
"decline_entry", "decline",
"Decline entry?", "Are you sure you want to decline this entry?",
)
elif state == "confirmed":
- buttons_html += _make_button(
+ buttons_html += await _make_button(
"provisional_entry", "provisional",
"Provisional entry?", "Are you sure you want to provisional this entry?",
trigger_type="button",
)
- return sx_call("events-entry-options",
+ return await render_to_sx("events-entry-options",
entry_id=str(eid), buttons=SxExpr(buttons_html))
@@ -1844,7 +1850,7 @@ def _entry_options_html(entry, calendar, day, month, year) -> str:
# Entry tickets config (display + form)
# ---------------------------------------------------------------------------
-def render_entry_tickets_config(entry, calendar, day, month, year) -> str:
+async def render_entry_tickets_config(entry, calendar, day, month, year) -> str:
"""Render ticket config display + edit form for admin entry view."""
from quart import url_for
from shared.browser.app.csrf import generate_csrf_token
@@ -1861,11 +1867,11 @@ def render_entry_tickets_config(entry, calendar, day, month, year) -> str:
if tp is not None:
tc_str = f"{tc} tickets" if tc is not None else "Unlimited"
- display_html = sx_call("events-ticket-config-display",
+ display_html = await render_to_sx("events-ticket-config-display",
price_str=f"£{tp:.2f}",
count_str=tc_str, show_js=show_js)
else:
- display_html = sx_call("events-ticket-config-none", show_js=show_js)
+ display_html = await render_to_sx("events-ticket-config-none", show_js=show_js)
update_url = url_for(
"calendar.day.calendar_entries.calendar_entry.update_tickets",
@@ -1875,7 +1881,7 @@ def render_entry_tickets_config(entry, calendar, day, month, year) -> str:
tp_val = f"{tp:.2f}" if tp is not None else ""
tc_val = str(tc) if tc is not None else ""
- form_html = sx_call("events-ticket-config-form",
+ form_html = await render_to_sx("events-ticket-config-form",
entry_id=eid_s, hidden_cls=hidden_cls,
update_url=update_url, csrf=csrf,
price_val=tp_val, count_val=tc_val, hide_js=hide_js)
@@ -1886,7 +1892,7 @@ def render_entry_tickets_config(entry, calendar, day, month, year) -> str:
# Entry posts panel
# ---------------------------------------------------------------------------
-def render_entry_posts_panel(entry_posts, entry, calendar, day, month, year) -> str:
+async def render_entry_posts_panel(entry_posts, entry, calendar, day, month, year) -> str:
"""Render associated posts list with remove buttons and search input."""
from quart import url_for
from shared.browser.app.csrf import generate_csrf_token
@@ -1903,28 +1909,28 @@ def render_entry_posts_panel(entry_posts, entry, calendar, day, month, year) ->
ep_title = getattr(ep, "title", "")
ep_id = getattr(ep, "id", 0)
feat = getattr(ep, "feature_image", None)
- img_html = (sx_call("events-post-img", src=feat, alt=ep_title)
- if feat else sx_call("events-post-img-placeholder"))
+ img_html = (await render_to_sx("events-post-img", src=feat, alt=ep_title)
+ if feat else await render_to_sx("events-post-img-placeholder"))
del_url = url_for(
"calendar.day.calendar_entries.calendar_entry.remove_post",
calendar_slug=cal_slug, day=day, month=month, year=year,
entry_id=eid, post_id=ep_id,
)
- items += sx_call("events-entry-post-item",
+ items += await render_to_sx("events-entry-post-item",
img=SxExpr(img_html), title=ep_title,
del_url=del_url, entry_id=eid_s,
csrf_hdr=f'{{"X-CSRFToken": "{csrf}"}}')
- posts_html = sx_call("events-entry-posts-list", items=SxExpr(items))
+ posts_html = await render_to_sx("events-entry-posts-list", items=SxExpr(items))
else:
- posts_html = sx_call("events-entry-posts-none")
+ posts_html = await render_to_sx("events-entry-posts-none")
search_url = url_for(
"calendar.day.calendar_entries.calendar_entry.search_posts",
calendar_slug=cal_slug, day=day, month=month, year=year, entry_id=eid,
)
- return sx_call("events-entry-posts-panel",
+ return await render_to_sx("events-entry-posts-panel",
posts=SxExpr(posts_html), search_url=search_url,
entry_id=eid_s)
@@ -1933,7 +1939,7 @@ def render_entry_posts_panel(entry_posts, entry, calendar, day, month, year) ->
# Entry posts nav OOB
# ---------------------------------------------------------------------------
-def render_entry_posts_nav_oob(entry_posts) -> str:
+async def render_entry_posts_nav_oob(entry_posts) -> str:
"""Render OOB nav for entry posts (scrolling menu)."""
from quart import g
styles = getattr(g, "styles", None) or {}
@@ -1941,7 +1947,7 @@ def render_entry_posts_nav_oob(entry_posts) -> str:
blog_url_fn = getattr(g, "blog_url", None)
if not entry_posts:
- return sx_call("events-entry-posts-nav-oob-empty")
+ return await render_to_sx("events-entry-posts-nav-oob-empty")
items = ""
for ep in entry_posts:
@@ -1949,20 +1955,20 @@ def render_entry_posts_nav_oob(entry_posts) -> str:
title = getattr(ep, "title", "")
feat = getattr(ep, "feature_image", None)
href = blog_url_fn(f"/{slug}/") if blog_url_fn else f"/{slug}/"
- img_html = (sx_call("events-post-img", src=feat, alt=title)
- if feat else sx_call("events-post-img-placeholder"))
- items += sx_call("events-entry-nav-post",
+ img_html = (await render_to_sx("events-post-img", src=feat, alt=title)
+ if feat else await render_to_sx("events-post-img-placeholder"))
+ items += await render_to_sx("events-entry-nav-post",
href=href, nav_btn=nav_btn,
img=SxExpr(img_html), title=title)
- return sx_call("events-entry-posts-nav-oob", items=SxExpr(items))
+ return await render_to_sx("events-entry-posts-nav-oob", items=SxExpr(items))
# ---------------------------------------------------------------------------
# Day entries nav OOB
# ---------------------------------------------------------------------------
-def render_day_entries_nav_oob(confirmed_entries, calendar, day_date) -> str:
+async def render_day_entries_nav_oob(confirmed_entries, calendar, day_date) -> str:
"""Render OOB nav for confirmed entries in a day."""
from quart import url_for, g
@@ -1971,7 +1977,7 @@ def render_day_entries_nav_oob(confirmed_entries, calendar, day_date) -> str:
cal_slug = getattr(calendar, "slug", "")
if not confirmed_entries:
- return sx_call("events-day-entries-nav-oob-empty")
+ return await render_to_sx("events-day-entries-nav-oob-empty")
items = ""
for entry in confirmed_entries:
@@ -1983,18 +1989,18 @@ def render_day_entries_nav_oob(confirmed_entries, calendar, day_date) -> str:
)
start = entry.start_at.strftime("%H:%M") if entry.start_at else ""
end = f" \u2013 {entry.end_at.strftime('%H:%M')}" if entry.end_at else ""
- items += sx_call("events-day-nav-entry",
+ items += await render_to_sx("events-day-nav-entry",
href=href, nav_btn=nav_btn,
name=entry.name, time_str=start + end)
- return sx_call("events-day-entries-nav-oob", items=SxExpr(items))
+ return await render_to_sx("events-day-entries-nav-oob", items=SxExpr(items))
# ---------------------------------------------------------------------------
# Post nav entries OOB
# ---------------------------------------------------------------------------
-def render_post_nav_entries_oob(associated_entries, calendars, post) -> str:
+async def render_post_nav_entries_oob(associated_entries, calendars, post) -> str:
"""Render OOB nav for associated entries and calendars of a post."""
from quart import g
from shared.infrastructure.urls import events_url
@@ -2006,7 +2012,7 @@ def render_post_nav_entries_oob(associated_entries, calendars, post) -> str:
has_items = has_entries or calendars
if not has_items:
- return sx_call("events-post-nav-oob-empty")
+ return await render_to_sx("events-post-nav-oob-empty")
slug = post.get("slug", "") if isinstance(post, dict) else getattr(post, "slug", "")
@@ -2021,7 +2027,7 @@ def render_post_nav_entries_oob(associated_entries, calendars, post) -> str:
href = events_url(entry_path)
time_str = entry.start_at.strftime("%b %d, %Y at %H:%M")
end_str = f" \u2013 {entry.end_at.strftime('%H:%M')}" if entry.end_at else ""
- items += sx_call("events-post-nav-entry",
+ items += await render_to_sx("events-post-nav-entry",
href=href, nav_btn=nav_btn,
name=entry.name, time_str=time_str + end_str)
@@ -2029,7 +2035,7 @@ def render_post_nav_entries_oob(associated_entries, calendars, post) -> str:
for cal in calendars:
cs = getattr(cal, "slug", "")
local_href = events_url(f"/{slug}/{cs}/")
- items += sx_call("events-post-nav-calendar",
+ items += await render_to_sx("events-post-nav-calendar",
href=local_href, nav_btn=nav_btn, name=cal.name)
hs = ("on load or scroll "
@@ -2037,7 +2043,7 @@ def render_post_nav_entries_oob(associated_entries, calendars, post) -> str:
"remove .hidden from .entries-nav-arrow add .flex to .entries-nav-arrow "
"else add .hidden to .entries-nav-arrow remove .flex from .entries-nav-arrow end")
- return sx_call("events-post-nav-wrapper",
+ return await render_to_sx("events-post-nav-wrapper",
items=SxExpr(items), hyperscript=hs)
@@ -2045,22 +2051,22 @@ def render_post_nav_entries_oob(associated_entries, calendars, post) -> str:
# Calendar description display + edit form
# ---------------------------------------------------------------------------
-def render_calendar_description(calendar, *, oob: bool = False) -> str:
+async def render_calendar_description(calendar, *, oob: bool = False) -> str:
"""Render calendar description display with edit button, optionally with OOB title."""
from quart import url_for
cal_slug = getattr(calendar, "slug", "")
edit_url = url_for("calendar.admin.calendar_description_edit", calendar_slug=cal_slug)
- html = _calendar_description_display_html(calendar, edit_url)
+ html = await _calendar_description_display_html(calendar, edit_url)
if oob:
desc = getattr(calendar, "description", "") or ""
- html += sx_call("events-calendar-description-title-oob",
+ html += await render_to_sx("events-calendar-description-title-oob",
description=desc)
return html
-def render_calendar_description_edit(calendar) -> str:
+async def render_calendar_description_edit(calendar) -> str:
"""Render calendar description edit form."""
from quart import url_for
from shared.browser.app.csrf import generate_csrf_token
@@ -2071,7 +2077,7 @@ def render_calendar_description_edit(calendar) -> str:
save_url = url_for("calendar.admin.calendar_description_save", calendar_slug=cal_slug)
cancel_url = url_for("calendar.admin.calendar_description_view", calendar_slug=cal_slug)
- return sx_call("events-calendar-description-edit-form",
+ return await render_to_sx("events-calendar-description-edit-form",
save_url=save_url, cancel_url=cancel_url,
csrf=csrf, description=desc)
@@ -2080,25 +2086,25 @@ def render_calendar_description_edit(calendar) -> str:
# Calendars list panel (for POST create / DELETE)
# ---------------------------------------------------------------------------
-def render_calendars_list_panel(ctx: dict) -> str:
+async def render_calendars_list_panel(ctx: dict) -> str:
"""Render the calendars main panel HTML for POST/DELETE response."""
- return _calendars_main_panel_sx(ctx)
+ return await _calendars_main_panel_sx(ctx)
# ---------------------------------------------------------------------------
# Markets list panel (for POST create / DELETE)
# ---------------------------------------------------------------------------
-def render_markets_list_panel(ctx: dict) -> str:
+async def render_markets_list_panel(ctx: dict) -> str:
"""Render the markets main panel HTML for POST/DELETE response."""
- return _markets_main_panel_html(ctx)
+ return await _markets_main_panel_html(ctx)
# ---------------------------------------------------------------------------
# Slot main panel
# ---------------------------------------------------------------------------
-def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
+async def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
"""Render slot detail view."""
from quart import url_for, g
@@ -2121,15 +2127,15 @@ def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
# Days pills
if days and days[0] != "\u2014":
days_inner = "".join(
- sx_call("events-slot-day-pill", day=d) for d in days
+ await render_to_sx("events-slot-day-pill", day=d) for d in days
)
- days_html = sx_call("events-slot-days-pills", days_inner=SxExpr(days_inner))
+ days_html = await render_to_sx("events-slot-days-pills", days_inner=SxExpr(days_inner))
else:
- days_html = sx_call("events-slot-no-days")
+ days_html = await render_to_sx("events-slot-no-days")
sid = str(slot.id)
- result = sx_call("events-slot-panel",
+ result = await render_to_sx("events-slot-panel",
slot_id=sid, list_container=list_container,
days=SxExpr(days_html),
flexible="yes" if flexible else "no",
@@ -2138,7 +2144,7 @@ def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
pre_action=pre_action, edit_url=edit_url)
if oob:
- result += sx_call("events-slot-description-oob", description=desc)
+ result += await render_to_sx("events-slot-description-oob", description=desc)
return result
@@ -2147,7 +2153,7 @@ def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
# Slots table
# ---------------------------------------------------------------------------
-def render_slots_table(slots, calendar) -> str:
+async def render_slots_table(slots, calendar) -> str:
"""Render slots table with rows and add button."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -2173,18 +2179,18 @@ def render_slots_table(slots, calendar) -> str:
day_list = days_display.split(", ")
if day_list and day_list[0] != "\u2014":
days_inner = "".join(
- sx_call("events-slot-day-pill", day=d) for d in day_list
+ await render_to_sx("events-slot-day-pill", day=d) for d in day_list
)
- days_html = sx_call("events-slot-days-pills", days_inner=SxExpr(days_inner))
+ days_html = await render_to_sx("events-slot-days-pills", days_inner=SxExpr(days_inner))
else:
- days_html = sx_call("events-slot-no-days")
+ days_html = await render_to_sx("events-slot-no-days")
time_start = s.time_start.strftime("%H:%M") if s.time_start else ""
time_end = s.time_end.strftime("%H:%M") if s.time_end else ""
cost = getattr(s, "cost", None)
cost_str = f"{cost:.2f}" if cost is not None else ""
- rows_html += sx_call("events-slots-row",
+ rows_html += await render_to_sx("events-slots-row",
tr_cls=tr_cls, slot_href=slot_href,
pill_cls=pill_cls, hx_select=hx_select,
slot_name=s.name, description=desc,
@@ -2195,11 +2201,11 @@ def render_slots_table(slots, calendar) -> str:
del_url=del_url,
csrf_hdr=f'{{"X-CSRFToken": "{csrf}"}}')
else:
- rows_html = sx_call("events-slots-empty-row")
+ rows_html = await render_to_sx("events-slots-empty-row")
add_url = url_for("calendar.slots.add_form", calendar_slug=cal_slug)
- return sx_call("events-slots-table",
+ return await render_to_sx("events-slots-table",
list_container=list_container, rows=SxExpr(rows_html),
pre_action=pre_action, add_url=add_url)
@@ -2208,7 +2214,7 @@ def render_slots_table(slots, calendar) -> str:
# Ticket type main panel
# ---------------------------------------------------------------------------
-def render_ticket_type_main_panel(ticket_type, entry, calendar, day, month, year, *, oob: bool = False) -> str:
+async def render_ticket_type_main_panel(ticket_type, entry, calendar, day, month, year, *, oob: bool = False) -> str:
"""Render ticket type detail view."""
from quart import url_for, g
@@ -2228,14 +2234,14 @@ def render_ticket_type_main_panel(ticket_type, entry, calendar, day, month, year
year=year, month=month, day=day, entry_id=entry.id,
)
- def _col(label, val):
- return sx_call("events-ticket-type-col", label=label, value=val)
+ async def _col(label, val):
+ return await render_to_sx("events-ticket-type-col", label=label, value=val)
- return sx_call("events-ticket-type-panel",
+ return await render_to_sx("events-ticket-type-panel",
ticket_id=tid, list_container=list_container,
- c1=_col("Name", ticket_type.name),
- c2=_col("Cost", cost_str),
- c3=_col("Count", str(count)),
+ c1=await _col("Name", ticket_type.name),
+ c2=await _col("Cost", cost_str),
+ c3=await _col("Count", str(count)),
pre_action=pre_action, edit_url=edit_url)
@@ -2243,7 +2249,7 @@ def render_ticket_type_main_panel(ticket_type, entry, calendar, day, month, year
# Ticket types table
# ---------------------------------------------------------------------------
-def render_ticket_types_table(ticket_types, entry, calendar, day, month, year) -> str:
+async def render_ticket_types_table(ticket_types, entry, calendar, day, month, year) -> str:
"""Render ticket types table with rows and add button."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -2274,7 +2280,7 @@ def render_ticket_types_table(ticket_types, entry, calendar, day, month, year) -
cost = getattr(tt, "cost", None)
cost_str = f"\u00a3{cost:.2f}" if cost is not None else "\u00a30.00"
- rows_html += sx_call("events-ticket-types-row",
+ rows_html += await render_to_sx("events-ticket-types-row",
tr_cls=tr_cls, tt_href=tt_href,
pill_cls=pill_cls, hx_select=hx_select,
tt_name=tt.name, cost_str=cost_str,
@@ -2282,14 +2288,14 @@ def render_ticket_types_table(ticket_types, entry, calendar, day, month, year) -
del_url=del_url,
csrf_hdr=f'{{"X-CSRFToken": "{csrf}"}}')
else:
- rows_html = sx_call("events-ticket-types-empty-row")
+ rows_html = await render_to_sx("events-ticket-types-empty-row")
add_url = url_for(
"calendar.day.calendar_entries.calendar_entry.ticket_types.add_form",
calendar_slug=cal_slug, entry_id=eid, year=year, month=month, day=day,
)
- return sx_call("events-ticket-types-table",
+ return await render_to_sx("events-ticket-types-table",
list_container=list_container, rows=SxExpr(rows_html),
action_btn=action_btn, add_url=add_url)
@@ -2298,11 +2304,11 @@ def render_ticket_types_table(ticket_types, entry, calendar, day, month, year) -
# Buy result (ticket purchase confirmation)
# ---------------------------------------------------------------------------
-def render_buy_result(entry, created_tickets, remaining, cart_count) -> str:
+async def render_buy_result(entry, created_tickets, remaining, cart_count) -> str:
"""Render buy result card with created tickets + OOB cart icon."""
from quart import url_for
- cart_html = _cart_icon_oob(cart_count)
+ cart_html = await _cart_icon_oob(cart_count)
count = len(created_tickets)
suffix = "s" if count != 1 else ""
@@ -2310,18 +2316,18 @@ def render_buy_result(entry, created_tickets, remaining, cart_count) -> str:
tickets_html = ""
for ticket in created_tickets:
href = url_for("defpage_ticket_detail", code=ticket.code)
- tickets_html += sx_call("events-buy-result-ticket",
+ tickets_html += await render_to_sx("events-buy-result-ticket",
href=href, code_short=ticket.code[:12] + "...")
remaining_html = ""
if remaining is not None:
r_suffix = "s" if remaining != 1 else ""
- remaining_html = sx_call("events-buy-result-remaining",
+ remaining_html = await render_to_sx("events-buy-result-remaining",
text=f"{remaining} ticket{r_suffix} remaining")
my_href = url_for("defpage_my_tickets")
- return cart_html + sx_call("events-buy-result",
+ return cart_html + await render_to_sx("events-buy-result",
entry_id=str(entry.id),
count_label=f"{count} ticket{suffix} reserved",
tickets=SxExpr(tickets_html),
@@ -2333,7 +2339,7 @@ def render_buy_result(entry, created_tickets, remaining, cart_count) -> str:
# Buy form (ticket +/- controls)
# ---------------------------------------------------------------------------
-def render_buy_form(entry, ticket_remaining, ticket_sold_count,
+async def render_buy_form(entry, ticket_remaining, ticket_sold_count,
user_ticket_count, user_ticket_counts_by_type) -> str:
"""Render the ticket buy/adjust form with +/- controls."""
from quart import url_for
@@ -2350,7 +2356,7 @@ def render_buy_form(entry, ticket_remaining, ticket_sold_count,
return ""
if state != "confirmed":
- return sx_call("events-buy-not-confirmed", entry_id=eid_s)
+ return await render_to_sx("events-buy-not-confirmed", entry_id=eid_s)
adjust_url = url_for("tickets.adjust_quantity")
target = f"#ticket-buy-{eid}"
@@ -2359,16 +2365,16 @@ def render_buy_form(entry, ticket_remaining, ticket_sold_count,
info_html = ""
info_items = ""
if ticket_sold_count:
- info_items += sx_call("events-buy-info-sold",
+ info_items += await render_to_sx("events-buy-info-sold",
count=str(ticket_sold_count))
if ticket_remaining is not None:
- info_items += sx_call("events-buy-info-remaining",
+ info_items += await render_to_sx("events-buy-info-remaining",
count=str(ticket_remaining))
if user_ticket_count:
- info_items += sx_call("events-buy-info-basket",
+ info_items += await render_to_sx("events-buy-info-basket",
count=str(user_ticket_count))
if info_items:
- info_html = sx_call("events-buy-info-bar", items=SxExpr(info_items))
+ info_html = await render_to_sx("events-buy-info-bar", items=SxExpr(info_items))
active_types = [tt for tt in ticket_types if getattr(tt, "deleted_at", None) is None]
@@ -2378,46 +2384,46 @@ def render_buy_form(entry, ticket_remaining, ticket_sold_count,
for tt in active_types:
type_count = user_ticket_counts_by_type.get(tt.id, 0) if user_ticket_counts_by_type else 0
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 += await render_to_sx("events-buy-type-item",
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)))
- body_html = sx_call("events-buy-types-wrapper", items=SxExpr(type_items))
+ adjust_controls=SxExpr(await _ticket_adjust_controls(csrf, adjust_url, target, eid, type_count, ticket_type_id=tt.id)))
+ body_html = await render_to_sx("events-buy-types-wrapper", items=SxExpr(type_items))
else:
qty = user_ticket_count or 0
- body_html = sx_call("events-buy-default",
+ body_html = await render_to_sx("events-buy-default",
price_str=f"\u00a3{tp:.2f}",
- adjust_controls=SxExpr(_ticket_adjust_controls(csrf, adjust_url, target, eid, qty)))
+ adjust_controls=SxExpr(await _ticket_adjust_controls(csrf, adjust_url, target, eid, qty)))
- return sx_call("events-buy-panel",
+ return await render_to_sx("events-buy-panel",
entry_id=eid_s, info=SxExpr(info_html), body=SxExpr(body_html))
-def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket_type_id=None):
+async def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket_type_id=None):
"""Render +/- ticket controls for buy form."""
from quart import url_for
- tt_html = sx_call("events-adjust-tt-hidden",
+ tt_html = await render_to_sx("events-adjust-tt-hidden",
ticket_type_id=str(ticket_type_id)) if ticket_type_id else ""
eid_s = str(entry_id)
- def _adj_form(count_val, btn_html, *, extra_cls=""):
- return sx_call("events-adjust-form",
+ async def _adj_form(count_val, btn_html, *, extra_cls=""):
+ return await render_to_sx("events-adjust-form",
adjust_url=adjust_url, target=target,
extra_cls=extra_cls, csrf=csrf,
entry_id=eid_s, tt=SxExpr(tt_html) if tt_html else None,
count_val=str(count_val), btn=SxExpr(btn_html))
if count == 0:
- return _adj_form(1, sx_call("events-adjust-cart-plus"),
+ return await _adj_form(1, await render_to_sx("events-adjust-cart-plus"),
extra_cls="flex items-center")
my_tickets_href = url_for("defpage_my_tickets")
- minus = _adj_form(count - 1, sx_call("events-adjust-minus"))
- cart_icon = sx_call("events-adjust-cart-icon",
+ minus = await _adj_form(count - 1, await render_to_sx("events-adjust-minus"))
+ cart_icon = await render_to_sx("events-adjust-cart-icon",
href=my_tickets_href, count=str(count))
- plus = _adj_form(count + 1, sx_call("events-adjust-plus"))
+ plus = await _adj_form(count + 1, await render_to_sx("events-adjust-plus"))
- return sx_call("events-adjust-controls",
+ return await render_to_sx("events-adjust-controls",
minus=SxExpr(minus), cart_icon=SxExpr(cart_icon), plus=SxExpr(plus))
@@ -2425,19 +2431,19 @@ def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket
# Adjust response (OOB cart icon + buy form)
# ---------------------------------------------------------------------------
-def render_adjust_response(entry, ticket_remaining, ticket_sold_count,
+async def render_adjust_response(entry, ticket_remaining, ticket_sold_count,
user_ticket_count, user_ticket_counts_by_type,
cart_count) -> str:
"""Render ticket adjust response: OOB cart icon + buy form."""
- cart_html = _cart_icon_oob(cart_count)
- form_html = render_buy_form(
+ cart_html = await _cart_icon_oob(cart_count)
+ form_html = await render_buy_form(
entry, ticket_remaining, ticket_sold_count,
user_ticket_count, user_ticket_counts_by_type,
)
return cart_html + form_html
-def _cart_icon_oob(count: int) -> str:
+async def _cart_icon_oob(count: int) -> str:
"""Render the OOB cart icon/badge swap."""
from quart import g
@@ -2451,11 +2457,11 @@ def _cart_icon_oob(count: int) -> str:
if count == 0:
blog_href = blog_url_fn("/") if blog_url_fn else "/"
- return sx_call("events-cart-icon-logo",
+ return await render_to_sx("events-cart-icon-logo",
blog_href=blog_href, logo=logo)
cart_href = cart_url_fn("/") if cart_url_fn else "/"
- return sx_call("events-cart-icon-badge",
+ return await render_to_sx("events-cart-icon-badge",
cart_href=cart_href, count=str(count))
@@ -2564,7 +2570,7 @@ _SLOT_PICKER_JS = """\
# Entry edit form
# ===========================================================================
-def _slot_options_html(day_slots, selected_slot_id=None) -> str:
+async def _slot_options_html(day_slots, selected_slot_id=None) -> str:
"""Build slot