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:
2026-04-04 20:49:02 +00:00
parent aee4770a6a
commit c0665ba58e
19 changed files with 4974 additions and 3771 deletions

View File

@@ -144,77 +144,139 @@
edit-form delete-form)) edit-form delete-form))
;; Data-driven snippets list (replaces Python _snippets_sx loop) ;; Data-driven snippets list (replaces Python _snippets_sx loop)
(defcomp ~admin/snippets-from-data (&key snippets user-id is-admin csrf badge-colours) (defcomp
~admin/snippets-from-data
(&key snippets user-id is-admin csrf badge-colours)
(~admin/snippets-list (~admin/snippets-list
:rows (<> (map (lambda (s) :rows (<>
(let* ((s-id (get s "id")) (map
(s-name (get s "name")) (lambda
(s-uid (get s "user_id")) (s)
(s-vis (get s "visibility")) (let-match
(owner (if (= s-uid user-id) "You" (str "User #" s-uid))) {:visibility s-vis :delete_url delete-url :patch_url patch-url :id s-id :user_id s-uid :name s-name}
(badge-cls (or (get badge-colours s-vis) "bg-stone-200 text-stone-700")) s
(extra (<> (let*
(when is-admin ((owner (if (= s-uid user-id) "You" (str "User #" s-uid)))
(badge-cls
(or (get badge-colours s-vis) "bg-stone-200 text-stone-700"))
(extra
(<>
(when
is-admin
(~admin/snippet-visibility-select (~admin/snippet-visibility-select
:patch-url (get s "patch_url") :patch-url patch-url
:hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}") :hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")
:options (<> :options (<>
(~admin/snippet-option :value "private" :selected (= s-vis "private") :label "private") (~admin/snippet-option
(~admin/snippet-option :value "shared" :selected (= s-vis "shared") :label "shared") :value "private"
(~admin/snippet-option :value "admin" :selected (= s-vis "admin") :label "admin")) :selected (= s-vis "private")
:cls "text-sm border border-stone-300 rounded px-2 py-1")) :label "private")
(when (or (= s-uid user-id) is-admin) (~admin/snippet-option
(~shared:misc/delete-btn :url (get s "delete_url") :trigger-target "#snippets-list" :value "shared"
:selected (= s-vis "shared")
:label "shared")
(~admin/snippet-option
:value "admin"
:selected (= s-vis "admin")
:label "admin"))
:cls "text-sm border border-stone-300 rounded px-2 py-0.5"))
(when
(or (= s-uid user-id) is-admin)
(~shared:misc/delete-btn
:url delete-url
:trigger-target "#snippets-list"
:title "Delete snippet?" :title "Delete snippet?"
:text (str "Delete \u201c" s-name "\u201d?") :text (str "Delete \"" s-name "\"?")
:sx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}") :sx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")
:cls "px-3 py-1 text-sm bg-red-200 hover:bg-red-300 rounded text-red-800 flex-shrink-0"))))) :cls "px-3 py-1 text-sm bg-red-200 hover:bg-red-300 text-red-800 rounded-full")))))
(~admin/snippet-row :name s-name :owner owner :badge-cls badge-cls (~admin/snippet-row
:visibility s-vis :extra extra))) :name s-name
:owner owner
:badge-cls badge-cls
:visibility s-vis
:extra extra))))
(or snippets (list)))))) (or snippets (list))))))
;; Data-driven menu items list (replaces Python _menu_items_list_sx loop) ;; Data-driven menu items list (replaces Python _menu_items_list_sx loop)
(defcomp ~admin/menu-items-from-data (&key items csrf) (defcomp
~admin/menu-items-from-data
(&key items csrf)
(~admin/menu-items-list (~admin/menu-items-list
:rows (<> (map (lambda (item) :rows (<>
(let* ((img (~shared:misc/img-or-placeholder :src (get item "feature_image") :alt (get item "label") (map
:size-cls "w-12 h-12 rounded-full object-cover flex-shrink-0"))) (lambda
(item)
(let-match
{:delete_url delete-url :sort_order sort-order :edit_url edit-url :feature_image feature-image :label label :slug slug}
item
(let
((img (~shared:misc/img-or-placeholder :src feature-image :alt label :size-cls "w-12 h-12 rounded-full object-cover flex-shrink-0")))
(~admin/menu-item-row (~admin/menu-item-row
:img img :label (get item "label") :slug (get item "slug") :img img
:sort-order (get item "sort_order") :edit-url (get item "edit_url") :label label
:delete-url (get item "delete_url") :slug slug
:confirm-text (str "Remove " (get item "label") " from the menu?") :sort-order sort-order
:hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")))) :edit-url edit-url
:delete-url delete-url
:confirm-text (str "Remove " label " from the menu?")
:hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")))))
(or items (list)))))) (or items (list))))))
;; Data-driven tag groups main (replaces Python _tag_groups_main_panel_sx loops) ;; Data-driven tag groups main (replaces Python _tag_groups_main_panel_sx loops)
(defcomp ~admin/tag-groups-from-data (&key groups unassigned-tags csrf create-url) (defcomp
~admin/tag-groups-from-data
(&key groups unassigned-tags csrf create-url)
(~admin/tag-groups-main (~admin/tag-groups-main
:form (~admin/tag-groups-create-form :create-url create-url :csrf csrf) :form (~admin/tag-groups-create-form :create-url create-url :csrf csrf)
:groups (if (empty? (or groups (list))) :groups (if
(~shared:misc/empty-state :message "No tag groups yet." :cls "text-stone-500 text-sm") (empty? (or groups (list)))
(~shared:misc/empty-state
:message "No tag groups yet."
:cls "text-stone-500 text-sm")
(~admin/tag-groups-list (~admin/tag-groups-list
:items (<> (map (lambda (g) :items (<>
(let* ((icon (if (get g "feature_image") (map
(~admin/tag-group-icon-image :src (get g "feature_image") :name (get g "name")) (lambda
(~admin/tag-group-icon-color :style (get g "style") :initial (get g "initial"))))) (g)
(~admin/tag-group-li :icon icon :edit-href (get g "edit_href") (let-match
:name (get g "name") :slug (get g "slug") :sort-order (get g "sort_order")))) {:sort_order sort-order :feature_image feature-image :slug slug :edit_href edit-href :initial initial :name name :style style}
g
(let
((icon (if feature-image (~admin/tag-group-icon-image :src feature-image :name name) (~admin/tag-group-icon-color :style style :initial initial))))
(~admin/tag-group-li
:icon icon
:edit-href edit-href
:name name
:slug slug
:sort-order sort-order))))
groups)))) groups))))
:unassigned (when (not (empty? (or unassigned-tags (list)))) :unassigned (when
(not (empty? (or unassigned-tags (list))))
(~admin/unassigned-tags (~admin/unassigned-tags
:heading (str "Unassigned Tags (" (len unassigned-tags) ")") :heading (str "Unassigned Tags (" (len unassigned-tags) ")")
:spans (<> (map (lambda (t) :spans (<>
(~admin/unassigned-tag :name (get t "name"))) (map
(lambda (t) (~admin/unassigned-tag :name (get t "name")))
unassigned-tags)))))) unassigned-tags))))))
;; Data-driven tag group edit (replaces Python _tag_groups_edit_main_panel_sx loop) ;; Data-driven tag group edit (replaces Python _tag_groups_edit_main_panel_sx loop)
(defcomp ~admin/tag-checkboxes-from-data (&key tags) (defcomp
(<> (map (lambda (t) ~admin/tag-checkboxes-from-data
(&key tags)
(<>
(map
(lambda
(t)
(let-match
{:tag_id tag-id :checked checked :feature_image feature-image :name name}
t
(~admin/tag-checkbox (~admin/tag-checkbox
:tag-id (get t "tag_id") :checked (get t "checked") :tag-id tag-id
:img (when (get t "feature_image") (~admin/tag-checkbox-image :src (get t "feature_image"))) :checked checked
:name (get t "name"))) :img (when
feature-image
(~admin/tag-checkbox-image :src feature-image))
:name name)))
(or tags (list))))) (or tags (list)))))
;; Preview panel components ;; Preview panel components
@@ -258,113 +320,175 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Snippets — receives serialized snippet dicts from service ;; Snippets — receives serialized snippet dicts from service
(defcomp ~admin/snippets-content (&key snippets is-admin csrf) (defcomp
~admin/snippets-content
(&key snippets is-admin csrf)
(~admin/snippets-panel (~admin/snippets-panel
:list (if (empty? (or snippets (list))) :list (if
(~shared:misc/empty-state :icon "fa fa-puzzle-piece" (empty? (or snippets (list)))
(~shared:misc/empty-state
:icon "fa fa-puzzle-piece"
:message "No snippets yet. Create one from the blog editor.") :message "No snippets yet. Create one from the blog editor.")
(~admin/snippets-list (~admin/snippets-list
:rows (map (lambda (s) :rows (map
(let* ((badge-colours (dict (lambda
"private" "bg-stone-200 text-stone-700" (s)
"shared" "bg-blue-100 text-blue-700" (let-match
"admin" "bg-amber-100 text-amber-700")) {:visibility vis* :delete_url delete-url :owner owner :can_delete can-delete :patch_url patch-url :name name}
(vis (or (get s "visibility") "private")) s
(badge-cls (or (get badge-colours vis) "bg-stone-200 text-stone-700")) (let*
(name (get s "name")) ((vis (or vis* "private"))
(owner (get s "owner")) (badge-colours
(can-delete (get s "can_delete"))) (dict
"private"
"bg-stone-200 text-stone-700"
"shared"
"bg-blue-100 text-blue-700"
"admin"
"bg-amber-100 text-amber-700"))
(badge-cls
(or (get badge-colours vis) "bg-stone-200 text-stone-700")))
(~admin/snippet-row (~admin/snippet-row
:name name :owner owner :badge-cls badge-cls :visibility vis :name name
:owner owner
:badge-cls badge-cls
:visibility vis
:extra (<> :extra (<>
(when is-admin (when
is-admin
(~admin/snippet-visibility-select (~admin/snippet-visibility-select
:patch-url (get s "patch_url") :patch-url patch-url
:hx-headers {:X-CSRFToken csrf} :hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")
:options (<> :options (<>
(~admin/snippet-option :value "private" :selected (= vis "private") :label "private") (~admin/snippet-option
(~admin/snippet-option :value "shared" :selected (= vis "shared") :label "shared") :value "private"
(~admin/snippet-option :value "admin" :selected (= vis "admin") :label "admin")))) :selected (= vis "private")
(when can-delete :label "private")
(~admin/snippet-option
:value "shared"
:selected (= vis "shared")
:label "shared")
(~admin/snippet-option
:value "admin"
:selected (= vis "admin")
:label "admin"))))
(when
can-delete
(~shared:misc/delete-btn (~shared:misc/delete-btn
:url (get s "delete_url") :url delete-url
:trigger-target "#snippets-list" :trigger-target "#snippets-list"
:title "Delete snippet?" :title "Delete snippet?"
:text (str "Delete \u201c" name "\u201d?") :text (str "Delete \"" name "\"?")
:sx-headers {:X-CSRFToken csrf} :sx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}")
:cls "px-3 py-1 text-sm bg-red-200 hover:bg-red-300 rounded text-red-800 flex-shrink-0")))))) :cls "px-3 py-1 text-sm bg-red-200 hover:bg-red-300 text-red-800 rounded-full")))))))
(or snippets (list))))))) (or snippets (list)))))))
;; Menu Items — receives serialized menu item dicts from service ;; Menu Items — receives serialized menu item dicts from service
(defcomp ~admin/menu-items-content (&key menu-items new-url csrf) (defcomp
~admin/menu-items-content
(&key menu-items new-url csrf)
(~admin/menu-items-panel (~admin/menu-items-panel
:new-url new-url :new-url new-url
:list (if (empty? (or menu-items (list))) :list (if
(~shared:misc/empty-state :icon "fa fa-inbox" (empty? (or menu-items (list)))
(~shared:misc/empty-state
:icon "fa fa-inbox"
:message "No menu items yet. Add one to get started!") :message "No menu items yet. Add one to get started!")
(~admin/menu-items-list (~admin/menu-items-list
:rows (map (lambda (mi) :rows (map
(lambda
(mi)
(let-match
{:delete_url delete-url :url url :sort_order sort-order :edit_url edit-url :feature_image feature-image :label label}
mi
(~admin/menu-item-row (~admin/menu-item-row
:img (~shared:misc/img-or-placeholder :img (~shared:misc/img-or-placeholder
:src (get mi "feature_image") :alt (get mi "label") :src feature-image
:alt label
:size-cls "w-12 h-12 rounded-full object-cover flex-shrink-0") :size-cls "w-12 h-12 rounded-full object-cover flex-shrink-0")
:label (get mi "label") :label label
:slug (get mi "url") :slug url
:sort-order (str (or (get mi "sort_order") 0)) :sort-order (str (or sort-order 0))
:edit-url (get mi "edit_url") :edit-url edit-url
:delete-url (get mi "delete_url") :delete-url delete-url
:confirm-text (str "Remove " (get mi "label") " from the menu?") :confirm-text (str "Remove " label " from the menu?")
:hx-headers {:X-CSRFToken csrf})) :hx-headers (str "{\"X-CSRFToken\": \"" csrf "\"}"))))
(or menu-items (list))))))) (or menu-items (list)))))))
;; Tag Groups — receives serialized tag group data from service ;; Tag Groups — receives serialized tag group data from service
(defcomp ~admin/tag-groups-content (&key groups unassigned-tags create-url csrf) (defcomp
~admin/tag-groups-content
(&key groups unassigned-tags create-url csrf)
(~admin/tag-groups-main (~admin/tag-groups-main
:form (~admin/tag-groups-create-form :create-url create-url :csrf csrf) :form (~admin/tag-groups-create-form :create-url create-url :csrf csrf)
:groups (if (empty? (or groups (list))) :groups (if
(~shared:misc/empty-state :icon "fa fa-tags" :message "No tag groups yet.") (empty? (or groups (list)))
(~shared:misc/empty-state
:icon "fa fa-tags"
:message "No tag groups yet.")
(~admin/tag-groups-list (~admin/tag-groups-list
:items (map (lambda (g) :items (map
(let* ((fi (get g "feature_image")) (lambda
(colour (get g "colour")) (g)
(name (get g "name")) (let-match
(initial (slice (or name "?") 0 1)) {:colour colour :sort_order sort-order* :feature_image fi :edit_href edit-href :slug slug* :name name}
(icon (if fi g
(let*
((initial (slice (or name "?") 0 1))
(icon
(if
fi
(~admin/tag-group-icon-image :src fi :name name) (~admin/tag-group-icon-image :src fi :name name)
(~admin/tag-group-icon-color (~admin/tag-group-icon-color
:style (if colour (str "background:" colour) "background:#e7e5e4") :style (if
colour
(str "background:" colour)
"background:#e7e5e4")
:initial initial)))) :initial initial))))
(~admin/tag-group-li (~admin/tag-group-li
:icon icon :icon icon
:edit-href (get g "edit_href") :edit-href edit-href
:name name :name name
:slug (or (get g "slug") "") :slug (or slug* "")
:sort-order (or (get g "sort_order") 0)))) :sort-order (or sort-order* 0)))))
(or groups (list))))) (or groups (list)))))
:unassigned (when (not (empty? (or unassigned-tags (list)))) :unassigned (when
(not (empty? (or unassigned-tags (list))))
(~admin/unassigned-tags (~admin/unassigned-tags
:heading (str (len (or unassigned-tags (list))) " Unassigned Tags") :heading (str (len (or unassigned-tags (list))) " Unassigned Tags")
:spans (map (lambda (t) :spans (map
(~admin/unassigned-tag :name (get t "name"))) (lambda (t) (~admin/unassigned-tag :name (get t "name")))
(or unassigned-tags (list))))))) (or unassigned-tags (list)))))))
;; Tag Group Edit — receives serialized tag group + tags from service ;; Tag Group Edit — receives serialized tag group + tags from service
(defcomp ~admin/tag-group-edit-content (&key group all-tags save-url delete-url csrf) (defcomp
~admin/tag-group-edit-content
(&key group all-tags save-url delete-url csrf)
(~admin/tag-group-edit-main (~admin/tag-group-edit-main
:edit-form (~admin/tag-group-edit-form :edit-form (let-match
:save-url save-url :csrf csrf {:colour colour :sort_order sort-order :feature_image feature-image :name name}
:name (get group "name") group
:colour (get group "colour") (~admin/tag-group-edit-form
:sort-order (get group "sort_order") :save-url save-url
:feature-image (get group "feature_image") :csrf csrf
:tags (map (lambda (t) :name name
:colour colour
:sort-order sort-order
:feature-image feature-image
:tags (map
(lambda
(t)
(let-match
{:checked checked :feature_image t-feature-image :id tag-id :name t-name}
t
(~admin/tag-checkbox (~admin/tag-checkbox
:tag-id (get t "id") :tag-id tag-id
:checked (get t "checked") :checked checked
:img (when (get t "feature_image") :img (when
(~admin/tag-checkbox-image :src (get t "feature_image"))) t-feature-image
:name (get t "name"))) (~admin/tag-checkbox-image :src t-feature-image))
(or all-tags (list)))) :name t-name)))
(or all-tags (list)))))
:delete-form (~admin/tag-group-delete-form :delete-url delete-url :csrf csrf))) :delete-form (~admin/tag-group-delete-form :delete-url delete-url :csrf csrf)))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
@@ -400,31 +524,54 @@
(code value) (code value)
value)))) value))))
(defcomp ~admin/data-scalar-table (&key columns) (defcomp
(div :class "w-full overflow-x-auto sm:overflow-visible" ~admin/data-scalar-table
(table :class "w-full table-fixed text-sm border border-neutral-200 rounded-xl overflow-hidden" (&key columns)
(thead :class "bg-neutral-50/70" (div
(tr (th :class "px-3 py-2 text-left font-medium w-40 sm:w-56" "Field") :class "w-full overflow-x-auto sm:overflow-visible"
(table
:class "w-full table-fixed text-sm border border-neutral-200 rounded-xl overflow-hidden"
(thead
:class "bg-neutral-50/70"
(tr
(th :class "px-3 py-2 text-left font-medium w-40 sm:w-56" "Field")
(th :class "px-3 py-2 text-left font-medium" "Value"))) (th :class "px-3 py-2 text-left font-medium" "Value")))
(tbody (tbody
(map (lambda (col) (map
(tr :class "border-t border-neutral-200 align-top" (lambda
(td :class "px-3 py-2 whitespace-nowrap text-neutral-600 align-top" (get col "key")) (col)
(td :class "px-3 py-2 align-top" (let-match
(~admin/data-value-cell :value (get col "value") :value-type (get col "type"))))) {:value value :key key :type type}
col
(tr
:class "border-t border-neutral-200 align-top"
(td :class "px-3 py-2 whitespace-nowrap text-neutral-600" key)
(td
:class "px-3 py-2 align-top"
(~admin/data-value-cell :value value :value-type type)))))
(or columns (list))))))) (or columns (list)))))))
(defcomp ~admin/data-relationship-item (&key index summary children) (defcomp
(tr :class "border-t border-neutral-200 align-top" ~admin/data-relationship-item
(&key index summary children)
(tr
:class "border-t border-neutral-200 align-top"
(td :class "px-2 py-1 whitespace-nowrap align-top" (str index)) (td :class "px-2 py-1 whitespace-nowrap align-top" (str index))
(td :class "px-2 py-1 align-top" (td
(pre :class "whitespace-pre-wrap break-words break-all text-xs" :class "px-2 py-1 align-top"
(pre
:class "whitespace-pre-wrap break-words break-all text-xs"
(code summary)) (code summary))
(when children (when
(div :class "mt-2 pl-3 border-l border-neutral-200" children
(div
:class "mt-2 pl-3 border-l border-neutral-200"
(let-match
{:relationships relationships :columns columns}
children
(~admin/data-model-content (~admin/data-model-content
:columns (get children "columns") :columns columns
:relationships (get children "relationships"))))))) :relationships relationships)))))))
(defcomp ~admin/data-relationship (&key name cardinality class-name loaded value) (defcomp ~admin/data-relationship (&key name cardinality class-name loaded value)
(div :class "rounded-xl border border-neutral-200" (div :class "rounded-xl border border-neutral-200"
@@ -463,29 +610,50 @@
:columns (get (get value "children") "columns") :columns (get (get value "children") "columns")
:relationships (get (get value "children") "relationships")))))))))) :relationships (get (get value "children") "relationships"))))))))))
(defcomp ~admin/data-model-content (&key columns relationships) (defcomp
(div :class "space-y-4" ~admin/data-model-content
(&key columns relationships)
(div
:class "space-y-4"
(~admin/data-scalar-table :columns columns) (~admin/data-scalar-table :columns columns)
(when (not (empty? (or relationships (list)))) (when
(div :class "space-y-3" (not (empty? (or relationships (list))))
(map (lambda (rel) (div
:class "space-y-3"
(map
(lambda
(rel)
(let-match
{:cardinality cardinality :class_name class-name :loaded loaded :value value :name name}
rel
(~admin/data-relationship (~admin/data-relationship
:name (get rel "name") :name name
:cardinality (get rel "cardinality") :cardinality cardinality
:class-name (get rel "class_name") :class-name class-name
:loaded (get rel "loaded") :loaded loaded
:value (get rel "value"))) :value value)))
relationships))))) relationships)))))
(defcomp ~admin/data-table-content (&key tablename model-data) (defcomp
(if (not model-data) ~admin/data-table-content
(&key tablename model-data)
(if
(not model-data)
(div :class "px-4 py-8 text-stone-400" "No post data available.") (div :class "px-4 py-8 text-stone-400" "No post data available.")
(div :class "px-4 py-8" (div
(div :class "mb-6 text-sm text-neutral-500" :class "px-4 py-8"
"Model: " (code "Post") " \u2022 Table: " (code tablename)) (div
:class "mb-6 text-sm text-neutral-500"
"Model: "
(code "Post")
" • Table: "
(code tablename))
(let-match
{:relationships relationships :columns columns}
model-data
(~admin/data-model-content (~admin/data-model-content
:columns (get model-data "columns") :columns columns
:relationships (get model-data "relationships"))))) :relationships relationships)))))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Calendar month view for browsing/toggling entries (B1) ;; Calendar month view for browsing/toggling entries (B1)
@@ -518,59 +686,117 @@
:sx-on:afterSwap "document.body.dispatchEvent(new CustomEvent('entryToggled'))" :sx-on:afterSwap "document.body.dispatchEvent(new CustomEvent('entryToggled'))"
(span :class "truncate block" name))) (span :class "truncate block" name)))
(defcomp ~admin/calendar-view (&key cal-id year month-name (defcomp
current-url prev-month-url prev-year-url ~admin/calendar-view
next-month-url next-year-url (&key
weekday-names days csrf) cal-id
(let* ((target (str "#calendar-view-" cal-id))) year
(div :id (str "calendar-view-" cal-id) month-name
:sx-get current-url :sx-trigger "entryToggled from:body" :sx-swap "outerHTML" current-url
(header :class "flex items-center justify-center mb-4" prev-month-url
(nav :class "flex items-center gap-2 text-xl" prev-year-url
(a :class "px-2 py-1 hover:bg-stone-100 rounded" next-month-url
:sx-get prev-year-url :sx-target target :sx-swap "outerHTML" next-year-url
weekday-names
days
csrf)
(let*
((target (str "#calendar-view-" cal-id)))
(div
:id (str "calendar-view-" cal-id)
:sx-get current-url
:sx-trigger "entryToggled from:body"
:sx-swap "outerHTML"
(header
:class "flex items-center justify-center mb-4"
(nav
:class "flex items-center gap-2 text-xl"
(a
:class "px-2 py-1 hover:bg-stone-100 rounded"
:sx-get prev-year-url
:sx-target target
:sx-swap "outerHTML"
(raw! "&laquo;")) (raw! "&laquo;"))
(a :class "px-2 py-1 hover:bg-stone-100 rounded" (a
:sx-get prev-month-url :sx-target target :sx-swap "outerHTML" :class "px-2 py-1 hover:bg-stone-100 rounded"
:sx-get prev-month-url
:sx-target target
:sx-swap "outerHTML"
(raw! "&lsaquo;")) (raw! "&lsaquo;"))
(div :class "px-3 font-medium" (str month-name " " year)) (div :class "px-3 font-medium" (str month-name " " year))
(a :class "px-2 py-1 hover:bg-stone-100 rounded" (a
:sx-get next-month-url :sx-target target :sx-swap "outerHTML" :class "px-2 py-1 hover:bg-stone-100 rounded"
:sx-get next-month-url
:sx-target target
:sx-swap "outerHTML"
(raw! "&rsaquo;")) (raw! "&rsaquo;"))
(a :class "px-2 py-1 hover:bg-stone-100 rounded" (a
:sx-get next-year-url :sx-target target :sx-swap "outerHTML" :class "px-2 py-1 hover:bg-stone-100 rounded"
:sx-get next-year-url
:sx-target target
:sx-swap "outerHTML"
(raw! "&raquo;")))) (raw! "&raquo;"))))
(div :class "rounded border bg-white" (div
(div :class "hidden sm:grid grid-cols-7 text-center text-xs font-semibold text-stone-700 bg-stone-50 border-b" :class "rounded border bg-white"
(map (lambda (wd) (div :class "py-2" wd)) (or weekday-names (list)))) (div
(div :class "grid grid-cols-1 sm:grid-cols-7 gap-px bg-stone-200" :class "hidden sm:grid grid-cols-7 text-center text-xs font-semibold text-stone-700 bg-stone-50 border-b"
(map (lambda (day) (map
(let* ((extra-cls (if (get day "in_month") "" " bg-stone-50 text-stone-400")) (lambda (wd) (div :class "py-2" wd))
(entries (or (get day "entries") (list)))) (or weekday-names (list))))
(div :class (str "min-h-20 bg-white px-2 py-2 text-xs" extra-cls) (div
(div :class "font-medium mb-1" (str (get day "day"))) :class "grid grid-cols-1 sm:grid-cols-7 gap-px bg-stone-200"
(when (not (empty? entries)) (map
(div :class "space-y-0.5" (lambda
(map (lambda (e) (day)
(if (get e "is_associated") (let-match
{:entries entries* :in_month in-month :day day-num}
day
(let*
((extra-cls (if in-month "" " bg-stone-50 text-stone-400"))
(entries (or entries* (list))))
(div
:class (str "min-h-20 bg-white px-2 py-2 text-xs" extra-cls)
(div :class "font-medium mb-1" (str day-num))
(when
(not (empty? entries))
(div
:class "space-y-0.5"
(map
(lambda
(e)
(let-match
{:is_associated is-associated :toggle_url toggle-url :name name}
e
(if
is-associated
(~admin/cal-entry-associated (~admin/cal-entry-associated
:name (get e "name") :toggle-url (get e "toggle_url") :csrf csrf) :name name
:toggle-url toggle-url
:csrf csrf)
(~admin/cal-entry-unassociated (~admin/cal-entry-unassociated
:name (get e "name") :toggle-url (get e "toggle_url") :csrf csrf))) :name name
entries)))))) :toggle-url toggle-url
:csrf csrf))))
entries)))))))
(or days (list)))))))) (or days (list))))))))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Nav entries OOB — renders associated entry/calendar items in scroll wrapper (B2) ;; Nav entries OOB — renders associated entry/calendar items in scroll wrapper (B2)
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defcomp ~admin/nav-entries-oob (&key entries calendars) (defcomp
(let* ((entry-list (or entries (list))) ~admin/nav-entries-oob
(&key entries calendars)
(let*
((entry-list (or entries (list)))
(cal-list (or calendars (list))) (cal-list (or calendars (list)))
(has-items (or (not (empty? entry-list)) (not (empty? cal-list)))) (has-items (or (not (empty? entry-list)) (not (empty? cal-list))))
(nav-cls "justify-center cursor-pointer flex flex-row items-center gap-2 rounded bg-stone-200 text-black [.hover-capable_&]:hover:bg-yellow-300 aria-selected:bg-stone-500 aria-selected:text-white [.hover-capable_&[aria-selected=true]:hover]:bg-orange-500 p-2") (nav-cls
(scroll-hs "on load or scroll if window.innerWidth >= 640 and my.scrollWidth > my.clientWidth remove .hidden from .entries-nav-arrow add .flex to .entries-nav-arrow else add .hidden to .entries-nav-arrow remove .flex from .entries-nav-arrow end")) "justify-center cursor-pointer flex flex-row items-center gap-2 rounded bg-stone-200 text-black [.hover-capable_&]:hover:bg-yellow-300 aria-selected:bg-stone-500 aria-selected:text-white [.hover-capable_&[aria-selected=true]:hover]:bg-orange-500 p-2")
(if (not has-items) (scroll-hs
"on load or scroll if window.innerWidth >= 640 and my.scrollWidth > my.clientWidth remove .hidden from .entries-nav-arrow add .flex to .entries-nav-arrow else add .hidden to .entries-nav-arrow remove .flex from .entries-nav-arrow end"))
(if
(not has-items)
(~shared:nav/blog-nav-entries-empty) (~shared:nav/blog-nav-entries-empty)
(~shared:misc/scroll-nav-wrapper (~shared:misc/scroll-nav-wrapper
:wrapper-id "entries-calendars-nav-wrapper" :wrapper-id "entries-calendars-nav-wrapper"
@@ -580,14 +806,27 @@
:scroll-hs scroll-hs :scroll-hs scroll-hs
:right-hs "on click set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft + 200" :right-hs "on click set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft + 200"
:items (<> :items (<>
(map (lambda (e) (map
(lambda
(e)
(let-match
{:href href :date_str date-str :name name}
e
(~shared:navigation/calendar-entry-nav (~shared:navigation/calendar-entry-nav
:href (get e "href") :nav-class nav-cls :href href
:name (get e "name") :date-str (get e "date_str"))) :nav-class nav-cls
:name name
:date-str date-str)))
entry-list) entry-list)
(map (lambda (c) (map
(lambda
(c)
(let-match
{:href href :name name}
c
(~shared:nav/blog-nav-calendar-item (~shared:nav/blog-nav-calendar-item
:href (get c "href") :nav-cls nav-cls :href href
:name (get c "name"))) :nav-cls nav-cls
:name name)))
cal-list)) cal-list))
:oob true)))) :oob true))))

View File

@@ -159,91 +159,147 @@
:btn (~page/tw-plus)))))) :btn (~page/tw-plus))))))
;; Entry card (list view) from data ;; Entry card (list view) from data
(defcomp ~entries/entry-card-from-data (&key entry-href name day-href (defcomp
page-badge-href page-badge-title cal-name ~entries/entry-card-from-data
date-str start-time end-time is-page-scoped (&key
cost has-ticket ticket-data) entry-href
name
day-href
page-badge-href
page-badge-title
cal-name
date-str
start-time
end-time
is-page-scoped
cost
has-ticket
ticket-data)
(~entries/entry-card (~entries/entry-card
:title (if entry-href :title (if
entry-href
(~entries/entry-title-linked :href entry-href :name name) (~entries/entry-title-linked :href entry-href :name name)
(~entries/entry-title-plain :name name)) (~entries/entry-title-plain :name name))
:badges (<> :badges (<>
(when page-badge-title (when
(~entries/entry-page-badge :href page-badge-href :title page-badge-title)) page-badge-title
(when cal-name (~entries/entry-page-badge
(~entries/entry-cal-badge :name cal-name))) :href page-badge-href
:title page-badge-title))
(when cal-name (~entries/entry-cal-badge :name cal-name)))
:time-parts (<> :time-parts (<>
(when (and day-href (not is-page-scoped)) (when
(and day-href (not is-page-scoped))
(~entries/entry-time-linked :href day-href :date-str date-str)) (~entries/entry-time-linked :href day-href :date-str date-str))
(when (and (not day-href) (not is-page-scoped) date-str) (when
(and (not day-href) (not is-page-scoped) date-str)
(~entries/entry-time-plain :date-str date-str)) (~entries/entry-time-plain :date-str date-str))
start-time start-time
(when end-time (str " \u2013 " end-time))) (when end-time (str " " end-time)))
:cost (when cost (~entries/entry-cost :cost cost)) :cost (when cost (~entries/entry-cost :cost cost))
:widget (when has-ticket :widget (when
has-ticket
(let-match
{:csrf csrf :entry-id entry-id :qty qty :price price :ticket-url ticket-url}
ticket-data
(~entries/entry-widget-wrapper (~entries/entry-widget-wrapper
:widget (~entries/tw-widget-from-data :widget (~entries/tw-widget-from-data
:entry-id (get ticket-data "entry-id") :entry-id entry-id
:price (get ticket-data "price") :price price
:qty (get ticket-data "qty") :qty qty
:ticket-url (get ticket-data "ticket-url") :ticket-url ticket-url
:csrf (get ticket-data "csrf")))))) :csrf csrf))))))
;; Entry card (tile view) from data ;; Entry card (tile view) from data
(defcomp ~entries/entry-card-tile-from-data (&key entry-href name day-href (defcomp
page-badge-href page-badge-title cal-name ~entries/entry-card-tile-from-data
date-str time-str (&key
cost has-ticket ticket-data) entry-href
name
day-href
page-badge-href
page-badge-title
cal-name
date-str
time-str
cost
has-ticket
ticket-data)
(~entries/entry-card-tile (~entries/entry-card-tile
:title (if entry-href :title (if
entry-href
(~entries/entry-title-tile-linked :href entry-href :name name) (~entries/entry-title-tile-linked :href entry-href :name name)
(~entries/entry-title-tile-plain :name name)) (~entries/entry-title-tile-plain :name name))
:badges (<> :badges (<>
(when page-badge-title (when
(~entries/entry-page-badge :href page-badge-href :title page-badge-title)) page-badge-title
(when cal-name (~entries/entry-page-badge
(~entries/entry-cal-badge :name cal-name))) :href page-badge-href
:title page-badge-title))
(when cal-name (~entries/entry-cal-badge :name cal-name)))
:time time-str :time time-str
:cost (when cost (~entries/entry-cost :cost cost)) :cost (when cost (~entries/entry-cost :cost cost))
:widget (when has-ticket :widget (when
has-ticket
(let-match
{:csrf csrf :entry-id entry-id :qty qty :price price :ticket-url ticket-url}
ticket-data
(~entries/entry-tile-widget-wrapper (~entries/entry-tile-widget-wrapper
:widget (~entries/tw-widget-from-data :widget (~entries/tw-widget-from-data
:entry-id (get ticket-data "entry-id") :entry-id entry-id
:price (get ticket-data "price") :price price
:qty (get ticket-data "qty") :qty qty
:ticket-url (get ticket-data "ticket-url") :ticket-url ticket-url
:csrf (get ticket-data "csrf")))))) :csrf csrf))))))
;; Entry cards list (with date separators + sentinel) from data ;; Entry cards list (with date separators + sentinel) from data
(defcomp ~entries/entry-cards-from-data (&key items view page has-more next-url) (defcomp
~entries/entry-cards-from-data
(&key items view page has-more next-url)
(<> (<>
(map (lambda (item) (map
(if (get item "is-separator") (lambda
(~entries/date-separator :date-str (get item "date-str")) (item)
(if (= view "tile") (let-match
{:date-str date-str :time-str time-str :has-ticket has-ticket :is-separator is-separator :ticket-data ticket-data :day-href day-href :page-badge-title page-badge-title :entry-href entry-href :start-time start-time :end-time end-time :is-page-scoped is-page-scoped :page-badge-href page-badge-href :cal-name cal-name :cost cost :name name}
item
(if
is-separator
(~entries/date-separator :date-str date-str)
(if
(= view "tile")
(~entries/entry-card-tile-from-data (~entries/entry-card-tile-from-data
:entry-href (get item "entry-href") :name (get item "name") :entry-href entry-href
:day-href (get item "day-href") :name name
:page-badge-href (get item "page-badge-href") :day-href day-href
:page-badge-title (get item "page-badge-title") :page-badge-href page-badge-href
:cal-name (get item "cal-name") :page-badge-title page-badge-title
:date-str (get item "date-str") :time-str (get item "time-str") :cal-name cal-name
:cost (get item "cost") :has-ticket (get item "has-ticket") :date-str date-str
:ticket-data (get item "ticket-data")) :time-str time-str
:cost cost
:has-ticket has-ticket
:ticket-data ticket-data)
(~entries/entry-card-from-data (~entries/entry-card-from-data
:entry-href (get item "entry-href") :name (get item "name") :entry-href entry-href
:day-href (get item "day-href") :name name
:page-badge-href (get item "page-badge-href") :day-href day-href
:page-badge-title (get item "page-badge-title") :page-badge-href page-badge-href
:cal-name (get item "cal-name") :page-badge-title page-badge-title
:date-str (get item "date-str") :cal-name cal-name
:start-time (get item "start-time") :end-time (get item "end-time") :date-str date-str
:is-page-scoped (get item "is-page-scoped") :start-time start-time
:cost (get item "cost") :has-ticket (get item "has-ticket") :end-time end-time
:ticket-data (get item "ticket-data"))))) :is-page-scoped is-page-scoped
:cost cost
:has-ticket has-ticket
:ticket-data ticket-data)))))
(or items (list))) (or items (list)))
(when has-more (when
(~shared:misc/sentinel-simple :id (str "sentinel-" page) :next-url next-url)))) has-more
(~shared:misc/sentinel-simple
:id (str "sentinel-" page)
:next-url next-url))))
;; Events main panel (toggle + cards grid) from data ;; Events main panel (toggle + cards grid) from data
(defcomp ~entries/main-panel-from-data (&key toggle items view page has-more next-url) (defcomp ~entries/main-panel-from-data (&key toggle items view page has-more next-url)

View File

@@ -323,27 +323,42 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Day checkboxes from data — replaces Python loop ;; Day checkboxes from data — replaces Python loop
(defcomp ~forms/day-checkboxes-from-data (&key days-data all-checked) (defcomp
~forms/day-checkboxes-from-data
(&key days-data all-checked)
(<> (<>
(~forms/day-all-checkbox :checked (when all-checked "checked")) (~forms/day-all-checkbox :checked (when all-checked "checked"))
(map (lambda (d) (map
(lambda
(d)
(let-match
{:checked checked :label label :name name}
d
(~forms/day-checkbox (~forms/day-checkbox
:name (get d "name") :name name
:label (get d "label") :label label
:checked (when (get d "checked") "checked"))) :checked (when checked "checked"))))
(or days-data (list))))) (or days-data (list)))))
;; Slot options from data — replaces _slot_options_html Python loop ;; Slot options from data — replaces _slot_options_html Python loop
(defcomp ~forms/slot-options-from-data (&key slots) (defcomp
(<> (map (lambda (s) ~forms/slot-options-from-data
(&key slots)
(<>
(map
(lambda
(s)
(let-match
{:data-end data-end :data-flexible data-flexible :selected selected :value value :data-cost data-cost :label label :data-start data-start}
s
(~forms/slot-option (~forms/slot-option
:value (get s "value") :value value
:data-start (get s "data-start") :data-start data-start
:data-end (get s "data-end") :data-end data-end
:data-flexible (get s "data-flexible") :data-flexible data-flexible
:data-cost (get s "data-cost") :data-cost data-cost
:selected (get s "selected") :selected selected
:label (get s "label"))) :label label)))
(or slots (list))))) (or slots (list)))))
;; Slot picker from data — wraps picker + options ;; Slot picker from data — wraps picker + options

View File

@@ -5,155 +5,247 @@
;; Auto-fetching header macros — calendar, day, entry, slot, tickets ;; Auto-fetching header macros — calendar, day, entry, slot, tickets
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defmacro ~events-calendar-header-auto (oob) (defmacro
~events-calendar-header-auto
(oob)
"Calendar header row using (events-calendar-ctx)." "Calendar header row using (events-calendar-ctx)."
(quasiquote (quasiquote
(let ((__cal (events-calendar-ctx)) (let
(__sc (select-colours))) ((__cal (events-calendar-ctx)) (__sc (select-colours)))
(when (get __cal "slug") (let-match
(~shared:layout/menu-row-sx :id "calendar-row" :level 3 {:description description :slug slug :name name}
:link-href (url-for "calendar.get" __cal
:calendar-slug (get __cal "slug")) (when
:link-label-content (~header/calendar-label slug
:name (get __cal "name") (~shared:layout/menu-row-sx
:description (get __cal "description")) :id "calendar-row"
:level 3
:link-href (url-for "calendar.get" :calendar-slug slug)
:link-label-content (~header/calendar-label :name name :description description)
:nav (<> :nav (<>
(~shared:layout/nav-link :href (url-for "defpage_slots_listing" (~shared:layout/nav-link
:calendar-slug (get __cal "slug")) :href (url-for "defpage_slots_listing" :calendar-slug slug)
:icon "fa fa-clock" :label "Slots" :icon "fa fa-clock"
:label "Slots"
:select-colours __sc) :select-colours __sc)
(let ((__rights (app-rights))) (let
(when (get __rights "admin") ((__rights (app-rights)))
(~shared:layout/nav-link :href (url-for "defpage_calendar_admin" (when
:calendar-slug (get __cal "slug")) (get __rights "admin")
(~shared:layout/nav-link
:href (url-for "defpage_calendar_admin" :calendar-slug slug)
:icon "fa fa-cog" :icon "fa fa-cog"
:select-colours __sc)))) :select-colours __sc))))
:child-id "calendar-header-child" :child-id "calendar-header-child"
:oob (unquote oob)))))) :oob (unquote oob)))))))
(defmacro ~events-calendar-admin-header-auto (oob) (defmacro
~events-calendar-admin-header-auto
(oob)
"Calendar admin header row." "Calendar admin header row."
(quasiquote (quasiquote
(let ((__cal (events-calendar-ctx)) (let
(__sc (select-colours))) ((__cal (events-calendar-ctx)) (__sc (select-colours)))
(when (get __cal "slug") (let-match
(~shared:layout/menu-row-sx :id "calendar-admin-row" :level 4 {:slug slug}
:link-label "admin" :icon "fa fa-cog" __cal
(when
slug
(~shared:layout/menu-row-sx
:id "calendar-admin-row"
:level 4
:link-label "admin"
:icon "fa fa-cog"
:nav (<> :nav (<>
(~shared:layout/nav-link :href (url-for "defpage_slots_listing" (~shared:layout/nav-link
:calendar-slug (get __cal "slug")) :href (url-for "defpage_slots_listing" :calendar-slug slug)
:label "slots" :select-colours __sc) :label "slots"
(~shared:layout/nav-link :href (url-for "calendar.admin.calendar_description_edit" :select-colours __sc)
:calendar-slug (get __cal "slug")) (~shared:layout/nav-link
:label "description" :select-colours __sc)) :href (url-for
"calendar.admin.calendar_description_edit"
:calendar-slug slug)
:label "description"
:select-colours __sc))
:child-id "calendar-admin-header-child" :child-id "calendar-admin-header-child"
:oob (unquote oob)))))) :oob (unquote oob)))))))
(defmacro ~events-day-header-auto (oob) (defmacro
~events-day-header-auto
(oob)
"Day header row using (events-day-ctx)." "Day header row using (events-day-ctx)."
(quasiquote (quasiquote
(let ((__day (events-day-ctx)) (let
(__cal (events-calendar-ctx))) ((__day (events-day-ctx)) (__cal (events-calendar-ctx)))
(when (get __day "date-str") (let-match
(~shared:layout/menu-row-sx :id "day-row" :level 4 {:date-str date-str :nav nav :year year :day day :month month}
:link-href (url-for "calendar.day.show_day" __day
:calendar-slug (get __cal "slug") (when
:year (get __day "year") date-str
:month (get __day "month") (let-match
:day (get __day "day")) {:slug cal-slug}
:link-label-content (~header/day-label __cal
:date-str (get __day "date-str")) (~shared:layout/menu-row-sx
:nav (get __day "nav") :id "day-row"
:level 4
:link-href (url-for
"calendar.day.show_day"
:calendar-slug cal-slug
:year year
:month month
:day day)
:link-label-content (~header/day-label :date-str date-str)
:nav nav
:child-id "day-header-child" :child-id "day-header-child"
:oob (unquote oob)))))) :oob (unquote oob))))))))
(defmacro ~events-day-admin-header-auto (oob) (defmacro
~events-day-admin-header-auto
(oob)
"Day admin header row." "Day admin header row."
(quasiquote (quasiquote
(let ((__day (events-day-ctx)) (let
(__cal (events-calendar-ctx))) ((__day (events-day-ctx)) (__cal (events-calendar-ctx)))
(when (get __day "date-str") (let-match
(~shared:layout/menu-row-sx :id "day-admin-row" :level 5 {:date-str date-str :year year :day day :month month}
:link-href (url-for "defpage_day_admin" __day
:calendar-slug (get __cal "slug") (when
:year (get __day "year") date-str
:month (get __day "month") (let-match
:day (get __day "day")) {:slug cal-slug}
:link-label "admin" :icon "fa fa-cog" __cal
(~shared:layout/menu-row-sx
:id "day-admin-row"
:level 5
:link-href (url-for
"defpage_day_admin"
:calendar-slug cal-slug
:year year
:month month
:day day)
:link-label "admin"
:icon "fa fa-cog"
:child-id "day-admin-header-child" :child-id "day-admin-header-child"
:oob (unquote oob)))))) :oob (unquote oob))))))))
(defmacro ~events-entry-header-auto (oob) (defmacro
~events-entry-header-auto
(oob)
"Entry header row using (events-entry-ctx)." "Entry header row using (events-entry-ctx)."
(quasiquote (quasiquote
(let ((__ectx (events-entry-ctx))) (let
(when (get __ectx "id") ((__ectx (events-entry-ctx)))
(~shared:layout/menu-row-sx :id "entry-row" :level 5 (let-match
:link-href (get __ectx "link-href") {:time-str time-str :nav nav :link-href link-href :id id :name name}
__ectx
(when
id
(~shared:layout/menu-row-sx
:id "entry-row"
:level 5
:link-href link-href
:link-label-content (~header/entry-label :link-label-content (~header/entry-label
:entry-id (get __ectx "id") :entry-id id
:title (~admin/entry-title :name (get __ectx "name")) :title (~admin/entry-title :name name)
:times (~admin/entry-times :time-str (get __ectx "time-str"))) :times (~admin/entry-times :time-str time-str))
:nav (get __ectx "nav") :nav nav
:child-id "entry-header-child" :child-id "entry-header-child"
:oob (unquote oob)))))) :oob (unquote oob)))))))
(defmacro ~events-entry-admin-header-auto (oob) (defmacro
~events-entry-admin-header-auto
(oob)
"Entry admin header row." "Entry admin header row."
(quasiquote (quasiquote
(let ((__ectx (events-entry-ctx))) (let
(when (get __ectx "id") ((__ectx (events-entry-ctx)))
(~shared:layout/menu-row-sx :id "entry-admin-row" :level 6 (let-match
:link-href (get __ectx "admin-href") {:admin-href admin-href :is-admin is-admin :ticket-types-href ticket-types-href :select-colours select-colours :id id}
:link-label "admin" :icon "fa fa-cog" __ectx
:nav (when (get __ectx "is-admin") (when
(~shared:layout/nav-link :href (get __ectx "ticket-types-href") id
(~shared:layout/menu-row-sx
:id "entry-admin-row"
:level 6
:link-href admin-href
:link-label "admin"
:icon "fa fa-cog"
:nav (when
is-admin
(~shared:layout/nav-link
:href ticket-types-href
:label "ticket_types" :label "ticket_types"
:select-colours (get __ectx "select-colours"))) :select-colours select-colours))
:child-id "entry-admin-header-child" :child-id "entry-admin-header-child"
:oob (unquote oob)))))) :oob (unquote oob)))))))
(defmacro ~events-slot-header-auto (oob) (defmacro
~events-slot-header-auto
(oob)
"Slot detail header row using (events-slot-ctx)." "Slot detail header row using (events-slot-ctx)."
(quasiquote (quasiquote
(let ((__slot (events-slot-ctx))) (let
(when (get __slot "name") ((__slot (events-slot-ctx)))
(~shared:layout/menu-row-sx :id "slot-row" :level 5 (let-match
:link-label-content (~header/slot-label {:description description :name name}
:name (get __slot "name") __slot
:description (get __slot "description")) (when
name
(~shared:layout/menu-row-sx
:id "slot-row"
:level 5
:link-label-content (~header/slot-label :name name :description description)
:child-id "slot-header-child" :child-id "slot-header-child"
:oob (unquote oob)))))) :oob (unquote oob)))))))
(defmacro ~events-ticket-types-header-auto (oob) (defmacro
~events-ticket-types-header-auto
(oob)
"Ticket types header row." "Ticket types header row."
(quasiquote (quasiquote
(let ((__ectx (events-entry-ctx)) (let
(__cal (events-calendar-ctx))) ((__ectx (events-entry-ctx)) (__cal (events-calendar-ctx)))
(when (get __ectx "id") (let-match
(~shared:layout/menu-row-sx :id "ticket_types-row" :level 7 {:ticket-types-href ticket-types-href :id id}
:link-href (get __ectx "ticket-types-href") __ectx
(when
id
(~shared:layout/menu-row-sx
:id "ticket_types-row"
:level 7
:link-href ticket-types-href
:link-label-content (<> :link-label-content (<>
(i :class "fa fa-ticket") (i :class "fa fa-ticket")
(div :class "shrink-0" "ticket types")) (div :class "shrink-0" "ticket types"))
:nav (~forms/admin-placeholder-nav) :nav (~forms/admin-placeholder-nav)
:child-id "ticket_type-header-child" :child-id "ticket_type-header-child"
:oob (unquote oob)))))) :oob (unquote oob)))))))
(defmacro ~events-ticket-type-header-auto (oob) (defmacro
~events-ticket-type-header-auto
(oob)
"Single ticket type header row using (events-ticket-type-ctx)." "Single ticket type header row using (events-ticket-type-ctx)."
(quasiquote (quasiquote
(let ((__tt (events-ticket-type-ctx))) (let
(when (get __tt "id") ((__tt (events-ticket-type-ctx)))
(~shared:layout/menu-row-sx :id "ticket_type-row" :level 8 (let-match
:link-href (get __tt "link-href") {:link-href link-href :id id :name name}
:link-label-content (div :class "flex flex-col md:flex-row md:gap-2 items-center" __tt
(div :class "flex flex-row items-center gap-2" (when
id
(~shared:layout/menu-row-sx
:id "ticket_type-row"
:level 8
:link-href link-href
:link-label-content (div
:class "flex flex-col md:flex-row md:gap-2 items-baseline"
(div
:class "flex flex-row items-center gap-2"
(i :class "fa fa-ticket") (i :class "fa fa-ticket")
(div :class "shrink-0" (get __tt "name")))) (div :class "shrink-0" name)))
:nav (~forms/admin-placeholder-nav) :nav (~forms/admin-placeholder-nav)
:child-id "ticket_type-header-child-inner" :child-id "ticket_type-header-child-inner"
:oob (unquote oob)))))) :oob (unquote oob)))))))
(defmacro ~events-markets-header-auto (oob) (defmacro ~events-markets-header-auto (oob)
"Markets section header row." "Markets section header row."

View File

@@ -98,24 +98,47 @@
(~page/slot-description-oob :description (or description ""))))) (~page/slot-description-oob :description (or description "")))))
;; Slots table from data ;; Slots table from data
(defcomp ~page/slots-table-from-data (&key list-container slots pre-action add-url (defcomp
tr-cls pill-cls action-btn hx-select csrf-hdr) ~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 (~page/slots-table
:list-container list-container :list-container list-container
:rows (if (empty? (or slots (list))) :rows (if
(empty? (or slots (list)))
(~page/slots-empty-row) (~page/slots-empty-row)
(<> (map (lambda (s) (<>
(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 (~page/slots-row
:tr-cls tr-cls :slot-href (get s "slot-href") :tr-cls tr-cls
:pill-cls pill-cls :hx-select hx-select :slot-href slot-href
:slot-name (get s "slot-name") :description (get s "description") :pill-cls pill-cls
:flexible (get s "flexible") :hx-select hx-select
:days (~page/days-pills-from-data :days (get s "days")) :slot-name slot-name
:time-str (get s "time-str") :description description
:cost-str (get s "cost-str") :action-btn action-btn :flexible flexible
:del-url (get s "del-url") :csrf-hdr csrf-hdr)) :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))))) (or slots (list)))))
:pre-action pre-action :add-url add-url)) :pre-action pre-action
:add-url add-url))
(defcomp ~page/ticket-type-col (&key label value) (defcomp ~page/ticket-type-col (&key label value)
(div :class "flex flex-col" (div :class "flex flex-col"
@@ -203,47 +226,87 @@
:onclick hide-js "Cancel")))) :onclick hide-js "Cancel"))))
;; Data-driven buy form — Python passes pre-resolved data, .sx does layout + iteration ;; 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 (defcomp
ticket-types user-ticket-counts-by-type ~page/buy-form
user-ticket-count price-str adjust-url csrf state (&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) my-tickets-href)
(if (!= state "confirmed") (if
(!= state "confirmed")
(~page/buy-not-confirmed :entry-id (str entry-id)) (~page/buy-not-confirmed :entry-id (str entry-id))
(let ((eid-s (str entry-id)) (let
(target (str "#ticket-buy-" entry-id))) ((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" (div
(h3 :class "text-sm font-semibold text-stone-700 mb-3" :id (str "ticket-buy-" entry-id)
(i :class "fa fa-ticket mr-1" :aria-hidden "true") "Tickets") :class "rounded-xl border border-stone-200 bg-white p-4"
;; Info bar (h3
(when (or info-sold info-remaining info-basket) :class "text-sm font-semibold text-stone-700 mb-3"
(div :class "flex items-center gap-3 mb-3 text-xs text-stone-500" (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-sold (span (str info-sold " sold")))
(when info-remaining (span (str info-remaining " remaining"))) (when info-remaining (span (str info-remaining " remaining")))
(when info-basket (when
(span :class "text-emerald-600 font-medium" info-basket
(i :class "fa fa-shopping-cart text-[0.6rem]" :aria-hidden "true") (span
:class "text-emerald-600 font-medium"
(i
:class "fa fa-shopping-cart text-[0.6rem]"
:aria-hidden "true")
(str " " info-basket " in basket"))))) (str " " info-basket " in basket")))))
;; Body — multi-type or default (if
(if (and ticket-types (not (empty? ticket-types))) (and ticket-types (not (empty? ticket-types)))
(div :class "space-y-2" (div
(map (fn (tt) :class "space-y-2"
(let ((tt-count (if user-ticket-counts-by-type (map
(get user-ticket-counts-by-type (str (get tt "id")) 0) (fn
0)) (tt)
(tt-id (get tt "id"))) (let-match
(div :class "flex items-center justify-between p-3 rounded-lg bg-stone-50 border border-stone-100" {:cost_str cost-str :id tt-id :name tt-name}
(div (div :class "font-medium text-sm" (get tt "name")) tt
(div :class "text-xs text-stone-500" (get tt "cost_str"))) (let
(~page/adjust-inline :csrf csrf :adjust-url adjust-url :target target ((tt-count (if user-ticket-counts-by-type (get user-ticket-counts-by-type (str tt-id) 0) 0)))
:entry-id eid-s :count tt-count :ticket-type-id tt-id (div
:my-tickets-href my-tickets-href)))) :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)) ticket-types))
(<> (div :class "flex items-center justify-between mb-4" (<>
(div (span :class "font-medium text-green-600" price-str) (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"))) (span :class "text-sm text-stone-500 ml-2" "per ticket")))
(~page/adjust-inline :csrf csrf :adjust-url adjust-url :target target (~page/adjust-inline
:entry-id eid-s :count (if user-ticket-count user-ticket-count 0) :csrf csrf
:ticket-type-id nil :my-tickets-href my-tickets-href))))))) :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) ;; 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) (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.")) "Tickets available once this event is confirmed."))
(defcomp ~page/buy-result (&key entry-id tickets remaining my-tickets-href) (defcomp
(let ((count (len tickets)) ~page/buy-result
(suffix (if (= count 1) "" "s"))) (&key entry-id tickets remaining my-tickets-href)
(div :id (str "ticket-buy-" entry-id) :class "rounded-xl border border-emerald-200 bg-emerald-50 p-4" (let
(div :class "flex items-center gap-2 mb-3" ((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") (i :class "fa fa-check-circle text-emerald-600" :aria-hidden "true")
(span :class "font-semibold text-emerald-800" (str count " ticket" suffix " reserved"))) (span
(div :class "space-y-2 mb-4" :class "font-semibold text-emerald-800"
(map (fn (t) (str count " ticket" suffix " reserved")))
(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
(div :class "flex items-center gap-2" :class "space-y-2 mb-4"
(i :class "fa fa-ticket text-emerald-500" :aria-hidden "true") (map
(span :class "font-mono text-xs text-stone-500" (get t "code_short"))) (fn
(span :class "text-xs text-emerald-600 font-medium" "View ticket"))) (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)) tickets))
(when (not (nil? remaining)) (when
(let ((r-suffix (if (= remaining 1) "" "s"))) (not (nil? remaining))
(p :class "text-xs text-stone-500" (str remaining " ticket" r-suffix " remaining")))) (let
(div :class "mt-3 flex gap-2" ((r-suffix (if (= remaining 1) "" "s")))
(a :href my-tickets-href :class "text-sm text-emerald-700 hover:text-emerald-900 underline" (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"))))) "View all my tickets")))))
;; Single response wrappers for POST routes (include OOB cart icon) ;; Single response wrappers for POST routes (include OOB cart icon)
@@ -477,26 +567,45 @@
(~page/post-img-placeholder))) (~page/post-img-placeholder)))
;; Entry posts nav OOB from data ;; Entry posts nav OOB from data
(defcomp ~page/entry-posts-nav-oob-from-data (&key nav-btn posts) (defcomp
(if (empty? (or posts (list))) ~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-empty)
(~page/entry-posts-nav-oob (~page/entry-posts-nav-oob
:items (<> (map (lambda (p) :items (<>
(map
(lambda
(p)
(let-match
{:href href :title title :img img}
p
(~page/entry-nav-post (~page/entry-nav-post
:href (get p "href") :nav-btn nav-btn :href href
:img (~page/post-img-from-data :src (get p "img") :alt (get p "title")) :nav-btn nav-btn
:title (get p "title"))) :img (~page/post-img-from-data :src img :alt title)
:title title)))
posts))))) posts)))))
;; Entry posts nav (non-OOB) from data — for desktop nav embedding ;; Entry posts nav (non-OOB) from data — for desktop nav embedding
(defcomp ~page/entry-posts-nav-inner-from-data (&key posts) (defcomp
(when (not (empty? (or posts (list)))) ~page/entry-posts-nav-inner-from-data
(&key posts)
(when
(not (empty? (or posts (list))))
(~page/entry-posts-nav-oob (~page/entry-posts-nav-oob
:items (<> (map (lambda (p) :items (<>
(map
(lambda
(p)
(let-match
{:href href :title title :img img}
p
(~page/entry-nav-post-link (~page/entry-nav-post-link
:href (get p "href") :href href
:img (~page/post-img-from-data :src (get p "img") :alt (get p "title")) :img (~page/post-img-from-data :src img :alt title)
:title (get p "title"))) :title title)))
posts))))) posts)))))
;; Post nav entries+calendars OOB from data ;; Post nav entries+calendars OOB from data
@@ -602,14 +711,23 @@
(~shared:layout/nav-link :href admin-href :icon "fa fa-cog")))) (~shared:layout/nav-link :href admin-href :icon "fa fa-cog"))))
;; Post search results from data ;; 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) (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 (~forms/post-search-item
:post-url (get item "post-url") :entry-id (get item "entry-id") :post-url post-url
:csrf (get item "csrf") :post-id (get item "post-id") :entry-id entry-id
:img (~page/post-img-from-data :src (get item "img") :alt (get item "title")) :csrf csrf
:title (get item "title"))) :post-id post-id
:img (~page/post-img-from-data :src img :alt title)
:title title)))
(or items (list))) (or items (list)))
(cond (cond
(has-more (~forms/post-search-sentinel :page page :next-url next-url)) (has-more (~forms/post-search-sentinel :page page :next-url next-url))
@@ -617,16 +735,26 @@
(true "")))) (true ""))))
;; Entry options from data — state-driven button composition ;; 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 (~admin/entry-options
:entry-id entry-id :entry-id entry-id
:buttons (<> (map (lambda (b) :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 (~admin/entry-option-button
:url (get b "url") :target (str "#calendar_entry_options_" entry-id) :url url
:csrf (get b "csrf") :btn-type (get b "btn-type") :target (str "#calendar_entry_options_" entry-id)
:action-btn (get b "action-btn") :csrf csrf
:confirm-title (get b "confirm-title") :btn-type btn-type
:confirm-text (get b "confirm-text") :action-btn action-btn
:label (get b "label") :confirm-title confirm-title
:is-btn (get b "is-btn"))) :confirm-text confirm-text
:label label
:is-btn is-btn)))
(or buttons (list)))))) (or buttons (list))))))

View File

@@ -211,17 +211,27 @@
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; My tickets panel from data ;; My tickets panel from data
(defcomp ~tickets/panel-from-data (&key (list-container :as string) (tickets :as list?)) (defcomp
~tickets/panel-from-data
(&key (list-container :as string) (tickets :as list?))
(~tickets/panel (~tickets/panel
:list-container list-container :list-container list-container
:has-tickets (not (empty? (or tickets (list)))) :has-tickets (not (empty? (or tickets (list))))
:cards (<> (map (lambda (t) :cards (<>
(map
(lambda
(t)
(let-match
{:time-str time-str :href href :type-name type-name :code-prefix code-prefix :entry-name entry-name :cal-name cal-name :state state}
t
(~tickets/card (~tickets/card
:href (get t "href") :entry-name (get t "entry-name") :href href
:type-name (get t "type-name") :time-str (get t "time-str") :entry-name entry-name
:cal-name (get t "cal-name") :type-name type-name
:badge (~entries/ticket-state-badge :state (get t "state")) :time-str time-str
:code-prefix (get t "code-prefix"))) :cal-name cal-name
:badge (~entries/ticket-state-badge :state state)
:code-prefix code-prefix)))
(or tickets (list)))))) (or tickets (list))))))
;; Ticket detail from data — uses lg badge variant ;; Ticket detail from data — uses lg badge variant
@@ -256,53 +266,105 @@
(true nil)))) (true nil))))
;; Ticket admin panel from data ;; Ticket admin panel from data
(defcomp ~tickets/admin-panel-from-data (&key (list-container :as string) (lookup-url :as string) (tickets :as list?) (defcomp
(total :as number?) (confirmed :as number?) (checked-in :as number?) (reserved :as number?)) ~tickets/admin-panel-from-data
(&key
(list-container :as string)
(lookup-url :as string)
(tickets :as list?)
(total :as number?)
(confirmed :as number?)
(checked-in :as number?)
(reserved :as number?))
(~tickets/admin-panel (~tickets/admin-panel
:list-container list-container :list-container list-container
:stats (<> :stats (<>
(~tickets/admin-stat :border "border-stone-200" :bg "" (~tickets/admin-stat
:text-cls "text-stone-900" :label-cls "text-stone-500" :border "border-stone-200"
:value (str (or total 0)) :label "Total") :bg ""
(~tickets/admin-stat :border "border-emerald-200" :bg "bg-emerald-50" :text-cls "text-stone-900"
:text-cls "text-emerald-700" :label-cls "text-emerald-600" :label-cls "text-stone-500"
:value (str (or confirmed 0)) :label "Confirmed") :value (str (or total 0))
(~tickets/admin-stat :border "border-blue-200" :bg "bg-blue-50" :label "Total")
:text-cls "text-blue-700" :label-cls "text-blue-600" (~tickets/admin-stat
:value (str (or checked-in 0)) :label "Checked In") :border "border-emerald-200"
(~tickets/admin-stat :border "border-amber-200" :bg "bg-amber-50" :bg "bg-emerald-50"
:text-cls "text-amber-700" :label-cls "text-amber-600" :text-cls "text-emerald-700"
:value (str (or reserved 0)) :label "Reserved")) :label-cls "text-emerald-600"
:value (str (or confirmed 0))
:label "Confirmed")
(~tickets/admin-stat
:border "border-blue-200"
:bg "bg-blue-50"
:text-cls "text-blue-700"
:label-cls "text-blue-600"
:value (str (or checked-in 0))
:label "Checked In")
(~tickets/admin-stat
:border "border-amber-200"
:bg "bg-amber-50"
:text-cls "text-amber-700"
:label-cls "text-amber-600"
:value (str (or reserved 0))
:label "Reserved"))
:lookup-url lookup-url :lookup-url lookup-url
:has-tickets (not (empty? (or tickets (list)))) :has-tickets (not (empty? (or tickets (list))))
:rows (<> (map (lambda (t) :rows (<>
(map
(lambda
(t)
(let-match
{:date-str date-str :csrf csrf :type-name type-name :code-short code-short :entry-name entry-name :code code :checkin-url checkin-url :checked-in-time checked-in-time :state state}
t
(~tickets/admin-row-from-data (~tickets/admin-row-from-data
:code (get t "code") :code-short (get t "code-short") :code code
:entry-name (get t "entry-name") :date-str (get t "date-str") :code-short code-short
:type-name (get t "type-name") :state (get t "state") :entry-name entry-name
:checkin-url (get t "checkin-url") :csrf (get t "csrf") :date-str date-str
:checked-in-time (get t "checked-in-time"))) :type-name type-name
:state state
:checkin-url checkin-url
:csrf csrf
:checked-in-time checked-in-time)))
(or tickets (list)))))) (or tickets (list))))))
;; Entry tickets admin from data ;; Entry tickets admin from data
(defcomp ~tickets/entry-tickets-admin-from-data (&key (entry-name :as string) (count-label :as string) (tickets :as list?) (csrf :as string)) (defcomp
~tickets/entry-tickets-admin-from-data
(&key
(entry-name :as string)
(count-label :as string)
(tickets :as list?)
(csrf :as string))
(~tickets/entry-tickets-admin-panel (~tickets/entry-tickets-admin-panel
:entry-name entry-name :count-label count-label :entry-name entry-name
:body (if (empty? (or tickets (list))) :count-label count-label
:body (if
(empty? (or tickets (list)))
(~tickets/entry-tickets-admin-empty) (~tickets/entry-tickets-admin-empty)
(~tickets/entry-tickets-admin-table (~tickets/entry-tickets-admin-table
:rows (<> (map (lambda (t) :rows (<>
(map
(lambda
(t)
(let-match
{:type-name type-name :code-short code-short :code code :checkin-url checkin-url :checked-in-time checked-in-time :state state}
t
(~tickets/entry-tickets-admin-row (~tickets/entry-tickets-admin-row
:code (get t "code") :code-short (get t "code-short") :code code
:type-name (get t "type-name") :code-short code-short
:badge (~entries/ticket-state-badge :state (get t "state")) :type-name type-name
:badge (~entries/ticket-state-badge :state state)
:action (cond :action (cond
((or (= (get t "state") "confirmed") (= (get t "state") "reserved")) ((or (= state "confirmed") (= state "paid"))
(~tickets/entry-tickets-admin-checkin (~tickets/entry-tickets-admin-checkin
:checkin-url (get t "checkin-url") :code (get t "code") :csrf csrf)) :checkin-url checkin-url
((= (get t "state") "checked_in") :code code
(~tickets/admin-checked-in :time-str (or (get t "checked-in-time") ""))) :csrf csrf))
(true nil)))) ((= state "checked-in")
(~tickets/admin-checked-in
:time-str (or checked-in-time "")))
(true nil)))))
(or tickets (list)))))))) (or tickets (list))))))))
;; Checkin success row from data ;; Checkin success row from data
@@ -316,21 +378,43 @@
:time-str time-str)) :time-str time-str))
;; Ticket types table from data ;; Ticket types table from data
(defcomp ~tickets/types-table-from-data (&key (list-container :as string) (ticket-types :as list?) (action-btn :as string) (add-url :as string) (defcomp
(tr-cls :as string) (pill-cls :as string) (hx-select :as string) (csrf-hdr :as string)) ~tickets/types-table-from-data
(&key
(list-container :as string)
(ticket-types :as list?)
(action-btn :as string)
(add-url :as string)
(tr-cls :as string)
(pill-cls :as string)
(hx-select :as string)
(csrf-hdr :as string))
(~page/ticket-types-table (~page/ticket-types-table
:list-container list-container :list-container list-container
:rows (if (empty? (or ticket-types (list))) :rows (if
(empty? (or ticket-types (list)))
(~page/ticket-types-empty-row) (~page/ticket-types-empty-row)
(<> (map (lambda (tt) (<>
(map
(lambda
(tt)
(let-match
{:tt-href tt-href :count count :cost-str cost-str :tt-name tt-name :del-url del-url}
tt
(~page/ticket-types-row (~page/ticket-types-row
:tr-cls tr-cls :tt-href (get tt "tt-href") :tr-cls tr-cls
:pill-cls pill-cls :hx-select hx-select :tt-href tt-href
:tt-name (get tt "tt-name") :cost-str (get tt "cost-str") :pill-cls pill-cls
:count (get tt "count") :action-btn action-btn :hx-select hx-select
:del-url (get tt "del-url") :csrf-hdr csrf-hdr)) :tt-name tt-name
:cost-str cost-str
:count count
:action-btn action-btn
:del-url del-url
:csrf-hdr csrf-hdr)))
(or ticket-types (list))))) (or ticket-types (list)))))
:action-btn action-btn :add-url add-url)) :action-btn action-btn
:add-url add-url))
;; Lookup result from data ;; Lookup result from data
(defcomp ~tickets/lookup-result-from-data (&key (entry-name :as string) (type-name :as string?) (date-str :as string?) (cal-name :as string?) (defcomp ~tickets/lookup-result-from-data (&key (entry-name :as string) (type-name :as string?) (date-str :as string?) (cal-name :as string?)

View File

@@ -92,52 +92,95 @@
;; --- Data-driven post card (replaces Python _post_card_sx loop) --- ;; --- Data-driven post card (replaces Python _post_card_sx loop) ---
(defcomp ~social/post-card-from-data (&key (d :as dict) (has-actor :as boolean) (csrf :as string) (defcomp
(like-url :as string) (unlike-url :as string) ~social/post-card-from-data
(boost-url :as string) (unboost-url :as string)) (&key
(let* ((boosted-by (get d "boosted_by")) (d :as dict)
(actor-icon (get d "actor_icon")) (has-actor :as boolean)
(actor-name (get d "actor_name")) (csrf :as string)
(initial (or (get d "initial") "?")) (like-url :as string)
(avatar (~shared:misc/avatar (unlike-url :as string)
(boost-url :as string)
(unboost-url :as string))
(let-match
{:actor_name actor-name :liked_by_me liked :boosted_by_me boosted-me :time time :actor_username actor-username :domain domain :content content :object_id oid :boosted_by boosted-by :summary summary :original_url original-url :safe_id safe-id :author_inbox ainbox :reply_url reply-url :like_count like-count :boost_count boost-count :actor_icon actor-icon :initial initial*}
d
(let*
((initial (or initial* "?"))
(avatar
(~shared:misc/avatar
:src actor-icon :src actor-icon
:cls (if actor-icon "w-10 h-10 rounded-full" :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") "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))) :initial (when (not actor-icon) initial)))
(boost (when boosted-by (~social/boost-label :name boosted-by))) (boost (when boosted-by (~social/boost-label :name boosted-by)))
(content-sx (if (get d "summary") (content-sx
(~social/content :content (get d "content") :summary (get d "summary")) (if
(~social/content :content (get d "content")))) summary
(original (when (get d "original_url") (~social/content :content content :summary summary)
(~social/original-link :url (get d "original_url")))) (~social/content :content content)))
(safe-id (get d "safe_id")) (original
(interactions (when has-actor (when original-url (~social/original-link :url original-url)))
(let* ((oid (get d "object_id")) (interactions
(ainbox (get d "author_inbox")) (when
(target (str "#interactions-" safe-id)) has-actor
(liked (get d "liked_by_me")) (let*
(boosted-me (get d "boosted_by_me")) ((target (str "#interactions-" safe-id))
(l-action (if liked unlike-url like-url)) (l-action (if liked unlike-url like-url))
(l-cls (str "flex items-center gap-1 " (if liked "text-red-500 hover:text-red-600" "hover:text-red-500"))) (l-cls
(l-icon (if liked "\u2665" "\u2661")) (str
"flex items-center gap-1 "
(if
liked
"text-red-500 hover:text-red-600"
"hover:text-red-500")))
(l-icon (if liked "♥" "♡"))
(b-action (if boosted-me unboost-url boost-url)) (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"))) (b-cls
(reply-url (get d "reply_url")) (str
"flex items-center gap-1 "
(if
boosted-me
"text-green-600 hover:text-green-700"
"hover:text-green-600")))
(reply (when reply-url (~social/reply-link :url reply-url))) (reply (when reply-url (~social/reply-link :url reply-url)))
(like-form (~social/like-form (like-form
:action l-action :target target :oid oid :ainbox ainbox (~social/like-form
:csrf csrf :cls l-cls :icon l-icon :count (get d "like_count"))) :action l-action
(boost-form (~social/boost-form :target target
:action b-action :target target :oid oid :ainbox ainbox :oid oid
:csrf csrf :cls b-cls :count (get d "boost_count")))) :ainbox ainbox
(div :id (str "interactions-" safe-id) :csrf csrf
(~social/interaction-buttons :like like-form :boost boost-form :reply reply)))))) :cls l-cls
:icon l-icon
:count like-count))
(boost-form
(~social/boost-form
:action b-action
:target target
:oid oid
:ainbox ainbox
:csrf csrf
:cls b-cls
:count boost-count)))
(div
:id (str "interactions-" safe-id)
(~social/interaction-buttons
:like like-form
:boost boost-form
:reply reply))))))
(~social/post-card (~social/post-card
:boost boost :avatar avatar :boost boost
:actor-name actor-name :actor-username (get d "actor_username") :avatar avatar
:domain (get d "domain") :time (get d "time") :actor-name actor-name
:content content-sx :original original :actor-username actor-username
:interactions interactions))) :domain domain
:time time
:content content-sx
:original original
:interactions interactions))))
;; Data-driven timeline items (replaces Python _timeline_items_sx loop) ;; Data-driven timeline items (replaces Python _timeline_items_sx loop)
(defcomp ~social/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)
@@ -174,11 +217,17 @@
;; Assembled social nav — replaces Python _social_nav_sx ;; Assembled social nav — replaces Python _social_nav_sx
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
(defcomp ~social/nav (&key actor) (defcomp
(if (not actor) ~social/nav
(~social/nav-choose-username :url (url-for "identity.choose_username_form")) (&key actor)
(let* ((rp (request-path)) (if
(links (list (not actor)
(~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") (dict :endpoint "social.defpage_home_timeline" :label "Timeline")
(dict :endpoint "social.defpage_public_timeline" :label "Public") (dict :endpoint "social.defpage_public_timeline" :label "Public")
(dict :endpoint "social.defpage_compose_form" :label "Compose") (dict :endpoint "social.defpage_compose_form" :label "Compose")
@@ -187,22 +236,34 @@
(dict :endpoint "social.defpage_search" :label "Search")))) (dict :endpoint "social.defpage_search" :label "Search"))))
(~social/nav-bar (~social/nav-bar
:items (<> :items (<>
(map (lambda (lnk) (map
(let* ((href (url-for (get lnk "endpoint"))) (lambda
(lnk)
(let-match
{:label label :endpoint endpoint}
lnk
(let*
((href (url-for endpoint))
(bold (if (= rp href) " font-bold" ""))) (bold (if (= rp href) " font-bold" "")))
(a :href href (a
:href href
:class (str "px-2 py-1 rounded hover:bg-stone-200" bold) :class (str "px-2 py-1 rounded hover:bg-stone-200" bold)
(get lnk "label")))) label))))
links) links)
(let* ((notif-url (url-for "social.defpage_notifications")) (let*
((notif-url (url-for "social.defpage_notifications"))
(notif-bold (if (= rp notif-url) " font-bold" ""))) (notif-bold (if (= rp notif-url) " font-bold" "")))
(~social/nav-notification-link (~social/nav-notification-link
:href notif-url :href notif-url
:cls (str "px-2 py-1 rounded hover:bg-stone-200 relative" notif-bold) :cls (str "px-2 py-1 rounded hover:bg-stone-200 relative" notif-bold)
:count-url (url-for "social.notification_count"))) :count-url (url-for "social.notification_count")))
(a :href (url-for "activitypub.actor_profile" :username (get actor "preferred_username")) (let-match
{:preferred_username username}
actor
(a
:href (url-for "activitypub.actor_profile" :username username)
:class "px-2 py-1 rounded hover:bg-stone-200" :class "px-2 py-1 rounded hover:bg-stone-200"
(str "@" (get actor "preferred_username")))))))) (str "@" username))))))))
;; --------------------------------------------------------------------------- ;; ---------------------------------------------------------------------------
;; Assembled post card — replaces Python _post_card_sx ;; Assembled post card — replaces Python _post_card_sx

View File

@@ -21,7 +21,8 @@
;; Registry of freeze scopes: name → list of {name signal} entries ;; Registry of freeze scopes: name → list of {name signal} entries
(define-library (sx freeze) (define-library
(sx freeze)
(export (export
freeze-registry freeze-registry
freeze-signal freeze-signal
@@ -33,82 +34,96 @@
freeze-to-sx freeze-to-sx
thaw-from-sx) thaw-from-sx)
(begin (begin
(define freeze-registry (dict)) (define freeze-registry (dict))
(define
;; Register a signal in the current freeze scope freeze-signal
(define freeze-signal :effects [mutation] :effects (mutation)
(fn (name sig) (fn
(let ((scope-name (context "sx-freeze-scope" nil))) (name sig)
(when scope-name (let
(let ((entries (or (get freeze-registry scope-name) (list)))) ((scope-name (context "sx-freeze-scope" nil)))
(when
scope-name
(let
((entries (or (get freeze-registry scope-name) (list))))
(append! entries (dict "name" name "signal" sig)) (append! entries (dict "name" name "signal" sig))
(dict-set! freeze-registry scope-name entries)))))) (dict-set! freeze-registry scope-name entries))))))
(define
;; Freeze scope delimiter — collects signals registered within body freeze-scope
(define freeze-scope :effects [mutation] :effects (mutation)
(fn (name body-fn) (fn
(name body-fn)
(scope-push! "sx-freeze-scope" name) (scope-push! "sx-freeze-scope" name)
;; Initialize empty entry list for this scope
(dict-set! freeze-registry name (list)) (dict-set! freeze-registry name (list))
(cek-call body-fn nil) (cek-call body-fn nil)
(scope-pop! "sx-freeze-scope") (scope-pop! "sx-freeze-scope")
nil)) nil))
(define
;; Freeze a named scope → SX dict of signal values cek-freeze-scope
(define cek-freeze-scope :effects [] :effects ()
(fn (name) (fn
(let ((entries (or (get freeze-registry name) (list))) (name)
(let
((entries (or (get freeze-registry name) (list)))
(signals-dict (dict))) (signals-dict (dict)))
(for-each (fn (entry) (for-each
(dict-set! signals-dict (fn
(entry)
(dict-set!
signals-dict
(get entry "name") (get entry "name")
(signal-value (get entry "signal")))) (signal-value (get entry "signal"))))
entries) entries)
(dict "name" name "signals" signals-dict)))) (dict "name" name "signals" signals-dict))))
(define
;; Freeze all scopes cek-freeze-all
(define cek-freeze-all :effects [] :effects ()
(fn () (fn
(map (fn (name) (cek-freeze-scope name)) ()
(keys freeze-registry)))) (map (fn (name) (cek-freeze-scope name)) (keys freeze-registry))))
(define
;; Thaw a named scope — restore signal values from frozen data cek-thaw-scope
(define cek-thaw-scope :effects [mutation] :effects (mutation)
(fn (name frozen) (fn
(let ((entries (or (get freeze-registry name) (list))) (name frozen)
(let
((entries (or (get freeze-registry name) (list)))
(values (get frozen "signals"))) (values (get frozen "signals")))
(when values (when
(for-each (fn (entry) values
(let ((sig-name (get entry "name")) (for-each
(fn
(entry)
(let
((sig-name (get entry "name"))
(sig (get entry "signal")) (sig (get entry "signal"))
(val (get values sig-name))) (val (get values sig-name)))
(when (not (nil? val)) (when (not (nil? val)) (reset! sig val))))
(reset! sig val))))
entries))))) entries)))))
(define
;; Thaw all scopes from a list of frozen scope dicts cek-thaw-all
(define cek-thaw-all :effects [mutation] :effects (mutation)
(fn (frozen-list) (fn
(for-each (fn (frozen) (frozen-list)
(cek-thaw-scope (get frozen "name") frozen)) (for-each
(fn (frozen) (cek-thaw-scope (get frozen "name") frozen))
frozen-list))) frozen-list)))
(define
;; Serialize a frozen scope to SX text freeze-to-sx
(define freeze-to-sx :effects [] :effects ()
(fn (name) (fn (name) (sx-serialize (cek-freeze-scope name))))
(sx-serialize (cek-freeze-scope name)))) (define
thaw-from-sx
;; Restore from SX text :effects (mutation)
(define thaw-from-sx :effects [mutation] (fn
(fn (sx-text) (sx-text)
(let ((parsed (sx-parse sx-text))) (let
(when (not (empty? parsed)) ((parsed (sx-parse sx-text)))
(let ((frozen (first parsed))) (when
(cek-thaw-scope (get frozen "name") frozen)))))) (not (empty? parsed))
(let
((frozen (first parsed)))
)) ;; end define-library (cek-thaw-scope (get frozen "name") frozen)))))))) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx freeze)) (import (sx freeze))

View File

@@ -1,6 +1,7 @@
(define-library (sx highlight) (define-library
(sx highlight)
(export (export
sx-specials sx-specials
sx-special? sx-special?
@@ -16,7 +17,6 @@
highlight-sx highlight-sx
highlight) highlight)
(begin (begin
(define (define
sx-specials sx-specials
(list (list
@@ -54,16 +54,32 @@
"for-each" "for-each"
"&key" "&key"
"&rest" "&rest"
"set!")) "set!"
"satisfies?"
"match"
"let-match"
"define-protocol"
"implement"
"->>"
"|>"
"as->"
"define-library"
"import"
"perform"
"guard"
"call/cc"
"raise"
"define-syntax"
"syntax-rules"
"make-parameter"
"parameterize"))
(define sx-special? (fn (s) (some (fn (x) (= x s)) sx-specials))) (define sx-special? (fn (s) (some (fn (x) (= x s)) sx-specials)))
(define hl-digit? (fn (c) (and (>= c "0") (<= c "9")))) (define hl-digit? (fn (c) (and (>= c "0") (<= c "9"))))
(define (define
hl-alpha? hl-alpha?
(fn (c) (or (and (>= c "a") (<= c "z")) (and (>= c "A") (<= c "Z"))))) (fn
(c)
(or (and (>= c "a") (<= c "z")) (and (>= c "A") (<= c "Z")))))
(define (define
hl-sym-char? hl-sym-char?
(fn (fn
@@ -83,11 +99,10 @@
(= c "=") (= c "=")
(= c "&") (= c "&")
(= c ".")))) (= c "."))))
(define
(define hl-ws? (fn (c) (or (= c " ") (= c "\n") (= c "\t") (= c "\r")))) hl-ws?
(fn (c) (or (= c " ") (= c "\n") (= c "\t") (= c "\r"))))
(define hl-escape (fn (s) s)) (define hl-escape (fn (s) s))
(define (define
hl-span hl-span
(fn (fn
@@ -96,7 +111,6 @@
(= class "") (= class "")
(list (quote span) text) (list (quote span) text)
(list (quote span) (make-keyword "class") class text)))) (list (quote span) (make-keyword "class") class text))))
(define (define
tokenize-sx tokenize-sx
(fn (fn
@@ -196,7 +210,8 @@
tokens tokens
(append (append
tokens tokens
(list (list "component" (substring code start i)))))) (list
(list "component" (substring code start i))))))
(if (if
(or (or
(= c "(") (= c "(")
@@ -229,7 +244,8 @@
tokens tokens
(append (append
tokens tokens
(list (list "number" (substring code start i)))))) (list
(list "number" (substring code start i))))))
(if (if
(hl-sym-char? c) (hl-sym-char? c)
(let (let
@@ -240,7 +256,8 @@
(when (when
(and (and
(< i len) (< i len)
(hl-sym-char? (substring code i (+ i 1)))) (hl-sym-char?
(substring code i (+ i 1))))
(set! i (+ i 1)) (set! i (+ i 1))
(scan))) (scan)))
(let (let
@@ -284,7 +301,8 @@
tokens tokens
(append (append
tokens tokens
(list (list "ws" (substring code start i)))))) (list
(list "ws" (substring code start i))))))
(do (do
(set! (set!
tokens tokens
@@ -292,9 +310,7 @@
(set! i (+ i 1)))))))))))) (set! i (+ i 1))))))))))))
(loop))) (loop)))
tokens))) tokens)))
(define sx-token-classes {:boolean "text-orange-600" :component "text-rose-600 font-semibold" :number "text-amber-700" :string "text-emerald-700" :special "text-sky-700 font-semibold" :paren "text-stone-400" :keyword "text-violet-600" :comment "text-stone-400 italic"}) (define sx-token-classes {:boolean "text-orange-600" :component "text-rose-600 font-semibold" :number "text-amber-700" :string "text-emerald-700" :special "text-sky-700 font-semibold" :paren "text-stone-400" :keyword "text-violet-600" :comment "text-stone-400 italic"})
(define (define
render-sx-tokens render-sx-tokens
(fn (fn
@@ -306,20 +322,19 @@
((cls (or (dict-get sx-token-classes (first tok)) ""))) ((cls (or (dict-get sx-token-classes (first tok)) "")))
(hl-span cls (nth tok 1)))) (hl-span cls (nth tok 1))))
tokens))) tokens)))
(define highlight-sx (fn (code) (-> code tokenize-sx render-sx-tokens)))
(define highlight-sx (fn (code) (render-sx-tokens (tokenize-sx code))))
(define (define
highlight highlight
(fn (fn
(code lang) (code lang)
(if (if
(or (= lang "lisp") (= lang "sx") (= lang "sexp") (= lang "scheme")) (or
(= lang "lisp")
(= lang "sx")
(= lang "sexp")
(= lang "scheme"))
(highlight-sx code) (highlight-sx code)
(list (quote code) code)))) (list (quote code) code)))))) ;; end define-library
)) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx highlight)) (import (sx highlight))

View File

@@ -1,6 +1,7 @@
(define-library (sx swap) (define-library
(sx swap)
(export (export
_skip-string _skip-string
_find-close _find-close
@@ -16,7 +17,6 @@
strip-oob strip-oob
apply-response) apply-response)
(begin (begin
(define (define
_skip-string _skip-string
(fn (fn
@@ -32,7 +32,6 @@
(= ch "\"") (= ch "\"")
(+ i 1) (+ i 1)
:else (_skip-string src (+ i 1))))))) :else (_skip-string src (+ i 1)))))))
(define (define
_find-close _find-close
(fn (fn
@@ -55,9 +54,11 @@
(= ch "(") (= ch "(")
(_find-close src (+ i 1) (+ depth 1) false) (_find-close src (+ i 1) (+ depth 1) false)
(= ch ")") (= ch ")")
(if (= depth 1) i (_find-close src (+ i 1) (- depth 1) false)) (if
(= depth 1)
i
(_find-close src (+ i 1) (- depth 1) false))
:else (_find-close src (+ i 1) depth false)))))) :else (_find-close src (+ i 1) depth false))))))
(define (define
_skip-ws _skip-ws
(fn (fn
@@ -71,7 +72,6 @@
(or (= ch " ") (= ch "\n") (= ch "\t") (= ch "\r")) (or (= ch " ") (= ch "\n") (= ch "\t") (= ch "\r"))
(_skip-ws src (+ i 1)) (_skip-ws src (+ i 1))
i))))) i)))))
(define (define
_skip-token _skip-token
(fn (fn
@@ -92,7 +92,6 @@
(= ch "\"")) (= ch "\""))
i i
(_skip-token src (+ i 1))))))) (_skip-token src (+ i 1)))))))
(define (define
_skip-value _skip-value
(fn (fn
@@ -110,7 +109,6 @@
((close (_find-close src (+ i 1) 1 false))) ((close (_find-close src (+ i 1) 1 false)))
(if (= close -1) (len src) (+ close 1))) (if (= close -1) (len src) (+ close 1)))
:else (_skip-token src i)))))) :else (_skip-token src i))))))
(define (define
_find-children-start _find-children-start
(fn (fn
@@ -132,16 +130,18 @@
(= (nth src pos) ":") (= (nth src pos) ":")
(let (let
((after-kw (_skip-token src pos))) ((after-kw (_skip-token src pos)))
(_skip-attrs (_skip-value src (_skip-ws src after-kw)))) (_skip-attrs
(_skip-value src (_skip-ws src after-kw))))
pos))))) pos)))))
(_skip-attrs after-tag))))) (_skip-attrs after-tag)))))
(define (define
_scan-back _scan-back
(fn (fn
(src i) (src i)
(if (< i 0) -1 (if (= (nth src i) "(") i (_scan-back src (- i 1)))))) (if
(< i 0)
-1
(if (= (nth src i) "(") i (_scan-back src (- i 1))))))
(define (define
find-element-by-id find-element-by-id
(fn (fn
@@ -166,7 +166,6 @@
(let (let
((cs (_find-children-start src elem-start elem-end))) ((cs (_find-children-start src elem-start elem-end)))
{:end elem-end :start elem-start :children-start cs})))))))))) {:end elem-end :start elem-start :children-start cs}))))))))))
(define (define
sx-swap sx-swap
(fn (fn
@@ -176,20 +175,30 @@
(if (if
(nil? info) (nil? info)
src src
(let (let-match
((s (get info "start")) {:end e :start s :children-start cs}
(e (get info "end")) info
(cs (get info "children-start")))
(case (case
mode mode
"innerHTML" "innerHTML"
(str (slice src 0 cs) new-content (slice src e (len src))) (str (slice src 0 cs) new-content (slice src e (len src)))
"outerHTML" "outerHTML"
(str (slice src 0 s) new-content (slice src (+ e 1) (len src))) (str
(slice src 0 s)
new-content
(slice src (+ e 1) (len src)))
"beforeend" "beforeend"
(str (slice src 0 e) " " new-content (slice src e (len src))) (str
(slice src 0 e)
" "
new-content
(slice src e (len src)))
"afterbegin" "afterbegin"
(str (slice src 0 cs) new-content " " (slice src cs (len src))) (str
(slice src 0 cs)
new-content
" "
(slice src cs (len src)))
"beforebegin" "beforebegin"
(str (slice src 0 s) new-content (slice src s (len src))) (str (slice src 0 s) new-content (slice src s (len src)))
"afterend" "afterend"
@@ -202,7 +211,6 @@
"none" "none"
src src
:else src)))))) :else src))))))
(define (define
_extract-attr-value _extract-attr-value
(fn (fn
@@ -217,7 +225,6 @@
(let (let
((tok-end (_skip-token src val-start))) ((tok-end (_skip-token src val-start)))
(slice src val-start tok-end)))))) (slice src val-start tok-end))))))
(define (define
find-oob-elements find-oob-elements
(fn (fn
@@ -264,7 +271,6 @@
(+ elem-end 1) (+ elem-end 1)
(append results (list {:end elem-end :mode mode :content children-str :start elem-start :id id-val})))))))))))))))))))) (append results (list {:end elem-end :mode mode :content children-str :start elem-start :id id-val}))))))))))))))))))))
(_scan 0 (list)))) (_scan 0 (list))))
(define (define
strip-oob strip-oob
(fn (fn
@@ -288,7 +294,6 @@
(after (slice s (+ (get item "end") 1) (len s)))) (after (slice s (+ (get item "end") 1) (len s))))
(_strip (str before after) (rest items))))))) (_strip (str before after) (rest items)))))))
(_strip src sorted))))) (_strip src sorted)))))
(define (define
apply-response apply-response
(fn (fn
@@ -316,10 +321,7 @@
(get oob "id") (get oob "id")
(get oob "content")) (get oob "content"))
(rest items)))))) (rest items))))))
(_apply-oobs result oobs))))))) (_apply-oobs result oobs))))))))) ;; end define-library
)) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx swap)) (import (sx swap))

View File

@@ -63,22 +63,18 @@
:effects () :effects ()
(fn (fn
(node) (node)
(cond (match
(nil? node) (type-of node)
"nil" ("nil" "nil")
(= (type-of node) "symbol") ("symbol" (symbol-name node))
(symbol-name node) ("keyword" (str ":" (keyword-name node)))
(= (type-of node) "keyword") ("string"
(str ":" (keyword-name node))
(= (type-of node) "string")
(let (let
((s (if (> (len node) 40) (str (slice node 0 37) "...") node))) ((s (if (> (len node) 40) (str (slice node 0 37) "...") node)))
(str "\"" s "\"")) (str "\"" s "\"")))
(= (type-of node) "number") ("number" (str node))
(str node) ("boolean" (if node "true" "false"))
(= (type-of node) "boolean") ("list"
(if node "true" "false")
(list? node)
(if (if
(empty? node) (empty? node)
"()" "()"
@@ -86,10 +82,9 @@
"(" "("
(node-display (first node)) (node-display (first node))
(if (> (len node) 1) " ..." "") (if (> (len node) 1) " ..." "")
")")) ")")))
(= (type-of node) "dict") ("dict" "{...}")
"{...}" (_ (str node)))))
:else (str node))))
(define (define
summarise summarise
@@ -244,17 +239,16 @@
:effects () :effects ()
(fn (fn
(node pattern) (node pattern)
(cond (match
(= (type-of node) "symbol") (type-of node)
(contains? (symbol-name node) pattern) ("symbol" (contains? (symbol-name node) pattern))
(string? node) ("string" (contains? node pattern))
(contains? node pattern) ("list"
(and (if
(list? node) (empty? node)
(not (empty? node)) false
(= (type-of (first node)) "symbol")) (some (fn (child) (node-matches? child pattern)) node)))
(contains? (symbol-name (first node)) pattern) (_ false))))
:else false)))
(define (define
node-summary-short node-summary-short
@@ -546,10 +540,10 @@
:effects () :effects ()
(fn (fn
(node replacement) (node replacement)
(cond (match
(and (= (type-of node) "symbol") (= (symbol-name node) "_")) (type-of node)
replacement ("symbol" (if (= (symbol-name node) "_") replacement nil))
(list? node) ("list"
(let (let
((found false) ((found false)
(result (result
@@ -571,8 +565,8 @@
(if (nil? sub) child (do (set! found true) sub))) (if (nil? sub) child (do (set! found true) sub)))
child)))) child))))
node))) node)))
(if found result nil)) (if found result nil)))
:else nil))) (_ nil))))
(define (define
tree-set tree-set
@@ -851,12 +845,13 @@
:effects () :effects ()
(fn (fn
(node old-name new-name) (node old-name new-name)
(cond (match
(and (= (type-of node) "symbol") (= (symbol-name node) old-name)) (type-of node)
(make-symbol new-name) ("symbol"
(list? node) (if (= (symbol-name node) old-name) (make-symbol new-name) node))
(map (fn (child) (rename-in-node child old-name new-name)) node) ("list"
:else node))) (map (fn (child) (rename-in-node child old-name new-name)) node))
(_ node))))
(define (define
count-renames count-renames
@@ -873,12 +868,12 @@
:effects () :effects ()
(fn (fn
(node old-name hits) (node old-name hits)
(cond (match
(and (= (type-of node) "symbol") (= (symbol-name node) old-name)) (type-of node)
(append! hits true) ("symbol" (when (= (symbol-name node) old-name) (append! hits true)))
(list? node) ("list"
(for-each (fn (child) (count-in-node child old-name hits)) node) (for-each (fn (child) (count-in-node child old-name hits)) node))
:else nil))) (_ nil))))
(define (define
replace-by-pattern replace-by-pattern
@@ -1341,17 +1336,30 @@
(walk node (dict)) (walk node (dict))
result))) result)))
(define find-use-declarations :effects () (define
(fn (nodes) find-use-declarations
(let ((uses (list))) :effects ()
(for-each (fn (node) (fn
(when (and (list? node) (>= (len node) 2) (nodes)
(let
((uses (list)))
(for-each
(fn
(node)
(when
(and
(list? node)
(>= (len node) 2)
(= (type-of (first node)) "symbol") (= (type-of (first node)) "symbol")
(= (symbol-name (first node)) "use")) (= (symbol-name (first node)) "use"))
(for-each (fn (arg) (for-each
(fn
(arg)
(cond (cond
(= (type-of arg) "symbol") (append! uses (symbol-name arg)) (= (type-of arg) "symbol")
(= (type-of arg) "string") (append! uses arg))) (append! uses (symbol-name arg))
(= (type-of arg) "string")
(append! uses arg)))
(rest node)))) (rest node))))
(if (list? nodes) nodes (list nodes))) (if (list? nodes) nodes (list nodes)))
uses))) uses)))

File diff suppressed because it is too large Load Diff

View File

@@ -79,35 +79,35 @@
(fn (fn
(vm value) (vm value)
(let (let
((sp (get vm "sp")) (stack (get vm "stack"))) ((sp (vm-sp vm)) (stack (vm-stack vm)))
(when (when
(>= sp (vm-stack-length stack)) (>= sp (vm-stack-length stack))
(let (let
((new-stack (make-vm-stack (* sp 2)))) ((new-stack (vm-stack-grow stack sp)))
(vm-stack-copy! stack new-stack sp) (vm-stack-copy! stack new-stack sp)
(dict-set! vm "stack" new-stack) (vm-set-stack! vm new-stack)
(set! stack new-stack))) (set! stack new-stack)))
(vm-stack-set! stack sp value) (vm-stack-set! stack sp value)
(dict-set! vm "sp" (+ sp 1))))) (vm-set-sp! vm (+ sp 1)))))
(define (define
vm-pop vm-pop
(fn (fn
(vm) (vm)
(let (let
((sp (- (get vm "sp") 1))) ((sp (- (vm-sp vm) 1)))
(dict-set! vm "sp" sp) (vm-set-sp! vm sp)
(vm-stack-get (get vm "stack") sp)))) (vm-stack-get (vm-stack vm) sp))))
(define (define
vm-peek vm-peek
(fn (vm) (vm-stack-get (get vm "stack") (- (get vm "sp") 1)))) (fn (vm) (vm-stack-get (vm-stack vm) (- (vm-sp vm) 1))))
(define (define
frame-read-u8 frame-read-u8
(fn (fn
(frame) (frame)
(let (let
((ip (get frame "ip")) ((ip (frame-ip frame))
(bc (get (get (get frame "closure") "vm-code") "vc-bytecode"))) (bc (-> frame frame-closure closure-code code-bytecode)))
(let ((v (nth bc ip))) (dict-set! frame "ip" (+ ip 1)) v)))) (let ((v (nth bc ip))) (frame-set-ip! frame (+ ip 1)) v))))
(define (define
frame-read-u16 frame-read-u16
(fn (fn
@@ -206,31 +206,28 @@
(if (if
(has-key? cells key) (has-key? cells key)
(uv-get (get cells key)) (uv-get (get cells key))
(vm-stack-get (get vm "stack") (+ (get frame "base") slot)))))) (vm-stack-get (vm-stack vm) (+ (frame-base frame) slot))))))
(define (define
frame-local-set frame-local-set
(fn (fn
(vm frame slot value) (vm frame slot value)
"Write a local variable — to shared cell if captured, else to stack." "Write a local variable — to shared cell or stack."
(let (let
((cells (get frame "local-cells")) (key (str slot))) ((cells (get frame "local-cells")) (key (str slot)))
(if (if
(has-key? cells key) (has-key? cells key)
(uv-set! (get cells key) value) (uv-set! (get cells key) value)
(vm-stack-set! (vm-stack-set! (vm-stack vm) (+ (frame-base frame) slot) value)))))
(get vm "stack")
(+ (get frame "base") slot)
value)))))
(define (define
frame-upvalue-get frame-upvalue-get
(fn (fn
(frame idx) (frame idx)
(uv-get (nth (get (get frame "closure") "vm-upvalues") idx)))) (uv-get (nth (-> frame frame-closure closure-upvalues) idx))))
(define (define
frame-upvalue-set frame-upvalue-set
(fn (fn
(frame idx value) (frame idx value)
(uv-set! (nth (get (get frame "closure") "vm-upvalues") idx) value))) (uv-set! (nth (-> frame frame-closure closure-upvalues) idx) value)))
(define frame-ip (fn (frame) (get frame "ip"))) (define frame-ip (fn (frame) (get frame "ip")))
(define frame-set-ip! (fn (frame val) (dict-set! frame "ip" val))) (define frame-set-ip! (fn (frame val) (dict-set! frame "ip" val)))
(define frame-base (fn (frame) (get frame "base"))) (define frame-base (fn (frame) (get frame "base")))
@@ -302,12 +299,12 @@
(vm frame name) (vm frame name)
"Look up a global: globals table → closure env → primitives → HO wrappers" "Look up a global: globals table → closure env → primitives → HO wrappers"
(let (let
((globals (get vm "globals"))) ((globals (vm-globals-ref vm)))
(if (if
(has-key? globals name) (has-key? globals name)
(get globals name) (get globals name)
(let (let
((closure-env (get (get frame "closure") "closure-env"))) ((closure-env (-> frame frame-closure closure-env)))
(if (if
(nil? closure-env) (nil? closure-env)
(cek-try (cek-try
@@ -325,41 +322,42 @@
vm-resolve-ho-form vm-resolve-ho-form
(fn (fn
(vm name) (vm name)
(cond (match
(= name "for-each") name
("for-each"
(fn (fn
(f coll) (f coll)
(for-each (fn (x) (vm-call-external vm f (list x))) coll)) (for-each (fn (x) (vm-call-external vm f (list x))) coll)))
(= name "map") ("map"
(fn (fn
(f coll) (f coll)
(map (fn (x) (vm-call-external vm f (list x))) coll)) (map (fn (x) (vm-call-external vm f (list x))) coll)))
(= name "map-indexed") ("map-indexed"
(fn (fn
(f coll) (f coll)
(map-indexed (map-indexed
(fn (i x) (vm-call-external vm f (list i x))) (fn (i x) (vm-call-external vm f (list i x)))
coll)) coll)))
(= name "filter") ("filter"
(fn (fn
(f coll) (f coll)
(filter (fn (x) (vm-call-external vm f (list x))) coll)) (filter (fn (x) (vm-call-external vm f (list x))) coll)))
(= name "reduce") ("reduce"
(fn (fn
(f init coll) (f init coll)
(reduce (reduce
(fn (acc x) (vm-call-external vm f (list acc x))) (fn (acc x) (vm-call-external vm f (list acc x)))
init init
coll)) coll)))
(= name "some") ("some"
(fn (fn
(f coll) (f coll)
(some (fn (x) (vm-call-external vm f (list x))) coll)) (some (fn (x) (vm-call-external vm f (list x))) coll)))
(= name "every?") ("every?"
(fn (fn
(f coll) (f coll)
(every? (fn (x) (vm-call-external vm f (list x))) coll)) (every? (fn (x) (vm-call-external vm f (list x))) coll)))
:else (error (str "VM undefined: " name))))) (_ (error (str "VM undefined: " name))))))
(define (define
vm-call-external vm-call-external
(fn (fn
@@ -372,14 +370,14 @@
vm-global-set vm-global-set
(fn (fn
(vm frame name value) (vm frame name value)
"Set a global: write to closure env if name exists there, else globals." "Set a global: write to closure env if found, else globals table."
(let (let
((closure-env (get (get frame "closure") "vm-closure-env")) ((closure-env (get (frame-closure frame) "vm-closure-env"))
(written false)) (written false))
(when (when
(not (nil? closure-env)) (not (nil? closure-env))
(set! written (env-walk-set! closure-env name value))) (set! written (env-walk-set! closure-env name value)))
(when (not written) (dict-set! (get vm "globals") name value))))) (when (not written) (dict-set! (vm-globals-ref vm) name value)))))
(define (define
env-walk env-walk
(fn (fn
@@ -414,20 +412,15 @@
(let (let
((code (code-from-value code-val)) ((code (code-from-value code-val))
(uv-count (uv-count
(if (if (dict? code-val) (or (get code-val "upvalue-count") 0) 0)))
(dict? code-val)
(let (let
((n (get code-val "upvalue-count"))) ((upvalues (let ((result (list)) (i 0)) (define capture-loop (fn () (when (< i uv-count) (let ((is-local (frame-read-u8 frame)) (index (frame-read-u8 frame))) (let ((cell (if (= is-local 1) (let ((cells (get frame "local-cells")) (key (str index))) (if (has-key? cells key) (get cells key) (let ((c (make-upvalue-cell (vm-stack-get (vm-stack vm) (+ (frame-base frame) index))))) (dict-set! cells key c) c))) (nth (-> frame frame-closure closure-upvalues) index)))) (append! result cell) (set! i (+ i 1)) (capture-loop)))))) (capture-loop) result)))
(if (nil? n) 0 n)) (make-vm-closure code upvalues nil (vm-globals-ref vm) nil)))))
0)))
(let
((upvalues (let ((result (list)) (i 0)) (define capture-loop (fn () (when (< i uv-count) (let ((is-local (frame-read-u8 frame)) (index (frame-read-u8 frame))) (let ((cell (if (= is-local 1) (let ((cells (get frame "local-cells")) (key (str index))) (if (has-key? cells key) (get cells key) (let ((c (make-upvalue-cell (vm-stack-get (get vm "stack") (+ (get frame "base") index))))) (dict-set! cells key c) c))) (nth (get (get frame "closure") "vm-upvalues") index)))) (append! result cell) (set! i (+ i 1)) (capture-loop)))))) (capture-loop) result)))
(make-vm-closure code upvalues nil (get vm "globals") nil)))))
(define (define
vm-run vm-run
(fn (fn
(vm) (vm)
"Execute bytecode until all frames are consumed." "Execute bytecode until all frames are done or IO suspension."
(define (define
loop loop
(fn (fn
@@ -438,9 +431,9 @@
((frame (first (vm-frames vm))) ((frame (first (vm-frames vm)))
(rest-frames (rest (vm-frames vm)))) (rest-frames (rest (vm-frames vm))))
(let (let
((bc (code-bytecode (closure-code (frame-closure frame)))) ((bc (-> frame frame-closure closure-code code-bytecode))
(consts (consts
(code-constants (closure-code (frame-closure frame))))) (-> frame frame-closure closure-code code-constants)))
(if (if
(>= (frame-ip frame) (len bc)) (>= (frame-ip frame) (len bc))
(vm-set-frames! vm (list)) (vm-set-frames! vm (list))

View File

@@ -121,57 +121,63 @@
(case (case
slug slug
"bundle-analyzer" "bundle-analyzer"
(let (let-match
((data (helper "bundle-analyzer-data"))) {:total-macros total-macros :pages pages :io-count io-count :pure-count pure-count :total-components total-components}
(helper "bundle-analyzer-data")
(quasiquote (quasiquote
(~analyzer/bundle-analyzer-content (~analyzer/bundle-analyzer-content
:pages (unquote (get data "pages")) :pages (unquote pages)
:total-components (unquote (get data "total-components")) :total-components (unquote total-components)
:total-macros (unquote (get data "total-macros")) :total-macros (unquote total-macros)
:pure-count (unquote (get data "pure-count")) :pure-count (unquote pure-count)
:io-count (unquote (get data "io-count"))))) :io-count (unquote io-count))))
"routing-analyzer" "routing-analyzer"
(let (let-match
((data (helper "routing-analyzer-data"))) {:pages pages :total-pages total-pages :server-count server-count :registry-sample registry-sample :client-count client-count}
(helper "routing-analyzer-data")
(quasiquote (quasiquote
(~routing-analyzer/content (~routing-analyzer/content
:pages (unquote (get data "pages")) :pages (unquote pages)
:total-pages (unquote (get data "total-pages")) :total-pages (unquote total-pages)
:client-count (unquote (get data "client-count")) :client-count (unquote client-count)
:server-count (unquote (get data "server-count")) :server-count (unquote server-count)
:registry-sample (unquote (get data "registry-sample"))))) :registry-sample (unquote registry-sample))))
"data-test" "data-test"
(let (let-match
((data (helper "data-test-data"))) {:server-time server-time :transport transport :phase phase :items items}
(helper "data-test-data")
(quasiquote (quasiquote
(~data-test/content (~data-test/content
:server-time (unquote (get data "server-time")) :server-time (unquote server-time)
:items (unquote (get data "items")) :items (unquote items)
:phase (unquote (get data "phase")) :phase (unquote phase)
:transport (unquote (get data "transport"))))) :transport (unquote transport))))
"async-io" "async-io"
(quote (~async-io-demo/content)) (quote (~async-io-demo/content))
"affinity" "affinity"
(let (let-match
((data (helper "affinity-demo-data"))) {:components components :page-plans page-plans}
(helper "affinity-demo-data")
(quasiquote (quasiquote
(~affinity-demo/content (~affinity-demo/content
:components (unquote (get data "components")) :components (unquote components)
:page-plans (unquote (get data "page-plans"))))) :page-plans (unquote page-plans))))
"optimistic" "optimistic"
(let (let-match
((data (helper "optimistic-demo-data"))) {:server-time server-time :items items}
(helper "optimistic-demo-data")
(quasiquote (quasiquote
(~optimistic-demo/content (~optimistic-demo/content
:items (unquote (get data "items")) :items (unquote items)
:server-time (unquote (get data "server-time"))))) :server-time (unquote server-time))))
"offline" "offline"
(let (let-match
((data (helper "offline-demo-data"))) {:server-time server-time :notes notes}
(helper "offline-demo-data")
(quasiquote (quasiquote
(~offline-demo/content (~offline-demo/content
:notes (unquote (get data "notes")) :notes (unquote notes)
:server-time (unquote (get data "server-time"))))) :server-time (unquote server-time))))
:else (quote (~plans/isomorphic/plan-isomorphic-content)))))) :else (quote (~plans/isomorphic/plan-isomorphic-content))))))
(define (define
@@ -261,16 +267,19 @@
:else (let :else (let
((found-spec (find-spec slug))) ((found-spec (find-spec slug)))
(if (if
found-spec
(let-match
{:desc desc :prose prose :title title :filename filename}
found-spec found-spec
(let (let
((src (helper "read-spec-file" (get found-spec "filename")))) ((src (helper "read-spec-file" filename)))
(quasiquote (quasiquote
(~specs/detail-content (~specs/detail-content
:spec-title (unquote (get found-spec "title")) :spec-title (unquote title)
:spec-desc (unquote (get found-spec "desc")) :spec-desc (unquote desc)
:spec-filename (unquote (get found-spec "filename")) :spec-filename (unquote filename)
:spec-source (unquote src) :spec-source (unquote src)
:spec-prose (unquote (get found-spec "prose"))))) :spec-prose (unquote prose)))))
(quasiquote (~specs/not-found :slug (unquote slug))))))))) (quasiquote (~specs/not-found :slug (unquote slug)))))))))
(define (define
@@ -324,54 +333,67 @@
(case (case
slug slug
"self-hosting" "self-hosting"
(let-match
{:g1-output g1-output :py-sx-source py-sx-source :g0-bytes g0-bytes :verification-status verification-status :g0-output g0-output :defines-total defines-total :defines-matched defines-matched :g0-lines g0-lines}
data
(quasiquote (quasiquote
(~specs/bootstrapper-self-hosting-content (~specs/bootstrapper-self-hosting-content
:py-sx-source (unquote (get data "py-sx-source")) :py-sx-source (unquote py-sx-source)
:g0-output (unquote (get data "g0-output")) :g0-output (unquote g0-output)
:g1-output (unquote (get data "g1-output")) :g1-output (unquote g1-output)
:defines-matched (unquote (get data "defines-matched")) :defines-matched (unquote defines-matched)
:defines-total (unquote (get data "defines-total")) :defines-total (unquote defines-total)
:g0-lines (unquote (get data "g0-lines")) :g0-lines (unquote g0-lines)
:g0-bytes (unquote (get data "g0-bytes")) :g0-bytes (unquote g0-bytes)
:verification-status (unquote (get data "verification-status")))) :verification-status (unquote verification-status))))
"self-hosting-js" "self-hosting-js"
(let-match
{:js-sx-source js-sx-source :verification-status verification-status :js-sx-lines js-sx-lines :defines-total defines-total :defines-matched defines-matched}
data
(quasiquote (quasiquote
(~specs/bootstrapper-self-hosting-js-content (~specs/bootstrapper-self-hosting-js-content
:js-sx-source (unquote (get data "js-sx-source")) :js-sx-source (unquote js-sx-source)
:defines-matched (unquote (get data "defines-matched")) :defines-matched (unquote defines-matched)
:defines-total (unquote (get data "defines-total")) :defines-total (unquote defines-total)
:js-sx-lines (unquote (get data "js-sx-lines")) :js-sx-lines (unquote js-sx-lines)
:verification-status (unquote (get data "verification-status")))) :verification-status (unquote verification-status))))
"python" "python"
(let-match
{:bootstrapper-source bootstrapper-source :bootstrapped-output bootstrapped-output}
data
(quasiquote (quasiquote
(~specs/bootstrapper-py-content (~specs/bootstrapper-py-content
:bootstrapper-source (unquote (get data "bootstrapper-source")) :bootstrapper-source (unquote bootstrapper-source)
:bootstrapped-output (unquote (get data "bootstrapped-output")))) :bootstrapped-output (unquote bootstrapped-output))))
"page-helpers" "page-helpers"
(let (let-match
((ph-data (helper "page-helpers-demo-data"))) {:attr-result attr-result :sf-source sf-source :ref-ms ref-ms :req-attrs req-attrs :attr-detail attr-detail :attr-keys attr-keys :server-total-ms server-total-ms :attr-ms attr-ms :comp-ms comp-ms :routing-ms routing-ms :comp-source comp-source :routing-result routing-result :sf-categories sf-categories :sf-total sf-total :sf-ms sf-ms :ref-sample ref-sample}
(helper "page-helpers-demo-data")
(quasiquote (quasiquote
(~page-helpers-demo/content (~page-helpers-demo/content
:sf-categories (unquote (get ph-data "sf-categories")) :sf-categories (unquote sf-categories)
:sf-total (unquote (get ph-data "sf-total")) :sf-total (unquote sf-total)
:sf-ms (unquote (get ph-data "sf-ms")) :sf-ms (unquote sf-ms)
:ref-sample (unquote (get ph-data "ref-sample")) :ref-sample (unquote ref-sample)
:ref-ms (unquote (get ph-data "ref-ms")) :ref-ms (unquote ref-ms)
:attr-result (unquote (get ph-data "attr-result")) :attr-result (unquote attr-result)
:attr-ms (unquote (get ph-data "attr-ms")) :attr-ms (unquote attr-ms)
:comp-source (unquote (get ph-data "comp-source")) :comp-source (unquote comp-source)
:comp-ms (unquote (get ph-data "comp-ms")) :comp-ms (unquote comp-ms)
:routing-result (unquote (get ph-data "routing-result")) :routing-result (unquote routing-result)
:routing-ms (unquote (get ph-data "routing-ms")) :routing-ms (unquote routing-ms)
:server-total-ms (unquote (get ph-data "server-total-ms")) :server-total-ms (unquote server-total-ms)
:sf-source (unquote (get ph-data "sf-source")) :sf-source (unquote sf-source)
:attr-detail (unquote (get ph-data "attr-detail")) :attr-detail (unquote attr-detail)
:req-attrs (unquote (get ph-data "req-attrs")) :req-attrs (unquote req-attrs)
:attr-keys (unquote (get ph-data "attr-keys"))))) :attr-keys (unquote attr-keys))))
:else (quasiquote :else (let-match
{:bootstrapper-source bootstrapper-source :bootstrapped-output bootstrapped-output}
data
(quasiquote
(~specs/bootstrapper-js-content (~specs/bootstrapper-js-content
:bootstrapper-source (unquote (get data "bootstrapper-source")) :bootstrapper-source (unquote bootstrapper-source)
:bootstrapped-output (unquote (get data "bootstrapped-output")))))))))) :bootstrapped-output (unquote bootstrapped-output))))))))))
(define (define
test test
@@ -379,24 +401,26 @@
(slug) (slug)
(if (if
(nil? slug) (nil? slug)
(let (let-match
((data (helper "run-modular-tests" "all"))) {:server-results server-results :parser-source parser-source :framework-source framework-source :eval-source eval-source :router-source router-source :engine-source engine-source :render-source render-source :deps-source deps-source}
(helper "run-modular-tests" "all")
(quasiquote (quasiquote
(~testing/overview-content (~testing/overview-content
:server-results (unquote (get data "server-results")) :server-results (unquote server-results)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:eval-source (unquote (get data "eval-source")) :eval-source (unquote eval-source)
:parser-source (unquote (get data "parser-source")) :parser-source (unquote parser-source)
:router-source (unquote (get data "router-source")) :router-source (unquote router-source)
:render-source (unquote (get data "render-source")) :render-source (unquote render-source)
:deps-source (unquote (get data "deps-source")) :deps-source (unquote deps-source)
:engine-source (unquote (get data "engine-source"))))) :engine-source (unquote engine-source))))
(case (case
slug slug
"runners" "runners"
(quote (~testing/runners-content)) (quote (~testing/runners-content))
:else (let :else (let-match
((data (helper "run-modular-tests" slug))) {:server-results server-results :spec-source spec-source :framework-source framework-source}
(helper "run-modular-tests" slug)
(case (case
slug slug
"eval" "eval"
@@ -404,67 +428,67 @@
(~testing/spec-content (~testing/spec-content
:spec-name "eval" :spec-name "eval"
:spec-title "Evaluator Tests" :spec-title "Evaluator Tests"
:spec-desc "81 tests covering the core evaluator and all primitives." :spec-desc "81 tests covering the core evaluator — literals, symbols, special forms, closures."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"parser" "parser"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "parser" :spec-name "parser"
:spec-title "Parser Tests" :spec-title "Parser Tests"
:spec-desc "39 tests covering tokenization and parsing." :spec-desc "39 tests covering tokenization and parsing."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"router" "router"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "router" :spec-name "router"
:spec-title "Router Tests" :spec-title "Router Tests"
:spec-desc "18 tests covering client-side route matching." :spec-desc "18 tests covering client-side route matching."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"render" "render"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "render" :spec-name "render"
:spec-title "Renderer Tests" :spec-title "Renderer Tests"
:spec-desc "23 tests covering HTML rendering." :spec-desc "23 tests covering HTML rendering."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"deps" "deps"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "deps" :spec-name "deps"
:spec-title "Dependency Analysis Tests" :spec-title "Dependency Analysis Tests"
:spec-desc "33 tests covering component dependency analysis." :spec-desc "33 tests covering component dependency analysis."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"engine" "engine"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "engine" :spec-name "engine"
:spec-title "Engine Tests" :spec-title "Engine Tests"
:spec-desc "37 tests covering engine pure functions." :spec-desc "37 tests covering engine pure functions."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
"orchestration" "orchestration"
(quasiquote (quasiquote
(~testing/spec-content (~testing/spec-content
:spec-name "orchestration" :spec-name "orchestration"
:spec-title "Orchestration Tests" :spec-title "Orchestration Tests"
:spec-desc "17 tests covering orchestration." :spec-desc "17 tests covering orchestration."
:spec-source (unquote (get data "spec-source")) :spec-source (unquote spec-source)
:framework-source (unquote (get data "framework-source")) :framework-source (unquote framework-source)
:server-results (unquote (get data "server-results")))) :server-results (unquote server-results)))
:else (quasiquote :else (quasiquote
(~testing/overview-content (~testing/overview-content
:server-results (unquote (get data "server-results")))))))))) :server-results (unquote server-results)))))))))
(define (define
reference reference
@@ -478,26 +502,32 @@
(case (case
slug slug
"attributes" "attributes"
(let-match
{:req-attrs req-attrs :beh-attrs beh-attrs :uniq-attrs uniq-attrs}
data
(quasiquote (quasiquote
(~reference/attrs-content (~reference/attrs-content
:req-table (~docs/attr-table-from-data :req-table (~docs/attr-table-from-data
:title "Request Attributes" :title "Request Attributes"
:attrs (unquote (get data "req-attrs"))) :attrs (unquote req-attrs))
:beh-table (~docs/attr-table-from-data :beh-table (~docs/attr-table-from-data
:title "Behavior Attributes" :title "Behavior Attributes"
:attrs (unquote (get data "beh-attrs"))) :attrs (unquote beh-attrs))
:uniq-table (~docs/attr-table-from-data :uniq-table (~docs/attr-table-from-data
:title "Unique to sx" :title "Unique to sx"
:attrs (unquote (get data "uniq-attrs"))))) :attrs (unquote uniq-attrs)))))
"headers" "headers"
(let-match
{:req-headers req-headers :resp-headers resp-headers}
data
(quasiquote (quasiquote
(~reference/headers-content (~reference/headers-content
:req-table (~docs/headers-table-from-data :req-table (~docs/headers-table-from-data
:title "Request Headers" :title "Request Headers"
:headers (unquote (get data "req-headers"))) :headers (unquote req-headers))
:resp-table (~docs/headers-table-from-data :resp-table (~docs/headers-table-from-data
:title "Response Headers" :title "Response Headers"
:headers (unquote (get data "resp-headers"))))) :headers (unquote resp-headers)))))
"events" "events"
(quasiquote (quasiquote
(~reference/events-content (~reference/events-content
@@ -514,17 +544,20 @@
:col1 "Method" :col1 "Method"
:col2 "Description" :col2 "Description"
:items (unquote (get data "js-api-list"))))) :items (unquote (get data "js-api-list")))))
:else (quasiquote :else (let-match
{:req-attrs req-attrs :beh-attrs beh-attrs :uniq-attrs uniq-attrs}
data
(quasiquote
(~reference/attrs-content (~reference/attrs-content
:req-table (~docs/attr-table-from-data :req-table (~docs/attr-table-from-data
:title "Request Attributes" :title "Request Attributes"
:attrs (unquote (get data "req-attrs"))) :attrs (unquote req-attrs))
:beh-table (~docs/attr-table-from-data :beh-table (~docs/attr-table-from-data
:title "Behavior Attributes" :title "Behavior Attributes"
:attrs (unquote (get data "beh-attrs"))) :attrs (unquote beh-attrs))
:uniq-table (~docs/attr-table-from-data :uniq-table (~docs/attr-table-from-data
:title "Unique to sx" :title "Unique to sx"
:attrs (unquote (get data "uniq-attrs")))))))))) :attrs (unquote uniq-attrs))))))))))
(define (define
reference-detail reference-detail
@@ -541,39 +574,48 @@
(if (if
(get data "attr-not-found") (get data "attr-not-found")
(quasiquote (~reference/attr-not-found :slug (unquote slug))) (quasiquote (~reference/attr-not-found :slug (unquote slug)))
(let-match
{:attr-handler attr-handler :attr-title attr-title :attr-example attr-example :attr-description attr-description :attr-demo attr-demo :attr-wire-id attr-wire-id}
data
(quasiquote (quasiquote
(~reference/attr-detail-content (~reference/attr-detail-content
:title (unquote (get data "attr-title")) :title (unquote attr-title)
:description (unquote (get data "attr-description")) :description (unquote attr-description)
:demo (unquote (get data "attr-demo")) :demo (unquote attr-demo)
:example-code (unquote (get data "attr-example")) :example-code (unquote attr-example)
:handler-code (unquote (get data "attr-handler")) :handler-code (unquote attr-handler)
:wire-placeholder-id (unquote (get data "attr-wire-id")))))) :wire-placeholder-id (unquote attr-wire-id))))))
"headers" "headers"
(let (let
((data (helper "header-detail-data" slug))) ((data (helper "header-detail-data" slug)))
(if (if
(get data "header-not-found") (get data "header-not-found")
(quasiquote (~reference/attr-not-found :slug (unquote slug))) (quasiquote (~reference/attr-not-found :slug (unquote slug)))
(let-match
{:header-description header-description :header-demo header-demo :header-title header-title :header-example header-example :header-direction header-direction}
data
(quasiquote (quasiquote
(~reference/header-detail-content (~reference/header-detail-content
:title (unquote (get data "header-title")) :title (unquote header-title)
:direction (unquote (get data "header-direction")) :direction (unquote header-direction)
:description (unquote (get data "header-description")) :description (unquote header-description)
:example-code (unquote (get data "header-example")) :example-code (unquote header-example)
:demo (unquote (get data "header-demo")))))) :demo (unquote header-demo))))))
"events" "events"
(let (let
((data (helper "event-detail-data" slug))) ((data (helper "event-detail-data" slug)))
(if (if
(get data "event-not-found") (get data "event-not-found")
(quasiquote (~reference/attr-not-found :slug (unquote slug))) (quasiquote (~reference/attr-not-found :slug (unquote slug)))
(let-match
{:event-example event-example :event-demo event-demo :event-description event-description :event-title event-title}
data
(quasiquote (quasiquote
(~reference/event-detail-content (~reference/event-detail-content
:title (unquote (get data "event-title")) :title (unquote event-title)
:description (unquote (get data "event-description")) :description (unquote event-description)
:example-code (unquote (get data "event-example")) :example-code (unquote event-example)
:demo (unquote (get data "event-demo")))))) :demo (unquote event-demo))))))
:else nil)))) :else nil))))
(define (define

View File

@@ -1,6 +1,7 @@
(define-library (sx signals) (define-library
(sx signals)
(export (export
make-signal make-signal
signal? signal?
@@ -26,29 +27,33 @@
with-island-scope with-island-scope
register-in-scope) register-in-scope)
(begin (begin
(define (define
make-signal make-signal
(fn (fn
(value) (value)
(dict "__signal" true "value" value "subscribers" (list) "deps" (list)))) (dict
"__signal"
true
"value"
value
"subscribers"
(list)
"deps"
(list))))
(define signal? (fn (x) (and (dict? x) (has-key? x "__signal")))) (define signal? (fn (x) (and (dict? x) (has-key? x "__signal"))))
(define signal-value (fn (s) (get s "value"))) (define signal-value (fn (s) (get s "value")))
(define signal-set-value! (fn (s v) (dict-set! s "value" v))) (define signal-set-value! (fn (s v) (dict-set! s "value" v)))
(define signal-subscribers (fn (s) (get s "subscribers"))) (define signal-subscribers (fn (s) (get s "subscribers")))
(define (define
signal-add-sub! signal-add-sub!
(fn (fn
(s f) (s f)
(when (when
(not (contains? (get s "subscribers") f)) (not (contains? (get s "subscribers") f))
(dict-set! s "subscribers" (append (get s "subscribers") (list f)))))) (dict-set!
s
"subscribers"
(append (get s "subscribers") (list f))))))
(define (define
signal-remove-sub! signal-remove-sub!
(fn (fn
@@ -56,17 +61,15 @@
(dict-set! (dict-set!
s s
"subscribers" "subscribers"
(filter (fn (sub) (not (identical? sub f))) (get s "subscribers"))))) (filter
(fn (sub) (not (identical? sub f)))
(get s "subscribers")))))
(define signal-deps (fn (s) (get s "deps"))) (define signal-deps (fn (s) (get s "deps")))
(define signal-set-deps! (fn (s deps) (dict-set! s "deps" deps))) (define signal-set-deps! (fn (s deps) (dict-set! s "deps" deps)))
(define (define
signal signal
:effects () :effects ()
(fn ((initial-value :as any)) (make-signal initial-value))) (fn ((initial-value :as any)) (make-signal initial-value)))
(define (define
deref deref
:effects () :effects ()
@@ -80,13 +83,13 @@
(when (when
ctx ctx
(let (let
((dep-list (get ctx "deps")) (notify-fn (get ctx "notify"))) {:notify notify-fn :deps dep-list}
ctx
(when (when
(not (contains? dep-list s)) (not (contains? dep-list s))
(append! dep-list s) (append! dep-list s)
(signal-add-sub! s notify-fn)))) (signal-add-sub! s notify-fn))))
(signal-value s))))) (signal-value s)))))
(define (define
reset! reset!
:effects (mutation) :effects (mutation)
@@ -100,7 +103,6 @@
(not (identical? old value)) (not (identical? old value))
(signal-set-value! s value) (signal-set-value! s value)
(notify-subscribers s)))))) (notify-subscribers s))))))
(define (define
swap! swap!
:effects (mutation) :effects (mutation)
@@ -115,7 +117,6 @@
(not (identical? old new-val)) (not (identical? old new-val))
(signal-set-value! s new-val) (signal-set-value! s new-val)
(notify-subscribers s)))))) (notify-subscribers s))))))
(define (define
computed computed
:effects (mutation) :effects (mutation)
@@ -128,7 +129,6 @@
(recompute) (recompute)
(register-in-scope (fn () (dispose-computed s))) (register-in-scope (fn () (dispose-computed s)))
s)))) s))))
(define (define
effect effect
:effects (mutation) :effects (mutation)
@@ -143,11 +143,8 @@
((dispose-fn (fn () (set! disposed true) (when cleanup-fn (cek-call cleanup-fn nil)) (for-each (fn ((dep :as signal)) (signal-remove-sub! dep run-effect)) deps) (set! deps (list))))) ((dispose-fn (fn () (set! disposed true) (when cleanup-fn (cek-call cleanup-fn nil)) (for-each (fn ((dep :as signal)) (signal-remove-sub! dep run-effect)) deps) (set! deps (list)))))
(register-in-scope dispose-fn) (register-in-scope dispose-fn)
dispose-fn))))) dispose-fn)))))
(define *batch-depth* 0) (define *batch-depth* 0)
(define *batch-queue* (list)) (define *batch-queue* (list))
(define (define
batch batch
:effects (mutation) :effects (mutation)
@@ -176,7 +173,6 @@
(signal-subscribers s))) (signal-subscribers s)))
queue) queue)
(for-each (fn ((sub :as lambda)) (sub)) pending)))))) (for-each (fn ((sub :as lambda)) (sub)) pending))))))
(define (define
notify-subscribers notify-subscribers
:effects (mutation) :effects (mutation)
@@ -186,14 +182,12 @@
(> *batch-depth* 0) (> *batch-depth* 0)
(when (not (contains? *batch-queue* s)) (append! *batch-queue* s)) (when (not (contains? *batch-queue* s)) (append! *batch-queue* s))
(flush-subscribers s)))) (flush-subscribers s))))
(define (define
flush-subscribers flush-subscribers
:effects (mutation) :effects (mutation)
(fn (fn
((s :as dict)) ((s :as dict))
(for-each (fn (sub) (cek-call sub nil)) (signal-subscribers s)))) (for-each (fn (sub) (cek-call sub nil)) (signal-subscribers s))))
(define (define
dispose-computed dispose-computed
:effects (mutation) :effects (mutation)
@@ -205,7 +199,6 @@
(fn ((dep :as signal)) (signal-remove-sub! dep nil)) (fn ((dep :as signal)) (signal-remove-sub! dep nil))
(signal-deps s)) (signal-deps s))
(signal-set-deps! s (list))))) (signal-set-deps! s (list)))))
(define (define
with-island-scope with-island-scope
:effects (mutation) :effects (mutation)
@@ -213,7 +206,6 @@
((scope-fn :as lambda) (body-fn :as lambda)) ((scope-fn :as lambda) (body-fn :as lambda))
(scope-push! "sx-island-scope" scope-fn) (scope-push! "sx-island-scope" scope-fn)
(let ((result (body-fn))) (scope-pop! "sx-island-scope") result))) (let ((result (body-fn))) (scope-pop! "sx-island-scope") result)))
(define (define
register-in-scope register-in-scope
:effects (mutation) :effects (mutation)
@@ -221,10 +213,7 @@
((disposable :as lambda)) ((disposable :as lambda))
(let (let
((collector (scope-peek "sx-island-scope"))) ((collector (scope-peek "sx-island-scope")))
(when collector (cek-call collector (list disposable)))))) (when collector (cek-call collector (list disposable)))))))) ;; end define-library
)) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (sx signals)) (import (sx signals))

View File

@@ -9,40 +9,57 @@
;; Actor ;; Actor
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-actor (defhandler
pub-actor
:path "/pub/actor" :path "/pub/actor"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((actor (helper "pub-actor-data"))) (let-match
{:domain domain :summary summary :display-name display-name :public-key-pem public-key-pem}
(helper "pub-actor-data")
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(str (str
"(SxActor" "(SxActor"
"\n :id \"https://" (get actor "domain") "/pub/actor\"" "\n :id \"https://"
domain
"/pub/actor\""
"\n :type \"SxPublisher\"" "\n :type \"SxPublisher\""
"\n :name \"" (get actor "display-name") "\"" "\n :name \""
"\n :summary \"" (get actor "summary") "\"" display-name
"\""
"\n :summary \""
summary
"\""
"\n :inbox \"/pub/inbox\"" "\n :inbox \"/pub/inbox\""
"\n :outbox \"/pub/outbox\"" "\n :outbox \"/pub/outbox\""
"\n :followers \"/pub/followers\"" "\n :followers \"/pub/followers\""
"\n :following \"/pub/following\"" "\n :following \"/pub/following\""
"\n :public-key-pem \"" (get actor "public-key-pem") "\")")))) "\n :public-key-pem \""
public-key-pem
"\")"))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
;; Webfinger ;; Webfinger
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-webfinger (defhandler
pub-webfinger
:path "/pub/webfinger" :path "/pub/webfinger"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((resource (helper "request-arg" "resource" "")) (let
(actor (helper "pub-actor-data"))) ((resource (helper "request-arg" "resource" "")))
(let ((expected (str "acct:" (get actor "preferred-username") "@" (get actor "domain")))) (let-match
(if (!= resource expected) {:domain domain :preferred-username preferred-username}
(helper "pub-actor-data")
(let
((expected (str "acct:" preferred-username "@" domain)))
(if
(!= resource expected)
(do (do
(set-response-status 404) (set-response-status 404)
(str "(Error :message \"Resource not found\")")) (str "(Error :message \"Resource not found\")"))
@@ -50,30 +67,31 @@
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(str (str
"(SxWebfinger" "(SxWebfinger"
"\n :subject \"" expected "\"" "\n :subject \""
"\n :actor \"https://" (get actor "domain") "/pub/actor\"" expected
"\n :type \"SxPublisher\")")))))) "\""
"\n :actor \"https://"
domain
"/pub/actor\""
"\n :type \"SxPublisher\")")))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
;; Collections ;; Collections
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-collections (defhandler
pub-collections
:path "/pub/collections" :path "/pub/collections"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((collections (helper "pub-collections-data"))) (let
((collections (helper "pub-collections-data")))
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(let ((items (map (fn (c) (let
(str "\n (SxCollection" ((items (map (fn (c) (let-match {:description description :slug slug :name name} c (str "\n (SxCollection" " :slug \"" slug "\"" " :name \"" name "\"" " :description \"" description "\"" " :href \"/pub/" slug "\")"))) collections)))
" :slug \"" (get c "slug") "\""
" :name \"" (get c "name") "\""
" :description \"" (get c "description") "\""
" :href \"/pub/" (get c "slug") "\")"))
collections)))
(str "(SxCollections" (join "" items) ")"))))) (str "(SxCollections" (join "" items) ")")))))
@@ -81,21 +99,33 @@
;; Status ;; Status
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-status (defhandler
pub-status
:path "/pub/status" :path "/pub/status"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((status (helper "pub-status-data"))) (let-match
{:db db :domain domain :healthy healthy :ipfs ipfs :actor actor}
(helper "pub-status-data")
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(str (str
"(SxPubStatus" "(SxPubStatus"
"\n :healthy " (get status "healthy") "\n :healthy "
"\n :db \"" (get status "db") "\"" healthy
"\n :ipfs \"" (get status "ipfs") "\"" "\n :db \""
"\n :actor \"" (get status "actor") "\"" db
"\n :domain \"" (or (get status "domain") "unknown") "\")")))) "\""
"\n :ipfs \""
ipfs
"\""
"\n :actor \""
actor
"\""
"\n :domain \""
(or domain "unknown")
"\")"))))
;; ========================================================================== ;; ==========================================================================
@@ -107,72 +137,100 @@
;; Publish ;; Publish
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-publish (defhandler
pub-publish
:path "/pub/publish" :path "/pub/publish"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
(&key) (&key)
(let ((collection (helper "request-form" "collection" "")) (let
((collection (helper "request-form" "collection" ""))
(slug (helper "request-form" "slug" "")) (slug (helper "request-form" "slug" ""))
(content (helper "request-form" "content" "")) (content (helper "request-form" "content" ""))
(title (helper "request-form" "title" "")) (title (helper "request-form" "title" ""))
(summary (helper "request-form" "summary" ""))) (summary (helper "request-form" "summary" "")))
(if (or (= collection "") (= slug "") (= content "")) (if
(or (= collection "") (= slug "") (= content ""))
(do (do
(set-response-status 400) (set-response-status 400)
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
"(Error :message \"Missing collection, slug, or content\")") "(Error :message \"Missing collection, slug, or content\")")
(let ((result (helper "pub-publish" collection slug content title summary))) (let
(if (get result "error") ((result (helper "pub-publish" collection slug content title summary)))
(if
(get result "error")
(do (do
(set-response-status 500) (set-response-status 500)
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(str "(Error :message \"" (get result "error") "\")")) (str "(Error :message \"" (get result "error") "\")"))
(let-match
{:cid cid :hash hash :size size :title title :path path :slug slug :collection collection}
result
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(str (str
"(Published" "(Published"
"\n :path \"" (get result "path") "\"" "\n :path \""
"\n :cid \"" (get result "cid") "\"" path
"\n :hash \"" (get result "hash") "\"" "\""
"\n :size " (get result "size") "\n :cid \""
"\n :collection \"" (get result "collection") "\"" cid
"\n :slug \"" (get result "slug") "\"" "\""
"\n :title \"" (get result "title") "\")"))))))) "\n :hash \""
hash
"\""
"\n :size "
size
"\n :collection \""
collection
"\""
"\n :slug \""
slug
"\""
"\n :title \""
title
"\")"))))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
;; Browse collection ;; Browse collection
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-browse-collection (defhandler
pub-browse-collection
:path "/pub/browse/<collection_slug>" :path "/pub/browse/<collection_slug>"
:method :get :method :get
:returns "element" :returns "element"
(&key collection_slug) (&key collection_slug)
(let ((data (helper "pub-collection-items" collection_slug))) (let
(if (get data "error") ((data (helper "pub-collection-items" collection_slug)))
(if
(get data "error")
(do (do
(set-response-status 404) (set-response-status 404)
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(str "(Error :message \"" (get data "error") "\")")) (str "(Error :message \"" (get data "error") "\")"))
(let-match
{:description description :items items-data :collection collection :name name}
data
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(let ((items (map (fn (d) (let
(str "\n (SxDocument" ((items (map (fn (d) (let-match {:cid cid :size size :summary summary :title title :slug slug} d (str "\n (SxDocument" " :slug \"" slug "\"" " :title \"" title "\"" " :summary \"" summary "\"" " :cid \"" cid "\"" " :size " size ")"))) items-data)))
" :slug \"" (get d "slug") "\""
" :title \"" (get d "title") "\""
" :summary \"" (get d "summary") "\""
" :cid \"" (get d "cid") "\""
" :size " (get d "size") ")"))
(get data "items"))))
(str (str
"(SxCollection" "(SxCollection"
"\n :slug \"" (get data "collection") "\"" "\n :slug \""
"\n :name \"" (get data "name") "\"" collection
"\n :description \"" (get data "description") "\"" "\""
(join "" items) ")")))))) "\n :name \""
name
"\""
"\n :description \""
description
"\""
(join "" items)
")")))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
@@ -226,26 +284,30 @@
;; Outbox ;; Outbox
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-outbox (defhandler
pub-outbox
:path "/pub/outbox" :path "/pub/outbox"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((page (helper "request-arg" "page" "")) (let
((page (helper "request-arg" "page" ""))
(data (helper "pub-outbox-data" page))) (data (helper "pub-outbox-data" page)))
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(let ((items (map (fn (a) (let-match
(str "\n (" (get a "type") {:total total :page page :items items-data}
" :object-type \"" (get a "object-type") "\"" data
" :published \"" (get a "published") "\"" (let
" :cid \"" (get a "cid") "\")")) ((items (map (fn (a) (let-match {:cid cid :type type :object-type object-type :published published} a (str "\n (" type " :object-type \"" object-type "\"" " :published \"" published "\"" " :cid \"" cid "\")"))) items-data)))
(get data "items"))))
(str (str
"(SxOutbox" "(SxOutbox"
"\n :total " (get data "total") "\n :total "
"\n :page " (get data "page") total
(join "" items) ")"))))) "\n :page "
page
(join "" items)
")"))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
@@ -275,48 +337,59 @@
;; Follow a remote server ;; Follow a remote server
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-follow (defhandler
pub-follow
:path "/pub/follow" :path "/pub/follow"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
(&key) (&key)
(let ((actor-url (helper "request-form" "actor_url" ""))) (let
(if (= actor-url "") ((actor-url (helper "request-form" "actor_url" "")))
(if
(= actor-url "")
(do (do
(set-response-status 400) (set-response-status 400)
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
"(Error :message \"Missing actor_url\")") "(Error :message \"Missing actor_url\")")
(let ((result (helper "pub-follow-remote" actor-url))) (let
((result (helper "pub-follow-remote" actor-url)))
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(if (get result "error") (if
(get result "error")
(do (do
(set-response-status 502) (set-response-status 502)
(str "(Error :message \"" (get result "error") "\")")) (str "(Error :message \"" (get result "error") "\")"))
(let-match
{:status status :actor-url actor-url}
result
(str (str
"(FollowSent" "(FollowSent"
"\n :actor-url \"" (get result "actor-url") "\"" "\n :actor-url \""
"\n :status \"" (get result "status") "\")"))))))) actor-url
"\""
"\n :status \""
status
"\")"))))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
;; Followers ;; Followers
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-followers (defhandler
pub-followers
:path "/pub/followers" :path "/pub/followers"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
(let ((data (helper "pub-followers-data"))) (let
((data (helper "pub-followers-data")))
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(let ((items (map (fn (f) (let
(str "\n (SxFollower" ((items (map (fn (f) (let-match {:actor-url actor-url :acct acct} f (str "\n (SxFollower" " :acct \"" acct "\"" " :actor-url \"" actor-url "\")"))) data)))
" :acct \"" (get f "acct") "\""
" :actor-url \"" (get f "actor-url") "\")"))
data)))
(str "(SxFollowers" (join "" items) ")"))))) (str "(SxFollowers" (join "" items) ")")))))
@@ -348,48 +421,80 @@
;; Anchor pending activities ;; Anchor pending activities
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-anchor (defhandler
pub-anchor
:path "/pub/anchor" :path "/pub/anchor"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
(&key) (&key)
(let ((result (helper "pub-anchor-pending"))) (let-match
{:tree-cid tree-cid :status status :count count :ots-proof-cid ots-proof-cid :merkle-root merkle-root}
(helper "pub-anchor-pending")
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(if (= (get result "status") "nothing-to-anchor") (if
(= status "nothing-to-anchor")
"(Anchor :status \"nothing-to-anchor\" :count 0)" "(Anchor :status \"nothing-to-anchor\" :count 0)"
(str (str
"(Anchor" "(Anchor"
"\n :status \"" (get result "status") "\"" "\n :status \""
"\n :count " (get result "count") status
"\n :merkle-root \"" (get result "merkle-root") "\"" "\""
"\n :tree-cid \"" (get result "tree-cid") "\"" "\n :count "
"\n :ots-proof-cid \"" (get result "ots-proof-cid") "\")"))))) count
"\n :merkle-root \""
merkle-root
"\""
"\n :tree-cid \""
tree-cid
"\""
"\n :ots-proof-cid \""
ots-proof-cid
"\")")))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
;; Verify a CID's anchor ;; Verify a CID's anchor
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler pub-verify (defhandler
pub-verify
:path "/pub/verify/<cid>" :path "/pub/verify/<cid>"
:method :get :method :get
:returns "element" :returns "element"
(&key cid) (&key cid)
(let ((data (helper "pub-verify-anchor" cid))) (let
((data (helper "pub-verify-anchor" cid)))
(do (do
(set-response-header "Content-Type" "text/sx; charset=utf-8") (set-response-header "Content-Type" "text/sx; charset=utf-8")
(if (get data "error") (if
(get data "error")
(do (do
(set-response-status 404) (set-response-status 404)
(str "(Error :message \"" (get data "error") "\")")) (str "(Error :message \"" (get data "error") "\")"))
(let-match
{:cid cid* :tree-cid tree-cid :status status :verified verified :ots-proof-cid ots-proof-cid :merkle-root merkle-root :published published}
data
(str (str
"(AnchorVerification" "(AnchorVerification"
"\n :cid \"" (get data "cid") "\"" "\n :cid \""
"\n :status \"" (get data "status") "\"" cid*
"\n :verified " (get data "verified") "\""
"\n :merkle-root \"" (get data "merkle-root") "\"" "\n :status \""
"\n :tree-cid \"" (get data "tree-cid") "\"" status
"\n :ots-proof-cid \"" (get data "ots-proof-cid") "\"" "\""
"\n :published \"" (get data "published") "\")"))))) "\n :verified "
verified
"\n :merkle-root \""
merkle-root
"\""
"\n :tree-cid \""
tree-cid
"\""
"\n :ots-proof-cid \""
ots-proof-cid
"\""
"\n :published \""
published
"\")"))))))

View File

@@ -2,25 +2,27 @@
~specs-explorer/spec-explorer-content ~specs-explorer/spec-explorer-content
(&key data) (&key data)
:affinity :server :affinity :server
(let-match
{:stats stats :desc desc :title title :filename filename :platform-interface platform-interface :sections sections}
data
(~docs/page (~docs/page
:title (str (get data "title") " — Explorer") :title (str title " — Explorer")
(~specs-explorer/spec-explorer-header (~specs-explorer/spec-explorer-header
:filename (get data "filename") :filename filename
:title (get data "title") :title title
:desc (get data "desc") :desc desc
:slug (replace (get data "filename") ".sx" "")) :slug (replace filename ".sx" ""))
(~specs-explorer/spec-explorer-stats :stats (get data "stats")) (~specs-explorer/spec-explorer-stats :stats stats)
(map (map
(fn (fn
(section) (section)
(~specs-explorer/spec-explorer-section (~specs-explorer/spec-explorer-section
:section section :section section
:filename (get data "filename"))) :filename filename))
(get data "sections")) sections)
(when (when
(not (empty? (get data "platform-interface"))) (not (empty? platform-interface))
(~specs-explorer/spec-platform-interface (~specs-explorer/spec-platform-interface :items platform-interface)))))
:items (get data "platform-interface")))))
(defcomp (defcomp
~specs-explorer/spec-explorer-header ~specs-explorer/spec-explorer-header
@@ -46,80 +48,91 @@
(defcomp (defcomp
~specs-explorer/spec-explorer-stats ~specs-explorer/spec-explorer-stats
(&key stats) (&key stats)
(let-match
{:lines lines :io-count io-count :render-count render-count :pure-count pure-count :mutation-count mutation-count :test-total test-total :total-defines total-defines}
stats
(div (div
(~tw :tokens "flex flex-wrap gap-2 mb-6 text-xs") (~tw :tokens "flex flex-wrap gap-2 mb-6 text-xs")
(span (span
(~tw :tokens "bg-stone-100 text-stone-600 px-2 py-0.5 rounded font-medium") (~tw
(str (get stats "total-defines") " defines")) :tokens "bg-stone-100 text-stone-600 px-2 py-0.5 rounded font-medium")
(str total-defines " defines"))
(when (when
(> (get stats "pure-count") 0) (> pure-count 0)
(span (span
(~tw :tokens "bg-green-100 text-green-700 px-2 py-0.5 rounded") (~tw :tokens "bg-green-100 text-green-700 px-2 py-0.5 rounded")
(str (get stats "pure-count") " pure"))) (str pure-count " pure")))
(when (when
(> (get stats "mutation-count") 0) (> mutation-count 0)
(span (span
(~tw :tokens "bg-amber-100 text-amber-700 px-2 py-0.5 rounded") (~tw :tokens "bg-amber-100 text-amber-700 px-2 py-0.5 rounded")
(str (get stats "mutation-count") " mutation"))) (str mutation-count " mutation")))
(when (when
(> (get stats "io-count") 0) (> io-count 0)
(span (span
(~tw :tokens "bg-orange-100 text-orange-700 px-2 py-0.5 rounded") (~tw :tokens "bg-orange-100 text-orange-700 px-2 py-0.5 rounded")
(str (get stats "io-count") " io"))) (str io-count " io")))
(when (when
(> (get stats "render-count") 0) (> render-count 0)
(span (span
(~tw :tokens "bg-sky-100 text-sky-700 px-2 py-0.5 rounded") (~tw :tokens "bg-sky-100 text-sky-700 px-2 py-0.5 rounded")
(str (get stats "render-count") " render"))) (str render-count " render")))
(when (when
(> (get stats "test-total") 0) (> test-total 0)
(span (span
(~tw :tokens "bg-violet-100 text-violet-700 px-2 py-0.5 rounded") (~tw :tokens "bg-violet-100 text-violet-700 px-2 py-0.5 rounded")
(str (get stats "test-total") " tests"))) (str test-total " tests")))
(span (span
(~tw :tokens "bg-stone-100 text-stone-500 px-2 py-0.5 rounded") (~tw :tokens "bg-stone-100 text-stone-500 px-2 py-0.5 rounded")
(str (get stats "lines") " lines")))) (str lines " lines")))))
(defcomp (defcomp
~specs-explorer/spec-explorer-section ~specs-explorer/spec-explorer-section
(&key section filename) (&key section filename)
(let-match
{:defines defines :title title :comment comment}
section
(div (div
(~tw :tokens "mb-6") (~tw :tokens "mb-6")
(h2 (h2
(~tw :tokens "text-base font-semibold text-stone-600 mb-2 border-b border-stone-200 pb-1") (~tw
:id (replace (lower (get section "title")) " " "-") :tokens "text-base font-semibold text-stone-600 mb-2 border-b border-stone-200 pb-1")
(get section "title")) :id (replace (lower title) " " "-")
(when title)
(get section "comment") (when comment (p (~tw :tokens "text-sm text-stone-500 mb-2") comment))
(p (~tw :tokens "text-sm text-stone-500 mb-2") (get section "comment")))
(div (div
(~tw :tokens "space-y-0.5") (~tw :tokens "space-y-0.5")
(map (map
(fn (fn
(d) (d)
(~specs-explorer/spec-explorer-define :d d :filename filename)) (~specs-explorer/spec-explorer-define :d d :filename filename))
(get section "defines"))))) defines)))))
(defcomp (defcomp
~specs-explorer/spec-explorer-define ~specs-explorer/spec-explorer-define
(&key d filename) (&key d filename)
(let-match
{:kind kind :name name}
d
(div (div
(~tw :tokens "flex items-center gap-2 py-1.5 px-2 rounded hover:bg-stone-50 cursor-pointer group") (~tw
:id (str "fn-" (get d "name")) :tokens "flex items-center gap-2 py-1.5 px-2 rounded hover:bg-stone-50 cursor-pointer group")
:id (str "fn-" name)
:sx-get (str :sx-get (str
"/sx/(language.(spec.(explore." "/sx/(language.(spec.(explore."
(replace filename ".sx" "") (replace filename ".sx" "")
"." "."
(get d "name") name
")))") ")))")
:sx-target "#sx-content" :sx-target "#sx-content"
:sx-select "#sx-content" :sx-select "#sx-content"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-push-url "true" :sx-push-url "true"
(span (span
(~tw :tokens "font-mono text-sm font-medium text-stone-800 group-hover:text-violet-700") (~tw
(get d "name")) :tokens "font-mono text-sm font-medium text-stone-800 group-hover:text-violet-700")
(span (~tw :tokens "text-xs text-stone-400") (get d "kind")))) name)
(span (~tw :tokens "text-xs text-stone-400") kind))))
(defcomp (defcomp
~specs-explorer/spec-explorer-define-detail ~specs-explorer/spec-explorer-define-detail
@@ -144,33 +157,39 @@
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-push-url "true" :sx-push-url "true"
(str "← Back to " (replace filename ".sx" "")))) (str "← Back to " (replace filename ".sx" ""))))
(let-match
{:kind kind :effects effects :params params :source source :name name}
d
(div (div
(~tw :tokens "rounded border border-violet-200 bg-violet-50/30 p-4") (~tw :tokens "rounded border border-violet-200 bg-violet-50/30 p-4")
(div (div
(~tw :tokens "flex items-center gap-2 flex-wrap mb-3") (~tw :tokens "flex items-center gap-2 flex-wrap mb-3")
(span (span
(~tw :tokens "font-mono text-lg font-semibold text-stone-800") (~tw :tokens "font-mono text-lg font-semibold text-stone-800")
(get d "name")) name)
(span (~tw :tokens "text-xs text-stone-400") (get d "kind")) (span (~tw :tokens "text-xs text-stone-400") kind)
(if (if
(empty? (get d "effects")) (empty? effects)
(span (span
(~tw :tokens "text-xs px-1.5 py-0.5 rounded bg-green-100 text-green-700") (~tw
:tokens "text-xs px-1.5 py-0.5 rounded bg-green-100 text-green-700")
"pure") "pure")
(map (map
(fn (eff) (~specs-explorer/spec-effect-badge :effect eff)) (fn (eff) (~specs-explorer/spec-effect-badge :effect eff))
(get d "effects")))) effects)))
(when (when
(not (empty? (get d "params"))) (not (empty? params))
(~specs-explorer/spec-param-list :params (get d "params"))) (~specs-explorer/spec-param-list :params params))
(details (details
:open "true" :open "true"
(summary (summary
(~tw :tokens "px-3 py-1.5 bg-stone-50 text-xs font-medium text-stone-600 cursor-pointer select-none mt-3 rounded") (~tw
:tokens "px-3 py-1.5 bg-stone-50 text-xs font-medium text-stone-600 cursor-pointer select-none mt-3 rounded")
"SX Source") "SX Source")
(pre (pre
(~tw :tokens "text-xs p-3 overflow-x-auto bg-white rounded mt-1 border border-stone-200") (~tw
(code (~tw :tokens "language-sx") (get d "source"))))))) :tokens "text-xs p-3 overflow-x-auto bg-white rounded mt-1 border border-stone-200")
(code (~tw :tokens "language-sx") source)))))))
(defcomp (defcomp
~specs-explorer/spec-effect-badge ~specs-explorer/spec-effect-badge
@@ -197,13 +216,15 @@
(map (map
(fn (fn
(p) (p)
(let (let-match
((name (get p "name")) (typ (get p "type"))) {:type typ :name name}
p
(if (if
(or (= name "&rest") (= name "&key")) (or (= name "&rest") (= name "&key"))
(span (~tw :tokens "text-xs font-mono text-violet-500") name) (span (~tw :tokens "text-xs font-mono text-violet-500") name)
(span (span
(~tw :tokens "text-xs font-mono px-1 py-0.5 rounded bg-stone-50 border border-stone-200") (~tw
:tokens "text-xs font-mono px-1 py-0.5 rounded bg-stone-50 border border-stone-200")
(if (if
typ typ
(<> (<>
@@ -300,7 +321,8 @@
(div (div
(~tw :tokens "mt-8") (~tw :tokens "mt-8")
(h2 (h2
(~tw :tokens "text-lg font-semibold text-stone-700 border-b border-stone-200 pb-1 mb-3") (~tw
:tokens "text-lg font-semibold text-stone-700 border-b border-stone-200 pb-1 mb-3")
"Platform Interface") "Platform Interface")
(p (p
(~tw :tokens "text-sm text-stone-500 mb-3") (~tw :tokens "text-sm text-stone-500 mb-3")
@@ -314,22 +336,29 @@
(~tw :tokens "border-b border-stone-200 bg-stone-50") (~tw :tokens "border-b border-stone-200 bg-stone-50")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Name") (th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Name")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Params") (th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Params")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Returns") (th
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Description"))) (~tw :tokens "px-3 py-2 font-medium text-stone-600")
"Returns")
(th
(~tw :tokens "px-3 py-2 font-medium text-stone-600")
"Description")))
(tbody (tbody
(map (map
(fn (fn
(item) (item)
(let-match
{:doc doc :params params :returns returns :name name}
item
(tr (tr
(~tw :tokens "border-b border-stone-100") (~tw :tokens "border-b border-stone-100")
(td (td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(get item "name")) name)
(td (td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500") (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
(get item "params")) params)
(td (td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500") (~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
(get item "returns")) returns)
(td (~tw :tokens "px-3 py-2 text-stone-600") (get item "doc")))) (td (~tw :tokens "px-3 py-2 text-stone-600") doc))))
items)))))) items))))))

View File

@@ -1,6 +1,7 @@
(define-library (web deps) (define-library
(web deps)
(export (export
scan-refs scan-refs
scan-refs-walk scan-refs-walk
@@ -22,32 +23,29 @@
page-render-plan page-render-plan
env-components) env-components)
(begin (begin
(define (define
scan-refs scan-refs
:effects () :effects ()
(fn (node) (let ((refs (list))) (scan-refs-walk node refs) refs))) (fn (node) (let ((refs (list))) (scan-refs-walk node refs) refs)))
(define (define
scan-refs-walk scan-refs-walk
:effects () :effects ()
(fn (fn
(node (refs :as list)) (node (refs :as list))
(cond (match
(= (type-of node) "symbol") (type-of node)
("symbol"
(let (let
((name (symbol-name node))) ((name (symbol-name node)))
(when (when
(starts-with? name "~") (starts-with? name "~")
(when (not (contains? refs name)) (append! refs name)))) (when (not (contains? refs name)) (append! refs name)))))
(= (type-of node) "list") ("list" (for-each (fn (child) (scan-refs-walk child refs)) node))
(for-each (fn (item) (scan-refs-walk item refs)) node) ("dict"
(= (type-of node) "dict")
(for-each (for-each
(fn (key) (scan-refs-walk (dict-get node key) refs)) (fn (key) (scan-refs-walk (dict-get node key) refs))
(keys node)) (keys node)))
:else nil))) (_ nil))))
(define (define
transitive-deps-walk transitive-deps-walk
:effects () :effects ()
@@ -68,7 +66,6 @@
(fn ((ref :as string)) (transitive-deps-walk ref seen env)) (fn ((ref :as string)) (transitive-deps-walk ref seen env))
(scan-refs (macro-body val))) (scan-refs (macro-body val)))
:else nil))))) :else nil)))))
(define (define
transitive-deps transitive-deps
:effects () :effects ()
@@ -79,7 +76,6 @@
(key (if (starts-with? name "~") name (str "~" name)))) (key (if (starts-with? name "~") name (str "~" name))))
(transitive-deps-walk key seen env) (transitive-deps-walk key seen env)
(filter (fn ((x :as string)) (not (= x key))) seen)))) (filter (fn ((x :as string)) (not (= x key))) seen))))
(define (define
compute-all-deps compute-all-deps
:effects (mutation) :effects (mutation)
@@ -91,10 +87,11 @@
(let (let
((val (env-get env name))) ((val (env-get env name)))
(when (when
(or (= (type-of val) "component") (= (type-of val) "island")) (or
(= (type-of val) "component")
(= (type-of val) "island"))
(component-set-deps! val (transitive-deps name env))))) (component-set-deps! val (transitive-deps name env)))))
(env-components env)))) (env-components env))))
(define (define
scan-components-from-source scan-components-from-source
:effects () :effects ()
@@ -103,7 +100,6 @@
(let (let
((matches (regex-find-all "\\(~([a-zA-Z_][a-zA-Z0-9_\\-:/]*)" source))) ((matches (regex-find-all "\\(~([a-zA-Z_][a-zA-Z0-9_\\-:/]*)" source)))
(map (fn ((m :as string)) (str "~" m)) matches)))) (map (fn ((m :as string)) (str "~" m)) matches))))
(define (define
components-needed components-needed
:effects () :effects ()
@@ -115,7 +111,9 @@
(for-each (for-each
(fn (fn
((name :as string)) ((name :as string))
(when (not (contains? all-needed name)) (append! all-needed name)) (when
(not (contains? all-needed name))
(append! all-needed name))
(let (let
((val (env-get env name))) ((val (env-get env name)))
(let (let
@@ -129,14 +127,12 @@
deps)))) deps))))
direct) direct)
all-needed))) all-needed)))
(define (define
page-component-bundle page-component-bundle
:effects () :effects ()
(fn (fn
((page-source :as string) (env :as dict)) ((page-source :as string) (env :as dict))
(components-needed page-source env))) (components-needed page-source env)))
(define (define
page-css-classes page-css-classes
:effects () :effects ()
@@ -154,7 +150,9 @@
(for-each (for-each
(fn (fn
((cls :as string)) ((cls :as string))
(when (not (contains? classes cls)) (append! classes cls))) (when
(not (contains? classes cls))
(append! classes cls)))
(component-css-classes val))))) (component-css-classes val)))))
needed) needed)
(for-each (for-each
@@ -163,34 +161,36 @@
(when (not (contains? classes cls)) (append! classes cls))) (when (not (contains? classes cls)) (append! classes cls)))
(scan-css-classes page-source)) (scan-css-classes page-source))
classes))) classes)))
(define (define
scan-io-refs-walk scan-io-refs-walk
:effects () :effects ()
(fn (fn
(node (io-names :as list) (refs :as list)) (node (io-names :as list) (refs :as list))
(cond (match
(= (type-of node) "symbol") (type-of node)
("symbol"
(let (let
((name (symbol-name node))) ((name (symbol-name node)))
(when (when
(contains? io-names name) (contains? io-names name)
(when (not (contains? refs name)) (append! refs name)))) (when (not (contains? refs name)) (append! refs name)))))
(= (type-of node) "list") ("list"
(for-each (fn (item) (scan-io-refs-walk item io-names refs)) node)
(= (type-of node) "dict")
(for-each (for-each
(fn (key) (scan-io-refs-walk (dict-get node key) io-names refs)) (fn (item) (scan-io-refs-walk item io-names refs))
(keys node)) node))
:else nil))) ("dict"
(for-each
(fn
(key)
(scan-io-refs-walk (dict-get node key) io-names refs))
(keys node)))
(_ nil))))
(define (define
scan-io-refs scan-io-refs
:effects () :effects ()
(fn (fn
(node (io-names :as list)) (node (io-names :as list))
(let ((refs (list))) (scan-io-refs-walk node io-names refs) refs))) (let ((refs (list))) (scan-io-refs-walk node io-names refs) refs)))
(define (define
transitive-io-refs-walk transitive-io-refs-walk
:effects () :effects ()
@@ -211,7 +211,9 @@
(for-each (for-each
(fn (fn
((ref :as string)) ((ref :as string))
(when (not (contains? all-refs ref)) (append! all-refs ref))) (when
(not (contains? all-refs ref))
(append! all-refs ref)))
(scan-io-refs (component-body val) io-names)) (scan-io-refs (component-body val) io-names))
(for-each (for-each
(fn (fn
@@ -223,7 +225,9 @@
(for-each (for-each
(fn (fn
((ref :as string)) ((ref :as string))
(when (not (contains? all-refs ref)) (append! all-refs ref))) (when
(not (contains? all-refs ref))
(append! all-refs ref)))
(scan-io-refs (macro-body val) io-names)) (scan-io-refs (macro-body val) io-names))
(for-each (for-each
(fn (fn
@@ -231,7 +235,6 @@
(transitive-io-refs-walk dep seen all-refs env io-names)) (transitive-io-refs-walk dep seen all-refs env io-names))
(scan-refs (macro-body val)))) (scan-refs (macro-body val))))
:else nil))))) :else nil)))))
(define (define
transitive-io-refs transitive-io-refs
:effects () :effects ()
@@ -243,7 +246,6 @@
(key (if (starts-with? name "~") name (str "~" name)))) (key (if (starts-with? name "~") name (str "~" name))))
(transitive-io-refs-walk key seen all-refs env io-names) (transitive-io-refs-walk key seen all-refs env io-names)
all-refs))) all-refs)))
(define (define
compute-all-io-refs compute-all-io-refs
:effects (mutation) :effects (mutation)
@@ -260,7 +262,6 @@
val val
(transitive-io-refs name env io-names))))) (transitive-io-refs name env io-names)))))
(env-components env)))) (env-components env))))
(define (define
component-io-refs-cached component-io-refs-cached
:effects () :effects ()
@@ -277,7 +278,6 @@
(not (empty? (component-io-refs val)))) (not (empty? (component-io-refs val))))
(component-io-refs val) (component-io-refs val)
(transitive-io-refs name env io-names)))))) (transitive-io-refs name env io-names))))))
(define (define
component-pure? component-pure?
:effects () :effects ()
@@ -294,7 +294,6 @@
(not (empty? (component-io-refs val)))) (not (empty? (component-io-refs val))))
false false
(empty? (transitive-io-refs name env io-names))))))) (empty? (transitive-io-refs name env io-names)))))))
(define (define
render-target render-target
:effects () :effects ()
@@ -317,7 +316,6 @@
(not (component-pure? name env io-names)) (not (component-pure? name env io-names))
"server" "server"
:else "client"))))))) :else "client")))))))
(define (define
page-render-plan page-render-plan
:effects () :effects ()
@@ -349,7 +347,6 @@
(append! client-list name)))) (append! client-list name))))
needed) needed)
{:io-deps io-deps :server server-list :components comp-targets :client client-list}))) {:io-deps io-deps :server server-list :components comp-targets :client client-list})))
(define (define
env-components env-components
:effects () :effects ()
@@ -359,10 +356,7 @@
(fn (fn
((k :as string)) ((k :as string))
(let ((v (env-get env k))) (or (component? v) (macro? v)))) (let ((v (env-get env k))) (or (component? v) (macro? v))))
(keys env)))) (keys env)))))) ;; end define-library
)) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (web deps)) (import (web deps))

View File

@@ -4,7 +4,8 @@
(import (sx dom)) (import (sx dom))
(import (sx browser)) (import (sx browser))
(define-library (web engine) (define-library
(web engine)
(export (export
ENGINE_VERBS ENGINE_VERBS
DEFAULT_SWAP DEFAULT_SWAP
@@ -40,11 +41,8 @@
should-boost-form? should-boost-form?
parse-sse-swap) parse-sse-swap)
(begin (begin
(define ENGINE_VERBS (list "get" "post" "put" "delete" "patch")) (define ENGINE_VERBS (list "get" "post" "put" "delete" "patch"))
(define DEFAULT_SWAP "outerHTML") (define DEFAULT_SWAP "outerHTML")
(define (define
parse-time parse-time
:effects () :effects ()
@@ -60,7 +58,6 @@
(ends-with? s "s") (ends-with? s "s")
(* (parse-int (replace s "s" "") 0) 1000) (* (parse-int (replace s "s" "") 0) 1000)
(parse-int s 0)))))) (parse-int s 0))))))
(define (define
parse-trigger-spec parse-trigger-spec
:effects () :effects ()
@@ -82,7 +79,9 @@
(empty? tokens) (empty? tokens)
nil nil
(if (if
(and (= (first tokens) "every") (>= (len tokens) 2)) (and
(= (first tokens) "every")
(>= (len tokens) 2))
(dict (dict
"event" "event"
"every" "every"
@@ -127,22 +126,18 @@
mods)) mods))
(dict "event" raw-event "modifiers" mods))))))))) (dict "event" raw-event "modifiers" mods)))))))))
raw-parts)))))) raw-parts))))))
(define (define
default-trigger default-trigger
:effects () :effects ()
(fn (fn
((tag-name :as string)) ((tag-name :as string))
(cond (match
(= tag-name "form") tag-name
(list (dict "event" "submit" "modifiers" (dict))) ("form" (list (dict "event" "submit" "modifiers" (dict))))
(or ("input" (list (dict "event" "change" "modifiers" (dict))))
(= tag-name "input") ("select" (list (dict "event" "change" "modifiers" (dict))))
(= tag-name "select") ("textarea" (list (dict "event" "change" "modifiers" (dict))))
(= tag-name "textarea")) (_ (list (dict "event" "click" "modifiers" (dict)))))))
(list (dict "event" "change" "modifiers" (dict)))
:else (list (dict "event" "click" "modifiers" (dict))))))
(define (define
get-verb-info get-verb-info
:effects (io) :effects (io)
@@ -155,7 +150,6 @@
((url (dom-get-attr el (str "sx-" verb)))) ((url (dom-get-attr el (str "sx-" verb))))
(if url (dict "method" (upper verb) "url" url) nil))) (if url (dict "method" (upper verb) "url" url) nil)))
ENGINE_VERBS))) ENGINE_VERBS)))
(define (define
build-request-headers build-request-headers
:effects (io) :effects (io)
@@ -168,7 +162,9 @@
(when target-sel (dict-set! headers "SX-Target" target-sel))) (when target-sel (dict-set! headers "SX-Target" target-sel)))
(let (let
((comp-hash (dom-get-attr (dom-query "script[data-components][data-hash]") "data-hash"))) ((comp-hash (dom-get-attr (dom-query "script[data-components][data-hash]") "data-hash")))
(when comp-hash (dict-set! headers "SX-Components-Hash" comp-hash))) (when
comp-hash
(dict-set! headers "SX-Components-Hash" comp-hash)))
(let (let
((extra-h (dom-get-attr el "sx-headers"))) ((extra-h (dom-get-attr el "sx-headers")))
(when (when
@@ -183,7 +179,6 @@
(dict-set! headers key (str (get parsed key)))) (dict-set! headers key (str (get parsed key))))
(keys parsed)))))) (keys parsed))))))
headers))) headers)))
(define (define
process-response-headers process-response-headers
:effects () :effects ()
@@ -214,7 +209,6 @@
(get-header "SX-Cache-Invalidate") (get-header "SX-Cache-Invalidate")
"cache-update" "cache-update"
(get-header "SX-Cache-Update")))) (get-header "SX-Cache-Update"))))
(define (define
parse-swap-spec parse-swap-spec
:effects () :effects ()
@@ -234,7 +228,6 @@
(set! use-transition false))) (set! use-transition false)))
(rest parts)) (rest parts))
(dict "style" style "transition" use-transition)))) (dict "style" style "transition" use-transition))))
(define (define
parse-retry-spec parse-retry-spec
:effects () :effects ()
@@ -252,14 +245,12 @@
(parse-int (nth parts 1) 1000) (parse-int (nth parts 1) 1000)
"cap-ms" "cap-ms"
(parse-int (nth parts 2) 30000)))))) (parse-int (nth parts 2) 30000))))))
(define (define
next-retry-ms next-retry-ms
:effects () :effects ()
(fn (fn
((current-ms :as number) (cap-ms :as number)) ((current-ms :as number) (cap-ms :as number))
(min (* current-ms 2) cap-ms))) (min (* current-ms 2) cap-ms)))
(define (define
filter-params filter-params
:effects () :effects ()
@@ -279,14 +270,15 @@
(let (let
((excluded (map trim (split (slice params-spec 4) ",")))) ((excluded (map trim (split (slice params-spec 4) ","))))
(filter (filter
(fn ((p :as list)) (not (contains? excluded (first p)))) (fn
((p :as list))
(not (contains? excluded (first p))))
all-params)) all-params))
(let (let
((allowed (map trim (split params-spec ",")))) ((allowed (map trim (split params-spec ","))))
(filter (filter
(fn ((p :as list)) (contains? allowed (first p))) (fn ((p :as list)) (contains? allowed (first p)))
all-params)))))))) all-params))))))))
(define (define
resolve-target resolve-target
:effects (io) :effects (io)
@@ -294,13 +286,12 @@
(el) (el)
(let (let
((sel (dom-get-attr el "sx-target"))) ((sel (dom-get-attr el "sx-target")))
(cond (match
(or (nil? sel) (= sel "this")) sel
el (nil el)
(= sel "closest") ("this" el)
(dom-parent el) ("closest" (dom-parent el))
:else (dom-query sel))))) (_ (dom-query sel))))))
(define (define
apply-optimistic apply-optimistic
:effects (mutation io) :effects (mutation io)
@@ -322,7 +313,10 @@
(dom-set-style target "pointer-events" "none")) (dom-set-style target "pointer-events" "none"))
(= directive "disable") (= directive "disable")
(do (do
(dict-set! state "disabled" (dom-get-prop target "disabled")) (dict-set!
state
"disabled"
(dom-get-prop target "disabled"))
(dom-set-prop target "disabled" true)) (dom-set-prop target "disabled" true))
(starts-with? directive "add-class:") (starts-with? directive "add-class:")
(let (let
@@ -330,7 +324,6 @@
(dict-set! state "add-class" cls) (dict-set! state "add-class" cls)
(dom-add-class target cls))) (dom-add-class target cls)))
state))))) state)))))
(define (define
revert-optimistic revert-optimistic
:effects (mutation io) :effects (mutation io)
@@ -339,17 +332,23 @@
(when (when
state state
(let (let
((target (get state "target")) (directive (get state "directive"))) ((target (get state "target"))
(directive (get state "directive")))
(cond (cond
(= directive "remove") (= directive "remove")
(do (do
(dom-set-style target "opacity" (or (get state "opacity") "")) (dom-set-style
target
"opacity"
(or (get state "opacity") ""))
(dom-set-style target "pointer-events" "")) (dom-set-style target "pointer-events" ""))
(= directive "disable") (= directive "disable")
(dom-set-prop target "disabled" (or (get state "disabled") false)) (dom-set-prop
target
"disabled"
(or (get state "disabled") false))
(get state "add-class") (get state "add-class")
(dom-remove-class target (get state "add-class"))))))) (dom-remove-class target (get state "add-class")))))))
(define (define
find-oob-swaps find-oob-swaps
:effects (mutation io) :effects (mutation io)
@@ -383,7 +382,6 @@
oob-els))) oob-els)))
(list "sx-swap-oob" "hx-swap-oob")) (list "sx-swap-oob" "hx-swap-oob"))
results))) results)))
(define (define
morph-node morph-node
:effects (mutation io) :effects (mutation io)
@@ -416,9 +414,12 @@
(dom-parent old-node) (dom-parent old-node)
(dom-clone new-node true) (dom-clone new-node true)
old-node) old-node)
(or (= (dom-node-type old-node) 3) (= (dom-node-type old-node) 8)) (or
(= (dom-node-type old-node) 3)
(= (dom-node-type old-node) 8))
(when (when
(not (= (dom-text-content old-node) (dom-text-content new-node))) (not
(= (dom-text-content old-node) (dom-text-content new-node)))
(dom-set-text-content old-node (dom-text-content new-node))) (dom-set-text-content old-node (dom-text-content new-node)))
(= (dom-node-type old-node) 1) (= (dom-node-type old-node) 1)
(do (do
@@ -439,7 +440,6 @@
(dom-is-active-element? old-node) (dom-is-active-element? old-node)
(dom-is-input-element? old-node))) (dom-is-input-element? old-node)))
(morph-children old-node new-node)))))) (morph-children old-node new-node))))))
(define (define
sync-attrs sync-attrs
:effects (mutation io) :effects (mutation io)
@@ -471,7 +471,6 @@
(not (= aname "data-sx-reactive-attrs"))) (not (= aname "data-sx-reactive-attrs")))
(dom-remove-attr old-el aname)))) (dom-remove-attr old-el aname))))
(dom-attr-list old-el))))) (dom-attr-list old-el)))))
(define (define
morph-children morph-children
:effects (mutation io) :effects (mutation io)
@@ -501,8 +500,10 @@
(new-child) (new-child)
(let (let
((raw-id (dom-id new-child)) ((raw-id (dom-id new-child))
(match-id (if (and raw-id (not (empty? raw-id))) raw-id nil)) (match-id
(match-by-id (if match-id (dict-get old-by-id match-id) nil))) (if (and raw-id (not (empty? raw-id))) raw-id nil))
(match-by-id
(if match-id (dict-get old-by-id match-id) nil)))
(cond (cond
(and match-by-id (not (nil? match-by-id))) (and match-by-id (not (nil? match-by-id)))
(do (do
@@ -552,7 +553,6 @@
(not (dom-has-attr? leftover "sx-ignore"))) (not (dom-has-attr? leftover "sx-ignore")))
(dom-remove-child old-parent leftover))))) (dom-remove-child old-parent leftover)))))
(range 0 (len old-kids)))))) (range 0 (len old-kids))))))
(define (define
morph-island-children morph-island-children
:effects (mutation io) :effects (mutation io)
@@ -598,10 +598,11 @@
((id (dom-get-attr old-marsh "data-sx-marsh"))) ((id (dom-get-attr old-marsh "data-sx-marsh")))
(let (let
((new-marsh (dict-get new-marsh-map id))) ((new-marsh (dict-get new-marsh-map id)))
(when new-marsh (morph-marsh old-marsh new-marsh old-island))))) (when
new-marsh
(morph-marsh old-marsh new-marsh old-island)))))
old-marshes) old-marshes)
(process-signal-updates new-island))))) (process-signal-updates new-island)))))
(define (define
morph-marsh morph-marsh
:effects (mutation io) :effects (mutation io)
@@ -629,7 +630,6 @@
(do (do
(sync-attrs old-marsh new-marsh) (sync-attrs old-marsh new-marsh)
(morph-children old-marsh new-marsh)))))) (morph-children old-marsh new-marsh))))))
(define (define
process-signal-updates process-signal-updates
:effects (mutation io) :effects (mutation io)
@@ -656,7 +656,6 @@
(reset! (use-store store-name) parsed)) (reset! (use-store store-name) parsed))
(dom-remove-attr el "data-sx-signal"))))))) (dom-remove-attr el "data-sx-signal")))))))
signal-els)))) signal-els))))
(define (define
swap-dom-nodes swap-dom-nodes
:effects (mutation io) :effects (mutation io)
@@ -674,7 +673,8 @@
(morph-children target wrapper))) (morph-children target wrapper)))
"outerHTML" "outerHTML"
(let (let
((parent (dom-parent target)) (new-el (dom-clone new-nodes true))) ((parent (dom-parent target))
(new-el (dom-clone new-nodes true)))
(if (if
(dom-is-fragment? new-nodes) (dom-is-fragment? new-nodes)
(let (let
@@ -709,7 +709,6 @@
((wrapper (dom-create-element "div" nil))) ((wrapper (dom-create-element "div" nil)))
(dom-append wrapper new-nodes) (dom-append wrapper new-nodes)
(morph-children target wrapper)))))) (morph-children target wrapper))))))
(define (define
insert-remaining-siblings insert-remaining-siblings
:effects (mutation io) :effects (mutation io)
@@ -721,7 +720,6 @@
((next (dom-next-sibling sib))) ((next (dom-next-sibling sib)))
(dom-insert-after ref-node sib) (dom-insert-after ref-node sib)
(insert-remaining-siblings parent sib next))))) (insert-remaining-siblings parent sib next)))))
(define (define
swap-html-string swap-html-string
:effects (mutation io) :effects (mutation io)
@@ -750,7 +748,6 @@
"none" "none"
nil nil
:else (dom-set-inner-html target html)))) :else (dom-set-inner-html target html))))
(define (define
handle-history handle-history
:effects (io) :effects (io)
@@ -768,10 +765,9 @@
(save-scroll-position) (save-scroll-position)
(browser-push-state (if (= push-url "true") url push-url))) (browser-push-state (if (= push-url "true") url push-url)))
(and replace-url (not (= replace-url "false"))) (and replace-url (not (= replace-url "false")))
(browser-replace-state (if (= replace-url "true") url replace-url)))))) (browser-replace-state
(if (= replace-url "true") url replace-url))))))
(define PRELOAD_TTL 30000) (define PRELOAD_TTL 30000)
(define (define
preload-cache-get preload-cache-get
:effects (mutation) :effects (mutation)
@@ -786,7 +782,6 @@
(> (- (now-ms) (get entry "timestamp")) PRELOAD_TTL) (> (- (now-ms) (get entry "timestamp")) PRELOAD_TTL)
(do (dict-delete! cache url) nil) (do (dict-delete! cache url) nil)
(do (dict-delete! cache url) entry)))))) (do (dict-delete! cache url) entry))))))
(define (define
preload-cache-set preload-cache-set
:effects (mutation) :effects (mutation)
@@ -799,7 +794,6 @@
cache cache
url url
(dict "text" text "content-type" content-type "timestamp" (now-ms))))) (dict "text" text "content-type" content-type "timestamp" (now-ms)))))
(define (define
classify-trigger classify-trigger
:effects () :effects ()
@@ -807,17 +801,13 @@
((trigger :as dict)) ((trigger :as dict))
(let (let
((event (get trigger "event"))) ((event (get trigger "event")))
(cond (match
(= event "every") event
"poll" ("every" "poll")
(= event "intersect") ("intersect" "intersect")
"intersect" ("load" "load")
(= event "load") ("revealed" "revealed")
"load" (_ "event")))))
(= event "revealed")
"revealed"
:else "event"))))
(define (define
should-boost-link? should-boost-link?
:effects (io) :effects (io)
@@ -834,7 +824,6 @@
(not (dom-has-attr? link "sx-get")) (not (dom-has-attr? link "sx-get"))
(not (dom-has-attr? link "sx-post")) (not (dom-has-attr? link "sx-post"))
(not (dom-has-attr? link "sx-disable")))))) (not (dom-has-attr? link "sx-disable"))))))
(define (define
should-boost-form? should-boost-form?
:effects (io) :effects (io)
@@ -844,14 +833,10 @@
(not (dom-has-attr? form "sx-get")) (not (dom-has-attr? form "sx-get"))
(not (dom-has-attr? form "sx-post")) (not (dom-has-attr? form "sx-post"))
(not (dom-has-attr? form "sx-disable"))))) (not (dom-has-attr? form "sx-disable")))))
(define (define
parse-sse-swap parse-sse-swap
:effects (io) :effects (io)
(fn (el) (or (dom-get-attr el "sx-sse-swap") "message"))) (fn (el) (or (dom-get-attr el "sx-sse-swap") "message"))))) ;; end define-library
)) ;; end define-library
;; Re-export to global namespace for backward compatibility ;; Re-export to global namespace for backward compatibility
(import (web engine)) (import (web engine))