Adopt Step 7 language features across SX codebase
112 conversions across 19 .sx files using match, let-match, and pipe operators: match (17): type/value dispatch replacing cond/if chains - lib/vm.sx: HO form dispatch (for-each/map/filter/reduce/some/every?) - lib/tree-tools.sx: node-display, node-matches?, rename, count, replace, free-symbols - lib/types.sx: narrow-type, substitute-in-type, infer-type, resolve-type - web/engine.sx: default-trigger, resolve-target, classify-trigger - web/deps.sx: scan-refs-walk, scan-io-refs-walk let-match (89): dict destructuring replacing (get d "key") patterns - shared/page-functions.sx (20), blog/admin.sx (17), pub-api.sx (13) - events/ layouts/page/tickets/entries/forms (27 total) - specs-explorer.sx (7), federation/social.sx (3), lib/ small files (3) -> pipes (6): replacing triple-chained gets in lib/vm.sx - frame-closure → closure-code → code-bytecode chains Also: lib/vm.sx accessor upgrades (get vm "sp" → vm-sp vm throughout) 2650/2650 tests pass, zero regressions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -98,24 +98,47 @@
|
||||
(~page/slot-description-oob :description (or description "")))))
|
||||
|
||||
;; Slots table from data
|
||||
(defcomp ~page/slots-table-from-data (&key list-container slots pre-action add-url
|
||||
tr-cls pill-cls action-btn hx-select csrf-hdr)
|
||||
(defcomp
|
||||
~page/slots-table-from-data
|
||||
(&key
|
||||
list-container
|
||||
slots
|
||||
pre-action
|
||||
add-url
|
||||
tr-cls
|
||||
pill-cls
|
||||
action-btn
|
||||
hx-select
|
||||
csrf-hdr)
|
||||
(~page/slots-table
|
||||
:list-container list-container
|
||||
:rows (if (empty? (or slots (list)))
|
||||
:rows (if
|
||||
(empty? (or slots (list)))
|
||||
(~page/slots-empty-row)
|
||||
(<> (map (lambda (s)
|
||||
(~page/slots-row
|
||||
:tr-cls tr-cls :slot-href (get s "slot-href")
|
||||
:pill-cls pill-cls :hx-select hx-select
|
||||
:slot-name (get s "slot-name") :description (get s "description")
|
||||
:flexible (get s "flexible")
|
||||
:days (~page/days-pills-from-data :days (get s "days"))
|
||||
:time-str (get s "time-str")
|
||||
:cost-str (get s "cost-str") :action-btn action-btn
|
||||
:del-url (get s "del-url") :csrf-hdr csrf-hdr))
|
||||
(or slots (list)))))
|
||||
:pre-action pre-action :add-url add-url))
|
||||
(<>
|
||||
(map
|
||||
(lambda
|
||||
(s)
|
||||
(let-match
|
||||
{:slot-name slot-name :time-str time-str :flexible flexible :description description :days days :cost-str cost-str :del-url del-url :slot-href slot-href}
|
||||
s
|
||||
(~page/slots-row
|
||||
:tr-cls tr-cls
|
||||
:slot-href slot-href
|
||||
:pill-cls pill-cls
|
||||
:hx-select hx-select
|
||||
:slot-name slot-name
|
||||
:description description
|
||||
:flexible flexible
|
||||
:days (~page/days-pills-from-data :days days)
|
||||
:time-str time-str
|
||||
:cost-str cost-str
|
||||
:action-btn action-btn
|
||||
:del-url del-url
|
||||
:csrf-hdr csrf-hdr)))
|
||||
(or slots (list)))))
|
||||
:pre-action pre-action
|
||||
:add-url add-url))
|
||||
|
||||
(defcomp ~page/ticket-type-col (&key label value)
|
||||
(div :class "flex flex-col"
|
||||
@@ -203,47 +226,87 @@
|
||||
:onclick hide-js "Cancel"))))
|
||||
|
||||
;; Data-driven buy form — Python passes pre-resolved data, .sx does layout + iteration
|
||||
(defcomp ~page/buy-form (&key entry-id info-sold info-remaining info-basket
|
||||
ticket-types user-ticket-counts-by-type
|
||||
user-ticket-count price-str adjust-url csrf state
|
||||
my-tickets-href)
|
||||
(if (!= state "confirmed")
|
||||
(defcomp
|
||||
~page/buy-form
|
||||
(&key
|
||||
entry-id
|
||||
info-sold
|
||||
info-remaining
|
||||
info-basket
|
||||
ticket-types
|
||||
user-ticket-counts-by-type
|
||||
user-ticket-count
|
||||
price-str
|
||||
adjust-url
|
||||
csrf
|
||||
state
|
||||
my-tickets-href)
|
||||
(if
|
||||
(!= state "confirmed")
|
||||
(~page/buy-not-confirmed :entry-id (str entry-id))
|
||||
(let ((eid-s (str entry-id))
|
||||
(target (str "#ticket-buy-" entry-id)))
|
||||
(div :id (str "ticket-buy-" entry-id) :class "rounded-xl border border-stone-200 bg-white p-4"
|
||||
(h3 :class "text-sm font-semibold text-stone-700 mb-3"
|
||||
(i :class "fa fa-ticket mr-1" :aria-hidden "true") "Tickets")
|
||||
;; Info bar
|
||||
(when (or info-sold info-remaining info-basket)
|
||||
(div :class "flex items-center gap-3 mb-3 text-xs text-stone-500"
|
||||
(let
|
||||
((eid-s (str entry-id)) (target (str "#ticket-buy-" entry-id)))
|
||||
(div
|
||||
:id (str "ticket-buy-" entry-id)
|
||||
:class "rounded-xl border border-stone-200 bg-white p-4"
|
||||
(h3
|
||||
:class "text-sm font-semibold text-stone-700 mb-3"
|
||||
(i :class "fa fa-ticket mr-1" :aria-hidden "true")
|
||||
"Tickets")
|
||||
(when
|
||||
(or info-sold info-remaining info-basket)
|
||||
(div
|
||||
:class "flex items-center gap-3 mb-3 text-xs text-stone-500"
|
||||
(when info-sold (span (str info-sold " sold")))
|
||||
(when info-remaining (span (str info-remaining " remaining")))
|
||||
(when info-basket
|
||||
(span :class "text-emerald-600 font-medium"
|
||||
(i :class "fa fa-shopping-cart text-[0.6rem]" :aria-hidden "true")
|
||||
(when
|
||||
info-basket
|
||||
(span
|
||||
:class "text-emerald-600 font-medium"
|
||||
(i
|
||||
:class "fa fa-shopping-cart text-[0.6rem]"
|
||||
:aria-hidden "true")
|
||||
(str " " info-basket " in basket")))))
|
||||
;; Body — multi-type or default
|
||||
(if (and ticket-types (not (empty? ticket-types)))
|
||||
(div :class "space-y-2"
|
||||
(map (fn (tt)
|
||||
(let ((tt-count (if user-ticket-counts-by-type
|
||||
(get user-ticket-counts-by-type (str (get tt "id")) 0)
|
||||
0))
|
||||
(tt-id (get tt "id")))
|
||||
(div :class "flex items-center justify-between p-3 rounded-lg bg-stone-50 border border-stone-100"
|
||||
(div (div :class "font-medium text-sm" (get tt "name"))
|
||||
(div :class "text-xs text-stone-500" (get tt "cost_str")))
|
||||
(~page/adjust-inline :csrf csrf :adjust-url adjust-url :target target
|
||||
:entry-id eid-s :count tt-count :ticket-type-id tt-id
|
||||
:my-tickets-href my-tickets-href))))
|
||||
(if
|
||||
(and ticket-types (not (empty? ticket-types)))
|
||||
(div
|
||||
:class "space-y-2"
|
||||
(map
|
||||
(fn
|
||||
(tt)
|
||||
(let-match
|
||||
{:cost_str cost-str :id tt-id :name tt-name}
|
||||
tt
|
||||
(let
|
||||
((tt-count (if user-ticket-counts-by-type (get user-ticket-counts-by-type (str tt-id) 0) 0)))
|
||||
(div
|
||||
:class "flex items-center justify-between p-3 rounded-lg bg-stone-50 border border-stone-100"
|
||||
(div
|
||||
(div :class "font-medium text-sm" tt-name)
|
||||
(div :class "text-xs text-stone-500" cost-str))
|
||||
(~page/adjust-inline
|
||||
:csrf csrf
|
||||
:adjust-url adjust-url
|
||||
:target target
|
||||
:entry-id eid-s
|
||||
:count tt-count
|
||||
:ticket-type-id tt-id
|
||||
:my-tickets-href my-tickets-href)))))
|
||||
ticket-types))
|
||||
(<> (div :class "flex items-center justify-between mb-4"
|
||||
(div (span :class "font-medium text-green-600" price-str)
|
||||
(span :class "text-sm text-stone-500 ml-2" "per ticket")))
|
||||
(~page/adjust-inline :csrf csrf :adjust-url adjust-url :target target
|
||||
:entry-id eid-s :count (if user-ticket-count user-ticket-count 0)
|
||||
:ticket-type-id nil :my-tickets-href my-tickets-href)))))))
|
||||
(<>
|
||||
(div
|
||||
:class "flex items-center justify-between mb-4"
|
||||
(div
|
||||
(span :class "font-medium text-green-600" price-str)
|
||||
(span :class "text-sm text-stone-500 ml-2" "per ticket")))
|
||||
(~page/adjust-inline
|
||||
:csrf csrf
|
||||
:adjust-url adjust-url
|
||||
:target target
|
||||
:entry-id eid-s
|
||||
:count (if user-ticket-count user-ticket-count 0)
|
||||
:ticket-type-id nil
|
||||
:my-tickets-href my-tickets-href)))))))
|
||||
|
||||
;; Inline +/- controls (used by both default and per-type)
|
||||
(defcomp ~page/adjust-inline (&key csrf adjust-url target entry-id count ticket-type-id my-tickets-href)
|
||||
@@ -285,26 +348,53 @@
|
||||
"Tickets available once this event is confirmed."))
|
||||
|
||||
|
||||
(defcomp ~page/buy-result (&key entry-id tickets remaining my-tickets-href)
|
||||
(let ((count (len tickets))
|
||||
(suffix (if (= count 1) "" "s")))
|
||||
(div :id (str "ticket-buy-" entry-id) :class "rounded-xl border border-emerald-200 bg-emerald-50 p-4"
|
||||
(div :class "flex items-center gap-2 mb-3"
|
||||
(defcomp
|
||||
~page/buy-result
|
||||
(&key entry-id tickets remaining my-tickets-href)
|
||||
(let
|
||||
((count (len tickets)) (suffix (if (= count 1) "" "s")))
|
||||
(div
|
||||
:id (str "ticket-buy-" entry-id)
|
||||
:class "rounded-xl border border-emerald-200 bg-emerald-50 p-4"
|
||||
(div
|
||||
:class "flex items-center gap-2 mb-3"
|
||||
(i :class "fa fa-check-circle text-emerald-600" :aria-hidden "true")
|
||||
(span :class "font-semibold text-emerald-800" (str count " ticket" suffix " reserved")))
|
||||
(div :class "space-y-2 mb-4"
|
||||
(map (fn (t)
|
||||
(a :href (get t "href") :class "flex items-center justify-between p-2 rounded-lg bg-white border border-emerald-100 hover:border-emerald-300 transition text-sm"
|
||||
(div :class "flex items-center gap-2"
|
||||
(i :class "fa fa-ticket text-emerald-500" :aria-hidden "true")
|
||||
(span :class "font-mono text-xs text-stone-500" (get t "code_short")))
|
||||
(span :class "text-xs text-emerald-600 font-medium" "View ticket")))
|
||||
(span
|
||||
:class "font-semibold text-emerald-800"
|
||||
(str count " ticket" suffix " reserved")))
|
||||
(div
|
||||
:class "space-y-2 mb-4"
|
||||
(map
|
||||
(fn
|
||||
(t)
|
||||
(let-match
|
||||
{:href href :code_short code-short}
|
||||
t
|
||||
(a
|
||||
:href href
|
||||
:class "flex items-center justify-between p-2 rounded-lg bg-white border border-emerald-100 hover:border-emerald-300 transition text-sm"
|
||||
(div
|
||||
:class "flex items-center gap-2"
|
||||
(i
|
||||
:class "fa fa-ticket text-emerald-500"
|
||||
:aria-hidden "true")
|
||||
(span :class "font-mono text-xs text-stone-500" code-short))
|
||||
(span
|
||||
:class "text-xs text-emerald-600 font-medium"
|
||||
"View ticket"))))
|
||||
tickets))
|
||||
(when (not (nil? remaining))
|
||||
(let ((r-suffix (if (= remaining 1) "" "s")))
|
||||
(p :class "text-xs text-stone-500" (str remaining " ticket" r-suffix " remaining"))))
|
||||
(div :class "mt-3 flex gap-2"
|
||||
(a :href my-tickets-href :class "text-sm text-emerald-700 hover:text-emerald-900 underline"
|
||||
(when
|
||||
(not (nil? remaining))
|
||||
(let
|
||||
((r-suffix (if (= remaining 1) "" "s")))
|
||||
(p
|
||||
:class "text-xs text-stone-500"
|
||||
(str remaining " ticket" r-suffix " remaining"))))
|
||||
(div
|
||||
:class "mt-3 flex gap-2"
|
||||
(a
|
||||
:href my-tickets-href
|
||||
:class "text-sm text-emerald-700 hover:text-emerald-900 underline"
|
||||
"View all my tickets")))))
|
||||
|
||||
;; Single response wrappers for POST routes (include OOB cart icon)
|
||||
@@ -477,27 +567,46 @@
|
||||
(~page/post-img-placeholder)))
|
||||
|
||||
;; Entry posts nav OOB from data
|
||||
(defcomp ~page/entry-posts-nav-oob-from-data (&key nav-btn posts)
|
||||
(if (empty? (or posts (list)))
|
||||
(defcomp
|
||||
~page/entry-posts-nav-oob-from-data
|
||||
(&key nav-btn posts)
|
||||
(if
|
||||
(empty? (or posts (list)))
|
||||
(~page/entry-posts-nav-oob-empty)
|
||||
(~page/entry-posts-nav-oob
|
||||
:items (<> (map (lambda (p)
|
||||
(~page/entry-nav-post
|
||||
:href (get p "href") :nav-btn nav-btn
|
||||
:img (~page/post-img-from-data :src (get p "img") :alt (get p "title"))
|
||||
:title (get p "title")))
|
||||
posts)))))
|
||||
:items (<>
|
||||
(map
|
||||
(lambda
|
||||
(p)
|
||||
(let-match
|
||||
{:href href :title title :img img}
|
||||
p
|
||||
(~page/entry-nav-post
|
||||
:href href
|
||||
:nav-btn nav-btn
|
||||
:img (~page/post-img-from-data :src img :alt title)
|
||||
:title title)))
|
||||
posts)))))
|
||||
|
||||
;; Entry posts nav (non-OOB) from data — for desktop nav embedding
|
||||
(defcomp ~page/entry-posts-nav-inner-from-data (&key posts)
|
||||
(when (not (empty? (or posts (list))))
|
||||
(defcomp
|
||||
~page/entry-posts-nav-inner-from-data
|
||||
(&key posts)
|
||||
(when
|
||||
(not (empty? (or posts (list))))
|
||||
(~page/entry-posts-nav-oob
|
||||
:items (<> (map (lambda (p)
|
||||
(~page/entry-nav-post-link
|
||||
:href (get p "href")
|
||||
:img (~page/post-img-from-data :src (get p "img") :alt (get p "title"))
|
||||
:title (get p "title")))
|
||||
posts)))))
|
||||
:items (<>
|
||||
(map
|
||||
(lambda
|
||||
(p)
|
||||
(let-match
|
||||
{:href href :title title :img img}
|
||||
p
|
||||
(~page/entry-nav-post-link
|
||||
:href href
|
||||
:img (~page/post-img-from-data :src img :alt title)
|
||||
:title title)))
|
||||
posts)))))
|
||||
|
||||
;; Post nav entries+calendars OOB from data
|
||||
(defcomp ~page/post-nav-wrapper-from-data (&key nav-btn entries calendars hyperscript)
|
||||
@@ -602,14 +711,23 @@
|
||||
(~shared:layout/nav-link :href admin-href :icon "fa fa-cog"))))
|
||||
|
||||
;; Post search results from data
|
||||
(defcomp ~page/post-search-results-from-data (&key items page next-url has-more)
|
||||
(defcomp
|
||||
~page/post-search-results-from-data
|
||||
(&key items page next-url has-more)
|
||||
(<>
|
||||
(map (lambda (item)
|
||||
(~forms/post-search-item
|
||||
:post-url (get item "post-url") :entry-id (get item "entry-id")
|
||||
:csrf (get item "csrf") :post-id (get item "post-id")
|
||||
:img (~page/post-img-from-data :src (get item "img") :alt (get item "title"))
|
||||
:title (get item "title")))
|
||||
(map
|
||||
(lambda
|
||||
(item)
|
||||
(let-match
|
||||
{:csrf csrf :entry-id entry-id :post-url post-url :title title :img img :post-id post-id}
|
||||
item
|
||||
(~forms/post-search-item
|
||||
:post-url post-url
|
||||
:entry-id entry-id
|
||||
:csrf csrf
|
||||
:post-id post-id
|
||||
:img (~page/post-img-from-data :src img :alt title)
|
||||
:title title)))
|
||||
(or items (list)))
|
||||
(cond
|
||||
(has-more (~forms/post-search-sentinel :page page :next-url next-url))
|
||||
@@ -617,16 +735,26 @@
|
||||
(true ""))))
|
||||
|
||||
;; Entry options from data — state-driven button composition
|
||||
(defcomp ~page/entry-options-from-data (&key entry-id state buttons)
|
||||
(defcomp
|
||||
~page/entry-options-from-data
|
||||
(&key entry-id state buttons)
|
||||
(~admin/entry-options
|
||||
:entry-id entry-id
|
||||
:buttons (<> (map (lambda (b)
|
||||
(~admin/entry-option-button
|
||||
:url (get b "url") :target (str "#calendar_entry_options_" entry-id)
|
||||
:csrf (get b "csrf") :btn-type (get b "btn-type")
|
||||
:action-btn (get b "action-btn")
|
||||
:confirm-title (get b "confirm-title")
|
||||
:confirm-text (get b "confirm-text")
|
||||
:label (get b "label")
|
||||
:is-btn (get b "is-btn")))
|
||||
(or buttons (list))))))
|
||||
:buttons (<>
|
||||
(map
|
||||
(lambda
|
||||
(b)
|
||||
(let-match
|
||||
{:csrf csrf :confirm-title confirm-title :url url :btn-type btn-type :action-btn action-btn :confirm-text confirm-text :label label :is-btn is-btn}
|
||||
b
|
||||
(~admin/entry-option-button
|
||||
:url url
|
||||
:target (str "#calendar_entry_options_" entry-id)
|
||||
:csrf csrf
|
||||
:btn-type btn-type
|
||||
:action-btn action-btn
|
||||
:confirm-title confirm-title
|
||||
:confirm-text confirm-text
|
||||
:label label
|
||||
:is-btn is-btn)))
|
||||
(or buttons (list))))))
|
||||
|
||||
Reference in New Issue
Block a user