Rename all 1,169 components to path-based names with namespace support

Component names now reflect filesystem location using / as path separator
and : as namespace separator for shared components:
  ~sx-header → ~layouts/header
  ~layout-app-body → ~shared:layout/app-body
  ~blog-admin-dashboard → ~admin/dashboard

209 files, 4,941 replacements across all services.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 22:00:12 +00:00
parent de80d921e9
commit b0920a1121
209 changed files with 4620 additions and 4620 deletions

View File

@@ -1,7 +1,7 @@
;; Auth components (choose username — federation-specific)
;; Login and check-email components are shared: see shared/sx/templates/auth.sx
(defcomp ~federation-choose-username (&key (domain :as string) error (csrf :as string) (username :as string) (check-url :as string))
(defcomp ~auth/choose-username (&key (domain :as string) error (csrf :as string) (username :as string) (check-url :as string))
(div :class "py-8 max-w-md mx-auto"
(h1 :class "text-2xl font-bold mb-2" "Choose your username")
(p :class "text-stone-600 mb-6" "This will be your identity on the fediverse: "

View File

@@ -12,7 +12,7 @@
(let ((actor (service "federation" "get-actor-by-username" :username u)))
(<> (str "<!-- fragment:" u " -->")
(when (not (nil? actor))
(~link-card
(~shared:fragments/link-card
:link (app-url "federation"
(str "/users/" (get actor "preferred_username")))
:title (or (get actor "display_name")
@@ -28,7 +28,7 @@
(let ((actor (service "federation" "get-actor-by-username"
:username lookup)))
(when (not (nil? actor))
(~link-card
(~shared:fragments/link-card
:link (app-url "federation"
(str "/users/" (get actor "preferred_username")))
:title (or (get actor "display_name")

View File

@@ -2,16 +2,16 @@
;; Registered via register_sx_layout("social", ...) in __init__.py.
;; Full page: root header + social header in header-child
(defcomp ~social-layout-full ()
(defcomp ~layouts/social-layout-full ()
(<> (~root-header-auto)
(~header-child-sx
:inner (~federation-social-header
:nav (~federation-social-nav :actor (federation-actor-ctx))))))
(~shared:layout/header-child-sx
:inner (~social/header
:nav (~social/nav :actor (federation-actor-ctx))))))
;; OOB (HTMX): social header oob + root header oob
(defcomp ~social-layout-oob ()
(<> (~oob-header-sx
(defcomp ~layouts/social-layout-oob ()
(<> (~shared:layout/oob-header-sx
:parent-id "root-header-child"
:row (~federation-social-header
:nav (~federation-social-nav :actor (federation-actor-ctx))))
:row (~social/header
:nav (~social/nav :actor (federation-actor-ctx))))
(~root-header-auto true)))

View File

@@ -1,9 +1,9 @@
;; Notification components
(defcomp ~federation-notification-preview (&key (preview :as string))
(defcomp ~notifications/preview (&key (preview :as string))
(div :class "text-sm text-stone-500 mt-1 truncate" preview))
(defcomp ~federation-notification-card (&key (cls :as string) avatar (from-name :as string) (from-username :as string) (from-domain :as string) (action-text :as string) preview (time :as string))
(defcomp ~notifications/card (&key (cls :as string) avatar (from-name :as string) (from-username :as string) (from-domain :as string) (action-text :as string) preview (time :as string))
(div :class cls
(div :class "flex items-start gap-3"
avatar
@@ -15,14 +15,14 @@
preview
(div :class "text-xs text-stone-400 mt-1" time)))))
(defcomp ~federation-notifications-list (&key (items :as list))
(defcomp ~notifications/list (&key (items :as list))
(div :class "space-y-2" items))
(defcomp ~federation-notifications-page (&key notifs)
(defcomp ~notifications/page (&key notifs)
(h1 :class "text-2xl font-bold mb-6" "Notifications") notifs)
;; Assembled notification card — replaces Python _notification_sx
(defcomp ~federation-notification-from-data (&key (notif :as dict))
(defcomp ~notifications/from-data (&key (notif :as dict))
(let* ((from-name (or (get notif "from_actor_name") "?"))
(from-username (or (get notif "from_actor_username") ""))
(from-domain (or (get notif "from_actor_domain") ""))
@@ -44,9 +44,9 @@
((= ntype "mention") "mentioned you")
((= ntype "reply") "replied to your post")
(true ""))))
(~federation-notification-card
(~notifications/card
:cls (str "bg-white rounded-lg shadow-sm border border-stone-200 p-4" border)
:avatar (~avatar
:avatar (~shared:misc/avatar
:src from-icon
:cls (if from-icon "w-8 h-8 rounded-full"
"w-8 h-8 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-xs")
@@ -55,15 +55,15 @@
:from-username (escape from-username)
:from-domain (if from-domain (str "@" (escape from-domain)) "")
:action-text action-text
:preview (when preview (~federation-notification-preview :preview (escape preview)))
:preview (when preview (~notifications/preview :preview (escape preview)))
:time created)))
;; Assembled notifications content — replaces Python _notifications_content_sx
(defcomp ~federation-notifications-content (&key (notifications :as list))
(~federation-notifications-page
(defcomp ~notifications/content (&key (notifications :as list))
(~notifications/page
:notifs (if (empty? notifications)
(~empty-state :message "No notifications yet." :cls "text-stone-500")
(~federation-notifications-list
(~shared:misc/empty-state :message "No notifications yet." :cls "text-stone-500")
(~notifications/list
:items (map (lambda (n)
(~federation-notification-from-data :notif n))
(~notifications/from-data :notif n))
notifications)))))

View File

@@ -1,6 +1,6 @@
;; Profile and actor timeline components
(defcomp ~federation-actor-profile-header (&key avatar (display-name :as string) (username :as string) (domain :as string) summary follow)
(defcomp ~profile/actor-profile-header (&key avatar (display-name :as string) (username :as string) (domain :as string) summary follow)
(div :class "bg-white rounded-lg shadow-sm border border-stone-200 p-6 mb-6"
(div :class "flex items-center gap-4"
avatar
@@ -10,39 +10,39 @@
summary)
follow)))
(defcomp ~federation-actor-timeline-layout (&key header timeline)
(defcomp ~profile/actor-timeline-layout (&key header timeline)
header
(div :id "timeline" timeline))
(defcomp ~federation-follow-form (&key (action :as string) (csrf :as string) (actor-url :as string) (label :as string) (cls :as string))
(defcomp ~profile/follow-form (&key (action :as string) (csrf :as string) (actor-url :as string) (label :as string) (cls :as string))
(div :class "flex-shrink-0"
(form :method "post" :action action
(input :type "hidden" :name "csrf_token" :value csrf)
(input :type "hidden" :name "actor_url" :value actor-url)
(button :type "submit" :class cls label))))
(defcomp ~federation-profile-summary (&key (summary :as string))
(defcomp ~profile/summary (&key (summary :as string))
(div :class "text-sm text-stone-600 mt-2" (~rich-text :html summary)))
;; Public profile page
(defcomp ~federation-activity-obj-type (&key (obj-type :as string))
(defcomp ~profile/activity-obj-type (&key (obj-type :as string))
(span :class "text-sm text-stone-500" obj-type))
(defcomp ~federation-activity-card (&key (activity-type :as string) (published :as string) obj-type)
(defcomp ~profile/activity-card (&key (activity-type :as string) (published :as string) obj-type)
(div :class "bg-white rounded-lg shadow p-4"
(div :class "flex justify-between items-start"
(span :class "font-medium" activity-type)
(span :class "text-sm text-stone-400" published))
obj-type))
(defcomp ~federation-activities-list (&key (items :as list))
(defcomp ~profile/activities-list (&key (items :as list))
(div :class "space-y-4" items))
(defcomp ~federation-activities-empty ()
(defcomp ~profile/activities-empty ()
(p :class "text-stone-500" "No activities yet."))
(defcomp ~federation-profile-page (&key (display-name :as string) (username :as string) (domain :as string) summary (activities-heading :as string) activities)
(defcomp ~profile/page (&key (display-name :as string) (username :as string) (domain :as string) summary (activities-heading :as string) activities)
(div :class "py-8"
(div :class "bg-white rounded-lg shadow p-6 mb-6"
(h1 :class "text-2xl font-bold" display-name)
@@ -51,11 +51,11 @@
(h2 :class "text-xl font-bold mb-4" activities-heading)
activities))
(defcomp ~federation-profile-summary-text (&key (text :as string))
(defcomp ~profile/summary-text (&key (text :as string))
(p :class "mt-2" text))
;; Assembled actor timeline content — replaces Python _actor_timeline_content_sx
(defcomp ~federation-actor-timeline-content (&key (remote-actor :as dict) (items :as list) (is-following :as boolean) actor)
(defcomp ~profile/actor-timeline-content (&key (remote-actor :as dict) (items :as list) (is-following :as boolean) actor)
(let* ((display-name (or (get remote-actor "display_name") (get remote-actor "preferred_username") ""))
(icon-url (get remote-actor "icon_url"))
(summary (get remote-actor "summary"))
@@ -63,9 +63,9 @@
(csrf (csrf-token))
(initial (if (and (not icon-url) display-name)
(upper (slice display-name 0 1)) "?")))
(~federation-actor-timeline-layout
:header (~federation-actor-profile-header
:avatar (~avatar
(~profile/actor-timeline-layout
:header (~profile/actor-profile-header
:avatar (~shared:misc/avatar
:src icon-url
:cls (if icon-url "w-16 h-16 rounded-full"
"w-16 h-16 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-xl")
@@ -73,18 +73,18 @@
:display-name (escape display-name)
:username (escape (or (get remote-actor "preferred_username") ""))
:domain (escape (or (get remote-actor "domain") ""))
:summary (when summary (~federation-profile-summary :summary summary))
:summary (when summary (~profile/summary :summary summary))
:follow (when actor
(if is-following
(~federation-follow-form
(~profile/follow-form
:action (url-for "social.unfollow") :csrf csrf :actor-url actor-url
:label "Unfollow"
:cls "border border-stone-300 rounded px-4 py-2 hover:bg-stone-100")
(~federation-follow-form
(~profile/follow-form
:action (url-for "social.follow") :csrf csrf :actor-url actor-url
:label "Follow"
:cls "bg-stone-800 text-white rounded px-4 py-2 hover:bg-stone-700"))))
:timeline (~federation-timeline-items
:timeline (~social/timeline-items
:items items :timeline-type "actor" :actor actor
:next-url (when (not (empty? items))
(url-for "social.actor_timeline_page"
@@ -92,14 +92,14 @@
:before (get (last items) "before_cursor")))))))
;; Data-driven activities list (replaces Python loop in render_profile_page)
(defcomp ~federation-activities-from-data (&key (activities :as list))
(defcomp ~profile/activities-from-data (&key (activities :as list))
(if (empty? (or activities (list)))
(~federation-activities-empty)
(~federation-activities-list
(~profile/activities-empty)
(~profile/activities-list
:items (<> (map (lambda (a)
(~federation-activity-card
(~profile/activity-card
:activity-type (get a "activity_type")
:published (get a "published")
:obj-type (when (get a "object_type")
(~federation-activity-obj-type :obj-type (get a "object_type")))))
(~profile/activity-obj-type :obj-type (get a "object_type")))))
activities)))))

View File

@@ -1,37 +1,37 @@
;; Search and actor card components
;; Aliases — delegate to shared ~avatar
(defcomp ~federation-actor-avatar-img (&key (src :as string) (cls :as string))
(~avatar :src src :cls cls))
;; Aliases — delegate to shared ~shared:misc/avatar
(defcomp ~search/actor-avatar-img (&key (src :as string) (cls :as string))
(~shared:misc/avatar :src src :cls cls))
(defcomp ~federation-actor-avatar-placeholder (&key (cls :as string) (initial :as string))
(~avatar :cls cls :initial initial))
(defcomp ~search/actor-avatar-placeholder (&key (cls :as string) (initial :as string))
(~shared:misc/avatar :cls cls :initial initial))
(defcomp ~federation-actor-name-link (&key (href :as string) (name :as string))
(defcomp ~search/actor-name-link (&key (href :as string) (name :as string))
(a :href href :class "font-semibold text-stone-900 hover:underline" name))
(defcomp ~federation-actor-name-link-external (&key (href :as string) (name :as string))
(defcomp ~search/actor-name-link-external (&key (href :as string) (name :as string))
(a :href href :target "_blank" :rel "noopener"
:class "font-semibold text-stone-900 hover:underline" name))
(defcomp ~federation-actor-summary (&key (summary :as string))
(defcomp ~search/actor-summary (&key (summary :as string))
(div :class "text-sm text-stone-600 mt-1 truncate" (~rich-text :html summary)))
(defcomp ~federation-unfollow-button (&key (action :as string) (csrf :as string) (actor-url :as string))
(defcomp ~search/unfollow-button (&key (action :as string) (csrf :as string) (actor-url :as string))
(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 :as string) (csrf :as string) (actor-url :as string) (label :as string))
(defcomp ~search/follow-button (&key (action :as string) (csrf :as string) (actor-url :as string) (label :as string))
(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 :as string) (id :as string) avatar name (username :as string) (domain :as string) summary button)
(defcomp ~search/actor-card (&key (cls :as string) (id :as string) avatar name (username :as string) (domain :as string) summary button)
(article :class cls :id id
avatar
(div :class "flex-1 min-w-0"
@@ -41,7 +41,7 @@
button))
;; Data-driven actor card (replaces Python _actor_card_sx loop)
(defcomp ~federation-actor-card-from-data (&key (d :as dict) (has-actor :as boolean) (csrf :as string) (follow-url :as string) (unfollow-url :as string) (list-type :as string))
(defcomp ~search/actor-card-from-data (&key (d :as dict) (has-actor :as boolean) (csrf :as string) (follow-url :as string) (unfollow-url :as string) (list-type :as string))
(let* ((icon-url (get d "icon_url"))
(display-name (get d "display_name"))
(username (get d "username"))
@@ -49,42 +49,42 @@
(actor-url (get d "actor_url"))
(safe-id (get d "safe_id"))
(initial (or (get d "initial") "?"))
(avatar (~avatar
(avatar (~shared:misc/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)))
(~search/actor-name-link-external :href (get d "name_href") :name display-name)
(~search/actor-name-link :href (get d "name_href") :name display-name)))
(summary-sx (when (get d "summary")
(~federation-actor-summary :summary (get d "summary"))))
(~search/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
(~search/unfollow-button :action unfollow-url :csrf csrf :actor-url actor-url)
(~search/follow-button :action follow-url :csrf csrf :actor-url actor-url
:label (if (= list-type "followers") "Follow Back" "Follow"))))))
(~federation-actor-card
(~search/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 :as list) (next-url :as string?) (has-actor :as boolean) (csrf :as string)
(defcomp ~search/actor-list-from-data (&key (actors :as list) (next-url :as string?) (has-actor :as boolean) (csrf :as string)
(follow-url :as string) (unfollow-url :as string) (list-type :as string))
(<>
(map (lambda (d)
(~federation-actor-card-from-data :d d :has-actor has-actor :csrf csrf
(~search/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))))
(when next-url (~social/scroll-sentinel :url next-url))))
(defcomp ~federation-search-info (&key (cls :as string) (text :as string))
(defcomp ~search/info (&key (cls :as string) (text :as string))
(p :class cls text))
(defcomp ~federation-search-page (&key (search-url :as string) (search-page-url :as string) (query :as string) info results)
(defcomp ~search/page (&key (search-url :as string) (search-page-url :as string) (query :as string) 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
@@ -97,7 +97,7 @@
(div :id "search-results" results))
;; Following / Followers list page
(defcomp ~federation-actor-list-page (&key (title :as string) (count-str :as string) items)
(defcomp ~search/actor-list-page (&key (title :as string) (count-str :as string) items)
(h1 :class "text-2xl font-bold mb-6" title " "
(span :class "text-stone-400 font-normal" count-str))
(div :id "actor-list" items))
@@ -106,7 +106,7 @@
;; Assembled actor card — replaces Python _actor_card_sx
;; ---------------------------------------------------------------------------
(defcomp ~federation-actor-card-from-data (&key (a :as dict) actor (followed-urls :as list) (list-type :as string))
(defcomp ~search/actor-card-from-data (&key (a :as dict) actor (followed-urls :as list) (list-type :as string))
(let* ((display-name (or (get a "display_name") (get a "preferred_username") ""))
(username (or (get a "preferred_username") ""))
(domain (or (get a "domain") ""))
@@ -119,81 +119,81 @@
(upper (slice (or display-name username) 0 1)) "?"))
(csrf (csrf-token))
(is-followed (contains? (or followed-urls (list)) actor-url)))
(~federation-actor-card
(~search/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
:avatar (~shared:misc/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
(~search/actor-name-link
:href (url-for "social.defpage_actor_timeline" :id aid)
:name (escape display-name))
(~federation-actor-name-link-external
(~search/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))
:summary (when summary (~search/actor-summary :summary summary))
:button (when actor
(if (or (= list-type "following") is-followed)
(~federation-unfollow-button
(~search/unfollow-button
:action (url-for "social.unfollow") :csrf csrf :actor-url actor-url)
(~federation-follow-button
(~search/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 :as string?) (actors :as list) (total :as number) (followed-urls :as list) actor)
(~federation-search-page
(defcomp ~search/content (&key (query :as string?) (actors :as list) (total :as number) (followed-urls :as list) actor)
(~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
(~search/info
:cls "text-sm text-stone-500 mb-4"
:text (str total " result" (pluralize total) " for " (escape query))))
(query
(~federation-search-info
(~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
(~search/actor-card-from-data
:a a :actor actor :followed-urls followed-urls :list-type "search"))
actors)
(when (>= (len actors) 20)
(~federation-scroll-sentinel
(~social/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 :as list) (total :as number) actor)
(~federation-actor-list-page
(defcomp ~search/following-content (&key (actors :as list) (total :as number) actor)
(~search/actor-list-page
:title "Following" :count-str (str "(" total ")")
:items (when (not (empty? actors))
(<>
(map (lambda (a)
(~federation-actor-card-from-data
(~search/actor-card-from-data
:a a :actor actor :followed-urls (list) :list-type "following"))
actors)
(when (>= (len actors) 20)
(~federation-scroll-sentinel
(~social/scroll-sentinel
:url (url-for "social.following_list_page" :page 2)))))))
(defcomp ~federation-followers-content (&key (actors :as list) (total :as number) (followed-urls :as list) actor)
(~federation-actor-list-page
(defcomp ~search/followers-content (&key (actors :as list) (total :as number) (followed-urls :as list) actor)
(~search/actor-list-page
:title "Followers" :count-str (str "(" total ")")
:items (when (not (empty? actors))
(<>
(map (lambda (a)
(~federation-actor-card-from-data
(~search/actor-card-from-data
:a a :actor actor :followed-urls followed-urls :list-type "followers"))
actors)
(when (>= (len actors) 20)
(~federation-scroll-sentinel
(~social/scroll-sentinel
:url (url-for "social.followers_list_page" :page 2)))))))

View File

@@ -2,46 +2,46 @@
;; --- Navigation ---
(defcomp ~federation-nav-choose-username (&key (url :as string))
(defcomp ~social/nav-choose-username (&key (url :as string))
(nav :class "flex gap-3 text-sm items-center"
(a :href url :class "px-2 py-1 rounded hover:bg-stone-200 font-bold" "Choose username")))
(defcomp ~federation-nav-notification-link (&key (href :as string) (cls :as string) (count-url :as string))
(defcomp ~social/nav-notification-link (&key (href :as string) (cls :as string) (count-url :as string))
(a :href href :class cls "Notifications"
(span :sx-get count-url :sx-trigger "load, every 30s" :sx-swap "innerHTML"
:class "absolute -top-2 -right-3 text-xs bg-red-500 text-white rounded-full px-1 empty:hidden")))
(defcomp ~federation-nav-bar (&key items)
(defcomp ~social/nav-bar (&key items)
(nav :class "flex gap-3 text-sm items-center flex-wrap" items))
(defcomp ~federation-social-header (&key nav)
(defcomp ~social/header (&key nav)
(div :id "social-row" :class "flex flex-col items-center md:flex-row justify-center md:justify-between w-full p-1 bg-sky-400"
(div :class "w-full flex flex-row items-center gap-2 flex-wrap" nav)))
;; --- Post card ---
(defcomp ~federation-boost-label (&key (name :as string))
(defcomp ~social/boost-label (&key (name :as string))
(div :class "text-sm text-stone-500 mb-2" "Boosted by " name))
;; Aliases — delegate to shared ~avatar
(defcomp ~federation-avatar-img (&key (src :as string) (cls :as string))
(~avatar :src src :cls cls))
;; Aliases — delegate to shared ~shared:misc/avatar
(defcomp ~social/avatar-img (&key (src :as string) (cls :as string))
(~shared:misc/avatar :src src :cls cls))
(defcomp ~federation-avatar-placeholder (&key (cls :as string) (initial :as string))
(~avatar :cls cls :initial initial))
(defcomp ~social/avatar-placeholder (&key (cls :as string) (initial :as string))
(~shared:misc/avatar :cls cls :initial initial))
(defcomp ~federation-content (&key (content :as string) (summary :as string?))
(defcomp ~social/content (&key (content :as string) (summary :as string?))
(if summary
(details :class "mt-2"
(summary :class "text-stone-500 cursor-pointer" "CW: " (~rich-text :html summary))
(div :class "mt-2 prose prose-sm prose-stone max-w-none" (~rich-text :html content)))
(div :class "mt-2 prose prose-sm prose-stone max-w-none" (~rich-text :html content))))
(defcomp ~federation-original-link (&key (url :as string))
(defcomp ~social/original-link (&key (url :as string))
(a :href url :target "_blank" :rel "noopener"
:class "text-sm text-stone-400 hover:underline mt-1 inline-block" "original"))
(defcomp ~federation-post-card (&key boost avatar (actor-name :as string) (actor-username :as string) (domain :as string) (time :as string) content original interactions)
(defcomp ~social/post-card (&key boost avatar (actor-name :as string) (actor-username :as string) (domain :as string) (time :as string) content original interactions)
(article :class "bg-white rounded-lg shadow-sm border border-stone-200 p-4 mb-4"
boost
(div :class "flex items-start gap-3"
@@ -55,36 +55,36 @@
;; --- Interaction buttons ---
(defcomp ~federation-reply-link (&key (url :as string))
(defcomp ~social/reply-link (&key (url :as string))
(a :href url :class "hover:text-stone-700" "Reply"))
(defcomp ~federation-like-form (&key (action :as string) (target :as string) (oid :as string) (ainbox :as string) (csrf :as string) (cls :as string) (icon :as string) count)
(defcomp ~social/like-form (&key (action :as string) (target :as string) (oid :as string) (ainbox :as string) (csrf :as string) (cls :as string) (icon :as string) count)
(form :sx-post action :sx-target target :sx-swap "innerHTML"
(input :type "hidden" :name "object_id" :value oid)
(input :type "hidden" :name "author_inbox" :value ainbox)
(input :type "hidden" :name "csrf_token" :value csrf)
(button :type "submit" :class cls (span icon) " " count)))
(defcomp ~federation-boost-form (&key (action :as string) (target :as string) (oid :as string) (ainbox :as string) (csrf :as string) (cls :as string) count)
(defcomp ~social/boost-form (&key (action :as string) (target :as string) (oid :as string) (ainbox :as string) (csrf :as string) (cls :as string) count)
(form :sx-post action :sx-target target :sx-swap "innerHTML"
(input :type "hidden" :name "object_id" :value oid)
(input :type "hidden" :name "author_inbox" :value ainbox)
(input :type "hidden" :name "csrf_token" :value csrf)
(button :type "submit" :class cls (span "\u21bb") " " count)))
(defcomp ~federation-interaction-buttons (&key like boost reply)
(defcomp ~social/interaction-buttons (&key like boost reply)
(div :class "flex items-center gap-4 mt-3 text-sm text-stone-500"
like boost reply))
;; --- Timeline ---
(defcomp ~federation-scroll-sentinel (&key (url :as string))
(defcomp ~social/scroll-sentinel (&key (url :as string))
(div :sx-get url :sx-trigger "revealed" :sx-swap "outerHTML"))
(defcomp ~federation-compose-button (&key (url :as string))
(defcomp ~social/compose-button (&key (url :as string))
(a :href url :class "bg-stone-800 text-white px-4 py-2 rounded hover:bg-stone-700" "Compose"))
(defcomp ~federation-timeline-page (&key (label :as string) compose timeline)
(defcomp ~social/timeline-page (&key (label :as string) compose timeline)
(div :class "flex items-center justify-between mb-6"
(h1 :class "text-2xl font-bold" label " Timeline")
compose)
@@ -92,24 +92,24 @@
;; --- Data-driven post card (replaces Python _post_card_sx loop) ---
(defcomp ~federation-post-card-from-data (&key (d :as dict) (has-actor :as boolean) (csrf :as string)
(defcomp ~social/post-card-from-data (&key (d :as dict) (has-actor :as boolean) (csrf :as string)
(like-url :as string) (unlike-url :as string)
(boost-url :as string) (unboost-url :as string))
(let* ((boosted-by (get d "boosted_by"))
(actor-icon (get d "actor_icon"))
(actor-name (get d "actor_name"))
(initial (or (get d "initial") "?"))
(avatar (~avatar
(avatar (~shared:misc/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)))
(boost (when boosted-by (~federation-boost-label :name boosted-by)))
(boost (when boosted-by (~social/boost-label :name boosted-by)))
(content-sx (if (get d "summary")
(~federation-content :content (get d "content") :summary (get d "summary"))
(~federation-content :content (get d "content"))))
(~social/content :content (get d "content") :summary (get d "summary"))
(~social/content :content (get d "content"))))
(original (when (get d "original_url")
(~federation-original-link :url (get d "original_url"))))
(~social/original-link :url (get d "original_url"))))
(safe-id (get d "safe_id"))
(interactions (when has-actor
(let* ((oid (get d "object_id"))
@@ -123,16 +123,16 @@
(b-action (if boosted-me unboost-url boost-url))
(b-cls (str "flex items-center gap-1 " (if boosted-me "text-green-600 hover:text-green-700" "hover:text-green-600")))
(reply-url (get d "reply_url"))
(reply (when reply-url (~federation-reply-link :url reply-url)))
(like-form (~federation-like-form
(reply (when reply-url (~social/reply-link :url reply-url)))
(like-form (~social/like-form
:action l-action :target target :oid oid :ainbox ainbox
:csrf csrf :cls l-cls :icon l-icon :count (get d "like_count")))
(boost-form (~federation-boost-form
(boost-form (~social/boost-form
:action b-action :target target :oid oid :ainbox ainbox
:csrf csrf :cls b-cls :count (get d "boost_count"))))
(div :id (str "interactions-" safe-id)
(~federation-interaction-buttons :like like-form :boost boost-form :reply reply))))))
(~federation-post-card
(~social/interaction-buttons :like like-form :boost boost-form :reply reply))))))
(~social/post-card
:boost boost :avatar avatar
:actor-name actor-name :actor-username (get d "actor_username")
:domain (get d "domain") :time (get d "time")
@@ -140,22 +140,22 @@
:interactions interactions)))
;; Data-driven timeline items (replaces Python _timeline_items_sx loop)
(defcomp ~federation-timeline-items-from-data (&key (items :as list) (next-url :as string?) (has-actor :as boolean) (csrf :as string)
(defcomp ~social/timeline-items-from-data (&key (items :as list) (next-url :as string?) (has-actor :as boolean) (csrf :as string)
(like-url :as string) (unlike-url :as string) (boost-url :as string) (unboost-url :as string))
(<>
(map (lambda (d)
(~federation-post-card-from-data :d d :has-actor has-actor :csrf csrf
(~social/post-card-from-data :d d :has-actor has-actor :csrf csrf
:like-url like-url :unlike-url unlike-url :boost-url boost-url :unboost-url unboost-url))
(or items (list)))
(when next-url (~federation-scroll-sentinel :url next-url))))
(when next-url (~social/scroll-sentinel :url next-url))))
;; --- Compose ---
(defcomp ~federation-compose-reply (&key (reply-to :as string))
(defcomp ~social/compose-reply (&key (reply-to :as string))
(input :type "hidden" :name "in_reply_to" :value reply-to)
(div :class "text-sm text-stone-500" "Replying to " (span :class "font-mono" reply-to)))
(defcomp ~federation-compose-form (&key (action :as string) (csrf :as string) reply)
(defcomp ~social/compose-form (&key (action :as string) (csrf :as string) reply)
(h1 :class "text-2xl font-bold mb-6" "Compose")
(form :method "post" :action action :class "space-y-4"
(input :type "hidden" :name "csrf_token" :value csrf)
@@ -174,9 +174,9 @@
;; Assembled social nav — replaces Python _social_nav_sx
;; ---------------------------------------------------------------------------
(defcomp ~federation-social-nav (&key actor)
(defcomp ~social/nav (&key actor)
(if (not actor)
(~federation-nav-choose-username :url (url-for "identity.choose_username_form"))
(~social/nav-choose-username :url (url-for "identity.choose_username_form"))
(let* ((rp (request-path))
(links (list
(dict :endpoint "social.defpage_home_timeline" :label "Timeline")
@@ -185,7 +185,7 @@
(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
(~social/nav-bar
:items (<>
(map (lambda (lnk)
(let* ((href (url-for (get lnk "endpoint")))
@@ -196,7 +196,7 @@
links)
(let* ((notif-url (url-for "social.defpage_notifications"))
(notif-bold (if (= rp notif-url) " font-bold" "")))
(~federation-nav-notification-link
(~social/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")))
@@ -208,7 +208,7 @@
;; Assembled post card — replaces Python _post_card_sx
;; ---------------------------------------------------------------------------
(defcomp ~federation-post-card-from-data (&key (item :as dict) actor)
(defcomp ~social/post-card-from-data (&key (item :as dict) actor)
(let* ((boosted-by (get item "boosted_by"))
(actor-icon (get item "actor_icon"))
(actor-name (or (get item "actor_name") "?"))
@@ -223,9 +223,9 @@
(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
(~social/post-card
:boost (when boosted-by (~social/boost-label :name (escape boosted-by)))
:avatar (~shared:misc/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")
@@ -235,10 +235,10 @@
:domain (if actor-domain (str "@" (escape actor-domain)) "")
:time published
:content (if summary
(~federation-content :content content :summary (escape summary))
(~federation-content :content content))
(~social/content :content content :summary (escape summary))
(~social/content :content content))
:original (when (and url (= post-type "remote"))
(~federation-original-link :url url))
(~social/original-link :url url))
:interactions (when actor
(let* ((csrf (csrf-token))
(liked (get item "liked_by_me"))
@@ -248,50 +248,50 @@
(ainbox (or (get item "author_inbox") ""))
(target (str "#interactions-" safe-id)))
(div :id (str "interactions-" safe-id)
(~federation-interaction-buttons
:like (~federation-like-form
(~social/interaction-buttons
:like (~social/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
:boost (~social/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
(~social/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 :as list) (timeline-type :as string) actor (next-url :as string?))
(defcomp ~social/timeline-items (&key (items :as list) (timeline-type :as string) actor (next-url :as string?))
(<>
(map (lambda (item)
(~federation-post-card-from-data :item item :actor actor))
(~social/post-card-from-data :item item :actor actor))
items)
(when next-url
(~federation-scroll-sentinel :url next-url))))
(~social/scroll-sentinel :url next-url))))
;; Assembled timeline content — replaces Python _timeline_content_sx
(defcomp ~federation-timeline-content (&key (items :as list) (timeline-type :as string) actor)
(defcomp ~social/timeline-content (&key (items :as list) (timeline-type :as string) actor)
(let* ((label (if (= timeline-type "home") "Home" "Public")))
(~federation-timeline-page
(~social/timeline-page
:label label
:compose (when actor
(~federation-compose-button :url (url-for "social.defpage_compose_form")))
:timeline (~federation-timeline-items
(~social/compose-button :url (url-for "social.defpage_compose_form")))
:timeline (~social/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 :as string?))
(~federation-compose-form
(defcomp ~social/compose-content (&key (reply-to :as string?))
(~social/compose-form
:action (url-for "social.compose_submit")
:csrf (csrf-token)
:reply (when reply-to
(~federation-compose-reply :reply-to (escape reply-to)))))
(~social/compose-reply :reply-to (escape reply-to)))))

View File

@@ -6,7 +6,7 @@
:auth :login
:layout :social
:data (service "federation-page" "home-timeline-data")
:content (~federation-timeline-content
:content (~social/timeline-content
:items items
:timeline-type timeline-type
:actor actor))
@@ -16,7 +16,7 @@
:auth :public
:layout :social
:data (service "federation-page" "public-timeline-data")
:content (~federation-timeline-content
:content (~social/timeline-content
:items items
:timeline-type timeline-type
:actor actor))
@@ -26,7 +26,7 @@
:auth :login
:layout :social
:data (service "federation-page" "compose-data")
:content (~federation-compose-content
:content (~social/compose-content
:reply-to reply-to))
(defpage search
@@ -34,7 +34,7 @@
:auth :public
:layout :social
:data (service "federation-page" "search-data")
:content (~federation-search-content
:content (~search/content
:query query
:actors actors
:total total
@@ -46,7 +46,7 @@
:auth :login
:layout :social
:data (service "federation-page" "following-data")
:content (~federation-following-content
:content (~search/following-content
:actors actors
:total total
:actor actor))
@@ -56,7 +56,7 @@
:auth :login
:layout :social
:data (service "federation-page" "followers-data")
:content (~federation-followers-content
:content (~search/followers-content
:actors actors
:total total
:followed-urls followed-urls
@@ -67,7 +67,7 @@
:auth :public
:layout :social
:data (service "federation-page" "actor-timeline-data" :id id)
:content (~federation-actor-timeline-content
:content (~profile/actor-timeline-content
:remote-actor remote-actor
:items items
:is-following is-following
@@ -78,5 +78,5 @@
:auth :login
:layout :social
:data (service "federation-page" "notifications-data")
:content (~federation-notifications-content
:content (~notifications/content
:notifications notifications))