Phase 5 cleanup: remove legacy HTML components, fix nav-tree fragment
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m43s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m43s
- Remove old raw! layout components (~app-head, ~app-layout, ~oob-response, ~header-row, ~menu-row, ~oob-header, ~header-child) from layout.sexp - Convert nav-tree fragment from Jinja HTML to sexp source, fixing the "Unexpected character: ." parse error caused by HTML leaking into sexp - Add _as_sexp() helper to safely coerce HTML fragments to ~rich-text - Fix federation/sexp/search.sexpr extra closing paren - Remove dead _html() wrappers from blog and account sexp_components - Remove stale render import from cart sexp_components - Add dev_watcher.py to auto-reload on .sexp/.sexpr/.js/.css changes - Add test_parse_all.py to parse-check all 59 sexpr/sexp files - Fix test assertions for sx- attribute prefix (was hx-) - Add sexp.js version logging for cache debugging Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,32 +28,86 @@ def register():
|
||||
if handler is None:
|
||||
return Response("", status=200, content_type="text/sexp")
|
||||
result = await handler()
|
||||
# nav-tree still returns HTML (Jinja template) for now
|
||||
ct = "text/html" if fragment_type == "nav-tree" else "text/sexp"
|
||||
return Response(result, status=200, content_type=ct)
|
||||
return Response(result, status=200, content_type="text/sexp")
|
||||
|
||||
# --- nav-tree fragment (still Jinja for now — complex template) ---
|
||||
# --- nav-tree fragment — returns sexp source ---
|
||||
async def _nav_tree_handler():
|
||||
from shared.sexp.helpers import sexp_call, SexpExpr
|
||||
from shared.infrastructure.urls import (
|
||||
blog_url, cart_url, market_url, events_url,
|
||||
federation_url, account_url, artdag_url,
|
||||
)
|
||||
|
||||
app_name = request.args.get("app_name", "")
|
||||
path = request.args.get("path", "/")
|
||||
first_seg = path.strip("/").split("/")[0]
|
||||
menu_items = list(await get_navigation_tree(g.s))
|
||||
|
||||
class _NavItem:
|
||||
__slots__ = ("slug", "label", "feature_image")
|
||||
def __init__(self, slug, label, feature_image=None):
|
||||
self.slug = slug
|
||||
self.label = label
|
||||
self.feature_image = feature_image
|
||||
app_slugs = {
|
||||
"cart": cart_url("/"),
|
||||
"market": market_url("/"),
|
||||
"events": events_url("/"),
|
||||
"federation": federation_url("/"),
|
||||
"account": account_url("/"),
|
||||
"artdag": artdag_url("/"),
|
||||
}
|
||||
|
||||
menu_items.append(_NavItem("artdag", "art-dag"))
|
||||
nav_cls = "whitespace-nowrap flex items-center gap-2 rounded p-2 text-sm"
|
||||
|
||||
return await render_template(
|
||||
"fragments/nav_tree.html",
|
||||
menu_items=menu_items,
|
||||
frag_app_name=app_name,
|
||||
frag_first_seg=first_seg,
|
||||
)
|
||||
item_sexps = []
|
||||
for item in menu_items:
|
||||
href = app_slugs.get(item.slug, blog_url(f"/{item.slug}/"))
|
||||
selected = "true" if (item.slug == first_seg
|
||||
or item.slug == app_name) else "false"
|
||||
img = sexp_call("blog-nav-item-image",
|
||||
src=getattr(item, "feature_image", None),
|
||||
label=getattr(item, "label", item.slug))
|
||||
item_sexps.append(sexp_call(
|
||||
"blog-nav-item-plain",
|
||||
href=href, selected=selected, nav_cls=nav_cls,
|
||||
img=SexpExpr(img), label=getattr(item, "label", item.slug),
|
||||
))
|
||||
|
||||
# artdag link
|
||||
href = artdag_url("/")
|
||||
selected = "true" if ("artdag" == first_seg
|
||||
or "artdag" == app_name) else "false"
|
||||
img = sexp_call("blog-nav-item-image", src=None, label="art-dag")
|
||||
item_sexps.append(sexp_call(
|
||||
"blog-nav-item-plain",
|
||||
href=href, selected=selected, nav_cls=nav_cls,
|
||||
img=SexpExpr(img), label="art-dag",
|
||||
))
|
||||
|
||||
if not item_sexps:
|
||||
return sexp_call("blog-nav-empty",
|
||||
wrapper_id="menu-items-nav-wrapper")
|
||||
|
||||
items_frag = "(<> " + " ".join(item_sexps) + ")"
|
||||
|
||||
arrow_cls = "scrolling-menu-arrow-menu-items-container"
|
||||
container_id = "menu-items-container"
|
||||
left_hs = ("on click set #" + container_id
|
||||
+ ".scrollLeft to #" + container_id + ".scrollLeft - 200")
|
||||
scroll_hs = ("on scroll "
|
||||
"set cls to '" + arrow_cls + "' "
|
||||
"set arrows to document.getElementsByClassName(cls) "
|
||||
"set show to (window.innerWidth >= 640 and "
|
||||
"my.scrollWidth > my.clientWidth) "
|
||||
"repeat for arrow in arrows "
|
||||
"if show remove .hidden from arrow add .flex to arrow "
|
||||
"else add .hidden to arrow remove .flex from arrow end "
|
||||
"end")
|
||||
right_hs = ("on click set #" + container_id
|
||||
+ ".scrollLeft to #" + container_id + ".scrollLeft + 200")
|
||||
|
||||
return sexp_call("blog-nav-wrapper",
|
||||
arrow_cls=arrow_cls,
|
||||
container_id=container_id,
|
||||
left_hs=left_hs,
|
||||
scroll_hs=scroll_hs,
|
||||
right_hs=right_hs,
|
||||
items=SexpExpr(items_frag))
|
||||
|
||||
_handlers["nav-tree"] = _nav_tree_handler
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ fi
|
||||
RELOAD_FLAG=""
|
||||
if [[ "${RELOAD:-}" == "true" ]]; then
|
||||
RELOAD_FLAG="--reload"
|
||||
python3 -m shared.dev_watcher &
|
||||
echo "Starting Hypercorn (${APP_MODULE:-app:app}) with auto-reload..."
|
||||
else
|
||||
echo "Starting Hypercorn (${APP_MODULE:-app:app})..."
|
||||
|
||||
@@ -387,10 +387,6 @@ def _page_card_sexp(page: dict, ctx: dict) -> str:
|
||||
excerpt=excerpt,
|
||||
)
|
||||
|
||||
def _page_card_html(page: dict, ctx: dict) -> str:
|
||||
"""Single page card (HTML, kept for backwards compat)."""
|
||||
return _page_card_sexp(page, ctx)
|
||||
|
||||
|
||||
def _view_toggle_sexp(ctx: dict) -> str:
|
||||
"""View toggle bar (list/tile) for desktop."""
|
||||
@@ -547,10 +543,6 @@ def _action_buttons_sexp(ctx: dict) -> str:
|
||||
inner=SexpExpr(inner) if inner else None,
|
||||
)
|
||||
|
||||
def _action_buttons_html(ctx: dict) -> str:
|
||||
"""New Post/Page + Drafts toggle buttons (HTML, kept for backwards compat)."""
|
||||
return _action_buttons_sexp(ctx)
|
||||
|
||||
|
||||
def _tag_groups_filter_sexp(ctx: dict) -> str:
|
||||
"""Tag group filter bar as sexp."""
|
||||
@@ -591,10 +583,6 @@ def _tag_groups_filter_sexp(ctx: dict) -> str:
|
||||
items = "(<> " + " ".join(li_parts) + ")"
|
||||
return sexp_call("blog-filter-nav", items=SexpExpr(items))
|
||||
|
||||
def _tag_groups_filter_html(ctx: dict) -> str:
|
||||
"""Tag group filter bar (HTML, kept for backwards compat)."""
|
||||
return _tag_groups_filter_sexp(ctx)
|
||||
|
||||
|
||||
def _authors_filter_sexp(ctx: dict) -> str:
|
||||
"""Author filter bar as sexp."""
|
||||
@@ -628,10 +616,6 @@ def _authors_filter_sexp(ctx: dict) -> str:
|
||||
items = "(<> " + " ".join(li_parts) + ")"
|
||||
return sexp_call("blog-filter-nav", items=SexpExpr(items))
|
||||
|
||||
def _authors_filter_html(ctx: dict) -> str:
|
||||
"""Author filter bar (HTML, kept for backwards compat)."""
|
||||
return _authors_filter_sexp(ctx)
|
||||
|
||||
|
||||
def _tag_groups_filter_summary_sexp(ctx: dict) -> str:
|
||||
"""Mobile filter summary for tag groups (sexp)."""
|
||||
@@ -649,9 +633,6 @@ def _tag_groups_filter_summary_sexp(ctx: dict) -> str:
|
||||
return ""
|
||||
return sexp_call("blog-filter-summary", text=", ".join(names))
|
||||
|
||||
def _tag_groups_filter_summary_html(ctx: dict) -> str:
|
||||
"""Mobile filter summary for tag groups (HTML, kept for backwards compat)."""
|
||||
return _tag_groups_filter_summary_sexp(ctx)
|
||||
|
||||
|
||||
def _authors_filter_summary_sexp(ctx: dict) -> str:
|
||||
@@ -670,9 +651,6 @@ def _authors_filter_summary_sexp(ctx: dict) -> str:
|
||||
return ""
|
||||
return sexp_call("blog-filter-summary", text=", ".join(names))
|
||||
|
||||
def _authors_filter_summary_html(ctx: dict) -> str:
|
||||
"""Mobile filter summary for authors (HTML, kept for backwards compat)."""
|
||||
return _authors_filter_summary_sexp(ctx)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user