Files
mono/events/sx/handlers/container-nav.sx
giles ab75e505a8 Add macros, declarative handlers (defhandler), and convert all fragment routes to sx
Phase 1 — Macros: defmacro + quasiquote syntax (`, ,, ,@) in parser,
evaluator, HTML renderer, and JS mirror. Macro type, expansion, and
round-trip serialization.

Phase 2 — Expanded primitives: app-url, url-for, asset-url, config,
format-date, parse-int (pure); service, request-arg, request-path,
nav-tree, get-children (I/O); jinja-global, relations-from (pure).
Updated _io_service to accept (service "registry-name" "method" :kwargs)
with auto kebab→snake conversion. DTO-to-dict now expands datetime fields
into year/month/day convenience keys. Tuple returns converted to lists.

Phase 3 — Declarative handlers: HandlerDef type, defhandler special form,
handler registry (service → name → HandlerDef), async evaluator+renderer
(async_eval.py) that awaits I/O primitives inline within control flow.
Handler loading from .sx files, execute_handler, blueprint factory.

Phase 4 — Convert all fragment routes: 13 Python fragment handlers across
8 services replaced with declarative .sx handler files. All routes.py
simplified to uniform sx dispatch pattern. Two Jinja HTML handlers
(events/container-cards, events/account-page) kept as Python.

New files: shared/sx/async_eval.py, shared/sx/handlers.py,
shared/sx/tests/test_handlers.py, plus 13 handler .sx files under
{service}/sx/handlers/. MarketService.product_by_slug() added.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 00:22:18 +00:00

82 lines
3.6 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
;; Events container-nav fragment handler
;;
;; Renders calendar entry nav items + calendar link nav items
;; for the scrollable navigation panel on blog post pages.
;;
;; Params (from request.args):
;; container_type — "page" (default)
;; container_id — int
;; post_slug — string
;; paginate_url — base URL for infinite scroll
;; page — current page (default 1)
;; exclude — comma-separated exclusion prefixes
;; current_calendar — currently selected calendar slug
(defhandler container-nav
(&key container_type container_id post_slug paginate_url page exclude current_calendar)
(let ((ct (or container_type "page"))
(cid (parse-int (or container_id "0")))
(slug (or post_slug ""))
(purl (or paginate_url ""))
(pg (parse-int (or page "1")))
(excl-raw (or exclude ""))
(cur-cal (or current_calendar ""))
(excludes (filter (fn (e) (not (empty? e)))
(map trim (split excl-raw ","))))
(has-cal-excl (not (empty? (filter (fn (e) (starts-with? e "calendar"))
excludes))))
(styles (or (jinja-global "styles") (dict)))
(nav-class (or (get styles "nav_button") ""))
(sel-colours (or (jinja-global "select_colours") "")))
;; Only render if no calendar-* exclusion
(when (not has-cal-excl)
(let ((result (service "calendar" "associated-entries"
:content-type ct :content-id cid :page pg))
(entries (first result))
(has-more (nth result 1))
(calendars (service "calendar" "calendars-for-container"
:container-type ct :container-id cid)))
(<>
;; Calendar entry nav items
(map (fn (entry)
(let ((entry-path (str "/" slug "/"
(get entry "calendar_slug") "/"
(get entry "start_at_year") "/"
(get entry "start_at_month") "/"
(get entry "start_at_day")
"/entries/" (get entry "id") "/"))
(date-str (str (format-date (get entry "start_at") "%b %d, %Y at %H:%M")
(if (get entry "end_at")
(str " " (format-date (get entry "end_at") "%H:%M"))
""))))
(~calendar-entry-nav
:href (app-url "events" entry-path)
:name (get entry "name")
:date-str date-str
:nav-class nav-class))) entries)
;; Infinite scroll sentinel
(when (and has-more (not (empty? purl)))
(~htmx-sentinel
:id (str "entries-load-sentinel-" pg)
:hx-get (str purl "?page=" (+ pg 1))
:hx-trigger "intersect once"
:hx-swap "beforebegin"
:class "flex-shrink-0 w-1"))
;; Calendar link nav items
(map (fn (cal)
(let ((href (app-url "events" (str "/" slug "/" (get cal "slug") "/")))
(is-selected (if (not (empty? cur-cal))
(= (get cal "slug") cur-cal)
false)))
(~calendar-link-nav
:href href
:name (get cal "name")
:nav-class nav-class
:is-selected is-selected
:select-colours sel-colours))) calendars))))))