URL restructure, 404 page, trailing slash normalization, layout fixes
- Rename /reactive-islands/ → /reactive/, /reference/ → /hypermedia/reference/, /examples/ → /hypermedia/examples/ across all .sx and .py files - Add 404 error page (not-found.sx) working on both server refresh and client-side SX navigation via orchestration.sx error response handling - Add trailing slash redirect (GET only, excludes /api/, /static/, /internal/) - Remove blue sky-500 header bar from SX docs layout (conditional on header-rows) - Fix 405 on API endpoints from trailing slash redirect hitting POST/PUT/DELETE - Fix client-side 404: orchestration.sx now swaps error response content instead of silently dropping it - Add new plan files and home page component Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,8 +52,13 @@
|
||||
(create-fragment)
|
||||
(render-dom-list expr env ns))
|
||||
|
||||
;; Fallback
|
||||
:else (create-text-node (str expr)))))
|
||||
;; Signal → reactive text in island scope, deref outside
|
||||
:else
|
||||
(if (signal? expr)
|
||||
(if *island-scope*
|
||||
(reactive-text expr)
|
||||
(create-text-node (str (deref expr))))
|
||||
(create-text-node (str expr))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
@@ -81,6 +86,10 @@
|
||||
(= name "lake")
|
||||
(render-dom-lake args env ns)
|
||||
|
||||
;; marsh — reactive server-morphable slot within an island
|
||||
(= name "marsh")
|
||||
(render-dom-marsh args env ns)
|
||||
|
||||
;; html: prefix → force element rendering
|
||||
(starts-with? name "html:")
|
||||
(render-dom-element (slice name 5) args env ns)
|
||||
@@ -490,7 +499,8 @@
|
||||
(if (and *island-scope*
|
||||
(= (type-of coll-expr) "list")
|
||||
(> (len coll-expr) 1)
|
||||
(= (first coll-expr) "deref"))
|
||||
(= (type-of (first coll-expr)) "symbol")
|
||||
(= (symbol-name (first coll-expr)) "deref"))
|
||||
;; Reactive path: pass signal to reactive-list
|
||||
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
||||
(sig (trampoline (eval-expr (nth coll-expr 1) env))))
|
||||
@@ -698,6 +708,56 @@
|
||||
el))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; render-dom-marsh — reactive server-morphable slot within an island
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; (marsh :id "name" :tag "div" :transform fn children...)
|
||||
;;
|
||||
;; Like a lake but reactive: during morph, new content is parsed as SX and
|
||||
;; re-evaluated in the island's signal scope. The :transform function (if
|
||||
;; present) reshapes server content before evaluation.
|
||||
;;
|
||||
;; Renders as <div data-sx-marsh="name">children</div>.
|
||||
;; Stores the island env and transform on the element for morph retrieval.
|
||||
|
||||
(define render-dom-marsh
|
||||
(fn (args env ns)
|
||||
(let ((marsh-id nil)
|
||||
(marsh-tag "div")
|
||||
(marsh-transform nil)
|
||||
(children (list)))
|
||||
(reduce
|
||||
(fn (state arg)
|
||||
(let ((skip (get state "skip")))
|
||||
(if skip
|
||||
(assoc state "skip" false "i" (inc (get state "i")))
|
||||
(if (and (= (type-of arg) "keyword")
|
||||
(< (inc (get state "i")) (len args)))
|
||||
(let ((kname (keyword-name arg))
|
||||
(kval (trampoline (eval-expr (nth args (inc (get state "i"))) env))))
|
||||
(cond
|
||||
(= kname "id") (set! marsh-id kval)
|
||||
(= kname "tag") (set! marsh-tag kval)
|
||||
(= kname "transform") (set! marsh-transform kval))
|
||||
(assoc state "skip" true "i" (inc (get state "i"))))
|
||||
(do
|
||||
(append! children arg)
|
||||
(assoc state "i" (inc (get state "i"))))))))
|
||||
(dict "i" 0 "skip" false)
|
||||
args)
|
||||
(let ((el (dom-create-element marsh-tag nil)))
|
||||
(dom-set-attr el "data-sx-marsh" (or marsh-id ""))
|
||||
;; Store transform function and island env for morph retrieval
|
||||
(when marsh-transform
|
||||
(dom-set-data el "sx-marsh-transform" marsh-transform))
|
||||
(dom-set-data el "sx-marsh-env" env)
|
||||
(for-each
|
||||
(fn (c) (dom-append el (render-to-dom c env ns)))
|
||||
children)
|
||||
el))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Reactive DOM rendering helpers
|
||||
;; --------------------------------------------------------------------------
|
||||
@@ -726,14 +786,17 @@
|
||||
(updated (if (empty? existing) attr-name (str existing "," attr-name))))
|
||||
(dom-set-attr el "data-sx-reactive-attrs" updated))
|
||||
(effect (fn ()
|
||||
(let ((val (compute-fn)))
|
||||
(cond
|
||||
(or (nil? val) (= val false))
|
||||
(dom-remove-attr el attr-name)
|
||||
(= val true)
|
||||
(dom-set-attr el attr-name "")
|
||||
:else
|
||||
(dom-set-attr el attr-name (str val))))))))
|
||||
(let ((raw (compute-fn)))
|
||||
;; If compute-fn returned a signal (e.g. from computed), deref it
|
||||
;; to get the actual value and track the dependency
|
||||
(let ((val (if (signal? raw) (deref raw) raw)))
|
||||
(cond
|
||||
(or (nil? val) (= val false))
|
||||
(dom-remove-attr el attr-name)
|
||||
(= val true)
|
||||
(dom-set-attr el attr-name "")
|
||||
:else
|
||||
(dom-set-attr el attr-name (str val)))))))))
|
||||
|
||||
;; reactive-fragment — conditionally render a fragment based on a signal
|
||||
;; Used for (when (deref sig) ...) or (if (deref sig) ...) inside an island.
|
||||
|
||||
@@ -85,6 +85,10 @@
|
||||
(= name "lake")
|
||||
(render-html-lake args env)
|
||||
|
||||
;; Marsh — reactive server-morphable slot within an island
|
||||
(= name "marsh")
|
||||
(render-html-marsh args env)
|
||||
|
||||
;; HTML tag
|
||||
(contains? HTML_TAGS name)
|
||||
(render-html-element name args env)
|
||||
@@ -334,6 +338,46 @@
|
||||
"</" lake-tag ">"))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; render-html-marsh — SSR rendering of a reactive server-morphable slot
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; (marsh :id "name" :tag "div" :transform fn children...)
|
||||
;; → <div data-sx-marsh="name">children</div>
|
||||
;;
|
||||
;; Like a lake but reactive: during morph, new content is parsed as SX and
|
||||
;; re-evaluated in the island's signal scope. Server renders children normally;
|
||||
;; the :transform is a client-only concern.
|
||||
|
||||
(define render-html-marsh
|
||||
(fn (args env)
|
||||
(let ((marsh-id nil)
|
||||
(marsh-tag "div")
|
||||
(children (list)))
|
||||
(reduce
|
||||
(fn (state arg)
|
||||
(let ((skip (get state "skip")))
|
||||
(if skip
|
||||
(assoc state "skip" false "i" (inc (get state "i")))
|
||||
(if (and (= (type-of arg) "keyword")
|
||||
(< (inc (get state "i")) (len args)))
|
||||
(let ((kname (keyword-name arg))
|
||||
(kval (trampoline (eval-expr (nth args (inc (get state "i"))) env))))
|
||||
(cond
|
||||
(= kname "id") (set! marsh-id kval)
|
||||
(= kname "tag") (set! marsh-tag kval)
|
||||
(= kname "transform") nil)
|
||||
(assoc state "skip" true "i" (inc (get state "i"))))
|
||||
(do
|
||||
(append! children arg)
|
||||
(assoc state "i" (inc (get state "i"))))))))
|
||||
(dict "i" 0 "skip" false)
|
||||
args)
|
||||
(str "<" marsh-tag " data-sx-marsh=\"" (escape-attr (or marsh-id "")) "\">"
|
||||
(join "" (map (fn (c) (render-to-html c env)) children))
|
||||
"</" marsh-tag ">"))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; render-html-island — SSR rendering of a reactive island
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
@@ -70,6 +70,10 @@
|
||||
(= name "lake")
|
||||
(aser-call name args env)
|
||||
|
||||
;; Marsh — serialize (reactive server-morphable slot)
|
||||
(= name "marsh")
|
||||
(aser-call name args env)
|
||||
|
||||
;; HTML tag — serialize
|
||||
(contains? HTML_TAGS name)
|
||||
(aser-call name args env)
|
||||
|
||||
@@ -339,6 +339,7 @@ class JSEmitter:
|
||||
"dom-remove-children-after": "domRemoveChildrenAfter",
|
||||
"dom-set-data": "domSetData",
|
||||
"dom-get-data": "domGetData",
|
||||
"dom-inner-html": "domInnerHtml",
|
||||
"json-parse": "jsonParse",
|
||||
"dict-has?": "dictHas",
|
||||
"has-key?": "dictHas",
|
||||
@@ -2966,6 +2967,9 @@ PLATFORM_DOM_JS = """
|
||||
function domGetData(el, key) {
|
||||
return (el && el._sxData) ? (el._sxData[key] != null ? el._sxData[key] : nil) : nil;
|
||||
}
|
||||
function domInnerHtml(el) {
|
||||
return (el && el.innerHTML != null) ? el.innerHTML : "";
|
||||
}
|
||||
function jsonParse(s) {
|
||||
try { return JSON.parse(s); } catch(e) { return {}; }
|
||||
}
|
||||
@@ -3854,11 +3858,17 @@ PLATFORM_ORCHESTRATION_JS = """
|
||||
}
|
||||
|
||||
function stripSxScripts(text) {
|
||||
// Strip <script type="text/sx">...</script> (without data-components).
|
||||
// Strip <script type="text/sx">...</script> (without data-components or data-init).
|
||||
// These contain extra component defs from streaming resolve chunks.
|
||||
// data-init scripts are preserved for process-sx-scripts to evaluate as side effects.
|
||||
var SxObj = typeof Sx !== "undefined" ? Sx : null;
|
||||
return text.replace(/<script[^>]*type="text\\/sx"[^>]*>([\\s\\S]*?)<\\/script>/gi,
|
||||
function(_, defs) { if (SxObj && SxObj.loadComponents) SxObj.loadComponents(defs); return ""; });
|
||||
return text.replace(/<script[^>]*type="text\\/sx"[^>]*>[\\s\\S]*?<\\/script>/gi,
|
||||
function(match) {
|
||||
if (/data-init/.test(match)) return match; // preserve data-init scripts
|
||||
var m = match.match(/<script[^>]*>([\\s\\S]*?)<\\/script>/i);
|
||||
if (m && SxObj && SxObj.loadComponents) SxObj.loadComponents(m[1]);
|
||||
return "";
|
||||
});
|
||||
}
|
||||
|
||||
function extractResponseCss(text) {
|
||||
@@ -4115,7 +4125,8 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False):
|
||||
if (typeof domMatches === "function") PRIMITIVES["dom-matches?"] = domMatches;
|
||||
if (typeof preventDefault_ === "function") PRIMITIVES["prevent-default"] = preventDefault_;
|
||||
if (typeof elementValue === "function") PRIMITIVES["element-value"] = elementValue;
|
||||
if (typeof domOuterHtml === "function") PRIMITIVES["dom-outer-html"] = domOuterHtml;''')
|
||||
if (typeof domOuterHtml === "function") PRIMITIVES["dom-outer-html"] = domOuterHtml;
|
||||
if (typeof domInnerHtml === "function") PRIMITIVES["dom-inner-html"] = domInnerHtml;''')
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
|
||||
@@ -474,16 +474,24 @@
|
||||
|
||||
(define morph-island-children
|
||||
(fn (old-island new-island)
|
||||
;; Find all lake slots in both old and new islands
|
||||
;; Find all lake and marsh slots in both old and new islands
|
||||
(let ((old-lakes (dom-query-all old-island "[data-sx-lake]"))
|
||||
(new-lakes (dom-query-all new-island "[data-sx-lake]")))
|
||||
;; Build ID→element map for new lakes
|
||||
(let ((new-lake-map (dict)))
|
||||
(new-lakes (dom-query-all new-island "[data-sx-lake]"))
|
||||
(old-marshes (dom-query-all old-island "[data-sx-marsh]"))
|
||||
(new-marshes (dom-query-all new-island "[data-sx-marsh]")))
|
||||
;; Build ID→element maps for new lakes and marshes
|
||||
(let ((new-lake-map (dict))
|
||||
(new-marsh-map (dict)))
|
||||
(for-each
|
||||
(fn (lake)
|
||||
(let ((id (dom-get-attr lake "data-sx-lake")))
|
||||
(when id (dict-set! new-lake-map id lake))))
|
||||
new-lakes)
|
||||
(for-each
|
||||
(fn (marsh)
|
||||
(let ((id (dom-get-attr marsh "data-sx-marsh")))
|
||||
(when id (dict-set! new-marsh-map id marsh))))
|
||||
new-marshes)
|
||||
;; Morph each old lake from its new counterpart
|
||||
(for-each
|
||||
(fn (old-lake)
|
||||
@@ -492,7 +500,76 @@
|
||||
(when new-lake
|
||||
(sync-attrs old-lake new-lake)
|
||||
(morph-children old-lake new-lake)))))
|
||||
old-lakes)))))
|
||||
old-lakes)
|
||||
;; Morph each old marsh from its new counterpart
|
||||
(for-each
|
||||
(fn (old-marsh)
|
||||
(let ((id (dom-get-attr old-marsh "data-sx-marsh")))
|
||||
(let ((new-marsh (dict-get new-marsh-map id)))
|
||||
(when new-marsh
|
||||
(morph-marsh old-marsh new-marsh old-island)))))
|
||||
old-marshes)
|
||||
;; Process data-sx-signal attributes — server writes to named stores
|
||||
(process-signal-updates new-island)))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; morph-marsh — re-evaluate server content in island's reactive scope
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; Marshes are zones inside islands where server content is re-evaluated by
|
||||
;; the island's reactive evaluator. During morph, the new content is parsed
|
||||
;; as SX and rendered in the island's signal context. If the marsh has a
|
||||
;; :transform function, it reshapes the content before evaluation.
|
||||
|
||||
(define morph-marsh
|
||||
(fn (old-marsh new-marsh island-el)
|
||||
(let ((transform (dom-get-data old-marsh "sx-marsh-transform"))
|
||||
(env (dom-get-data old-marsh "sx-marsh-env"))
|
||||
(new-html (dom-inner-html new-marsh)))
|
||||
(if (and env new-html (not (empty? new-html)))
|
||||
;; Parse new content as SX and re-evaluate in island scope
|
||||
(let ((parsed (parse new-html)))
|
||||
(let ((sx-content (if transform (invoke transform parsed) parsed)))
|
||||
;; Dispose old reactive bindings in this marsh
|
||||
(dispose-marsh-scope old-marsh)
|
||||
;; Evaluate the SX in a new marsh scope — creates new reactive bindings
|
||||
(with-marsh-scope old-marsh
|
||||
(fn ()
|
||||
(let ((new-dom (render-to-dom sx-content env nil)))
|
||||
;; Replace marsh children
|
||||
(dom-remove-children-after old-marsh nil)
|
||||
(dom-append old-marsh new-dom))))))
|
||||
;; Fallback: morph like a lake
|
||||
(do
|
||||
(sync-attrs old-marsh new-marsh)
|
||||
(morph-children old-marsh new-marsh))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; process-signal-updates — server responses write to named store signals
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; Elements with data-sx-signal="name:value" trigger signal writes.
|
||||
;; After processing, the attribute is removed (consumed).
|
||||
;;
|
||||
;; Values are JSON-parsed: "7" → 7, "\"hello\"" → "hello", "true" → true.
|
||||
|
||||
(define process-signal-updates
|
||||
(fn (root)
|
||||
(let ((signal-els (dom-query-all root "[data-sx-signal]")))
|
||||
(for-each
|
||||
(fn (el)
|
||||
(let ((spec (dom-get-attr el "data-sx-signal")))
|
||||
(when spec
|
||||
(let ((colon-idx (index-of spec ":")))
|
||||
(when (> colon-idx 0)
|
||||
(let ((store-name (slice spec 0 colon-idx))
|
||||
(raw-value (slice spec (+ colon-idx 1))))
|
||||
(let ((parsed (json-parse raw-value)))
|
||||
(reset! (use-store store-name) parsed))
|
||||
(dom-remove-attr el "data-sx-signal")))))))
|
||||
signal-els))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
@@ -113,10 +113,12 @@
|
||||
(when (= sync "replace")
|
||||
(abort-previous el))
|
||||
|
||||
;; Abort any in-flight request targeting the same swap target.
|
||||
;; This ensures rapid navigation (click A then B) cancels A's fetch.
|
||||
;; Abort any in-flight request targeting the same swap target,
|
||||
;; but only when trigger and target are different elements.
|
||||
;; This ensures rapid navigation (click A then B) cancels A's fetch,
|
||||
;; while polling (element targets itself) doesn't abort its own requests.
|
||||
(let ((target-el (resolve-target el)))
|
||||
(when target-el
|
||||
(when (and target-el (not (identical? el target-el)))
|
||||
(abort-previous-target target-el)))
|
||||
|
||||
(let ((ctrl (new-abort-controller)))
|
||||
@@ -178,7 +180,12 @@
|
||||
(do
|
||||
(dom-dispatch el "sx:responseError"
|
||||
(dict "status" status "text" text))
|
||||
(handle-retry el verb method final-url extraParams))
|
||||
;; If the error response has SX content, swap it in
|
||||
;; (e.g. 404 pages) instead of just retrying
|
||||
(if (and text (> (len text) 0))
|
||||
(handle-fetch-success el final-url verb extraParams
|
||||
get-header text)
|
||||
(handle-retry el verb method final-url extraParams)))
|
||||
(do
|
||||
(dom-dispatch el "sx:afterRequest"
|
||||
(dict "status" status))
|
||||
@@ -246,12 +253,16 @@
|
||||
;; History
|
||||
(handle-history el url resp-headers)
|
||||
|
||||
;; Settle triggers (after small delay)
|
||||
(when (get resp-headers "trigger-settle")
|
||||
(set-timeout
|
||||
(fn () (dispatch-trigger-events el
|
||||
(get resp-headers "trigger-settle")))
|
||||
20))
|
||||
;; Settle phase (after small delay): triggers + sx-on-settle hooks
|
||||
(set-timeout
|
||||
(fn ()
|
||||
;; Server-driven settle triggers
|
||||
(when (get resp-headers "trigger-settle")
|
||||
(dispatch-trigger-events el
|
||||
(get resp-headers "trigger-settle")))
|
||||
;; sx-on-settle: evaluate SX expression after swap settles
|
||||
(process-settle-hooks el))
|
||||
20)
|
||||
|
||||
;; Lifecycle event
|
||||
(dom-dispatch el "sx:afterSwap"
|
||||
@@ -452,6 +463,27 @@
|
||||
(process-elements root)))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; sx-on-settle — post-swap SX evaluation
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; After a swap settles, evaluate the SX expression in the trigger element's
|
||||
;; sx-on-settle attribute. The expression has access to all primitives
|
||||
;; (including use-store, reset!, deref) so it can update reactive state
|
||||
;; based on what the server returned.
|
||||
;;
|
||||
;; Example: (button :sx-get "/search" :sx-on-settle "(reset! (use-store \"count\") 0)")
|
||||
|
||||
(define process-settle-hooks
|
||||
(fn (el)
|
||||
(let ((settle-expr (dom-get-attr el "sx-on-settle")))
|
||||
(when (and settle-expr (not (empty? settle-expr)))
|
||||
(let ((exprs (sx-parse settle-expr)))
|
||||
(for-each
|
||||
(fn (expr) (eval-expr expr (env-extend (dict))))
|
||||
exprs))))))
|
||||
|
||||
|
||||
(define activate-scripts
|
||||
(fn (root)
|
||||
;; Re-activate scripts in swapped content.
|
||||
|
||||
@@ -306,7 +306,47 @@
|
||||
|
||||
|
||||
;; ==========================================================================
|
||||
;; 12. Named stores — page-level signal containers (L3)
|
||||
;; 12. Marsh scopes — child scopes within islands
|
||||
;; ==========================================================================
|
||||
;;
|
||||
;; Marshes are zones inside islands where server content is re-evaluated
|
||||
;; in the island's reactive context. When a marsh is re-morphed with new
|
||||
;; content, its old effects and computeds must be disposed WITHOUT disturbing
|
||||
;; the island's own reactive graph.
|
||||
;;
|
||||
;; Scope hierarchy: island → marsh → effects/computeds
|
||||
;; Disposing a marsh disposes its subscope. Disposing an island disposes
|
||||
;; all its marshes. The signal graph is a tree, not a flat list.
|
||||
;;
|
||||
;; Platform interface required:
|
||||
;; (dom-set-data el key val) → void — store JS value on element
|
||||
;; (dom-get-data el key) → any — retrieve stored value
|
||||
|
||||
(define with-marsh-scope
|
||||
(fn (marsh-el body-fn)
|
||||
;; Execute body-fn collecting all disposables into a marsh-local list.
|
||||
;; Nested under the current island scope — if the island is disposed,
|
||||
;; the marsh is disposed too (because island scope collected the marsh's
|
||||
;; own dispose function).
|
||||
(let ((disposers (list)))
|
||||
(with-island-scope
|
||||
(fn (d) (append! disposers d))
|
||||
body-fn)
|
||||
;; Store disposers on the marsh element for later cleanup
|
||||
(dom-set-data marsh-el "sx-marsh-disposers" disposers))))
|
||||
|
||||
(define dispose-marsh-scope
|
||||
(fn (marsh-el)
|
||||
;; Dispose all effects/computeds registered in this marsh's scope.
|
||||
;; Parent island scope and sibling marshes are unaffected.
|
||||
(let ((disposers (dom-get-data marsh-el "sx-marsh-disposers")))
|
||||
(when disposers
|
||||
(for-each (fn (d) (invoke d)) disposers)
|
||||
(dom-set-data marsh-el "sx-marsh-disposers" nil)))))
|
||||
|
||||
|
||||
;; ==========================================================================
|
||||
;; 13. Named stores — page-level signal containers (L3)
|
||||
;; ==========================================================================
|
||||
;;
|
||||
;; Stores persist across island creation/destruction. They live at page
|
||||
|
||||
@@ -1198,7 +1198,7 @@ RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do',
|
||||
is_render_html_form = lambda name: contains_p(RENDER_HTML_FORMS, name)
|
||||
|
||||
# render-list-to-html
|
||||
render_list_to_html = lambda expr, env: ('' if sx_truthy(empty_p(expr)) else (lambda head: (join('', map(lambda x: render_value_to_html(x, env), expr)) if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))) else (lambda name: (lambda args: (join('', map(lambda x: render_to_html(x, env), args)) if sx_truthy((name == '<>')) else (join('', map(lambda x: sx_str(trampoline(eval_expr(x, env))), args)) if sx_truthy((name == 'raw!')) else (render_html_lake(args, env) if sx_truthy((name == 'lake')) else (render_html_element(name, args, env) if sx_truthy(contains_p(HTML_TAGS, name)) else (render_html_island(env_get(env, name), args, env) if sx_truthy((starts_with_p(name, '~') if not sx_truthy(starts_with_p(name, '~')) else (env_has(env, name) if not sx_truthy(env_has(env, name)) else is_island(env_get(env, name))))) else ((lambda val: (render_html_component(val, args, env) if sx_truthy(is_component(val)) else (render_to_html(expand_macro(val, args, env), env) if sx_truthy(is_macro(val)) else error(sx_str('Unknown component: ', name)))))(env_get(env, name)) if sx_truthy(starts_with_p(name, '~')) else (dispatch_html_form(name, expr, env) if sx_truthy(is_render_html_form(name)) else (render_to_html(expand_macro(env_get(env, name), args, env), env) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else render_value_to_html(trampoline(eval_expr(expr, env)), env))))))))))(rest(expr)))(symbol_name(head))))(first(expr)))
|
||||
render_list_to_html = lambda expr, env: ('' if sx_truthy(empty_p(expr)) else (lambda head: (join('', map(lambda x: render_value_to_html(x, env), expr)) if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))) else (lambda name: (lambda args: (join('', map(lambda x: render_to_html(x, env), args)) if sx_truthy((name == '<>')) else (join('', map(lambda x: sx_str(trampoline(eval_expr(x, env))), args)) if sx_truthy((name == 'raw!')) else (render_html_lake(args, env) if sx_truthy((name == 'lake')) else (render_html_marsh(args, env) if sx_truthy((name == 'marsh')) else (render_html_element(name, args, env) if sx_truthy(contains_p(HTML_TAGS, name)) else (render_html_island(env_get(env, name), args, env) if sx_truthy((starts_with_p(name, '~') if not sx_truthy(starts_with_p(name, '~')) else (env_has(env, name) if not sx_truthy(env_has(env, name)) else is_island(env_get(env, name))))) else ((lambda val: (render_html_component(val, args, env) if sx_truthy(is_component(val)) else (render_to_html(expand_macro(val, args, env), env) if sx_truthy(is_macro(val)) else error(sx_str('Unknown component: ', name)))))(env_get(env, name)) if sx_truthy(starts_with_p(name, '~')) else (dispatch_html_form(name, expr, env) if sx_truthy(is_render_html_form(name)) else (render_to_html(expand_macro(env_get(env, name), args, env), env) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else render_value_to_html(trampoline(eval_expr(expr, env)), env)))))))))))(rest(expr)))(symbol_name(head))))(first(expr)))
|
||||
|
||||
# dispatch-html-form
|
||||
dispatch_html_form = lambda name, expr, env: ((lambda cond_val: (render_to_html(nth(expr, 2), env) if sx_truthy(cond_val) else (render_to_html(nth(expr, 3), env) if sx_truthy((len(expr) > 3)) else '')))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'if')) else (('' if sx_truthy((not sx_truthy(trampoline(eval_expr(nth(expr, 1), env))))) else join('', map(lambda i: render_to_html(nth(expr, i), env), range(2, len(expr))))) if sx_truthy((name == 'when')) else ((lambda branch: (render_to_html(branch, env) if sx_truthy(branch) else ''))(eval_cond(rest(expr), env)) if sx_truthy((name == 'cond')) else (render_to_html(trampoline(eval_expr(expr, env)), env) if sx_truthy((name == 'case')) else ((lambda local: join('', map(lambda i: render_to_html(nth(expr, i), local), range(2, len(expr)))))(process_bindings(nth(expr, 1), env)) if sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))) else (join('', map(lambda i: render_to_html(nth(expr, i), env), range(1, len(expr)))) if sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))) else (_sx_begin(trampoline(eval_expr(expr, env)), '') if sx_truthy(is_definition_form(name)) else ((lambda f: (lambda coll: join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll)))(trampoline(eval_expr(nth(expr, 2), env))))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'map')) else ((lambda f: (lambda coll: join('', map_indexed(lambda i, item: (render_lambda_html(f, [i, item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [i, item]), env)), coll)))(trampoline(eval_expr(nth(expr, 2), env))))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'map-indexed')) else (render_to_html(trampoline(eval_expr(expr, env)), env) if sx_truthy((name == 'filter')) else ((lambda f: (lambda coll: join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll)))(trampoline(eval_expr(nth(expr, 2), env))))(trampoline(eval_expr(nth(expr, 1), env))) if sx_truthy((name == 'for-each')) else render_value_to_html(trampoline(eval_expr(expr, env)), env))))))))))))
|
||||
@@ -1221,6 +1221,15 @@ def render_html_lake(args, env):
|
||||
reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'lake_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'lake_tag', kval) if sx_truthy((kname == 'tag')) else NIL)), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args)
|
||||
return sx_str('<', _cells['lake_tag'], ' data-sx-lake="', escape_attr((_cells['lake_id'] if sx_truthy(_cells['lake_id']) else '')), '">', join('', map(lambda c: render_to_html(c, env), children)), '</', _cells['lake_tag'], '>')
|
||||
|
||||
# render-html-marsh
|
||||
def render_html_marsh(args, env):
|
||||
_cells = {}
|
||||
_cells['marsh_id'] = NIL
|
||||
_cells['marsh_tag'] = 'div'
|
||||
children = []
|
||||
reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'marsh_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'marsh_tag', kval) if sx_truthy((kname == 'tag')) else (NIL if sx_truthy((kname == 'transform')) else NIL))), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args)
|
||||
return sx_str('<', _cells['marsh_tag'], ' data-sx-marsh="', escape_attr((_cells['marsh_id'] if sx_truthy(_cells['marsh_id']) else '')), '">', join('', map(lambda c: render_to_html(c, env), children)), '</', _cells['marsh_tag'], '>')
|
||||
|
||||
# render-html-island
|
||||
render_html_island = lambda island, args, env: (lambda kwargs: (lambda children: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(kwargs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), (lambda local: (lambda island_name: _sx_begin(for_each(lambda p: _sx_dict_set(local, p, (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)), component_params(island)), (_sx_dict_set(local, 'children', make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))) if sx_truthy(component_has_children(island)) else NIL), (lambda body_html: (lambda state_json: sx_str('<span data-sx-island="', escape_attr(island_name), '"', (sx_str(' data-sx-state="', escape_attr(state_json), '"') if sx_truthy(state_json) else ''), '>', body_html, '</span>'))(serialize_island_state(kwargs)))(render_to_html(component_body(island), local))))(component_name(island)))(env_merge(component_closure(island), env))))([]))({})
|
||||
|
||||
@@ -1237,7 +1246,7 @@ render_to_sx = lambda expr, env: (lambda result: (result if sx_truthy((type_of(r
|
||||
aser = lambda expr, env: _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else aser_list(expr, env))), (None, lambda: expr)])
|
||||
|
||||
# aser-list
|
||||
aser_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: aser(x, env), expr) if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))) else (lambda name: (aser_fragment(args, env) if sx_truthy((name == '<>')) else (aser_call(name, args, env) if sx_truthy(starts_with_p(name, '~')) else (aser_call(name, args, env) if sx_truthy((name == 'lake')) else (aser_call(name, args, env) if sx_truthy(contains_p(HTML_TAGS, name)) else (aser_special(name, expr, env) if sx_truthy((is_special_form(name) if sx_truthy(is_special_form(name)) else is_ho_form(name))) else (aser(expand_macro(env_get(env, name), args, env), env) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (lambda f: (lambda evaled_args: (apply(f, evaled_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else ((not sx_truthy(is_component(f))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))) else (trampoline(call_lambda(f, evaled_args, env)) if sx_truthy(is_lambda(f)) else (aser_call(sx_str('~', component_name(f)), args, env) if sx_truthy(is_component(f)) else (aser_call(sx_str('~', component_name(f)), args, env) if sx_truthy(is_island(f)) else error(sx_str('Not callable: ', inspect(f))))))))(map(lambda a: trampoline(eval_expr(a, env)), args)))(trampoline(eval_expr(head, env))))))))))(symbol_name(head))))(rest(expr)))(first(expr))
|
||||
aser_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: aser(x, env), expr) if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))) else (lambda name: (aser_fragment(args, env) if sx_truthy((name == '<>')) else (aser_call(name, args, env) if sx_truthy(starts_with_p(name, '~')) else (aser_call(name, args, env) if sx_truthy((name == 'lake')) else (aser_call(name, args, env) if sx_truthy((name == 'marsh')) else (aser_call(name, args, env) if sx_truthy(contains_p(HTML_TAGS, name)) else (aser_special(name, expr, env) if sx_truthy((is_special_form(name) if sx_truthy(is_special_form(name)) else is_ho_form(name))) else (aser(expand_macro(env_get(env, name), args, env), env) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (lambda f: (lambda evaled_args: (apply(f, evaled_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else ((not sx_truthy(is_component(f))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))) else (trampoline(call_lambda(f, evaled_args, env)) if sx_truthy(is_lambda(f)) else (aser_call(sx_str('~', component_name(f)), args, env) if sx_truthy(is_component(f)) else (aser_call(sx_str('~', component_name(f)), args, env) if sx_truthy(is_island(f)) else error(sx_str('Not callable: ', inspect(f))))))))(map(lambda a: trampoline(eval_expr(a, env)), args)))(trampoline(eval_expr(head, env)))))))))))(symbol_name(head))))(rest(expr)))(first(expr))
|
||||
|
||||
# aser-fragment
|
||||
aser_fragment = lambda children, env: (lambda parts: ('' if sx_truthy(empty_p(parts)) else sx_str('(<> ', join(' ', map(serialize, parts)), ')')))(filter(lambda x: (not sx_truthy(is_nil(x))), map(lambda c: aser(c, env), children)))
|
||||
@@ -1410,6 +1419,12 @@ def with_island_scope(scope_fn, body_fn):
|
||||
# register-in-scope
|
||||
register_in_scope = lambda disposable: (_island_scope(disposable) if sx_truthy(_island_scope) else NIL)
|
||||
|
||||
# with-marsh-scope
|
||||
with_marsh_scope = lambda marsh_el, body_fn: (lambda disposers: _sx_begin(with_island_scope(lambda d: _sx_append(disposers, d), body_fn), dom_set_data(marsh_el, 'sx-marsh-disposers', disposers)))([])
|
||||
|
||||
# dispose-marsh-scope
|
||||
dispose_marsh_scope = lambda marsh_el: (lambda disposers: (_sx_begin(for_each(lambda d: invoke(d), disposers), dom_set_data(marsh_el, 'sx-marsh-disposers', NIL)) if sx_truthy(disposers) else NIL))(dom_get_data(marsh_el, 'sx-marsh-disposers'))
|
||||
|
||||
# *store-registry*
|
||||
_store_registry = {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user