quarto-dev / quarto-cli

Open-source scientific and technical publishing system built on Pandoc.
https://quarto.org
Other
3.88k stars 318 forks source link

Custom callout boxes #844

Open tavareshugo opened 2 years ago

tavareshugo commented 2 years ago

Firstly, many thanks for the fantastic package you are developing!

I am following from @athulsudheesh's comment in #556, to request a new feature for custom callout boxes.

One use case I have in mind is for exercises in books or course materials, for example:

:::{.callout-exercise}

Exercise description.

:::{.callout-answer collapse="true"}
The answer to the exercise
:::

:::

I guess these would have to be defined somewhere following a template (on the front matter? or on a separate YAML, that is then referenced on the front-matter?).

callout: "exercise"
    title: "Exercise"
    color: "purple"
    icon: "fa-question"
    collapse: "false"
callout: "answer"
    title: "Answer"
    color: "green"
    icon: "fa-pen"
    collapse: "true"    

Alternatively maybe this could be done through a generic callout with a couple of extra options, such as:

:::{.callout-custom color="purple" icon="fa-question"}
## Exercise

A purple callout block with a question mark icon.
:::

The disadvantage here is that there is more typing involved.

mcanouil commented 1 year ago

@dragonstyle I just stumble upon this, is it not closed by "Quarto Callouts Custom Node API" added in 1.3 release (https://quarto.org/docs/prerelease/1.3/custom-ast-nodes/callout.html)?

cderv commented 1 year ago

I don't think one can create yet any arbitrary callout. The new AST node is for existing callout. @cscheid can confirm (or not)

cscheid commented 1 year ago

What @mcanouil is suggesting, I think, is for users to use a different div class (like .callout-custom, which we don't pick up on callout ourselves), but then write a Lua filter that does, and creates a callout programmatically. That should be possible with our current API. See callout_constructor.lua in tests/docs/smoke-all/2022/11/29/callout-constructor.qmd for a minimal example. We should properly document all of this, of course.

mcanouil commented 1 year ago

You read my mind @cscheid πŸ˜‰

cderv commented 1 year ago

πŸ‘Œ I felt that what I said was required an approval or correction πŸ˜…

I was under the impression it needed to be with a known type only, but if one can use any custom type I was indeed wrong. I remembered trying and something was not working (like icon or else)

Anyhow, quarto.Callout() in Lua is a good solution ! As @mcanouil said, probably issue closed then ?

cscheid commented 1 year ago

I think it's currently possible, but not easy. I want to leave this open in case we eventually design a more convenient fix.

tavareshugo commented 1 year ago

Hi all. Thanks for picking up on this. From a user perspective, who doesn't know Lua and is just trying to hack around (i.e. me), having explicit docs for this would indeed be great. I've played around a bit (including trying to make a custom Lua filter, but failed), and for now I've settled on specifying something like .callout-exercise, .callout-answer, .callout-hint and then style them up with some CSS. It works, but does seem like a workaround. Rendered example here if you're interested.

ute commented 1 year ago

I love the example, @tavareshugo!

Would be great if in some future the callouts could be cross referenced. I've made a Lua filter (custom-numbered-blocks) that shows what I mean.

cscheid commented 1 year ago

@ute we're tracking that here: https://github.com/quarto-dev/quarto-cli/issues/4863

ute commented 1 year ago

Just wanted to say: thank you for custom-AST-Nodes, @cscheid & all quarto-team. Very cool :-) I was somehow stuck in quarto 1.2..,

pglpm commented 1 year ago

@tavareshugo Thank you for sharing the rendered example from UoC, it looks great! I suppose the custom .callout-exercise doesn't render in pdf?

tavareshugo commented 1 year ago

@tavareshugo Thank you for sharing the rendered example from UoC, it looks great! I suppose the custom .callout-exercise doesn't render in pdf?

We haven't worked on a PDF template (yet), so they show as generic callout boxes without any styling applied (basically grey boxes, with no associated icons). I imagine it might be possible to customise these somehow, based on the quarto docs, but haven't looked at how this works in practice. If anyone has any tips on how to convert CSS styling to be applied to the PDF output, they are very welcome! :)

bsalehe commented 1 year ago

This seems to be working only with v1.2. not current version which I am using. As I like this feature, would downgrading to v1.2 be a possible workaround?? @cscheid

Thanks

mcanouil commented 1 year ago

This seems to be working only with v1.2

@bsalehe What "this" refers to exactly?

Currently and as stated there are no easy way to do it, thus the issue being opened. Some examples were shared by community members, but those are not made/supported by Quarto.

tavareshugo commented 1 year ago

@mcanouil, I believe @bsalehe was referring to our strategy of using ::: {.callout-exercise} for our custom "exercise" box. In v1.2 Quarto converted this to a generic box with class callout-exercise and so we could style it with CSS to make it appear as we wanted. But this doesn't seem to be the behaviour for v1.3 anymore. I guess an alternative is that we specify it as ::: {.callout .exercise} so it renders as the generic box but with an additional exercise class.

But the custom AST nodes feature seems more promising. Can you give us some clues of how to implement a custom callout with this feature?

I have looked at the example mentioned above, but I couldn't quite figure out how to make it work.

Click for details of my attempt Markdown: ```md --- format: html filters: - custom-callout.lua --- This box comes with Quarto and works fine: :::{.callout-tip} Native quarto callout. ::: This was intended to be our custom box, but it does not render as such: :::{.callout-exercise} Custom exercise callout content. ::: This should be the end of the document, but the custom box appears at the end. ``` The `custom-callout.lua` file: ```lua function Pandoc(doc) local c = quarto.Callout({ type = "exercise", content = { pandoc.Div(pandoc.Plain("This is an exercise")) }, title = "Exercise with Lua" }) doc.blocks:insert(c) return doc end ``` The rendered result: ![image](https://github.com/quarto-dev/quarto-cli/assets/10832356/d50ba115-4c56-4633-be7d-4a18228dcaa6)

Of course part of the issue is that I don't know how Lua filters work, and couldn't understand how to get there from the current docs, so I'm blindly trialing things out. πŸ™ˆ Namely, two issues at the moment:

If you have some quick suggestions, that would be great. Otherwise, we appreciate this may not be your highest priority and we can work around it.

cderv commented 1 year ago

@tavareshugo You need to learn a bit more about lua

This would help you understand the code you copied.

function Pandoc(doc) -- <- applies on the docment
  -- create a callout
  local c = quarto.Callout({ 
    type = "exercise",
    content = { pandoc.Div(pandoc.Plain("This is an exercise")) },
    title = "Exercise with Lua"
  })
  -- insert the callout at the end of the document
  doc.blocks:insert(c)
  -- return the whole document
  return doc
end

Here is how you could replace you div by the callout you created

function Div(div)
  if div.classes:includes("callout-exercise") then
    return quarto.Callout({
      type = "exercise",
      content = { pandoc.Div(pandoc.Plain("This is an exercise")) },
      title = "Exercise with Lua"
    })
  end
end

I think you can understand the difference. Now you could mimic part of the behavior of callout with more complex handling

---
format: html
filters:
  - custom-callout.lua
---

This box comes with Quarto and works fine:

:::{.callout-tip}

## This is the title

Native quarto callout.
:::

This was intended to be our custom box

:::{.callout-exercise}

## This is the title

Custom exercise callout content.
:::
function Div(div)
  -- process only specific div
  if div.classes:includes("callout-exercise") then
    -- default title
    local title = "Exercice"
    -- Use first element of div as title is this is a header
    if div.content[1] ~= nil and div.content[1].t == "Header" then
      title = div.content[1]
      div.content:remove(1)
    end
    -- return a callout instead of the Div
    return quarto.Callout({
      type = "exercise",
      content = div,
      title = title
    })
  end
end

Hopefully this give you enough example to build your own

tavareshugo commented 1 year ago

@cderv I just wanted to say a huge thank you again for your code. I've finally implemented it (here) and it worked, with only a couple of tweaks:

I really appreciate your input - the focus of our team is on course material development in the field of bioinformatics/statistics/machine learning, and while Python/R are staples for us, Pandoc/Lua development is beyond our current scope. But I'm really happy that we managed to get this one working with your help!

saforem2 commented 12 months ago

fwiw, I've been able to get custom callouts working with just a bit of CSS and inline styling for the custom {color, icon}

saforem2 commented 12 months ago

interestingly, this this seems broken (e.g. the icons fail to load when embedded in the callout title) in quarto-1.4.{357, 398, 415, 446, 448} but works in quarto-1.3.450

I tried playing around with it briefly, but couldn't quite get it working.

I'm guessing it would be best for me to create a new issue for this??

cscheid commented 12 months ago

I'm guessing it would be best for me to create a new issue for this??

Yes, pleas.e

nickriches commented 9 months ago

I'd also be keen to create my own bespoke callouts. It sounds like there is a workround, but this seems pretty complex.

coatless commented 7 hours ago

For those who stumble upon this, I've written up a quick {quarto-custom-callout} filter extension that allows you to define new callouts effortlessly from within the document's YAML.

For example, take the original ask, we could specify it as:

---
title: "Custom Callouts Rock!"
format: html
custom-callout:
  exercise:
    title: "Exercise"
    color: "purple"
    icon: "fa-question"
    collapse: "false"
  answer:
    title: "Answer"
    color: "green"
    icon: "fa-pen"
    collapse: "true"    
filters:
- custom-callout
---

From there, we could just use:

:::{.exercise}
This is an exercise callout.
:::

:::{.answer}
This is an answer callout.
:::

:::{.answer title="Custom Answer Title"}
This is an answer callout with a custom title.
:::

[!NOTE] Only color cannot be used as an attribute on the Callout itself.

Website: https://quarto.thecoatlessprofessor.com/custom-callout/ Repository: https://github.com/coatless-quarto/custom-callout