content: anchored-heading render (anchor.sx) + 6 tests (621/621)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
51
lib/content/anchor.sx
Normal file
51
lib/content/anchor.sx
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
;; content-on-sx — anchored-heading HTML render.
|
||||||
|
;;
|
||||||
|
;; Like asHTML, but headings carry an id attribute (the block id), so the TOC's
|
||||||
|
;; #id links resolve. A separate render so the plain asHTML stays unchanged.
|
||||||
|
;; Tree-aware (sections recurse); other blocks use their normal asHTML.
|
||||||
|
;;
|
||||||
|
;; Requires (loaded by harness): block.sx, doc.sx, render.sx (asHTML +
|
||||||
|
;; htmlEscaped).
|
||||||
|
|
||||||
|
(define
|
||||||
|
anch-section?
|
||||||
|
(fn (b) (and (st-instance? b) (= (get b :class) "CtSection"))))
|
||||||
|
(define anch-esc (fn (s) (str (st-send s "htmlEscaped" (list)))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
anchor-block
|
||||||
|
(fn
|
||||||
|
(b)
|
||||||
|
(cond
|
||||||
|
((= (blk-type b) "heading")
|
||||||
|
(let
|
||||||
|
((l (str (blk-get b "level"))) (id (blk-id b)))
|
||||||
|
(str
|
||||||
|
"<h"
|
||||||
|
l
|
||||||
|
" id=\""
|
||||||
|
id
|
||||||
|
"\">"
|
||||||
|
(anch-esc (str (blk-get b "text")))
|
||||||
|
"</h"
|
||||||
|
l
|
||||||
|
">")))
|
||||||
|
((anch-section? b)
|
||||||
|
(let
|
||||||
|
((ch (st-iv-get b "children")))
|
||||||
|
(str
|
||||||
|
"<section>"
|
||||||
|
(anchor-blocks (if (list? ch) ch (list)))
|
||||||
|
"</section>")))
|
||||||
|
(else (str (st-send b "asHTML" (list)))))))
|
||||||
|
|
||||||
|
(define
|
||||||
|
anchor-blocks
|
||||||
|
(fn
|
||||||
|
(blocks)
|
||||||
|
(if
|
||||||
|
(= (len blocks) 0)
|
||||||
|
""
|
||||||
|
(str (anchor-block (first blocks)) (anchor-blocks (rest blocks))))))
|
||||||
|
|
||||||
|
(define content/html-anchored (fn (doc) (anchor-blocks (doc-blocks doc))))
|
||||||
@@ -15,7 +15,7 @@ if [ ! -x "$SX_SERVER" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SUITES=(block doc render api meta page page-full markdown text section compose tree-edit clone query toc transform normalize find-replace stats table data wire validate store snapshot crdt crdt-store sync md-import md-doc fed)
|
SUITES=(block doc render api meta page page-full markdown text section compose tree-edit clone query toc anchor transform normalize find-replace stats table data wire validate store snapshot crdt crdt-store sync md-import md-doc fed)
|
||||||
|
|
||||||
OUT_JSON="lib/content/scoreboard.json"
|
OUT_JSON="lib/content/scoreboard.json"
|
||||||
OUT_MD="lib/content/scoreboard.md"
|
OUT_MD="lib/content/scoreboard.md"
|
||||||
@@ -50,6 +50,7 @@ run_suite() {
|
|||||||
(load "lib/content/clone.sx")
|
(load "lib/content/clone.sx")
|
||||||
(load "lib/content/query.sx")
|
(load "lib/content/query.sx")
|
||||||
(load "lib/content/toc.sx")
|
(load "lib/content/toc.sx")
|
||||||
|
(load "lib/content/anchor.sx")
|
||||||
(load "lib/content/transform.sx")
|
(load "lib/content/transform.sx")
|
||||||
(load "lib/content/normalize.sx")
|
(load "lib/content/normalize.sx")
|
||||||
(load "lib/content/find-replace.sx")
|
(load "lib/content/find-replace.sx")
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"clone": {"pass": 10, "fail": 0},
|
"clone": {"pass": 10, "fail": 0},
|
||||||
"query": {"pass": 13, "fail": 0},
|
"query": {"pass": 13, "fail": 0},
|
||||||
"toc": {"pass": 8, "fail": 0},
|
"toc": {"pass": 8, "fail": 0},
|
||||||
|
"anchor": {"pass": 6, "fail": 0},
|
||||||
"transform": {"pass": 12, "fail": 0},
|
"transform": {"pass": 12, "fail": 0},
|
||||||
"normalize": {"pass": 11, "fail": 0},
|
"normalize": {"pass": 11, "fail": 0},
|
||||||
"find-replace": {"pass": 10, "fail": 0},
|
"find-replace": {"pass": 10, "fail": 0},
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
"md-doc": {"pass": 12, "fail": 0},
|
"md-doc": {"pass": 12, "fail": 0},
|
||||||
"fed": {"pass": 20, "fail": 0}
|
"fed": {"pass": 20, "fail": 0}
|
||||||
},
|
},
|
||||||
"total_pass": 615,
|
"total_pass": 621,
|
||||||
"total_fail": 0,
|
"total_fail": 0,
|
||||||
"total": 615
|
"total": 621
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ _Generated by `lib/content/conformance.sh`_
|
|||||||
| clone | 10 | 0 | 10 |
|
| clone | 10 | 0 | 10 |
|
||||||
| query | 13 | 0 | 13 |
|
| query | 13 | 0 | 13 |
|
||||||
| toc | 8 | 0 | 8 |
|
| toc | 8 | 0 | 8 |
|
||||||
|
| anchor | 6 | 0 | 6 |
|
||||||
| transform | 12 | 0 | 12 |
|
| transform | 12 | 0 | 12 |
|
||||||
| normalize | 11 | 0 | 11 |
|
| normalize | 11 | 0 | 11 |
|
||||||
| find-replace | 10 | 0 | 10 |
|
| find-replace | 10 | 0 | 10 |
|
||||||
@@ -35,4 +36,4 @@ _Generated by `lib/content/conformance.sh`_
|
|||||||
| md-import | 38 | 0 | 38 |
|
| md-import | 38 | 0 | 38 |
|
||||||
| md-doc | 12 | 0 | 12 |
|
| md-doc | 12 | 0 | 12 |
|
||||||
| fed | 20 | 0 | 20 |
|
| fed | 20 | 0 | 20 |
|
||||||
| **Total** | **615** | **0** | **615** |
|
| **Total** | **621** | **0** | **621** |
|
||||||
|
|||||||
58
lib/content/tests/anchor.sx
Normal file
58
lib/content/tests/anchor.sx
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
;; Extension — anchored-heading HTML render (functional TOC links).
|
||||||
|
|
||||||
|
(st-bootstrap-classes!)
|
||||||
|
(content/bootstrap!)
|
||||||
|
(content-bootstrap-section!)
|
||||||
|
|
||||||
|
(define
|
||||||
|
d
|
||||||
|
(doc-append
|
||||||
|
(doc-append
|
||||||
|
(doc-append (doc-empty "d") (mk-heading "intro" 1 "Intro"))
|
||||||
|
(mk-text "p" "Body"))
|
||||||
|
(mk-section
|
||||||
|
"s"
|
||||||
|
(list (mk-heading "sub" 2 "Sub") (mk-text "n" "nested")))))
|
||||||
|
|
||||||
|
;; ── headings get id anchors; other blocks unchanged ──
|
||||||
|
(content-test
|
||||||
|
"anchored html"
|
||||||
|
(content/html-anchored d)
|
||||||
|
"<h1 id=\"intro\">Intro</h1><p>Body</p><section><h2 id=\"sub\">Sub</h2><p>nested</p></section>")
|
||||||
|
|
||||||
|
;; ── heading text escaped ──
|
||||||
|
(content-test
|
||||||
|
"anchored escapes text"
|
||||||
|
(content/html-anchored
|
||||||
|
(doc-append (doc-empty "d") (mk-heading "h" 2 "A < B")))
|
||||||
|
"<h2 id=\"h\">A < B</h2>")
|
||||||
|
|
||||||
|
;; ── non-heading-only doc identical to asHTML ──
|
||||||
|
(define
|
||||||
|
np
|
||||||
|
(doc-append
|
||||||
|
(doc-append (doc-empty "d") (mk-text "p" "x"))
|
||||||
|
(mk-image "i" "/a.png" "alt")))
|
||||||
|
(content-test "no headings == asHTML" (content/html-anchored np) (asHTML np))
|
||||||
|
|
||||||
|
;; ── empty doc ──
|
||||||
|
(content-test "anchored empty" (content/html-anchored (doc-empty "e")) "")
|
||||||
|
|
||||||
|
;; ── anchors match TOC ids (end-to-end) ──
|
||||||
|
(content-test
|
||||||
|
"anchor ids match toc"
|
||||||
|
(map (fn (h) (get h :id)) (content/headings d))
|
||||||
|
(list "intro" "sub"))
|
||||||
|
|
||||||
|
;; ── deep nesting ──
|
||||||
|
(define
|
||||||
|
deep
|
||||||
|
(doc-append
|
||||||
|
(doc-empty "d")
|
||||||
|
(mk-section
|
||||||
|
"o"
|
||||||
|
(list (mk-section "i" (list (mk-heading "deep" 3 "Deep")))))))
|
||||||
|
(content-test
|
||||||
|
"deep anchored"
|
||||||
|
(content/html-anchored deep)
|
||||||
|
"<section><section><h3 id=\"deep\">Deep</h3></section></section>")
|
||||||
@@ -19,7 +19,7 @@ injected adapter, not core.
|
|||||||
|
|
||||||
## Status (rolling)
|
## Status (rolling)
|
||||||
|
|
||||||
`bash lib/content/conformance.sh` → **615/615** (Phases 1–4 COMPLETE + extensions: HTML/SX escaping, Markdown render + import/export incl. tables & frontmatter (full round-trip), CRDT replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees + deep tree editing, doc stats, table block, HTML page wrapper + SEO page, doc composition + id-remap, portable data + wire serialization, block query + transforms + find/replace, TOC rendering, normalization)
|
`bash lib/content/conformance.sh` → **621/621** (Phases 1–4 COMPLETE + extensions: HTML/SX escaping, Markdown render + import/export incl. tables & frontmatter (full round-trip), CRDT replication, tree-aware validation, snapshot cache, doc metadata, plain-text render, nested block trees + deep tree editing, doc stats, table block, HTML page wrapper + SEO page, doc composition + id-remap, portable data + wire serialization, block query + transforms + find/replace, TOC rendering + anchored headings, normalization)
|
||||||
|
|
||||||
## Ground rules
|
## Ground rules
|
||||||
|
|
||||||
@@ -97,6 +97,7 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
|
|||||||
- [x] block query + TOC (`query.sx`: content/select/select-type/count-type/headings)
|
- [x] block query + TOC (`query.sx`: content/select/select-type/count-type/headings)
|
||||||
- [x] block transforms (`transform.sx`: content/map-blocks/map-type/set-field-on)
|
- [x] block transforms (`transform.sx`: content/map-blocks/map-type/set-field-on)
|
||||||
- [x] TOC rendering (`toc.sx`: content/toc-markdown + toc-html from headings)
|
- [x] TOC rendering (`toc.sx`: content/toc-markdown + toc-html from headings)
|
||||||
|
- [x] anchored-heading render (`anchor.sx`: content/html-anchored, functional TOC links)
|
||||||
- [x] document normalization (`normalize.sx`: content/normalize, drop empty blocks/sections)
|
- [x] document normalization (`normalize.sx`: content/normalize, drop empty blocks/sections)
|
||||||
- [x] global find/replace (`find-replace.sx`: content/find-replace across text-bearing blocks)
|
- [x] global find/replace (`find-replace.sx`: content/find-replace across text-bearing blocks)
|
||||||
- [x] portable data serialization (`data.sx`: content/to-data + from-data, round-trips tree)
|
- [x] portable data serialization (`data.sx`: content/to-data + from-data, round-trips tree)
|
||||||
@@ -104,6 +105,11 @@ lib/content/api.sx ── (content/edit) (content/render) (content/history) ─
|
|||||||
|
|
||||||
## Progress log
|
## Progress log
|
||||||
|
|
||||||
|
- 2026-06-07 — Extension: anchored-heading render (`anchor.sx`).
|
||||||
|
`content/html-anchored` renders like asHTML but headings carry `id="<block-id>"`
|
||||||
|
(tree-wide, sections recurse, text escaped), so the TOC's `#id` links resolve —
|
||||||
|
completing the TOC feature end-to-end. A separate render; plain asHTML
|
||||||
|
unchanged. 6 tests; suite 621/621.
|
||||||
- 2026-06-07 — Extension: global find/replace (`find-replace.sx`).
|
- 2026-06-07 — Extension: global find/replace (`find-replace.sx`).
|
||||||
`content/find-replace` replaces every occurrence of a substring in the text
|
`content/find-replace` replaces every occurrence of a substring in the text
|
||||||
field of text/heading/code/quote blocks tree-wide (via the transform layer) —
|
field of text/heading/code/quote blocks tree-wide (via the transform layer) —
|
||||||
|
|||||||
Reference in New Issue
Block a user