SxExpr is now a str subclass so it works everywhere a plain string does (join, isinstance, f-strings) while serialize() still emits it unquoted. sx_call() and all internal render functions (_render_to_sx, async_eval_to_sx, etc.) return SxExpr, eliminating the "forgot to wrap" bug class that caused the sx_content leak and list serialization bugs. - Phase 0: SxExpr(str) with .source property, __add__/__radd__ - Phase 1: sx_call returns SxExpr (drop-in, all 200+ sites unchanged) - Phase 2: async_eval_to_sx, async_eval_slot_to_sx, _render_to_sx, mobile_menu_sx return SxExpr; remove isinstance(str) workaround - Phase 3: Remove ~150 redundant SxExpr() wrappings across 45 files - Phase 4: serialize() docstring, handler return docs, ;; returns: sx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
83 lines
3.6 KiB
Plaintext
83 lines
3.6 KiB
Plaintext
;; Events container-nav fragment handler
|
||
;; returns: sx
|
||
;;
|
||
;; 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))))))
|