By using some of the same optimizations from How to mutate variables in a show rule? - #2 by Andrew, here is my slightly improved version:
#let make-outline-entry(label, body) = {
let filler = box(width: 1fr, repeat[.])
let content = body + " " + filler + counter(page).display()
block(link(label, strong(content)))
}
#show outline.entry: it => {
let label = <appendix>
let is-set = state("is-set", false)
context if not is-set.get() {
let found = query(selector(label).before(it.element.location()))
let is-after-appendix = found.len() > 0
if is-after-appendix {
make-outline-entry(<appendix>)[Appendix]
is-set.update(true)
}
}
it
}
The rest
#outline()
= H1
#lorem(25)
= H2
#lorem(25)
<appendix>
= A1 (Appendix starts here)
#lorem(25)
= A2
I made it more readable/maintainable. Or you can slap everything together to make the code smaller:
#show outline.entry: it => {
let label = <appendix>
let is-set = state("is-set", false)
context if not is-set.get() {
let found = query(selector(label).before(it.element.location()))
if found.len() > 0 {
block(link(label, strong({
"Appendix "
// box(width: 1fr, repeat[.])
box(width: 1fr, it.fill)
counter(page).display()
})))
is-set.update(true)
}
}
it
}
Some things (e.g., using ""
instead of []
for the name) are pure preference, but most things are the recommended way of writing Typst code (or code in general):
- using descriptive variable/function names;
- using
state()
overcounter()
if you don’t need to count natural numbers; - btw,
counter()
is initially set to0
, so you don’t have to explicitly set it initially unless you need to reset it at some point; - using
query(selector(pivot).before(location)).len() > 0
over manually comparing pages/y position;- there is also
.after()
; - sometimes you need to use things like
<element>.where()
which is already aselector
;
- there is also
- the recommended variable/function naming convention is to use kebab case (which is typically easier to type).
A little bit less “you should do it” things:
- adding aliases for things that need to be used multiple times (i.e.,
label
) to reduce chance of a “desync value” bug (and similar stuff); - making
context
scope as small as possible.