How to make a custom referenceable "Lecture" element and include its date in the outline?

Hi guys!

If I record my lectures like dis:

#let lec(date, number) = [
  // <here are some if-checks to make sure the file has been included into a parent-file
  //  o.w. it uses different styling>
  = Lecture \##number
  Date: #date
]

Is it possible to

  1. make them referenceable, so the following code would work:
#lec("23.09.2024", 6) <lec6>
// ....
For that, see @lec6
  1. Somehow include the date of the lecture into the output of #outline()?

Thanks in advance!

Hello and welcome!

There are two things to remember about labels (directly quoted from Typst docs):

  1. Inserting a label into content attaches it to the closest preceding element that is not a space.
  2. The preceding element must be in the same scope as the label, which means that Hello #[<label>], for instance, wouldn’t work.

In your case, you cannot reference a sequence. You can automatically label your heading (which is a element), directly from lec. To include the date in the output, you simply need to add it to the heading’s body. The character \ is a linebreak.

You might also want to read the introduction post How to post in the Questions category, and rename your post title.

#let lec(date, number) = [
  // <here are some if-checks to make sure the file has been included into a parent-file
  //  o.w. it uses different styling>
  = Lecture \##number\ Date: #date 
  #label("lec" + str(number))
]
#set heading(numbering: "1.")


#lec("23.09.2024", 6)
// ....
For that, see @lec6.
Output

image

Thank you for a reply!

To include the date in the output, you simply need to add it to the heading’s body. The character \ is a linebreak.

That’s the thing. I don’t want to have date inside my heading. Rather, directly below it and it the usual font styling.

But in that case ig I can do smth like this:

#let lec(date, number) = [
  #heading(supplement: "Lecture")[Lecture \##number]
  #label("lec" + str(number))
  Date: #date
]

#lec("23.09.2024", 1)

See @lec1 

But then idk how to record the date to later display it inside #outline…
In any case, thanks for your input!

Also, regarding:

You might also want to read the introduction post How to post in the Questions category, and rename your post title.

If you have any suggestions regarding the title – please, let me know :innocent:

Ah! I see I misunderstood your question. You can probably do it using a show rule on outline.entry. I am using three things:

  • A metadata element to “hide” the date information
  • A query to retrieve the date
  • A show rule to modify the outline entry to add the date :smiley:

FYI, you can use an actual datetime type in Typst, which might help if you ever decide to change your date display format.

Code
#let lec(date, number) = [
  #let labtext = "lec" + str(number)
  #metadata(date)
  #label(labtext + "date")
  
  #heading(supplement: "Lecture")[Lecture \##number]
  #label(labtext)
  Date: #date
]
#show outline.entry: it => {
  context { 
    query(<lec1date>).first().value 
  }
  [ -- ]
  it
}
#set heading(numbering: "1.")

#lec("23.09.2024", 1)

See @lec1.

#outline()

Maybe something like: “How do I label content from a function? How do I include text in the outline, but not in the heading?”. Although, you can create multiple posts for multiple questions with no worries in the future!

Hi,
I also played a little around with it, just didn’t have time to post it. @quachpas solution provides all the building blocks, but it does not fully work for multiple lectures.

The metadata element can be queried without label. When building the outline the location of the current heading element can be used to query the associated (next) metadata element with this code:

#context query(
  selector(metadata).after(it.element.location())
).first().value

The complete example looks then like this

image

Code
#set heading(numbering: "1.1")

#let lec(date, number) = [
  #heading(supplement: "Lecture")[
    Lecture \##number
  ]
  #label("lec" + str(number))
  Date: #date
  #metadata(date)
]

#show outline.entry: it => {
  link(it.element.location(), it.body)
  " ("
  context query(
    selector(metadata).after(it.element.location())
  ).first().value
  ")"
  box(width: 1fr, it.fill)
  " "
  it.page
}

#outline()

#lec("23.09.2024", 1)

#lec("30.10.2024", 2)

See @lec1 and @lec2

Ah, my mistake! I didn’t realize I hardcoded the label in the show rule. Good thing you managed to write a solution that fits your needs!

Welcome to the forum @tea! I’ve updated your post title according to our guidelines: How to post in the Questions category. Make sure your title is a question you’d ask to a friend! :wink:

I think I would go with something like this:

#let date-format = auto

#{
  show outline.entry: it => {
    context {
      let next-head = query(selector(heading).after(it.element.location(), inclusive: false)).at(0, default: none)
      let sel = selector(<lecture-date>).after(it.element.location())
      if next-head != none {
        sel = sel.before(next-head.location())
      }
      let date = query(sel).at(0, default: none)
      if date != none {
        date.value.display(date-format)
      } else {
        text(fill: red, "Date missing")
      }
    }
    [ -- ]
    it
  }
  outline()
}

#let date(year, month, day) = {
  let dt = datetime(year: year, month: month, day: day)
  [#metadata(dt) <lecture-date>]
  [Date: ]
  dt.display(date-format)
}

which is then used like this (lectures without a date will show “Date missing” in the outline):

== Lecture 1
#date(2024, 9, 22)

== Lecture 2

== Lecture 3
#date(2024, 9, 27)

== Lecture 4

I attached a <lecture-date> label to the metadata elements (so that they don’t interfere with other metadata elements). I also put just the date into it’s own function so that headings keep their normal markup in the document. The outline entry looks for the <lecture-date> metadata element between the referenced heading and the next one, so that it does not accidentally show the wrong date if you forget to specify it.

Some observations (about typst)

  • I think in situations where you are likely referencing an element (like with the it in show outline.entry and show ref rules), it would be nice to have the referenced location available at it.ref() or similar, instead of it.element.location().
  • It would be nice to not have to special case so much. For the last heading in the document, there is no next-head. It would be great if there was some end-of-document element that one could use instead of none in the first query. With it, the if next-head != none test would become unnecessary.
  • There is a similar problem if there is no date. It would also be nice if to be able to program the entire happy path first, and only check for none at the end. (Similar to ? operators in many programming languages.) I run into things like this frequently.
  • I would like it if it were possible to write
    metadata(dt)
    label("lecture-date")
    
    instead of
    [#metadata(dt) <lecture-date>]
    
    in the date function. But alas, label does not produce content and therefore cannot be joined with metadata. (And
    metadata(dt)
    [<lecture-date>]
    
    does not work because of label scoping rules.)
1 Like

Of note, this doesn’t have much to do with the label syntax you’re using, it’s just that you need to be in markup mode to attach a label; they’re special-cased. That is, #{ figure(...) <lbl> } won’t work but #[#figure(...) <lbl>] will.

Typst 0.12 will have a more useful hint when this generates an error (see Improve error message for labels in code mode · Issue #4317 · typst/typst · GitHub).

Yeah, my point was more about having to switch to markup mode to attach labels. I shouldn’t have changed the label syntax between my examples. (I guess label("lecture-date") felt more code-ish to me.)