diff --git a/lib/host/blog.sx b/lib/host/blog.sx index 419a0588..6473bfe4 100644 --- a/lib/host/blog.sx +++ b/lib/host/blog.sx @@ -481,6 +481,19 @@ (host/blog-fields-of t))) (list) (host/blog-types-of slug)))) +;; the COMPOSITION fields an object has — the fields its (transitive) types declare with +;; :type "Composition" (each edited by its own block editor). Default ["body"] if a type +;; declares none, so every object still has one root composition. This is layer 2: "types +;; declare which fields are compositions" (the schema for the object's structure). +(define host/blog--composition-fields + (fn (slug) + (let ((cf (reduce (fn (acc f) (if (= (get f :type) "Composition") (concat acc (list (str (get f :name)))) acc)) + (list) (host/blog--fields-for-post slug)))) + (if (empty? cf) (list "body") cf)))) +;; the SCALAR (non-composition) fields — the generic edit form's inputs (compositions get a +;; block editor instead of a text input). +(define host/blog--scalar-fields + (fn (slug) (filter (fn (f) (not (= (get f :type) "Composition"))) (host/blog--fields-for-post slug)))) ;; render one labelled input per field, pre-filled from `values`. Widget per value-type ;; (textarea for Text, else a typed ). Pure — takes pre-fetched fields + values. (define host/blog--field-inputs @@ -516,11 +529,8 @@ ;; A record may carry a :body — a composition node (seq/par/alt/each over object refs) ;; rendered by the render-fold (lib/host/compose.sx) against a context. When present it ;; supersedes :sx-content. This is fold #1; the same object renders differently per context. -(define host/blog-body-of (fn (slug) (get (host/blog-get slug) :body))) -(define host/blog--set-body! - (fn (slug body) - (let ((r (host/blog-get slug))) - (when r (host/blog--write! slug (merge r {:body body})))))) +(define host/blog-body-of (fn (slug) (host/blog--comp-of slug "body"))) +(define host/blog--set-body! (fn (slug body) (host/blog--set-comp! slug "body" body))) ;; The resolver for the composition `each` graph-query source (compose.sx asks the context ;; for "query"). `(query REL TYPE)` -> the objects related to TYPE by REL, as full records ;; so the per-item template can field them. Today the supported relation is is-a (TYPE's @@ -731,13 +741,30 @@ ;; CONDITIONAL (alt+when — the "or": show the first branch whose condition holds), or a ;; REPEATER (each — the loop: render a template per graph-query result). seq is the "and". ;; The editor edits this inline tree; leaves stay external refs. (composition-objects.md.) -(define host/blog-body-nodes - (fn (slug) - (let ((body (host/blog-body-of slug))) - (if (and (= (type-of body) "list") (= (str (first body)) "seq")) - (rest body) (list))))) -(define host/blog--set-body-nodes! - (fn (slug nodes) (host/blog--set-body! slug (cons (quote seq) nodes)))) +;; a composition FIELD's value on an object — inline, part of the CID. A type declares which +;; of its fields are compositions (host/blog--composition-fields); an object may carry several +;; (:body, :aside, …), each edited by its own block editor. Compositions live in a STRING-KEYED +;; sub-dict :comps (string keys round-trip through persist cleanly, unlike a mix of keyword and +;; string top-level keys). The default "body" field falls back to a legacy top-level :body. +(define host/blog--comps (fn (rec) (or (get rec :comps) {}))) +(define host/blog--comp-of + (fn (slug field) + (let ((r (host/blog-get slug))) + (let ((c (get (host/blog--comps r) field))) + (if (nil? c) (if (= field "body") (get r :body) nil) c))))) +(define host/blog--set-comp! + (fn (slug field v) + (let ((r (host/blog-get slug))) + (when r (host/blog--write! slug (assoc r :comps (assoc (host/blog--comps r) field v))))))) +(define host/blog--comp-nodes + (fn (slug field) + (let ((c (host/blog--comp-of slug field))) + (if (and (= (type-of c) "list") (= (str (first c)) "seq")) (rest c) (list))))) +(define host/blog--set-comp-nodes! + (fn (slug field nodes) (host/blog--set-comp! slug field (cons (quote seq) nodes)))) +;; back-compat: the default "body" field. +(define host/blog-body-nodes (fn (slug) (host/blog--comp-nodes slug "body"))) +(define host/blog--set-body-nodes! (fn (slug nodes) (host/blog--set-comp-nodes! slug "body" nodes))) ;; the value at index k of a list (any element type). (define host/blog--nth (fn (xs k) (let loop ((i 0) (ys xs)) @@ -770,7 +797,7 @@ (fn (container slug) (if (starts-with? slug (str container "__")) (substr slug (+ (len container) 2)) slug))) (define host/blog--append-node! - (fn (slug node) (host/blog--set-body-nodes! slug (concat (host/blog-body-nodes slug) (list node))))) + (fn (slug field node) (host/blog--set-comp-nodes! slug field (concat (host/blog--comp-nodes slug field) (list node))))) ;; the kind of a body node, for the editor: "card" | "cond" | "each" | "other". (define host/blog--node-kind (fn (node) @@ -863,55 +890,55 @@ (else "auth"))) "auth"))) -;; add a CARD block to `field`: (ref ). Returns the new card's storage slug. +;; add a CARD block to a composition `field`: (ref ). Returns the card slug. (define host/blog-block-add! - (fn (slug ctype fields) - (let ((cslug (host/blog--new-card! slug "body" ctype fields))) - (begin (host/blog--append-node! slug (list (quote ref) (host/blog--slug->ref slug cslug))) cslug)))) + (fn (slug field ctype fields) + (let ((cslug (host/blog--new-card! slug field ctype fields))) + (begin (host/blog--append-node! slug field (list (quote ref) (host/blog--slug->ref slug cslug))) cslug)))) ;; add a CONDITIONAL (or) block: (alt (when (ref A)) (else (ref B))) — A/B relative refs. (define host/blog-block-add-cond! - (fn (slug ckey) - (let ((a (host/blog--slug->ref slug (host/blog--new-card! slug "body" "card-text" {"text" "shown when the condition holds"}))) - (b (host/blog--slug->ref slug (host/blog--new-card! slug "body" "card-text" {"text" "shown otherwise"})))) - (host/blog--append-node! slug + (fn (slug field ckey) + (let ((a (host/blog--slug->ref slug (host/blog--new-card! slug field "card-text" {"text" "shown when the condition holds"}))) + (b (host/blog--slug->ref slug (host/blog--new-card! slug field "card-text" {"text" "shown otherwise"})))) + (host/blog--append-node! slug field (list (quote alt) (list (quote when) (host/blog--cond->pred ckey) (list (quote ref) a)) (list (quote else) (list (quote ref) b))))))) ;; add a REPEATER (each) block: (each (query is-a TYPE) (ref