diff --git a/blog/sexp/header.sexpr b/blog/sexp/header.sexpr index 3ab3654..6debebe 100644 --- a/blog/sexp/header.sexpr +++ b/blog/sexp/header.sexpr @@ -1,22 +1,8 @@ ;; Blog header components -(defcomp ~blog-oob-header (&key parent-id child-id row-html) - (div :id parent-id :hx-swap-oob "outerHTML" :class "w-full" - (div :class "w-full" (raw! row-html) - (div :id child-id)))) - (defcomp ~blog-header-label () (div)) -(defcomp ~blog-post-label (&key feature-image title) - (<> (when feature-image (img :src feature-image :class "h-8 w-8 rounded-full object-cover border border-stone-300 flex-shrink-0")) - (span title))) - -(defcomp ~blog-post-cart-link (&key href count) - (a :href href :class "relative inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-full border border-emerald-300 bg-emerald-50 text-emerald-800 hover:bg-emerald-100 transition" - (i :class "fa fa-shopping-cart" :aria-hidden "true") - (span count))) - (defcomp ~blog-container-nav (&key container-nav-html) (div :class "flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl" :id "entries-calendars-nav-wrapper" (raw! container-nav-html))) diff --git a/blog/sexp/sexp_components.py b/blog/sexp/sexp_components.py index 8c3a3c2..7b85bb9 100644 --- a/blog/sexp/sexp_components.py +++ b/blog/sexp/sexp_components.py @@ -15,6 +15,8 @@ from markupsafe import escape from shared.sexp.jinja_bridge import render, load_service_components from shared.sexp.helpers import ( call_url, get_asset_url, root_header_html, + post_header_html as _shared_post_header_html, + oob_header_html, search_mobile_html, search_desktop_html, full_page, oob_page, ) @@ -24,14 +26,10 @@ load_service_components(os.path.dirname(os.path.dirname(__file__))) # --------------------------------------------------------------------------- -# OOB header helper +# OOB header helper — delegates to shared # --------------------------------------------------------------------------- -def _oob_header_html(parent_id: str, child_id: str, row_html: str) -> str: - """Wrap a header row in OOB div with child placeholder.""" - return render("blog-oob-header", - parent_id=parent_id, child_id=child_id, row_html=row_html, - ) +_oob_header_html = oob_header_html # --------------------------------------------------------------------------- @@ -48,59 +46,40 @@ def _blog_header_html(ctx: dict, *, oob: bool = False) -> str: # --------------------------------------------------------------------------- -# Post header helpers +# Post header helpers — thin wrapper over shared post_header_html # --------------------------------------------------------------------------- def _post_header_html(ctx: dict, *, oob: bool = False) -> str: - """Build the post-level header row.""" - post = ctx.get("post") or {} - slug = post.get("slug", "") - title = (post.get("title") or "")[:160] - feature_image = post.get("feature_image") + """Build the post-level header row (blog-specific: container-nav wrapping + admin cog).""" + overrides: dict = {} - label_html = render("blog-post-label", - feature_image=feature_image, title=title, - ) - - nav_parts = [] - 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}/") - nav_parts.append(render("blog-post-cart-link", - href=cart_href, count=str(page_cart_count), - )) - - # Container nav fragments (calendars, markets) + # Blog wraps container_nav_html in border styling container_nav = ctx.get("container_nav_html", "") if container_nav: - nav_parts.append(render("blog-container-nav", + overrides["container_nav_html"] = render("blog-container-nav", container_nav_html=container_nav, - )) + ) - # Admin link - from quart import url_for as qurl, g, request + # Admin cog link + from quart import url_for as qurl, request + post = ctx.get("post") or {} + slug = post.get("slug", "") rights = ctx.get("rights") or {} has_admin = rights.get("admin") if isinstance(rights, dict) else getattr(rights, "admin", False) - if has_admin: + if has_admin and slug: select_colours = ctx.get("select_colours", "") styles = ctx.get("styles") or {} nav_btn = styles.get("nav_button", "") if isinstance(styles, dict) else getattr(styles, "nav_button", "") admin_href = qurl("blog.post.admin.admin", slug=slug) is_admin_page = "/admin" in request.path - nav_parts.append(render("nav-link", + overrides["post_admin_nav_html"] = render("nav-link", href=admin_href, hx_select="#main-panel", icon="fa fa-cog", aclass=f"{nav_btn} {select_colours}", select_colours=select_colours, is_selected=is_admin_page, - )) + ) - nav_html = "".join(nav_parts) - link_href = call_url(ctx, "blog_url", f"/{slug}/") - - return render("menu-row", - id="post-row", level=1, - link_href=link_href, link_label_html=label_html, - nav_html=nav_html, child_id="post-header-child", oob=oob, - ) + effective_ctx = {**ctx, **overrides} if overrides else ctx + return _shared_post_header_html(effective_ctx, oob=oob) # --------------------------------------------------------------------------- diff --git a/events/sexp/header.sexpr b/events/sexp/header.sexpr index 84d6990..69ba3dd 100644 --- a/events/sexp/header.sexpr +++ b/events/sexp/header.sexpr @@ -1,20 +1,5 @@ ;; Events header components -(defcomp ~events-oob-header (&key parent-id child-id row-html) - (div :id parent-id :hx-swap-oob "outerHTML" :class "w-full" - (div :class "w-full" - (raw! row-html) - (div :id child-id)))) - -(defcomp ~events-post-label (&key feature-image title) - (<> (when feature-image (img :src feature-image :class "h-8 w-8 rounded-full object-cover border border-stone-300 flex-shrink-0")) - (span title))) - -(defcomp ~events-post-cart-link (&key href count) - (a :href href :class "relative inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-full border border-emerald-300 bg-emerald-50 text-emerald-800 hover:bg-emerald-100 transition" - (i :class "fa fa-shopping-cart" :aria-hidden "true") - (span count))) - (defcomp ~events-calendars-label () (<> (i :class "fa fa-calendar" :aria-hidden "true") (div "Calendars"))) @@ -39,5 +24,3 @@ (div :id (str "entry-title-" entry-id) :class "flex gap-1 items-center" (raw! title-html) (raw! times-html))) -(defcomp ~events-header-child (&key inner-html) - (div :id "root-header-child" :class "w-full" (raw! inner-html))) diff --git a/events/sexp/sexp_components.py b/events/sexp/sexp_components.py index d0039d5..093e9d2 100644 --- a/events/sexp/sexp_components.py +++ b/events/sexp/sexp_components.py @@ -14,6 +14,8 @@ from markupsafe import escape from shared.sexp.jinja_bridge import render, load_service_components from shared.sexp.helpers import ( call_url, get_asset_url, root_header_html, + post_header_html as _shared_post_header_html, + oob_header_html, search_mobile_html, search_desktop_html, full_page, oob_page, ) @@ -23,46 +25,21 @@ load_service_components(os.path.dirname(os.path.dirname(__file__))) # --------------------------------------------------------------------------- -# OOB header helper (same pattern as market) +# OOB header helper — delegates to shared # --------------------------------------------------------------------------- -def _oob_header_html(parent_id: str, child_id: str, row_html: str) -> str: - """Wrap a header row in OOB div with child placeholder.""" - return render("events-oob-header", - parent_id=parent_id, child_id=child_id, row_html=row_html) +_oob_header_html = oob_header_html # --------------------------------------------------------------------------- -# Post header helpers (mirrors events/templates/_types/post/header/_header.html) +# Post header helpers — thin wrapper over shared post_header_html # --------------------------------------------------------------------------- def _post_header_html(ctx: dict, *, oob: bool = False) -> str: - """Build the post-level header row.""" - post = ctx.get("post") or {} - slug = post.get("slug", "") - title = (post.get("title") or "")[:160] - feature_image = post.get("feature_image") - - label_html = render("events-post-label", - feature_image=feature_image, title=title) - - nav_parts = [] - 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}/") - nav_parts.append(render("events-post-cart-link", - href=cart_href, count=str(page_cart_count))) - - # Post nav: calendar links + admin - nav_parts.append(_post_nav_html(ctx)) - - nav_html = "".join(nav_parts) - link_href = call_url(ctx, "blog_url", f"/{slug}/") - - return render("menu-row", id="post-row", level=1, - link_href=link_href, link_label_html=label_html, - nav_html=nav_html, child_id="post-header-child", oob=oob, - external=True) + """Build the post-level header row (events-specific: calendar links + admin cog).""" + admin_nav = _post_nav_html(ctx) + effective_ctx = {**ctx, "post_admin_nav_html": admin_nav} if admin_nav else ctx + return _shared_post_header_html(effective_ctx, oob=oob) def _post_nav_html(ctx: dict) -> str: @@ -1235,7 +1212,7 @@ async def render_page_summary_page(ctx: dict, entries, has_more, pending_tickets ) hdr = root_header_html(ctx) - hdr += render("events-header-child", + hdr += render("header-child", inner_html=_post_header_html(ctx)) return full_page(ctx, header_rows_html=hdr, content_html=content) @@ -1289,7 +1266,7 @@ async def render_calendars_page(ctx: dict) -> str: content = _calendars_main_panel_html(ctx) hdr = root_header_html(ctx) child = _post_header_html(ctx) + _calendars_header_html(ctx) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) return full_page(ctx, header_rows_html=hdr, content_html=content) @@ -1311,7 +1288,7 @@ async def render_calendar_page(ctx: dict) -> str: content = _calendar_main_panel_html(ctx) hdr = root_header_html(ctx) child = _post_header_html(ctx) + _calendar_header_html(ctx) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) return full_page(ctx, header_rows_html=hdr, content_html=content) @@ -1334,7 +1311,7 @@ async def render_day_page(ctx: dict) -> str: hdr = root_header_html(ctx) child = (_post_header_html(ctx) + _calendar_header_html(ctx) + _day_header_html(ctx)) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) return full_page(ctx, header_rows_html=hdr, content_html=content) @@ -1358,7 +1335,7 @@ async def render_day_admin_page(ctx: dict) -> str: child = (_post_header_html(ctx) + _calendar_header_html(ctx) + _day_header_html(ctx) + _day_admin_header_html(ctx)) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) return full_page(ctx, header_rows_html=hdr, content_html=content) @@ -1381,7 +1358,7 @@ async def render_calendar_admin_page(ctx: dict) -> str: hdr = root_header_html(ctx) child = (_post_header_html(ctx) + _calendar_header_html(ctx) + _calendar_admin_header_html(ctx)) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) return full_page(ctx, header_rows_html=hdr, content_html=content) @@ -1407,7 +1384,7 @@ async def render_slots_page(ctx: dict) -> str: hdr = root_header_html(ctx) child = (_post_header_html(ctx) + _calendar_header_html(ctx) + _calendar_admin_header_html(ctx)) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) return full_page(ctx, header_rows_html=hdr, content_html=content) @@ -1476,7 +1453,7 @@ async def render_markets_page(ctx: dict) -> str: content = _markets_main_panel_html(ctx) hdr = root_header_html(ctx) child = _post_header_html(ctx) + _markets_header_html(ctx) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) return full_page(ctx, header_rows_html=hdr, content_html=content) @@ -1860,7 +1837,7 @@ async def render_entry_page(ctx: dict) -> str: child = (_post_header_html(ctx) + _calendar_header_html(ctx) + _day_header_html(ctx) + _entry_header_html(ctx)) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) nav_html = _entry_nav_html(ctx) return full_page(ctx, header_rows_html=hdr, content_html=content, menu_html=nav_html) @@ -2873,7 +2850,7 @@ async def render_entry_admin_page(ctx: dict) -> str: child = (_post_header_html(ctx) + _calendar_header_html(ctx) + _day_header_html(ctx) + _entry_header_html(ctx) + _entry_admin_header_html(ctx)) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) nav_html = render("events-admin-placeholder-nav") return full_page(ctx, header_rows_html=hdr, content_html=content, menu_html=nav_html) @@ -2932,7 +2909,7 @@ async def render_slot_page(ctx: dict) -> str: child = (_post_header_html(ctx) + _calendar_header_html(ctx) + _calendar_admin_header_html(ctx) + _slot_header_html(ctx)) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) return full_page(ctx, header_rows_html=hdr, content_html=content) @@ -3140,7 +3117,7 @@ async def render_ticket_types_page(ctx: dict) -> str: + _calendar_header_html(ctx) + _day_header_html(ctx) + _entry_header_html(ctx) + _entry_admin_header_html(ctx) + _ticket_types_header_html(ctx)) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) nav_html = render("events-admin-placeholder-nav") return full_page(ctx, header_rows_html=hdr, content_html=content, menu_html=nav_html) @@ -3214,7 +3191,7 @@ async def render_ticket_type_page(ctx: dict) -> str: + _calendar_header_html(ctx) + _day_header_html(ctx) + _entry_header_html(ctx) + _entry_admin_header_html(ctx) + _ticket_types_header_html(ctx) + _ticket_type_header_html(ctx)) - hdr += render("events-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) nav_html = render("events-admin-placeholder-nav") return full_page(ctx, header_rows_html=hdr, content_html=content, menu_html=nav_html) diff --git a/market/sexp/headers.sexpr b/market/sexp/headers.sexpr index 1bbfe7c..57e7633 100644 --- a/market/sexp/headers.sexpr +++ b/market/sexp/headers.sexpr @@ -1,16 +1,5 @@ ;; Market header components -(defcomp ~market-post-label-image (&key src) - (img :src src :class "h-8 w-8 rounded-full object-cover border border-stone-300 flex-shrink-0")) - -(defcomp ~market-post-label-title (&key title) - (span title)) - -(defcomp ~market-post-cart-badge (&key href count) - (a :href href :class "relative inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-full border border-emerald-300 bg-emerald-50 text-emerald-800 hover:bg-emerald-100 transition" - (i :class "fa fa-shopping-cart" :aria-hidden "true") - (span count))) - (defcomp ~market-shop-label (&key title top-slug sub-div-html) (div :class "font-bold text-xl flex-shrink-0 flex gap-2 items-center" (div (i :class "fa fa-shop") " " title) @@ -29,10 +18,3 @@ :class "px-2 py-1 text-stone-500 hover:text-stone-700" (i :class "fa fa-cog" :aria-hidden "true"))) -(defcomp ~market-oob-header (&key parent-id child-id row-html) - (div :id parent-id :hx-swap-oob "outerHTML" :class "w-full" - (div :class "w-full" (raw! row-html) - (div :id child-id)))) - -(defcomp ~market-header-child (&key inner-html) - (div :id "root-header-child" :class "w-full" (raw! inner-html))) diff --git a/market/sexp/sexp_components.py b/market/sexp/sexp_components.py index fcf6616..3131aa7 100644 --- a/market/sexp/sexp_components.py +++ b/market/sexp/sexp_components.py @@ -14,6 +14,8 @@ from markupsafe import escape from shared.sexp.jinja_bridge import render, load_service_components from shared.sexp.helpers import ( call_url, get_asset_url, root_header_html, + post_header_html as _post_header_html, + oob_header_html as _oob_header_html, search_mobile_html, search_desktop_html, full_page, oob_page, ) @@ -66,42 +68,9 @@ def _card_price_html(p: dict) -> str: # --------------------------------------------------------------------------- -# Header helpers +# Header helpers — _post_header_html and _oob_header_html imported from shared # --------------------------------------------------------------------------- -def _post_header_html(ctx: dict, *, oob: bool = False) -> str: - """Build the post-level header row (feature image + title + page cart count).""" - post = ctx.get("post") or {} - slug = post.get("slug", "") - title = (post.get("title") or "")[:160] - feature_image = post.get("feature_image") - - label_html = "" - if feature_image: - label_html += render("market-post-label-image", src=feature_image) - label_html += render("market-post-label-title", title=title) - - nav_html = "" - 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}/") - nav_html += render("market-post-cart-badge", href=cart_href, count=str(page_cart_count)) - - # Container nav - container_nav = ctx.get("container_nav_html", "") - if container_nav: - nav_html += container_nav - - link_href = call_url(ctx, "blog_url", f"/{slug}/") - - return render( - "menu-row", - id="post-row", level=1, - link_href=link_href, link_label_html=label_html, - nav_html=nav_html, child_id="post-header-child", oob=oob, - external=True, - ) - def _market_header_html(ctx: dict, *, oob: bool = False) -> str: """Build the market-level header row (shop icon + market title + category slugs + nav).""" @@ -1153,16 +1122,9 @@ def _market_cards_html(markets: list, page_info: dict, page: int, has_more: bool # --------------------------------------------------------------------------- -# OOB header helpers +# OOB header helpers — _oob_header_html imported from shared # --------------------------------------------------------------------------- -def _oob_header_html(parent_id: str, child_id: str, row_html: str) -> str: - """Wrap a header row in OOB div with child placeholder.""" - return render( - "market-oob-header", - parent_id=parent_id, child_id=child_id, row_html=row_html, - ) - # =========================================================================== # PUBLIC API @@ -1258,7 +1220,7 @@ async def render_page_markets_page(ctx: dict, markets: list, has_more: bool, content += render("market-bottom-spacer") hdr = root_header_html(ctx) - hdr += render("market-header-child", inner_html=_post_header_html(ctx)) + hdr += render("header-child", inner_html=_post_header_html(ctx)) return full_page(ctx, header_rows_html=hdr, content_html=content) @@ -1309,7 +1271,7 @@ async def render_market_home_page(ctx: dict) -> str: hdr = root_header_html(ctx) child = _post_header_html(ctx) + _market_header_html(ctx) - hdr += render("market-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) menu = _mobile_nav_panel_html(ctx) return full_page(ctx, header_rows_html=hdr, content_html=content, menu_html=menu) @@ -1354,7 +1316,7 @@ async def render_browse_page(ctx: dict) -> str: hdr = root_header_html(ctx) child = _post_header_html(ctx) + _market_header_html(ctx) - hdr += render("market-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) menu = _mobile_nav_panel_html(ctx) filter_html = _mobile_filter_summary_html(ctx) aside_html = _desktop_filter_html(ctx) @@ -1395,7 +1357,7 @@ async def render_product_page(ctx: dict, d: dict) -> str: hdr = root_header_html(ctx) child = _post_header_html(ctx) + _market_header_html(ctx) + _product_header_html(ctx, d) - hdr += render("market-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) return full_page(ctx, header_rows_html=hdr, content_html=content, meta_html=meta) @@ -1421,7 +1383,7 @@ async def render_product_admin_page(ctx: dict, d: dict) -> str: hdr = root_header_html(ctx) child = (_post_header_html(ctx) + _market_header_html(ctx) + _product_header_html(ctx, d) + _product_admin_header_html(ctx, d)) - hdr += render("market-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) return full_page(ctx, header_rows_html=hdr, content_html=content) @@ -1459,7 +1421,7 @@ async def render_market_admin_page(ctx: dict) -> str: hdr = root_header_html(ctx) child = _post_header_html(ctx) + _market_header_html(ctx) + _market_admin_header_html(ctx) - hdr += render("market-header-child", inner_html=child) + hdr += render("header-child", inner_html=child) return full_page(ctx, header_rows_html=hdr, content_html=content) diff --git a/shared/browser/app/errors.py b/shared/browser/app/errors.py index 7cb2c3a..4a0d751 100644 --- a/shared/browser/app/errors.py +++ b/shared/browser/app/errors.py @@ -83,8 +83,7 @@ async def _rich_error_page(errnum: str, message: str, image: str | None = None) return None try: - from shared.sexp.jinja_bridge import render - from shared.sexp.helpers import full_page, call_url + from shared.sexp.helpers import full_page # Build a minimal context — avoid get_template_context() which # calls cross-service fragment fetches that may fail. @@ -150,44 +149,22 @@ async def _rich_error_page(errnum: str, message: str, image: str | None = None) pass # Root header (site nav bar) - from shared.sexp.helpers import root_header_html + from shared.sexp.helpers import ( + root_header_html, post_header_html, + header_child_html, error_content_html, + ) hdr = root_header_html(ctx) # Post breadcrumb if we resolved a post post = (post_data or {}).get("post") or ctx.get("post") or {} if post.get("slug"): - title = (post.get("title") or "")[:160] - feature_image = post.get("feature_image") - label_html = "" - if feature_image: - label_html += ( - f'' - ) - label_html += f"{escape(title)}" - post_row = render( - "menu-row", - id="post-row", level=1, - link_href=call_url(ctx, "blog_url", f"/{post['slug']}/"), - link_label_html=label_html, - child_id="post-header-child", - external=True, - ) - hdr += ( - f'
' - f'{post_row}' - f'
' - ) + ctx["post"] = post + post_row = post_header_html(ctx) + if post_row: + hdr += header_child_html(post_row) # Error content - error_html = ( - '
' - f'
{errnum}
' - f'
{escape(message)}
' - ) - if image: - error_html += f'
' - error_html += "
" + error_html = error_content_html(errnum, message, image) return full_page(ctx, header_rows_html=hdr, content_html=error_html) except Exception: diff --git a/shared/sexp/helpers.py b/shared/sexp/helpers.py index 9533692..59f17aa 100644 --- a/shared/sexp/helpers.py +++ b/shared/sexp/helpers.py @@ -67,6 +67,59 @@ def search_desktop_html(ctx: dict) -> str: ) +def post_header_html(ctx: dict, *, oob: bool = False) -> str: + """Build the post-level header row (level 1). Used by all apps + error pages.""" + post = ctx.get("post") or {} + slug = post.get("slug", "") + if not slug: + return "" + title = (post.get("title") or "")[:160] + feature_image = post.get("feature_image") + + label_html = render("post-label", feature_image=feature_image, title=title) + + nav_parts: list[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}/") + nav_parts.append(render("page-cart-badge", href=cart_href, count=str(page_cart_count))) + + container_nav = ctx.get("container_nav_html", "") + if container_nav: + nav_parts.append(container_nav) + + admin_nav = ctx.get("post_admin_nav_html", "") + if admin_nav: + nav_parts.append(admin_nav) + + nav_html = "".join(nav_parts) + link_href = call_url(ctx, "blog_url", f"/{slug}/") + + return render("menu-row", + id="post-row", level=1, + link_href=link_href, link_label_html=label_html, + nav_html=nav_html, child_id="post-header-child", + oob=oob, external=True, + ) + + +def oob_header_html(parent_id: str, child_id: str, row_html: str) -> str: + """Wrap a header row in an OOB swap div with child placeholder.""" + return render("oob-header", + parent_id=parent_id, child_id=child_id, row_html=row_html, + ) + + +def header_child_html(inner_html: str, *, id: str = "root-header-child") -> str: + """Wrap inner HTML in a header-child div.""" + return render("header-child", id=id, inner_html=inner_html) + + +def error_content_html(errnum: str, message: str, image: str | None = None) -> str: + """Render the error content block.""" + return render("error-content", errnum=errnum, message=message, image=image) + + def full_page(ctx: dict, *, header_rows_html: str, filter_html: str = "", aside_html: str = "", content_html: str = "", menu_html: str = "", diff --git a/shared/sexp/templates/layout.sexp b/shared/sexp/templates/layout.sexp index 3877a7f..cc34cf3 100644 --- a/shared/sexp/templates/layout.sexp +++ b/shared/sexp/templates/layout.sexp @@ -146,6 +146,32 @@ (div :id child-id :class "flex flex-col w-full items-center" (when child-html (raw! child-html))))))) +(defcomp ~post-label (&key feature-image title) + (<> (when feature-image + (img :src feature-image :class "h-8 w-8 rounded-full object-cover border border-stone-300 flex-shrink-0")) + (span title))) + +(defcomp ~page-cart-badge (&key href count) + (a :href href :class "relative inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-full border border-emerald-300 bg-emerald-50 text-emerald-800 hover:bg-emerald-100 transition" + (i :class "fa fa-shopping-cart" :aria-hidden "true") + (span count))) + +(defcomp ~oob-header (&key parent-id child-id row-html) + (div :id parent-id :hx-swap-oob "outerHTML" :class "w-full" + (div :class "w-full" (raw! row-html) + (div :id child-id)))) + +(defcomp ~header-child (&key id inner-html) + (div :id (or id "root-header-child") :class "w-full" (raw! inner-html))) + +(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) + (div :class "text-stone-600 mb-4" message) + (when image + (div :class "flex justify-center" + (img :src image :width "300" :height "300"))))) + (defcomp ~nav-link (&key href hx-select label icon aclass select-colours is-selected) (div :class "relative nav-group" (a :href href