How can I put tags on parts of a document which can be indexed in a page?

I’m adding a possible solution even though, as already mentioned, there’s probably a package for doing this.

Usually, metadata is used for having own locatable elements. All locatable elements need to be unique, like metadata("x") != metadata("y"). Since you’re looking to use the same function call multiple times, presumably even on the same pages, I would exclude Typst’s functions from locating the metadata.

We could still make use of metadata by suffixing its value with something like -123. The function calls would remain the same, so no problem there, but the implementation of Regex gets slightly messier for searching. See a recent example of Regex search within labels How can I match labels or strings within `where()` using regex?

So ultimately, custom-made searching becomes almost unavoidable:

  • We could stick to using selectors, but I think problems would quickly arise as your use of the tag function becomes more scattered. See this example of more segregated duplicated labels, where such an approach is better How to allow reusing of labels in different sections?
  • Then I thought a reasonable solution may as well come from state.

The latter was indeed what I chose to go with:

#let skills = state("skills")

// Its every use updates the state
#let tag(cat) = context {
  // Passes only the "legitimate" skills, remove at will 
  if cat not in ("skill1", "skill2") {
    panic("wrong category " + cat)
  }

  let pg = here().page()
  skills.update(it => it + ((cat, pg),))
}

#let tag-together = context {
  // Array to dictionary without loss unlike `to-dict()`
  let tmp = (:)
  for (val, pg) in skills.final() {
    if tmp.at(val, default: "") == "" {
      tmp.insert(val, (pg,))
    } else {
      tmp.at(val).push(pg)
    }
  }

  heading(level: 1)[Skills]
  for (val, pg) in tmp [
    #linebreak()
    #val: p. #pg.map(it => link(
      (page: it, x: 0em, y: 0em),
      str(it))
    ).join(", ")
  ]
}

#tag-together

#tag("skill1")
#tag("skill2")

#pagebreak()

#tag("skill2")
Output

  • Function tag:
    • has its use like you have described.
    • belongs anywhere, allows duplicates.
    • returns nothing.
  • Variable tag-together:
    • is the output you have described. [1]
    • belongs near outline, once. [2]
    • returns content.

I assume storing the skill-page pairs differently inside state could simplify this further.


  1. Linking to a specific page requires faking a location. See use of location inside link How to set outline to hyperlink page number rather than the heading title? ↩︎

  2. There’s nothing stopping you from doing the exact opposite. ↩︎

1 Like