Refactor SX templates: shared components, Python migration, cleanup
- Extract shared components (empty-state, delete-btn, sentinel, crud-*, view-toggle, img-or-placeholder, avatar, sumup-settings-form, auth forms, order tables/detail/checkout) - Migrate all Python sx_call() callers to use shared components directly - Remove 55+ thin wrapper defcomps from domain .sx files - Remove trivial passthrough wrappers (blog-header-label, market-card-text, etc) - Unify duplicate auth flows (account + federation) into shared/sx/templates/auth.sx - Unify duplicate order views (cart + orders) into shared/sx/templates/orders.sx - Disable static file caching in dev (SEND_FILE_MAX_AGE_DEFAULT=0) - Add SX response validation and debug headers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -59,9 +59,10 @@ def register():
|
||||
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 = sx_call("blog-nav-item-image",
|
||||
img = sx_call("img-or-placeholder",
|
||||
src=getattr(item, "feature_image", None),
|
||||
label=getattr(item, "label", item.slug))
|
||||
alt=getattr(item, "label", item.slug),
|
||||
size_cls="w-8 h-8 rounded-full object-cover flex-shrink-0")
|
||||
item_sxs.append(sx_call(
|
||||
"blog-nav-item-link",
|
||||
href=href, hx_get=href, selected=selected, nav_cls=nav_cls,
|
||||
@@ -72,7 +73,8 @@ def register():
|
||||
href = artdag_url("/")
|
||||
selected = "true" if ("artdag" == first_seg
|
||||
or "artdag" == app_name) else "false"
|
||||
img = sx_call("blog-nav-item-image", src=None, label="art-dag")
|
||||
img = sx_call("img-or-placeholder", src=None, alt="art-dag",
|
||||
size_cls="w-8 h-8 rounded-full object-cover flex-shrink-0")
|
||||
item_sxs.append(sx_call(
|
||||
"blog-nav-item-link",
|
||||
href=href, hx_get=href, selected=selected, nav_cls=nav_cls,
|
||||
@@ -101,13 +103,15 @@ def register():
|
||||
right_hs = ("on click set #" + container_id
|
||||
+ ".scrollLeft to #" + container_id + ".scrollLeft + 200")
|
||||
|
||||
return sx_call("blog-nav-wrapper",
|
||||
arrow_cls=arrow_cls,
|
||||
return sx_call("scroll-nav-wrapper",
|
||||
wrapper_id="menu-items-nav-wrapper",
|
||||
container_id=container_id,
|
||||
arrow_cls=arrow_cls,
|
||||
left_hs=left_hs,
|
||||
scroll_hs=scroll_hs,
|
||||
right_hs=right_hs,
|
||||
items=SxExpr(items_frag))
|
||||
items=SxExpr(items_frag),
|
||||
oob=True)
|
||||
|
||||
_handlers["nav-tree"] = _nav_tree_handler
|
||||
|
||||
|
||||
@@ -14,12 +14,6 @@
|
||||
(h1 :class "text-3xl font-bold" "Snippets"))
|
||||
(div :id "snippets-list" list)))
|
||||
|
||||
(defcomp ~blog-snippets-empty ()
|
||||
(div :class "bg-white rounded-lg shadow"
|
||||
(div :class "p-8 text-center text-stone-400"
|
||||
(i :class "fa fa-puzzle-piece text-4xl mb-2")
|
||||
(p "No snippets yet. Create one from the blog editor."))))
|
||||
|
||||
(defcomp ~blog-snippet-visibility-select (&key patch-url hx-headers options cls)
|
||||
(select :name "visibility" :sx-patch patch-url :sx-target "#snippets-list" :sx-swap "innerHTML"
|
||||
:sx-headers hx-headers :class "text-sm border border-stone-300 rounded px-2 py-1"
|
||||
@@ -28,16 +22,6 @@
|
||||
(defcomp ~blog-snippet-option (&key value selected label)
|
||||
(option :value value :selected selected label))
|
||||
|
||||
(defcomp ~blog-snippet-delete-button (&key confirm-text delete-url hx-headers)
|
||||
(button :type "button" :data-confirm "" :data-confirm-title "Delete snippet?"
|
||||
:data-confirm-text confirm-text :data-confirm-icon "warning"
|
||||
:data-confirm-confirm-text "Yes, delete" :data-confirm-cancel-text "Cancel"
|
||||
:data-confirm-event "confirmed"
|
||||
:sx-delete delete-url :sx-trigger "confirmed" :sx-target "#snippets-list" :sx-swap "innerHTML"
|
||||
:sx-headers hx-headers
|
||||
:class "px-3 py-1 text-sm bg-red-200 hover:bg-red-300 rounded text-red-800 flex-shrink-0"
|
||||
(i :class "fa fa-trash") " Delete"))
|
||||
|
||||
(defcomp ~blog-snippet-row (&key name owner badge-cls visibility extra)
|
||||
(div :class "flex items-center gap-4 p-4 hover:bg-stone-50 transition"
|
||||
(div :class "flex-1 min-w-0"
|
||||
@@ -58,16 +42,6 @@
|
||||
(div :id "menu-item-form" :class "mb-6")
|
||||
(div :id "menu-items-list" list)))
|
||||
|
||||
(defcomp ~blog-menu-items-empty ()
|
||||
(div :class "bg-white rounded-lg shadow"
|
||||
(div :class "p-8 text-center text-stone-400"
|
||||
(i :class "fa fa-inbox text-4xl mb-2")
|
||||
(p "No menu items yet. Add one to get started!"))))
|
||||
|
||||
(defcomp ~blog-menu-item-image (&key src label)
|
||||
(if src (img :src src :alt label :class "w-12 h-12 rounded-full object-cover flex-shrink-0")
|
||||
(div :class "w-12 h-12 rounded-full bg-stone-200 flex-shrink-0")))
|
||||
|
||||
(defcomp ~blog-menu-item-row (&key img label slug sort-order edit-url delete-url confirm-text hx-headers)
|
||||
(div :class "flex items-center gap-4 p-4 hover:bg-stone-50 transition"
|
||||
(div :class "text-stone-400 cursor-move" (i :class "fa fa-grip-vertical"))
|
||||
@@ -80,14 +54,9 @@
|
||||
(button :type "button" :sx-get edit-url :sx-target "#menu-item-form" :sx-swap "innerHTML"
|
||||
:class "px-3 py-1 text-sm bg-stone-200 hover:bg-stone-300 rounded"
|
||||
(i :class "fa fa-edit") " Edit")
|
||||
(button :type "button" :data-confirm "" :data-confirm-title "Delete menu item?"
|
||||
:data-confirm-text confirm-text :data-confirm-icon "warning"
|
||||
:data-confirm-confirm-text "Yes, delete" :data-confirm-cancel-text "Cancel"
|
||||
:data-confirm-event "confirmed"
|
||||
:sx-delete delete-url :sx-trigger "confirmed" :sx-target "#menu-items-list" :sx-swap "innerHTML"
|
||||
:sx-headers hx-headers
|
||||
:class "px-3 py-1 text-sm bg-red-200 hover:bg-red-300 rounded text-red-800"
|
||||
(i :class "fa fa-trash") " Delete"))))
|
||||
(~delete-btn :url delete-url :trigger-target "#menu-items-list"
|
||||
:title "Delete menu item?" :text confirm-text
|
||||
:sx-headers hx-headers))))
|
||||
|
||||
(defcomp ~blog-menu-items-list (&key rows)
|
||||
(div :class "bg-white rounded-lg shadow" (div :class "divide-y" rows)))
|
||||
@@ -123,9 +92,6 @@
|
||||
(defcomp ~blog-tag-groups-list (&key items)
|
||||
(ul :class "space-y-2" items))
|
||||
|
||||
(defcomp ~blog-tag-groups-empty ()
|
||||
(p :class "text-stone-500 text-sm" "No tag groups yet."))
|
||||
|
||||
(defcomp ~blog-unassigned-tag (&key name)
|
||||
(span :class "inline-block bg-stone-100 text-stone-600 px-2 py-1 text-xs font-medium border border-stone-200 rounded" name))
|
||||
|
||||
|
||||
@@ -52,9 +52,3 @@
|
||||
|
||||
(defcomp ~blog-home-main (&key html-content)
|
||||
(article :class "relative" (div :class "blog-content p-2" (~rich-text :html html-content))))
|
||||
|
||||
(defcomp ~blog-admin-empty ()
|
||||
(div :class "pb-8"))
|
||||
|
||||
(defcomp ~blog-settings-empty ()
|
||||
(div :class "max-w-2xl mx-auto px-4 py-6"))
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
;; Blog header components
|
||||
|
||||
(defcomp ~blog-header-label ()
|
||||
(div))
|
||||
|
||||
(defcomp ~blog-container-nav (&key container-nav)
|
||||
(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" container-nav))
|
||||
|
||||
@@ -1,55 +1,8 @@
|
||||
;; Blog index components
|
||||
|
||||
(defcomp ~blog-end-of-results ()
|
||||
(div :class "col-span-full mt-4 text-center text-xs text-stone-400" "End of results"))
|
||||
|
||||
(defcomp ~blog-sentinel-mobile (&key id next-url hyperscript)
|
||||
(div :id id :class "block md:hidden h-[60vh] opacity-0 pointer-events-none js-mobile-sentinel"
|
||||
:sx-get next-url :sx-trigger "intersect once delay:250ms, sentinelmobile:retry"
|
||||
:sx-swap "outerHTML" :_ hyperscript
|
||||
:role "status" :aria-live "polite" :aria-hidden "true"
|
||||
(div :class "js-loading hidden flex justify-center py-8"
|
||||
(div :class "animate-spin h-8 w-8 border-4 border-stone-300 border-t-stone-600 rounded-full"))
|
||||
(div :class "js-neterr hidden text-center py-8 text-stone-400"
|
||||
(i :class "fa fa-exclamation-triangle text-2xl")
|
||||
(p :class "mt-2" "Loading failed \u2014 retrying\u2026"))))
|
||||
|
||||
(defcomp ~blog-sentinel-desktop (&key id next-url hyperscript)
|
||||
(div :id id :class "hidden md:block h-4 opacity-0 pointer-events-none"
|
||||
:sx-get next-url :sx-trigger "intersect once delay:250ms, sentinel:retry"
|
||||
:sx-swap "outerHTML" :_ hyperscript
|
||||
:role "status" :aria-live "polite" :aria-hidden "true"
|
||||
(div :class "js-loading hidden flex justify-center py-2"
|
||||
(div :class "animate-spin h-6 w-6 border-2 border-stone-300 border-t-stone-600 rounded-full"))
|
||||
(div :class "js-neterr hidden text-center py-2 text-stone-400 text-sm" "Retry\u2026")))
|
||||
|
||||
(defcomp ~blog-page-sentinel (&key id next-url)
|
||||
(div :id id :class "h-4 opacity-0 pointer-events-none"
|
||||
:sx-get next-url :sx-trigger "intersect once delay:250ms" :sx-swap "outerHTML"))
|
||||
|
||||
(defcomp ~blog-no-pages ()
|
||||
(div :class "col-span-full mt-8 text-center text-stone-500" "No pages found."))
|
||||
|
||||
(defcomp ~blog-list-svg ()
|
||||
(svg :xmlns "http://www.w3.org/2000/svg" :class "h-5 w-5" :fill "none" :viewBox "0 0 24 24"
|
||||
:stroke "currentColor" :stroke-width "2"
|
||||
(path :stroke-linecap "round" :stroke-linejoin "round" :d "M4 6h16M4 12h16M4 18h16")))
|
||||
|
||||
(defcomp ~blog-tile-svg ()
|
||||
(svg :xmlns "http://www.w3.org/2000/svg" :class "h-5 w-5" :fill "none" :viewBox "0 0 24 24"
|
||||
:stroke "currentColor" :stroke-width "2"
|
||||
(path :stroke-linecap "round" :stroke-linejoin "round"
|
||||
:d "M4 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM14 5a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1V5zM4 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1H5a1 1 0 01-1-1v-4zM14 15a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z")))
|
||||
|
||||
(defcomp ~blog-view-toggle (&key list-href tile-href hx-select list-cls tile-cls list-svg tile-svg)
|
||||
(div :class "hidden md:flex justify-end px-3 pt-3 gap-1"
|
||||
(a :href list-href :sx-get list-href :sx-target "#main-panel" :sx-select hx-select
|
||||
:sx-swap "outerHTML" :sx-push-url "true" :class (str "p-1.5 rounded " list-cls) :title "List view"
|
||||
:_ "on click js localStorage.removeItem('blog_view') end" list-svg)
|
||||
(a :href tile-href :sx-get tile-href :sx-target "#main-panel" :sx-select hx-select
|
||||
:sx-swap "outerHTML" :sx-push-url "true" :class (str "p-1.5 rounded " tile-cls) :title "Tile view"
|
||||
:_ "on click js localStorage.setItem('blog_view','tile') end" tile-svg)))
|
||||
|
||||
(defcomp ~blog-content-type-tabs (&key posts-href pages-href hx-select posts-cls pages-cls)
|
||||
(div :class "flex justify-center gap-1 px-3 pt-3"
|
||||
(a :href posts-href :sx-get posts-href :sx-target "#main-panel"
|
||||
|
||||
@@ -18,33 +18,11 @@
|
||||
(i :class "fa fa-shopping-bag text-green-600 mr-1")
|
||||
" Market \u2014 enable product catalog on this page"))))
|
||||
|
||||
(defcomp ~blog-sumup-connected ()
|
||||
(span :class "ml-2 text-xs text-green-600" (i :class "fa fa-check-circle") " Connected"))
|
||||
|
||||
(defcomp ~blog-sumup-key-hint ()
|
||||
(p :class "text-xs text-stone-400 mt-0.5" "Key is set. Leave blank to keep current key."))
|
||||
|
||||
(defcomp ~blog-sumup-form (&key sumup-url merchant-code placeholder key-hint checkout-prefix connected)
|
||||
(defcomp ~blog-sumup-form (&key sumup-url merchant-code placeholder sumup-configured checkout-prefix)
|
||||
(div :class "mt-4 pt-4 border-t border-stone-100"
|
||||
(h4 :class "text-sm font-medium text-stone-700"
|
||||
(i :class "fa fa-credit-card text-purple-600 mr-1") " SumUp Payment")
|
||||
(p :class "text-xs text-stone-400 mt-1 mb-3"
|
||||
"Configure per-page SumUp credentials. Leave blank to use the global merchant account.")
|
||||
(form :sx-put sumup-url :sx-target "#features-panel" :sx-swap "outerHTML" :class "space-y-3"
|
||||
(div (label :class "block text-xs font-medium text-stone-600 mb-1" "Merchant Code")
|
||||
(input :type "text" :name "merchant_code" :value merchant-code :placeholder "e.g. ME4J6100"
|
||||
:class "w-full px-3 py-1.5 text-sm border border-stone-300 rounded focus:ring-purple-500 focus:border-purple-500"))
|
||||
(div (label :class "block text-xs font-medium text-stone-600 mb-1" "API Key")
|
||||
(input :type "password" :name "api_key" :value "" :placeholder placeholder
|
||||
:class "w-full px-3 py-1.5 text-sm border border-stone-300 rounded focus:ring-purple-500 focus:border-purple-500")
|
||||
key-hint)
|
||||
(div (label :class "block text-xs font-medium text-stone-600 mb-1" "Checkout Reference Prefix")
|
||||
(input :type "text" :name "checkout_prefix" :value checkout-prefix :placeholder "e.g. ROSE-"
|
||||
:class "w-full px-3 py-1.5 text-sm border border-stone-300 rounded focus:ring-purple-500 focus:border-purple-500"))
|
||||
(button :type "submit"
|
||||
:class "px-4 py-1.5 text-sm font-medium text-white bg-purple-600 rounded hover:bg-purple-700 focus:ring-2 focus:ring-purple-500"
|
||||
"Save SumUp Settings")
|
||||
connected)))
|
||||
(~sumup-settings-form :update-url sumup-url :merchant-code merchant-code
|
||||
:placeholder placeholder :sumup-configured sumup-configured
|
||||
:checkout-prefix checkout-prefix :panel-id "features-panel")))
|
||||
|
||||
(defcomp ~blog-features-panel (&key form sumup)
|
||||
(div :id "features-panel" :class "space-y-4 p-4 bg-white rounded-lg border border-stone-200"
|
||||
|
||||
@@ -51,10 +51,9 @@ _oob_header_sx = oob_header_sx
|
||||
|
||||
def _blog_header_sx(ctx: dict, *, oob: bool = False) -> str:
|
||||
"""Blog header row — empty child of root."""
|
||||
label_sx = sx_call("blog-header-label")
|
||||
return sx_call("menu-row-sx",
|
||||
id="blog-row", level=1,
|
||||
link_label_content=SxExpr(label_sx),
|
||||
link_label_content=SxExpr("(div)"),
|
||||
child_id="blog-header-child", oob=oob,
|
||||
)
|
||||
|
||||
@@ -160,7 +159,7 @@ def _blog_sentinel_sx(ctx: dict) -> str:
|
||||
total_pages = int(total_pages)
|
||||
|
||||
if page >= total_pages:
|
||||
return sx_call("blog-end-of-results")
|
||||
return sx_call("end-of-results")
|
||||
|
||||
current_local_href = ctx.get("current_local_href", "/index")
|
||||
next_url = f"{current_local_href}?page={page + 1}"
|
||||
@@ -190,9 +189,9 @@ def _blog_sentinel_sx(ctx: dict) -> str:
|
||||
)
|
||||
|
||||
return (
|
||||
sx_call("blog-sentinel-mobile", id=f"sentinel-{page}-m", next_url=next_url, hyperscript=mobile_hs)
|
||||
sx_call("sentinel-mobile", id=f"sentinel-{page}-m", next_url=next_url, hyperscript=mobile_hs)
|
||||
+ " "
|
||||
+ sx_call("blog-sentinel-desktop", id=f"sentinel-{page}-d", next_url=next_url, hyperscript=desktop_hs)
|
||||
+ sx_call("sentinel-desktop", id=f"sentinel-{page}-d", next_url=next_url, hyperscript=desktop_hs)
|
||||
)
|
||||
|
||||
|
||||
@@ -363,11 +362,11 @@ def _page_cards_sx(ctx: dict) -> str:
|
||||
if page_num < total_pages:
|
||||
current_local_href = ctx.get("current_local_href", "/index?type=pages")
|
||||
next_url = f"{current_local_href}&page={page_num + 1}" if "?" in current_local_href else f"{current_local_href}?page={page_num + 1}"
|
||||
parts.append(sx_call("blog-page-sentinel",
|
||||
parts.append(sx_call("sentinel-simple",
|
||||
id=f"sentinel-{page_num}-d", next_url=next_url,
|
||||
))
|
||||
elif pages:
|
||||
parts.append(sx_call("blog-end-of-results"))
|
||||
parts.append(sx_call("end-of-results"))
|
||||
else:
|
||||
parts.append(sx_call("blog-no-pages"))
|
||||
|
||||
@@ -407,12 +406,12 @@ def _view_toggle_sx(ctx: dict) -> str:
|
||||
list_href = f"{current_local_href}"
|
||||
tile_href = f"{current_local_href}{'&' if '?' in current_local_href else '?'}view=tile"
|
||||
|
||||
list_svg_sx = sx_call("blog-list-svg")
|
||||
tile_svg_sx = sx_call("blog-tile-svg")
|
||||
list_svg_sx = sx_call("list-svg")
|
||||
tile_svg_sx = sx_call("tile-svg")
|
||||
|
||||
return sx_call("blog-view-toggle",
|
||||
return sx_call("view-toggle",
|
||||
list_href=list_href, tile_href=tile_href, hx_select=hx_select,
|
||||
list_cls=list_cls, tile_cls=tile_cls,
|
||||
list_cls=list_cls, tile_cls=tile_cls, storage_key="blog_view",
|
||||
list_svg=SxExpr(list_svg_sx), tile_svg=SxExpr(tile_svg_sx),
|
||||
)
|
||||
|
||||
@@ -782,7 +781,7 @@ def _home_main_panel_sx(ctx: dict) -> str:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _post_admin_main_panel_sx(ctx: dict) -> str:
|
||||
return sx_call("blog-admin-empty")
|
||||
return '(div :class "pb-8")'
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -790,7 +789,7 @@ def _post_admin_main_panel_sx(ctx: dict) -> str:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _settings_main_panel_sx(ctx: dict) -> str:
|
||||
return sx_call("blog-settings-empty")
|
||||
return '(div :class "max-w-2xl mx-auto px-4 py-6")'
|
||||
|
||||
|
||||
def _cache_main_panel_sx(ctx: dict) -> str:
|
||||
@@ -821,7 +820,7 @@ def _snippets_list_sx(ctx: dict) -> str:
|
||||
user_id = getattr(user, "id", None)
|
||||
|
||||
if not snippets:
|
||||
return sx_call("blog-snippets-empty")
|
||||
return sx_call("empty-state", icon="fa fa-puzzle-piece", message="No snippets yet. Create one from the blog editor.")
|
||||
|
||||
badge_colours = {
|
||||
"private": "bg-stone-200 text-stone-700",
|
||||
@@ -856,10 +855,12 @@ def _snippets_list_sx(ctx: dict) -> str:
|
||||
|
||||
if s_uid == user_id or is_admin:
|
||||
del_url = qurl("snippets.delete_snippet", snippet_id=s_id)
|
||||
extra += sx_call("blog-snippet-delete-button",
|
||||
confirm_text=f'Delete \u201c{s_name}\u201d?',
|
||||
delete_url=del_url,
|
||||
hx_headers=f'{{"X-CSRFToken": "{csrf}"}}',
|
||||
extra += sx_call("delete-btn",
|
||||
url=del_url, trigger_target="#snippets-list",
|
||||
title="Delete snippet?",
|
||||
text=f'Delete \u201c{s_name}\u201d?',
|
||||
sx_headers=f'{{"X-CSRFToken": "{csrf}"}}',
|
||||
cls="px-3 py-1 text-sm bg-red-200 hover:bg-red-300 rounded text-red-800 flex-shrink-0",
|
||||
)
|
||||
|
||||
row_parts.append(sx_call("blog-snippet-row",
|
||||
@@ -890,7 +891,7 @@ def _menu_items_list_sx(ctx: dict) -> str:
|
||||
csrf = _ctx_csrf(ctx)
|
||||
|
||||
if not menu_items:
|
||||
return sx_call("blog-menu-items-empty")
|
||||
return sx_call("empty-state", icon="fa fa-inbox", message="No menu items yet. Add one to get started!")
|
||||
|
||||
row_parts = []
|
||||
for item in menu_items:
|
||||
@@ -903,7 +904,8 @@ def _menu_items_list_sx(ctx: dict) -> str:
|
||||
edit_url = qurl("menu_items.edit_menu_item", item_id=i_id)
|
||||
del_url = qurl("menu_items.delete_menu_item_route", item_id=i_id)
|
||||
|
||||
img_sx = sx_call("blog-menu-item-image", src=fi, label=label)
|
||||
img_sx = sx_call("img-or-placeholder", src=fi, alt=label,
|
||||
size_cls="w-12 h-12 rounded-full object-cover flex-shrink-0")
|
||||
|
||||
row_parts.append(sx_call("blog-menu-item-row",
|
||||
img=SxExpr(img_sx), label=label, slug=slug,
|
||||
@@ -958,7 +960,7 @@ def _tag_groups_main_panel_sx(ctx: dict) -> str:
|
||||
))
|
||||
groups_sx = sx_call("blog-tag-groups-list", items=SxExpr("(<> " + " ".join(li_parts) + ")"))
|
||||
else:
|
||||
groups_sx = sx_call("blog-tag-groups-empty")
|
||||
groups_sx = sx_call("empty-state", message="No tag groups yet.", cls="text-stone-500 text-sm")
|
||||
|
||||
# Unassigned tags
|
||||
unassigned_sx = ""
|
||||
@@ -1687,7 +1689,8 @@ def render_menu_items_nav_oob(menu_items, ctx: dict | None = None) -> str:
|
||||
|
||||
selected = "true" if (item_slug == first_seg or item_slug == app_name) else "false"
|
||||
|
||||
img_sx = sx_call("blog-nav-item-image", src=fi, label=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(sx_call("blog-nav-item-link",
|
||||
@@ -1702,12 +1705,13 @@ def render_menu_items_nav_oob(menu_items, ctx: dict | None = None) -> str:
|
||||
|
||||
items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else ""
|
||||
|
||||
return sx_call("blog-nav-wrapper",
|
||||
arrow_cls=arrow_cls, container_id=container_id,
|
||||
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",
|
||||
scroll_hs=scroll_hs,
|
||||
right_hs=f"on click set #{container_id}.scrollLeft to #{container_id}.scrollLeft + 200",
|
||||
items=SxExpr(items_sx) if items_sx else None,
|
||||
items=SxExpr(items_sx) if items_sx else None, oob=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -1737,15 +1741,12 @@ def render_features_panel(features: dict, post: dict,
|
||||
sumup_sx = ""
|
||||
if features.get("calendar") or features.get("market"):
|
||||
placeholder = "\u2022" * 8 if sumup_configured else "sup_sk_..."
|
||||
connected = sx_call("blog-sumup-connected") if sumup_configured else ""
|
||||
key_hint = sx_call("blog-sumup-key-hint") if sumup_configured else ""
|
||||
|
||||
sumup_sx = sx_call("blog-sumup-form",
|
||||
sumup_url=sumup_url, merchant_code=sumup_merchant_code,
|
||||
placeholder=placeholder,
|
||||
key_hint=SxExpr(key_hint) if key_hint else None,
|
||||
sumup_configured=sumup_configured,
|
||||
checkout_prefix=sumup_checkout_prefix,
|
||||
connected=SxExpr(connected) if connected else None,
|
||||
)
|
||||
|
||||
return sx_call("blog-features-panel",
|
||||
@@ -1906,8 +1907,8 @@ def render_nav_entries_oob(associated_entries, calendars, post: dict, ctx: dict
|
||||
|
||||
href = events_url_fn(entry_path) if events_url_fn else entry_path
|
||||
|
||||
item_parts.append(sx_call("blog-nav-entry-item",
|
||||
href=href, nav_cls=nav_cls, name=e_name, date_str=date_str,
|
||||
item_parts.append(sx_call("calendar-entry-nav",
|
||||
href=href, nav_class=nav_cls, name=e_name, date_str=date_str,
|
||||
))
|
||||
|
||||
# Calendar links
|
||||
@@ -1923,6 +1924,11 @@ def render_nav_entries_oob(associated_entries, calendars, post: dict, ctx: dict
|
||||
|
||||
items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else ""
|
||||
|
||||
return sx_call("blog-nav-entries-wrapper",
|
||||
scroll_hs=scroll_hs, items=SxExpr(items_sx) if items_sx else None,
|
||||
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",
|
||||
scroll_hs=scroll_hs,
|
||||
right_hs="on click set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft + 200",
|
||||
items=SxExpr(items_sx) if items_sx else None, oob=True,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user