# Conflicts: # blog/sx/sx_components.py # federation/sx/profile.sx # federation/sx/sx_components.py # orders/sx/sx_components.py
200 lines
9.4 KiB
Plaintext
200 lines
9.4 KiB
Plaintext
;; Search and actor card components
|
|
|
|
;; Aliases — delegate to shared ~avatar
|
|
(defcomp ~federation-actor-avatar-img (&key src cls)
|
|
(~avatar :src src :cls cls))
|
|
|
|
(defcomp ~federation-actor-avatar-placeholder (&key cls initial)
|
|
(~avatar :cls cls :initial initial))
|
|
|
|
(defcomp ~federation-actor-name-link (&key href name)
|
|
(a :href href :class "font-semibold text-stone-900 hover:underline" name))
|
|
|
|
(defcomp ~federation-actor-name-link-external (&key href name)
|
|
(a :href href :target "_blank" :rel "noopener"
|
|
:class "font-semibold text-stone-900 hover:underline" name))
|
|
|
|
(defcomp ~federation-actor-summary (&key summary)
|
|
(div :class "text-sm text-stone-600 mt-1 truncate" (~rich-text :html summary)))
|
|
|
|
(defcomp ~federation-unfollow-button (&key action csrf actor-url)
|
|
(div :class "flex-shrink-0"
|
|
(form :method "post" :action action :sx-post action :sx-target "closest article" :sx-swap "outerHTML"
|
|
(input :type "hidden" :name "csrf_token" :value csrf)
|
|
(input :type "hidden" :name "actor_url" :value actor-url)
|
|
(button :type "submit" :class "text-sm border border-stone-300 rounded px-3 py-1 hover:bg-stone-100" "Unfollow"))))
|
|
|
|
(defcomp ~federation-follow-button (&key action csrf actor-url label)
|
|
(div :class "flex-shrink-0"
|
|
(form :method "post" :action action :sx-post action :sx-target "closest article" :sx-swap "outerHTML"
|
|
(input :type "hidden" :name "csrf_token" :value csrf)
|
|
(input :type "hidden" :name "actor_url" :value actor-url)
|
|
(button :type "submit" :class "text-sm bg-stone-800 text-white rounded px-3 py-1 hover:bg-stone-700" label))))
|
|
|
|
(defcomp ~federation-actor-card (&key cls id avatar name username domain summary button)
|
|
(article :class cls :id id
|
|
avatar
|
|
(div :class "flex-1 min-w-0"
|
|
name
|
|
(div :class "text-sm text-stone-500" "@" username "@" domain)
|
|
summary)
|
|
button))
|
|
|
|
;; Data-driven actor card (replaces Python _actor_card_sx loop)
|
|
(defcomp ~federation-actor-card-from-data (&key d has-actor csrf follow-url unfollow-url list-type)
|
|
(let* ((icon-url (get d "icon_url"))
|
|
(display-name (get d "display_name"))
|
|
(username (get d "username"))
|
|
(domain (get d "domain"))
|
|
(actor-url (get d "actor_url"))
|
|
(safe-id (get d "safe_id"))
|
|
(initial (or (get d "initial") "?"))
|
|
(avatar (~avatar
|
|
:src icon-url
|
|
:cls (if icon-url "w-12 h-12 rounded-full"
|
|
"w-12 h-12 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold")
|
|
:initial (when (not icon-url) initial)))
|
|
(name-sx (if (get d "external_link")
|
|
(~federation-actor-name-link-external :href (get d "name_href") :name display-name)
|
|
(~federation-actor-name-link :href (get d "name_href") :name display-name)))
|
|
(summary-sx (when (get d "summary")
|
|
(~federation-actor-summary :summary (get d "summary"))))
|
|
(is-followed (get d "is_followed"))
|
|
(button (when has-actor
|
|
(if (or (= list-type "following") is-followed)
|
|
(~federation-unfollow-button :action unfollow-url :csrf csrf :actor-url actor-url)
|
|
(~federation-follow-button :action follow-url :csrf csrf :actor-url actor-url
|
|
:label (if (= list-type "followers") "Follow Back" "Follow"))))))
|
|
(~federation-actor-card
|
|
:cls "bg-white rounded-lg shadow-sm border border-stone-200 p-4 mb-3 flex items-center gap-4"
|
|
:id (str "actor-" safe-id)
|
|
:avatar avatar :name name-sx :username username :domain domain
|
|
:summary summary-sx :button button)))
|
|
|
|
;; Data-driven actor list (replaces Python _search_results_sx / _actor_list_items_sx loops)
|
|
(defcomp ~federation-actor-list-from-data (&key actors next-url has-actor csrf
|
|
follow-url unfollow-url list-type)
|
|
(<>
|
|
(map (lambda (d)
|
|
(~federation-actor-card-from-data :d d :has-actor has-actor :csrf csrf
|
|
:follow-url follow-url :unfollow-url unfollow-url :list-type list-type))
|
|
(or actors (list)))
|
|
(when next-url (~federation-scroll-sentinel :url next-url))))
|
|
|
|
(defcomp ~federation-search-info (&key cls text)
|
|
(p :class cls text))
|
|
|
|
(defcomp ~federation-search-page (&key search-url search-page-url query info results)
|
|
(h1 :class "text-2xl font-bold mb-6" "Search")
|
|
(form :method "get" :action search-url :class "mb-6"
|
|
:sx-get search-page-url :sx-target "#search-results" :sx-push-url search-url
|
|
(div :class "flex gap-2"
|
|
(input :type "text" :name "q" :value query
|
|
:class "flex-1 border border-stone-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-stone-500"
|
|
:placeholder "Search users or @user@instance.tld")
|
|
(button :type "submit" :class "bg-stone-800 text-white px-6 py-2 rounded hover:bg-stone-700" "Search")))
|
|
info
|
|
(div :id "search-results" results))
|
|
|
|
;; Following / Followers list page
|
|
(defcomp ~federation-actor-list-page (&key title count-str items)
|
|
(h1 :class "text-2xl font-bold mb-6" title " "
|
|
(span :class "text-stone-400 font-normal" count-str))
|
|
(div :id "actor-list" items))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Assembled actor card — replaces Python _actor_card_sx
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~federation-actor-card-from-data (&key a actor followed-urls list-type)
|
|
(let* ((display-name (or (get a "display_name") (get a "preferred_username") ""))
|
|
(username (or (get a "preferred_username") ""))
|
|
(domain (or (get a "domain") ""))
|
|
(icon-url (get a "icon_url"))
|
|
(actor-url (or (get a "actor_url") ""))
|
|
(summary (get a "summary"))
|
|
(aid (get a "id"))
|
|
(safe-id (replace (replace actor-url "/" "_") ":" "_"))
|
|
(initial (if (and (not icon-url) (or display-name username))
|
|
(upper (slice (or display-name username) 0 1)) "?"))
|
|
(csrf (csrf-token))
|
|
(is-followed (contains? (or followed-urls (list)) actor-url)))
|
|
(~federation-actor-card
|
|
:cls "bg-white rounded-lg shadow-sm border border-stone-200 p-4 mb-3 flex items-center gap-4"
|
|
:id (str "actor-" safe-id)
|
|
:avatar (~avatar
|
|
:src icon-url
|
|
:cls (if icon-url "w-12 h-12 rounded-full"
|
|
"w-12 h-12 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold")
|
|
:initial (when (not icon-url) initial))
|
|
:name (if (and (or (= list-type "following") (= list-type "search")) aid)
|
|
(~federation-actor-name-link
|
|
:href (url-for "social.defpage_actor_timeline" :id aid)
|
|
:name (escape display-name))
|
|
(~federation-actor-name-link-external
|
|
:href (str "https://" domain "/@" username)
|
|
:name (escape display-name)))
|
|
:username (escape username)
|
|
:domain (escape domain)
|
|
:summary (when summary (~federation-actor-summary :summary summary))
|
|
:button (when actor
|
|
(if (or (= list-type "following") is-followed)
|
|
(~federation-unfollow-button
|
|
:action (url-for "social.unfollow") :csrf csrf :actor-url actor-url)
|
|
(~federation-follow-button
|
|
:action (url-for "social.follow") :csrf csrf :actor-url actor-url
|
|
:label (if (= list-type "followers") "Follow Back" "Follow")))))))
|
|
|
|
;; Assembled search content — replaces Python _search_content_sx
|
|
(defcomp ~federation-search-content (&key query actors total followed-urls actor)
|
|
(~federation-search-page
|
|
:search-url (url-for "social.defpage_search")
|
|
:search-page-url (url-for "social.search_page")
|
|
:query (escape (or query ""))
|
|
:info (cond
|
|
((and query (> total 0))
|
|
(~federation-search-info
|
|
:cls "text-sm text-stone-500 mb-4"
|
|
:text (str total " result" (pluralize total) " for " (escape query))))
|
|
(query
|
|
(~federation-search-info
|
|
:cls "text-stone-500 mb-4"
|
|
:text (str "No results found for " (escape query))))
|
|
(true nil))
|
|
:results (when (not (empty? actors))
|
|
(<>
|
|
(map (lambda (a)
|
|
(~federation-actor-card-from-data
|
|
:a a :actor actor :followed-urls followed-urls :list-type "search"))
|
|
actors)
|
|
(when (>= (len actors) 20)
|
|
(~federation-scroll-sentinel
|
|
:url (url-for "social.search_page" :q query :page 2)))))))
|
|
|
|
;; Assembled following/followers content — replaces Python _following_content_sx etc.
|
|
(defcomp ~federation-following-content (&key actors total actor)
|
|
(~federation-actor-list-page
|
|
:title "Following" :count-str (str "(" total ")")
|
|
:items (when (not (empty? actors))
|
|
(<>
|
|
(map (lambda (a)
|
|
(~federation-actor-card-from-data
|
|
:a a :actor actor :followed-urls (list) :list-type "following"))
|
|
actors)
|
|
(when (>= (len actors) 20)
|
|
(~federation-scroll-sentinel
|
|
:url (url-for "social.following_list_page" :page 2)))))))
|
|
|
|
(defcomp ~federation-followers-content (&key actors total followed-urls actor)
|
|
(~federation-actor-list-page
|
|
:title "Followers" :count-str (str "(" total ")")
|
|
:items (when (not (empty? actors))
|
|
(<>
|
|
(map (lambda (a)
|
|
(~federation-actor-card-from-data
|
|
:a a :actor actor :followed-urls followed-urls :list-type "followers"))
|
|
actors)
|
|
(when (>= (len actors) 20)
|
|
(~federation-scroll-sentinel
|
|
:url (url-for "social.followers_list_page" :page 2)))))))
|