Move SX construction from Python to .sx defcomps (phases 0-4)
Eliminate Python s-expression string building across account, orders, federation, and cart services. Visual rendering logic now lives entirely in .sx defcomp components; Python files contain only data serialization, header/layout wiring, and thin wrappers that call defcomps. Phase 0: Shared DRY extraction — auth/orders header defcomps, format-decimal/ pluralize/escape/route-prefix primitives. Phase 1: Account — dashboard, newsletters, login/device/check-email content. Phase 2: Orders — order list, detail, filter, checkout return assembled defcomps. Phase 3: Federation — social nav, post cards, timeline, search, actors, notifications, compose, profile assembled defcomps. Phase 4: Cart — overview, page cart items/calendar/tickets/summary, admin, payments assembled defcomps; orders rendering reuses Phase 2 shared defcomps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -110,3 +110,129 @@
|
||||
(option :value "unlisted" "Unlisted")
|
||||
(option :value "followers" "Followers only"))
|
||||
(button :type "submit" :class "bg-stone-800 text-white px-6 py-2 rounded hover:bg-stone-700" "Publish"))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Assembled social nav — replaces Python _social_nav_sx
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~federation-social-nav (&key actor)
|
||||
(if (not actor)
|
||||
(~federation-nav-choose-username :url (url-for "identity.choose_username_form"))
|
||||
(let* ((rp (request-path))
|
||||
(links (list
|
||||
(dict :endpoint "social.defpage_home_timeline" :label "Timeline")
|
||||
(dict :endpoint "social.defpage_public_timeline" :label "Public")
|
||||
(dict :endpoint "social.defpage_compose_form" :label "Compose")
|
||||
(dict :endpoint "social.defpage_following_list" :label "Following")
|
||||
(dict :endpoint "social.defpage_followers_list" :label "Followers")
|
||||
(dict :endpoint "social.defpage_search" :label "Search"))))
|
||||
(~federation-nav-bar
|
||||
:items (<>
|
||||
(map (lambda (lnk)
|
||||
(let* ((href (url-for (get lnk "endpoint")))
|
||||
(bold (if (= rp href) " font-bold" "")))
|
||||
(a :href href
|
||||
:class (str "px-2 py-1 rounded hover:bg-stone-200" bold)
|
||||
(get lnk "label"))))
|
||||
links)
|
||||
(let* ((notif-url (url-for "social.defpage_notifications"))
|
||||
(notif-bold (if (= rp notif-url) " font-bold" "")))
|
||||
(~federation-nav-notification-link
|
||||
:href notif-url
|
||||
:cls (str "px-2 py-1 rounded hover:bg-stone-200 relative" notif-bold)
|
||||
:count-url (url-for "social.notification_count")))
|
||||
(a :href (url-for "activitypub.actor_profile" :username (get actor "preferred_username"))
|
||||
:class "px-2 py-1 rounded hover:bg-stone-200"
|
||||
(str "@" (get actor "preferred_username"))))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Assembled post card — replaces Python _post_card_sx
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~federation-post-card-from-data (&key item actor)
|
||||
(let* ((boosted-by (get item "boosted_by"))
|
||||
(actor-icon (get item "actor_icon"))
|
||||
(actor-name (or (get item "actor_name") "?"))
|
||||
(actor-username (or (get item "actor_username") ""))
|
||||
(actor-domain (or (get item "actor_domain") ""))
|
||||
(content (or (get item "content") ""))
|
||||
(summary (get item "summary"))
|
||||
(published (or (get item "published") ""))
|
||||
(url (get item "url"))
|
||||
(post-type (or (get item "post_type") ""))
|
||||
(oid (or (get item "object_id") ""))
|
||||
(safe-id (replace (replace oid "/" "_") ":" "_"))
|
||||
(initial (if (and (not actor-icon) actor-name)
|
||||
(upper (slice actor-name 0 1)) "?")))
|
||||
(~federation-post-card
|
||||
:boost (when boosted-by (~federation-boost-label :name (escape boosted-by)))
|
||||
:avatar (~avatar
|
||||
:src actor-icon
|
||||
:cls (if actor-icon "w-10 h-10 rounded-full"
|
||||
"w-10 h-10 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-sm")
|
||||
:initial (when (not actor-icon) initial))
|
||||
:actor-name (escape actor-name)
|
||||
:actor-username (escape actor-username)
|
||||
:domain (if actor-domain (str "@" (escape actor-domain)) "")
|
||||
:time published
|
||||
:content (if summary
|
||||
(~federation-content :content content :summary (escape summary))
|
||||
(~federation-content :content content))
|
||||
:original (when (and url (= post-type "remote"))
|
||||
(~federation-original-link :url url))
|
||||
:interactions (when actor
|
||||
(let* ((csrf (csrf-token))
|
||||
(liked (get item "liked_by_me"))
|
||||
(boosted-me (get item "boosted_by_me"))
|
||||
(lcount (or (get item "like_count") 0))
|
||||
(bcount (or (get item "boost_count") 0))
|
||||
(ainbox (or (get item "author_inbox") ""))
|
||||
(target (str "#interactions-" safe-id)))
|
||||
(div :id (str "interactions-" safe-id)
|
||||
(~federation-interaction-buttons
|
||||
:like (~federation-like-form
|
||||
:action (url-for (if liked "social.unlike" "social.like"))
|
||||
:target target :oid oid :ainbox ainbox :csrf csrf
|
||||
:cls (str "flex items-center gap-1 " (if liked "text-red-500 hover:text-red-600" "hover:text-red-500"))
|
||||
:icon (if liked "\u2665" "\u2661") :count (str lcount))
|
||||
:boost (~federation-boost-form
|
||||
:action (url-for (if boosted-me "social.unboost" "social.boost"))
|
||||
:target target :oid oid :ainbox ainbox :csrf csrf
|
||||
:cls (str "flex items-center gap-1 " (if boosted-me "text-green-600 hover:text-green-700" "hover:text-green-600"))
|
||||
:count (str bcount))
|
||||
:reply (when oid
|
||||
(~federation-reply-link
|
||||
:url (url-for "social.defpage_compose_form" :reply-to oid))))))))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Assembled timeline items — replaces Python _timeline_items_sx
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~federation-timeline-items (&key items timeline-type actor next-url)
|
||||
(<>
|
||||
(map (lambda (item)
|
||||
(~federation-post-card-from-data :item item :actor actor))
|
||||
items)
|
||||
(when next-url
|
||||
(~federation-scroll-sentinel :url next-url))))
|
||||
|
||||
;; Assembled timeline content — replaces Python _timeline_content_sx
|
||||
(defcomp ~federation-timeline-content (&key items timeline-type actor)
|
||||
(let* ((label (if (= timeline-type "home") "Home" "Public")))
|
||||
(~federation-timeline-page
|
||||
:label label
|
||||
:compose (when actor
|
||||
(~federation-compose-button :url (url-for "social.defpage_compose_form")))
|
||||
:timeline (~federation-timeline-items
|
||||
:items items :timeline-type timeline-type :actor actor
|
||||
:next-url (when (not (empty? items))
|
||||
(url-for (str "social." timeline-type "_timeline_page")
|
||||
:before (get (last items) "before_cursor")))))))
|
||||
|
||||
;; Assembled compose content — replaces Python _compose_content_sx
|
||||
(defcomp ~federation-compose-content (&key reply-to)
|
||||
(~federation-compose-form
|
||||
:action (url-for "social.compose_submit")
|
||||
:csrf (csrf-token)
|
||||
:reply (when reply-to
|
||||
(~federation-compose-reply :reply-to (escape reply-to)))))
|
||||
|
||||
Reference in New Issue
Block a user