;; content-on-sx — external CMS sync via an injected adapter. ;; ;; Sync is a peripheral, not a feature. The core defines a SHAPE — an adapter is ;; a dict {:import (fn external doc-id -> doc) :export (fn doc -> external)} — and ;; delegates to it. The core knows nothing about Ghost's data model; all ;; translation lives in the adapter. Swap the adapter and the core is unchanged; ;; if Ghost goes away, nothing here does. ;; ;; Requires (loaded by harness): block.sx, doc.sx. ;; ── generic boundary: pure delegation ── (define content/import (fn (adapter external doc-id) ((get adapter :import) external doc-id))) (define content/export (fn (adapter doc) ((get adapter :export) doc))) ;; round-trip a document through an adapter (export then import). (define content/round-trip (fn (adapter doc) (content/import adapter (content/export adapter doc) (doc-id doc)))) ;; ── a Ghost-flavoured adapter (the peripheral). Ghost knowledge is confined ;; here: a post is {:title :sections (list section)}; a section is a tagged dict ;; {:kind ...} that this adapter maps to/from content blocks. ── (define ghost-section->block (fn (sec) (let ((kind (get sec :kind)) (id (get sec :id))) (cond ((= kind "heading") (mk-heading id (get sec :level) (get sec :text))) ((= kind "paragraph") (mk-text id (get sec :text))) ((= kind "image") (mk-image id (get sec :src) (get sec :alt))) ((= kind "code") (mk-code id (get sec :language) (get sec :text))) ((= kind "quote") (mk-quote id (get sec :cite) (get sec :text))) ((= kind "hr") (mk-divider id)) ((= kind "list") (mk-list id (get sec :ordered) (get sec :items))) ((= kind "embed") (mk-embed id (get sec :url) (get sec :provider))) (else (mk-text id (get sec :text))))))) (define block->ghost-section (fn (b) (let ((t (blk-type b)) (id (blk-id b))) (cond ((= t "heading") {:id id :text (str (blk-send b "text")) :kind "heading" :level (blk-send b "level")}) ((= t "text") {:id id :text (str (blk-send b "text")) :kind "paragraph"}) ((= t "image") {:id id :src (str (blk-send b "src")) :alt (str (blk-send b "alt")) :kind "image"}) ((= t "code") {:id id :text (str (blk-send b "text")) :kind "code" :language (str (blk-send b "language"))}) ((= t "quote") {:cite (str (blk-send b "cite")) :id id :text (str (blk-send b "text")) :kind "quote"}) ((= t "divider") {:id id :kind "hr"}) ((= t "list") {:items (blk-send b "items") :id id :kind "list" :ordered (blk-send b "ordered")}) ((= t "embed") {:id id :provider (str (blk-send b "provider")) :kind "embed" :url (str (blk-send b "url"))}) (else {:id id :text "" :kind "paragraph"}))))) (define ghost-import (fn (post doc-id) (doc-new doc-id (map ghost-section->block (get post :sections))))) (define ghost-export (fn (doc) {:sections (map block->ghost-section (doc-blocks doc))})) (define ghost-adapter {:export ghost-export :import ghost-import})