@vmartel08 I added the links properly to the original post 
Adding classes to HTML <ul>
elements
I did some transforming of Typst lists into custom HTML (actually just adding a class
to the ul
element) for a package of mine, see here: typst-moodular/src/c4l/mod.typ at main · SillyFreak/typst-moodular · GitHub
This gives you a first step in that direction:
#show list: it => {
show: html.elem.with("ul", attrs: (class: "tree"))
for elem in it.children {
html.elem("li", elem.body)
}
}
- Giant planets
- Gas giants
- Jupiter
- Saturn
- Ice giants
- Uranus
- Neptune
HTML output
<ul class="tree">
<li>
<p>Giant planets</p>
<ul class="tree">
<li>
<p>Gas giants</p>
<ul class="tree">
<li>Jupiter</li>
<li>Saturn</li>
</ul>
</li>
<li>
<p>Ice giants</p>
<ul class="tree">
<li>Uranus</li>
<li>Neptune</li>
</ul>
</li>
</ul>
</li>
</ul>
Selecting specific nesting levels
The class is wrongly applied to all nested lists too; we can prevent that with a state:
#let tree-depth = state("tree-depth", 0)
#show list: it => context {
show: html.elem.with("ul", attrs: (:
..if tree-depth.get() == 0 { (class: "tree") }
))
tree-depth.update(x => x + 1)
for elem in it.children {
html.elem("li", elem.body)
}
tree-depth.update(x => x - 1)
}
...
HTML output
<ul class="tree">
<li>
<p>Giant planets</p>
<ul>
<li>
<p>Gas giants</p>
<ul>
<li>Jupiter</li>
<li>Saturn</li>
</ul>
</li>
<li>
<p>Ice giants</p>
<ul>
<li>Uranus</li>
<li>Neptune</li>
</ul>
</li>
</ul>
</li>
</ul>
Transforming list items into <details>
elements
The next step is a bit more involved; basically, we want to determine if a list element contains a nested list, and if so, split that into summary and details:
/// returns either a tuple (summary, details) or none
#let split-summary(body) = {
assert.eq(type(body), content)
let sequence = [].func()
// this is a list: no summary
if body.func() in (list, list.item) { return (none, body) }
// if it's not a sequence it can't contain a list
// at least not directly – a block containing a list will throw us off!
if body.func() not in (sequence,) { return none }
let index = body.children.position(c => c.func() in (list, list.item))
// if no list or item is found, it doesn't contain a list
if index == none { return none }
// split and return the parts
let summary = body.children.slice(0, index).join()
let body = body.children.slice(index).join()
(summary, body)
}
I’m making it a bit easy for myself here; Once I found a list item, everything else is part of the <details>
. This will break for list items like these:
- Summary
- the
- details
More content; should not be part of the details
However since I don’t know if the CSS would handle that anyway, I have chosen to ignore this limitation.
Now, we create a <details>
element. Setting <details open>
is easy since we know the depth of each list anyway.
#show list: it => context {
let top-level = tree-depth.get() == 0
show: html.elem.with("ul", attrs: (:
..if top-level { (class: "tree") }
))
tree-depth.update(x => x + 1)
for elem in it.children {
let result = split-summary(elem.body)
let body = if result == none {
// a regular list element if there's no nested list
elem.body
} else {
// a <details> element
show: html.elem.with("details", attrs: (:
..if top-level { (open: "true") }
))
let (summary, body) = result
if summary != none {
html.elem("summary", summary)
}
body
}
html.elem("li", body)
}
tree-depth.update(x => x - 1)
}
The result is almost perfectly what’s shown in [1]:
HTML output
<ul class="tree">
<li>
<details open="true">
<summary>Giant planets</summary>
<ul>
<li>
<details>
<summary>Gas giants</summary>
<ul>
<li>Jupiter</li>
<li>Saturn</li>
</ul>
</details>
</li>
<li>
<details>
<summary>Ice giants</summary>
<ul>
<li>Uranus</li>
<li>Neptune</li>
</ul>
</details>
</li>
</ul>
</details>
</li>
</ul>
The only difference is pretty printing, and that Typst produced <details open="true">
instead of <details open>
.