How to outline all heading with label of the form with `<exercise:...>`?

Hello, first post in here, hope I didn’t mess up the tags and category.
So I am making revision cards, in which there are exercises that I’d like to review. I labelled the header of each exercise with <exercise:name-of-the-execise> and since I have so many, I would like to make an outline specifically for them. Is there a way to do so, and if not do you have any idea of how to do something similar? Thank you.

This took a bit, but should work well enough for you to customize to your own liking! I’m not sure how much your document might kick the bucket, but here it is anyway.

  1. First any element, you have referenced with a exercise label should be selected via the selector.or → here I’ve just limited to heading, but if you have more, just add it there
  2. Get the labels of the elements and insert none if it doesn’t have one → we get a list of all elements reduced down to their labels
  3. Then filter for the exercise: references → in exercises you’ll have the desired references
  4. Throw it in a for loop, query the element and start building your outline
    • I’ve created a simple list
#set page(height: auto, width: 10cm, margin: 5mm)

#let exercise-outline = context{
 let exercises = query(
     selector.or(
       heading,
       /* other referencable elements such as math.equation or figure */
     )
   ).map(x => x.at("label", default: none)).filter(x => (x!= none) and str(x).contains("exercise:"))

 heading(level: 1)[List of Exercises]
 for rf in exercises {
   let element = query(rf)
   block(above: 0.4em,grid(columns: (auto, 1fr, auto), element.at(0).body, box(width: 1fr, repeat[.]), link(element.first().location(), [#element.first().location().page()])))
 }
}

#exercise-outline


= Exercises

== Exercise \#1 <exercise:1>

== Exercise \#2 <exercise:2>

== Exercise \#3 <exercise:3>

== Exercise \#4 <exercise:4>

== Exercise \#5 <exercise:5>

= Some other stuff <blabla>

When you throw a reference into str(...), you’ll get the reference text itself!

Hey @Ruben, welcome to the forum! I’ve changed your question post’s title to better fit our guidelines: How to post in the Questions category

Make sure your title is a question you’d ask to a friend about Typst. :wink:

I’m guessing the best bet for now is either to recreate outline from scratch (@Electron_Wizard’s example), or wrap the headings in a figure with a custom kind that can be passed as a selector to the outline.target field, which is @PgBiel’s idea:

#set heading(numbering: "1.")
#show heading: it => {
  if not it.has("label") { return it }
  let label-name = repr(it.label).replace(regex("(^<|>$)"), "")
  if not label-name.starts-with("exercise:") { return it }
  show figure: set align(left)
  figure(it, kind: "exercise", supplement: none)
}

#show outline.entry: it => {
  if it.element.func() != figure or it.element.kind != "exercise" { return it }
  let h = it.element.body
  let numbers = counter(heading).at(h.location())
  let prefix = context numbering(h.numbering, ..numbers)
  it.indented(prefix)[#h.body #box(width: 1fr, it.fill)#it.page()]
}

#outline(title: "Exercises", target: figure.where(kind: "exercise"))
#outline()

= Exercises

== Exercise for this <exercise:1>

== Exercise for that <exercise:2>

== Exercise a <exercise:3>

== Exercise b <exercise:4>

== Exercise c <exercise:5>

= Some other stuff <blabla>
output

But I think the perfect outline formatting is gone anyway, unless there is a better way.

1 Like

Here is one solution that starts similar to the one given by @Electron_Wizard, but uses the actual outline element, which simplifies this a bit and allows show and set rules to still be applied:

#let exercise-outline(title: [List of Exercises]) = context {
  // Find all used <exercise:..> labels.
  let labels = query(heading)
    .filter(it => it.has("label") and str(it.label).starts-with("exercise:"))
    .map(it => it.label)

  // Create outline targeting headings with those labels.
  outline(
    indent: 0pt, // (maybe not necessary in your case)
    title: title,
    target: selector.or(..labels.map(label => heading.where(label: label)))
  )
}

#exercise-outline()

image