sx-headers attributes now use native SX dict format {:key val} instead of
JSON strings. Eliminates manual JSON string construction in both .sx files
and Python callers.
- sx.js: parse sx-headers/sx-vals as SX dict ({: prefix) with JSON fallback,
add _serializeDict for dict→attribute serialization, fix verbInfo scope in
_doFetch error handler
- html.py: serialize dict attribute values via SX serialize() not str()
- All .sx files: {:X-CSRFToken csrf} replaces (str "{\"X-CSRFToken\": ...}")
- All Python callers: {"X-CSRFToken": csrf} dict replaces f-string JSON
- Blog like: extract ~blog-like-toggle, fix POST returning wrong component,
fix emoji escapes in .sx (parser has no \U support), fix card :hx-headers
keyword mismatch, wrap sx_content in SxExpr for evaluation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
94 lines
4.2 KiB
Plaintext
94 lines
4.2 KiB
Plaintext
;; Blog post detail components
|
|
|
|
(defcomp ~blog-detail-edit-link (&key href hx-select)
|
|
(a :href href :sx-get href :sx-target "#main-panel"
|
|
:sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true"
|
|
:class "inline-block px-3 py-1 rounded-full text-sm font-semibold bg-stone-700 text-white hover:bg-stone-800 transition-colors"
|
|
(i :class "fa fa-pencil mr-1") " Edit"))
|
|
|
|
(defcomp ~blog-detail-draft (&key publish-requested edit)
|
|
(div :class "flex items-center justify-center gap-2 mb-3"
|
|
(span :class "inline-block px-3 py-1 rounded-full text-sm font-semibold bg-amber-100 text-amber-800" "Draft")
|
|
(when publish-requested (span :class "inline-block px-3 py-1 rounded-full text-sm font-semibold bg-blue-100 text-blue-800" "Publish requested"))
|
|
edit))
|
|
|
|
(defcomp ~blog-like-toggle (&key like-url hx-headers heart)
|
|
(button :sx-post like-url :sx-swap "outerHTML"
|
|
:sx-headers hx-headers :class "cursor-pointer" heart))
|
|
|
|
(defcomp ~blog-detail-like (&key like-url hx-headers heart)
|
|
(div :class "absolute top-2 right-2 z-10 text-8xl md:text-6xl"
|
|
(~blog-like-toggle :like-url like-url :hx-headers hx-headers :heart heart)))
|
|
|
|
(defcomp ~blog-detail-excerpt (&key excerpt)
|
|
(div :class "w-full text-center italic text-3xl p-2" excerpt))
|
|
|
|
(defcomp ~blog-detail-chrome (&key like excerpt at-bar)
|
|
(<> like
|
|
excerpt
|
|
(div :class "hidden md:block" at-bar)))
|
|
|
|
(defcomp ~blog-detail-main (&key draft chrome feature-image html-content sx-content)
|
|
(<> (article :class "relative"
|
|
draft
|
|
chrome
|
|
(when feature-image (div :class "mb-3 flex justify-center"
|
|
(img :src feature-image :alt "" :class "rounded-lg w-full md:w-3/4 object-cover")))
|
|
(if sx-content
|
|
(div :class "blog-content p-2" sx-content)
|
|
(when html-content (div :class "blog-content p-2" (~rich-text :html html-content)))))
|
|
(div :class "pb-8")))
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Data-driven composition — replaces _post_main_panel_sx
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~blog-post-detail-content (&key slug is-draft publish-requested can-edit edit-href
|
|
is-page has-user liked like-url csrf
|
|
custom-excerpt tags authors
|
|
feature-image html-content sx-content)
|
|
(let* ((hx-select "#main-panel")
|
|
(draft-sx (when is-draft
|
|
(~blog-detail-draft
|
|
:publish-requested publish-requested
|
|
:edit (when can-edit
|
|
(~blog-detail-edit-link :href edit-href :hx-select hx-select)))))
|
|
(chrome-sx (when (not is-page)
|
|
(~blog-detail-chrome
|
|
:like (when has-user
|
|
(~blog-detail-like
|
|
:like-url like-url
|
|
:hx-headers {:X-CSRFToken csrf}
|
|
:heart (if liked "❤️" "🤍")))
|
|
:excerpt (when (not (= custom-excerpt ""))
|
|
(~blog-detail-excerpt :excerpt custom-excerpt))
|
|
:at-bar (~blog-at-bar :tags tags :authors authors)))))
|
|
(~blog-detail-main
|
|
:draft draft-sx
|
|
:chrome chrome-sx
|
|
:feature-image feature-image
|
|
:html-content html-content
|
|
:sx-content sx-content)))
|
|
|
|
(defcomp ~blog-meta (&key robots page-title desc canonical og-type og-title image twitter-card twitter-title)
|
|
(<>
|
|
(meta :name "robots" :content robots)
|
|
(title page-title)
|
|
(meta :name "description" :content desc)
|
|
(when canonical (link :rel "canonical" :href canonical))
|
|
(meta :property "og:type" :content og-type)
|
|
(meta :property "og:title" :content og-title)
|
|
(meta :property "og:description" :content desc)
|
|
(when canonical (meta :property "og:url" :content canonical))
|
|
(when image (meta :property "og:image" :content image))
|
|
(meta :name "twitter:card" :content twitter-card)
|
|
(meta :name "twitter:title" :content twitter-title)
|
|
(meta :name "twitter:description" :content desc)
|
|
(when image (meta :name "twitter:image" :content image))))
|
|
|
|
(defcomp ~blog-home-main (&key html-content sx-content)
|
|
(article :class "relative"
|
|
(if sx-content
|
|
(div :class "blog-content p-2" sx-content)
|
|
(div :class "blog-content p-2" (~rich-text :html html-content)))))
|