Is there a (possibly experimental) way to export to multiple html documents?

I am trying to generate many flashcards from one document.
I have a perfectly fine way to generate one interactive html flashcard (with collapsible answer, I use single side anki cards that auto flip/answer to the grading screen, making the collapsible html element act as the answering action) per file for importing into anki (I copy the generated html to clipboard with a vscode task triggered by a keybinding).

The problem is that this forces a one-to-one mapping of files to export targets/flashcards.

I’d prefer to generate one html document per #card[...] invocation.

Is there ongoing work for that?

I was thinking about a hack like having some kind of counter that counts the cards and having them be elements in the html with style rules with an integer

let index = {
  let index = sys.inputs.at("index", default: none)
  if not (type(index) == int and index >= 0) {
    panic("Invalid index")
  }
  index
}

interpolated into a style rule

.card:not(:nth-of-type(${index})) {
  display: none;
}

and then invoking the compiler for each index up to the first failure:

for index in {0..10000}; do
  if ! typst compile \
    --features html \
    --format html \
    --input "index=${index}" \
    ./cards.typ \
    "./card_${index}.typ";
  then
    break
  fi
done
1 Like

For posterity, using my current workaround, I am building the independent HTML flashcards like this:

mkdir --parents -- ./output
tailwindcss --input <(<<<'@import "tailwindcss";') --output './output.css'
local index=0
while true; do
  typst compile \
    --features html \
    --format html \
    --input "index=${index}" \
    ./main.typ \
    "./output/${index}.html" \
  || break
  ((index++))
done

and the main document ./main.typ looks like this:

#let card = {
  let card_counter = counter("card_counter")

  (..args) => context {
    let args = args.pos()
    if not args.len() in (1, 2) {
      panic("flashcards have one or two sides")
    }

    let front = args.at(0, default: none)
    let back = args.at(1, default: none)

    let index = {
      let index = sys.inputs.at("index", default: none)
      if index != none {
        int(index)
      }
    }

    context if index != none and not (0 <= index and index < card_counter.final().at(0)) {
      panic("Index " + str(index) + " out of range")
    }

    show math.equation: it => {
      show: math.display
      if target() == "html" {
        if it.block {
          html.div(
            class: "flex justify-center-safe min-w-full overflow-x-auto py-4",
            style: "scrollbar-width: thin;",
            html.figure(role: "math", html.frame(it)),
          )
        } else {
          html.span(role: "math", class: "inline-block", html.frame(it))
        }
      } else {
        it
      }
    }

    if target() == "html" {
      if index == none or index == card_counter.get().at(0) {
        html.main(class: "card mx-auto w-full max-w-[40rem] text-left flex-col p-2")[
          #html.div(class: "w-full rounded-xl border border-black bg-blue-100 p-4")[
            #front
          ]
          #if back != none {
            html.details(class: "group mt-4 w-full rounded-2xl border border-gray-200")[
              #html.summary(
                class: "h-12 w-full list-none cursor-pointer select-none rounded-xl border border-black bg-slate-200 hover:bg-slate-300 transition flex items-center justify-center gap-2",
              )[
                #html.span(
                  class: "inline-flex items-center justify-center w-10 h-10 text-2xl leading-none transition-transform duration-200 group-open:rotate-90",
                )[
                  ▶
                ]
                #html.span(class: "font-semibold text-lg")[
                  Details
                ]
              ]
              #html.div(class: "mt-2 w-full rounded-xl border border-black p-4")[
                #back
              ]
            ]
          }
        ]
      }
    } else {
      set page(height: auto, width: 40em, margin: 1em)
      set text(font: "DINish", stretch: 0%)
      set box(
        width: 100%,
        inset: 1em,
        radius: 0.5em,
        stroke: 1pt,
        fill: (if back == none { green } else { blue }).lighten(60%),
      )

      box(front)
      if back != none {
        v(0.8em, weak: true)
        back
      }
    }
    [#card_counter.step()]
  }
}

#context if target() == "html" {
  html.style(read("/output.css"))
}

#card[card 1 front][card 1 back]
#card[card 2 front][card 2 back]
#card[single side card]

This seems related to a question I asked about creating multiple PDF outputs from one call. It is possible to generate multiple PNG files from a single call to Typst, but it appears it’s not yet possible for PDF or HTML.