Remove render_to_sx from public API: enforce sx_call for all service code
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m44s

Replace ~250 render_to_sx calls across all services with sync sx_call,
converting many async functions to sync where no other awaits remained.
Make render_to_sx/render_to_sx_with_env private (_render_to_sx).
Add (post-header-ctx) IO primitive and shared post/post-admin defmacros.
Convert built-in post/post-admin layouts from Python to register_sx_layout
with .sx defcomps. Remove dead post_admin_mobile_nav_sx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 19:30:45 +00:00
parent 57e0d0c341
commit 959e63d440
61 changed files with 1352 additions and 1208 deletions

View File

@@ -13,7 +13,7 @@ from sqlalchemy import select
from shared.models import UserNewsletter
from shared.infrastructure.fragments import fetch_fragments
from shared.sx.helpers import sx_response, render_to_sx
from shared.sx.helpers import sx_response, sx_call
def register(url_prefix="/"):
@@ -66,7 +66,7 @@ def register(url_prefix="/"):
translate = "translate-x-6" if un.subscribed else "translate-x-1"
checked = "true" if un.subscribed else "false"
return sx_response(await render_to_sx(
return sx_response(sx_call(
"account-newsletter-toggle",
id=f"nl-{nid}", url=toggle_url,
hdrs=f'{{"X-CSRFToken": "{csrf}"}}',

View File

@@ -47,11 +47,11 @@ ACCOUNT_SESSION_KEY = "account_sid"
async def _render_auth_page(component: str, title: str, **kwargs) -> str:
"""Render an auth page with root layout — replaces sx_components helpers."""
from shared.sx.helpers import render_to_sx, full_page_sx, root_header_sx
from shared.sx.helpers import sx_call, full_page_sx, root_header_sx
from shared.sx.page import get_template_context
ctx = await get_template_context()
hdr = await root_header_sx(ctx)
content = await render_to_sx(component, **{k: v for k, v in kwargs.items() if v})
content = sx_call(component, **{k: v for k, v in kwargs.items() if v})
return await full_page_sx(ctx, header_rows=hdr, content=content,
meta_html=f"<title>{title}</title>")

View File

@@ -21,7 +21,7 @@ from .services.pages_data import pages_data
from shared.browser.app.redis_cacher import cache_page, invalidate_tag_cache
from shared.browser.app.utils.htmx import is_htmx_request
from shared.browser.app.authz import require_admin
from shared.sx.helpers import sx_response, render_to_sx
from shared.sx.helpers import sx_response, sx_call
from shared.utils import host_url
def register(url_prefix, title):
@@ -67,7 +67,7 @@ def register(url_prefix, title):
from shared.sx.helpers import root_header_sx, full_page_sx
from shared.sx.parser import SxExpr
root_hdr = await root_header_sx(tctx)
blog_hdr = await render_to_sx("menu-row-sx",
blog_hdr = sx_call("menu-row-sx",
id="blog-row", level=1,
link_label_content=SxExpr("(div)"),
child_id="blog-header-child")
@@ -132,7 +132,7 @@ def register(url_prefix, title):
from shared.sx.page import get_template_context
from shared.sx.helpers import (
render_to_sx, root_header_sx, full_page_sx, oob_page_sx,
sx_call, root_header_sx, full_page_sx, oob_page_sx,
post_header_sx, oob_header_sx, mobile_menu_sx,
post_mobile_nav_sx, mobile_root_nav_sx,
)
@@ -143,11 +143,11 @@ def register(url_prefix, title):
tctx.update(ctx)
post = ctx.get("post", {})
content = await render_to_sx("blog-home-main",
content = sx_call("blog-home-main",
html_content=post.get("html", ""),
sx_content=SxExpr(post.get("sx_content", "")) if post.get("sx_content") else None)
meta_data = services.blog_page.post_meta_data(post, ctx.get("base_title", ""))
meta = await render_to_sx("blog-meta", **meta_data)
meta = sx_call("blog-meta", **meta_data)
if not is_htmx_request():
root_hdr = await root_header_sx(tctx)
@@ -171,12 +171,12 @@ def register(url_prefix, title):
"""Blog listing — moved from / to /index."""
from shared.services.registry import services
from shared.sx.helpers import (
render_to_sx, root_header_sx, full_page_sx, oob_page_sx, oob_header_sx,
sx_call, root_header_sx, full_page_sx, oob_page_sx, oob_header_sx,
)
from shared.sx.parser import SxExpr
async def _blog_hdr(ctx, oob=False):
return await render_to_sx("menu-row-sx",
def _blog_hdr(ctx, oob=False):
return sx_call("menu-row-sx",
id="blog-row", level=1,
link_label_content=SxExpr("(div)"),
child_id="blog-header-child", oob=oob)
@@ -184,16 +184,16 @@ def register(url_prefix, title):
data = await services.blog_page.index_data(g.s)
# Render content, aside, and filter via .sx defcomps
content = await render_to_sx("blog-index-main-content", **data)
aside = await render_to_sx("blog-index-aside-content", **data)
filter_sx = await render_to_sx("blog-index-filter-content", **data)
content = sx_call("blog-index-main-content", **data)
aside = sx_call("blog-index-aside-content", **data)
filter_sx = sx_call("blog-index-filter-content", **data)
from shared.sx.page import get_template_context
tctx = await get_template_context()
if not is_htmx_request():
root_hdr = await root_header_sx(tctx)
blog_hdr = await _blog_hdr(tctx)
blog_hdr = _blog_hdr(tctx)
header_rows = "(<> " + root_hdr + " " + blog_hdr + ")"
html = await full_page_sx(tctx, header_rows=header_rows,
content=content, aside=aside, filter=filter_sx)
@@ -203,7 +203,7 @@ def register(url_prefix, title):
return sx_response(content)
else:
root_hdr = await root_header_sx(tctx)
blog_hdr = await _blog_hdr(tctx)
blog_hdr = _blog_hdr(tctx)
rows = "(<> " + root_hdr + " " + blog_hdr + ")"
header_oob = await oob_header_sx("root-header-child", "blog-header-child", rows)
sx_src = await oob_page_sx(oobs=header_oob, content=content,
@@ -231,7 +231,7 @@ def register(url_prefix, title):
from shared.sx.page import get_template_context
from sxc.pages.renders import render_editor_panel
tctx = await get_template_context()
tctx["editor_html"] = await render_editor_panel(save_error="Invalid JSON in editor content.")
tctx["editor_html"] = render_editor_panel(save_error="Invalid JSON in editor content.")
html = await _render_new_post_page(tctx)
return await make_response(html, 400)
@@ -240,7 +240,7 @@ def register(url_prefix, title):
from shared.sx.page import get_template_context
from sxc.pages.renders import render_editor_panel
tctx = await get_template_context()
tctx["editor_html"] = await render_editor_panel(save_error=reason)
tctx["editor_html"] = render_editor_panel(save_error=reason)
html = await _render_new_post_page(tctx)
return await make_response(html, 400)
@@ -287,7 +287,7 @@ def register(url_prefix, title):
from shared.sx.page import get_template_context
from sxc.pages.renders import render_editor_panel
tctx = await get_template_context()
tctx["editor_html"] = await render_editor_panel(save_error="Invalid JSON in editor content.", is_page=True)
tctx["editor_html"] = 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)
@@ -297,7 +297,7 @@ def register(url_prefix, title):
from shared.sx.page import get_template_context
from sxc.pages.renders import render_editor_panel
tctx = await get_template_context()
tctx["editor_html"] = await render_editor_panel(save_error=reason, is_page=True)
tctx["editor_html"] = 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)

View File

@@ -13,12 +13,12 @@ from .services.menu_items import (
MenuItemError,
)
from markupsafe import escape
from shared.sx.helpers import sx_response, render_to_sx
from shared.sx.helpers import sx_response, sx_call
from shared.sx.parser import SxExpr
from shared.browser.app.csrf import generate_csrf_token
async def _render_menu_items_list(menu_items):
def _render_menu_items_list(menu_items):
"""Serialize ORM menu items and render via .sx defcomp."""
csrf = generate_csrf_token()
items = []
@@ -32,8 +32,8 @@ async def _render_menu_items_list(menu_items):
"delete_url": url_for("menu_items.delete_menu_item_route", item_id=item.id),
})
new_url = url_for("menu_items.new_menu_item")
return await render_to_sx("blog-menu-items-content",
menu_items=items, new_url=new_url, csrf=csrf)
return sx_call("blog-menu-items-content",
menu_items=items, new_url=new_url, csrf=csrf)
def _render_menu_item_form(menu_item=None) -> str:
@@ -120,16 +120,16 @@ document.addEventListener('click', function(e) {{
return html
async def _render_page_search_results(pages, query, page, has_more) -> str:
def _render_page_search_results(pages, query, page, has_more) -> str:
"""Render page search results."""
if not pages and query:
return await render_to_sx("page-search-empty", query=query)
return sx_call("page-search-empty", query=query)
if not pages:
return ""
items = []
for post in pages:
items.append(await render_to_sx("page-search-item",
items.append(sx_call("page-search-item",
id=post.id, title=post.title,
slug=post.slug,
feature_image=post.feature_image or None))
@@ -137,22 +137,22 @@ async def _render_page_search_results(pages, query, page, has_more) -> str:
sentinel = ""
if has_more:
search_url = url_for("menu_items.search_pages_route")
sentinel = await render_to_sx("page-search-sentinel",
sentinel = sx_call("page-search-sentinel",
url=search_url, query=query,
next_page=page + 1)
items_sx = "(<> " + " ".join(items) + ")"
return await render_to_sx("page-search-results",
return sx_call("page-search-results",
items=SxExpr(items_sx),
sentinel=SxExpr(sentinel) if sentinel else None)
async def _render_menu_items_nav_oob(menu_items) -> str:
def _render_menu_items_nav_oob(menu_items) -> str:
"""Render OOB nav update for menu items."""
from quart import request as qrequest
if not menu_items:
return await render_to_sx("blog-nav-empty", wrapper_id="menu-items-nav-wrapper")
return sx_call("blog-nav-empty", wrapper_id="menu-items-nav-wrapper")
first_seg = qrequest.path.strip("/").split("/")[0] if qrequest else ""
@@ -185,23 +185,23 @@ async def _render_menu_items_nav_oob(menu_items) -> str:
href = f"/{item_slug}/"
selected = "true" if item_slug == first_seg else "false"
img_sx = await render_to_sx("img-or-placeholder", src=fi, alt=label,
img_sx = sx_call("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(await render_to_sx("blog-nav-item-link",
item_parts.append(sx_call("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(await render_to_sx("blog-nav-item-plain",
item_parts.append(sx_call("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 await render_to_sx("scroll-nav-wrapper",
return sx_call("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",
@@ -214,9 +214,9 @@ async def _render_menu_items_nav_oob(menu_items) -> str:
def register():
bp = Blueprint("menu_items", __name__, url_prefix='/settings/menu_items')
async def get_menu_items_nav_oob_async(menu_items):
def get_menu_items_nav_oob_sync(menu_items):
"""Helper to generate OOB update for root nav menu items"""
return await _render_menu_items_nav_oob(menu_items)
return _render_menu_items_nav_oob(menu_items)
@bp.get("/new/")
@require_admin
@@ -245,8 +245,8 @@ def register():
# Get updated list and nav OOB
menu_items = await get_all_menu_items(g.s)
html = await _render_menu_items_list(menu_items)
nav_oob = await get_menu_items_nav_oob_async(menu_items)
html = _render_menu_items_list(menu_items)
nav_oob = get_menu_items_nav_oob_sync(menu_items)
return sx_response(html + nav_oob)
except MenuItemError as e:
@@ -283,8 +283,8 @@ def register():
# Get updated list and nav OOB
menu_items = await get_all_menu_items(g.s)
html = await _render_menu_items_list(menu_items)
nav_oob = await get_menu_items_nav_oob_async(menu_items)
html = _render_menu_items_list(menu_items)
nav_oob = get_menu_items_nav_oob_sync(menu_items)
return sx_response(html + nav_oob)
except MenuItemError as e:
@@ -303,8 +303,8 @@ def register():
# Get updated list and nav OOB
menu_items = await get_all_menu_items(g.s)
html = await _render_menu_items_list(menu_items)
nav_oob = await get_menu_items_nav_oob_async(menu_items)
html = _render_menu_items_list(menu_items)
nav_oob = get_menu_items_nav_oob_sync(menu_items)
return sx_response(html + nav_oob)
@bp.get("/pages/search/")
@@ -318,7 +318,7 @@ def register():
pages, total = await search_pages(g.s, query, page, per_page)
has_more = (page * per_page) < total
return sx_response(await _render_page_search_results(pages, query, page, has_more))
return sx_response(_render_page_search_results(pages, query, page, has_more))
@bp.post("/reorder/")
@require_admin
@@ -342,8 +342,8 @@ def register():
# Get updated list and nav OOB
menu_items = await get_all_menu_items(g.s)
html = await _render_menu_items_list(menu_items)
nav_oob = await get_menu_items_nav_oob_async(menu_items)
html = _render_menu_items_list(menu_items)
nav_oob = get_menu_items_nav_oob_sync(menu_items)
return sx_response(html + nav_oob)
return bp

View File

@@ -11,7 +11,7 @@ from quart import (
)
from shared.browser.app.authz import require_admin, require_post_author
from markupsafe import escape
from shared.sx.helpers import sx_response, render_to_sx
from shared.sx.helpers import sx_response, sx_call
from shared.sx.parser import SxExpr, serialize as sx_serialize
from shared.utils import host_url
@@ -60,10 +60,10 @@ def _post_to_edit_dict(post) -> dict:
return d
async def _render_features(features, post, result):
def _render_features(features, post, result):
"""Render features panel via .sx defcomp."""
slug = post.get("slug", "")
return await render_to_sx("blog-features-panel-content",
return sx_call("blog-features-panel-content",
features_url=host_url(url_for("blog.post.admin.update_features", slug=slug)),
calendar_checked=bool(features.get("calendar")),
market_checked=bool(features.get("market")),
@@ -187,7 +187,7 @@ def _render_calendar_view(
return _raw_html_sx(html)
async def _render_associated_entries(all_calendars, associated_entry_ids, post_slug: str) -> str:
def _render_associated_entries(all_calendars, associated_entry_ids, post_slug: str) -> str:
"""Render the associated entries panel."""
from shared.browser.app.csrf import generate_csrf_token
from quart import url_for as qurl
@@ -216,13 +216,13 @@ async def _render_associated_entries(all_calendars, associated_entry_ids, post_s
toggle_url = host_url(qurl("blog.post.admin.toggle_entry", slug=post_slug, entry_id=e_id))
img_sx = await render_to_sx("blog-entry-image", src=cal_fi, title=cal_title)
img_sx = sx_call("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(await render_to_sx("blog-associated-entry",
entry_items.append(sx_call("blog-associated-entry",
confirm_text=f"This will remove {e_name} from this post",
toggle_url=toggle_url,
hx_headers=f'{{"X-CSRFToken": "{csrf}"}}',
@@ -231,16 +231,16 @@ async def _render_associated_entries(all_calendars, associated_entry_ids, post_s
))
if has_entries:
content_sx = await render_to_sx("blog-associated-entries-content",
content_sx = sx_call("blog-associated-entries-content",
items=SxExpr("(<> " + " ".join(entry_items) + ")"),
)
else:
content_sx = await render_to_sx("blog-associated-entries-empty")
content_sx = sx_call("blog-associated-entries-empty")
return await render_to_sx("blog-associated-entries-panel", content=SxExpr(content_sx))
return sx_call("blog-associated-entries-panel", content=SxExpr(content_sx))
async def _render_nav_entries_oob(associated_entries, calendars, post: dict) -> str:
def _render_nav_entries_oob(associated_entries, calendars, post: dict) -> str:
"""Render the OOB nav entries swap."""
entries_list = []
if associated_entries and hasattr(associated_entries, "entries"):
@@ -249,7 +249,7 @@ async def _render_nav_entries_oob(associated_entries, calendars, post: dict) ->
has_items = bool(entries_list or calendars)
if not has_items:
return await render_to_sx("blog-nav-entries-empty")
return sx_call("blog-nav-entries-empty")
select_colours = (
"[.hover-capable_&]:hover:bg-yellow-300"
@@ -291,7 +291,7 @@ async def _render_nav_entries_oob(associated_entries, calendars, post: dict) ->
entry_path = f"/{post_slug}/{cal_slug}/"
date_str = ""
item_parts.append(await render_to_sx("calendar-entry-nav",
item_parts.append(sx_call("calendar-entry-nav",
href=entry_path, nav_class=nav_cls, name=e_name, date_str=date_str,
))
@@ -300,13 +300,13 @@ async def _render_nav_entries_oob(associated_entries, calendars, post: dict) ->
cal_slug = getattr(calendar, "slug", "")
cal_path = f"/{post_slug}/{cal_slug}/"
item_parts.append(await render_to_sx("blog-nav-calendar-item",
item_parts.append(sx_call("blog-nav-calendar-item",
href=cal_path, nav_cls=nav_cls, name=cal_name,
))
items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else ""
return await render_to_sx("scroll-nav-wrapper",
return sx_call("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",
@@ -353,7 +353,7 @@ def register():
})
features = result.get("features", {})
html = await _render_features(features, post, result)
html = _render_features(features, post, result)
return sx_response(html)
@bp.put("/admin/sumup/")
@@ -386,7 +386,7 @@ def register():
result = await call_action("blog", "update-page-config", payload=payload)
features = result.get("features", {})
html = await _render_features(features, post, result)
html = _render_features(features, post, result)
return sx_response(html)
@bp.get("/entries/calendar/<int:calendar_id>/")
@@ -508,8 +508,8 @@ def register():
# Return the associated entries admin list + OOB update for nav entries
post = g.post_data["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)
admin_list = _render_associated_entries(all_calendars, associated_entry_ids, post["slug"])
nav_entries_html = _render_nav_entries_oob(associated_entries, calendars, post)
return sx_response(admin_list + nav_entries_html)
@@ -686,7 +686,7 @@ def register():
slug = post.get("slug", "")
create_url = host_url(url_for("blog.post.admin.create_market", slug=slug))
html = await render_to_sx("blog-markets-panel-content",
html = sx_call("blog-markets-panel-content",
markets=_serialize_markets(page_markets, slug), create_url=create_url)
return sx_response(html)
@@ -715,7 +715,7 @@ def register():
slug = post.get("slug", "")
create_url = host_url(url_for("blog.post.admin.create_market", slug=slug))
html = await render_to_sx("blog-markets-panel-content",
html = sx_call("blog-markets-panel-content",
markets=_serialize_markets(page_markets, slug), create_url=create_url)
return sx_response(html)
@@ -738,7 +738,7 @@ def register():
slug = post.get("slug", "")
create_url = host_url(url_for("blog.post.admin.create_market", slug=slug))
html = await render_to_sx("blog-markets-panel-content",
html = sx_call("blog-markets-panel-content",
markets=_serialize_markets(page_markets, slug), create_url=create_url)
return sx_response(html)

View File

@@ -106,7 +106,7 @@ def register():
async def post_detail(slug: str):
from shared.sx.page import get_template_context
from shared.sx.helpers import (
render_to_sx, root_header_sx, full_page_sx, oob_page_sx,
sx_call, root_header_sx, full_page_sx, oob_page_sx,
post_header_sx, oob_header_sx, mobile_menu_sx,
post_mobile_nav_sx, mobile_root_nav_sx,
)
@@ -124,9 +124,9 @@ def register():
csrf = generate_csrf_token()
svc = services.blog_page
detail_data = svc.post_detail_data(post, user, rights, csrf, blog_url_base)
content = await render_to_sx("blog-post-detail-content", **detail_data)
content = sx_call("blog-post-detail-content", **detail_data)
meta_data = svc.post_meta_data(post, tctx.get("base_title", ""))
meta = await render_to_sx("blog-meta", **meta_data)
meta = sx_call("blog-meta", **meta_data)
if not is_htmx_request():
root_hdr = await root_header_sx(tctx)
@@ -149,24 +149,24 @@ def register():
@clear_cache(tag="post.post_detail", tag_scope="user")
async def like_toggle(slug: str):
from shared.utils import host_url
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.browser.app.csrf import generate_csrf_token
like_url = host_url(url_for('blog.post.like_toggle', slug=slug))
csrf = generate_csrf_token()
async def _like_btn(liked):
def _like_btn(liked):
if liked:
colour, icon, label = "text-red-600", "fa-solid fa-heart", "Unlike this post"
else:
colour, icon, label = "text-stone-300", "fa-regular fa-heart", "Like this post"
return await render_to_sx("market-like-toggle-button",
return sx_call("market-like-toggle-button",
colour=colour, action=like_url,
hx_headers=f'{{"X-CSRFToken": "{csrf}"}}',
label=label, icon_cls=icon)
if not g.user:
return sx_response(await _like_btn(False), status=403)
return sx_response(_like_btn(False), status=403)
post_id = g.post_data["post"]["id"]
user_id = g.user.id
@@ -175,7 +175,7 @@ def register():
"user_id": user_id, "target_type": "post", "target_id": post_id,
})
return sx_response(await _like_btn(result["liked"]))
return sx_response(_like_btn(result["liked"]))
@bp.get("/w/<widget_domain>/")
async def widget_paginate(slug: str, widget_domain: str):

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from quart import Blueprint, request, g, abort
from shared.browser.app.authz import require_login
from shared.sx.helpers import sx_response, render_to_sx
from shared.sx.helpers import sx_response, sx_call
from models import Snippet
@@ -14,7 +14,7 @@ async def _render_snippets():
"""Render snippets list via service data + .sx defcomp."""
from shared.services.registry import services
data = await services.blog_page.snippets_data(g.s)
return await render_to_sx("blog-snippets-content", **data)
return sx_call("blog-snippets-content", **data)
def register():

View File

@@ -126,22 +126,22 @@ def _register_blog_helpers() -> None:
# --- Editor helpers ---
async def _h_editor_content(**kw):
def _h_editor_content(**kw):
from .renders import render_editor_panel
return await render_editor_panel()
return render_editor_panel()
async def _h_editor_page_content(**kw):
def _h_editor_page_content(**kw):
from .renders import render_editor_panel
return await render_editor_panel(is_page=True)
return render_editor_panel(is_page=True)
# --- Post admin helpers ---
async def _h_post_admin_content(slug=None, **kw):
await _ensure_post_data(slug)
from shared.sx.helpers import render_to_sx
return await render_to_sx("blog-admin-placeholder")
from shared.sx.helpers import sx_call
return sx_call("blog-admin-placeholder")
async def _h_post_data_content(slug=None, **kw):
@@ -264,32 +264,32 @@ async def _h_post_preview_content(slug=None, **kw):
await _ensure_post_data(slug)
from quart import g
from shared.services.registry import services
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
preview = await services.blog_page.preview_data(g.s)
sections: list[str] = []
if preview.get("sx_pretty"):
sections.append(await render_to_sx("blog-preview-section",
sections.append(sx_call("blog-preview-section",
title="S-Expression Source", content=SxExpr(preview["sx_pretty"])))
if preview.get("json_pretty"):
sections.append(await render_to_sx("blog-preview-section",
sections.append(sx_call("blog-preview-section",
title="Lexical JSON", content=SxExpr(preview["json_pretty"])))
if preview.get("sx_rendered"):
rendered_sx = await render_to_sx("blog-preview-rendered", html=preview["sx_rendered"])
sections.append(await render_to_sx("blog-preview-section",
rendered_sx = sx_call("blog-preview-rendered", html=preview["sx_rendered"])
sections.append(sx_call("blog-preview-section",
title="SX Rendered", content=SxExpr(rendered_sx)))
if preview.get("lex_rendered"):
rendered_sx = await render_to_sx("blog-preview-rendered", html=preview["lex_rendered"])
sections.append(await render_to_sx("blog-preview-section",
rendered_sx = sx_call("blog-preview-rendered", html=preview["lex_rendered"])
sections.append(sx_call("blog-preview-section",
title="Lexical Rendered", content=SxExpr(rendered_sx)))
if not sections:
return await render_to_sx("blog-preview-empty")
return sx_call("blog-preview-empty")
inner = " ".join(sections)
return await render_to_sx("blog-preview-panel", sections=SxExpr(f"(<> {inner})"))
return sx_call("blog-preview-panel", sections=SxExpr(f"(<> {inner})"))
async def _h_post_entries_content(slug=None, **kw):
@@ -315,7 +315,7 @@ async def _h_post_entries_content(slug=None, **kw):
await g.s.refresh(calendar, ["entries", "post"])
# Associated entries list
assoc_html = await _render_associated_entries(all_calendars, associated_entry_ids, post_slug)
assoc_html = _render_associated_entries(all_calendars, associated_entry_ids, post_slug)
# Calendar browser
cal_items: list[str] = []
@@ -505,7 +505,7 @@ async def _h_post_edit_content(slug=None, **kw):
from sqlalchemy.orm import selectinload
from shared.infrastructure.data_client import fetch_data
from shared.browser.app.csrf import generate_csrf_token
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr, serialize as sx_serialize
from bp.post.admin.routes import _post_to_edit_dict
@@ -584,9 +584,9 @@ async def _h_post_edit_content(slug=None, **kw):
parts: list[str] = []
if save_error:
parts.append(await render_to_sx("blog-editor-error", error=save_error))
parts.append(sx_call("blog-editor-error", error=save_error))
parts.append(await render_to_sx("blog-editor-edit-form",
parts.append(sx_call("blog-editor-edit-form",
csrf=csrf,
updated_at=str(updated_at),
title_val=title_val,
@@ -603,9 +603,9 @@ async def _h_post_edit_content(slug=None, **kw):
footer_extra=footer_extra_sx,
))
parts.append(await render_to_sx("blog-editor-publish-js", already_emailed=already_emailed))
parts.append(await render_to_sx("blog-editor-styles", css_href=editor_css))
parts.append(await render_to_sx("sx-editor-styles"))
parts.append(sx_call("blog-editor-publish-js", already_emailed=already_emailed))
parts.append(sx_call("blog-editor-styles", css_href=editor_css))
parts.append(sx_call("sx-editor-styles"))
init_js = (
'(function() {'
@@ -705,10 +705,10 @@ async def _h_post_edit_content(slug=None, **kw):
' }, 50); }'
'})();'
)
parts.append(await render_to_sx("blog-editor-scripts",
parts.append(sx_call("blog-editor-scripts",
js_src=editor_js,
sx_editor_js_src=sx_editor_js,
init_js=init_js))
return await render_to_sx("blog-editor-panel",
return sx_call("blog-editor-panel",
parts=SxExpr("(<> " + " ".join(parts) + ")"))

View File

@@ -5,19 +5,19 @@ from typing import Any
# ---------------------------------------------------------------------------
# Header helpers (moved from sx_components — thin render_to_sx wrappers)
# Header helpers (moved from sx_components — thin sx_call wrappers)
# ---------------------------------------------------------------------------
async def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
from shared.sx.helpers import render_to_sx
def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
from quart import url_for as qurl
settings_href = qurl("settings.defpage_settings_home")
label_sx = await render_to_sx("blog-admin-label")
nav_sx = await _settings_nav_sx(ctx)
label_sx = sx_call("blog-admin-label")
nav_sx = _settings_nav_sx(ctx)
return await render_to_sx("menu-row-sx",
return sx_call("menu-row-sx",
id="root-settings-row", level=1,
link_href=settings_href,
link_label_content=SxExpr(label_sx),
@@ -25,20 +25,20 @@ async def _settings_header_sx(ctx: dict, *, oob: bool = False) -> str:
child_id="root-settings-header-child", oob=oob)
async def _settings_nav_sx(ctx: dict) -> str:
from shared.sx.helpers import render_to_sx
return await render_to_sx("blog-settings-nav")
def _settings_nav_sx(ctx: dict) -> str:
from shared.sx.helpers import sx_call
return sx_call("blog-settings-nav")
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:
from shared.sx.helpers import render_to_sx
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:
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
label_sx = await render_to_sx("blog-sub-settings-label",
label_sx = sx_call("blog-sub-settings-label",
icon=f"fa fa-{icon}", label=label)
return await render_to_sx("menu-row-sx",
return sx_call("menu-row-sx",
id=row_id, level=2,
link_href=href,
link_label_content=SxExpr(label_sx),
@@ -82,19 +82,19 @@ async def _settings_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("settings-layout-full", {},
settings_header=SxExpr(await _settings_header_sx(ctx)))
settings_header=SxExpr(_settings_header_sx(ctx)))
async def _settings_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
rows = await render_to_sx_with_env("settings-layout-full", {},
settings_header=SxExpr(await _settings_header_sx(ctx)))
settings_header=SxExpr(_settings_header_sx(ctx)))
return await oob_header_sx("root-header-child", "root-settings-header-child", rows)
async def _settings_mobile(ctx: dict, **kw: Any) -> str:
return await _settings_nav_sx(ctx)
def _settings_mobile(ctx: dict, **kw: Any) -> str:
return _settings_nav_sx(ctx)
# --- Sub-settings helpers ---
@@ -105,21 +105,21 @@ async def _sub_settings_full(ctx: dict, row_id: str, child_id: str,
from shared.sx.parser import SxExpr
from quart import url_for as qurl
return await render_to_sx_with_env("sub-settings-layout-full", {},
settings_header=SxExpr(await _settings_header_sx(ctx)),
sub_header=SxExpr(await _sub_settings_header_sx(
settings_header=SxExpr(_settings_header_sx(ctx)),
sub_header=SxExpr(_sub_settings_header_sx(
row_id, child_id, qurl(endpoint), icon, label, ctx)))
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, render_to_sx
from shared.sx.helpers import oob_header_sx, sx_call
from shared.sx.parser import SxExpr
from quart import url_for as qurl
settings_hdr_oob = await _settings_header_sx(ctx, oob=True)
sub_hdr = await _sub_settings_header_sx(
settings_hdr_oob = _settings_header_sx(ctx, oob=True)
sub_hdr = _sub_settings_header_sx(
row_id, child_id, qurl(endpoint), icon, label, ctx)
sub_oob = await oob_header_sx("root-settings-header-child", child_id, sub_hdr)
return await render_to_sx("sub-settings-layout-oob",
return sx_call("sub-settings-layout-oob",
settings_header_oob=SxExpr(settings_hdr_oob),
sub_header_oob=SxExpr(sub_oob))
@@ -180,8 +180,8 @@ async def _tag_group_edit_full(ctx: dict, **kw: Any) -> str:
from shared.sx.parser import SxExpr
g_id = (request.view_args or {}).get("id")
return await render_to_sx_with_env("sub-settings-layout-full", {},
settings_header=SxExpr(await _settings_header_sx(ctx)),
sub_header=SxExpr(await _sub_settings_header_sx(
settings_header=SxExpr(_settings_header_sx(ctx)),
sub_header=SxExpr(_sub_settings_header_sx(
"tag-groups-row", "tag-groups-header-child",
qurl("defpage_tag_group_edit", id=g_id),
"tags", "Tag Groups", ctx)))
@@ -189,15 +189,15 @@ async def _tag_group_edit_full(ctx: dict, **kw: Any) -> str:
async def _tag_group_edit_oob(ctx: dict, **kw: Any) -> str:
from quart import request, url_for as qurl
from shared.sx.helpers import oob_header_sx, render_to_sx
from shared.sx.helpers import oob_header_sx, sx_call
from shared.sx.parser import SxExpr
g_id = (request.view_args or {}).get("id")
settings_hdr_oob = await _settings_header_sx(ctx, oob=True)
sub_hdr = await _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",
qurl("defpage_tag_group_edit", id=g_id),
"tags", "Tag Groups", ctx)
sub_oob = await oob_header_sx("root-settings-header-child", "tag-groups-header-child", sub_hdr)
return await render_to_sx("sub-settings-layout-oob",
return sx_call("sub-settings-layout-oob",
settings_header_oob=SxExpr(settings_hdr_oob),
sub_header_oob=SxExpr(sub_oob))

View File

@@ -2,12 +2,12 @@
from __future__ import annotations
async def render_editor_panel(save_error: str | None = None, is_page: bool = False) -> str:
def render_editor_panel(save_error: str | None = None, is_page: bool = False) -> str:
"""Build the WYSIWYG editor panel HTML for new post/page creation."""
import os
from quart import url_for as qurl, current_app
from shared.browser.app.csrf import generate_csrf_token
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
csrf = generate_csrf_token()
asset_url_fn = current_app.jinja_env.globals.get("asset_url", lambda p: "")
@@ -28,15 +28,15 @@ async def render_editor_panel(save_error: str | None = None, is_page: bool = Fal
parts: list[str] = []
if save_error:
parts.append(await render_to_sx("blog-editor-error", error=str(save_error)))
parts.append(sx_call("blog-editor-error", error=str(save_error)))
parts.append(await render_to_sx("blog-editor-form",
parts.append(sx_call("blog-editor-form",
csrf=csrf, title_placeholder=title_placeholder,
create_label=create_label,
))
parts.append(await render_to_sx("blog-editor-styles", css_href=editor_css))
parts.append(await render_to_sx("sx-editor-styles"))
parts.append(sx_call("blog-editor-styles", css_href=editor_css))
parts.append(sx_call("sx-editor-styles"))
init_js = (
"console.log('[EDITOR-DEBUG] init script running');\n"
@@ -167,11 +167,11 @@ async def render_editor_panel(save_error: str | None = None, is_page: bool = Fal
" }\n"
"})();\n"
)
parts.append(await render_to_sx("blog-editor-scripts",
parts.append(sx_call("blog-editor-scripts",
js_src=editor_js,
sx_editor_js_src=sx_editor_js,
init_js=init_js))
from shared.sx.parser import SxExpr
return await render_to_sx("blog-editor-panel",
return sx_call("blog-editor-panel",
parts=SxExpr("(<> " + " ".join(parts) + ")")) if parts else ""

View File

@@ -154,7 +154,7 @@ def register(url_prefix: str) -> Blueprint:
)
resp = await make_response(html)
elif page > 1:
sx_src = await render_orders_rows(
sx_src = render_orders_rows(
ctx, orders, page, total_pages, url_for, qs_fn,
)
resp = sx_response(sx_src)

View File

@@ -49,7 +49,7 @@ def register():
from shared.sx.page import get_template_context
from sxc.pages.renders import render_cart_payments_panel
ctx = await get_template_context()
html = await render_cart_payments_panel(ctx)
html = render_cart_payments_panel(ctx)
return sx_response(html)
return bp

View File

@@ -57,9 +57,9 @@ async def _post_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> st
return await _shared_post_header_sx(ctx, oob=oob)
async def _cart_header_sx(ctx: dict, *, oob: bool = False) -> str:
from shared.sx.helpers import render_to_sx, call_url
return await render_to_sx(
def _cart_header_sx(ctx: dict, *, oob: bool = False) -> str:
from shared.sx.helpers import sx_call, call_url
return sx_call(
"menu-row-sx",
id="cart-row", level=1, colour="sky",
link_href=call_url(ctx, "cart_url", "/"),
@@ -68,15 +68,15 @@ async def _cart_header_sx(ctx: dict, *, oob: bool = False) -> str:
)
async def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> str:
from shared.sx.helpers import render_to_sx, call_url
def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> str:
from shared.sx.helpers import sx_call, call_url
slug = page_post.slug if page_post else ""
title = ((page_post.title if page_post else None) or "")[:160]
label_sx = await render_to_sx("cart-page-label",
label_sx = sx_call("cart-page-label",
feature_image=page_post.feature_image if page_post else None,
title=title)
nav_sx = await render_to_sx("cart-all-carts-link", href=call_url(ctx, "cart_url", "/"))
return await render_to_sx(
nav_sx = sx_call("cart-all-carts-link", href=call_url(ctx, "cart_url", "/"))
return sx_call(
"menu-row-sx",
id="page-cart-row", level=2, colour="sky",
link_href=call_url(ctx, "cart_url", f"/{slug}/"),
@@ -102,8 +102,8 @@ async def _cart_page_full(ctx: dict, **kw: Any) -> str:
page_post = ctx.get("page_post")
env = {}
return await render_to_sx_with_env("cart-page-layout-full", env,
cart_row=SxExpr(await _cart_header_sx(ctx)),
page_cart_row=SxExpr(await _page_cart_header_sx(ctx, page_post)),
cart_row=SxExpr(_cart_header_sx(ctx)),
page_cart_row=SxExpr(_page_cart_header_sx(ctx, page_post)),
)
@@ -113,8 +113,8 @@ async def _cart_page_oob(ctx: dict, **kw: Any) -> str:
env = {}
return await render_to_sx_with_env("cart-page-layout-oob", env,
root_header_oob=SxExpr(await root_header_sx(ctx, oob=True)),
cart_row_oob=SxExpr(await _cart_header_sx(ctx, oob=True)),
page_cart_row=SxExpr(await _page_cart_header_sx(ctx, page_post)),
cart_row_oob=SxExpr(_cart_header_sx(ctx, oob=True)),
page_cart_row=SxExpr(_page_cart_header_sx(ctx, page_post)),
)

View File

@@ -7,7 +7,7 @@ from .utils import _serialize_order, _serialize_calendar_entry
async def render_orders_page(ctx, orders, page, total_pages, search, search_count, url_for_fn, qs_fn):
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, search_desktop_sx, search_mobile_sx, full_page_sx
from shared.sx.helpers import sx_call, render_to_sx_with_env, search_desktop_sx, search_mobile_sx, full_page_sx
from shared.utils import route_prefix
ctx["search"] = search
ctx["search_count"] = search_count
@@ -15,18 +15,18 @@ async def render_orders_page(ctx, orders, page, total_pages, search, search_coun
list_url = pfx + url_for_fn("orders.list_orders")
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 = await render_to_sx("orders-list-content", orders=order_dicts,
content = sx_call("orders-list-content", orders=order_dicts,
page=page, total_pages=total_pages, rows_url=list_url, detail_url_prefix=detail_url_prefix)
header_rows = await render_to_sx_with_env("cart-orders-layout-full", {},
list_url=list_url,
)
filt = await render_to_sx("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
filt = sx_call("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
return await full_page_sx(ctx, header_rows=header_rows, filter=filt,
aside=await search_desktop_sx(ctx), content=content)
async def render_orders_rows(ctx, orders, page, total_pages, url_for_fn, qs_fn):
from shared.sx.helpers import render_to_sx
def render_orders_rows(ctx, orders, page, total_pages, url_for_fn, qs_fn):
from shared.sx.helpers import sx_call
from shared.utils import route_prefix
pfx = route_prefix()
list_url = pfx + url_for_fn("orders.list_orders")
@@ -34,22 +34,22 @@ async def render_orders_rows(ctx, orders, page, total_pages, url_for_fn, qs_fn):
order_dicts = [_serialize_order(o) for o in orders]
parts = []
for od in order_dicts:
parts.append(await render_to_sx("order-row-pair", order=od, detail_url_prefix=detail_url_prefix))
parts.append(sx_call("order-row-pair", order=od, detail_url_prefix=detail_url_prefix))
next_scroll = ""
if page < total_pages:
next_url = list_url + qs_fn(page=page + 1)
next_scroll = await render_to_sx("infinite-scroll", url=next_url, page=page,
next_scroll = sx_call("infinite-scroll", url=next_url, page=page,
total_pages=total_pages, id_prefix="orders", colspan=5)
else:
next_scroll = await render_to_sx("order-end-row")
return await render_to_sx("cart-orders-rows",
next_scroll = sx_call("order-end-row")
return sx_call("cart-orders-rows",
rows=SxExpr("(<> " + " ".join(parts) + ")"),
next_scroll=SxExpr(next_scroll),
)
async def render_orders_oob(ctx, orders, page, total_pages, search, search_count, url_for_fn, qs_fn):
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, search_desktop_sx, search_mobile_sx, oob_page_sx
from shared.sx.helpers import sx_call, render_to_sx_with_env, search_desktop_sx, search_mobile_sx, oob_page_sx
from shared.utils import route_prefix
ctx["search"] = search
ctx["search_count"] = search_count
@@ -57,17 +57,17 @@ async def render_orders_oob(ctx, orders, page, total_pages, search, search_count
list_url = pfx + url_for_fn("orders.list_orders")
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 = await render_to_sx("orders-list-content", orders=order_dicts,
content = sx_call("orders-list-content", orders=order_dicts,
page=page, total_pages=total_pages, rows_url=list_url, detail_url_prefix=detail_url_prefix)
oobs = await render_to_sx_with_env("cart-orders-layout-oob", {},
list_url=list_url,
)
filt = await render_to_sx("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
filt = sx_call("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
return await oob_page_sx(oobs=oobs, filter=filt, aside=await search_desktop_sx(ctx), content=content)
async def render_order_page(ctx, order, calendar_entries, url_for_fn):
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, full_page_sx
from shared.sx.helpers import sx_call, render_to_sx_with_env, full_page_sx
from shared.utils import route_prefix
from shared.browser.app.csrf import generate_csrf_token
pfx = route_prefix()
@@ -77,8 +77,8 @@ async def render_order_page(ctx, order, calendar_entries, url_for_fn):
pay_url = pfx + url_for_fn("orders.order.order_pay", order_id=order.id)
order_data = _serialize_order(order)
cal_data = [_serialize_calendar_entry(e) for e in (calendar_entries or [])]
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,
main = sx_call("order-detail-content", order=order_data, calendar_entries=cal_data)
filt = sx_call("order-detail-filter-content", order=order_data,
list_url=list_url, recheck_url=recheck_url, pay_url=pay_url, csrf=generate_csrf_token())
header_rows = await render_to_sx_with_env("cart-order-detail-layout-full", {},
list_url=list_url, detail_url=detail_url,
@@ -88,7 +88,7 @@ async def render_order_page(ctx, order, calendar_entries, url_for_fn):
async def render_order_oob(ctx, order, calendar_entries, url_for_fn):
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, oob_page_sx
from shared.sx.helpers import sx_call, render_to_sx_with_env, oob_page_sx
from shared.utils import route_prefix
from shared.browser.app.csrf import generate_csrf_token
pfx = route_prefix()
@@ -98,8 +98,8 @@ async def render_order_oob(ctx, order, calendar_entries, url_for_fn):
pay_url = pfx + url_for_fn("orders.order.order_pay", order_id=order.id)
order_data = _serialize_order(order)
cal_data = [_serialize_calendar_entry(e) for e in (calendar_entries or [])]
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,
main = sx_call("order-detail-content", order=order_data, calendar_entries=cal_data)
filt = sx_call("order-detail-filter-content", order=order_data,
list_url=list_url, recheck_url=recheck_url, pay_url=pay_url, csrf=generate_csrf_token())
oobs = await render_to_sx_with_env("cart-order-detail-layout-oob", {},
detail_url=detail_url,
@@ -109,19 +109,19 @@ async def render_order_oob(ctx, order, calendar_entries, url_for_fn):
async def render_checkout_error_page(ctx, error=None, order=None):
from shared.sx.helpers import render_to_sx, render_to_sx_with_env, full_page_sx
from shared.sx.helpers import sx_call, render_to_sx_with_env, full_page_sx
from shared.infrastructure.urls import cart_url
err_msg = error or "Unexpected error while creating the hosted checkout session."
order_sx = await render_to_sx("checkout-error-order-id", oid=f"#{order.id}") if order else None
order_sx = sx_call("checkout-error-order-id", oid=f"#{order.id}") if order else None
hdr = await render_to_sx_with_env("layout-root-full", {})
filt = await render_to_sx("checkout-error-header")
content = await render_to_sx("checkout-error-content", msg=err_msg,
filt = sx_call("checkout-error-header")
content = sx_call("checkout-error-content", msg=err_msg,
order=SxExpr(order_sx) if order_sx else None, back_url=cart_url("/"))
return await full_page_sx(ctx, header_rows=hdr, filter=filt, content=content)
async def render_cart_payments_panel(ctx):
from shared.sx.helpers import render_to_sx
def render_cart_payments_panel(ctx):
from shared.sx.helpers import sx_call
page_config = ctx.get("page_config")
pc_data = None
if page_config:
@@ -130,4 +130,4 @@ async def render_cart_payments_panel(ctx):
"sumup_merchant_code": getattr(page_config, "sumup_merchant_code", None) or "",
"sumup_checkout_prefix": getattr(page_config, "sumup_checkout_prefix", None) or "",
}
return await render_to_sx("cart-payments-content", page_config=pc_data)
return sx_call("cart-payments-content", page_config=pc_data)

View File

@@ -85,7 +85,7 @@ def register() -> Blueprint:
entries, has_more, pending_tickets, page_info = await _load_entries(page)
from sxc.pages.renders import render_all_events_cards
sx_src = await render_all_events_cards(entries, has_more, pending_tickets, page_info, page, view)
sx_src = render_all_events_cards(entries, has_more, pending_tickets, page_info, page, view)
return sx_response(sx_src)
@bp.post("/all-tickets/adjust")
@@ -126,7 +126,7 @@ def register() -> Blueprint:
frag_params["session_id"] = ident["session_id"]
from sxc.pages.tickets import render_ticket_widget
widget_html = await render_ticket_widget(entry, qty, "/all-tickets/adjust")
widget_html = 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 ""))

View File

@@ -19,7 +19,7 @@ def register():
@require_admin
async def calendar_description_edit(calendar_slug: str, **kwargs):
from sxc.pages.renders import render_calendar_description_edit
html = await render_calendar_description_edit(g.calendar)
html = render_calendar_description_edit(g.calendar)
return sx_response(html)
@@ -35,7 +35,7 @@ def register():
await g.s.flush()
from sxc.pages.renders import render_calendar_description
html = await render_calendar_description(g.calendar, oob=True)
html = 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 sxc.pages.renders import render_calendar_description
html = await render_calendar_description(g.calendar)
html = render_calendar_description(g.calendar)
return sx_response(html)
return bp

View File

@@ -201,7 +201,7 @@ def register():
from shared.sx.page import get_template_context
from sxc.pages.calendar import _calendar_admin_main_panel_html
ctx = await get_template_context()
html = await _calendar_admin_main_panel_html(ctx)
html = _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 sxc.pages.renders import render_calendars_list_panel
ctx = await get_template_context()
html = await render_calendars_list_panel(ctx)
html = 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 = await render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
nav_oob = render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
html = html + nav_oob
return sx_response(html)

View File

@@ -259,7 +259,7 @@ def register():
}
from sxc.pages.renders import render_day_main_panel
html = await render_day_main_panel(ctx)
html = 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 sxc.pages.entries import render_entry_add_form
return sx_response(await render_entry_add_form(g.calendar, day, month, year, day_slots))
return sx_response(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 sxc.pages.entries import render_entry_add_button
return sx_response(await render_entry_add_button(g.calendar, day, month, year))
return sx_response(render_entry_add_button(g.calendar, day, month, year))

View File

@@ -112,7 +112,7 @@ def register():
# Render OOB nav
from sxc.pages.entries import render_day_entries_nav_oob
return await render_day_entries_nav_oob(visible.confirmed_entries, calendar, day_date)
return 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 sxc.pages.entries import render_post_nav_entries_oob
nav_oob = await render_post_nav_entries_oob(associated_entries, calendars, post)
nav_oob = 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 sxc.pages.entries import render_entry_edit_form
return sx_response(await render_entry_edit_form(g.entry, g.calendar, day, month, year, day_slots))
return sx_response(render_entry_edit_form(g.entry, g.calendar, day, month, year, day_slots))
@bp.put("/")
@require_admin
@@ -423,7 +423,7 @@ def register():
from sxc.pages.entries import _entry_main_panel_html
tctx = await get_template_context()
html = await _entry_main_panel_html(tctx)
html = _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 sxc.pages.entries import render_entry_optioned
html = await render_entry_optioned(g.entry, g.calendar, day, month, year)
html = 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 sxc.pages.entries import render_entry_optioned
html = await render_entry_optioned(g.entry, g.calendar, day, month, year)
html = 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 sxc.pages.entries import render_entry_optioned
html = await render_entry_optioned(g.entry, g.calendar, day, month, year)
html = 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 sxc.pages.entries import render_entry_tickets_config
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"))
html = 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 sxc.pages.entries import render_post_search_results
return sx_response(await render_post_search_results(
return sx_response(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 sxc.pages.entries import render_entry_posts_panel, render_entry_posts_nav_oob
va = request.view_args or {}
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)
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)
return sx_response(html + nav_oob)
@bp.delete("/posts/<int:post_id>/")
@@ -616,8 +616,8 @@ def register():
# Return updated posts list + OOB nav update
from sxc.pages.entries import render_entry_posts_panel, render_entry_posts_nav_oob
va = request.view_args or {}
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)
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)
return sx_response(html + nav_oob)
return bp

View File

@@ -69,7 +69,7 @@ def register():
from shared.sx.page import get_template_context
from sxc.pages.renders import render_calendars_list_panel
ctx = await get_template_context()
html = await render_calendars_list_panel(ctx)
html = 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 = await render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
nav_oob = render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
html = html + nav_oob
return sx_response(html)

View File

@@ -44,7 +44,7 @@ def register():
from shared.sx.page import get_template_context
from sxc.pages.renders import render_markets_list_panel
ctx = await get_template_context()
return sx_response(await render_markets_list_panel(ctx))
return sx_response(render_markets_list_panel(ctx))
@bp.delete("/<market_slug>/")
@require_admin
@@ -57,6 +57,6 @@ def register():
from shared.sx.page import get_template_context
from sxc.pages.renders import render_markets_list_panel
ctx = await get_template_context()
return sx_response(await render_markets_list_panel(ctx))
return sx_response(render_markets_list_panel(ctx))
return bp

View File

@@ -66,7 +66,7 @@ def register() -> Blueprint:
entries, has_more, pending_tickets = await _load_entries(post["id"], page)
from sxc.pages.renders import render_page_summary_cards
sx_src = await render_page_summary_cards(entries, has_more, pending_tickets, {}, page, view, post)
sx_src = render_page_summary_cards(entries, has_more, pending_tickets, {}, page, view, post)
return sx_response(sx_src)
@bp.post("/tickets/adjust")
@@ -107,7 +107,7 @@ def register() -> Blueprint:
frag_params["session_id"] = ident["session_id"]
from sxc.pages.tickets import render_ticket_widget
widget_html = await render_ticket_widget(entry, qty, f"/{g.post_slug}/tickets/adjust")
widget_html = 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 ""))

View File

@@ -36,7 +36,7 @@ def register():
if not slot:
return await make_response("Not found", 404)
from sxc.pages.slots import render_slot_edit_form
return sx_response(await render_slot_edit_form(slot, g.calendar))
return sx_response(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 sxc.pages.slots import render_slot_main_panel
return sx_response(await render_slot_main_panel(slot, g.calendar))
return sx_response(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 sxc.pages.slots import render_slots_table
return sx_response(await render_slots_table(slots, g.calendar))
return sx_response(render_slots_table(slots, g.calendar))
@bp.put("/")
@require_admin
@@ -136,7 +136,7 @@ def register():
), 422
from sxc.pages.slots import render_slot_main_panel
return sx_response(await render_slot_main_panel(slot, g.calendar, oob=True))
return sx_response(render_slot_main_panel(slot, g.calendar, oob=True))

View File

@@ -111,19 +111,19 @@ def register():
# Success → re-render the slots table
slots = await svc_list_slots(g.s, g.calendar.id)
from sxc.pages.slots import render_slots_table
return sx_response(await render_slots_table(slots, g.calendar))
return sx_response(render_slots_table(slots, g.calendar))
@bp.get("/add")
@require_admin
async def add_form(**kwargs):
from sxc.pages.slots import render_slot_add_form
return sx_response(await render_slot_add_form(g.calendar))
return sx_response(render_slot_add_form(g.calendar))
@bp.get("/add-button")
@require_admin
async def add_button(**kwargs):
from sxc.pages.slots import render_slot_add_button
return sx_response(await render_slot_add_button(g.calendar))
return sx_response(render_slot_add_button(g.calendar))
return bp

View File

@@ -54,7 +54,7 @@ def register() -> Blueprint:
tickets = await get_tickets_for_entry(g.s, entry_id)
from sxc.pages.tickets import render_entry_tickets_admin
html = await render_entry_tickets_admin(entry, tickets)
html = 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 sxc.pages.tickets import render_lookup_result
if not ticket:
return sx_response(await render_lookup_result(None, "Ticket not found"))
return sx_response(render_lookup_result(None, "Ticket not found"))
return sx_response(await render_lookup_result(ticket, None))
return sx_response(render_lookup_result(ticket, None))
@bp.post("/<code>/checkin/")
@require_admin
@@ -84,9 +84,9 @@ def register() -> Blueprint:
from sxc.pages.tickets import render_checkin_result
if not success:
return sx_response(await render_checkin_result(False, error, None))
return sx_response(render_checkin_result(False, error, None))
ticket = await get_ticket_by_code(g.s, code)
return sx_response(await render_checkin_result(True, None, ticket))
return sx_response(render_checkin_result(True, None, ticket))
return bp

View File

@@ -32,7 +32,7 @@ def register():
from sxc.pages.tickets import render_ticket_type_edit_form
va = request.view_args or {}
return sx_response(await render_ticket_type_edit_form(
return sx_response(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 sxc.pages.tickets import render_ticket_type_main_panel
va = request.view_args or {}
return sx_response(await render_ticket_type_main_panel(
return sx_response(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 sxc.pages.tickets import render_ticket_type_main_panel
va = request.view_args or {}
return sx_response(await render_ticket_type_main_panel(
return sx_response(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 sxc.pages.tickets import render_ticket_types_table
va = request.view_args or {}
return sx_response(await render_ticket_types_table(
return sx_response(render_ticket_types_table(
ticket_types, g.entry, g.calendar,
va.get("day"), va.get("month"), va.get("year"),
))

View File

@@ -95,7 +95,7 @@ def register():
ticket_types = await svc_list_ticket_types(g.s, g.entry.id)
from sxc.pages.tickets import render_ticket_types_table
va = request.view_args or {}
return sx_response(await render_ticket_types_table(
return sx_response(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 sxc.pages.tickets import render_ticket_type_add_form
va = request.view_args or {}
return sx_response(await render_ticket_type_add_form(
return sx_response(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 sxc.pages.tickets import render_ticket_type_add_button
va = request.view_args or {}
return sx_response(await render_ticket_type_add_button(
return sx_response(render_ticket_type_add_button(
g.entry, g.calendar,
va.get("day"), va.get("month"), va.get("year"),
))

View File

@@ -127,7 +127,7 @@ def register() -> Blueprint:
cart_count = summary.count + summary.calendar_count + summary.ticket_count
from sxc.pages.tickets import render_buy_result
return sx_response(await render_buy_result(entry, created, remaining, cart_count))
return sx_response(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 sxc.pages.tickets import render_adjust_response
return sx_response(await render_adjust_response(
return sx_response(render_adjust_response(
entry, ticket_remaining, ticket_sold_count,
user_ticket_count, user_ticket_counts_by_type, cart_count,
))

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
from shared.sx.helpers import (
call_url, render_to_sx, render_to_sx_with_env,
call_url, sx_call, render_to_sx_with_env,
post_admin_header_sx,
)
from shared.sx.parser import SxExpr
@@ -23,7 +23,7 @@ async def _post_header_sx(ctx: dict, *, oob: bool = False) -> str:
return await post_header_sx(ctx, oob=oob)
async def _post_nav_sx(ctx: dict) -> str:
def _post_nav_sx(ctx: dict) -> str:
"""Post desktop nav: calendar links + container nav (markets, etc.)."""
from quart import url_for, g
@@ -37,7 +37,7 @@ async 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(await render_to_sx("nav-link", href=href, icon="fa fa-calendar",
parts.append(sx_call("nav-link", href=href, icon="fa fa-calendar",
label=cal_name, select_colours=select_colours,
is_selected=is_sel))
# Container nav fragments (markets, etc.)
@@ -72,13 +72,13 @@ async def _post_nav_sx(ctx: dict) -> str:
# Calendars header
# ---------------------------------------------------------------------------
async def _calendars_header_sx(ctx: dict, *, oob: bool = False) -> str:
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 await render_to_sx("menu-row-sx", id="calendars-row", level=3,
return sx_call("menu-row-sx", id="calendars-row", level=3,
link_href=link_href,
link_label_content=SxExpr(await render_to_sx("events-calendars-label")),
link_label_content=SxExpr(sx_call("events-calendars-label")),
child_id="calendars-header-child", oob=oob)
@@ -86,7 +86,7 @@ async def _calendars_header_sx(ctx: dict, *, oob: bool = False) -> str:
# Calendar header
# ---------------------------------------------------------------------------
async def _calendar_header_sx(ctx: dict, *, oob: bool = False) -> str:
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")
@@ -97,18 +97,18 @@ async 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 = await render_to_sx("events-calendar-label",
label_html = sx_call("events-calendar-label",
name=cal_name, description=cal_desc)
# Desktop nav: slots + admin
nav_html = await _calendar_nav_sx(ctx)
nav_html = _calendar_nav_sx(ctx)
return await render_to_sx("menu-row-sx", id="calendar-row", level=3,
return sx_call("menu-row-sx", id="calendar-row", level=3,
link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="calendar-header-child", oob=oob)
async def _calendar_nav_sx(ctx: dict) -> str:
def _calendar_nav_sx(ctx: dict) -> str:
"""Calendar desktop nav: Slots + admin link."""
from quart import url_for
calendar = ctx.get("calendar")
@@ -121,11 +121,11 @@ async def _calendar_nav_sx(ctx: dict) -> str:
parts = []
slots_href = url_for("defpage_slots_listing", calendar_slug=cal_slug)
parts.append(await render_to_sx("nav-link", href=slots_href, icon="fa fa-clock",
parts.append(sx_call("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(await render_to_sx("nav-link", href=admin_href, icon="fa fa-cog",
parts.append(sx_call("nav-link", href=admin_href, icon="fa fa-cog",
select_colours=select_colours))
return "(<> " + " ".join(parts) + ")" if parts else ""
@@ -134,7 +134,7 @@ async def _calendar_nav_sx(ctx: dict) -> str:
# Day header
# ---------------------------------------------------------------------------
async def _day_header_sx(ctx: dict, *, oob: bool = False) -> str:
def _day_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build day detail header row."""
from quart import url_for
calendar = ctx.get("calendar")
@@ -152,17 +152,17 @@ async def _day_header_sx(ctx: dict, *, oob: bool = False) -> str:
month=day_date.month,
day=day_date.day,
)
label_html = await render_to_sx("events-day-label",
label_html = sx_call("events-day-label",
date_str=day_date.strftime("%A %d %B %Y"))
nav_html = await _day_nav_sx(ctx)
nav_html = _day_nav_sx(ctx)
return await render_to_sx("menu-row-sx", id="day-row", level=4,
return sx_call("menu-row-sx", id="day-row", level=4,
link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="day-header-child", oob=oob)
async def _day_nav_sx(ctx: dict) -> str:
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")
@@ -189,11 +189,11 @@ async 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(await render_to_sx("events-day-entry-link",
entry_links.append(sx_call("events-day-entry-link",
href=href, name=entry.name,
time_str=f"{start}{end}"))
inner = "".join(entry_links)
parts.append(await render_to_sx("events-day-entries-nav", inner=SxExpr(inner)))
parts.append(sx_call("events-day-entries-nav", inner=SxExpr(inner)))
if is_admin and day_date:
admin_href = url_for(
@@ -203,7 +203,7 @@ async def _day_nav_sx(ctx: dict) -> str:
month=day_date.month,
day=day_date.day,
)
parts.append(await render_to_sx("nav-link", href=admin_href, icon="fa fa-cog"))
parts.append(sx_call("nav-link", href=admin_href, icon="fa fa-cog"))
return "".join(parts)
@@ -211,7 +211,7 @@ async def _day_nav_sx(ctx: dict) -> str:
# Day admin header
# ---------------------------------------------------------------------------
async def _day_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
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")
@@ -229,7 +229,7 @@ async def _day_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
month=day_date.month,
day=day_date.day,
)
return await render_to_sx("menu-row-sx", id="day-admin-row", level=5,
return sx_call("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)
@@ -238,7 +238,7 @@ async def _day_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
# Calendar admin header
# ---------------------------------------------------------------------------
async def _calendar_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
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")
@@ -252,11 +252,11 @@ async 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(await render_to_sx("nav-link", href=href, label=label,
nav_parts.append(sx_call("nav-link", href=href, label=label,
select_colours=select_colours))
nav_html = "".join(nav_parts)
return await render_to_sx("menu-row-sx", id="calendar-admin-row", level=4,
return sx_call("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)
@@ -265,13 +265,13 @@ async def _calendar_admin_header_sx(ctx: dict, *, oob: bool = False) -> str:
# Markets header
# ---------------------------------------------------------------------------
async def _markets_header_sx(ctx: dict, *, oob: bool = False) -> str:
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 await render_to_sx("menu-row-sx", id="markets-row", level=3,
return sx_call("menu-row-sx", id="markets-row", level=3,
link_href=link_href,
link_label_content=SxExpr(await render_to_sx("events-markets-label")),
link_label_content=SxExpr(sx_call("events-markets-label")),
child_id="markets-header-child", oob=oob)
@@ -279,7 +279,7 @@ async def _markets_header_sx(ctx: dict, *, oob: bool = False) -> str:
# Calendars main panel
# ---------------------------------------------------------------------------
async def _calendars_main_panel_sx(ctx: dict) -> str:
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 {}
@@ -294,18 +294,18 @@ async def _calendars_main_panel_sx(ctx: dict) -> str:
form_html = ""
if can_create:
create_url = url_for("calendars.create_calendar")
form_html = await render_to_sx("crud-create-form",
form_html = sx_call("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 = await _calendars_list_sx(ctx, calendars)
return await render_to_sx("crud-panel",
list_html = _calendars_list_sx(ctx, calendars)
return sx_call("crud-panel",
form=SxExpr(form_html), list=SxExpr(list_html),
list_id="calendars-list")
async def _calendars_list_sx(ctx: dict, calendars: list) -> str:
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
@@ -314,7 +314,7 @@ async def _calendars_list_sx(ctx: dict, calendars: list) -> str:
prefix = route_prefix()
if not calendars:
return await render_to_sx("empty-state", message="No calendars yet. Create one above.",
return sx_call("empty-state", message="No calendars yet. Create one above.",
cls="text-gray-500 mt-4")
parts = []
@@ -324,7 +324,7 @@ async 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(await render_to_sx("crud-item",
parts.append(sx_call("crud-item",
href=href, name=cal_name, slug=cal_slug,
del_url=del_url, csrf_hdr=csrf_hdr,
list_id="calendars-list",
@@ -337,7 +337,7 @@ async def _calendars_list_sx(ctx: dict, calendars: list) -> str:
# Calendar month grid
# ---------------------------------------------------------------------------
async def _calendar_main_panel_html(ctx: dict) -> str:
def _calendar_main_panel_html(ctx: dict) -> str:
"""Render the calendar month grid."""
from quart import url_for
from quart import session as qsession
@@ -375,10 +375,10 @@ async def _calendar_main_panel_html(ctx: dict) -> str:
("\u2039", prev_month_year, prev_month),
]:
href = nav_link(yr, mn)
nav_arrows.append(await render_to_sx("events-calendar-nav-arrow",
nav_arrows.append(sx_call("events-calendar-nav-arrow",
pill_cls=pill_cls, href=href, label=label))
nav_arrows.append(await render_to_sx("events-calendar-month-label",
nav_arrows.append(sx_call("events-calendar-month-label",
month_name=month_name, year=str(year)))
for label, yr, mn in [
@@ -386,13 +386,13 @@ async def _calendar_main_panel_html(ctx: dict) -> str:
("\u00bb", next_year, month),
]:
href = nav_link(yr, mn)
nav_arrows.append(await render_to_sx("events-calendar-nav-arrow",
nav_arrows.append(sx_call("events-calendar-nav-arrow",
pill_cls=pill_cls, href=href, label=label))
# Weekday headers
wd_parts = []
for wd in weekday_names:
wd_parts.append(await render_to_sx("events-calendar-weekday", name=wd))
wd_parts.append(sx_call("events-calendar-weekday", name=wd))
wd_html = "".join(wd_parts)
# Day cells
@@ -423,9 +423,9 @@ async 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 = await render_to_sx("events-calendar-day-short",
day_short_html = sx_call("events-calendar-day-short",
day_str=day_date.strftime("%a"))
day_num_html = await render_to_sx("events-calendar-day-num",
day_num_html = sx_call("events-calendar-day-num",
pill_cls=pill_cls, href=day_href,
num=str(day_date.day))
@@ -443,12 +443,12 @@ async 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(await render_to_sx("events-calendar-entry-badge",
entry_badges.append(sx_call("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(await render_to_sx("events-calendar-cell",
cells.append(sx_call("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))
@@ -456,7 +456,7 @@ async def _calendar_main_panel_html(ctx: dict) -> str:
cells_html = "(<> " + "".join(cells) + ")"
arrows_html = "(<> " + "".join(nav_arrows) + ")"
wd_html = "(<> " + wd_html + ")"
return await render_to_sx("events-calendar-grid",
return sx_call("events-calendar-grid",
arrows=SxExpr(arrows_html), weekdays=SxExpr(wd_html),
cells=SxExpr(cells_html))
@@ -465,7 +465,7 @@ async def _calendar_main_panel_html(ctx: dict) -> str:
# Day main panel
# ---------------------------------------------------------------------------
async def _day_main_panel_html(ctx: dict) -> str:
def _day_main_panel_html(ctx: dict) -> str:
"""Render the day entries table + add button."""
from quart import url_for
@@ -488,10 +488,10 @@ async def _day_main_panel_html(ctx: dict) -> str:
if day_entries:
row_parts = []
for entry in day_entries:
row_parts.append(await _day_row_html(ctx, entry))
row_parts.append(_day_row_html(ctx, entry))
rows_html = "".join(row_parts)
else:
rows_html = await render_to_sx("events-day-empty-row")
rows_html = sx_call("events-day-empty-row")
add_url = url_for(
"calendar.day.calendar_entries.add_form",
@@ -499,12 +499,12 @@ async def _day_main_panel_html(ctx: dict) -> str:
day=day, month=month, year=year,
)
return await render_to_sx("events-day-table",
return sx_call("events-day-table",
list_container=list_container, rows=SxExpr(rows_html),
pre_action=pre_action, add_url=add_url)
async def _day_row_html(ctx: dict, entry) -> str:
def _day_row_html(ctx: dict, entry) -> str:
"""Render a single day table row."""
from quart import url_for
calendar = ctx.get("calendar")
@@ -523,7 +523,7 @@ async def _day_row_html(ctx: dict, entry) -> str:
)
# Name
name_html = await render_to_sx("events-day-row-name",
name_html = sx_call("events-day-row-name",
href=entry_href, pill_cls=pill_cls, name=entry.name)
# Slot/Time
@@ -532,38 +532,38 @@ async 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 = await render_to_sx("events-day-row-slot",
slot_html = sx_call("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 = await render_to_sx("events-day-row-time", start=start, end=end)
slot_html = sx_call("events-day-row-time", start=start, end=end)
# State
state = getattr(entry, "state", "pending") or "pending"
state_badge = await _entry_state_badge_html(state)
state_td = await render_to_sx("events-day-row-state",
state_badge = _entry_state_badge_html(state)
state_td = sx_call("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 = await render_to_sx("events-day-row-cost", cost_str=cost_str)
cost_td = sx_call("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 = await render_to_sx("events-day-row-tickets",
tickets_td = sx_call("events-day-row-tickets",
price_str=f"\u00a3{tp:.2f}", count_str=tc_str)
else:
tickets_td = await render_to_sx("events-day-row-no-tickets")
tickets_td = sx_call("events-day-row-no-tickets")
actions_td = await render_to_sx("events-day-row-actions")
actions_td = sx_call("events-day-row-actions")
return await render_to_sx("events-day-row",
return sx_call("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))
@@ -573,16 +573,16 @@ async def _day_row_html(ctx: dict, entry) -> str:
# Day admin main panel
# ---------------------------------------------------------------------------
async def _day_admin_main_panel_html(ctx: dict) -> str:
def _day_admin_main_panel_html(ctx: dict) -> str:
"""Render day admin panel (placeholder nav)."""
return await render_to_sx("events-day-admin-panel")
return sx_call("events-day-admin-panel")
# ---------------------------------------------------------------------------
# Calendar admin main panel
# ---------------------------------------------------------------------------
async def _calendar_admin_main_panel_html(ctx: dict) -> str:
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")
@@ -595,17 +595,17 @@ async 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 = await _calendar_description_display_html(calendar, desc_edit_url)
description_html = _calendar_description_display_html(calendar, desc_edit_url)
return await render_to_sx("events-calendar-admin-panel",
return sx_call("events-calendar-admin-panel",
description_content=SxExpr(description_html), csrf=csrf,
description=desc)
async def _calendar_description_display_html(calendar, edit_url: str) -> str:
def _calendar_description_display_html(calendar, edit_url: str) -> str:
"""Render calendar description display with edit button."""
desc = getattr(calendar, "description", "") or ""
return await render_to_sx("events-calendar-description-display",
return sx_call("events-calendar-description-display",
description=desc, edit_url=edit_url)
@@ -613,7 +613,7 @@ async def _calendar_description_display_html(calendar, edit_url: str) -> str:
# Markets main panel
# ---------------------------------------------------------------------------
async def _markets_main_panel_html(ctx: dict) -> str:
def _markets_main_panel_html(ctx: dict) -> str:
"""Render markets list + create form panel."""
from quart import url_for
rights = ctx.get("rights") or {}
@@ -627,18 +627,18 @@ async def _markets_main_panel_html(ctx: dict) -> str:
form_html = ""
if can_create:
create_url = url_for("markets.create_market")
form_html = await render_to_sx("crud-create-form",
form_html = sx_call("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 = await _markets_list_html(ctx, markets)
return await render_to_sx("crud-panel",
list_html = _markets_list_html(ctx, markets)
return sx_call("crud-panel",
form=SxExpr(form_html), list=SxExpr(list_html),
list_id="markets-list")
async def _markets_list_html(ctx: dict, markets: list) -> str:
def _markets_list_html(ctx: dict, markets: list) -> str:
"""Render markets list items."""
from quart import url_for
csrf_token = ctx.get("csrf_token")
@@ -647,7 +647,7 @@ async def _markets_list_html(ctx: dict, markets: list) -> str:
slug = post.get("slug", "")
if not markets:
return await render_to_sx("empty-state", message="No markets yet. Create one above.",
return sx_call("empty-state", message="No markets yet. Create one above.",
cls="text-gray-500 mt-4")
parts = []
@@ -657,7 +657,7 @@ async 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(await render_to_sx("crud-item",
parts.append(sx_call("crud-item",
href=market_href, name=m_name,
slug=m_slug, del_url=del_url,
csrf_hdr=csrf_hdr,

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from markupsafe import escape
from shared.sx.helpers import render_to_sx, render_to_sx_with_env
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
from .utils import (
@@ -16,7 +16,7 @@ from .utils import (
# All events / page summary entry cards
# ---------------------------------------------------------------------------
async def _entry_card_html(entry, page_info: dict, pending_tickets: dict,
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."""
@@ -35,36 +35,36 @@ async def _entry_card_html(entry, page_info: dict, pending_tickets: dict,
# Title (linked or plain)
if entry_href:
title_html = await render_to_sx("events-entry-title-linked",
title_html = sx_call("events-entry-title-linked",
href=entry_href, name=entry.name)
else:
title_html = await render_to_sx("events-entry-title-plain", name=entry.name)
title_html = sx_call("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 += await render_to_sx("events-entry-page-badge",
badges_html += sx_call("events-entry-page-badge",
href=page_href, title=page_title)
cal_name = getattr(entry, "calendar_name", "")
if cal_name:
badges_html += await render_to_sx("events-entry-cal-badge", name=cal_name)
badges_html += sx_call("events-entry-cal-badge", name=cal_name)
# Time line
time_parts = ""
if day_href and not is_page_scoped:
time_parts += await render_to_sx("events-entry-time-linked",
time_parts += sx_call("events-entry-time-linked",
href=day_href,
date_str=entry.start_at.strftime("%a %-d %b"))
elif not is_page_scoped:
time_parts += await render_to_sx("events-entry-time-plain",
time_parts += sx_call("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 = await render_to_sx("events-entry-cost",
cost_html = sx_call("events-entry-cost",
cost=f"\u00a3{cost:.2f}") if cost else ""
# Ticket widget
@@ -72,16 +72,16 @@ async 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 = await render_to_sx("events-entry-widget-wrapper",
widget=SxExpr(await _ticket_widget_html(entry, qty, ticket_url, ctx={})))
widget_html = sx_call("events-entry-widget-wrapper",
widget=SxExpr(_ticket_widget_html(entry, qty, ticket_url, ctx={})))
return await render_to_sx("events-entry-card",
return sx_call("events-entry-card",
title=SxExpr(title_html), badges=SxExpr(badges_html),
time_parts=SxExpr(time_parts), cost=SxExpr(cost_html),
widget=SxExpr(widget_html))
async def _entry_card_tile_html(entry, page_info: dict, pending_tickets: dict,
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."""
@@ -100,25 +100,25 @@ async def _entry_card_tile_html(entry, page_info: dict, pending_tickets: dict,
# Title
if entry_href:
title_html = await render_to_sx("events-entry-title-tile-linked",
title_html = sx_call("events-entry-title-tile-linked",
href=entry_href, name=entry.name)
else:
title_html = await render_to_sx("events-entry-title-tile-plain", name=entry.name)
title_html = sx_call("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 += await render_to_sx("events-entry-page-badge",
badges_html += sx_call("events-entry-page-badge",
href=page_href, title=page_title)
cal_name = getattr(entry, "calendar_name", "")
if cal_name:
badges_html += await render_to_sx("events-entry-cal-badge", name=cal_name)
badges_html += sx_call("events-entry-cal-badge", name=cal_name)
# Time
time_html = ""
if day_href:
time_html += (await render_to_sx("events-entry-time-linked",
time_html += (sx_call("events-entry-time-linked",
href=day_href,
date_str=entry.start_at.strftime("%a %-d %b"))).replace(" &middot; ", "")
else:
@@ -128,7 +128,7 @@ async 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 = await render_to_sx("events-entry-cost",
cost_html = sx_call("events-entry-cost",
cost=f"\u00a3{cost:.2f}") if cost else ""
# Ticket widget
@@ -136,16 +136,16 @@ async 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 = await render_to_sx("events-entry-tile-widget-wrapper",
widget=SxExpr(await _ticket_widget_html(entry, qty, ticket_url, ctx={})))
widget_html = sx_call("events-entry-tile-widget-wrapper",
widget=SxExpr(_ticket_widget_html(entry, qty, ticket_url, ctx={})))
return await render_to_sx("events-entry-card-tile",
return sx_call("events-entry-card-tile",
title=SxExpr(title_html), badges=SxExpr(badges_html),
time=SxExpr(time_html), cost=SxExpr(cost_html),
widget=SxExpr(widget_html))
async def _entry_cards_html(entries, page_info, pending_tickets, ticket_url,
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."""
@@ -153,23 +153,23 @@ async def _entry_cards_html(entries, page_info, pending_tickets, ticket_url,
last_date = None
for entry in entries:
if view == "tile":
parts.append(await _entry_card_tile_html(
parts.append(_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(await render_to_sx("events-date-separator",
parts.append(sx_call("events-date-separator",
date_str=entry_date))
last_date = entry_date
parts.append(await _entry_card_html(
parts.append(_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(await render_to_sx("sentinel-simple",
parts.append(sx_call("sentinel-simple",
id=f"sentinel-{page}", next_url=next_url))
return "".join(parts)
@@ -178,27 +178,27 @@ async def _entry_cards_html(entries, page_info, pending_tickets, ticket_url,
# All events / page summary main panels
# ---------------------------------------------------------------------------
async def _events_main_panel_html(ctx: dict, entries, has_more, pending_tickets, page_info,
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 = await _view_toggle_html(ctx, view)
toggle = _view_toggle_html(ctx, view)
if entries:
cards = await _entry_cards_html(
cards = _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 = await render_to_sx("events-grid", grid_cls=grid_cls, cards=SxExpr(cards))
body = sx_call("events-grid", grid_cls=grid_cls, cards=SxExpr(cards))
else:
body = await render_to_sx("empty-state", icon="fa fa-calendar-xmark",
body = sx_call("empty-state", icon="fa fa-calendar-xmark",
message="No upcoming events",
cls="px-3 py-12 text-center text-stone-400")
return await render_to_sx("events-main-panel-body",
return sx_call("events-main-panel-body",
toggle=SxExpr(toggle), body=SxExpr(body))
@@ -206,7 +206,7 @@ async def _events_main_panel_html(ctx: dict, entries, has_more, pending_tickets,
# Entry main panel
# ---------------------------------------------------------------------------
async def _entry_main_panel_html(ctx: dict) -> str:
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
@@ -228,63 +228,63 @@ async def _entry_main_panel_html(ctx: dict) -> str:
eid = entry.id
state = getattr(entry, "state", "pending") or "pending"
async def _field(label, content_html):
return await render_to_sx("events-entry-field", label=label, content=SxExpr(content_html))
def _field(label, content_html):
return sx_call("events-entry-field", label=label, content=SxExpr(content_html))
# Name
name_html = await _field("Name", await render_to_sx("events-entry-name-field", name=entry.name))
name_html = _field("Name", sx_call("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 = await render_to_sx("events-entry-slot-assigned",
slot_inner = sx_call("events-entry-slot-assigned",
slot_name=slot.name, flex_label=flex_label)
else:
slot_inner = await render_to_sx("events-entry-slot-none")
slot_html = await _field("Slot", slot_inner)
slot_inner = sx_call("events-entry-slot-none")
slot_html = _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 = await _field("Time Period", await render_to_sx("events-entry-time-field",
time_html = _field("Time Period", sx_call("events-entry-time-field",
time_str=start_str + end_str))
# State
state_html = await _field("State", await render_to_sx("events-entry-state-field",
state_html = _field("State", sx_call("events-entry-state-field",
entry_id=str(eid),
badge=SxExpr(await _entry_state_badge_html(state))))
badge=SxExpr(_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 = await _field("Cost", await render_to_sx("events-entry-cost-field",
cost_html = _field("Cost", sx_call("events-entry-cost-field",
cost=f"\u00a3{cost_str}"))
# Ticket Configuration (admin)
tickets_html = await _field("Tickets", await render_to_sx("events-entry-tickets-field",
tickets_html = _field("Tickets", sx_call("events-entry-tickets-field",
entry_id=str(eid),
tickets_config=SxExpr(await render_entry_tickets_config(entry, calendar, day, month, year))))
tickets_config=SxExpr(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 = await render_buy_form(
buy_html = 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 = await _field("Date", await render_to_sx("events-entry-date-field", date_str=date_str))
date_html = _field("Date", sx_call("events-entry-date-field", date_str=date_str))
# Associated Posts
entry_posts = ctx.get("entry_posts") or []
posts_html = await _field("Associated Posts", await render_to_sx("events-entry-posts-field",
posts_html = _field("Associated Posts", sx_call("events-entry-posts-field",
entry_id=str(eid),
posts_panel=SxExpr(await render_entry_posts_panel(entry_posts, entry, calendar, day, month, year))))
posts_panel=SxExpr(render_entry_posts_panel(entry_posts, entry, calendar, day, month, year))))
# Options and Edit Button
edit_url = url_for(
@@ -293,14 +293,14 @@ async def _entry_main_panel_html(ctx: dict) -> str:
day=day, month=month, year=year,
)
return await render_to_sx("events-entry-panel",
return sx_call("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(await _entry_options_html(entry, calendar, day, month, year)),
options=SxExpr(_entry_options_html(entry, calendar, day, month, year)),
pre_action=pre_action, edit_url=edit_url)
@@ -308,7 +308,7 @@ async def _entry_main_panel_html(ctx: dict) -> str:
# Entry header row
# ---------------------------------------------------------------------------
async def _entry_header_html(ctx: dict, *, oob: bool = False) -> str:
def _entry_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build entry detail header row."""
from quart import url_for
@@ -329,19 +329,19 @@ async def _entry_header_html(ctx: dict, *, oob: bool = False) -> str:
year=year, month=month, day=day,
entry_id=entry.id,
)
label_html = await render_to_sx("events-entry-label",
label_html = sx_call("events-entry-label",
entry_id=str(entry.id),
title=SxExpr(await _entry_title_html(entry)),
times=SxExpr(await _entry_times_html(entry)))
title=SxExpr(_entry_title_html(entry)),
times=SxExpr(_entry_times_html(entry)))
nav_html = await _entry_nav_html(ctx)
nav_html = _entry_nav_html(ctx)
return await render_to_sx("menu-row-sx", id="entry-row", level=5,
return sx_call("menu-row-sx", id="entry-row", level=5,
link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="entry-header-child", oob=oob)
async def _entry_times_html(entry) -> str:
def _entry_times_html(entry) -> str:
"""Render entry times label."""
start = entry.start_at
end = entry.end_at
@@ -349,14 +349,14 @@ async 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 await render_to_sx("events-entry-times", time_str=start_str + end_str)
return sx_call("events-entry-times", time_str=start_str + end_str)
# ---------------------------------------------------------------------------
# Entry nav (desktop + admin link)
# ---------------------------------------------------------------------------
async def _entry_nav_html(ctx: dict) -> str:
def _entry_nav_html(ctx: dict) -> str:
"""Entry desktop nav: associated posts scrolling menu + admin link."""
from quart import url_for
@@ -387,12 +387,12 @@ async 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 = await render_to_sx("events-post-img", src=feat, alt=title)
img_html = sx_call("events-post-img", src=feat, alt=title)
else:
img_html = await render_to_sx("events-post-img-placeholder")
post_links += await render_to_sx("events-entry-nav-post-link",
img_html = sx_call("events-post-img-placeholder")
post_links += sx_call("events-entry-nav-post-link",
href=href, img=SxExpr(img_html), title=title)
parts.append((await render_to_sx("events-entry-posts-nav-oob",
parts.append((sx_call("events-entry-posts-nav-oob",
items=SxExpr(post_links))).replace(' :hx-swap-oob "true"', ''))
# Admin link
@@ -403,7 +403,7 @@ async def _entry_nav_html(ctx: dict) -> str:
day=day, month=month, year=year,
entry_id=entry.id,
)
parts.append(await render_to_sx("events-entry-admin-link", href=admin_url))
parts.append(sx_call("events-entry-admin-link", href=admin_url))
return "".join(parts)
@@ -412,26 +412,26 @@ async def _entry_nav_html(ctx: dict) -> str:
# Entry optioned (confirm/decline/provisional response)
# ---------------------------------------------------------------------------
async def render_entry_optioned(entry, calendar, day, month, year) -> str:
def render_entry_optioned(entry, calendar, day, month, year) -> str:
"""Render entry options buttons + OOB title & state swaps."""
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")
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")
return options + await render_to_sx("events-entry-optioned-oob",
return options + sx_call("events-entry-optioned-oob",
entry_id=str(entry.id),
title=SxExpr(title), state=SxExpr(state))
async def _entry_title_html(entry) -> str:
def _entry_title_html(entry) -> str:
"""Render entry title (icon + name + state badge)."""
state = getattr(entry, "state", "pending") or "pending"
return await render_to_sx("events-entry-title",
return sx_call("events-entry-title",
name=entry.name,
badge=SxExpr(await _entry_state_badge_html(state)))
badge=SxExpr(_entry_state_badge_html(state)))
async def _entry_options_html(entry, calendar, day, month, year) -> str:
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
@@ -445,13 +445,13 @@ async def _entry_options_html(entry, calendar, day, month, year) -> str:
state = getattr(entry, "state", "pending") or "pending"
target = f"#calendar_entry_options_{eid}"
async def _make_button(action_name, label, confirm_title, confirm_text, *, trigger_type="submit"):
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 await render_to_sx("events-entry-option-button",
return sx_call("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,
@@ -459,22 +459,22 @@ async def _entry_options_html(entry, calendar, day, month, year) -> str:
buttons_html = ""
if state == "provisional":
buttons_html += await _make_button(
buttons_html += _make_button(
"confirm_entry", "confirm",
"Confirm entry?", "Are you sure you want to confirm this entry?",
)
buttons_html += await _make_button(
buttons_html += _make_button(
"decline_entry", "decline",
"Decline entry?", "Are you sure you want to decline this entry?",
)
elif state == "confirmed":
buttons_html += await _make_button(
buttons_html += _make_button(
"provisional_entry", "provisional",
"Provisional entry?", "Are you sure you want to provisional this entry?",
trigger_type="button",
)
return await render_to_sx("events-entry-options",
return sx_call("events-entry-options",
entry_id=str(eid), buttons=SxExpr(buttons_html))
@@ -482,7 +482,7 @@ async def _entry_options_html(entry, calendar, day, month, year) -> str:
# Entry tickets config (display + form)
# ---------------------------------------------------------------------------
async def render_entry_tickets_config(entry, calendar, day, month, year) -> str:
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
@@ -499,11 +499,11 @@ async 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 = await render_to_sx("events-ticket-config-display",
display_html = sx_call("events-ticket-config-display",
price_str=f"\u00a3{tp:.2f}",
count_str=tc_str, show_js=show_js)
else:
display_html = await render_to_sx("events-ticket-config-none", show_js=show_js)
display_html = sx_call("events-ticket-config-none", show_js=show_js)
update_url = url_for(
"calendar.day.calendar_entries.calendar_entry.update_tickets",
@@ -513,7 +513,7 @@ async 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 = await render_to_sx("events-ticket-config-form",
form_html = sx_call("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)
@@ -524,7 +524,7 @@ async def render_entry_tickets_config(entry, calendar, day, month, year) -> str:
# Entry posts panel
# ---------------------------------------------------------------------------
async def render_entry_posts_panel(entry_posts, entry, calendar, day, month, year) -> str:
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
@@ -541,28 +541,28 @@ async def render_entry_posts_panel(entry_posts, entry, calendar, day, month, yea
ep_title = getattr(ep, "title", "")
ep_id = getattr(ep, "id", 0)
feat = getattr(ep, "feature_image", None)
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"))
img_html = (sx_call("events-post-img", src=feat, alt=ep_title)
if feat else sx_call("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 += await render_to_sx("events-entry-post-item",
items += sx_call("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 = await render_to_sx("events-entry-posts-list", items=SxExpr(items))
posts_html = sx_call("events-entry-posts-list", items=SxExpr(items))
else:
posts_html = await render_to_sx("events-entry-posts-none")
posts_html = sx_call("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 await render_to_sx("events-entry-posts-panel",
return sx_call("events-entry-posts-panel",
posts=SxExpr(posts_html), search_url=search_url,
entry_id=eid_s)
@@ -571,7 +571,7 @@ async def render_entry_posts_panel(entry_posts, entry, calendar, day, month, yea
# Entry posts nav OOB
# ---------------------------------------------------------------------------
async def render_entry_posts_nav_oob(entry_posts) -> str:
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 {}
@@ -579,7 +579,7 @@ async def render_entry_posts_nav_oob(entry_posts) -> str:
blog_url_fn = getattr(g, "blog_url", None)
if not entry_posts:
return await render_to_sx("events-entry-posts-nav-oob-empty")
return sx_call("events-entry-posts-nav-oob-empty")
items = ""
for ep in entry_posts:
@@ -587,20 +587,20 @@ async 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 = (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",
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",
href=href, nav_btn=nav_btn,
img=SxExpr(img_html), title=title)
return await render_to_sx("events-entry-posts-nav-oob", items=SxExpr(items))
return sx_call("events-entry-posts-nav-oob", items=SxExpr(items))
# ---------------------------------------------------------------------------
# Day entries nav OOB
# ---------------------------------------------------------------------------
async def render_day_entries_nav_oob(confirmed_entries, calendar, day_date) -> str:
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
@@ -609,7 +609,7 @@ async def render_day_entries_nav_oob(confirmed_entries, calendar, day_date) -> s
cal_slug = getattr(calendar, "slug", "")
if not confirmed_entries:
return await render_to_sx("events-day-entries-nav-oob-empty")
return sx_call("events-day-entries-nav-oob-empty")
items = ""
for entry in confirmed_entries:
@@ -621,18 +621,18 @@ async def render_day_entries_nav_oob(confirmed_entries, calendar, day_date) -> s
)
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 += await render_to_sx("events-day-nav-entry",
items += sx_call("events-day-nav-entry",
href=href, nav_btn=nav_btn,
name=entry.name, time_str=start + end)
return await render_to_sx("events-day-entries-nav-oob", items=SxExpr(items))
return sx_call("events-day-entries-nav-oob", items=SxExpr(items))
# ---------------------------------------------------------------------------
# Post nav entries OOB
# ---------------------------------------------------------------------------
async def render_post_nav_entries_oob(associated_entries, calendars, post) -> str:
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
@@ -644,7 +644,7 @@ async def render_post_nav_entries_oob(associated_entries, calendars, post) -> st
has_items = has_entries or calendars
if not has_items:
return await render_to_sx("events-post-nav-oob-empty")
return sx_call("events-post-nav-oob-empty")
slug = post.get("slug", "") if isinstance(post, dict) else getattr(post, "slug", "")
@@ -659,7 +659,7 @@ async def render_post_nav_entries_oob(associated_entries, calendars, post) -> st
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 += await render_to_sx("events-post-nav-entry",
items += sx_call("events-post-nav-entry",
href=href, nav_btn=nav_btn,
name=entry.name, time_str=time_str + end_str)
@@ -667,7 +667,7 @@ async def render_post_nav_entries_oob(associated_entries, calendars, post) -> st
for cal in calendars:
cs = getattr(cal, "slug", "")
local_href = events_url(f"/{slug}/{cs}/")
items += await render_to_sx("events-post-nav-calendar",
items += sx_call("events-post-nav-calendar",
href=local_href, nav_btn=nav_btn, name=cal.name)
hs = ("on load or scroll "
@@ -675,7 +675,7 @@ async def render_post_nav_entries_oob(associated_entries, calendars, post) -> st
"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 await render_to_sx("events-post-nav-wrapper",
return sx_call("events-post-nav-wrapper",
items=SxExpr(items), hyperscript=hs)
@@ -683,23 +683,23 @@ async def render_post_nav_entries_oob(associated_entries, calendars, post) -> st
# Calendar description display + edit form
# ---------------------------------------------------------------------------
async def render_calendar_description(calendar, *, oob: bool = False) -> str:
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
from .calendar import _calendar_description_display_html
cal_slug = getattr(calendar, "slug", "")
edit_url = url_for("calendar.admin.calendar_description_edit", calendar_slug=cal_slug)
html = await _calendar_description_display_html(calendar, edit_url)
html = _calendar_description_display_html(calendar, edit_url)
if oob:
desc = getattr(calendar, "description", "") or ""
html += await render_to_sx("events-calendar-description-title-oob",
html += sx_call("events-calendar-description-title-oob",
description=desc)
return html
async def render_calendar_description_edit(calendar) -> str:
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
@@ -710,7 +710,7 @@ async 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 await render_to_sx("events-calendar-description-edit-form",
return sx_call("events-calendar-description-edit-form",
save_url=save_url, cancel_url=cancel_url,
csrf=csrf, description=desc)
@@ -719,7 +719,7 @@ async def render_calendar_description_edit(calendar) -> str:
# Entry admin page / OOB
# ---------------------------------------------------------------------------
async def _entry_admin_header_html(ctx: dict, *, oob: bool = False) -> str:
def _entry_admin_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build the entry admin header row."""
from quart import url_for
@@ -739,14 +739,14 @@ async def _entry_admin_header_html(ctx: dict, *, oob: bool = False) -> str:
calendar_slug=cal_slug, day=day, month=month, year=year, entry_id=entry.id,
)
# Nav: ticket_types link
nav_html = await _entry_admin_nav_html(ctx)
nav_html = _entry_admin_nav_html(ctx)
return await render_to_sx("menu-row-sx", id="entry-admin-row", level=6,
return sx_call("menu-row-sx", id="entry-admin-row", level=6,
link_href=link_href, link_label="admin", icon="fa fa-cog",
nav=SxExpr(nav_html) if nav_html else None, child_id="entry-admin-header-child", oob=oob)
async def _entry_admin_nav_html(ctx: dict) -> str:
def _entry_admin_nav_html(ctx: dict) -> str:
"""Entry admin nav: ticket_types link."""
from quart import url_for
@@ -765,11 +765,11 @@ async def _entry_admin_nav_html(ctx: dict) -> str:
href = url_for("calendar.day.calendar_entries.calendar_entry.ticket_types.get",
calendar_slug=cal_slug, entry_id=entry.id,
year=year, month=month, day=day)
return await render_to_sx("nav-link", href=href, label="ticket_types",
return sx_call("nav-link", href=href, label="ticket_types",
select_colours=select_colours)
async def _entry_admin_main_panel_html(ctx: dict) -> str:
def _entry_admin_main_panel_html(ctx: dict) -> str:
"""Entry admin main panel: just a ticket_types link."""
from quart import url_for
@@ -789,7 +789,7 @@ async def _entry_admin_main_panel_html(ctx: dict) -> str:
href = url_for("calendar.day.calendar_entries.calendar_entry.ticket_types.get",
calendar_slug=cal_slug, entry_id=entry.id,
year=year, month=month, day=day)
return await render_to_sx("nav-link", href=href, label="ticket_types",
return sx_call("nav-link", href=href, label="ticket_types",
select_colours=select_colours, aclass=nav_btn,
is_selected=False)
@@ -798,7 +798,7 @@ async def _entry_admin_main_panel_html(ctx: dict) -> str:
# Post search results
# ---------------------------------------------------------------------------
async def render_post_search_results(search_posts, search_query, page, total_pages,
def render_post_search_results(search_posts, search_query, page, total_pages,
entry, calendar, day, month, year) -> str:
"""Render post search results (replaces _types/entry/_post_search_results.html)."""
from quart import url_for
@@ -816,11 +816,11 @@ async def render_post_search_results(search_posts, search_query, page, total_pag
feat = getattr(sp, "feature_image", None)
title = getattr(sp, "title", "")
if feat:
img_html = await render_to_sx("events-post-img", src=feat, alt=title)
img_html = sx_call("events-post-img", src=feat, alt=title)
else:
img_html = await render_to_sx("events-post-img-placeholder")
img_html = sx_call("events-post-img-placeholder")
parts.append(await render_to_sx("events-post-search-item",
parts.append(sx_call("events-post-search-item",
post_url=post_url, entry_id=str(eid), csrf=csrf,
post_id=str(sp.id), img=SxExpr(img_html), title=title))
@@ -830,10 +830,10 @@ async def render_post_search_results(search_posts, search_query, page, total_pag
next_url = url_for("calendar.day.calendar_entries.calendar_entry.search_posts",
calendar_slug=cal_slug, day=day, month=month, year=year,
entry_id=eid, q=search_query, page=page + 1)
result += await render_to_sx("events-post-search-sentinel",
result += sx_call("events-post-search-sentinel",
page=str(page), next_url=next_url)
elif search_posts:
result += await render_to_sx("events-post-search-end")
result += sx_call("events-post-search-end")
return result
@@ -842,7 +842,7 @@ async def render_post_search_results(search_posts, search_query, page, total_pag
# Entry edit form
# ---------------------------------------------------------------------------
async def render_entry_edit_form(entry, calendar, day, month, year, day_slots) -> str:
def render_entry_edit_form(entry, calendar, day, month, year, day_slots) -> str:
"""Render entry edit form (replaces _types/entry/_edit.html)."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -863,11 +863,11 @@ async def render_entry_edit_form(entry, calendar, day, month, year, day_slots) -
# Slot picker
if day_slots:
options_html = await _slot_options_html(day_slots, selected_slot_id=getattr(entry, "slot_id", None))
slot_picker_html = await render_to_sx("events-slot-picker",
options_html = _slot_options_html(day_slots, selected_slot_id=getattr(entry, "slot_id", None))
slot_picker_html = sx_call("events-slot-picker",
id=f"entry-slot-{eid}", options=SxExpr(options_html))
else:
slot_picker_html = await render_to_sx("events-no-slots")
slot_picker_html = sx_call("events-no-slots")
# Values
start_val = entry.start_at.strftime("%H:%M") if entry.start_at else ""
@@ -879,7 +879,7 @@ async def render_entry_edit_form(entry, calendar, day, month, year, day_slots) -
tp_val = f"{tp:.2f}" if tp is not None else ""
tc_val = str(tc) if tc is not None else ""
html = await render_to_sx("events-entry-edit-form",
html = sx_call("events-entry-edit-form",
entry_id=str(eid), list_container=list_container,
put_url=put_url, cancel_url=cancel_url, csrf=csrf,
name_val=entry.name or "", slot_picker=SxExpr(slot_picker_html),
@@ -893,7 +893,7 @@ async def render_entry_edit_form(entry, calendar, day, month, year, day_slots) -
# Entry add form / button
# ---------------------------------------------------------------------------
async def render_entry_add_form(calendar, day, month, year, day_slots) -> str:
def render_entry_add_form(calendar, day, month, year, day_slots) -> str:
"""Render entry add form (replaces _types/day/_add.html)."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -912,13 +912,13 @@ async def render_entry_add_form(calendar, day, month, year, day_slots) -> str:
# Slot picker
if day_slots:
options_html = await _slot_options_html(day_slots)
slot_picker_html = await render_to_sx("events-slot-picker",
options_html = _slot_options_html(day_slots)
slot_picker_html = sx_call("events-slot-picker",
id="entry-slot-new", options=SxExpr(options_html))
else:
slot_picker_html = await render_to_sx("events-no-slots")
slot_picker_html = sx_call("events-no-slots")
html = await render_to_sx("events-entry-add-form",
html = sx_call("events-entry-add-form",
post_url=post_url, csrf=csrf,
slot_picker=SxExpr(slot_picker_html),
action_btn=action_btn, cancel_btn=cancel_btn,
@@ -926,7 +926,7 @@ async def render_entry_add_form(calendar, day, month, year, day_slots) -> str:
return html + _SLOT_PICKER_JS
async def render_entry_add_button(calendar, day, month, year) -> str:
def render_entry_add_button(calendar, day, month, year) -> str:
"""Render entry add button (replaces _types/day/_add_button.html)."""
from quart import url_for, g
@@ -936,14 +936,14 @@ async def render_entry_add_button(calendar, day, month, year) -> str:
add_url = url_for("calendar.day.calendar_entries.add_form",
calendar_slug=cal_slug, day=day, month=month, year=year)
return await render_to_sx("events-entry-add-button", pre_action=pre_action, add_url=add_url)
return sx_call("events-entry-add-button", pre_action=pre_action, add_url=add_url)
# ---------------------------------------------------------------------------
# Fragment: container cards entries
# ---------------------------------------------------------------------------
async def render_fragment_container_cards(batch, post_ids, slug_map) -> str:
def render_fragment_container_cards(batch, post_ids, slug_map) -> str:
"""Render container cards entries (replaces fragments/container_cards_entries.html)."""
from shared.infrastructure.urls import events_url
@@ -963,12 +963,12 @@ async def render_fragment_container_cards(batch, post_ids, slug_map) -> str:
time_str = entry.start_at.strftime("%H:%M")
if entry.end_at:
time_str += f" \u2013 {entry.end_at.strftime('%H:%M')}"
cards_html += await render_to_sx("events-frag-entry-card",
cards_html += sx_call("events-frag-entry-card",
href=events_url(_entry_path),
name=entry.name,
date_str=entry.start_at.strftime("%a, %b %d"),
time_str=time_str)
parts.append(await render_to_sx("events-frag-entries-widget", cards=SxExpr(cards_html)))
parts.append(sx_call("events-frag-entries-widget", cards=SxExpr(cards_html)))
parts.append(f"<!-- /card-widget:{post_id} -->")
return "\n".join(parts)
@@ -978,7 +978,7 @@ async def render_fragment_container_cards(batch, post_ids, slug_map) -> str:
# Fragment: account page tickets
# ---------------------------------------------------------------------------
async def render_fragment_account_tickets(tickets) -> str:
def render_fragment_account_tickets(tickets) -> str:
"""Render account page tickets (replaces fragments/account_page_tickets.html)."""
from shared.infrastructure.urls import events_url
@@ -993,25 +993,25 @@ async def render_fragment_account_tickets(tickets) -> str:
type_name = ""
if getattr(ticket, "ticket_type_name", None):
type_name = f'<span>&middot; {escape(ticket.ticket_type_name)}</span>'
badge_html = await render_to_sx("status-pill",
badge_html = sx_call("status-pill",
status=getattr(ticket, "state", ""))
items_html += await render_to_sx("events-frag-ticket-item",
items_html += sx_call("events-frag-ticket-item",
href=href, entry_name=ticket.entry_name,
date_str=date_str, calendar_name=cal_name,
type_name=type_name, badge=SxExpr(badge_html))
body = await render_to_sx("events-frag-tickets-list", items=SxExpr(items_html))
body = sx_call("events-frag-tickets-list", items=SxExpr(items_html))
else:
body = await render_to_sx("empty-state", message="No tickets yet.",
body = sx_call("empty-state", message="No tickets yet.",
cls="text-sm text-stone-500")
return await render_to_sx("events-frag-tickets-panel", items=SxExpr(body))
return sx_call("events-frag-tickets-panel", items=SxExpr(body))
# ---------------------------------------------------------------------------
# Fragment: account page bookings
# ---------------------------------------------------------------------------
async def render_fragment_account_bookings(bookings) -> str:
def render_fragment_account_bookings(bookings) -> str:
"""Render account page bookings (replaces fragments/account_page_bookings.html)."""
if bookings:
items_html = ""
@@ -1027,16 +1027,16 @@ async def render_fragment_account_bookings(bookings) -> str:
cost_str = ""
if getattr(booking, "cost", None):
cost_str = f'<span>&middot; &pound;{escape(str(booking.cost))}</span>'
badge_html = await render_to_sx("status-pill",
badge_html = sx_call("status-pill",
status=getattr(booking, "state", ""))
items_html += await render_to_sx("events-frag-booking-item",
items_html += sx_call("events-frag-booking-item",
name=booking.name,
date_str=date_str + date_str_extra,
calendar_name=cal_name, cost_str=cost_str,
badge=SxExpr(badge_html))
body = await render_to_sx("events-frag-bookings-list", items=SxExpr(items_html))
body = sx_call("events-frag-bookings-list", items=SxExpr(items_html))
else:
body = await render_to_sx("empty-state", message="No bookings yet.",
body = sx_call("empty-state", message="No bookings yet.",
cls="text-sm text-stone-500")
return await render_to_sx("events-frag-bookings-panel", items=SxExpr(body))
return sx_call("events-frag-bookings-panel", items=SxExpr(body))

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Any
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from .utils import _clear_deeper_oob, _ensure_container_nav
from .calendar import (
@@ -235,8 +235,8 @@ async def _cal_admin_full(ctx: dict, **kw: Any) -> str:
return await render_to_sx_with_env("events-cal-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
calendar_admin_header=SxExpr(await _calendar_admin_header_sx(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
calendar_admin_header=SxExpr(_calendar_admin_header_sx(ctx)),
)
@@ -247,9 +247,9 @@ async def _cal_admin_oob(ctx: dict, **kw: Any) -> str:
slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-cal-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")),
cal_oob=SxExpr(await _calendar_header_sx(ctx, oob=True)),
cal_oob=SxExpr(_calendar_header_sx(ctx, oob=True)),
cal_admin_oob_wrap=SxExpr(await oob_header_sx("calendar-header-child",
"calendar-admin-header-child", await _calendar_admin_header_sx(ctx))),
"calendar-admin-header-child", _calendar_admin_header_sx(ctx))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
@@ -270,7 +270,7 @@ async def _slots_oob(ctx: dict, **kw: Any) -> str:
slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-slots-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")),
cal_admin_oob=SxExpr(await _calendar_admin_header_sx(ctx, oob=True)),
cal_admin_oob=SxExpr(_calendar_admin_header_sx(ctx, oob=True)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
@@ -288,9 +288,9 @@ async def _slot_full(ctx: dict, **kw: Any) -> str:
return await render_to_sx_with_env("events-slot-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
calendar_admin_header=SxExpr(await _calendar_admin_header_sx(ctx)),
slot_header=SxExpr(await _slot_header_html(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
calendar_admin_header=SxExpr(_calendar_admin_header_sx(ctx)),
slot_header=SxExpr(_slot_header_html(ctx)),
)
@@ -301,9 +301,9 @@ async def _slot_oob(ctx: dict, **kw: Any) -> str:
slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-slot-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")),
cal_admin_oob=SxExpr(await _calendar_admin_header_sx(ctx, oob=True)),
cal_admin_oob=SxExpr(_calendar_admin_header_sx(ctx, oob=True)),
slot_oob_wrap=SxExpr(await oob_header_sx("calendar-admin-header-child",
"slot-header-child", await _slot_header_html(ctx))),
"slot-header-child", _slot_header_html(ctx))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
@@ -322,9 +322,9 @@ async def _day_admin_full(ctx: dict, **kw: Any) -> str:
return await render_to_sx_with_env("events-day-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
day_header=SxExpr(await _day_header_sx(ctx)),
day_admin_header=SxExpr(await _day_admin_header_sx(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
day_header=SxExpr(_day_header_sx(ctx)),
day_admin_header=SxExpr(_day_admin_header_sx(ctx)),
)
@@ -335,9 +335,9 @@ async def _day_admin_oob(ctx: dict, **kw: Any) -> str:
slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-day-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")),
cal_oob=SxExpr(await _calendar_header_sx(ctx, oob=True)),
cal_oob=SxExpr(_calendar_header_sx(ctx, oob=True)),
day_admin_oob_wrap=SxExpr(await oob_header_sx("day-header-child",
"day-admin-header-child", await _day_admin_header_sx(ctx))),
"day-admin-header-child", _day_admin_header_sx(ctx))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
@@ -353,9 +353,9 @@ async def _entry_full(ctx: dict, **kw: Any) -> str:
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-entry-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
day_header=SxExpr(await _day_header_sx(ctx)),
entry_header=SxExpr(await _entry_header_html(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
day_header=SxExpr(_day_header_sx(ctx)),
entry_header=SxExpr(_entry_header_html(ctx)),
)
@@ -363,9 +363,9 @@ async def _entry_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-entry-layout-oob", {},
day_oob=SxExpr(await _day_header_sx(ctx, oob=True)),
day_oob=SxExpr(_day_header_sx(ctx, oob=True)),
entry_oob_wrap=SxExpr(await oob_header_sx("day-header-child",
"entry-header-child", await _entry_header_html(ctx))),
"entry-header-child", _entry_header_html(ctx))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"calendar-row", "calendar-header-child",
"day-row", "day-header-child",
@@ -383,10 +383,10 @@ async def _entry_admin_full(ctx: dict, **kw: Any) -> str:
return await render_to_sx_with_env("events-entry-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
day_header=SxExpr(await _day_header_sx(ctx)),
entry_header=SxExpr(await _entry_header_html(ctx)),
entry_admin_header=SxExpr(await _entry_admin_header_html(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
day_header=SxExpr(_day_header_sx(ctx)),
entry_header=SxExpr(_entry_header_html(ctx)),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)),
)
@@ -397,9 +397,9 @@ async def _entry_admin_oob(ctx: dict, **kw: Any) -> str:
slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-entry-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")),
entry_oob=SxExpr(await _entry_header_html(ctx, oob=True)),
entry_oob=SxExpr(_entry_header_html(ctx, oob=True)),
entry_admin_oob_wrap=SxExpr(await oob_header_sx("entry-header-child",
"entry-admin-header-child", await _entry_admin_header_html(ctx))),
"entry-admin-header-child", _entry_admin_header_html(ctx))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
@@ -416,11 +416,11 @@ async def _ticket_types_full(ctx: dict, **kw: Any) -> str:
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-types-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
day_header=SxExpr(await _day_header_sx(ctx)),
entry_header=SxExpr(await _entry_header_html(ctx)),
entry_admin_header=SxExpr(await _entry_admin_header_html(ctx)),
ticket_types_header=SxExpr(await _ticket_types_header_html(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
day_header=SxExpr(_day_header_sx(ctx)),
entry_header=SxExpr(_entry_header_html(ctx)),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)),
ticket_types_header=SxExpr(_ticket_types_header_html(ctx)),
)
@@ -428,9 +428,9 @@ async def _ticket_types_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-types-layout-oob", {},
entry_admin_oob=SxExpr(await _entry_admin_header_html(ctx, oob=True)),
entry_admin_oob=SxExpr(_entry_admin_header_html(ctx, oob=True)),
ticket_types_oob_wrap=SxExpr(await oob_header_sx("entry-admin-header-child",
"ticket_types-header-child", await _ticket_types_header_html(ctx))),
"ticket_types-header-child", _ticket_types_header_html(ctx))),
)
@@ -441,12 +441,12 @@ async def _ticket_type_full(ctx: dict, **kw: Any) -> str:
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-type-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
day_header=SxExpr(await _day_header_sx(ctx)),
entry_header=SxExpr(await _entry_header_html(ctx)),
entry_admin_header=SxExpr(await _entry_admin_header_html(ctx)),
ticket_types_header=SxExpr(await _ticket_types_header_html(ctx)),
ticket_type_header=SxExpr(await _ticket_type_header_html(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
day_header=SxExpr(_day_header_sx(ctx)),
entry_header=SxExpr(_entry_header_html(ctx)),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)),
ticket_types_header=SxExpr(_ticket_types_header_html(ctx)),
ticket_type_header=SxExpr(_ticket_type_header_html(ctx)),
)
@@ -454,9 +454,9 @@ async def _ticket_type_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-type-layout-oob", {},
ticket_types_oob=SxExpr(await _ticket_types_header_html(ctx, oob=True)),
ticket_types_oob=SxExpr(_ticket_types_header_html(ctx, oob=True)),
ticket_type_oob_wrap=SxExpr(await oob_header_sx("ticket_types-header-child",
"ticket_type-header-child", await _ticket_type_header_html(ctx))),
"ticket_type-header-child", _ticket_type_header_html(ctx))),
)
@@ -467,7 +467,7 @@ async def _markets_full(ctx: dict, **kw: Any) -> str:
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-markets-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
markets_header=SxExpr(await _markets_header_sx(ctx)),
markets_header=SxExpr(_markets_header_sx(ctx)),
)
@@ -477,7 +477,7 @@ async def _markets_oob(ctx: dict, **kw: Any) -> str:
return await render_to_sx_with_env("events-markets-layout-oob", {},
post_oob=SxExpr(await _post_header_sx(ctx, oob=True)),
markets_oob_wrap=SxExpr(await oob_header_sx("post-header-child",
"markets-header-child", await _markets_header_sx(ctx))),
"markets-header-child", _markets_header_sx(ctx))),
)
@@ -510,14 +510,14 @@ async def _h_calendar_admin_content(calendar_slug=None, **kw):
await _ensure_calendar(calendar_slug)
from shared.sx.page import get_template_context
ctx = await get_template_context()
return await _calendar_admin_main_panel_html(ctx)
return _calendar_admin_main_panel_html(ctx)
async def _h_day_admin_content(calendar_slug=None, year=None, month=None, day=None, **kw):
await _ensure_calendar(calendar_slug)
if year is not None:
await _ensure_day_data(int(year), int(month), int(day))
return await _day_admin_main_panel_html({})
return _day_admin_main_panel_html({})
async def _h_slots_content(calendar_slug=None, **kw):
@@ -527,7 +527,7 @@ async def _h_slots_content(calendar_slug=None, **kw):
from bp.slots.services.slots import list_slots as svc_list_slots
slots = await svc_list_slots(g.s, calendar.id) if calendar else []
_add_to_defpage_ctx(slots=slots)
return await render_slots_table(slots, calendar)
return render_slots_table(slots, calendar)
async def _h_slot_content(calendar_slug=None, slot_id=None, **kw):
@@ -540,7 +540,7 @@ async def _h_slot_content(calendar_slug=None, slot_id=None, **kw):
g.slot = slot
_add_to_defpage_ctx(slot=slot)
calendar = getattr(g, "calendar", None)
return await render_slot_main_panel(slot, calendar)
return render_slot_main_panel(slot, calendar)
async def _h_entry_content(calendar_slug=None, entry_id=None, **kw):
@@ -548,7 +548,7 @@ async def _h_entry_content(calendar_slug=None, entry_id=None, **kw):
await _ensure_entry_context(entry_id)
from shared.sx.page import get_template_context
ctx = await get_template_context()
return await _entry_main_panel_html(ctx)
return _entry_main_panel_html(ctx)
async def _h_entry_menu(calendar_slug=None, entry_id=None, **kw):
@@ -556,7 +556,7 @@ async def _h_entry_menu(calendar_slug=None, entry_id=None, **kw):
await _ensure_entry_context(entry_id)
from shared.sx.page import get_template_context
ctx = await get_template_context()
return await _entry_nav_html(ctx)
return _entry_nav_html(ctx)
async def _h_entry_admin_content(calendar_slug=None, entry_id=None, **kw):
@@ -564,12 +564,11 @@ async def _h_entry_admin_content(calendar_slug=None, entry_id=None, **kw):
await _ensure_entry_context(entry_id)
from shared.sx.page import get_template_context
ctx = await get_template_context()
return await _entry_admin_main_panel_html(ctx)
return _entry_admin_main_panel_html(ctx)
async def _h_admin_menu():
from shared.sx.helpers import render_to_sx
return await render_to_sx("events-admin-placeholder-nav")
def _h_admin_menu():
return sx_call("events-admin-placeholder-nav")
async def _h_ticket_types_content(calendar_slug=None, entry_id=None,
@@ -582,7 +581,7 @@ async def _h_ticket_types_content(calendar_slug=None, entry_id=None,
from bp.ticket_types.services.tickets import list_ticket_types as svc_list_ticket_types
ticket_types = await svc_list_ticket_types(g.s, entry.id) if entry else []
_add_to_defpage_ctx(ticket_types=ticket_types)
return await render_ticket_types_table(ticket_types, entry, calendar, day, month, year)
return render_ticket_types_table(ticket_types, entry, calendar, day, month, year)
async def _h_ticket_type_content(calendar_slug=None, entry_id=None,
@@ -598,7 +597,7 @@ async def _h_ticket_type_content(calendar_slug=None, entry_id=None,
_add_to_defpage_ctx(ticket_type=ticket_type)
entry = getattr(g, "entry", None)
calendar = getattr(g, "calendar", None)
return await render_ticket_type_main_panel(ticket_type, entry, calendar, day, month, year)
return render_ticket_type_main_panel(ticket_type, entry, calendar, day, month, year)
async def _h_tickets_content(**kw):
@@ -613,7 +612,7 @@ async def _h_tickets_content(**kw):
)
from shared.sx.page import get_template_context
ctx = await get_template_context()
return await _tickets_main_panel_html(ctx, tickets)
return _tickets_main_panel_html(ctx, tickets)
async def _h_ticket_detail_content(code=None, **kw):
@@ -635,7 +634,7 @@ async def _h_ticket_detail_content(code=None, **kw):
abort(404)
from shared.sx.page import get_template_context
ctx = await get_template_context()
return await _ticket_detail_panel_html(ctx, ticket)
return _ticket_detail_panel_html(ctx, ticket)
async def _h_ticket_admin_content(**kw):
@@ -674,10 +673,10 @@ async def _h_ticket_admin_content(**kw):
from shared.sx.page import get_template_context
ctx = await get_template_context()
return await _ticket_admin_main_panel_html(ctx, tickets, stats)
return _ticket_admin_main_panel_html(ctx, tickets, stats)
async def _h_markets_content(**kw):
from shared.sx.page import get_template_context
ctx = await get_template_context()
return await _markets_main_panel_html(ctx)
return _markets_main_panel_html(ctx)

View File

@@ -42,8 +42,8 @@ async def _cal_admin_full(ctx: dict, **kw: Any) -> str:
return await render_to_sx_with_env("events-cal-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
calendar_admin_header=SxExpr(await _calendar_admin_header_sx(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
calendar_admin_header=SxExpr(_calendar_admin_header_sx(ctx)),
)
@@ -54,9 +54,9 @@ async def _cal_admin_oob(ctx: dict, **kw: Any) -> str:
slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-cal-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")),
cal_oob=SxExpr(await _calendar_header_sx(ctx, oob=True)),
cal_oob=SxExpr(_calendar_header_sx(ctx, oob=True)),
cal_admin_oob_wrap=SxExpr(await oob_header_sx("calendar-header-child",
"calendar-admin-header-child", await _calendar_admin_header_sx(ctx))),
"calendar-admin-header-child", _calendar_admin_header_sx(ctx))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
@@ -77,7 +77,7 @@ async def _slots_oob(ctx: dict, **kw: Any) -> str:
slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-slots-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")),
cal_admin_oob=SxExpr(await _calendar_admin_header_sx(ctx, oob=True)),
cal_admin_oob=SxExpr(_calendar_admin_header_sx(ctx, oob=True)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
@@ -95,9 +95,9 @@ async def _slot_full(ctx: dict, **kw: Any) -> str:
return await render_to_sx_with_env("events-slot-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
calendar_admin_header=SxExpr(await _calendar_admin_header_sx(ctx)),
slot_header=SxExpr(await _slot_header_html(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
calendar_admin_header=SxExpr(_calendar_admin_header_sx(ctx)),
slot_header=SxExpr(_slot_header_html(ctx)),
)
@@ -108,9 +108,9 @@ async def _slot_oob(ctx: dict, **kw: Any) -> str:
slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-slot-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")),
cal_admin_oob=SxExpr(await _calendar_admin_header_sx(ctx, oob=True)),
cal_admin_oob=SxExpr(_calendar_admin_header_sx(ctx, oob=True)),
slot_oob_wrap=SxExpr(await oob_header_sx("calendar-admin-header-child",
"slot-header-child", await _slot_header_html(ctx))),
"slot-header-child", _slot_header_html(ctx))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
@@ -129,9 +129,9 @@ async def _day_admin_full(ctx: dict, **kw: Any) -> str:
return await render_to_sx_with_env("events-day-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
day_header=SxExpr(await _day_header_sx(ctx)),
day_admin_header=SxExpr(await _day_admin_header_sx(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
day_header=SxExpr(_day_header_sx(ctx)),
day_admin_header=SxExpr(_day_admin_header_sx(ctx)),
)
@@ -142,9 +142,9 @@ async def _day_admin_oob(ctx: dict, **kw: Any) -> str:
slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-day-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")),
cal_oob=SxExpr(await _calendar_header_sx(ctx, oob=True)),
cal_oob=SxExpr(_calendar_header_sx(ctx, oob=True)),
day_admin_oob_wrap=SxExpr(await oob_header_sx("day-header-child",
"day-admin-header-child", await _day_admin_header_sx(ctx))),
"day-admin-header-child", _day_admin_header_sx(ctx))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
@@ -160,9 +160,9 @@ async def _entry_full(ctx: dict, **kw: Any) -> str:
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-entry-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
day_header=SxExpr(await _day_header_sx(ctx)),
entry_header=SxExpr(await _entry_header_html(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
day_header=SxExpr(_day_header_sx(ctx)),
entry_header=SxExpr(_entry_header_html(ctx)),
)
@@ -170,9 +170,9 @@ async def _entry_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-entry-layout-oob", {},
day_oob=SxExpr(await _day_header_sx(ctx, oob=True)),
day_oob=SxExpr(_day_header_sx(ctx, oob=True)),
entry_oob_wrap=SxExpr(await oob_header_sx("day-header-child",
"entry-header-child", await _entry_header_html(ctx))),
"entry-header-child", _entry_header_html(ctx))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"calendar-row", "calendar-header-child",
"day-row", "day-header-child",
@@ -190,10 +190,10 @@ async def _entry_admin_full(ctx: dict, **kw: Any) -> str:
return await render_to_sx_with_env("events-entry-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
admin_header=SxExpr(await post_admin_header_sx(ctx, slug, selected="calendars")),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
day_header=SxExpr(await _day_header_sx(ctx)),
entry_header=SxExpr(await _entry_header_html(ctx)),
entry_admin_header=SxExpr(await _entry_admin_header_html(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
day_header=SxExpr(_day_header_sx(ctx)),
entry_header=SxExpr(_entry_header_html(ctx)),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)),
)
@@ -204,9 +204,9 @@ async def _entry_admin_oob(ctx: dict, **kw: Any) -> str:
slug = (ctx.get("post") or {}).get("slug", "")
return await render_to_sx_with_env("events-entry-admin-layout-oob", {},
admin_oob=SxExpr(await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")),
entry_oob=SxExpr(await _entry_header_html(ctx, oob=True)),
entry_oob=SxExpr(_entry_header_html(ctx, oob=True)),
entry_admin_oob_wrap=SxExpr(await oob_header_sx("entry-header-child",
"entry-admin-header-child", await _entry_admin_header_html(ctx))),
"entry-admin-header-child", _entry_admin_header_html(ctx))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"post-admin-row", "post-admin-header-child",
"calendar-row", "calendar-header-child",
@@ -223,11 +223,11 @@ async def _ticket_types_full(ctx: dict, **kw: Any) -> str:
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-types-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
day_header=SxExpr(await _day_header_sx(ctx)),
entry_header=SxExpr(await _entry_header_html(ctx)),
entry_admin_header=SxExpr(await _entry_admin_header_html(ctx)),
ticket_types_header=SxExpr(await _ticket_types_header_html(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
day_header=SxExpr(_day_header_sx(ctx)),
entry_header=SxExpr(_entry_header_html(ctx)),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)),
ticket_types_header=SxExpr(_ticket_types_header_html(ctx)),
)
@@ -235,9 +235,9 @@ async def _ticket_types_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-types-layout-oob", {},
entry_admin_oob=SxExpr(await _entry_admin_header_html(ctx, oob=True)),
entry_admin_oob=SxExpr(_entry_admin_header_html(ctx, oob=True)),
ticket_types_oob_wrap=SxExpr(await oob_header_sx("entry-admin-header-child",
"ticket_types-header-child", await _ticket_types_header_html(ctx))),
"ticket_types-header-child", _ticket_types_header_html(ctx))),
)
@@ -248,12 +248,12 @@ async def _ticket_type_full(ctx: dict, **kw: Any) -> str:
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-type-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
calendar_header=SxExpr(await _calendar_header_sx(ctx)),
day_header=SxExpr(await _day_header_sx(ctx)),
entry_header=SxExpr(await _entry_header_html(ctx)),
entry_admin_header=SxExpr(await _entry_admin_header_html(ctx)),
ticket_types_header=SxExpr(await _ticket_types_header_html(ctx)),
ticket_type_header=SxExpr(await _ticket_type_header_html(ctx)),
calendar_header=SxExpr(_calendar_header_sx(ctx)),
day_header=SxExpr(_day_header_sx(ctx)),
entry_header=SxExpr(_entry_header_html(ctx)),
entry_admin_header=SxExpr(_entry_admin_header_html(ctx)),
ticket_types_header=SxExpr(_ticket_types_header_html(ctx)),
ticket_type_header=SxExpr(_ticket_type_header_html(ctx)),
)
@@ -261,9 +261,9 @@ async def _ticket_type_oob(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-ticket-type-layout-oob", {},
ticket_types_oob=SxExpr(await _ticket_types_header_html(ctx, oob=True)),
ticket_types_oob=SxExpr(_ticket_types_header_html(ctx, oob=True)),
ticket_type_oob_wrap=SxExpr(await oob_header_sx("ticket_types-header-child",
"ticket_type-header-child", await _ticket_type_header_html(ctx))),
"ticket_type-header-child", _ticket_type_header_html(ctx))),
)
@@ -274,7 +274,7 @@ async def _markets_full(ctx: dict, **kw: Any) -> str:
from shared.sx.parser import SxExpr
return await render_to_sx_with_env("events-markets-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
markets_header=SxExpr(await _markets_header_sx(ctx)),
markets_header=SxExpr(_markets_header_sx(ctx)),
)
@@ -284,5 +284,5 @@ async def _markets_oob(ctx: dict, **kw: Any) -> str:
return await render_to_sx_with_env("events-markets-layout-oob", {},
post_oob=SxExpr(await _post_header_sx(ctx, oob=True)),
markets_oob_wrap=SxExpr(await oob_header_sx("post-header-child",
"markets-header-child", await _markets_header_sx(ctx))),
"markets-header-child", _markets_header_sx(ctx))),
)

View File

@@ -40,7 +40,7 @@ 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 = await _events_main_panel_html(
content = _events_main_panel_html(
ctx, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url,
)
@@ -59,15 +59,15 @@ 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 = await _events_main_panel_html(
content = _events_main_panel_html(
ctx, entries, has_more, pending_tickets, page_info,
page, view, ticket_url, next_url, events_url,
)
return await oob_page_sx(content=content)
async def render_all_events_cards(entries, has_more, pending_tickets,
page_info, page, view) -> str:
def render_all_events_cards(entries, has_more, pending_tickets,
page_info, page, view) -> str:
"""Pagination fragment: all events cards only."""
from quart import url_for
from shared.utils import route_prefix
@@ -77,7 +77,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 await _entry_cards_html(
return _entry_cards_html(
entries, page_info, pending_tickets, ticket_url, events_url,
view, page, has_more, next_url,
)
@@ -99,7 +99,7 @@ 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 = await _events_main_panel_html(
content = _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,
@@ -122,7 +122,7 @@ 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 = await _events_main_panel_html(
content = _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,
@@ -133,8 +133,8 @@ async def render_page_summary_oob(ctx: dict, entries, has_more, pending_tickets,
return await oob_page_sx(oobs=oobs, content=content)
async def render_page_summary_cards(entries, has_more, pending_tickets,
page_info, page, view, post) -> str:
def render_page_summary_cards(entries, has_more, pending_tickets,
page_info, page, view, post) -> str:
"""Pagination fragment: page-scoped events cards only."""
from quart import url_for
from shared.utils import route_prefix
@@ -144,7 +144,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 await _entry_cards_html(
return _entry_cards_html(
entries, page_info, pending_tickets, ticket_url, events_url,
view, page, has_more, next_url,
is_page_scoped=True, post=post,
@@ -157,7 +157,7 @@ async def render_page_summary_cards(entries, has_more, pending_tickets,
async def render_calendars_page(ctx: dict) -> str:
"""Full page: calendars listing."""
content = await _calendars_main_panel_sx(ctx)
content = _calendars_main_panel_sx(ctx)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
root_hdr = await render_to_sx_with_env("layout-root-full", {})
@@ -168,7 +168,7 @@ async def render_calendars_page(ctx: dict) -> str:
async def render_calendars_oob(ctx: dict) -> str:
"""OOB response: calendars listing."""
content = await _calendars_main_panel_sx(ctx)
content = _calendars_main_panel_sx(ctx)
ctx = await _ensure_container_nav(ctx)
slug = (ctx.get("post") or {}).get("slug", "")
oobs = await post_admin_header_sx(ctx, slug, oob=True, selected="calendars")
@@ -183,19 +183,19 @@ async def render_calendars_oob(ctx: dict) -> str:
async def render_calendar_page(ctx: dict) -> str:
"""Full page: calendar month view."""
content = await _calendar_main_panel_html(ctx)
content = _calendar_main_panel_html(ctx)
hdr = await render_to_sx_with_env("layout-root-full", {})
child = await _post_header_sx(ctx) + await _calendar_header_sx(ctx)
child = await _post_header_sx(ctx) + _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 = await _calendar_main_panel_html(ctx)
content = _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))
_calendar_header_sx(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"calendar-row", "calendar-header-child")
return await oob_page_sx(oobs=oobs, content=content)
@@ -207,20 +207,20 @@ async def render_calendar_oob(ctx: dict) -> str:
async def render_day_page(ctx: dict) -> str:
"""Full page: day detail."""
content = await _day_main_panel_html(ctx)
content = _day_main_panel_html(ctx)
hdr = await render_to_sx_with_env("layout-root-full", {})
child = (await _post_header_sx(ctx)
+ await _calendar_header_sx(ctx) + await _day_header_sx(ctx))
+ _calendar_header_sx(ctx) + _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 = await _day_main_panel_html(ctx)
oobs = await _calendar_header_sx(ctx, oob=True)
content = _day_main_panel_html(ctx)
oobs = _calendar_header_sx(ctx, oob=True)
oobs += await oob_header_sx("calendar-header-child", "day-header-child",
await _day_header_sx(ctx))
_day_header_sx(ctx))
oobs += _clear_deeper_oob("post-row", "post-header-child",
"calendar-row", "calendar-header-child",
"day-row", "day-header-child")
@@ -231,36 +231,36 @@ async def render_day_oob(ctx: dict) -> str:
# Day main panel -- public API
# ---------------------------------------------------------------------------
async def render_day_main_panel(ctx: dict) -> str:
def render_day_main_panel(ctx: dict) -> str:
"""Public wrapper for day main panel rendering."""
return await _day_main_panel_html(ctx)
return _day_main_panel_html(ctx)
# ---------------------------------------------------------------------------
# Calendar description display + edit form
# ---------------------------------------------------------------------------
async def render_calendar_description(calendar, *, oob: bool = False) -> str:
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
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
cal_slug = getattr(calendar, "slug", "")
edit_url = url_for("calendar.admin.calendar_description_edit", calendar_slug=cal_slug)
html = await _calendar_description_display_html(calendar, edit_url)
html = _calendar_description_display_html(calendar, edit_url)
if oob:
desc = getattr(calendar, "description", "") or ""
html += await render_to_sx("events-calendar-description-title-oob",
html += sx_call("events-calendar-description-title-oob",
description=desc)
return html
async def render_calendar_description_edit(calendar) -> str:
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
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
csrf = generate_csrf_token()
cal_slug = getattr(calendar, "slug", "")
desc = getattr(calendar, "description", "") or ""
@@ -268,7 +268,7 @@ async 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 await render_to_sx("events-calendar-description-edit-form",
return sx_call("events-calendar-description-edit-form",
save_url=save_url, cancel_url=cancel_url,
csrf=csrf, description=desc)
@@ -277,11 +277,11 @@ async def render_calendar_description_edit(calendar) -> str:
# Calendars / Markets list panels (for POST create / DELETE)
# ---------------------------------------------------------------------------
async def render_calendars_list_panel(ctx: dict) -> str:
def render_calendars_list_panel(ctx: dict) -> str:
"""Render the calendars main panel HTML for POST/DELETE response."""
return await _calendars_main_panel_sx(ctx)
return _calendars_main_panel_sx(ctx)
async def render_markets_list_panel(ctx: dict) -> str:
def render_markets_list_panel(ctx: dict) -> str:
"""Render the markets main panel HTML for POST/DELETE response."""
return await _markets_main_panel_html(ctx)
return _markets_main_panel_html(ctx)

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
@@ -111,7 +111,7 @@ _SLOT_PICKER_JS = """\
# Slot options (shared by entry edit + add forms)
# ---------------------------------------------------------------------------
async def _slot_options_html(day_slots, selected_slot_id=None) -> str:
def _slot_options_html(day_slots, selected_slot_id=None) -> str:
"""Build slot <option> elements."""
parts = []
for slot in day_slots:
@@ -129,7 +129,7 @@ async def _slot_options_html(day_slots, selected_slot_id=None) -> str:
label_parts.append("[flexible]")
label = " ".join(label_parts)
parts.append(await render_to_sx("events-slot-option",
parts.append(sx_call("events-slot-option",
value=str(slot.id),
data_start=start, data_end=end,
data_flexible="1" if flexible else "0",
@@ -143,7 +143,7 @@ async def _slot_options_html(day_slots, selected_slot_id=None) -> str:
# Slot header row
# ---------------------------------------------------------------------------
async def _slot_header_html(ctx: dict, *, oob: bool = False) -> str:
def _slot_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build the slot detail header row."""
from quart import url_for
@@ -156,10 +156,10 @@ async def _slot_header_html(ctx: dict, *, oob: bool = False) -> str:
return ""
desc = getattr(slot, "description", "") or ""
label_sx = await render_to_sx("events-slot-label",
label_sx = sx_call("events-slot-label",
name=slot.name, description=desc)
return await render_to_sx("menu-row-sx", id="slot-row", level=5,
return sx_call("menu-row-sx", id="slot-row", level=5,
link_label_content=SxExpr(label_sx),
child_id="slot-header-child", oob=oob)
@@ -168,7 +168,7 @@ async def _slot_header_html(ctx: dict, *, oob: bool = False) -> str:
# Slot main panel
# ---------------------------------------------------------------------------
async def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
"""Render slot detail view."""
from quart import url_for, g
@@ -191,15 +191,15 @@ async def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
# Days pills
if days and days[0] != "\u2014":
days_inner = "".join(
await render_to_sx("events-slot-day-pill", day=d) for d in days
sx_call("events-slot-day-pill", day=d) for d in days
)
days_html = await render_to_sx("events-slot-days-pills", days_inner=SxExpr(days_inner))
days_html = sx_call("events-slot-days-pills", days_inner=SxExpr(days_inner))
else:
days_html = await render_to_sx("events-slot-no-days")
days_html = sx_call("events-slot-no-days")
sid = str(slot.id)
result = await render_to_sx("events-slot-panel",
result = sx_call("events-slot-panel",
slot_id=sid, list_container=list_container,
days=SxExpr(days_html),
flexible="yes" if flexible else "no",
@@ -208,7 +208,7 @@ async def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
pre_action=pre_action, edit_url=edit_url)
if oob:
result += await render_to_sx("events-slot-description-oob", description=desc)
result += sx_call("events-slot-description-oob", description=desc)
return result
@@ -217,7 +217,7 @@ async def render_slot_main_panel(slot, calendar, *, oob: bool = False) -> str:
# Slots table
# ---------------------------------------------------------------------------
async def render_slots_table(slots, calendar) -> str:
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
@@ -243,18 +243,18 @@ async def render_slots_table(slots, calendar) -> str:
day_list = days_display.split(", ")
if day_list and day_list[0] != "\u2014":
days_inner = "".join(
await render_to_sx("events-slot-day-pill", day=d) for d in day_list
sx_call("events-slot-day-pill", day=d) for d in day_list
)
days_html = await render_to_sx("events-slot-days-pills", days_inner=SxExpr(days_inner))
days_html = sx_call("events-slot-days-pills", days_inner=SxExpr(days_inner))
else:
days_html = await render_to_sx("events-slot-no-days")
days_html = sx_call("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 += await render_to_sx("events-slots-row",
rows_html += sx_call("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,
@@ -265,11 +265,11 @@ async def render_slots_table(slots, calendar) -> str:
del_url=del_url,
csrf_hdr=f'{{"X-CSRFToken": "{csrf}"}}')
else:
rows_html = await render_to_sx("events-slots-empty-row")
rows_html = sx_call("events-slots-empty-row")
add_url = url_for("calendar.slots.add_form", calendar_slug=cal_slug)
return await render_to_sx("events-slots-table",
return sx_call("events-slots-table",
list_container=list_container, rows=SxExpr(rows_html),
pre_action=pre_action, add_url=add_url)
@@ -278,7 +278,7 @@ async def render_slots_table(slots, calendar) -> str:
# Slot edit form
# ---------------------------------------------------------------------------
async def render_slot_edit_form(slot, calendar) -> str:
def render_slot_edit_form(slot, calendar) -> str:
"""Render slot edit form (replaces _types/slot/_edit.html)."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -305,18 +305,18 @@ async def render_slot_edit_form(slot, calendar) -> str:
("fri", "Fri"), ("sat", "Sat"), ("sun", "Sun")]
all_checked = all(getattr(slot, k, False) for k, _ in day_keys)
days_parts = [await render_to_sx("events-day-all-checkbox",
days_parts = [sx_call("events-day-all-checkbox",
checked="checked" if all_checked else None)]
for key, label in day_keys:
checked = getattr(slot, key, False)
days_parts.append(await render_to_sx("events-day-checkbox",
days_parts.append(sx_call("events-day-checkbox",
name=key, label=label,
checked="checked" if checked else None))
days_html = "".join(days_parts)
flexible = getattr(slot, "flexible", False)
return await render_to_sx("events-slot-edit-form",
return sx_call("events-slot-edit-form",
slot_id=str(sid), list_container=list_container,
put_url=put_url, cancel_url=cancel_url, csrf=csrf,
name_val=slot.name or "", cost_val=cost_val,
@@ -330,7 +330,7 @@ async def render_slot_edit_form(slot, calendar) -> str:
# Slot add form / button
# ---------------------------------------------------------------------------
async def render_slot_add_form(calendar) -> str:
def render_slot_add_form(calendar) -> str:
"""Render slot add form (replaces _types/slots/_add.html)."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -348,19 +348,19 @@ async def render_slot_add_form(calendar) -> str:
# Days checkboxes (all unchecked for add)
day_keys = [("mon", "Mon"), ("tue", "Tue"), ("wed", "Wed"), ("thu", "Thu"),
("fri", "Fri"), ("sat", "Sat"), ("sun", "Sun")]
days_parts = [await render_to_sx("events-day-all-checkbox", checked=None)]
days_parts = [sx_call("events-day-all-checkbox", checked=None)]
for key, label in day_keys:
days_parts.append(await render_to_sx("events-day-checkbox", name=key, label=label, checked=None))
days_parts.append(sx_call("events-day-checkbox", name=key, label=label, checked=None))
days_html = "".join(days_parts)
return await render_to_sx("events-slot-add-form",
return sx_call("events-slot-add-form",
post_url=post_url, csrf=csrf_hdr,
days=SxExpr(days_html),
action_btn=action_btn, cancel_btn=cancel_btn,
cancel_url=cancel_url)
async def render_slot_add_button(calendar) -> str:
def render_slot_add_button(calendar) -> str:
"""Render slot add button (replaces _types/slots/_add_button.html)."""
from quart import url_for, g
@@ -369,4 +369,4 @@ async def render_slot_add_button(calendar) -> str:
cal_slug = getattr(calendar, "slug", "")
add_url = url_for("calendar.slots.add_form", calendar_slug=cal_slug)
return await render_to_sx("events-slot-add-button", pre_action=pre_action, add_url=add_url)
return sx_call("events-slot-add-button", pre_action=pre_action, add_url=add_url)

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from markupsafe import escape
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
from .utils import (
@@ -15,7 +15,7 @@ from .utils import (
# Ticket widget (inline +/- for entry cards)
# ---------------------------------------------------------------------------
async def _ticket_widget_html(entry, qty: int, ticket_url: str, *, ctx: dict) -> str:
def _ticket_widget_html(entry, qty: int, ticket_url: str, *, ctx: dict) -> str:
"""Render the inline +/- ticket widget."""
csrf_token_val = ""
if ctx:
@@ -32,22 +32,22 @@ async def _ticket_widget_html(entry, qty: int, ticket_url: str, *, ctx: dict) ->
tp = getattr(entry, "ticket_price", 0) or 0
tgt = f"#page-ticket-{eid}"
async def _tw_form(count_val, btn_html):
return await render_to_sx("events-tw-form",
def _tw_form(count_val, btn_html):
return sx_call("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 = await _tw_form(1, await render_to_sx("events-tw-cart-plus"))
inner = _tw_form(1, sx_call("events-tw-cart-plus"))
else:
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"))
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"))
inner = minus + cart_icon + plus
return await render_to_sx("events-tw-widget",
entry_id=str(eid), price=f"£{tp:.2f}",
return sx_call("events-tw-widget",
entry_id=str(eid), price=f"\u00a3{tp:.2f}",
inner=SxExpr(inner))
@@ -55,7 +55,7 @@ async def _ticket_widget_html(entry, qty: int, ticket_url: str, *, ctx: dict) ->
# Tickets main panel (my tickets)
# ---------------------------------------------------------------------------
async def _tickets_main_panel_html(ctx: dict, tickets: list) -> str:
def _tickets_main_panel_html(ctx: dict, tickets: list) -> str:
"""Render my tickets list."""
from quart import url_for
@@ -75,16 +75,16 @@ async 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(await render_to_sx("events-ticket-card",
ticket_cards.append(sx_call("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(await _ticket_state_badge_html(state)),
badge=SxExpr(_ticket_state_badge_html(state)),
code_prefix=ticket.code[:8]))
cards_html = "".join(ticket_cards)
return await render_to_sx("events-tickets-panel",
return sx_call("events-tickets-panel",
list_container=_list_container(ctx),
has_tickets=bool(tickets), cards=SxExpr(cards_html))
@@ -93,7 +93,7 @@ async def _tickets_main_panel_html(ctx: dict, tickets: list) -> str:
# Ticket detail panel
# ---------------------------------------------------------------------------
async def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
"""Render a single ticket detail with QR code."""
from quart import url_for
@@ -110,7 +110,7 @@ async def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
back_href = url_for("defpage_my_tickets")
# Badge with larger sizing
badge = (await _ticket_state_badge_html(state)).replace('px-2 py-0.5 text-xs', 'px-3 py-1 text-sm')
badge = (_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
@@ -129,7 +129,7 @@ async def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
"}})()"
)
return await render_to_sx("events-ticket-detail",
return sx_call("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,
@@ -143,7 +143,7 @@ async def _ticket_detail_panel_html(ctx: dict, ticket) -> str:
# Ticket admin main panel
# ---------------------------------------------------------------------------
async def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -> str:
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")
@@ -160,7 +160,7 @@ async def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -
]:
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 += await render_to_sx("events-ticket-admin-stat",
stats_html += sx_call("events-ticket-admin-stat",
border=border, bg=bg, text_cls=text_cls,
label_cls=lbl_cls, value=str(val), label=label)
@@ -174,29 +174,29 @@ async def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -
date_html = ""
if entry and entry.start_at:
date_html = await render_to_sx("events-ticket-admin-date",
date_html = sx_call("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 = await render_to_sx("events-ticket-admin-checkin-form",
action_html = sx_call("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 = await render_to_sx("events-ticket-admin-checked-in",
action_html = sx_call("events-ticket-admin-checked-in",
time_str=t_str)
rows_html += await render_to_sx("events-ticket-admin-row",
rows_html += sx_call("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(await _ticket_state_badge_html(state)),
badge=SxExpr(_ticket_state_badge_html(state)),
action=SxExpr(action_html))
return await render_to_sx("events-ticket-admin-panel",
return sx_call("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))
@@ -206,19 +206,19 @@ async def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -
# Public render: ticket widget
# ---------------------------------------------------------------------------
async def render_ticket_widget(entry, qty: int, ticket_url: str) -> str:
def render_ticket_widget(entry, qty: int, ticket_url: str) -> str:
"""Render the +/- ticket widget for page_summary / all_events adjust_ticket."""
return await _ticket_widget_html(entry, qty, ticket_url, ctx={})
return _ticket_widget_html(entry, qty, ticket_url, ctx={})
# ---------------------------------------------------------------------------
# Ticket admin: checkin result
# ---------------------------------------------------------------------------
async def render_checkin_result(success: bool, error: str | None, ticket) -> str:
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 await render_to_sx("events-checkin-error",
return sx_call("events-checkin-error",
message=error or "Check-in failed")
if not ticket:
return ""
@@ -230,15 +230,15 @@ async def render_checkin_result(success: bool, error: str | None, ticket) -> str
date_html = ""
if entry and entry.start_at:
date_html = await render_to_sx("events-ticket-admin-date",
date_html = sx_call("events-ticket-admin-date",
date_str=entry.start_at.strftime("%d %b %Y, %H:%M"))
return await render_to_sx("events-checkin-success-row",
return sx_call("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(await _ticket_state_badge_html("checked_in")),
badge=SxExpr(_ticket_state_badge_html("checked_in")),
time_str=time_str)
@@ -246,13 +246,13 @@ async def render_checkin_result(success: bool, error: str | None, ticket) -> str
# Ticket admin: lookup result
# ---------------------------------------------------------------------------
async def render_lookup_result(ticket, error: str | None) -> str:
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 await render_to_sx("events-lookup-error", message=error)
return sx_call("events-lookup-error", message=error)
if not ticket:
return ""
@@ -264,34 +264,34 @@ async def render_lookup_result(ticket, error: str | None) -> str:
csrf = generate_csrf_token()
# Info section
info_html = await render_to_sx("events-lookup-info",
info_html = sx_call("events-lookup-info",
entry_name=entry.name if entry else "Unknown event")
if tt:
info_html += await render_to_sx("events-lookup-type", type_name=tt.name)
info_html += sx_call("events-lookup-type", type_name=tt.name)
if entry and entry.start_at:
info_html += await render_to_sx("events-lookup-date",
info_html += sx_call("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 += 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)
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)
if checked_in_at:
info_html += await render_to_sx("events-lookup-checkin-time",
info_html += sx_call("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 = await render_to_sx("events-lookup-checkin-btn",
action_html = sx_call("events-lookup-checkin-btn",
checkin_url=checkin_url, code=code, csrf=csrf)
elif state == "checked_in":
action_html = await render_to_sx("events-lookup-checked-in")
action_html = sx_call("events-lookup-checked-in")
elif state == "cancelled":
action_html = await render_to_sx("events-lookup-cancelled")
action_html = sx_call("events-lookup-cancelled")
return await render_to_sx("events-lookup-card",
return sx_call("events-lookup-card",
info=SxExpr(info_html), code=code, action=SxExpr(action_html))
@@ -299,7 +299,7 @@ async def render_lookup_result(ticket, error: str | None) -> str:
# Ticket admin: entry tickets table
# ---------------------------------------------------------------------------
async def render_entry_tickets_admin(entry, tickets: list) -> str:
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
@@ -318,26 +318,26 @@ async 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 = await render_to_sx("events-entry-tickets-admin-checkin",
action_html = sx_call("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 = await render_to_sx("events-ticket-admin-checked-in",
action_html = sx_call("events-ticket-admin-checked-in",
time_str=t_str)
rows_html += await render_to_sx("events-entry-tickets-admin-row",
rows_html += sx_call("events-entry-tickets-admin-row",
code=code, code_short=code[:12] + "...",
type_name=tt.name if tt else "\u2014",
badge=SxExpr(await _ticket_state_badge_html(state)),
badge=SxExpr(_ticket_state_badge_html(state)),
action=SxExpr(action_html))
if tickets:
body_html = await render_to_sx("events-entry-tickets-admin-table",
body_html = sx_call("events-entry-tickets-admin-table",
rows=SxExpr(rows_html))
else:
body_html = await render_to_sx("events-entry-tickets-admin-empty")
body_html = sx_call("events-entry-tickets-admin-empty")
return await render_to_sx("events-entry-tickets-admin-panel",
return sx_call("events-entry-tickets-admin-panel",
entry_name=entry.name,
count_label=f"{count} ticket{suffix}",
body=SxExpr(body_html))
@@ -347,7 +347,7 @@ async def render_entry_tickets_admin(entry, tickets: list) -> str:
# Ticket type main panel
# ---------------------------------------------------------------------------
async def render_ticket_type_main_panel(ticket_type, entry, calendar, day, month, year, *, oob: bool = False) -> str:
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
@@ -367,14 +367,14 @@ async def render_ticket_type_main_panel(ticket_type, entry, calendar, day, month
year=year, month=month, day=day, entry_id=entry.id,
)
async def _col(label, val):
return await render_to_sx("events-ticket-type-col", label=label, value=val)
def _col(label, val):
return sx_call("events-ticket-type-col", label=label, value=val)
return await render_to_sx("events-ticket-type-panel",
return sx_call("events-ticket-type-panel",
ticket_id=tid, list_container=list_container,
c1=await _col("Name", ticket_type.name),
c2=await _col("Cost", cost_str),
c3=await _col("Count", str(count)),
c1=_col("Name", ticket_type.name),
c2=_col("Cost", cost_str),
c3=_col("Count", str(count)),
pre_action=pre_action, edit_url=edit_url)
@@ -382,7 +382,7 @@ async def render_ticket_type_main_panel(ticket_type, entry, calendar, day, month
# Ticket types table
# ---------------------------------------------------------------------------
async def render_ticket_types_table(ticket_types, entry, calendar, day, month, year) -> str:
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
@@ -413,7 +413,7 @@ async def render_ticket_types_table(ticket_types, entry, calendar, day, month, y
cost = getattr(tt, "cost", None)
cost_str = f"\u00a3{cost:.2f}" if cost is not None else "\u00a30.00"
rows_html += await render_to_sx("events-ticket-types-row",
rows_html += sx_call("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,
@@ -421,14 +421,14 @@ async def render_ticket_types_table(ticket_types, entry, calendar, day, month, y
del_url=del_url,
csrf_hdr=f'{{"X-CSRFToken": "{csrf}"}}')
else:
rows_html = await render_to_sx("events-ticket-types-empty-row")
rows_html = sx_call("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 await render_to_sx("events-ticket-types-table",
return sx_call("events-ticket-types-table",
list_container=list_container, rows=SxExpr(rows_html),
action_btn=action_btn, add_url=add_url)
@@ -437,11 +437,11 @@ async def render_ticket_types_table(ticket_types, entry, calendar, day, month, y
# Buy result (ticket purchase confirmation)
# ---------------------------------------------------------------------------
async def render_buy_result(entry, created_tickets, remaining, cart_count) -> str:
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 = await _cart_icon_oob(cart_count)
cart_html = _cart_icon_oob(cart_count)
count = len(created_tickets)
suffix = "s" if count != 1 else ""
@@ -449,18 +449,18 @@ async def render_buy_result(entry, created_tickets, remaining, cart_count) -> st
tickets_html = ""
for ticket in created_tickets:
href = url_for("defpage_ticket_detail", code=ticket.code)
tickets_html += await render_to_sx("events-buy-result-ticket",
tickets_html += sx_call("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 = await render_to_sx("events-buy-result-remaining",
remaining_html = sx_call("events-buy-result-remaining",
text=f"{remaining} ticket{r_suffix} remaining")
my_href = url_for("defpage_my_tickets")
return cart_html + await render_to_sx("events-buy-result",
return cart_html + sx_call("events-buy-result",
entry_id=str(entry.id),
count_label=f"{count} ticket{suffix} reserved",
tickets=SxExpr(tickets_html),
@@ -472,7 +472,7 @@ async def render_buy_result(entry, created_tickets, remaining, cart_count) -> st
# Buy form (ticket +/- controls)
# ---------------------------------------------------------------------------
async def render_buy_form(entry, ticket_remaining, ticket_sold_count,
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
@@ -489,7 +489,7 @@ async def render_buy_form(entry, ticket_remaining, ticket_sold_count,
return ""
if state != "confirmed":
return await render_to_sx("events-buy-not-confirmed", entry_id=eid_s)
return sx_call("events-buy-not-confirmed", entry_id=eid_s)
adjust_url = url_for("tickets.adjust_quantity")
target = f"#ticket-buy-{eid}"
@@ -498,16 +498,16 @@ async def render_buy_form(entry, ticket_remaining, ticket_sold_count,
info_html = ""
info_items = ""
if ticket_sold_count:
info_items += await render_to_sx("events-buy-info-sold",
info_items += sx_call("events-buy-info-sold",
count=str(ticket_sold_count))
if ticket_remaining is not None:
info_items += await render_to_sx("events-buy-info-remaining",
info_items += sx_call("events-buy-info-remaining",
count=str(ticket_remaining))
if user_ticket_count:
info_items += await render_to_sx("events-buy-info-basket",
info_items += sx_call("events-buy-info-basket",
count=str(user_ticket_count))
if info_items:
info_html = await render_to_sx("events-buy-info-bar", items=SxExpr(info_items))
info_html = sx_call("events-buy-info-bar", items=SxExpr(info_items))
active_types = [tt for tt in ticket_types if getattr(tt, "deleted_at", None) is None]
@@ -517,46 +517,46 @@ async 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 += await render_to_sx("events-buy-type-item",
type_items += sx_call("events-buy-type-item",
type_name=tt.name, cost_str=cost_str,
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))
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))
else:
qty = user_ticket_count or 0
body_html = await render_to_sx("events-buy-default",
body_html = sx_call("events-buy-default",
price_str=f"\u00a3{tp:.2f}",
adjust_controls=SxExpr(await _ticket_adjust_controls(csrf, adjust_url, target, eid, qty)))
adjust_controls=SxExpr(_ticket_adjust_controls(csrf, adjust_url, target, eid, qty)))
return await render_to_sx("events-buy-panel",
return sx_call("events-buy-panel",
entry_id=eid_s, info=SxExpr(info_html), body=SxExpr(body_html))
async def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket_type_id=None):
def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket_type_id=None):
"""Render +/- ticket controls for buy form."""
from quart import url_for
tt_html = await render_to_sx("events-adjust-tt-hidden",
tt_html = sx_call("events-adjust-tt-hidden",
ticket_type_id=str(ticket_type_id)) if ticket_type_id else ""
eid_s = str(entry_id)
async def _adj_form(count_val, btn_html, *, extra_cls=""):
return await render_to_sx("events-adjust-form",
def _adj_form(count_val, btn_html, *, extra_cls=""):
return sx_call("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 await _adj_form(1, await render_to_sx("events-adjust-cart-plus"),
return _adj_form(1, sx_call("events-adjust-cart-plus"),
extra_cls="flex items-center")
my_tickets_href = url_for("defpage_my_tickets")
minus = await _adj_form(count - 1, await render_to_sx("events-adjust-minus"))
cart_icon = await render_to_sx("events-adjust-cart-icon",
minus = _adj_form(count - 1, sx_call("events-adjust-minus"))
cart_icon = sx_call("events-adjust-cart-icon",
href=my_tickets_href, count=str(count))
plus = await _adj_form(count + 1, await render_to_sx("events-adjust-plus"))
plus = _adj_form(count + 1, sx_call("events-adjust-plus"))
return await render_to_sx("events-adjust-controls",
return sx_call("events-adjust-controls",
minus=SxExpr(minus), cart_icon=SxExpr(cart_icon), plus=SxExpr(plus))
@@ -564,12 +564,12 @@ async def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *,
# Adjust response (OOB cart icon + buy form)
# ---------------------------------------------------------------------------
async def render_adjust_response(entry, ticket_remaining, ticket_sold_count,
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 = await _cart_icon_oob(cart_count)
form_html = await render_buy_form(
cart_html = _cart_icon_oob(cart_count)
form_html = render_buy_form(
entry, ticket_remaining, ticket_sold_count,
user_ticket_count, user_ticket_counts_by_type,
)
@@ -580,7 +580,7 @@ async def render_adjust_response(entry, ticket_remaining, ticket_sold_count,
# Ticket types header rows
# ---------------------------------------------------------------------------
async def _ticket_types_header_html(ctx: dict, *, oob: bool = False) -> str:
def _ticket_types_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build the ticket types header row."""
from quart import url_for
@@ -599,15 +599,15 @@ async def _ticket_types_header_html(ctx: dict, *, oob: bool = False) -> str:
label_html = '<i class="fa fa-ticket"></i><div class="shrink-0">ticket types</div>'
nav_html = await render_to_sx("events-admin-placeholder-nav")
nav_html = sx_call("events-admin-placeholder-nav")
return await render_to_sx("menu-row-sx", id="ticket_types-row", level=7,
return sx_call("menu-row-sx", id="ticket_types-row", level=7,
link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="ticket_type-header-child", oob=oob)
async def _ticket_type_header_html(ctx: dict, *, oob: bool = False) -> str:
def _ticket_type_header_html(ctx: dict, *, oob: bool = False) -> str:
"""Build the single ticket type header row."""
from quart import url_for
@@ -635,9 +635,9 @@ async def _ticket_type_header_html(ctx: dict, *, oob: bool = False) -> str:
f'</div></div>'
)
nav_html = await render_to_sx("events-admin-placeholder-nav")
nav_html = sx_call("events-admin-placeholder-nav")
return await render_to_sx("menu-row-sx", id="ticket_type-row", level=8,
return sx_call("menu-row-sx", id="ticket_type-row", level=8,
link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="ticket_type-header-child-inner", oob=oob)
@@ -646,7 +646,7 @@ async def _ticket_type_header_html(ctx: dict, *, oob: bool = False) -> str:
# Ticket type edit form
# ---------------------------------------------------------------------------
async def render_ticket_type_edit_form(ticket_type, entry, calendar, day, month, year) -> str:
def render_ticket_type_edit_form(ticket_type, entry, calendar, day, month, year) -> str:
"""Render ticket type edit form (replaces _types/ticket_type/_edit.html)."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -670,7 +670,7 @@ async def render_ticket_type_edit_form(ticket_type, entry, calendar, day, month,
cost_val = f"{cost:.2f}" if cost is not None else ""
count = getattr(ticket_type, "count", 0)
return await render_to_sx("events-ticket-type-edit-form",
return sx_call("events-ticket-type-edit-form",
ticket_id=str(tid), list_container=list_container,
put_url=put_url, cancel_url=cancel_url, csrf=csrf,
name_val=ticket_type.name or "",
@@ -682,7 +682,7 @@ async def render_ticket_type_edit_form(ticket_type, entry, calendar, day, month,
# Ticket type add form / button
# ---------------------------------------------------------------------------
async def render_ticket_type_add_form(entry, calendar, day, month, year) -> str:
def render_ticket_type_add_form(entry, calendar, day, month, year) -> str:
"""Render ticket type add form (replaces _types/ticket_types/_add.html)."""
from quart import url_for, g
from shared.browser.app.csrf import generate_csrf_token
@@ -701,13 +701,13 @@ async def render_ticket_type_add_form(entry, calendar, day, month, year) -> str:
year=year, month=month, day=day)
csrf_hdr = f'{{"X-CSRFToken": "{csrf}"}}'
return await render_to_sx("events-ticket-type-add-form",
return sx_call("events-ticket-type-add-form",
post_url=post_url, csrf=csrf_hdr,
action_btn=action_btn, cancel_btn=cancel_btn,
cancel_url=cancel_url)
async def render_ticket_type_add_button(entry, calendar, day, month, year) -> str:
def render_ticket_type_add_button(entry, calendar, day, month, year) -> str:
"""Render ticket type add button (replaces _types/ticket_types/_add_button.html)."""
from quart import url_for, g
@@ -719,5 +719,5 @@ async def render_ticket_type_add_button(entry, calendar, day, month, year) -> st
calendar_slug=cal_slug, entry_id=entry.id,
year=year, month=month, day=day)
return await render_to_sx("events-ticket-type-add-button",
return sx_call("events-ticket-type-add-button",
action_btn=action_btn, add_url=add_url)

View File

@@ -1,7 +1,7 @@
"""Badge helpers, OOB helpers, formatting utilities, list containers."""
from __future__ import annotations
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
@@ -74,7 +74,7 @@ def _list_container(ctx: dict) -> str:
return getattr(styles, "list_container", "") if hasattr(styles, "list_container") else styles.get("list_container", "")
async def _entry_state_badge_html(state: str) -> str:
def _entry_state_badge_html(state: str) -> str:
"""Render an entry state badge."""
state_classes = {
"confirmed": "bg-emerald-100 text-emerald-800",
@@ -85,10 +85,10 @@ async def _entry_state_badge_html(state: str) -> str:
}
cls = state_classes.get(state, "bg-stone-100 text-stone-700")
label = state.replace("_", " ").capitalize()
return await render_to_sx("badge", cls=cls, label=label)
return sx_call("badge", cls=cls, label=label)
async def _ticket_state_badge_html(state: str) -> str:
def _ticket_state_badge_html(state: str) -> str:
"""Render a ticket state badge."""
cls_map = {
"confirmed": "bg-emerald-100 text-emerald-800",
@@ -98,7 +98,7 @@ async 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 await render_to_sx("badge", cls=cls, label=label)
return sx_call("badge", cls=cls, label=label)
# ---------------------------------------------------------------------------
@@ -109,21 +109,21 @@ _LIST_SVG = None
_TILE_SVG = None
async def _get_list_svg():
def _get_list_svg():
global _LIST_SVG
if _LIST_SVG is None:
_LIST_SVG = await render_to_sx("list-svg")
_LIST_SVG = sx_call("list-svg")
return _LIST_SVG
async def _get_tile_svg():
def _get_tile_svg():
global _TILE_SVG
if _TILE_SVG is None:
_TILE_SVG = await render_to_sx("tile-svg")
_TILE_SVG = sx_call("tile-svg")
return _TILE_SVG
async def _view_toggle_html(ctx: dict, view: str) -> str:
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()
@@ -142,14 +142,14 @@ async 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 await render_to_sx("view-toggle",
return sx_call("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(await _get_list_svg()), tile_svg=SxExpr(await _get_tile_svg()))
list_svg=SxExpr(_get_list_svg()), tile_svg=SxExpr(_get_tile_svg()))
async def _cart_icon_oob(count: int) -> str:
def _cart_icon_oob(count: int) -> str:
"""Render the OOB cart icon/badge swap."""
from quart import g
@@ -163,9 +163,9 @@ async def _cart_icon_oob(count: int) -> str:
if count == 0:
blog_href = blog_url_fn("/") if blog_url_fn else "/"
return await render_to_sx("events-cart-icon-logo",
return sx_call("events-cart-icon-logo",
blog_href=blog_href, logo=logo)
cart_href = cart_url_fn("/") if cart_url_fn else "/"
return await render_to_sx("events-cart-icon-badge",
return sx_call("events-cart-icon-badge",
cart_href=cart_href, count=str(count))

View File

@@ -44,11 +44,11 @@ ALLOWED_CLIENTS = {"blog", "market", "cart", "events", "account"}
async def _render_social_auth_page(component: str, title: str, **kwargs) -> str:
"""Render an auth page with social layout — replaces sx_components helpers."""
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.page import get_template_context
from sxc.pages.utils import _social_page
ctx = await get_template_context()
content = await render_to_sx(component, **{k: v for k, v in kwargs.items() if v})
content = sx_call(component, **{k: v for k, v in kwargs.items() if v})
return await _social_page(ctx, None, content=content, title=title)

View File

@@ -30,7 +30,7 @@ async def _render_choose_username(*, actor=None, error="", username=""):
"""Render choose-username page — replaces sx_components helper."""
from shared.browser.app.csrf import generate_csrf_token
from shared.config import config
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
from shared.sx.page import get_template_context
from sxc.pages.utils import _social_page
@@ -41,8 +41,8 @@ async def _render_choose_username(*, actor=None, error="", username=""):
ap_domain = config().get("ap_domain", "rose-ash.com")
check_url = url_for("identity.check_username")
error_sx = await render_to_sx("auth-error-banner", error=error) if error else ""
content = await render_to_sx(
error_sx = sx_call("auth-error-banner", error=error) if error else ""
content = sx_call(
"federation-choose-username",
domain=str(escape(ap_domain)),
error=SxExpr(error_sx) if error_sx else None,

View File

@@ -7,7 +7,7 @@ from datetime import datetime
from quart import Blueprint, request, g, redirect, url_for, abort, Response
from shared.services.registry import services
from shared.sx.helpers import sx_response, render_to_sx
from shared.sx.helpers import sx_response, sx_call
log = logging.getLogger(__name__)
@@ -47,7 +47,7 @@ def register(url_prefix="/social"):
items = await services.federation.get_home_timeline(
g.s, actor.id, before=before,
)
sx_src = await _render_timeline_items(items, "home", actor)
sx_src = _render_timeline_items(items, "home", actor)
return sx_response(sx_src)
@bp.get("/public/timeline")
@@ -61,7 +61,7 @@ def register(url_prefix="/social"):
pass
items = await services.federation.get_public_timeline(g.s, before=before)
actor = getattr(g, "_social_actor", None)
sx_src = await _render_timeline_items(items, "public", actor)
sx_src = _render_timeline_items(items, "public", actor)
return sx_response(sx_src)
# -- Compose ---------------------------------------------------------------
@@ -117,12 +117,12 @@ def register(url_prefix="/social"):
actor_data = _serialize_actor(actor)
parts = []
for ad in actor_dicts:
parts.append(await render_to_sx("federation-actor-card-from-data",
parts.append(sx_call("federation-actor-card-from-data",
a=ad, actor=actor_data,
followed_urls=list(followed_urls), list_type="search"))
if len(actors_list) >= 20:
next_url = url_for("social.search_page", q=query, page=page + 1)
parts.append(await render_to_sx("federation-scroll-sentinel", url=next_url))
parts.append(sx_call("federation-scroll-sentinel", url=next_url))
sx_src = "(<> " + " ".join(parts) + ")" if parts else ""
return sx_response(sx_src)
@@ -166,7 +166,7 @@ def register(url_prefix="/social"):
list_type = "followers" if "/followers" in referer else "following"
actor_data = _serialize_actor(actor)
ad = _serialize_remote_actor(remote_dto)
return sx_response(await render_to_sx("federation-actor-card-from-data",
return sx_response(sx_call("federation-actor-card-from-data",
a=ad, actor=actor_data,
followed_urls=list(followed_urls), list_type=list_type))
@@ -277,19 +277,19 @@ def register(url_prefix="/social"):
boost_cls = "hover:text-green-600"
reply_url = url_for("social.defpage_compose_form", reply_to=object_id) if object_id else ""
reply_sx = await render_to_sx("federation-reply-link", url=reply_url) if reply_url else ""
reply_sx = sx_call("federation-reply-link", url=reply_url) if reply_url else ""
like_form = await render_to_sx("federation-like-form",
like_form = sx_call("federation-like-form",
action=like_action, target=target, oid=object_id, ainbox=author_inbox,
csrf=csrf, cls=f"flex items-center gap-1 {like_cls}",
icon=like_icon, count=str(like_count))
boost_form = await render_to_sx("federation-boost-form",
boost_form = sx_call("federation-boost-form",
action=boost_action, target=target, oid=object_id, ainbox=author_inbox,
csrf=csrf, cls=f"flex items-center gap-1 {boost_cls}",
count=str(boost_count))
return sx_response(await render_to_sx("federation-interaction-buttons",
return sx_response(sx_call("federation-interaction-buttons",
like=SxExpr(like_form),
boost=SxExpr(boost_form),
reply=SxExpr(reply_sx) if reply_sx else None))
@@ -309,12 +309,12 @@ def register(url_prefix="/social"):
actor_data = _serialize_actor(actor)
parts = []
for ad in actor_dicts:
parts.append(await render_to_sx("federation-actor-card-from-data",
parts.append(sx_call("federation-actor-card-from-data",
a=ad, actor=actor_data,
followed_urls=[], list_type="following"))
if len(actors_list) >= 20:
next_url = url_for("social.following_list_page", page=page + 1)
parts.append(await render_to_sx("federation-scroll-sentinel", url=next_url))
parts.append(sx_call("federation-scroll-sentinel", url=next_url))
sx_src = "(<> " + " ".join(parts) + ")" if parts else ""
return sx_response(sx_src)
@@ -335,12 +335,12 @@ def register(url_prefix="/social"):
actor_data = _serialize_actor(actor)
parts = []
for ad in actor_dicts:
parts.append(await render_to_sx("federation-actor-card-from-data",
parts.append(sx_call("federation-actor-card-from-data",
a=ad, actor=actor_data,
followed_urls=list(followed_urls), list_type="followers"))
if len(actors_list) >= 20:
next_url = url_for("social.followers_list_page", page=page + 1)
parts.append(await render_to_sx("federation-scroll-sentinel", url=next_url))
parts.append(sx_call("federation-scroll-sentinel", url=next_url))
sx_src = "(<> " + " ".join(parts) + ")" if parts else ""
return sx_response(sx_src)
@@ -357,7 +357,7 @@ def register(url_prefix="/social"):
items = await services.federation.get_actor_timeline(
g.s, id, before=before,
)
sx_src = await _render_timeline_items(items, "actor", actor, id)
sx_src = _render_timeline_items(items, "actor", actor, id)
return sx_response(sx_src)
# -- Notifications ---------------------------------------------------------
@@ -385,7 +385,7 @@ def register(url_prefix="/social"):
return bp
async def _render_timeline_items(items, timeline_type, actor, actor_id=None):
def _render_timeline_items(items, timeline_type, actor, actor_id=None):
"""Render timeline pagination items as SX fragment."""
from sxc.pages.utils import _serialize_timeline_item, _serialize_actor
@@ -401,7 +401,7 @@ async def _render_timeline_items(items, timeline_type, actor, actor_id=None):
else:
next_url = url_for(f"social.{timeline_type}_timeline_page", before=before)
return await render_to_sx("federation-timeline-items",
return sx_call("federation-timeline-items",
items=item_dicts,
timeline_type=timeline_type,
actor=actor_data,

View File

@@ -47,7 +47,7 @@ def register() -> Blueprint:
markets, has_more, page_info = await _load_markets(page)
from sxc.pages.renders import render_all_markets_cards
sx_src = await render_all_markets_cards(markets, has_more, page_info, page)
sx_src = render_all_markets_cards(markets, has_more, page_info, page)
return sx_response(sx_src)
return bp

View File

@@ -58,7 +58,7 @@ def register():
resp = await make_response(html)
elif product_info["page"] > 1:
tctx.update(product_info)
sx_src = await render_browse_cards(tctx)
sx_src = render_browse_cards(tctx)
resp = sx_response(sx_src)
else:
sx_src = await render_browse_oob(tctx)
@@ -99,7 +99,7 @@ def register():
resp = await make_response(html)
elif product_info["page"] > 1:
tctx.update(product_info)
sx_src = await render_browse_cards(tctx)
sx_src = render_browse_cards(tctx)
resp = sx_response(sx_src)
else:
sx_src = await render_browse_oob(tctx)
@@ -140,7 +140,7 @@ def register():
resp = await make_response(html)
elif product_info["page"] > 1:
tctx.update(product_info)
sx_src = await render_browse_cards(tctx)
sx_src = render_browse_cards(tctx)
resp = sx_response(sx_src)
else:
sx_src = await render_browse_oob(tctx)

View File

@@ -32,7 +32,7 @@ def register() -> Blueprint:
from sxc.pages.renders import render_page_markets_cards
post_slug = post.get("slug", "")
sx_src = await render_page_markets_cards(markets, has_more, page, post_slug)
sx_src = render_page_markets_cards(markets, has_more, page, post_slug)
return sx_response(sx_src)
return bp

View File

@@ -129,7 +129,7 @@ def register():
from sxc.pages.renders import render_like_toggle_button
if not g.user:
return sx_response(await render_like_toggle_button(product_slug, False), status=403)
return sx_response(render_like_toggle_button(product_slug, False), status=403)
user_id = g.user.id
@@ -138,7 +138,7 @@ def register():
})
liked = result["liked"]
return sx_response(await render_like_toggle_button(product_slug, liked))
return sx_response(render_like_toggle_button(product_slug, liked))
@@ -257,7 +257,7 @@ def register():
from sxc.pages.renders import render_cart_added_response
item_data = getattr(g, "item_data", {})
d = item_data.get("d", {})
return sx_response(await render_cart_added_response(g.cart, ci_ns, d))
return sx_response(render_cart_added_response(g.cart, ci_ns, d))
# normal POST: go to cart page
from shared.infrastructure.urls import cart_url

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
from typing import Any
from shared.sx.parser import SxExpr
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from .utils import _set_prices, _price_str
from .filters import _MOBILE_SENTINEL_HS, _DESKTOP_SENTINEL_HS
@@ -14,7 +14,7 @@ from .filters import _MOBILE_SENTINEL_HS, _DESKTOP_SENTINEL_HS
# Product card (browse grid item)
# ---------------------------------------------------------------------------
async def _product_card_sx(p: dict, ctx: dict) -> str:
def _product_card_sx(p: dict, ctx: dict) -> str:
"""Build a single product card for browse grid as sx call."""
from quart import url_for
from shared.browser.app.csrf import generate_csrf_token
@@ -98,10 +98,10 @@ async def _product_card_sx(p: dict, ctx: dict) -> str:
kwargs["search_mid"] = search_mid
kwargs["search_post"] = search_post
return await render_to_sx("market-product-card", **kwargs)
return sx_call("market-product-card", **kwargs)
async def _product_cards_sx(ctx: dict) -> str:
def _product_cards_sx(ctx: dict) -> str:
"""S-expression wire format for product cards (client renders)."""
from shared.utils import route_prefix
@@ -114,7 +114,7 @@ async def _product_cards_sx(ctx: dict) -> str:
parts = []
for p in products:
parts.append(await _product_card_sx(p, ctx))
parts.append(_product_card_sx(p, ctx))
if page < total_pages:
if callable(qs_fn):
@@ -122,25 +122,25 @@ async def _product_cards_sx(ctx: dict) -> str:
else:
next_qs = f"?page={page + 1}"
next_url = prefix + current_local_href + next_qs
parts.append(await render_to_sx("sentinel-mobile",
parts.append(sx_call("sentinel-mobile",
id=f"sentinel-{page}-m", next_url=next_url,
hyperscript=_MOBILE_SENTINEL_HS))
parts.append(await render_to_sx("sentinel-desktop",
parts.append(sx_call("sentinel-desktop",
id=f"sentinel-{page}-d", next_url=next_url,
hyperscript=_DESKTOP_SENTINEL_HS))
else:
parts.append(await render_to_sx("end-of-results"))
parts.append(sx_call("end-of-results"))
return "(<> " + " ".join(parts) + ")"
async def _like_button_sx(slug: str, liked: bool, csrf: str, ctx: dict) -> str:
def _like_button_sx(slug: str, liked: bool, csrf: str, ctx: dict) -> str:
"""Build the like/unlike heart button overlay as sx."""
from quart import url_for
action = url_for("market.browse.product.like_toggle", product_slug=slug)
icon_cls = "fa-solid fa-heart text-red-500" if liked else "fa-regular fa-heart text-stone-400"
return await render_to_sx(
return sx_call(
"market-like-button",
form_id=f"like-{slug}", action=action, slug=slug,
csrf=csrf, icon_cls=icon_cls,
@@ -151,7 +151,7 @@ async def _like_button_sx(slug: str, liked: bool, csrf: str, ctx: dict) -> str:
# Market cards (all markets / page markets)
# ---------------------------------------------------------------------------
async def _market_card_sx(market: Any, page_info: dict, *, show_page_badge: bool = True,
def _market_card_sx(market: Any, page_info: dict, *, show_page_badge: bool = True,
post_slug: str = "") -> str:
"""Build a single market card as sx."""
from shared.infrastructure.urls import market_url
@@ -173,20 +173,20 @@ async def _market_card_sx(market: Any, page_info: dict, *, show_page_badge: bool
title_sx = ""
if market_href:
title_sx = await render_to_sx("market-market-card-title-link", href=market_href, name=name)
title_sx = sx_call("market-market-card-title-link", href=market_href, name=name)
else:
title_sx = await render_to_sx("market-market-card-title", name=name)
title_sx = sx_call("market-market-card-title", name=name)
desc_sx = ""
if description:
desc_sx = await render_to_sx("market-market-card-desc", description=description)
desc_sx = sx_call("market-market-card-desc", description=description)
badge_sx = ""
if show_page_badge and p_title:
badge_href = market_url(f"/{p_slug}/")
badge_sx = await render_to_sx("market-market-card-badge", href=badge_href, title=p_title)
badge_sx = sx_call("market-market-card-badge", href=badge_href, title=p_title)
return await render_to_sx(
return sx_call(
"market-market-card",
title_content=SxExpr(title_sx) if title_sx else None,
desc_content=SxExpr(desc_sx) if desc_sx else None,
@@ -194,30 +194,30 @@ async def _market_card_sx(market: Any, page_info: dict, *, show_page_badge: bool
)
async def _market_cards_sx(markets: list, page_info: dict, page: int, has_more: bool,
def _market_cards_sx(markets: list, page_info: dict, page: int, has_more: bool,
next_url: str, *, show_page_badge: bool = True,
post_slug: str = "") -> str:
"""Build market cards with infinite scroll sentinel as sx."""
parts = []
for m in markets:
parts.append(await _market_card_sx(m, page_info, show_page_badge=show_page_badge,
parts.append(_market_card_sx(m, page_info, show_page_badge=show_page_badge,
post_slug=post_slug))
if has_more:
parts.append(await render_to_sx(
parts.append(sx_call(
"sentinel-simple",
id=f"sentinel-{page}", next_url=next_url,
))
return "(<> " + " ".join(parts) + ")"
async def _markets_grid(cards_sx: str) -> str:
def _markets_grid(cards_sx: str) -> str:
"""Wrap market cards in a grid as sx."""
return await render_to_sx("market-markets-grid", cards=SxExpr(cards_sx))
return sx_call("market-markets-grid", cards=SxExpr(cards_sx))
async def _no_markets_sx(message: str = "No markets available") -> str:
def _no_markets_sx(message: str = "No markets available") -> str:
"""Empty state for markets as sx."""
return await render_to_sx("empty-state", icon="fa fa-store", message=message,
return sx_call("empty-state", icon="fa fa-store", message=message,
cls="px-3 py-12 text-center text-stone-400")
@@ -225,14 +225,14 @@ async def _no_markets_sx(message: str = "No markets available") -> str:
# Market landing page
# ---------------------------------------------------------------------------
async def _market_landing_content_sx(post: dict) -> str:
def _market_landing_content_sx(post: dict) -> str:
"""Build market landing page content as sx."""
parts: list[str] = []
if post.get("custom_excerpt"):
parts.append(await render_to_sx("market-landing-excerpt", text=post["custom_excerpt"]))
parts.append(sx_call("market-landing-excerpt", text=post["custom_excerpt"]))
if post.get("feature_image"):
parts.append(await render_to_sx("market-landing-image", src=post["feature_image"]))
parts.append(sx_call("market-landing-image", src=post["feature_image"]))
if post.get("html"):
parts.append(await render_to_sx("market-landing-html", html=post["html"]))
parts.append(sx_call("market-landing-html", html=post["html"]))
inner = "(<> " + " ".join(parts) + ")" if parts else "(<>)"
return await render_to_sx("market-landing-content", inner=SxExpr(inner))
return sx_call("market-landing-content", inner=SxExpr(inner))

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from shared.sx.parser import SxExpr
from shared.sx.helpers import (
render_to_sx,
sx_call,
search_mobile_sx, search_desktop_sx,
)
@@ -101,31 +101,31 @@ async def _desktop_filter_sx(ctx: dict) -> str:
search_sx = await search_desktop_sx(ctx)
# Category summary + sort + like + labels + stickers
cat_parts = [await render_to_sx("market-filter-category-label", label=category_label)]
cat_parts = [sx_call("market-filter-category-label", label=category_label)]
if sort_options:
cat_parts.append(await _sort_stickers_sx(sort_options, sort, ctx))
cat_parts.append(_sort_stickers_sx(sort_options, sort, ctx))
like_label_parts = [await _like_filter_sx(liked, liked_count, ctx)]
like_label_parts = [_like_filter_sx(liked, liked_count, ctx)]
if labels:
like_label_parts.append(await _labels_filter_sx(labels, selected_labels, ctx, prefix="nav-labels"))
like_label_parts.append(_labels_filter_sx(labels, selected_labels, ctx, prefix="nav-labels"))
like_labels_sx = "(<> " + " ".join(like_label_parts) + ")"
cat_parts.append(await render_to_sx("market-filter-like-labels-nav", inner=SxExpr(like_labels_sx)))
cat_parts.append(sx_call("market-filter-like-labels-nav", inner=SxExpr(like_labels_sx)))
if stickers:
cat_parts.append(await _stickers_filter_sx(stickers, selected_stickers, ctx))
cat_parts.append(_stickers_filter_sx(stickers, selected_stickers, ctx))
if subs_local and top_local_href:
cat_parts.append(await _subcategory_selector_sx(subs_local, top_local_href, sub_slug, ctx))
cat_parts.append(_subcategory_selector_sx(subs_local, top_local_href, sub_slug, ctx))
cat_inner_sx = "(<> " + " ".join(cat_parts) + ")"
cat_summary = await render_to_sx("market-desktop-category-summary", inner=SxExpr(cat_inner_sx))
cat_summary = sx_call("market-desktop-category-summary", inner=SxExpr(cat_inner_sx))
# Brand filter
brand_inner = ""
if brands:
brand_inner = await _brand_filter_sx(brands, selected_brands, ctx)
brand_summary = await render_to_sx("market-desktop-brand-summary",
brand_inner = _brand_filter_sx(brands, selected_brands, ctx)
brand_summary = sx_call("market-desktop-brand-summary",
inner=SxExpr(brand_inner) if brand_inner else None)
return "(<> " + " ".join([search_sx, cat_summary, brand_summary]) + ")"
@@ -154,14 +154,14 @@ async def _mobile_filter_summary_sx(ctx: dict) -> str:
if sort and sort_options:
for k, l, i in sort_options:
if k == sort and callable(asset_url_fn):
chip_parts.append(await render_to_sx("market-mobile-chip-sort", src=asset_url_fn(i), label=l))
chip_parts.append(sx_call("market-mobile-chip-sort", src=asset_url_fn(i), label=l))
if liked:
liked_parts = [await render_to_sx("market-mobile-chip-liked-icon")]
liked_parts = [sx_call("market-mobile-chip-liked-icon")]
if liked_count is not None:
cls = "text-[10px] text-stone-500" if liked_count != 0 else "text-md text-red-500 font-bold"
liked_parts.append(await render_to_sx("market-mobile-chip-count", cls=cls, count=str(liked_count)))
liked_parts.append(sx_call("market-mobile-chip-count", cls=cls, count=str(liked_count)))
liked_inner = "(<> " + " ".join(liked_parts) + ")"
chip_parts.append(await render_to_sx("market-mobile-chip-liked", inner=SxExpr(liked_inner)))
chip_parts.append(sx_call("market-mobile-chip-liked", inner=SxExpr(liked_inner)))
# Selected labels
if selected_labels:
@@ -169,18 +169,18 @@ async def _mobile_filter_summary_sx(ctx: dict) -> str:
for sl in selected_labels:
for lb in labels:
if lb.get("name") == sl and callable(asset_url_fn):
li_parts = [await render_to_sx(
li_parts = [sx_call(
"market-mobile-chip-image",
src=asset_url_fn("nav-labels/" + sl + ".svg"), name=sl,
)]
if lb.get("count") is not None:
cls = "text-[10px] text-stone-500" if lb["count"] != 0 else "text-md text-red-500 font-bold"
li_parts.append(await render_to_sx("market-mobile-chip-count", cls=cls, count=str(lb["count"])))
li_parts.append(sx_call("market-mobile-chip-count", cls=cls, count=str(lb["count"])))
li_inner = "(<> " + " ".join(li_parts) + ")"
label_item_parts.append(await render_to_sx("market-mobile-chip-item", inner=SxExpr(li_inner)))
label_item_parts.append(sx_call("market-mobile-chip-item", inner=SxExpr(li_inner)))
if label_item_parts:
label_items = "(<> " + " ".join(label_item_parts) + ")"
chip_parts.append(await render_to_sx("market-mobile-chip-list", items=SxExpr(label_items)))
chip_parts.append(sx_call("market-mobile-chip-list", items=SxExpr(label_items)))
# Selected stickers
if selected_stickers:
@@ -188,18 +188,18 @@ async def _mobile_filter_summary_sx(ctx: dict) -> str:
for ss in selected_stickers:
for st in stickers:
if st.get("name") == ss and callable(asset_url_fn):
si_parts = [await render_to_sx(
si_parts = [sx_call(
"market-mobile-chip-image",
src=asset_url_fn("stickers/" + ss + ".svg"), name=ss,
)]
if st.get("count") is not None:
cls = "text-[10px] text-stone-500" if st["count"] != 0 else "text-md text-red-500 font-bold"
si_parts.append(await render_to_sx("market-mobile-chip-count", cls=cls, count=str(st["count"])))
si_parts.append(sx_call("market-mobile-chip-count", cls=cls, count=str(st["count"])))
si_inner = "(<> " + " ".join(si_parts) + ")"
sticker_item_parts.append(await render_to_sx("market-mobile-chip-item", inner=SxExpr(si_inner)))
sticker_item_parts.append(sx_call("market-mobile-chip-item", inner=SxExpr(si_inner)))
if sticker_item_parts:
sticker_items = "(<> " + " ".join(sticker_item_parts) + ")"
chip_parts.append(await render_to_sx("market-mobile-chip-list", items=SxExpr(sticker_items)))
chip_parts.append(sx_call("market-mobile-chip-list", items=SxExpr(sticker_items)))
# Selected brands
if selected_brands:
@@ -210,21 +210,21 @@ async def _mobile_filter_summary_sx(ctx: dict) -> str:
if br.get("name") == b:
count = br.get("count", 0)
if count:
brand_item_parts.append(await render_to_sx("market-mobile-chip-brand", name=b, count=str(count)))
brand_item_parts.append(sx_call("market-mobile-chip-brand", name=b, count=str(count)))
else:
brand_item_parts.append(await render_to_sx("market-mobile-chip-brand-zero", name=b))
brand_item_parts.append(sx_call("market-mobile-chip-brand-zero", name=b))
brand_items = "(<> " + " ".join(brand_item_parts) + ")"
chip_parts.append(await render_to_sx("market-mobile-chip-brand-list", items=SxExpr(brand_items)))
chip_parts.append(sx_call("market-mobile-chip-brand-list", items=SxExpr(brand_items)))
chips_sx = "(<> " + " ".join(chip_parts) + ")" if chip_parts else '(<>)'
chips_row = await render_to_sx("market-mobile-chips-row", inner=SxExpr(chips_sx))
chips_row = sx_call("market-mobile-chips-row", inner=SxExpr(chips_sx))
# Full mobile filter details
from shared.utils import route_prefix
prefix = route_prefix()
mobile_filter = await _mobile_filter_content_sx(ctx, prefix)
mobile_filter = _mobile_filter_content_sx(ctx, prefix)
return await render_to_sx(
return sx_call(
"market-mobile-filter-summary",
search_bar=SxExpr(search_bar),
chips=SxExpr(chips_row),
@@ -232,7 +232,7 @@ async def _mobile_filter_summary_sx(ctx: dict) -> str:
)
async def _mobile_filter_content_sx(ctx: dict, prefix: str) -> str:
def _mobile_filter_content_sx(ctx: dict, prefix: str) -> str:
"""Build the expanded mobile filter panel contents as sx."""
selected_labels = ctx.get("selected_labels", [])
selected_stickers = ctx.get("selected_stickers", [])
@@ -253,33 +253,33 @@ async def _mobile_filter_content_sx(ctx: dict, prefix: str) -> str:
# Sort options
if sort_options:
parts.append(await _sort_stickers_sx(sort_options, sort, ctx, mobile=True))
parts.append(_sort_stickers_sx(sort_options, sort, ctx, mobile=True))
# Clear filters button
has_filters = search or selected_labels or selected_stickers or selected_brands
if has_filters and callable(qs_fn):
clear_url = prefix + current_local_href + qs_fn({"clear_filters": True})
parts.append(await render_to_sx("market-mobile-clear-filters", href=clear_url, hx_select=hx_select))
parts.append(sx_call("market-mobile-clear-filters", href=clear_url, hx_select=hx_select))
# Like + labels row
like_label_parts = [await _like_filter_sx(liked, liked_count, ctx, mobile=True)]
like_label_parts = [_like_filter_sx(liked, liked_count, ctx, mobile=True)]
if labels:
like_label_parts.append(await _labels_filter_sx(labels, selected_labels, ctx, prefix="nav-labels", mobile=True))
like_label_parts.append(_labels_filter_sx(labels, selected_labels, ctx, prefix="nav-labels", mobile=True))
like_labels_sx = "(<> " + " ".join(like_label_parts) + ")"
parts.append(await render_to_sx("market-mobile-like-labels-row", inner=SxExpr(like_labels_sx)))
parts.append(sx_call("market-mobile-like-labels-row", inner=SxExpr(like_labels_sx)))
# Stickers
if stickers:
parts.append(await _stickers_filter_sx(stickers, selected_stickers, ctx, mobile=True))
parts.append(_stickers_filter_sx(stickers, selected_stickers, ctx, mobile=True))
# Brands
if brands:
parts.append(await _brand_filter_sx(brands, selected_brands, ctx, mobile=True))
parts.append(_brand_filter_sx(brands, selected_brands, ctx, mobile=True))
return "(<> " + " ".join(parts) + ")" if parts else "(<>)"
async def _sort_stickers_sx(sort_options: list, current_sort: str, ctx: dict, mobile: bool = False) -> str:
def _sort_stickers_sx(sort_options: list, current_sort: str, ctx: dict, mobile: bool = False) -> str:
"""Build sort option stickers as sx."""
asset_url_fn = ctx.get("asset_url")
current_local_href = ctx.get("current_local_href", "/")
@@ -297,15 +297,15 @@ async def _sort_stickers_sx(sort_options: list, current_sort: str, ctx: dict, mo
active = (k == current_sort)
ring = " ring-2 ring-emerald-500 rounded" if active else ""
src = asset_url_fn(icon) if callable(asset_url_fn) else icon
item_parts.append(await render_to_sx(
item_parts.append(sx_call(
"market-filter-sort-item",
href=href, hx_select=hx_select, ring_cls=ring, src=src, label=label,
))
items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else "(<>)"
return await render_to_sx("market-filter-sort-row", items=SxExpr(items_sx))
return sx_call("market-filter-sort-row", items=SxExpr(items_sx))
async def _like_filter_sx(liked: bool, liked_count: int, ctx: dict, mobile: bool = False) -> str:
def _like_filter_sx(liked: bool, liked_count: int, ctx: dict, mobile: bool = False) -> str:
"""Build the like filter toggle as sx."""
current_local_href = ctx.get("current_local_href", "/")
hx_select = ctx.get("hx_select_search", "#main-panel")
@@ -320,13 +320,13 @@ async def _like_filter_sx(liked: bool, liked_count: int, ctx: dict, mobile: bool
icon_cls = "fa-solid fa-heart text-red-500" if liked else "fa-regular fa-heart text-stone-400"
size = "text-[40px]" if mobile else "text-2xl"
return await render_to_sx(
return sx_call(
"market-filter-like",
href=href, hx_select=hx_select, icon_cls=icon_cls, size_cls=size,
)
async def _labels_filter_sx(labels: list, selected: list, ctx: dict, *,
def _labels_filter_sx(labels: list, selected: list, ctx: dict, *,
prefix: str = "nav-labels", mobile: bool = False) -> str:
"""Build label filter buttons as sx."""
asset_url_fn = ctx.get("asset_url")
@@ -347,14 +347,14 @@ async def _labels_filter_sx(labels: list, selected: list, ctx: dict, *,
href = "#"
ring = " ring-2 ring-emerald-500 rounded" if is_sel else ""
src = asset_url_fn(f"{prefix}/{name}.svg") if callable(asset_url_fn) else ""
item_parts.append(await render_to_sx(
item_parts.append(sx_call(
"market-filter-label-item",
href=href, hx_select=hx_select, ring_cls=ring, src=src, name=name,
))
return "(<> " + " ".join(item_parts) + ")" if item_parts else "(<>)"
async def _stickers_filter_sx(stickers: list, selected: list, ctx: dict, mobile: bool = False) -> str:
def _stickers_filter_sx(stickers: list, selected: list, ctx: dict, mobile: bool = False) -> str:
"""Build sticker filter grid as sx."""
asset_url_fn = ctx.get("asset_url")
current_local_href = ctx.get("current_local_href", "/")
@@ -376,16 +376,16 @@ async def _stickers_filter_sx(stickers: list, selected: list, ctx: dict, mobile:
ring = " ring-2 ring-emerald-500 rounded" if is_sel else ""
src = asset_url_fn(f"stickers/{name}.svg") if callable(asset_url_fn) else ""
cls = "text-[10px] text-stone-500" if count != 0 else "text-md text-red-500 font-bold"
item_parts.append(await render_to_sx(
item_parts.append(sx_call(
"market-filter-sticker-item",
href=href, hx_select=hx_select, ring_cls=ring,
src=src, name=name, count_cls=cls, count=str(count),
))
items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else "(<>)"
return await render_to_sx("market-filter-stickers-row", items=SxExpr(items_sx))
return sx_call("market-filter-stickers-row", items=SxExpr(items_sx))
async def _brand_filter_sx(brands: list, selected: list, ctx: dict, mobile: bool = False) -> str:
def _brand_filter_sx(brands: list, selected: list, ctx: dict, mobile: bool = False) -> str:
"""Build brand filter checkboxes as sx."""
current_local_href = ctx.get("current_local_href", "/")
hx_select = ctx.get("hx_select_search", "#main-panel")
@@ -405,16 +405,16 @@ async def _brand_filter_sx(brands: list, selected: list, ctx: dict, mobile: bool
href = "#"
bg = " bg-yellow-200" if is_sel else ""
cls = "text-md" if count else "text-md text-red-500"
item_parts.append(await render_to_sx(
item_parts.append(sx_call(
"market-filter-brand-item",
href=href, hx_select=hx_select, bg_cls=bg,
name_cls=cls, name=name, count=str(count),
))
items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else "(<>)"
return await render_to_sx("market-filter-brands-panel", items=SxExpr(items_sx))
return sx_call("market-filter-brands-panel", items=SxExpr(items_sx))
async def _subcategory_selector_sx(subs: list, top_href: str, current_sub: str, ctx: dict) -> str:
def _subcategory_selector_sx(subs: list, top_href: str, current_sub: str, ctx: dict) -> str:
"""Build subcategory vertical nav as sx."""
hx_select = ctx.get("hx_select_search", "#main-panel")
from shared.utils import route_prefix
@@ -422,7 +422,7 @@ async def _subcategory_selector_sx(subs: list, top_href: str, current_sub: str,
all_cls = " bg-stone-200 font-medium" if not current_sub else ""
all_full_href = rp + top_href
item_parts = [await render_to_sx(
item_parts = [sx_call(
"market-filter-subcategory-item",
href=all_full_href, hx_select=hx_select, active_cls=all_cls, name="All",
)]
@@ -433,9 +433,9 @@ async def _subcategory_selector_sx(subs: list, top_href: str, current_sub: str,
active = (slug == current_sub)
active_cls = " bg-stone-200 font-medium" if active else ""
full_href = rp + href
item_parts.append(await render_to_sx(
item_parts.append(sx_call(
"market-filter-subcategory-item",
href=full_href, hx_select=hx_select, active_cls=active_cls, name=name,
))
items_sx = "(<> " + " ".join(item_parts) + ")"
return await render_to_sx("market-filter-subcategory-panel", items=SxExpr(items_sx))
return sx_call("market-filter-subcategory-panel", items=SxExpr(items_sx))

View File

@@ -15,7 +15,7 @@ async def _markets_admin_panel_sx(ctx: dict) -> str:
"""Render the markets list + create form panel."""
from quart import g, url_for
from shared.services.registry import services
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
rights = ctx.get("rights") or {}
@@ -32,30 +32,30 @@ async def _markets_admin_panel_sx(ctx: dict) -> str:
form_html = ""
if can_create:
create_url = url_for("page_admin.create_market")
form_html = await render_to_sx("crud-create-form",
form_html = sx_call("crud-create-form",
create_url=create_url, csrf=csrf,
errors_id="market-create-errors",
list_id="markets-list",
placeholder="e.g. Suma, Craft Fair",
btn_label="Add market")
list_html = await _markets_admin_list_sx(ctx, markets)
return await render_to_sx("crud-panel",
list_html = _markets_admin_list_sx(ctx, markets)
return sx_call("crud-panel",
form=SxExpr(form_html), list=SxExpr(list_html),
list_id="markets-list")
async def _markets_admin_list_sx(ctx: dict, markets: list) -> str:
def _markets_admin_list_sx(ctx: dict, markets: list) -> str:
"""Render the markets list items."""
from quart import url_for
from shared.utils import route_prefix
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
csrf_token = ctx.get("csrf_token")
csrf = csrf_token() if callable(csrf_token) else (csrf_token or "")
prefix = route_prefix()
if not markets:
return await render_to_sx("empty-state",
return sx_call("empty-state",
message="No markets yet. Create one above.",
cls="text-gray-500 mt-4")
@@ -67,7 +67,7 @@ async def _markets_admin_list_sx(ctx: dict, markets: list) -> str:
href = prefix + f"/{post_slug}/{m_slug}/"
del_url = url_for("page_admin.delete_market", market_slug=m_slug)
csrf_hdr = f'{{"X-CSRFToken":"{csrf}"}}'
parts.append(await render_to_sx("crud-item",
parts.append(sx_call("crud-item",
href=href, name=m_name, slug=m_slug,
del_url=del_url, csrf_hdr=csrf_hdr,
list_id="markets-list",
@@ -116,13 +116,13 @@ async def _h_all_markets_content(**kw):
page_info[p.id] = {"title": p.title, "slug": p.slug}
if not markets:
return await _no_markets_sx()
return _no_markets_sx()
prefix = route_prefix()
next_url = prefix + url_for("all_markets.markets_fragment", page=page + 1)
cards = await _market_cards_sx(markets, page_info, page, has_more, next_url)
return await _markets_grid(cards)
cards = _market_cards_sx(markets, page_info, page, has_more, next_url)
return _markets_grid(cards)
async def _h_page_markets_content(slug=None, **kw):
@@ -138,30 +138,30 @@ async def _h_page_markets_content(slug=None, **kw):
post_slug = post.get("slug", "")
if not markets:
return await _no_markets_sx("No markets for this page")
return _no_markets_sx("No markets for this page")
prefix = route_prefix()
next_url = prefix + url_for("page_markets.markets_fragment", page=page + 1)
cards = await _market_cards_sx(markets, {}, page, has_more, next_url,
cards = _market_cards_sx(markets, {}, page, has_more, next_url,
show_page_badge=False, post_slug=post_slug)
return await _markets_grid(cards)
return _markets_grid(cards)
async def _h_page_admin_content(slug=None, **kw):
from shared.sx.page import get_template_context
from shared.sx.helpers import render_to_sx
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
ctx = await get_template_context()
content = await _markets_admin_panel_sx(ctx)
return await render_to_sx("market-admin-content-wrap", inner=SxExpr(content))
return sx_call("market-admin-content-wrap", inner=SxExpr(content))
async def _h_market_home_content(page_slug=None, market_slug=None, **kw):
def _h_market_home_content(page_slug=None, market_slug=None, **kw):
from quart import g
post_data = getattr(g, "post_data", {})
post = post_data.get("post", {})
return await _market_landing_content_sx(post)
return _market_landing_content_sx(post)
def _h_market_admin_content(page_slug=None, market_slug=None, **kw):

View File

@@ -5,7 +5,7 @@ from typing import Any
from shared.sx.parser import SxExpr
from shared.sx.helpers import (
render_to_sx,
sx_call,
post_header_sx as _post_header_sx,
post_admin_header_sx,
oob_header_sx as _oob_header_sx,
@@ -19,7 +19,7 @@ from .utils import _set_prices, _price_str, _clear_deeper_oob
# Header helpers
# ---------------------------------------------------------------------------
async def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
"""Build the market-level header row as sx call string."""
from quart import url_for
@@ -28,7 +28,7 @@ async def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
sub_slug = ctx.get("sub_slug", "")
hx_select_search = ctx.get("hx_select_search", "#main-panel")
label_sx = await render_to_sx(
label_sx = sx_call(
"market-shop-label",
title=market_title, top_slug=top_slug or "",
sub_div=sub_slug or None,
@@ -39,9 +39,9 @@ async def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
# Build desktop nav from categories
categories = ctx.get("categories", {})
qs = ctx.get("qs", "")
nav_sx = await _desktop_category_nav_sx(ctx, categories, qs, hx_select_search)
nav_sx = _desktop_category_nav_sx(ctx, categories, qs, hx_select_search)
return await render_to_sx(
return sx_call(
"menu-row-sx",
id="market-row", level=2,
link_href=link_href, link_label_content=SxExpr(label_sx),
@@ -50,7 +50,7 @@ async def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
)
async def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
hx_select: str) -> str:
"""Build desktop category navigation links as sx."""
from quart import url_for
@@ -63,7 +63,7 @@ async def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
all_href = prefix + url_for("market.browse.browse_all") + qs
all_active = (category_label == "All Products")
link_parts = [await render_to_sx(
link_parts = [sx_call(
"market-category-link",
href=all_href, hx_select=hx_select, active=all_active,
select_colours=select_colours, label="All",
@@ -72,7 +72,7 @@ async def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
for cat, data in categories.items():
cat_href = prefix + url_for("market.browse.browse_top", top_slug=data["slug"]) + qs
cat_active = (cat == category_label)
link_parts.append(await render_to_sx(
link_parts.append(sx_call(
"market-category-link",
href=cat_href, hx_select=hx_select, active=cat_active,
select_colours=select_colours, label=cat,
@@ -83,14 +83,14 @@ async def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
admin_sx = ""
if rights and rights.get("admin"):
admin_href = prefix + url_for("defpage_market_admin")
admin_sx = await render_to_sx("market-admin-link", href=admin_href, hx_select=hx_select)
admin_sx = sx_call("market-admin-link", href=admin_href, hx_select=hx_select)
return await render_to_sx("market-desktop-category-nav",
return sx_call("market-desktop-category-nav",
links=SxExpr(links_sx),
admin=SxExpr(admin_sx) if admin_sx else None)
async def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
"""Build the product-level header row as sx call string."""
from quart import url_for
from shared.browser.app.csrf import generate_csrf_token
@@ -100,21 +100,21 @@ async def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
hx_select_search = ctx.get("hx_select_search", "#main-panel")
link_href = url_for("market.browse.product.product_detail", product_slug=slug)
label_sx = await render_to_sx("market-product-label", title=title)
label_sx = sx_call("market-product-label", title=title)
# Prices in nav area
pr = _set_prices(d)
cart = ctx.get("cart", [])
prices_nav = await _prices_header_sx(d, pr, cart, slug, ctx)
prices_nav = _prices_header_sx(d, pr, cart, slug, ctx)
rights = ctx.get("rights", {})
nav_parts = [prices_nav]
if rights and rights.get("admin"):
admin_href = url_for("market.browse.product.admin", product_slug=slug)
nav_parts.append(await render_to_sx("market-admin-link", href=admin_href, hx_select=hx_select_search))
nav_parts.append(sx_call("market-admin-link", href=admin_href, hx_select=hx_select_search))
nav_sx = "(<> " + " ".join(nav_parts) + ")"
return await render_to_sx(
return sx_call(
"menu-row-sx",
id="product-row", level=3,
link_href=link_href, link_label_content=SxExpr(label_sx),
@@ -122,7 +122,7 @@ async def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
)
async def _prices_header_sx(d: dict, pr: dict, cart: list, slug: str, ctx: dict) -> str:
def _prices_header_sx(d: dict, pr: dict, cart: list, slug: str, ctx: dict) -> str:
"""Build prices + add-to-cart for product header row as sx."""
from quart import url_for
from shared.browser.app.csrf import generate_csrf_token
@@ -133,20 +133,20 @@ async def _prices_header_sx(d: dict, pr: dict, cart: list, slug: str, ctx: dict)
# Add-to-cart button
quantity = sum(ci.quantity for ci in cart if ci.product.slug == slug) if cart else 0
add_sx = await _cart_add_sx(slug, quantity, cart_action, csrf, cart_url_fn)
add_sx = _cart_add_sx(slug, quantity, cart_action, csrf, cart_url_fn)
parts = [add_sx]
sp_val, rp_val = pr.get("sp_val"), pr.get("rp_val")
if sp_val:
parts.append(await render_to_sx("market-header-price-special-label"))
parts.append(await render_to_sx("market-header-price-special",
parts.append(sx_call("market-header-price-special-label"))
parts.append(sx_call("market-header-price-special",
price=_price_str(sp_val, pr["sp_raw"], pr["sp_cur"])))
if rp_val:
parts.append(await render_to_sx("market-header-price-strike",
parts.append(sx_call("market-header-price-strike",
price=_price_str(rp_val, pr["rp_raw"], pr["rp_cur"])))
elif rp_val:
parts.append(await render_to_sx("market-header-price-regular-label"))
parts.append(await render_to_sx("market-header-price-regular",
parts.append(sx_call("market-header-price-regular-label"))
parts.append(sx_call("market-header-price-regular",
price=_price_str(rp_val, pr["rp_raw"], pr["rp_cur"])))
# RRP
@@ -155,23 +155,23 @@ async def _prices_header_sx(d: dict, pr: dict, cart: list, slug: str, ctx: dict)
case_size = d.get("case_size_count") or 1
if rrp_raw and rrp_val:
rrp_str = f"{rrp_raw[0]}{rrp_val * case_size:.2f}"
parts.append(await render_to_sx("market-header-rrp", rrp=rrp_str))
parts.append(sx_call("market-header-rrp", rrp=rrp_str))
inner_sx = "(<> " + " ".join(parts) + ")"
return await render_to_sx("market-prices-row", inner=SxExpr(inner_sx))
return sx_call("market-prices-row", inner=SxExpr(inner_sx))
async def _cart_add_sx(slug: str, quantity: int, action: str, csrf: str,
def _cart_add_sx(slug: str, quantity: int, action: str, csrf: str,
cart_url_fn: Any = None) -> str:
"""Build add-to-cart button or quantity controls as sx."""
if not quantity:
return await render_to_sx(
return sx_call(
"market-cart-add-empty",
cart_id=f"cart-{slug}", action=action, csrf=csrf,
)
cart_href = cart_url_fn("/") if callable(cart_url_fn) else "/"
return await render_to_sx(
return sx_call(
"market-cart-add-quantity",
cart_id=f"cart-{slug}", action=action, csrf=csrf,
minus_val=str(quantity - 1), plus_val=str(quantity + 1),
@@ -183,7 +183,7 @@ async def _cart_add_sx(slug: str, quantity: int, action: str, csrf: str,
# Mobile nav panel
# ---------------------------------------------------------------------------
async def _mobile_nav_panel_sx(ctx: dict) -> str:
def _mobile_nav_panel_sx(ctx: dict) -> str:
"""Build mobile nav panel with category accordion as sx."""
from quart import url_for
from shared.utils import route_prefix
@@ -199,7 +199,7 @@ async def _mobile_nav_panel_sx(ctx: dict) -> str:
all_href = prefix + url_for("market.browse.browse_all") + qs
all_active = (category_label == "All Products")
item_parts = [await render_to_sx(
item_parts = [sx_call(
"market-mobile-all-link",
href=all_href, hx_select=hx_select, active=all_active,
select_colours=select_colours,
@@ -211,10 +211,10 @@ async def _mobile_nav_panel_sx(ctx: dict) -> str:
cat_href = prefix + url_for("market.browse.browse_top", top_slug=cat_slug) + qs
bg_cls = " bg-stone-900 text-white hover:bg-stone-900" if cat_active else ""
chevron_sx = await render_to_sx("market-mobile-chevron")
chevron_sx = sx_call("market-mobile-chevron")
cat_count = data.get("count", 0)
summary_sx = await render_to_sx(
summary_sx = sx_call(
"market-mobile-cat-summary",
bg_cls=bg_cls, href=cat_href, hx_select=hx_select,
select_colours=select_colours, cat_name=cat,
@@ -231,19 +231,19 @@ async def _mobile_nav_panel_sx(ctx: dict) -> str:
sub_active = (cat_active and sub_slug == sub.get("slug"))
sub_label = sub.get("html_label") or sub.get("name", "")
sub_count = sub.get("count", 0)
sub_link_parts.append(await render_to_sx(
sub_link_parts.append(sx_call(
"market-mobile-sub-link",
select_colours=select_colours, active=sub_active,
href=sub_href, hx_select=hx_select, label=sub_label,
count_label=f"{sub_count} products", count_str=str(sub_count),
))
sub_links_sx = "(<> " + " ".join(sub_link_parts) + ")"
subs_sx = await render_to_sx("market-mobile-subs-panel", links=SxExpr(sub_links_sx))
subs_sx = sx_call("market-mobile-subs-panel", links=SxExpr(sub_links_sx))
else:
view_href = prefix + url_for("market.browse.browse_top", top_slug=cat_slug) + qs
subs_sx = await render_to_sx("market-mobile-view-all", href=view_href, hx_select=hx_select)
subs_sx = sx_call("market-mobile-view-all", href=view_href, hx_select=hx_select)
item_parts.append(await render_to_sx(
item_parts.append(sx_call(
"market-mobile-cat-details",
open=cat_active or None,
summary=SxExpr(summary_sx),
@@ -251,20 +251,20 @@ async def _mobile_nav_panel_sx(ctx: dict) -> str:
))
items_sx = "(<> " + " ".join(item_parts) + ")"
return await render_to_sx("market-mobile-nav-wrapper", items=SxExpr(items_sx))
return sx_call("market-mobile-nav-wrapper", items=SxExpr(items_sx))
# ---------------------------------------------------------------------------
# Product admin header
# ---------------------------------------------------------------------------
async def _product_admin_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
def _product_admin_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
"""Build product admin header row as sx."""
from quart import url_for
slug = d.get("slug", "")
link_href = url_for("market.browse.product.admin", product_slug=slug)
return await render_to_sx(
return sx_call(
"menu-row-sx",
id="product-admin-row", level=4,
link_href=link_href, link_label="admin!!", icon="fa fa-cog",
@@ -296,21 +296,21 @@ async def _market_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env
return await render_to_sx_with_env("market-browse-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
market_header=SxExpr(await _market_header_sx(ctx)))
market_header=SxExpr(_market_header_sx(ctx)))
async def _market_oob(ctx: dict, **kw: Any) -> str:
oob_hdr = await _oob_header_sx("post-header-child", "market-header-child",
await _market_header_sx(ctx))
return await render_to_sx("market-browse-layout-oob",
_market_header_sx(ctx))
return sx_call("market-browse-layout-oob",
oob_header=SxExpr(oob_hdr),
post_header_oob=SxExpr(await _post_header_sx(ctx, oob=True)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child")))
async def _market_mobile(ctx: dict, **kw: Any) -> str:
return await _mobile_nav_panel_sx(ctx)
def _market_mobile(ctx: dict, **kw: Any) -> str:
return _mobile_nav_panel_sx(ctx)
async def _market_admin_full(ctx: dict, **kw: Any) -> str:
@@ -318,14 +318,14 @@ async def _market_admin_full(ctx: dict, **kw: Any) -> str:
selected = kw.get("selected", "")
return await render_to_sx_with_env("market-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
market_header=SxExpr(await _market_header_sx(ctx)),
market_header=SxExpr(_market_header_sx(ctx)),
admin_header=SxExpr(await _market_admin_header_sx(ctx, selected=selected)))
async def _market_admin_oob(ctx: dict, **kw: Any) -> str:
selected = kw.get("selected", "")
return await render_to_sx("market-admin-layout-oob",
market_header_oob=SxExpr(await _market_header_sx(ctx, oob=True)),
return sx_call("market-admin-layout-oob",
market_header_oob=SxExpr(_market_header_sx(ctx, oob=True)),
admin_oob_header=SxExpr(await _oob_header_sx("market-header-child", "market-admin-header-child",
await _market_admin_header_sx(ctx, selected=selected))),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",

View File

@@ -5,7 +5,7 @@ from typing import Any
from shared.sx.parser import SxExpr
from shared.sx.helpers import (
render_to_sx,
sx_call,
post_header_sx as _post_header_sx,
oob_header_sx as _oob_header_sx,
full_page_sx, oob_page_sx,
@@ -25,21 +25,21 @@ from .helpers import _markets_admin_panel_sx
# Browse page
# ---------------------------------------------------------------------------
async def _product_grid(cards_sx: str) -> str:
def _product_grid(cards_sx: str) -> str:
"""Wrap product cards in a grid as sx."""
return await render_to_sx("market-product-grid", cards=SxExpr(cards_sx))
return sx_call("market-product-grid", cards=SxExpr(cards_sx))
async def render_browse_page(ctx: dict) -> str:
"""Full page: product browse with filters."""
cards = await _product_cards_sx(ctx)
content = await _product_grid(cards)
cards = _product_cards_sx(ctx)
content = _product_grid(cards)
from shared.sx.helpers import render_to_sx_with_env
hdr = await render_to_sx_with_env("market-browse-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
market_header=SxExpr(await _market_header_sx(ctx)))
menu = await _mobile_nav_panel_sx(ctx)
market_header=SxExpr(_market_header_sx(ctx)))
menu = _mobile_nav_panel_sx(ctx)
filter_sx = await _mobile_filter_summary_sx(ctx)
aside_sx = await _desktop_filter_sx(ctx)
@@ -49,17 +49,17 @@ async def render_browse_page(ctx: dict) -> str:
async def render_browse_oob(ctx: dict) -> str:
"""OOB response: product browse."""
cards = await _product_cards_sx(ctx)
content = await _product_grid(cards)
cards = _product_cards_sx(ctx)
content = _product_grid(cards)
oob_hdr = await _oob_header_sx("post-header-child", "market-header-child",
await _market_header_sx(ctx))
oobs = await render_to_sx("market-browse-layout-oob",
_market_header_sx(ctx))
oobs = sx_call("market-browse-layout-oob",
oob_header=SxExpr(oob_hdr),
post_header_oob=SxExpr(await _post_header_sx(ctx, oob=True)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child")))
menu = await _mobile_nav_panel_sx(ctx)
menu = _mobile_nav_panel_sx(ctx)
filter_sx = await _mobile_filter_summary_sx(ctx)
aside_sx = await _desktop_filter_sx(ctx)
@@ -67,9 +67,9 @@ async def render_browse_oob(ctx: dict) -> str:
menu=menu, filter=filter_sx, aside=aside_sx)
async def render_browse_cards(ctx: dict) -> str:
def render_browse_cards(ctx: dict) -> str:
"""Pagination fragment: product cards -- sx wire format."""
return await _product_cards_sx(ctx)
return _product_cards_sx(ctx)
# ---------------------------------------------------------------------------
@@ -78,29 +78,29 @@ async def render_browse_cards(ctx: dict) -> str:
async def render_product_page(ctx: dict, d: dict) -> str:
"""Full page: product detail."""
content = await _product_detail_sx(d, ctx)
meta = await _product_meta_sx(d, ctx)
content = _product_detail_sx(d, ctx)
meta = _product_meta_sx(d, ctx)
from shared.sx.helpers import render_to_sx_with_env
hdr = await render_to_sx_with_env("market-product-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
market_header=SxExpr(await _market_header_sx(ctx)),
product_header=SxExpr(await _product_header_sx(ctx, d)))
market_header=SxExpr(_market_header_sx(ctx)),
product_header=SxExpr(_product_header_sx(ctx, d)))
return await full_page_sx(ctx, header_rows=hdr, content=content, meta=meta)
async def render_product_oob(ctx: dict, d: dict) -> str:
"""OOB response: product detail."""
content = await _product_detail_sx(d, ctx)
content = _product_detail_sx(d, ctx)
oobs = await render_to_sx("market-oob-wrap",
parts=SxExpr("(<> " + await _market_header_sx(ctx, oob=True) + " "
oobs = sx_call("market-oob-wrap",
parts=SxExpr("(<> " + _market_header_sx(ctx, oob=True) + " "
+ await _oob_header_sx("market-header-child", "product-header-child",
await _product_header_sx(ctx, d)) + " "
_product_header_sx(ctx, d)) + " "
+ _clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child",
"product-row", "product-header-child") + ")"))
menu = await _mobile_nav_panel_sx(ctx)
menu = _mobile_nav_panel_sx(ctx)
return await oob_page_sx(oobs=oobs, content=content, menu=menu)
@@ -110,25 +110,25 @@ async def render_product_oob(ctx: dict, d: dict) -> str:
async def render_product_admin_page(ctx: dict, d: dict) -> str:
"""Full page: product admin."""
content = await _product_detail_sx(d, ctx)
content = _product_detail_sx(d, ctx)
from shared.sx.helpers import render_to_sx_with_env
hdr = await render_to_sx_with_env("market-product-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
market_header=SxExpr(await _market_header_sx(ctx)),
product_header=SxExpr(await _product_header_sx(ctx, d)),
admin_header=SxExpr(await _product_admin_header_sx(ctx, d)))
market_header=SxExpr(_market_header_sx(ctx)),
product_header=SxExpr(_product_header_sx(ctx, d)),
admin_header=SxExpr(_product_admin_header_sx(ctx, d)))
return await full_page_sx(ctx, header_rows=hdr, content=content)
async def render_product_admin_oob(ctx: dict, d: dict) -> str:
"""OOB response: product admin."""
content = await _product_detail_sx(d, ctx)
content = _product_detail_sx(d, ctx)
oobs = await render_to_sx("market-oob-wrap",
parts=SxExpr("(<> " + await _product_header_sx(ctx, d, oob=True) + " "
oobs = sx_call("market-oob-wrap",
parts=SxExpr("(<> " + _product_header_sx(ctx, d, oob=True) + " "
+ await _oob_header_sx("product-header-child", "product-admin-header-child",
await _product_admin_header_sx(ctx, d)) + " "
_product_admin_header_sx(ctx, d)) + " "
+ _clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child",
"product-row", "product-header-child",
@@ -149,7 +149,7 @@ async def render_markets_admin_list_panel(ctx: dict) -> str:
# Public API: POST handler fragment renderers
# ---------------------------------------------------------------------------
async def render_all_markets_cards(markets: list, has_more: bool,
def render_all_markets_cards(markets: list, has_more: bool,
page_info: dict, page: int) -> str:
"""Pagination fragment: all markets cards."""
from quart import url_for
@@ -157,10 +157,10 @@ async def render_all_markets_cards(markets: list, has_more: bool,
prefix = route_prefix()
next_url = prefix + url_for("all_markets.markets_fragment", page=page + 1)
return await _market_cards_sx(markets, page_info, page, has_more, next_url)
return _market_cards_sx(markets, page_info, page, has_more, next_url)
async def render_page_markets_cards(markets: list, has_more: bool,
def render_page_markets_cards(markets: list, has_more: bool,
page: int, post_slug: str) -> str:
"""Pagination fragment: page-scoped markets cards."""
from quart import url_for
@@ -168,11 +168,11 @@ async def render_page_markets_cards(markets: list, has_more: bool,
prefix = route_prefix()
next_url = prefix + url_for("page_markets.markets_fragment", page=page + 1)
return await _market_cards_sx(markets, {}, page, has_more, next_url,
return _market_cards_sx(markets, {}, page, has_more, next_url,
show_page_badge=False, post_slug=post_slug)
async def render_like_toggle_button(slug: str, liked: bool, *,
def render_like_toggle_button(slug: str, liked: bool, *,
like_url: str | None = None,
item_type: str = "product") -> str:
"""Render a standalone like toggle button for HTMX POST response."""
@@ -193,7 +193,7 @@ async def render_like_toggle_button(slug: str, liked: bool, *,
icon = "fa-regular fa-heart"
label = f"Like this {item_type}"
return await render_to_sx(
return sx_call(
"market-like-toggle-button",
colour=colour, action=like_url,
hx_headers=f'{{"X-CSRFToken": "{csrf}"}}',
@@ -201,7 +201,7 @@ async def render_like_toggle_button(slug: str, liked: bool, *,
)
async def render_cart_added_response(cart: list, item: Any, d: dict) -> str:
def render_cart_added_response(cart: list, item: Any, d: dict) -> str:
"""Render the HTMX response after add-to-cart.
Returns OOB fragments: cart-mini icon + product add/remove buttons + cart item row.
@@ -217,30 +217,30 @@ async def render_cart_added_response(cart: list, item: Any, d: dict) -> str:
# 1. Cart mini icon OOB
if count > 0:
cart_href = _cart_url("/")
cart_mini = await render_to_sx("market-cart-mini-count", href=cart_href, count=str(count))
cart_mini = sx_call("market-cart-mini-count", href=cart_href, count=str(count))
else:
from shared.config import config
blog_href = config().get("blog_url", "/")
logo = config().get("logo", "")
cart_mini = await render_to_sx("market-cart-mini-empty", href=blog_href, logo=logo)
cart_mini = sx_call("market-cart-mini-empty", href=blog_href, logo=logo)
# 2. Add/remove buttons OOB
action = url_for("market.browse.product.cart", product_slug=slug)
quantity = getattr(item, "quantity", 0) if item else 0
if not quantity:
cart_add = await render_to_sx(
cart_add = sx_call(
"market-cart-add-empty",
cart_id=f"cart-{slug}", action=action, csrf=csrf,
)
else:
cart_href = _cart_url("/") if callable(_cart_url) else "/"
cart_add = await render_to_sx(
cart_add = sx_call(
"market-cart-add-quantity",
cart_id=f"cart-{slug}", action=action, csrf=csrf,
minus_val=str(quantity - 1), plus_val=str(quantity + 1),
quantity=str(quantity), cart_href=cart_href,
)
add_sx = await render_to_sx(
add_sx = sx_call(
"market-cart-add-oob",
id=f"cart-add-{slug}",
inner=SxExpr(cart_add),

View File

@@ -3,8 +3,8 @@ from __future__ import annotations
from typing import Any
from shared.sx.parser import serialize, SxExpr
from shared.sx.helpers import render_to_sx
from shared.sx.parser import SxExpr
from shared.sx.helpers import sx_call
# ---------------------------------------------------------------------------
@@ -22,7 +22,6 @@ _MARKET_DEEP_IDS = [
def _clear_deeper_oob(*keep_ids: str) -> str:
"""Clear all market header rows/children NOT in keep_ids."""
from shared.sx.helpers import sx_call
to_clear = [i for i in _MARKET_DEEP_IDS if i not in keep_ids]
return " ".join(sx_call("clear-oob-div", id=i) for i in to_clear)
@@ -55,27 +54,27 @@ def _set_prices(item: dict) -> dict:
rp_val=rp_val, rp_raw=rp_raw, rp_cur=rp_cur)
async def _card_price_sx(p: dict) -> str:
def _card_price_sx(p: dict) -> str:
"""Build price line for product card as sx call."""
pr = _set_prices(p)
sp_str = _price_str(pr["sp_val"], pr["sp_raw"], pr["sp_cur"])
rp_str = _price_str(pr["rp_val"], pr["rp_raw"], pr["rp_cur"])
parts: list[str] = []
if pr["sp_val"]:
parts.append(await render_to_sx("market-price-special", price=sp_str))
parts.append(sx_call("market-price-special", price=sp_str))
if pr["rp_val"]:
parts.append(await render_to_sx("market-price-regular-strike", price=rp_str))
parts.append(sx_call("market-price-regular-strike", price=rp_str))
elif pr["rp_val"]:
parts.append(await render_to_sx("market-price-regular", price=rp_str))
parts.append(sx_call("market-price-regular", price=rp_str))
inner = "(<> " + " ".join(parts) + ")" if parts else None
return await render_to_sx("market-price-line", inner=SxExpr(inner) if inner else None)
return sx_call("market-price-line", inner=SxExpr(inner) if inner else None)
# ---------------------------------------------------------------------------
# Product detail page content
# ---------------------------------------------------------------------------
async def _product_detail_sx(d: dict, ctx: dict) -> str:
def _product_detail_sx(d: dict, ctx: dict) -> str:
"""Build product detail main panel content as sx."""
from quart import url_for
from shared.browser.app.csrf import generate_csrf_token
@@ -97,19 +96,19 @@ async def _product_detail_sx(d: dict, ctx: dict) -> str:
# Like button
like_sx = ""
if user:
like_sx = await _like_button_sx(slug, liked_by_current_user, csrf, ctx)
like_sx = _like_button_sx(slug, liked_by_current_user, csrf, ctx)
# Main image + labels
label_parts: list[str] = []
if callable(asset_url_fn):
for l in labels:
label_parts.append(await render_to_sx(
label_parts.append(sx_call(
"market-label-overlay",
src=asset_url_fn("labels/" + l + ".svg"),
))
labels_sx = "(<> " + " ".join(label_parts) + ")" if label_parts else None
gallery_inner = await render_to_sx(
gallery_inner = sx_call(
"market-detail-gallery-inner",
like=SxExpr(like_sx) if like_sx else None,
image=images[0], alt=d.get("title", ""),
@@ -120,9 +119,9 @@ async def _product_detail_sx(d: dict, ctx: dict) -> str:
# Prev/next buttons
nav_buttons = ""
if len(images) > 1:
nav_buttons = await render_to_sx("market-detail-nav-buttons")
nav_buttons = sx_call("market-detail-nav-buttons")
gallery_sx = await render_to_sx(
gallery_sx = sx_call(
"market-detail-gallery",
inner=SxExpr(gallery_inner),
nav=SxExpr(nav_buttons) if nav_buttons else None,
@@ -133,18 +132,18 @@ async def _product_detail_sx(d: dict, ctx: dict) -> str:
if len(images) > 1:
thumb_parts = []
for i, u in enumerate(images):
thumb_parts.append(await render_to_sx(
thumb_parts.append(sx_call(
"market-detail-thumb",
title=f"Image {i+1}", src=u, alt=f"thumb {i+1}",
))
thumbs_sx = "(<> " + " ".join(thumb_parts) + ")"
gallery_parts.append(await render_to_sx("market-detail-thumbs", thumbs=SxExpr(thumbs_sx)))
gallery_parts.append(sx_call("market-detail-thumbs", thumbs=SxExpr(thumbs_sx)))
gallery_final = "(<> " + " ".join(gallery_parts) + ")"
else:
like_sx = ""
if user:
like_sx = await _like_button_sx(slug, liked_by_current_user, csrf, ctx)
gallery_final = await render_to_sx("market-detail-no-image",
like_sx = _like_button_sx(slug, liked_by_current_user, csrf, ctx)
gallery_final = sx_call("market-detail-no-image",
like=SxExpr(like_sx) if like_sx else None)
# Stickers below gallery
@@ -152,12 +151,12 @@ async def _product_detail_sx(d: dict, ctx: dict) -> str:
if stickers and callable(asset_url_fn):
sticker_parts = []
for s in stickers:
sticker_parts.append(await render_to_sx(
sticker_parts.append(sx_call(
"market-detail-sticker",
src=asset_url_fn("stickers/" + s + ".svg"), name=s,
))
sticker_items_sx = "(<> " + " ".join(sticker_parts) + ")"
stickers_sx = await render_to_sx("market-detail-stickers", items=SxExpr(sticker_items_sx))
stickers_sx = sx_call("market-detail-stickers", items=SxExpr(sticker_items_sx))
# Right column: prices, description, sections
pr = _set_prices(d)
@@ -167,15 +166,15 @@ async def _product_detail_sx(d: dict, ctx: dict) -> str:
extra_parts: list[str] = []
ppu = d.get("price_per_unit") or d.get("price_per_unit_raw")
if ppu:
extra_parts.append(await render_to_sx(
extra_parts.append(sx_call(
"market-detail-unit-price",
price=_price_str(d.get("price_per_unit"), d.get("price_per_unit_raw"), d.get("price_per_unit_currency")),
))
if d.get("case_size_raw"):
extra_parts.append(await render_to_sx("market-detail-case-size", size=d["case_size_raw"]))
extra_parts.append(sx_call("market-detail-case-size", size=d["case_size_raw"]))
if extra_parts:
extras_sx = "(<> " + " ".join(extra_parts) + ")"
detail_parts.append(await render_to_sx("market-detail-extras", inner=SxExpr(extras_sx)))
detail_parts.append(sx_call("market-detail-extras", inner=SxExpr(extras_sx)))
# Description
desc_short = d.get("description_short")
@@ -183,28 +182,28 @@ async def _product_detail_sx(d: dict, ctx: dict) -> str:
if desc_short or desc_html_val:
desc_parts: list[str] = []
if desc_short:
desc_parts.append(await render_to_sx("market-detail-desc-short", text=desc_short))
desc_parts.append(sx_call("market-detail-desc-short", text=desc_short))
if desc_html_val:
desc_parts.append(await render_to_sx("market-detail-desc-html", html=desc_html_val))
desc_parts.append(sx_call("market-detail-desc-html", html=desc_html_val))
desc_inner = "(<> " + " ".join(desc_parts) + ")"
detail_parts.append(await render_to_sx("market-detail-desc-wrapper", inner=SxExpr(desc_inner)))
detail_parts.append(sx_call("market-detail-desc-wrapper", inner=SxExpr(desc_inner)))
# Sections (expandable)
sections = d.get("sections", [])
if sections:
sec_parts = []
for sec in sections:
sec_parts.append(await render_to_sx(
sec_parts.append(sx_call(
"market-detail-section",
title=sec.get("title", ""), html=sec.get("html", ""),
))
sec_items_sx = "(<> " + " ".join(sec_parts) + ")"
detail_parts.append(await render_to_sx("market-detail-sections", items=SxExpr(sec_items_sx)))
detail_parts.append(sx_call("market-detail-sections", items=SxExpr(sec_items_sx)))
details_inner_sx = "(<> " + " ".join(detail_parts) + ")" if detail_parts else "(<>)"
details_sx = await render_to_sx("market-detail-right-col", inner=SxExpr(details_inner_sx))
details_sx = sx_call("market-detail-right-col", inner=SxExpr(details_inner_sx))
return await render_to_sx(
return sx_call(
"market-detail-layout",
gallery=SxExpr(gallery_final),
stickers=SxExpr(stickers_sx) if stickers_sx else None,
@@ -216,7 +215,7 @@ async def _product_detail_sx(d: dict, ctx: dict) -> str:
# Product meta (OpenGraph, JSON-LD)
# ---------------------------------------------------------------------------
async def _product_meta_sx(d: dict, ctx: dict) -> str:
def _product_meta_sx(d: dict, ctx: dict) -> str:
"""Build product meta tags as sx (auto-hoisted to <head> by sx.js)."""
import json
from quart import request
@@ -234,34 +233,34 @@ async def _product_meta_sx(d: dict, ctx: dict) -> str:
price = d.get("special_price") or d.get("regular_price") or d.get("rrp")
price_currency = d.get("special_price_currency") or d.get("regular_price_currency") or d.get("rrp_currency")
parts = [await render_to_sx("market-meta-title", title=title)]
parts.append(await render_to_sx("market-meta-description", description=description))
parts = [sx_call("market-meta-title", title=title)]
parts.append(sx_call("market-meta-description", description=description))
if canonical:
parts.append(await render_to_sx("market-meta-canonical", href=canonical))
parts.append(sx_call("market-meta-canonical", href=canonical))
# OpenGraph
site_title = ctx.get("base_title", "")
parts.append(await render_to_sx("market-meta-og", property="og:site_name", content=site_title))
parts.append(await render_to_sx("market-meta-og", property="og:type", content="product"))
parts.append(await render_to_sx("market-meta-og", property="og:title", content=title))
parts.append(await render_to_sx("market-meta-og", property="og:description", content=description))
parts.append(sx_call("market-meta-og", property="og:site_name", content=site_title))
parts.append(sx_call("market-meta-og", property="og:type", content="product"))
parts.append(sx_call("market-meta-og", property="og:title", content=title))
parts.append(sx_call("market-meta-og", property="og:description", content=description))
if canonical:
parts.append(await render_to_sx("market-meta-og", property="og:url", content=canonical))
parts.append(sx_call("market-meta-og", property="og:url", content=canonical))
if image_url:
parts.append(await render_to_sx("market-meta-og", property="og:image", content=image_url))
parts.append(sx_call("market-meta-og", property="og:image", content=image_url))
if price and price_currency:
parts.append(await render_to_sx("market-meta-og", property="product:price:amount", content=f"{price:.2f}"))
parts.append(await render_to_sx("market-meta-og", property="product:price:currency", content=price_currency))
parts.append(sx_call("market-meta-og", property="product:price:amount", content=f"{price:.2f}"))
parts.append(sx_call("market-meta-og", property="product:price:currency", content=price_currency))
if brand:
parts.append(await render_to_sx("market-meta-og", property="product:brand", content=brand))
parts.append(sx_call("market-meta-og", property="product:brand", content=brand))
# Twitter
card_type = "summary_large_image" if image_url else "summary"
parts.append(await render_to_sx("market-meta-twitter", name="twitter:card", content=card_type))
parts.append(await render_to_sx("market-meta-twitter", name="twitter:title", content=title))
parts.append(await render_to_sx("market-meta-twitter", name="twitter:description", content=description))
parts.append(sx_call("market-meta-twitter", name="twitter:card", content=card_type))
parts.append(sx_call("market-meta-twitter", name="twitter:title", content=title))
parts.append(sx_call("market-meta-twitter", name="twitter:description", content=description))
if image_url:
parts.append(await render_to_sx("market-meta-twitter", name="twitter:image", content=image_url))
parts.append(sx_call("market-meta-twitter", name="twitter:image", content=image_url))
# JSON-LD
jsonld = {
@@ -283,6 +282,6 @@ async def _product_meta_sx(d: dict, ctx: dict) -> str:
"url": canonical,
"availability": "https://schema.org/InStock",
}
parts.append(await render_to_sx("market-meta-jsonld", json=json.dumps(jsonld)))
parts.append(sx_call("market-meta-jsonld", json=json.dumps(jsonld)))
return "(<> " + " ".join(parts) + ")"

View File

@@ -15,17 +15,17 @@ async def _render_checkout_return(ctx: dict, order=None, status: str = "",
calendar_entries=None, order_tickets=None) -> str:
"""Render checkout return page — replaces sx_components helper."""
from shared.sx.helpers import (
render_to_sx, root_header_sx, header_child_sx, full_page_sx, call_url,
sx_call, root_header_sx, header_child_sx, full_page_sx, call_url,
)
from shared.sx.parser import SxExpr
from shared.infrastructure.urls import market_product_url
filt = await render_to_sx("checkout-return-header", status=status)
filt = sx_call("checkout-return-header", status=status)
if not order:
content = await render_to_sx("checkout-return-missing")
content = sx_call("checkout-return-missing")
else:
summary = await render_to_sx("order-summary-card",
summary = sx_call("order-summary-card",
order_id=order.id,
created_at=order.created_at.strftime("%-d %b %Y, %H:%M") if order.created_at else None,
description=order.description, status=order.status,
@@ -39,19 +39,19 @@ async def _render_checkout_return(ctx: dict, order=None, status: str = "",
for item in order.items:
product_url = market_product_url(item.product_slug)
if item.product_image:
img = await render_to_sx("order-item-image",
img = sx_call("order-item-image",
src=item.product_image,
alt=item.product_title or "Product image")
else:
img = await render_to_sx("order-item-no-image")
item_parts.append(await render_to_sx("order-item-row",
img = sx_call("order-item-no-image")
item_parts.append(sx_call("order-item-row",
href=product_url, img=SxExpr(img),
title=item.product_title or "Unknown product",
pid=f"Product ID: {item.product_id}",
qty=f"Qty: {item.quantity}",
price=f"{item.currency or order.currency or 'GBP'} {item.unit_price or 0:.2f}",
))
items = await render_to_sx("order-items-panel",
items = sx_call("order-items-panel",
items=SxExpr("(<> " + " ".join(item_parts) + ")"))
calendar = ""
@@ -68,13 +68,13 @@ async def _render_checkout_return(ctx: dict, order=None, status: str = "",
ds = e.start_at.strftime("%-d %b %Y, %H:%M") if e.start_at else ""
if e.end_at:
ds += f" \u2013 {e.end_at.strftime('%-d %b %Y, %H:%M')}"
cal_parts.append(await render_to_sx("order-calendar-entry",
cal_parts.append(sx_call("order-calendar-entry",
name=e.name,
pill=f"inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium {pill}",
status=st.capitalize(), date_str=ds,
cost=f"\u00a3{e.cost or 0:.2f}",
))
calendar = await render_to_sx("order-calendar-section",
calendar = sx_call("order-calendar-section",
items=SxExpr("(<> " + " ".join(cal_parts) + ")"))
tickets = ""
@@ -92,23 +92,23 @@ async def _render_checkout_return(ctx: dict, order=None, status: str = "",
ds = tk.entry_start_at.strftime("%-d %b %Y, %H:%M") if tk.entry_start_at else ""
if tk.entry_end_at:
ds += f" \u2013 {tk.entry_end_at.strftime('%-d %b %Y, %H:%M')}"
tk_parts.append(await render_to_sx("checkout-return-ticket",
tk_parts.append(sx_call("checkout-return-ticket",
name=tk.entry_name, pill=pill_cls,
state=st.replace("_", " ").capitalize(),
type_name=tk.ticket_type_name or None,
date_str=ds, code=tk.code,
price=f"\u00a3{tk.price or 0:.2f}",
))
tickets = await render_to_sx("checkout-return-tickets",
tickets = sx_call("checkout-return-tickets",
items=SxExpr("(<> " + " ".join(tk_parts) + ")"))
status_msg = ""
if order.status == "failed":
status_msg = await render_to_sx("checkout-return-failed", order_id=order.id)
status_msg = sx_call("checkout-return-failed", order_id=order.id)
elif order.status == "paid":
status_msg = await render_to_sx("checkout-return-paid")
status_msg = sx_call("checkout-return-paid")
content = await render_to_sx("checkout-return-content",
content = sx_call("checkout-return-content",
summary=SxExpr(summary),
items=SxExpr(items) if items else None,
calendar=SxExpr(calendar) if calendar else None,
@@ -117,7 +117,7 @@ async def _render_checkout_return(ctx: dict, order=None, status: str = "",
)
account_url = call_url(ctx, "account_url", "")
auth_hdr = await render_to_sx("auth-header-row", account_url=account_url)
auth_hdr = sx_call("auth-header-row", account_url=account_url)
hdr = "(<> " + await root_header_sx(ctx) + " " + await header_child_sx(auth_hdr) + ")"
return await full_page_sx(ctx, header_rows=hdr, filter=filt, content=content)

View File

@@ -70,16 +70,16 @@ def register() -> Blueprint:
if not hosted_url:
from shared.sx.page import get_template_context
from shared.sx.helpers import render_to_sx, root_header_sx, header_child_sx, full_page_sx, call_url
from shared.sx.helpers import sx_call, root_header_sx, header_child_sx, full_page_sx, call_url
from shared.sx.parser import SxExpr
from shared.infrastructure.urls import cart_url
tctx = await get_template_context()
account_url = call_url(tctx, "account_url", "")
auth_hdr = await render_to_sx("auth-header-row", account_url=account_url)
auth_hdr = sx_call("auth-header-row", account_url=account_url)
hdr = "(<> " + await root_header_sx(tctx) + " " + await header_child_sx(auth_hdr) + ")"
filt = await render_to_sx("checkout-error-header")
order_sx = await render_to_sx("checkout-error-order-id", oid=f"#{order.id}")
content = await render_to_sx(
filt = sx_call("checkout-error-header")
order_sx = sx_call("checkout-error-order-id", oid=f"#{order.id}")
content = sx_call(
"checkout-error-content",
msg="No hosted checkout URL returned from SumUp when trying to reopen payment.",
order=SxExpr(order_sx),

View File

@@ -73,7 +73,7 @@ def register(url_prefix: str) -> Blueprint:
result = await g.s.execute(stmt)
orders = result.scalars().all()
from shared.sx.helpers import sx_response, render_to_sx
from shared.sx.helpers import sx_response, sx_call
from shared.utils import route_prefix
pfx = route_prefix()
@@ -95,16 +95,16 @@ def register(url_prefix: str) -> Blueprint:
# Build just the rows fragment (not full table) for infinite scroll
parts = []
for od in order_dicts:
parts.append(await render_to_sx("order-row-pair",
parts.append(sx_call("order-row-pair",
order=od,
detail_url_prefix=detail_prefix))
if page < total_pages:
parts.append(await render_to_sx("infinite-scroll",
parts.append(sx_call("infinite-scroll",
url=rows_url + qs_fn(page=page + 1),
page=page, total_pages=total_pages,
id_prefix="orders", colspan=5))
else:
parts.append(await render_to_sx("order-end-row"))
parts.append(sx_call("order-end-row"))
sx_src = "(<> " + " ".join(parts) + ")"
resp = sx_response(sx_src)

View File

@@ -149,7 +149,7 @@ async def _rich_error_page(errnum: str, message: str, image: str | None = None)
# Root header (site nav bar)
from shared.sx.helpers import (
root_header_sx, post_header_sx,
header_child_sx, full_page_sx, render_to_sx,
header_child_sx, full_page_sx, sx_call,
)
hdr = await root_header_sx(ctx)
@@ -162,7 +162,7 @@ async def _rich_error_page(errnum: str, message: str, image: str | None = None)
hdr = "(<> " + hdr + " " + await header_child_sx(post_row) + ")"
# Error content
error_content = await render_to_sx("error-content", errnum=errnum, message=message, image=image)
error_content = sx_call("error-content", errnum=errnum, message=message, image=image)
return await full_page_sx(ctx, header_rows=hdr, content=error_content)
except Exception:

View File

@@ -60,7 +60,7 @@ async def root_header_sx(ctx: dict, *, oob: bool = False) -> str:
rights = ctx.get("rights") or {}
is_admin = rights.get("admin") if isinstance(rights, dict) else getattr(rights, "admin", False)
settings_url = call_url(ctx, "blog_url", "/settings/") if is_admin else ""
return await render_to_sx("header-row-sx",
return await _render_to_sx("header-row-sx",
cart_mini=_as_sx(ctx.get("cart_mini")),
blog_url=call_url(ctx, "blog_url", ""),
site_title=ctx.get("base_title", ""),
@@ -86,7 +86,7 @@ async def mobile_root_nav_sx(ctx: dict) -> str:
auth_menu = ctx.get("auth_menu") or ""
if not nav_tree and not auth_menu:
return ""
return await render_to_sx("mobile-root-nav",
return await _render_to_sx("mobile-root-nav",
nav_tree=_as_sx(nav_tree),
auth_menu=_as_sx(auth_menu),
)
@@ -107,13 +107,13 @@ async def _post_nav_items_sx(ctx: dict) -> str:
page_cart_count = ctx.get("page_cart_count", 0)
if page_cart_count and page_cart_count > 0:
cart_href = call_url(ctx, "cart_url", f"/{slug}/")
parts.append(await render_to_sx("page-cart-badge", href=cart_href,
parts.append(await _render_to_sx("page-cart-badge", href=cart_href,
count=str(page_cart_count)))
container_nav = str(ctx.get("container_nav") or "").strip()
# Skip empty fragment wrappers like "(<> )"
if container_nav and container_nav.replace("(<>", "").replace(")", "").strip():
parts.append(await render_to_sx("container-nav-wrapper",
parts.append(await _render_to_sx("container-nav-wrapper",
content=SxExpr(container_nav)))
# Admin cog
@@ -125,7 +125,7 @@ async def _post_nav_items_sx(ctx: dict) -> str:
from quart import request
admin_href = call_url(ctx, "blog_url", f"/{slug}/admin/")
is_admin_page = ctx.get("is_admin_section") or "/admin" in request.path
admin_nav = await render_to_sx("admin-cog-button",
admin_nav = await _render_to_sx("admin-cog-button",
href=admin_href,
is_admin_page=is_admin_page or None)
if admin_nav:
@@ -155,7 +155,7 @@ async def _post_admin_nav_items_sx(ctx: dict, slug: str,
continue
href = url_fn(path)
is_sel = label == selected
parts.append(await render_to_sx("nav-link", href=href, label=label,
parts.append(await _render_to_sx("nav-link", href=href, label=label,
select_colours=select_colours,
is_selected=is_sel or None))
return "(<> " + " ".join(parts) + ")" if parts else ""
@@ -173,7 +173,7 @@ async def post_mobile_nav_sx(ctx: dict) -> str:
post = ctx.get("post") or {}
slug = post.get("slug", "")
title = (post.get("title") or slug)[:40]
return await render_to_sx("mobile-menu-section",
return await _render_to_sx("mobile-menu-section",
label=title,
href=call_url(ctx, "blog_url", f"/{slug}/"),
level=1,
@@ -181,22 +181,10 @@ async def post_mobile_nav_sx(ctx: dict) -> str:
)
async def post_admin_mobile_nav_sx(ctx: dict, slug: str,
selected: str = "") -> str:
"""Post-admin mobile menu section."""
nav = await _post_admin_nav_items_sx(ctx, slug, selected)
if not nav:
return ""
admin_href = call_url(ctx, "blog_url", f"/{slug}/admin/")
return await render_to_sx("mobile-menu-section",
label="admin", href=admin_href, level=2,
items=SxExpr(nav),
)
async def search_mobile_sx(ctx: dict) -> str:
"""Build mobile search input as sx wire format."""
return await render_to_sx("search-mobile",
return await _render_to_sx("search-mobile",
current_local_href=ctx.get("current_local_href", "/"),
search=ctx.get("search", ""),
search_count=ctx.get("search_count", ""),
@@ -207,7 +195,7 @@ async def search_mobile_sx(ctx: dict) -> str:
async def search_desktop_sx(ctx: dict) -> str:
"""Build desktop search input as sx wire format."""
return await render_to_sx("search-desktop",
return await _render_to_sx("search-desktop",
current_local_href=ctx.get("current_local_href", "/"),
search=ctx.get("search", ""),
search_count=ctx.get("search_count", ""),
@@ -225,11 +213,11 @@ async def post_header_sx(ctx: dict, *, oob: bool = False, child: str = "") -> st
title = (post.get("title") or "")[:160]
feature_image = post.get("feature_image")
label_sx = await render_to_sx("post-label", feature_image=feature_image, title=title)
label_sx = await _render_to_sx("post-label", feature_image=feature_image, title=title)
nav_sx = await _post_nav_items_sx(ctx) or None
link_href = call_url(ctx, "blog_url", f"/{slug}/")
return await render_to_sx("menu-row-sx",
return await _render_to_sx("menu-row-sx",
id="post-row", level=1,
link_href=link_href,
link_label_content=SxExpr(label_sx),
@@ -244,7 +232,7 @@ async def post_admin_header_sx(ctx: dict, slug: str, *, oob: bool = False,
selected: str = "", admin_href: str = "") -> str:
"""Post admin header row as sx wire format."""
# Label
label_sx = await render_to_sx("post-admin-label",
label_sx = await _render_to_sx("post-admin-label",
selected=str(escape(selected)) if selected else None)
nav_sx = await _post_admin_nav_items_sx(ctx, slug, selected) or None
@@ -253,7 +241,7 @@ async def post_admin_header_sx(ctx: dict, slug: str, *, oob: bool = False,
blog_fn = ctx.get("blog_url")
admin_href = blog_fn(f"/{slug}/admin/") if callable(blog_fn) else f"/{slug}/admin/"
return await render_to_sx("menu-row-sx",
return await _render_to_sx("menu-row-sx",
id="post-admin-row", level=2,
link_href=admin_href,
link_label_content=SxExpr(label_sx),
@@ -268,7 +256,7 @@ async def oob_header_sx(parent_id: str, child_id: str, row_sx: str) -> str:
child_id is accepted for call-site compatibility but no longer used —
the child placeholder is created by ~menu-row-sx itself.
"""
return await render_to_sx("oob-header-sx",
return await _render_to_sx("oob-header-sx",
parent_id=parent_id,
row=SxExpr(row_sx),
)
@@ -276,7 +264,7 @@ async def oob_header_sx(parent_id: str, child_id: str, row_sx: str) -> str:
async def header_child_sx(inner_sx: str, *, id: str = "root-header-child") -> str:
"""Wrap inner sx in a header-child div."""
return await render_to_sx("header-child-sx",
return await _render_to_sx("header-child-sx",
id=id, inner=SxExpr(f"(<> {inner_sx})"),
)
@@ -284,7 +272,7 @@ async def header_child_sx(inner_sx: str, *, id: str = "root-header-child") -> st
async def oob_page_sx(*, oobs: str = "", filter: str = "", aside: str = "",
content: str = "", menu: str = "") -> str:
"""Build OOB response as sx wire format."""
return await render_to_sx("oob-sx",
return await _render_to_sx("oob-sx",
oobs=SxExpr(f"(<> {oobs})") if oobs else None,
filter=SxExpr(filter) if filter else None,
aside=SxExpr(aside) if aside else None,
@@ -305,7 +293,7 @@ async def full_page_sx(ctx: dict, *, header_rows: str,
# Auto-generate mobile nav from context when no menu provided
if not menu:
menu = await mobile_root_nav_sx(ctx)
body_sx = await render_to_sx("app-body",
body_sx = await _render_to_sx("app-body",
header_rows=SxExpr(f"(<> {header_rows})") if header_rows else None,
filter=SxExpr(filter) if filter else None,
aside=SxExpr(aside) if aside else None,
@@ -345,8 +333,8 @@ def _build_component_ast(__name: str, **kwargs: Any) -> list:
return ast
async def render_to_sx_with_env(__name: str, extra_env: dict, **kwargs: Any) -> str:
"""Like ``render_to_sx`` but merges *extra_env* into the evaluation
async def _render_to_sx_with_env(__name: str, extra_env: dict, **kwargs: Any) -> str:
"""Like ``_render_to_sx`` but merges *extra_env* into the evaluation
environment before eval. Used by ``register_sx_layout`` so .sx
defcomps can read ctx values as free variables.
@@ -354,6 +342,8 @@ async def render_to_sx_with_env(__name: str, extra_env: dict, **kwargs: Any) ->
top-level component body is expanded server-side — free variables
from *extra_env* are resolved during expansion rather than being
serialized as unresolved symbols for the client.
**Private** — service code should use ``sx_call()`` or defmacros instead.
"""
from .jinja_bridge import get_component_env, _get_request_context
from .async_eval import async_eval_slot_to_sx
@@ -365,16 +355,15 @@ async def render_to_sx_with_env(__name: str, extra_env: dict, **kwargs: Any) ->
return await async_eval_slot_to_sx(ast, env, ctx)
async def render_to_sx(__name: str, **kwargs: Any) -> str:
async def _render_to_sx(__name: str, **kwargs: Any) -> str:
"""Call a defcomp and get SX wire format back. No SX string literals.
Builds an AST from Python values and evaluates it through the SX
evaluator, which resolves IO primitives and serializes component/tag
calls as SX wire format.
await render_to_sx("card", title="hello", count=3)
# equivalent to old: sx_call("card", title="hello", count=3)
# but values flow as native objects, not serialized strings
**Private** — service code should use ``sx_call()`` or defmacros instead.
Only infrastructure code (helpers.py, layouts.py) should call this.
"""
from .jinja_bridge import get_component_env, _get_request_context
from .async_eval import async_eval_to_sx
@@ -385,6 +374,11 @@ async def render_to_sx(__name: str, **kwargs: Any) -> str:
return await async_eval_to_sx(ast, env, ctx)
# Backwards-compat alias — layout infrastructure still imports this.
# Will be removed once all layouts use register_sx_layout().
render_to_sx_with_env = _render_to_sx_with_env
async def render_to_html(__name: str, **kwargs: Any) -> str:
"""Call a defcomp and get HTML back. No SX string literals.

View File

@@ -2,8 +2,8 @@
Named layout presets for defpage.
Each layout generates header rows for full-page and OOB rendering.
Layouts wrap existing helper functions from ``shared.sx.helpers`` so
defpage can reference them by name (e.g. ``:layout :root``).
Built-in layouts delegate to .sx defcomps via ``register_sx_layout``.
Services register custom layouts via ``register_custom_layout``.
Layouts are registered in ``_LAYOUT_REGISTRY`` and looked up by
``get_layout()`` at request time.
@@ -13,12 +13,6 @@ from __future__ import annotations
from typing import Any, Callable, Awaitable
from .helpers import (
root_header_sx, post_header_sx, post_admin_header_sx,
oob_header_sx,
mobile_menu_sx, mobile_root_nav_sx,
post_mobile_nav_sx, post_admin_mobile_nav_sx,
)
# ---------------------------------------------------------------------------
@@ -83,57 +77,8 @@ def get_layout(name: str) -> Layout | None:
return _LAYOUT_REGISTRY.get(name)
# ---------------------------------------------------------------------------
# Built-in layouts
# ---------------------------------------------------------------------------
async def _post_full(ctx: dict, **kw: Any) -> str:
root_hdr = await root_header_sx(ctx)
post_hdr = await post_header_sx(ctx)
return "(<> " + root_hdr + " " + post_hdr + ")"
async def _post_oob(ctx: dict, **kw: Any) -> str:
post_hdr = await post_header_sx(ctx, oob=True)
# Also replace #post-header-child (empty — clears any nested admin rows)
child_oob = await oob_header_sx("post-header-child", "", "")
return "(<> " + post_hdr + " " + child_oob + ")"
async def _post_admin_full(ctx: dict, **kw: Any) -> str:
slug = ctx.get("post", {}).get("slug", "")
selected = kw.get("selected", "")
root_hdr = await root_header_sx(ctx)
admin_hdr = await post_admin_header_sx(ctx, slug, selected=selected)
post_hdr = await post_header_sx(ctx, child=admin_hdr)
return "(<> " + root_hdr + " " + post_hdr + ")"
async def _post_admin_oob(ctx: dict, **kw: Any) -> str:
slug = ctx.get("post", {}).get("slug", "")
selected = kw.get("selected", "")
post_hdr = await post_header_sx(ctx, oob=True)
admin_hdr = await post_admin_header_sx(ctx, slug, selected=selected)
admin_oob = await oob_header_sx("post-header-child", "post-admin-header-child", admin_hdr)
return "(<> " + post_hdr + " " + admin_oob + ")"
async def _post_mobile(ctx: dict, **kw: Any) -> str:
return mobile_menu_sx(await post_mobile_nav_sx(ctx), await mobile_root_nav_sx(ctx))
async def _post_admin_mobile(ctx: dict, **kw: Any) -> str:
slug = ctx.get("post", {}).get("slug", "")
selected = kw.get("selected", "")
return mobile_menu_sx(
await post_admin_mobile_nav_sx(ctx, slug, selected),
await post_mobile_nav_sx(ctx),
await mobile_root_nav_sx(ctx),
)
register_layout(Layout("post", _post_full, _post_oob, _post_mobile))
register_layout(Layout("post-admin", _post_admin_full, _post_admin_oob, _post_admin_mobile))
# Built-in post/post-admin layouts are registered below via register_sx_layout,
# after that function is defined.
# ---------------------------------------------------------------------------
@@ -153,27 +98,29 @@ def register_sx_layout(name: str, full_defcomp: str, oob_defcomp: str,
register_sx_layout("account", "account-layout-full",
"account-layout-oob", "account-layout-mobile")
"""
from .helpers import render_to_sx_with_env
from .helpers import _render_to_sx_with_env
async def full_fn(ctx: dict, **kw: Any) -> str:
env = {k.replace("_", "-"): v for k, v in kw.items()}
return await render_to_sx_with_env(full_defcomp, env)
return await _render_to_sx_with_env(full_defcomp, env)
async def oob_fn(ctx: dict, **kw: Any) -> str:
env = {k.replace("_", "-"): v for k, v in kw.items()}
return await render_to_sx_with_env(oob_defcomp, env)
return await _render_to_sx_with_env(oob_defcomp, env)
mobile_fn = None
if mobile_defcomp:
async def mobile_fn(ctx: dict, **kw: Any) -> str:
env = {k.replace("_", "-"): v for k, v in kw.items()}
return await render_to_sx_with_env(mobile_defcomp, env)
return await _render_to_sx_with_env(mobile_defcomp, env)
register_layout(Layout(name, full_fn, oob_fn, mobile_fn))
# Register built-in "root" layout via .sx defcomps
# Register built-in layouts via .sx defcomps
register_sx_layout("root", "layout-root-full", "layout-root-oob", "layout-root-mobile")
register_sx_layout("post", "layout-post-full", "layout-post-oob", "layout-post-mobile")
register_sx_layout("post-admin", "layout-post-admin-full", "layout-post-admin-oob", "layout-post-admin-mobile")
# ---------------------------------------------------------------------------

View File

@@ -46,6 +46,7 @@ IO_PRIMITIVES: frozenset[str] = frozenset({
"url-for",
"route-prefix",
"root-header-ctx",
"post-header-ctx",
"select-colours",
"account-nav-ctx",
"app-rights",
@@ -482,6 +483,80 @@ async def _io_app_rights(
return getattr(g, "rights", None) or {}
async def _io_post_header_ctx(
args: list[Any], kwargs: dict[str, Any], ctx: RequestContext
) -> dict[str, Any]:
"""``(post-header-ctx)`` → dict with post-level header values.
Reads post data from ``g._defpage_ctx`` (set by per-service page
helpers), fetches container-nav and page cart count. Result is
cached on ``g`` per request.
Returns dict with keys: slug, title, feature-image, link-href,
container-nav, page-cart-count, cart-href, admin-href, is-admin,
is-admin-page, select-colours.
"""
from quart import g, request
cached = getattr(g, "_post_header_ctx", None)
if cached is not None:
return cached
from shared.infrastructure.urls import app_url
from .types import NIL
from .parser import SxExpr
dctx = getattr(g, "_defpage_ctx", None) or {}
post = dctx.get("post") or {}
slug = post.get("slug", "")
if not slug:
result: dict[str, Any] = {"slug": ""}
g._post_header_ctx = result
return result
title = (post.get("title") or "")[:160]
feature_image = post.get("feature_image") or NIL
# Container nav (pre-fetched by page helper into defpage ctx)
raw_nav = dctx.get("container_nav") or ""
container_nav: Any = NIL
nav_str = str(raw_nav).strip()
if nav_str and nav_str.replace("(<>", "").replace(")", "").strip():
if isinstance(raw_nav, SxExpr):
container_nav = raw_nav
else:
container_nav = SxExpr(nav_str)
page_cart_count = dctx.get("page_cart_count", 0) or 0
rights = getattr(g, "rights", None) or {}
is_admin = (
rights.get("admin", False)
if isinstance(rights, dict)
else getattr(rights, "admin", False)
)
is_admin_page = dctx.get("is_admin_section") or "/admin" in request.path
from quart import current_app
select_colours = current_app.jinja_env.globals.get("select_colours", "")
result = {
"slug": slug,
"title": title,
"feature-image": feature_image,
"link-href": app_url("blog", f"/{slug}/"),
"container-nav": container_nav,
"page-cart-count": page_cart_count,
"cart-href": app_url("cart", f"/{slug}/") if page_cart_count else "",
"admin-href": app_url("blog", f"/{slug}/admin/"),
"is-admin": is_admin,
"is-admin-page": is_admin_page or NIL,
"select-colours": select_colours,
}
g._post_header_ctx = result
return result
_IO_HANDLERS: dict[str, Any] = {
"frag": _io_frag,
"query": _io_query,
@@ -499,6 +574,7 @@ _IO_HANDLERS: dict[str, Any] = {
"url-for": _io_url_for,
"route-prefix": _io_route_prefix,
"root-header-ctx": _io_root_header_ctx,
"post-header-ctx": _io_post_header_ctx,
"select-colours": _io_select_colours,
"account-nav-ctx": _io_account_nav_ctx,
"app-rights": _io_app_rights,

View File

@@ -203,6 +203,56 @@
(defcomp ~layout-root-mobile ()
(~root-mobile-auto))
;; Post layout — root + post header
(defcomp ~layout-post-full ()
(<> (~root-header-auto)
(~header-child-sx :inner (~post-header-auto))))
(defcomp ~layout-post-oob ()
(<> (~post-header-auto true)
(~oob-header-sx :parent-id "post-header-child" :row "")))
(defcomp ~layout-post-mobile ()
(let ((__phctx (post-header-ctx))
(__rhctx (root-header-ctx)))
(<>
(when (get __phctx "slug")
(~mobile-menu-section
:label (slice (get __phctx "title") 0 40)
:href (get __phctx "link-href")
:level 1
:items (~post-nav-auto)))
(~root-mobile-auto))))
;; Post-admin layout — root + post header with nested admin row
(defcomp ~layout-post-admin-full (&key selected)
(let ((__admin-hdr (~post-admin-header-auto nil selected)))
(<> (~root-header-auto)
(~header-child-sx
:inner (~post-header-auto nil)))))
(defcomp ~layout-post-admin-oob (&key selected)
(<> (~post-header-auto true)
(~oob-header-sx :parent-id "post-header-child"
:row (~post-admin-header-auto nil selected))))
(defcomp ~layout-post-admin-mobile (&key selected)
(let ((__phctx (post-header-ctx)))
(<>
(when (get __phctx "slug")
(~mobile-menu-section
:label "admin"
:href (get __phctx "admin-href")
:level 2
:items (~post-admin-nav-auto selected)))
(when (get __phctx "slug")
(~mobile-menu-section
:label (slice (get __phctx "title") 0 40)
:href (get __phctx "link-href")
:level 1
:items (~post-nav-auto)))
(~root-mobile-auto))))
(defcomp ~error-content (&key errnum message image)
(div :class "text-center p-8 max-w-lg mx-auto"
(div :class "font-bold text-2xl md:text-4xl text-red-500 mb-4" errnum)
@@ -214,6 +264,85 @@
(defcomp ~clear-oob-div (&key id)
(div :id id :sx-swap-oob "outerHTML"))
;; ---------------------------------------------------------------------------
;; Post-level auto-fetching macros — use (post-header-ctx) IO primitive
;; ---------------------------------------------------------------------------
(defmacro ~post-nav-auto ()
"Post-level nav items: page cart badge + container nav + admin cog."
(quasiquote
(let ((__phctx (post-header-ctx)))
(when (get __phctx "slug")
(<>
(when (> (get __phctx "page-cart-count") 0)
(~page-cart-badge :href (get __phctx "cart-href")
:count (str (get __phctx "page-cart-count"))))
(when (get __phctx "container-nav")
(~container-nav-wrapper :content (get __phctx "container-nav")))
(when (get __phctx "is-admin")
(~admin-cog-button :href (get __phctx "admin-href")
:is-admin-page (get __phctx "is-admin-page"))))))))
(defmacro ~post-header-auto (oob)
"Post-level header row. Reads post data via (post-header-ctx)."
(quasiquote
(let ((__phctx (post-header-ctx)))
(when (get __phctx "slug")
(~menu-row-sx :id "post-row" :level 1
:link-href (get __phctx "link-href")
:link-label-content (~post-label
:feature-image (get __phctx "feature-image")
:title (get __phctx "title"))
:nav (~post-nav-auto)
:child-id "post-header-child"
:oob (unquote oob) :external true)))))
(defmacro ~post-admin-nav-auto (selected)
"Post-admin nav items: calendars, markets, etc."
(quasiquote
(let ((__phctx (post-header-ctx)))
(when (get __phctx "slug")
(let ((__slug (get __phctx "slug"))
(__sc (get __phctx "select-colours")))
(<>
(~nav-link :href (app-url "events" (str "/" __slug "/admin/"))
:label "calendars" :select-colours __sc
:is-selected (when (= (unquote selected) "calendars") "true"))
(~nav-link :href (app-url "market" (str "/" __slug "/admin/"))
:label "markets" :select-colours __sc
:is-selected (when (= (unquote selected) "markets") "true"))
(~nav-link :href (app-url "cart" (str "/" __slug "/admin/payments/"))
:label "payments" :select-colours __sc
:is-selected (when (= (unquote selected) "payments") "true"))
(~nav-link :href (app-url "blog" (str "/" __slug "/admin/entries/"))
:label "entries" :select-colours __sc
:is-selected (when (= (unquote selected) "entries") "true"))
(~nav-link :href (app-url "blog" (str "/" __slug "/admin/data/"))
:label "data" :select-colours __sc
:is-selected (when (= (unquote selected) "data") "true"))
(~nav-link :href (app-url "blog" (str "/" __slug "/admin/preview/"))
:label "preview" :select-colours __sc
:is-selected (when (= (unquote selected) "preview") "true"))
(~nav-link :href (app-url "blog" (str "/" __slug "/admin/edit/"))
:label "edit" :select-colours __sc
:is-selected (when (= (unquote selected) "edit") "true"))
(~nav-link :href (app-url "blog" (str "/" __slug "/admin/settings/"))
:label "settings" :select-colours __sc
:is-selected (when (= (unquote selected) "settings") "true"))))))))
(defmacro ~post-admin-header-auto (oob selected)
"Post-admin header row. Uses (post-header-ctx) for slug + URLs."
(quasiquote
(let ((__phctx (post-header-ctx)))
(when (get __phctx "slug")
(~menu-row-sx :id "post-admin-row" :level 2
:link-href (get __phctx "admin-href")
:link-label-content (~post-admin-label
:selected (unquote selected))
:nav (~post-admin-nav-auto (unquote selected))
:child-id "post-admin-header-child"
:oob (unquote oob))))))
;; ---------------------------------------------------------------------------
;; Shared nav helpers — used by post_header_sx / post_admin_header_sx
;; ---------------------------------------------------------------------------

View File

@@ -222,8 +222,8 @@ def _docs_evaluator_sx() -> str:
)
async def _docs_primitives_sx() -> str:
prims = await _primitives_section_sx()
def _docs_primitives_sx() -> str:
prims = _primitives_section_sx()
return (
f'(~doc-page :title "Primitives"'
f' (~doc-section :title "Built-in functions" :id "builtins"'
@@ -402,11 +402,11 @@ def _reference_attr_detail_sx(slug: str) -> str:
)
async def _reference_attrs_sx() -> str:
def _reference_attrs_sx() -> str:
from content.pages import REQUEST_ATTRS, BEHAVIOR_ATTRS, SX_UNIQUE_ATTRS
req = await _attr_table_sx("Request Attributes", REQUEST_ATTRS)
beh = await _attr_table_sx("Behavior Attributes", BEHAVIOR_ATTRS)
uniq = await _attr_table_sx("Unique to sx", SX_UNIQUE_ATTRS)
req = _attr_table_sx("Request Attributes", REQUEST_ATTRS)
beh = _attr_table_sx("Behavior Attributes", BEHAVIOR_ATTRS)
uniq = _attr_table_sx("Unique to sx", SX_UNIQUE_ATTRS)
return (
f'(~doc-page :title "Attribute Reference"'
f' (p :class "text-stone-600 mb-6"'
@@ -419,10 +419,10 @@ async def _reference_attrs_sx() -> str:
)
async def _reference_headers_sx() -> str:
def _reference_headers_sx() -> str:
from content.pages import REQUEST_HEADERS, RESPONSE_HEADERS
req_table = await _headers_table_sx("Request Headers", REQUEST_HEADERS)
resp_table = await _headers_table_sx("Response Headers", RESPONSE_HEADERS)
req_table = _headers_table_sx("Request Headers", REQUEST_HEADERS)
resp_table = _headers_table_sx("Response Headers", RESPONSE_HEADERS)
return (
f'(~doc-page :title "Headers"'
f' (p :class "text-stone-600 mb-6"'
@@ -433,29 +433,29 @@ async def _reference_headers_sx() -> str:
)
async def _reference_events_sx() -> str:
from shared.sx.helpers import render_to_sx
def _reference_events_sx() -> str:
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
from content.pages import EVENTS
rows = []
for name, desc in EVENTS:
rows.append(await render_to_sx("doc-two-col-row", name=name, description=desc))
rows.append(sx_call("doc-two-col-row", name=name, description=desc))
rows_sx = "(<> " + " ".join(rows) + ")"
table = await render_to_sx("doc-two-col-table",
table = sx_call("doc-two-col-table",
intro="sx fires custom DOM events at various points in the request lifecycle.",
col1="Event", col2="Description", rows=SxExpr(rows_sx))
return f'(~doc-page :title "Events" {table})'
async def _reference_js_api_sx() -> str:
from shared.sx.helpers import render_to_sx
def _reference_js_api_sx() -> str:
from shared.sx.helpers import sx_call
from shared.sx.parser import SxExpr
from content.pages import JS_API
rows = []
for name, desc in JS_API:
rows.append(await render_to_sx("doc-two-col-row", name=name, description=desc))
rows.append(sx_call("doc-two-col-row", name=name, description=desc))
rows_sx = "(<> " + " ".join(rows) + ")"
table = await render_to_sx("doc-two-col-table",
table = sx_call("doc-two-col-table",
intro="The client-side sx.js library exposes a public API for programmatic use.",
col1="Method", col2="Description", rows=SxExpr(rows_sx))
return f'(~doc-page :title "JavaScript API" {table})'

View File

@@ -18,8 +18,8 @@ async def _sx_full_headers(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env
from shared.sx.parser import SxExpr
main_nav = await _main_nav_sx(kw.get("section"))
sx_row = await _sx_header_sx(main_nav)
main_nav = _main_nav_sx(kw.get("section"))
sx_row = _sx_header_sx(main_nav)
return await render_to_sx_with_env("sx-layout-full", {},
sx_row=SxExpr(sx_row))
@@ -29,8 +29,8 @@ async def _sx_oob_headers(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env, oob_header_sx
from shared.sx.parser import SxExpr
main_nav = await _main_nav_sx(kw.get("section"))
sx_row = await _sx_header_sx(main_nav)
main_nav = _main_nav_sx(kw.get("section"))
sx_row = _sx_header_sx(main_nav)
rows = await render_to_sx_with_env("sx-layout-full", {},
sx_row=SxExpr(sx_row))
return await oob_header_sx("root-header-child", "sx-header-child", rows)
@@ -47,9 +47,9 @@ async def _sx_section_full_headers(ctx: dict, **kw: Any) -> str:
sub_nav = kw.get("sub_nav", "")
selected = kw.get("selected", "")
main_nav = await _main_nav_sx(section)
sub_row = await _sub_row_sx(sub_label, sub_href, sub_nav, selected)
sx_row = await _sx_header_sx(main_nav, child=sub_row)
main_nav = _main_nav_sx(section)
sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected)
sx_row = _sx_header_sx(main_nav, child=sub_row)
return await render_to_sx_with_env("sx-section-layout-full", {},
sx_row=SxExpr(sx_row))
@@ -65,9 +65,9 @@ async def _sx_section_oob_headers(ctx: dict, **kw: Any) -> str:
sub_nav = kw.get("sub_nav", "")
selected = kw.get("selected", "")
main_nav = await _main_nav_sx(section)
sub_row = await _sub_row_sx(sub_label, sub_href, sub_nav, selected)
sx_row = await _sx_header_sx(main_nav, child=sub_row)
main_nav = _main_nav_sx(section)
sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected)
sx_row = _sx_header_sx(main_nav, child=sub_row)
rows = await render_to_sx_with_env("sx-section-layout-full", {},
sx_row=SxExpr(sx_row))
return await oob_header_sx("root-header-child", "sx-header-child", rows)
@@ -76,12 +76,12 @@ async def _sx_section_oob_headers(ctx: dict, **kw: Any) -> str:
async def _sx_mobile(ctx: dict, **kw: Any) -> str:
"""Mobile menu for sx home page: main nav + root."""
from shared.sx.helpers import (
mobile_menu_sx, mobile_root_nav_sx, render_to_sx, SxExpr,
mobile_menu_sx, mobile_root_nav_sx, sx_call, SxExpr,
)
main_nav = await _main_nav_sx(kw.get("section"))
main_nav = _main_nav_sx(kw.get("section"))
return mobile_menu_sx(
await render_to_sx("mobile-menu-section",
sx_call("mobile-menu-section",
label="sx", href="/", level=1, colour="violet",
items=SxExpr(main_nav)),
await mobile_root_nav_sx(ctx),
@@ -91,21 +91,21 @@ async def _sx_mobile(ctx: dict, **kw: Any) -> str:
async def _sx_section_mobile(ctx: dict, **kw: Any) -> str:
"""Mobile menu for sx section pages: sub nav + main nav + root."""
from shared.sx.helpers import (
mobile_menu_sx, mobile_root_nav_sx, render_to_sx, SxExpr,
mobile_menu_sx, mobile_root_nav_sx, sx_call, SxExpr,
)
section = kw.get("section", "")
sub_label = kw.get("sub_label", section)
sub_href = kw.get("sub_href", "/")
sub_nav = kw.get("sub_nav", "")
main_nav = await _main_nav_sx(section)
main_nav = _main_nav_sx(section)
parts = []
if sub_nav:
parts.append(await render_to_sx("mobile-menu-section",
parts.append(sx_call("mobile-menu-section",
label=sub_label, href=sub_href, level=2, colour="violet",
items=SxExpr(sub_nav)))
parts.append(await render_to_sx("mobile-menu-section",
parts.append(sx_call("mobile-menu-section",
label="sx", href="/", level=1, colour="violet",
items=SxExpr(main_nav)))
parts.append(await mobile_root_nav_sx(ctx))

View File

@@ -2,15 +2,15 @@
from __future__ import annotations
from shared.sx.helpers import (
render_to_sx, SxExpr,
sx_call, SxExpr,
)
async def _nav_items_sx(items: list[tuple[str, str]], current: str | None = None) -> str:
def _nav_items_sx(items: list[tuple[str, str]], current: str | None = None) -> str:
"""Build nav link items as sx."""
parts = []
for label, href in items:
parts.append(await render_to_sx("nav-link",
parts.append(sx_call("nav-link",
href=href, label=label,
is_selected="true" if current == label else None,
select_colours="aria-selected:bg-violet-200 aria-selected:text-violet-900",
@@ -18,54 +18,54 @@ async def _nav_items_sx(items: list[tuple[str, str]], current: str | None = None
return "(<> " + " ".join(parts) + ")"
async def _doc_nav_sx(items: list[tuple[str, str]], current: str) -> str:
def _doc_nav_sx(items: list[tuple[str, str]], current: str) -> str:
"""Build the in-page doc navigation pills."""
items_sx = " ".join(
f'(list "{label}" "{href}")'
for label, href in items
)
return await render_to_sx("doc-nav", items=SxExpr(f"(list {items_sx})"), current=current)
return sx_call("doc-nav", items=SxExpr(f"(list {items_sx})"), current=current)
async def _attr_table_sx(title: str, attrs: list[tuple[str, str, bool]]) -> str:
def _attr_table_sx(title: str, attrs: list[tuple[str, str, bool]]) -> str:
"""Build an attribute reference table."""
from content.pages import ATTR_DETAILS
rows = []
for attr, desc, exists in attrs:
href = f"/reference/attributes/{attr}" if exists and attr in ATTR_DETAILS else None
rows.append(await render_to_sx("doc-attr-row", attr=attr, description=desc,
rows.append(sx_call("doc-attr-row", attr=attr, description=desc,
exists="true" if exists else None,
href=href))
rows_sx = "(<> " + " ".join(rows) + ")"
return await render_to_sx("doc-attr-table", title=title, rows=SxExpr(rows_sx))
return sx_call("doc-attr-table", title=title, rows=SxExpr(rows_sx))
async def _headers_table_sx(title: str, headers: list[tuple[str, str, str]]) -> str:
def _headers_table_sx(title: str, headers: list[tuple[str, str, str]]) -> str:
"""Build a headers reference table."""
rows = []
for name, value, desc in headers:
rows.append(await render_to_sx("doc-headers-row",
rows.append(sx_call("doc-headers-row",
name=name, value=value, description=desc))
rows_sx = "(<> " + " ".join(rows) + ")"
return await render_to_sx("doc-headers-table", title=title, rows=SxExpr(rows_sx))
return sx_call("doc-headers-table", title=title, rows=SxExpr(rows_sx))
async def _primitives_section_sx() -> str:
def _primitives_section_sx() -> str:
"""Build the primitives section."""
from content.pages import PRIMITIVES
parts = []
for category, prims in PRIMITIVES.items():
prims_sx = " ".join(f'"{p}"' for p in prims)
parts.append(await render_to_sx("doc-primitives-table",
parts.append(sx_call("doc-primitives-table",
category=category,
primitives=SxExpr(f"(list {prims_sx})")))
return " ".join(parts)
async def _sx_header_sx(nav: str | None = None, *, child: str | None = None) -> str:
def _sx_header_sx(nav: str | None = None, *, child: str | None = None) -> str:
"""Build the sx docs menu-row."""
label_sx = await render_to_sx("sx-docs-label")
return await render_to_sx("menu-row-sx",
label_sx = sx_call("sx-docs-label")
return sx_call("menu-row-sx",
id="sx-row", level=1, colour="violet",
link_href="/", link_label="sx",
link_label_content=SxExpr(label_sx),
@@ -75,40 +75,40 @@ async def _sx_header_sx(nav: str | None = None, *, child: str | None = None) ->
)
async def _docs_nav_sx(current: str | None = None) -> str:
def _docs_nav_sx(current: str | None = None) -> str:
from content.pages import DOCS_NAV
return await _nav_items_sx(DOCS_NAV, current)
return _nav_items_sx(DOCS_NAV, current)
async def _reference_nav_sx(current: str | None = None) -> str:
def _reference_nav_sx(current: str | None = None) -> str:
from content.pages import REFERENCE_NAV
return await _nav_items_sx(REFERENCE_NAV, current)
return _nav_items_sx(REFERENCE_NAV, current)
async def _protocols_nav_sx(current: str | None = None) -> str:
def _protocols_nav_sx(current: str | None = None) -> str:
from content.pages import PROTOCOLS_NAV
return await _nav_items_sx(PROTOCOLS_NAV, current)
return _nav_items_sx(PROTOCOLS_NAV, current)
async def _examples_nav_sx(current: str | None = None) -> str:
def _examples_nav_sx(current: str | None = None) -> str:
from content.pages import EXAMPLES_NAV
return await _nav_items_sx(EXAMPLES_NAV, current)
return _nav_items_sx(EXAMPLES_NAV, current)
async def _essays_nav_sx(current: str | None = None) -> str:
def _essays_nav_sx(current: str | None = None) -> str:
from content.pages import ESSAYS_NAV
return await _nav_items_sx(ESSAYS_NAV, current)
return _nav_items_sx(ESSAYS_NAV, current)
async def _main_nav_sx(current_section: str | None = None) -> str:
def _main_nav_sx(current_section: str | None = None) -> str:
from content.pages import MAIN_NAV
return await _nav_items_sx(MAIN_NAV, current_section)
return _nav_items_sx(MAIN_NAV, current_section)
async def _sub_row_sx(sub_label: str, sub_href: str, sub_nav: str,
def _sub_row_sx(sub_label: str, sub_href: str, sub_nav: str,
selected: str = "") -> str:
"""Build the level-2 sub-section menu-row."""
return await render_to_sx("menu-row-sx",
return sx_call("menu-row-sx",
id="sx-sub-row", level=2, colour="violet",
link_href=sub_href, link_label=sub_label,
selected=selected or None,

View File

@@ -5,7 +5,7 @@ import os
from datetime import datetime
from shared.sx.jinja_bridge import load_service_components
from shared.sx.helpers import render_to_sx, SxExpr, render_to_sx_with_env, full_page_sx
from shared.sx.helpers import sx_call, SxExpr, render_to_sx_with_env, full_page_sx
# Load test-specific .sx components at import time
load_service_components(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
@@ -75,7 +75,7 @@ def _build_summary_data(result: dict | None, running: bool, csrf: str,
async def test_detail_sx(test: dict) -> str:
"""Return s-expression wire format for a test detail view."""
return await render_to_sx("test-detail-section", test=test)
return sx_call("test-detail-section", test=test)
async def render_dashboard_page_sx(ctx: dict, result: dict | None,
@@ -96,9 +96,9 @@ async def render_dashboard_page_sx(ctx: dict, result: dict | None,
else:
summary_data["state"] = "empty-filtered"
inner = await render_to_sx("test-results-partial",
inner = sx_call("test-results-partial",
summary_data=summary_data, sections=sections, has_failures=has_failures)
content = await render_to_sx("test-results-wrap", running=running, inner=SxExpr(inner))
content = sx_call("test-results-wrap", running=running, inner=SxExpr(inner))
hdr = await render_to_sx_with_env("test-layout-full", {},
services=_service_list(),
active_service=active_service,
@@ -124,9 +124,9 @@ async def render_results_partial_sx(result: dict | None, running: bool,
else:
summary_data["state"] = "empty-filtered"
inner = await render_to_sx("test-results-partial",
inner = sx_call("test-results-partial",
summary_data=summary_data, sections=sections, has_failures=has_failures)
return await render_to_sx("test-results-wrap", running=running, inner=SxExpr(inner))
return sx_call("test-results-wrap", running=running, inner=SxExpr(inner))
async def render_test_detail_page_sx(ctx: dict, test: dict) -> str:
@@ -136,7 +136,7 @@ async def render_test_detail_page_sx(ctx: dict, test: dict) -> str:
test_nodeid=test["nodeid"],
test_label=test["nodeid"].rsplit("::", 1)[-1],
)
content = await render_to_sx("test-detail",
content = sx_call("test-detail",
nodeid=test["nodeid"],
outcome=test["outcome"],
duration=str(test["duration"]),