Send all responses as sexp wire format with client-side rendering
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m35s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m35s
- Server sends sexp source text, client (sexp.js) renders everything - SexpExpr marker class for nested sexp composition in serialize() - sexp_page() HTML shell with data-mount="body" for full page loads - sexp_response() returns text/sexp for OOB/partial responses - ~app-body layout component replaces ~app-layout (no raw!) - ~rich-text is the only component using raw! (for CMS HTML content) - Fragment endpoints return text/sexp, auto-wrapped in SexpExpr - All _*_html() helpers converted to _*_sexp() returning sexp source - Head auto-hoist: sexp.js moves meta/title/link/script[ld+json] from rendered body to document.head automatically - Unknown components render warning box instead of crashing page - Component kwargs preserve AST for lazy rendering (fixes <> in kwargs) - Fix unterminated paren in events/sexp/tickets.sexpr Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ from quart import (
|
||||
)
|
||||
from shared.browser.app.authz import require_admin, require_post_author
|
||||
from shared.browser.app.utils.htmx import is_htmx_request
|
||||
from shared.sexp.helpers import sexp_response
|
||||
from shared.utils import host_url
|
||||
|
||||
def register():
|
||||
@@ -58,10 +59,10 @@ def register():
|
||||
tctx.update(ctx)
|
||||
if not is_htmx_request():
|
||||
html = await render_post_admin_page(tctx)
|
||||
return await make_response(html)
|
||||
else:
|
||||
html = await render_post_admin_oob(tctx)
|
||||
|
||||
return await make_response(html)
|
||||
sexp_src = await render_post_admin_oob(tctx)
|
||||
return sexp_response(sexp_src)
|
||||
|
||||
@bp.put("/features/")
|
||||
@require_admin
|
||||
@@ -105,7 +106,7 @@ def register():
|
||||
sumup_merchant_code=result.get("sumup_merchant_code") or "",
|
||||
sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "",
|
||||
)
|
||||
return await make_response(html)
|
||||
return sexp_response(html)
|
||||
|
||||
@bp.put("/admin/sumup/")
|
||||
@require_admin
|
||||
@@ -144,7 +145,7 @@ def register():
|
||||
sumup_merchant_code=result.get("sumup_merchant_code") or "",
|
||||
sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "",
|
||||
)
|
||||
return await make_response(html)
|
||||
return sexp_response(html)
|
||||
|
||||
@bp.get("/data/")
|
||||
@require_admin
|
||||
@@ -157,10 +158,10 @@ def register():
|
||||
tctx["data_html"] = data_html
|
||||
if not is_htmx_request():
|
||||
html = await render_post_data_page(tctx)
|
||||
return await make_response(html)
|
||||
else:
|
||||
html = await render_post_data_oob(tctx)
|
||||
|
||||
return await make_response(html)
|
||||
sexp_src = await render_post_data_oob(tctx)
|
||||
return sexp_response(sexp_src)
|
||||
|
||||
@bp.get("/entries/calendar/<int:calendar_id>/")
|
||||
@require_admin
|
||||
@@ -280,10 +281,10 @@ def register():
|
||||
tctx["entries_html"] = entries_html
|
||||
if not is_htmx_request():
|
||||
html = await render_post_entries_page(tctx)
|
||||
return await make_response(html)
|
||||
else:
|
||||
html = await render_post_entries_oob(tctx)
|
||||
|
||||
return await make_response(html)
|
||||
sexp_src = await render_post_entries_oob(tctx)
|
||||
return sexp_response(sexp_src)
|
||||
|
||||
@bp.post("/entries/<int:entry_id>/toggle/")
|
||||
@require_admin
|
||||
@@ -335,7 +336,7 @@ def register():
|
||||
admin_list = render_associated_entries(all_calendars, associated_entry_ids, post["slug"])
|
||||
nav_entries_html = render_nav_entries_oob(associated_entries, calendars, post)
|
||||
|
||||
return await make_response(admin_list + nav_entries_html)
|
||||
return sexp_response(admin_list + nav_entries_html)
|
||||
|
||||
@bp.get("/settings/")
|
||||
@require_post_author
|
||||
@@ -359,10 +360,10 @@ def register():
|
||||
tctx["settings_html"] = settings_html
|
||||
if not is_htmx_request():
|
||||
html = await render_post_settings_page(tctx)
|
||||
return await make_response(html)
|
||||
else:
|
||||
html = await render_post_settings_oob(tctx)
|
||||
|
||||
return await make_response(html)
|
||||
sexp_src = await render_post_settings_oob(tctx)
|
||||
return sexp_response(sexp_src)
|
||||
|
||||
@bp.post("/settings/")
|
||||
@require_post_author
|
||||
@@ -465,10 +466,10 @@ def register():
|
||||
tctx["edit_html"] = edit_html
|
||||
if not is_htmx_request():
|
||||
html = await render_post_edit_page(tctx)
|
||||
return await make_response(html)
|
||||
else:
|
||||
html = await render_post_edit_oob(tctx)
|
||||
|
||||
return await make_response(html)
|
||||
sexp_src = await render_post_edit_oob(tctx)
|
||||
return sexp_response(sexp_src)
|
||||
|
||||
@bp.post("/edit/")
|
||||
@require_post_author
|
||||
@@ -598,8 +599,7 @@ def register():
|
||||
page_markets = await _fetch_page_markets(post_id)
|
||||
|
||||
from sexp.sexp_components import render_markets_panel
|
||||
html = render_markets_panel(page_markets, post)
|
||||
return await make_response(html)
|
||||
return sexp_response(render_markets_panel(page_markets, post))
|
||||
|
||||
@bp.post("/markets/new/")
|
||||
@require_admin
|
||||
@@ -625,8 +625,7 @@ def register():
|
||||
page_markets = await _fetch_page_markets(post_id)
|
||||
|
||||
from sexp.sexp_components import render_markets_panel
|
||||
html = render_markets_panel(page_markets, post)
|
||||
return await make_response(html)
|
||||
return sexp_response(render_markets_panel(page_markets, post))
|
||||
|
||||
@bp.delete("/markets/<market_slug>/")
|
||||
@require_admin
|
||||
@@ -646,7 +645,6 @@ def register():
|
||||
page_markets = await _fetch_page_markets(post_id)
|
||||
|
||||
from sexp.sexp_components import render_markets_panel
|
||||
html = render_markets_panel(page_markets, post)
|
||||
return await make_response(html)
|
||||
return sexp_response(render_markets_panel(page_markets, post))
|
||||
|
||||
return bp
|
||||
|
||||
@@ -21,6 +21,7 @@ from shared.browser.app.redis_cacher import cache_page, clear_cache
|
||||
from .admin.routes import register as register_admin
|
||||
from shared.config import config
|
||||
from shared.browser.app.utils.htmx import is_htmx_request
|
||||
from shared.sexp.helpers import sexp_response
|
||||
|
||||
def register():
|
||||
bp = Blueprint("post", __name__, url_prefix='/<slug>')
|
||||
@@ -70,7 +71,7 @@ def register():
|
||||
post_slug = (g.post_data.get("post") or {}).get("slug", "")
|
||||
|
||||
# Fetch container nav from relations service
|
||||
container_nav_html = await fetch_fragment("relations", "container-nav", params={
|
||||
container_nav = await fetch_fragment("relations", "container-nav", params={
|
||||
"container_type": "page",
|
||||
"container_id": str(db_post_id),
|
||||
"post_slug": post_slug,
|
||||
@@ -79,7 +80,7 @@ def register():
|
||||
ctx = {
|
||||
**p_data,
|
||||
"base_title": config()["title"],
|
||||
"container_nav_html": container_nav_html,
|
||||
"container_nav": container_nav,
|
||||
}
|
||||
|
||||
# Page cart badge via HTTP
|
||||
@@ -109,10 +110,10 @@ def register():
|
||||
tctx = await get_template_context()
|
||||
if not is_htmx_request():
|
||||
html = await render_post_page(tctx)
|
||||
return await make_response(html)
|
||||
else:
|
||||
html = await render_post_oob(tctx)
|
||||
|
||||
return await make_response(html)
|
||||
sexp_src = await render_post_oob(tctx)
|
||||
return sexp_response(sexp_src)
|
||||
|
||||
@bp.post("/like/toggle/")
|
||||
@clear_cache(tag="post.post_detail", tag_scope="user")
|
||||
@@ -124,9 +125,7 @@ def register():
|
||||
|
||||
# Get post_id from g.post_data
|
||||
if not g.user:
|
||||
html = render_like_toggle_button(slug, False, like_url)
|
||||
resp = make_response(html, 403)
|
||||
return resp
|
||||
return sexp_response(render_like_toggle_button(slug, False, like_url), status=403)
|
||||
|
||||
post_id = g.post_data["post"]["id"]
|
||||
user_id = g.user.id
|
||||
@@ -136,8 +135,7 @@ def register():
|
||||
})
|
||||
liked = result["liked"]
|
||||
|
||||
html = render_like_toggle_button(slug, liked, like_url)
|
||||
return html
|
||||
return sexp_response(render_like_toggle_button(slug, liked, like_url))
|
||||
|
||||
@bp.get("/w/<widget_domain>/")
|
||||
async def widget_paginate(slug: str, widget_domain: str):
|
||||
|
||||
Reference in New Issue
Block a user