Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
164 lines
4.9 KiB
Plaintext
164 lines
4.9 KiB
Plaintext
;; content-on-sx — typed block objects on Smalltalk-on-SX.
|
|
;;
|
|
;; A block is a Smalltalk instance. Behaviour (type tag, later render) is a
|
|
;; message, not a property switch. Fields are immutable: blk-set / mk-* build a
|
|
;; fresh instance via the functional st-iv-set!, so old versions are never
|
|
;; clobbered (history-safe for the persist op log and CRDT merge).
|
|
;;
|
|
;; Hierarchy:
|
|
;; CtBlock (id)
|
|
;; CtText (text)
|
|
;; CtHeading (level)
|
|
;; CtCode (language)
|
|
;; CtQuote (cite)
|
|
;; CtImage (src alt)
|
|
;; CtEmbed (url provider)
|
|
;; CtDivider
|
|
;; CtList (ordered items)
|
|
|
|
(define
|
|
ct-def-method!
|
|
(fn (cls sel src) (st-class-add-method! cls sel (st-parse-method src))))
|
|
|
|
;; Register the block hierarchy in the Smalltalk class table. Call AFTER
|
|
;; st-bootstrap-classes! (which resets the table). Idempotent.
|
|
(define
|
|
content-bootstrap-blocks!
|
|
(fn
|
|
()
|
|
(begin
|
|
(st-class-define! "CtBlock" "Object" (list "id"))
|
|
(ct-def-method! "CtBlock" "id" "id ^ id")
|
|
(ct-def-method! "CtBlock" "type" "type ^ #block")
|
|
(ct-def-method! "CtBlock" "isBlock" "isBlock ^ true")
|
|
(st-class-define! "CtText" "CtBlock" (list "text"))
|
|
(ct-def-method! "CtText" "text" "text ^ text")
|
|
(ct-def-method! "CtText" "type" "type ^ #text")
|
|
(st-class-define! "CtHeading" "CtText" (list "level"))
|
|
(ct-def-method! "CtHeading" "level" "level ^ level")
|
|
(ct-def-method! "CtHeading" "type" "type ^ #heading")
|
|
(st-class-define! "CtCode" "CtText" (list "language"))
|
|
(ct-def-method! "CtCode" "language" "language ^ language")
|
|
(ct-def-method! "CtCode" "type" "type ^ #code")
|
|
(st-class-define! "CtQuote" "CtText" (list "cite"))
|
|
(ct-def-method! "CtQuote" "cite" "cite ^ cite")
|
|
(ct-def-method! "CtQuote" "type" "type ^ #quote")
|
|
(st-class-define! "CtImage" "CtBlock" (list "src" "alt"))
|
|
(ct-def-method! "CtImage" "src" "src ^ src")
|
|
(ct-def-method! "CtImage" "alt" "alt ^ alt")
|
|
(ct-def-method! "CtImage" "type" "type ^ #image")
|
|
(st-class-define! "CtEmbed" "CtBlock" (list "url" "provider"))
|
|
(ct-def-method! "CtEmbed" "url" "url ^ url")
|
|
(ct-def-method! "CtEmbed" "provider" "provider ^ provider")
|
|
(ct-def-method! "CtEmbed" "type" "type ^ #embed")
|
|
(st-class-define! "CtDivider" "CtBlock" (list))
|
|
(ct-def-method! "CtDivider" "type" "type ^ #divider")
|
|
(st-class-define! "CtList" "CtBlock" (list "ordered" "items"))
|
|
(ct-def-method! "CtList" "ordered" "ordered ^ ordered")
|
|
(ct-def-method! "CtList" "items" "items ^ items")
|
|
(ct-def-method! "CtList" "type" "type ^ #list")
|
|
true)))
|
|
|
|
;; Apply (name value) pairs functionally onto a fresh instance.
|
|
(define
|
|
ct-apply-fields
|
|
(fn
|
|
(inst pairs)
|
|
(if
|
|
(= (len pairs) 0)
|
|
inst
|
|
(ct-apply-fields
|
|
(st-iv-set!
|
|
inst
|
|
(first (first pairs))
|
|
(first (rest (first pairs))))
|
|
(rest pairs)))))
|
|
|
|
(define
|
|
ct-class-for-type
|
|
(fn
|
|
(tag)
|
|
(cond
|
|
((= tag "text") "CtText")
|
|
((= tag "heading") "CtHeading")
|
|
((= tag "code") "CtCode")
|
|
((= tag "quote") "CtQuote")
|
|
((= tag "image") "CtImage")
|
|
((= tag "embed") "CtEmbed")
|
|
((= tag "divider") "CtDivider")
|
|
((= tag "list") "CtList")
|
|
(else (error (str "unknown block type: " tag))))))
|
|
|
|
;; Generic constructor — wire tag + id + (name value) field pairs.
|
|
(define
|
|
mk-block
|
|
(fn
|
|
(type-tag id fields)
|
|
(ct-apply-fields
|
|
(st-iv-set! (st-make-instance (ct-class-for-type type-tag)) "id" id)
|
|
fields)))
|
|
|
|
(define
|
|
mk-text
|
|
(fn (id text) (mk-block "text" id (list (list "text" text)))))
|
|
|
|
(define
|
|
mk-heading
|
|
(fn
|
|
(id level text)
|
|
(mk-block "heading" id (list (list "level" level) (list "text" text)))))
|
|
|
|
(define
|
|
mk-code
|
|
(fn
|
|
(id language text)
|
|
(mk-block
|
|
"code"
|
|
id
|
|
(list (list "language" language) (list "text" text)))))
|
|
|
|
(define
|
|
mk-quote
|
|
(fn
|
|
(id cite text)
|
|
(mk-block "quote" id (list (list "cite" cite) (list "text" text)))))
|
|
|
|
(define
|
|
mk-image
|
|
(fn
|
|
(id src alt)
|
|
(mk-block "image" id (list (list "src" src) (list "alt" alt)))))
|
|
|
|
(define
|
|
mk-embed
|
|
(fn
|
|
(id url provider)
|
|
(mk-block "embed" id (list (list "url" url) (list "provider" provider)))))
|
|
|
|
(define mk-divider (fn (id) (mk-block "divider" id (list))))
|
|
|
|
(define
|
|
mk-list
|
|
(fn
|
|
(id ordered items)
|
|
(mk-block
|
|
"list"
|
|
id
|
|
(list (list "ordered" ordered) (list "items" items)))))
|
|
|
|
;; Accessors. blk-type / blk-id go through message dispatch (polymorphic);
|
|
;; blk-get reads any ivar directly; blk-set is copy-on-write.
|
|
(define blk-id (fn (b) (st-send b "id" (list))))
|
|
(define blk-type (fn (b) (str (st-send b "type" (list)))))
|
|
(define blk-send (fn (b sel) (st-send b sel (list))))
|
|
(define blk-get (fn (b field) (st-iv-get b field)))
|
|
(define blk-set (fn (b field val) (st-iv-set! b field val)))
|
|
|
|
(define
|
|
block?
|
|
(fn
|
|
(v)
|
|
(and
|
|
(st-instance? v)
|
|
(st-class-inherits-from? (get v :class) "CtBlock"))))
|