-
DescriptionI'm trying to create a 'Q&A' filter that would allow me to do some very basic manipulation of a tabset before Quarto renders the document 'as usual'. So the behaviour I'm looking to achieve is:
I have tried two different approaches to this:
For elegance I prefer option 2 (only change what you need to change when you need to change it) but I am struggling to get either approach to work:
So for the basic markdown:
So far I have the skeleton of a Lua filter (in print("Loaded!")
-- Detects the Tabset (yay) but I can't
-- tell if it's one of my 'special' Q&A tabsets
-- or a 'normal' one.
function Tabset(el)
print(">>>>>>>")
print("Tabset detected")
print("vvvvvvv")
for k, v in pairs(el) do
print('k: ' .. k)
end
print("^^^^^^^^")
-- el.attr seems to have the class information
-- but its a userdata type that I can't get useable
-- information from
print(el.attr)
print(el.attr[1]) -- was hoping for ['qna','panel-tabset']
print(type(el.attr)) -- but it's type userdata
print(getmetatable(el.attr)) -- this doesn't help
print('----')
-- and this seems to have no obvious order that relates
-- the source QMD file though there must be *some* link
print(el.__quarto_custom_node.content[1]) -- this is tab content
print(el.__quarto_custom_node.content[2]) -- this is tab title
print("<<<<<<<<")
end
-- Detects the Div (yay) but I can't
-- work out how to 'promote' the element
-- to a tabset if I *don't* want to manipulate it
function Div(el)
if el.classes:includes"qna" then
print(">>>>>>>")
print("Q&A detected")
if quarto.doc.is_format("html") then
-- need to create tabset here
-- return quarto.Tabset(???)
print(el.classes)
el.classes[1] = 'panel-tabset'
print(el.classes)
return el -- no effect on output
else -- Stub for PDF/IPYNB logic later
print("Not html")
return nil
end
print("<<<<<<<<")
end
end For reference, the output I get when rendering to HTML only is: pandoc
to: html
output-file: example.html
standalone: true
section-divs: true
html-math-method: mathjax
wrap: none
default-image-extension: png
metadata
document-css: false
link-citations: true
date-format: long
lang: en
title: Q&A Example
Loaded!
>>>>>>>
Q&A detected
List {qna}
List {panel-tabset}
>>>>>>>
Tabset detected
vvvvvvv
k: t
k: level
k: attr
k: __quarto_custom_node
^^^^^^^^
("",["qna","panel-tabset"],[])
nil
userdata
true
----
Div ("",[],[("__quarto_custom_scaffold","true")]) [CodeBlock ("",["python"],[]) "print(l[???])"]
Div ("",[],[("__quarto_custom_scaffold","true")]) [Plain [Str "Question"]]
<<<<<<<<
Output created: example.html |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 2 replies
-
You should never really be using There is a way to create tabsets in Lua, and that's using |
Beta Was this translation helpful? Give feedback.
-
Thank you, that was really helpful. I should note that I didn't want to use the I'm stuck on one last thing: I can't get FYI: I also had issues with finding an alternative to ---
title: "Q&A Example 2"
format:
html: default
pdf: default
ipynb: default
filters:
- qna.lua
- quarto
---
This filter changes the output from a panel depending on the output format:
- Rendering to `.ipynb` you will see the question code.
- Rendering to `.pdf` you will see the answer.
- Rendering to `.html` you will see both.
::: {.qna}
#### Question
How do you print out the last element of this list?
:::::: {.jupyter-python .code execution_count=0}
```python
l = range(1,5)
print(l[???])
```
:::::
```python {.code}
l = range(1,5)
print(l[???])
```
#### Answer
```{python}
l = range(1,5)
print(l[-1])
```
::: function Div(div)
if not div.classes:includes("qna") then
return
end
raw = {}
local div_num = 0
local header_level = 0
div:walk({
Block = function(block)
print('vvvv')
print("Unknown block of type " .. block.t)
if next(block.classes) ~= nil then
print("Has classes: ")
print(block.classes)
end
print("Has attributes: ")
for k,v in pairs(block.attributes) do
print(k)
print(v)
end
print('^^^^')
end,
Div = function(div)
if next(div.classes) ~= nil and (div.classes:includes('cell') or div.classes:includes('cell-output')) then
return {}
else
table.insert(raw[div_num].content, div)
end
end,
CodeBlock = function(code)
-- print(code.attr)
-- print(code.text)
local resource_attr = pandoc.Attr('', {'cell','code'}, {})
-- print(code.classes)
-- if not code.classes:includes('code') then
-- code.classes:insert('code')
-- end
-- print(code.classes)
table.insert(raw[div_num].content, pandoc.Div(code, resource_attr))
end,
Para = function(para)
table.insert(raw[div_num].content, para)
end,
BulletList = function(bl)
table.insert(raw[div_num].content, bl)
end,
OrderedList = function(ol)
table.insert(raw[div_num].content, ol)
end,
Plain = function(pl)
return pl -- table.insert(raw[div_num].content, pl)
end,
Header = function(header)
if header_level == 0 then
header_level = header.level
end
if header.level == header_level then
div_num = div_num + 1
raw[div_num] = {}
raw[div_num]['title'] = header.content
raw[div_num]['content'] = {}
else
table.insert(raw[div_num].content, header)
end
end
})
-- Note the critical assumption that the question
-- is always the first tab, the answer is always
-- the second. We remove the question *or* answer
-- for some output formats based on filter spec
if quarto.doc.is_format("ipynb") then
print("Writing Notebook!!!")
table.remove(raw,2)
elseif quarto.doc.is_format("pdf") then
print("Writing PDF!!!")
table.remove(raw,1)
end
if #raw == 1 then
local resources = {}
local resource_attr = pandoc.Attr('', {'qna-question'}, {})
resources[1] = pandoc.Header(header_level, raw[1].title, resource_attr)
resources[2] = pandoc.Div(raw[1].content, resource_attr)
return resources
else
tabs = {}
for i, t in pairs(raw) do
table.insert(tabs, quarto.Tab({title=t.title, content=t.content}) )
end
return quarto.Tabset({
level = 3,
tabs = pandoc.List( tabs ),
attr = pandoc.Attr("", {"panel-tabset", "qna-question"}) -- bug @cscheid mentioned.
})
end
end |
Beta Was this translation helpful? Give feedback.
-
I played with a similar idea that changed the output based on profiles present, c.f. |
Beta Was this translation helpful? Give feedback.
You should never really be using
__quarto_custom_node
; we thought the double underscores were scary enough to indicate this was for internal usage only!There is a way to create tabsets in Lua, and that's using
quarto.Tabset()
andquarto.Tab()
. These are intended to be used by filters, but we haven't documented them properly yet (it's coming!). We have a minimal example in our test suite at https://github.com/quarto-dev/quarto-cli/blob/main/tests/docs/smoke-all/2024/05/15/9691.qmd and https://github.com/quarto-dev/quarto-cli/blob/main/tests/docs/smoke-all/2024/05/15/topanel.lua