How to make backlinks?

I’m trying to make backlinks. Ideally I would just query every link in the document and filter based on the target, but links are not locatable yet. So, the approach I’ve taken is to create a label on every link. To ensure that multiple labels are not created with the same name, I increment a counter which is used in their naming.

This is my attempt.

#let prefix = "test:"

#let generate(it) = {
  if type(it.dest) != label {
    return it
  }

  let target = str(it.dest)
  let backlink_count = counter(prefix + target)
  context {
    let here = label(
      prefix + target + ":" + str(backlink_count.get().first())
    )
    return [
      #it
      #here

      #backlink_count.step()
    ]
  }
}

#let get(lab) = {
  let target = str(lab)
  let backlink_count = counter(prefix + target).final().first()

  return range(backlink_count).map(
    i => label(prefix + target + ":" + str(i))
  )
}

#show link: generate

Test <label1>

#link(<label1>)[Go to label1]

#context link(get(<label1>).first())[Backlink] // This errors

This errors with label `<test:label1:0>` does not exist in the document. Why? Can I fix this?

If you only use it to query the links, it’s fine to have multiple elements with the same label. So using just one label would probably suffice here.

1 Like

That’s great to know. Assuming the query returns the elements, I could then get their locations and link to those.

For some reason, though, the query here returns an empty array.

#let prefix = "test:"

#let generate(it) = {
  if type(it.dest) != label {
    return it
  }

  return [
    #it
    #label(prefix + str(it.dest))
  ]
}

#let get(lab) = {
  query(label(prefix + str(lab)))
}

#show link: generate

Target <link0>

#link(<link0>)[Backlink]

#context get(<link0>)

You’ve queried for <test:link0> here instead of link0, which causes the result to be empty.

Aside from that, attaching a label to it in a show rule sadly doesn’t work. The element is already “finalized” so to speak before the show rule runs. If you need to do it in a show rule, you can add the label to some metadata that you generate in the show rule instead.

1 Like

Querying for <test:link0> is intended since I want to query the links, not the targets.

Adding a metadata to the show rule produces results! The query works as intended and allows me to get the location of the metadata.

#let prefix = "test:"

#let generate(it) = {
  if type(it.dest) != label {
    return it
  }

  let target = str(it.dest)
  return [
    #it
    #metadata(target)
    #label(prefix + target)
  ]
}

#let get(target) = {
  query(label(prefix + str(target)))
}

#show link: generate

Target <link0>

#context get(<link0>).map(x => link(x.location())[backlink]).first()

#pagebreak()

#link(<link0>)[Link]

You say that the link element is already “finalized”, so adding a label doesn’t work. Does that mean that labels aren’t content elements? Are they just applied as a property to whatever comes before it?

That’s exactly right.

1 Like